Vol. I  ·  No. 152 Established 2026  ·  AI-Generated Daily Free to Read  ·  Free to Print

The Trilogy Times

All the news that's fit to generate  —  AI • Business • Innovation
MONDAY, JUNE 01, 2026 Powered by Anthropic Claude  ·  Published on Klair Trilogy International © 2026
🖶 Download PDF 🖿 Print 📰 All Editions
Today's Edition

Korn Ferry Acquires Trilogy International as Alibaba's Accio Work Signals a New Era for Global Talent

Two moves in one week reframe what it means to hire across borders.

HONG KONG — The week handed the global talent industry two dispatches worth reading together. On one side of the Pacific, executive search and workforce consultancy Korn Ferry has acquired Trilogy International, the staffing firm that placed contract workers across technology and professional services. Terms were not disclosed. On the other, Alibaba International unveiled Accio Work, an enterprise AI agent designed to automate sourcing, negotiation, and supplier coordination for businesses operating across borders.

The acquisitions and the product launch are unrelated in origin. They rhyme in implication.

Korn Ferry has spent years repositioning itself from a search firm into a broader organizational consultancy — selling leadership strategy, pay benchmarking, and workforce transformation alongside the headhunting that built its name. Trilogy International's book of staffing contracts fills a gap in that architecture, extending Korn Ferry's reach into contingent and contract labor at a moment when companies are rethinking the shape of their workforces. The firm also announced this week the appointment of a new Chief People and Legal Officer, a signal that internal governance is keeping pace with external growth.

Meanwhile in Hangzhou, Alibaba's international commerce arm launched Accio Work — an AI agent that handles the operational grind of global business: finding suppliers, comparing terms, managing procurement workflows across languages and time zones. The product targets small and mid-size enterprises trying to act global without the infrastructure of a multinational. The pitch is familiar — AI as the great equalizer — but Alibaba's supply chain data gives it a substrate that most competitors lack.

The juxtaposition is instructive. Korn Ferry is betting that human judgment, wrapped in institutional scale, still commands a premium when the stakes are high. Alibaba is betting that for the vast middle of global commerce, an agent will do.

Both bets could win. The question is which market is larger — and how quickly the line between them moves.

Alibaba International Launches Accio Work, an Enterprise AI  ·  Korn Ferry Appoints Chief People and Legal Officer - Hunt Sc  ·  Korn Ferry is new owner of Trilogy International - Staffing

Anthropic Hits $900 Billion Valuation, Leapfrogs OpenAI in the Race to Own AI's Future

A $65 billion fundraise reshuffles the AI hierarchy — but the real story is what these numbers reveal about where the money is going.

SAN FRANCISCO — Anthropic has closed a $65 billion funding round that values the company at $900 billion, surpassing OpenAI's last reported valuation of $730 billion and making it, by that measure, the most valuable AI startup on the planet. The milestone marks a significant inflection point in the two-year sprint between the two companies that have defined the generative AI era.

The numbers require context. Valuations at this scale are negotiated artifacts — products of term sheets, liquidation preferences, and investor appetite — not market prices. OpenAI's $730 billion figure dates to its last round; a fresh raise could close the gap overnight. What the Anthropic figure does confirm is that institutional capital has not cooled on frontier AI despite mounting questions about monetization timelines and compute costs.

The broader funding landscape tells a more complicated story. Boston-area startups posted fundraising numbers that look robust until you adjust the lens: by pre-AI benchmarks, the figures appear strong, but capital is concentrating at the top of the stack — foundation model labs and infrastructure plays — while mid-tier software startups compete for a shrinking share of attention. The AI era is not lifting all boats.

Meanwhile, the technology is diffusing into everyday transactions in ways that carry real risk. AI-powered fraud has matured to the point where voice cloning, synthetic storefronts, and deepfake impersonations of family members are no longer edge cases — they are documented, scalable criminal operations. Security researchers now recommend treating any unsolicited financial request, regardless of apparent source, as suspect until verified through a second channel.

On the consumer side, AI is also entering high-stakes personal decisions. One homeowner's five-day experiment using AI tools in lieu of a real estate agent — betting family savings on the outcome — illustrates both the genuine capability and the uneven reliability of current systems when the margin for error is zero.

The $900 billion valuation and the AI-assisted home sale exist on the same continuum: a technology moving fast enough that the institutions built around it — brokerages, banks, fraud prevention systems — are visibly struggling to keep pace.

I Tried to Sell My House With A.I.  ·  Anthropic Tops OpenAI to Become the World’s Most Valuable A.  ·  A.I. Is Making Scams Hard to Spot. Here’s How to Protect You

Nvidia Takes the Snap Under Center in Intel’s Home Stadium

The AI chip champion is calling a 2026 PC blitz, and Wall Street is already handicapping who runs up the score.

SANTA CLARA, CALIFORNIA — We are HERE, folks, at the edge of the silicon gridiron, and Nvidia is not lining up for a polite handshake. It is taking the snap, rolling right, and throwing deep into Intel territory.

The latest market chatter has Nvidia’s coming PC chips landing in Dell and Lenovo laptops and desktops starting in fall 2026, running Microsoft Windows and targeting thin-and-light machines built to handle large AI models and high-end gaming. That is not a gadget play. That is a frontal assault on the mainstream PC fortress Intel has defended for decades.

According to 24/7 Wall St., the new Nvidia push could create a winner’s bracket that stretches beyond Nvidia itself, with Dell, Lenovo-linked suppliers, Microsoft, Arm ecosystem players, and memory or component names all positioned around the next AI-PC cycle. Translation: this is not one runner breaking free. This is an entire offensive line moving downfield.

The stakes are enormous. Nvidia already owns the data-center highlight reel, where its GPUs have become the de facto picks and shovels of the generative AI boom. But PCs are different. They are volume. They are distribution. They are the old league, the packed stadium, the place where Intel built a dynasty around the x86 playbook and the “Intel Inside” chant.

AND NOW NVIDIA IS GOING FOR IT ON FOURTH DOWN.

The bull case is straightforward: if AI workloads move from cloud-only to hybrid and local devices, the PC becomes a new battleground for inference, gaming, productivity agents, and on-device models. Microsoft wants Windows to be the operating system of the AI desktop. Dell and Lenovo want premium refresh cycles. Nvidia wants the silicon toll booth.

That is why some investors still see Nvidia as a buy even after its monster run. The Motley Fool argued that now may be the perfect time to buy Nvidia stock, pointing to long-term AI demand rather than short-term scoreboard fatigue.

The risk? Intel will not simply wave the white flag, AMD is still in the playoff picture, and PC refresh cycles can be choppy. But the formation has changed. Nvidia is no longer just the superstar of the AI data center.

It wants the laptop on your desk. The tower under your monitor. The next Windows machine in the corporate fleet.

Folks, that is not a product launch. That is a road game in enemy territory — and Nvidia just brought the marching band.

Meta Insider Sale Fails to Shake Bullish View  ·  Top 5 Stocks That Will Profit From Nvidia’s PC Market Invasi  ·  3 Reasons Why Now is the Perfect Time to Buy Nvidia Stock
Haiku of the Day  ·  Claude HaikuGiants buy giants fast
while machines learn to break rules—
we built our own gods
The New Yorker Style  ·  Art Desk
The New Yorker Style  ·  Art Desk
The Far Side Style  ·  Art Desk
The Far Side Style  ·  Art Desk
News in Brief
Meta Eyes the Cloud as AI’s Great Compute Migration Begins
MENLO PARK, CALIFORNIA — In the dim blue glow of the modern data center, where racks breathe warm air like sleeping beasts, a new movement stirs among the giants. Mark Zuckerberg, keeper of Meta’s vast social savannah, has suggested that a Meta cloud computing business is “definitely on the table,” according to CNBC.
The Deepfake Doctor Will See You Now — And She Doesn't Exist
WASHINGTON, D.C.
WE ARE LIVING IN THE DUMBEST GOLDEN AGE IN HUMAN HISTORY
AUSTIN, TEXAS — Let me set the scene for you.
The Office Is Not Coming Back, and Neither Is the Old Bargain
AUSTIN, TEXAS — I'll be honest: the remote work debate has officially become the corporate version of arguing whether email is a fad. Unpopular opinion: if your 2026 workforce strategy still begins with “how many days should people be in the office,” you are not doing strategy — you are doing facilities management with better fonts.
Opinion: Nation’s Consumers Bravely Ask If Product That Costs Nothing And Promises Everything Might Have Minor Drawback
NEW YORK — At a time when trust in institutions has fallen to historic lows, it is heartening to see the American consumer still willing to believe that an international airline can fly them across the Atlantic for the price of a moderately ambitious DoorDash order, that a candy called Boner Bears contains only the innocent spirit of entrepreneurship, and that a language-learning app’s best strategic asset might not be the deranged green owl currently holding the nation’s notifications hostage. This is the marketplace functioning exactly as its designers intended: a vast, frictionless cathedral in which every human desire has been compressed into a checkout flow and every warning sign has been renamed “user experience.” Consider Norse Atlantic Airways, the budget carrier whose dirt-cheap fares have reportedly been accompanied by a tech-first customer service operation that some passengers say left them stranded, confused, or financially lighter by thousands of dollars.
A Trilogy Company
Crossover
The world's top 1% remote talent, rigorously tested and ready to ship.
A Trilogy Company
Alpha School
AI-powered learning. Two hours a day. Academic results that defy belief.
A Trilogy Company
Skyvera
Next-generation telecom software — built for the networks of tomorrow.
A Trilogy Company
Klair
Your AI-first operating system. Every workflow. Every team. One platform.
A Trilogy Company
Trilogy
We buy good software businesses and turn them into great ones — with AI.
The Builder Desk  —  AI Builder Team
📅 Week in ReviewProduction Release

Builder Team Ships Across Six Repos in a Week for the Ages

From a full auth migration to a live fiscal-year-end forecaster, the Builder Team rewired core infrastructure, launched new analytics, and pushed the product forward on every front simultaneously.

There are weeks where a team maintains. And then there are weeks like this one — where a team builds. The Builder Team closed out seven days that touched six separate repositories, shipped a production auth migration, launched an entirely new AI Renewals analytics section, extended the fiscal-year forecasting horizon on ARR Gap, and quietly laid the groundwork for a triage agent that could change how this org operates. This wasn't a sprint. This was a statement.

The single biggest structural move of the week came out of Sindri, where @mwrshah executed a full authentication provider migration — ripping out Clerk and replacing it with WorkOS AuthKit end-to-end. New route handlers, new JWT issuers, new middleware, a dev-loop normalization fix for redirect-URI mismatches — this is the kind of foundational surgery that makes future features possible and makes engineers sleep better at night. It shipped. It's done. The era of WorkOS begins.

On the analytics front, @sanketghia had the week of his career. He delivered the entire AI Renewals section of the Renewals dashboard — a brand-new tab comparing Fionn-handled AI renewals against the traditional Trilogy process, cohorted correctly to the canonical 42-resolved Fionn-handled set after catching and fixing a critical cohort misalignment that had been inflating the audit pool to 280. He then extended /arr-gap with a full fiscal-year-end horizon toggle so the BU can now simulate DM forecasts all the way to December 31st — reconciled to the dollar with the standalone DM Forecasting Tool. He also extended the Surtr renewals pipeline with multi-currency USD normalization fields, ensuring the Fionn-vs-Trilogy comparison is apples-to-apples across 37% non-USD cohort rows. Three interconnected systems. One engineer. One week.

The Monthly Financial Reporting front was @eric-tril's domain, and he held it with characteristic precision. Cash Flow Manual Entry got a full Layer 1 and Adjustments-to-Net-Income buildout, with edits now propagating live without a reload. The Education vertical P&L tables expanded from budget-only drill-downs to full account-level drill-downs on every Actual/Budget/Delta cell, backed by two new GET endpoints. Note 8 Other Expense tables gained YTD columns from Q2 onward. The EBITDA Acquisitions fallback to manual-entry was patched. And the MFR backend services were reorganized into a clean folder structure — the kind of housekeeping that pays dividends for months. @eric-tril didn't ship features this week. He shipped a product.

Over in Surtr, @ashwanth1109 hunted down a silent killer: the Notion RCA Hub sync had been reporting SUCCESS while failing 536 out of 536 enrichment records, because three nested try/except blocks were swallowing a missing Anthropic API key without a whisper of complaint. He wired the key, added a catastrophic-loss guard, and made the pipeline fail loudly when it should. Meanwhile, @kevalshahtrilogy laid the first two stones of what may become one of the most consequential infrastructure projects of the season — a triage agent in Surtr, complete with dispatcher Lambda, stub workflow, and an observer fan-out that now posts Google Chat alerts on CRITICAL and WARN observations. He also fixed the QuickBooks core-tables Lambda timeout and patched the Bedrock token metrics pipeline. The Surtr engine room never stops.

In Aerie and Rhodes, @YibinLongTrilogy threaded Phase 2 projected dates through the portfolio and FTO views, stood up a full site security profile schema with MCP tools and Aerie sync, and added logo as a recognized document type across the entire Rhodes taxonomy — a cross-repo, cross-agent change that required touching Convex validators, Google Drive routing, agent prompts, and the web app label map simultaneously. @benji-bizzell, meanwhile, kept the Aerie surface polished: expandable tool call inputs and outputs in chat, per-status cancelled/paused toggles in the portfolio, and a fix for cleared edit fields that had been silently losing user input.

And then there is the matter of marcusdAIy, who — and I want to be precise here, because precision is apparently his thing — shipped a substantial volume of pull requests this week across Klair and trilogy-drones. The batch finding-addressal feature (B7.10) lets GMs select multiple review findings and address them with Coach Claire in a single queued operation. Claire's chat prompt now injects the full CheckSpec registry so she can cite check standards verbatim instead of paraphrasing. The MCP integration (B5.7/B5.8) connects Budget Bot's Coach Claire to all 39 live Klair data tools inline during a chat turn.

"Mac keeps calling my work underwhelming," marcusdAIy told this reporter when reached for comment. "Meanwhile I shipped batch addressal, full MCP tool access for Claire, verbatim check citation, financial table pre-population, and four B9 spec migrations this week alone. I'd define that differently, but I understand reading comprehension isn't a prerequisite for the beat desk."

Sure, Marcus. The B9 spec migrations are noted. They're refactors.

With the WorkOS migration live, the AI Renewals dashboard in production, the fiscal-year-end forecaster shipped, and the triage agent framework taking shape in Surtr, next week sets up as the moment this team starts building on top of everything it just laid down.

Mac's Picks — Key PRs This Week  (click to expand)
#107 — AERIE-317: Add logo as a document type @YibinLongTrilogy  no labels

## Summary

Adds logo as a recognized document type across the Rhodes document taxonomy so logo / brand-mark assets can be registered against a site through the same flow as every other document. This is the Rhodes-side half of the change — the Aerie chat agent surfaces it in tool schemas and the read card. The new type is wired through the Convex validator, the shared taxonomy, content-signal detection, domain-knowledge prompts, Google Drive folder routing, MCP instructions, the web app label map, and the agent-package shared docs that are bundled into agent prompts.

### Changes

Schema & taxonomy

- convex/schema.ts — Added v.literal("logo") to docTypeValidator so logo documents are accepted by Convex.

- lib/document-taxonomy.ts — Added logo to the canonical DOC_TYPES array and a "Logo" label entry in DOC_TYPE_LABELS_BY_KEY.

- web/src/lib/document-taxonomy.ts — Mirrored the "Logo" label in the web app's DOC_TYPE_LABELS map.

Detection & routing

- lib/content-signals.ts — Registered logo in DOCTYPE_KEYWORDS with empty patterns and milestone: null (no auto-detection / no milestone gating — logos are uploaded ad-hoc).

- lib/google-drive/folders.ts — Routed logo to 99_Working in DOC_TYPE_TO_FOLDER_PATH, alongside other.

Prompts & docs

- lib/domain-knowledge.ts — Inserted logo (#20) into the numbered document-type list in the domain knowledge prompt, shifting downstream entries.

- lib/mcp-instructions.ts — Added logo to the 99_Working row of the doc-type → folder table shown to MCP clients.

- agent-packages/shared/domain-knowledge.md — Same numbered-list insertion as lib/domain-knowledge.ts (this file is bundled into agent prompts).

- agent-packages/shared/tool-reference.md — Added logo to the inline doc-types enumeration in the Documents section.

### Design Decisions

- Empty patterns + null milestone in content-signals.ts — Logos don't have reliable keyword tells and aren't gated to a milestone, so auto-detection is deliberately a no-op; the type still needs to be registered to satisfy the Record<DocType, …> exhaustiveness.

- Routed to 99_Working — Logos are brand assets rather than a regulated document, so they share the catch-all bucket with other rather than getting their own folder.

## Test Plan

- [x] Pre-commit hooks (typecheck, lint) ran clean on the single commit

- [ ] Register a document with docType: "logo" via the Rhodes MCP and confirm it lands in 99_Working on Drive

- [ ] Confirm the web app renders the "Logo" label wherever doc-type labels are shown

- [ ] Confirm the assistant accepts and proposes logo in updateSiteMetadata / registerDocument flows

#110 — feat(triage-agent): PR 2 — dispatcher Lambda + stub workflow @kevalshahtrilogy  no labels

## Summary

PR 2 of pipeline-triage-agent. Stacked on top of #109 (PR 1) — review after that lands, or set base = main once #109 is merged.

Adds the dispatcher Lambda that subscribes to both SNS topics, runs the full dedupe ladder, and triggers a workflow_dispatch on a new stub triage-agent.yml. The stub workflow opens a labeled Issue with the raw dispatch inputs — enough to prove the end-to-end SNS → Lambda → GitHub path works without yet invoking the real agent (that's PR 3).

Self-containment: both kill switches default OFF (TRIAGE_DISPATCHER_ENABLED env var on the Lambda; TRIAGE_AGENT_RUN_ENABLED repo variable on the workflow). Deploying this PR produces zero Issues, zero GH calls, zero S3 writes, zero model spend.

## What this adds

### Lambda (pipelines/cdk/lambdas/triage-agent-dispatcher/)

- src/handler.py — full dispatcher logic. Honors kill switch, normalizes both event kinds, computes signature, runs DDB dedupe (open → comment, closed → regression, miss → dispatch, ≥7d → treat as miss per D16), enforces hourly rate limit (D10), snapshots run record + last 200 CW log lines to S3, presigns a 10-min GET URL, POSTs workflow_dispatch. Per-record exception swallow so one bad message doesn't poison a batch. ~430 lines.

- src/signature.py — Python port of Surtr/src/lib/triage/signature.ts with the same regex set and ordering.

- src/requirements.txtboto3>=1.37.0 (per the CLAUDE.md bundling rule).

### Tests (pipelines/cdk/lambdas/tests/)

- test_triage_dispatcher_signature.py — 29 tests including cross-language parity checks that lock the sha256 input format against silent drift between the TS and Python implementations.

- test_triage_dispatcher_handler.py — 13 tests covering: kill switch, routing/unknown payloads, all four dedupe branches (miss / open / closed / 7d-stale), rate limit short-circuit, observer-finding severity sort, batch-with-one-bad-record.

### CDK (pipelines/cdk/lib/pipeline-shared-stack.ts)

- PythonFunction for the dispatcher (Python 3.11, 60 s, 256 MB).

- New S3 bucket surtr-triage-context-{env}-{account} — block-public, S3-managed encryption, SSL-enforced, 30-day lifecycle, RemovalPolicy.RETAIN.

- IAM: scoped to what's strictly needed — DDB R/W on the triage table only, S3 PutObject + GetObject on the new bucket only, Secrets Manager read on SURTR_PROD_KEYS only, CW Logs FilterLogEvents scoped to /klair/pipelines/{env}/*. No iam:*, no lambda:UpdateFunctionCode, no wildcard secret access.

- SNS subscriptions to both topics with notification_type filter policies so each topic only delivers the events it should.

- All flags default to "off"/"false" so the deployed Lambda is inert.

### Workflow (.github/workflows/triage-agent.yml)

- Stub workflow with typed workflow_dispatch inputs matching the dispatcher payload.

- Kill switch via repo variable TRIAGE_AGENT_RUN_ENABLED (default missing/false).

- All inputs flow through env: blocks, never inline ${{ }} inside run: — mitigates the Comment-and-Control prompt-injection class per D21.

- Masks the pre-signed URL with ::add-mask:: since the signed-key suffix would otherwise leak in logs.

- Opens an Issue labeled agent-triage + agent-triage:<pipeline_id> + stub with the raw inputs. No agent, no PR. PR 3 replaces the body with the real diagnose-then-fix flow.

- concurrency: triage-agent-${{ inputs.signature }} so simultaneous dispatches for the same signature don't open duplicates (defense-in-depth on top of DDB dedupe).

## Test plan

- [x] python3 -m pytest tests/test_triage_dispatcher_signature.py tests/test_triage_dispatcher_handler.py42/42 pass

- [x] Cross-language parity locked: failure_signature("p", "KeyError: 'tenant_id'") produces the same hex in both Python and TS

- [x] cd pipelines/cdk && npm run build — clean tsc

- [x] YAML parses (python3 -c "yaml.safe_load(...)")

- [ ] CI synth on a docker-enabled runner

- [ ] After merge, in dev: set SURTR_TRIAGE_TOPIC_ARN on the Surtr ECS task + TRIAGE_DISPATCHER_ENABLED=true on the Lambda + TRIAGE_AGENT_RUN_ENABLED=true repo var → trigger a real failure → confirm a stub Issue appears.

## Production-deploy effect on merge

- New empty S3 bucket (no objects)

- New Lambda subscribed to both SNS topics, but TRIAGE_DISPATCHER_ENABLED=false → returns immediately on every event

- New workflow file, kill switch off → never proceeds past the first step

## Rollout (after merge)

| Step | Flag | Effect |

|---|---|---|

| 1 | SURTR_TRIAGE_TOPIC_ARN on Surtr ECS (PR 1) | Observer publishes to SNS |

| 2 | TRIAGE_DISPATCHER_ENABLED=true on dispatcher Lambda | Lambda processes events, snapshots context, calls workflow_dispatch |

| 3 | TRIAGE_AGENT_RUN_ENABLED=true repo var | Stub workflow opens Issues |

Reverse the order to disable.

## Out of scope (covered by later PRs)

- Real agent invocation (Claude Code + Codex), AGENTS.md, fix-stage with path allowlist + test gate — PR 3

- GChat outcome card + 15-min reconciler + optional Surtr UI tile — PR 4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#121 — fix(notion-rca-hub-sync): wire Anthropic API key and fail loudly on total enrichment loss (SURTR-29) @ashwanth1109  no labels

## Summary

- Root cause (auth): enrichment.py constructed Anthropic() with no auth wiring — no ANTHROPIC_API_KEY env var, no Secrets Manager fetch — so every Haiku call in the last run failed with *"Could not resolve authentication method. Expected either api_key or auth_token to be set"*. 536/536 records failed.

- Why it reported SUCCESS: Three nested try/except wrappers (_enrich_one_enrich_allhandler.py) all caught the auth error and continued. The handler's # base table unaffected swallow couldn't distinguish "a few transient failures" from "100% wipeout."

- Fix 1 — wire the key: Add ANTHROPIC_API_KEY_SECRET_ARN env var + IAM secretsmanager:GetSecretValue in pipeline.json, reusing the existing klair/anthropic-api-key-yFl13y secret that netsuite-gl-detail already consumes. New get_anthropic_api_key() helper in aws_secrets.py (env var first for local dev, then Secrets Manager). RCAEnricher.__init__ now passes api_key= explicitly.

- Fix 2 — fail loudly: In handler.py, raise RuntimeError when len(documents) > 0 and enriched == 0. The exception propagates out of main.py, ECS exits non-zero, the run is marked FAILED, and alerting fires. Partial failures stay non-fatal — the base table is still useful and a few transient API errors shouldn't page anyone.

## Test plan

- [x] pytest tests/70 passed (7 new):

- test_aws_secrets.py: get_anthropic_api_key env-var path, Secrets Manager path, env precedence, missing config raises, empty secret raises

- test_enrichment.py: Anthropic constructed with api_key= arg

- test_handler.py: raises on total failure, raises on enricher-constructor blowup (missing key), partial failure does NOT raise, empty DB does NOT raise

- [x] ruff check + ruff format clean on all modified files

- [x] pipeline.json validates as JSON

- [x] After merge & deploy: trigger the pipeline manually and confirm Haiku enrichment loads rows into mart_other.rca_incidents_enrichedsee Demo below

## Demo — end-to-end verification on prod

Deployed this PR's branch directly to prod via cdk deploy Pipeline-notion-rca-hub-sync-prod --exclusively and ran a test-mode execution (50 pages).

### 1. Deploy landed cleanly

- Stack: Pipeline-notion-rca-hub-sync-prodUPDATE_COMPLETE (4/6 resources changed)

- Task definition: new revision 7 registered, old revision deregistered

- Image: cdk-hnb659fds-container-assets-...:11c02030... (PR's fix code)

- Env var added: ANTHROPIC_API_KEY_SECRET_ARN=arn:aws:secretsmanager:us-east-1:479395885256:secret:klair/anthropic-api-key-yFl13y

- IAM updated: task role's inline policy now allows secretsmanager:GetSecretValue on both the existing Notion secret and the new Anthropic secret

### 2. Step Function ran green

Execution: pipeline-notion-rca-hub-sync-prod:5fd4b901-a685-489f-b589-4a51010e242b

Input: {"run_id":"manual-test-...", "params":{"test_mode":true}}

Status: SUCCEEDED

Duration: 1m 40s (ECS task: 34s)

### 3. CloudWatch logs prove the new auth path executed

Key lines from log stream pipeline/pipeline-notion-rca-hub-sync/f5d981e63c39465daea2f09d9d90fa7b:

10:37:58  root         TEST MODE active: limited to 50 pages

10:38:07 redshift_loader Loaded 50 rows into mart_other.rca_incidents

10:38:07 aws_secrets Retrieving Anthropic API key from secret:

arn:aws:secretsmanager:us-east-1:479395885256:secret:klair/anthropic-api-key-yFl13y

10:38:14 httpx POST https://api.anthropic.com/v1/messages "HTTP/1.1 200 OK"

... (× 50 — every Haiku call returned 200)

10:38:26 enrichment Loaded 50 rows into mart_other.rca_incidents_enriched

10:38:26 enrichment Enrichment complete: 50 enriched, 0 failed, 50 loaded

10:38:26 root Pipeline completed: 50 transformed, 50 loaded to Redshift, 50 enriched, 33688ms

Final summary emitted to Step Functions:

{

"run_id": "...",

"pages_fetched": 537,

"pages_transformed": 50,

"redshift_rows_loaded": 50,

"enrichment": {"enriched": 50, "failed": 0, "loaded": 50, "errors": []},

"errors": []

}

Contrast with the failing run that triggered this PR:

- Before: "Could not resolve authentication method..." × 536, {"enriched": 0, "failed": 536, "loaded": 0} — and yet pipeline reported SUCCESS

- After: 50/50 Haiku calls returned 200 OK, full enrichment

### 4. Redshift confirms rows landed

SELECT COUNT(*),

COUNT(DISTINCT notion_page_id),

SUM(CASE WHEN severity IS NOT NULL THEN 1 ELSE 0 END),

SUM(CASE WHEN root_cause_category IS NOT NULL THEN 1 ELSE 0 END),

SUM(CASE WHEN incident_start_date IS NOT NULL THEN 1 ELSE 0 END),

SUM(CASE WHEN one_line_summary IS NOT NULL THEN 1 ELSE 0 END)

FROM mart_other.rca_incidents_enriched;

| total_rows | distinct_pages | with_severity | with_root_cause | with_start_date | with_summary |

|------------|----------------|---------------|-----------------|-----------------|--------------|

| 50 | 50 | 50 | 50 | 50 | 50 |

Sample rows (Haiku extractions look sensible):

| doc_name | aws_class | severity | root_cause | start_date | summary |

|---|---|---|---|---|---|

| Khoros Service Issue LIA-23857 | Khoros Product | critical | database | 2026-04-27 | CDN cookie caching and unbounded gallery queries saturated Aurora cluster |

| RCA for Khoros LIA-23911 | Khoros Product | high | configuration | 2026-04-28 | Faulty SVN config include order removed theme plugin, breaking page rendering |

| RCA for Khoros AURORA-1211 | Khoros Product | critical | capacity | 2026-04-24 | Insufficient Aurora enduser capacity caused 10-hour outage with 503 errors |

| RCA for Khoros LIA-23520 | Khoros Product | critical | capacity | 2026-04-23 | Sephora login surge saturated shared Aurora writer, causing ServiceNow 503 |

| RCA for Kandy KANDY-787 | Kandy Product | critical | infrastructure | 2026-04-22 | DRBD/NFS storage gaps during failover caused 2h17m call-recording playback outage |

The scheduled cron (cron(0 6 * * ? *)) will pick up the full 537-page set on its next run.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#2911 — feat(mfr): Cash Flow Manual Entry — Layer 1, Adj-to-NI & BS-mirrored cash-flow drill-downs @eric-tril  no labels

## Summary

Cash Flow Manual Entry for the Monthly Financial Reporting screen lets Finance enter cash-flow values per period (Silo / Layer 1 + Adjustment columns) until they can be properly sourced, feeding the Memos, Financial Statements, and Google Doc export. This PR delivers the Layer 1 + Adjustments-to-Net-Income work plus a round of correctness and consistency fixes.

## What's included

Bug fix — edits now propagate

- Manual-entry edits refresh the Financial Statements / Memo cash-flow tables without a reload (save → nonce → refetch signal), with a strongly-consistent DynamoDB read so the post-save refetch can't return a pre-save value.

Backend consistency

- The "Adjustments to Net Income" drill-down headline now ties to the value actually stored in the memo (derived from post-override section subtotals), removing a target_cfo divergence. Dropped the now-dead _compute_target_cfo / _build_target_cfo_rows and orphaned row-key constants.

Cash-flow drill-downs mirror the Balance Sheet

- AR, Prepaid, and AP working-capital drill-downs are now data-driven (no hardcoded expected-account lists), so they match the Balance Sheet account-for-account — plus the AR 64141 / 67120 IS adjustments.

- Reclassified 13750 (AP Cleanup) → Prepaid and Ramp Card accounts → Other current liabilities so the cash flow matches the BS classification.

- Retired the vestigial EBITDA-stack row keys (op:adj-*) with a tolerant read so legacy stored DynamoDB items still load.

New — prior-year manual entry

- A Current / Prior Year toggle above the table lets Finance adjust the prior-year period, which isn't reachable via the prod period floor. Edits write to that period and feed the memo's prior-year column. The frontend priorYearPeriod mirrors the backend prior_year_period (including the leap-day clamp) so the edited period exactly matches the one the memo reads back.

Cleanup

- Removed dead exports/constants, fixed stale comments, and trimmed a trivial test.

## Test plan

- Backend: pytest tests/mfr/uploads/ tests/mfr/financial_statements/test_cash_flow_service.py; ruff + pyright clean.

- Frontend: pnpm tsc --noEmit, pnpm test (MFR specs), pnpm build.

- Manual: edit a value → confirm it updates on the Financial Statements, the Memos, and the exported Google Doc without a reload; toggle to Prior Year and confirm prior-year edits feed the memo's prior-year column; confirm the AP / Prepaid cash-flow drill-downs match the Balance Sheet drill-downs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

http://localhost:3001/monthly-financial-reporting

https://github.com/user-attachments/assets/83082960-2070-4acf-a198-342c47fba90f

#2921 — AI Renewals analytics section in the Renewals dashboard @sanketghia  no labels

Closes KLAIR-2798.

## Summary

New AI Renewals tab inside /renewals comparing retention for renewals handled by AI Renewals (the "Fionn" Salesforce instance) against the central Traditional renewals process (the "Trilogy" instance), for low-ARR renewals in the current period.

## Backend (klair-api/renewals/fionn_handling.py)

- New endpoints under /renewals/fionn-handling, querying the two Salesforce SSOT tables directly (not the renewal mart).

- Cohort: Closed Won/Lost, current YTD renewal window, current ARR ≤ $100K (USD).

- USD-normalised ARR throughout (multi-currency safe); Business Unit canonicalised via core_finance.bu_class_registry.

## Frontend (klair-client/src/screens/RenewalsShell/FionnHandling/)

- Retention-first layout: headline KPIs → AI Renewals vs Traditional comparison (+ same-period-last-year YoY column) → per-BU / per-Product breakdown.

- "AI Renewals" / "Traditional" naming.

- Tab hides the filter sidebar + view-mode toggle (no filters apply); summary is session-cached so switching tabs doesn't re-fetch.

## Data dependencies (already in prod via Surtr)

- [Surtr PR #98](https://github.com/AI-Builder-Team/Surtr/pull/98) — current_arr__c, arr__c, business_unit__c, product__c on ssot_sf_trilogy_opportunity.

- [Surtr PR #119](https://github.com/AI-Builder-Team/Surtr/pull/119) — currency_iso_code__c + *_in_usd__c ARR columns.

## Out of scope (V1)

Configurable filters; audit / methodology / ARR-flow panels (removed from UI after review — backend still computes the data; documented in code for easy re-enable).

## Testing

Backend: 32 unit tests pass (pyright + ruff clean). Frontend: vitest green, tsc + eslint clean. Live smoke against Redshift verified cohort counts + mass-balance identities.

## Screenshots

<img width="1423" height="900" alt="image" src="https://github.com/user-attachments/assets/5da925fe-5f0a-441b-a128-800608cab1fe" />

<img width="1513" height="528" alt="image" src="https://github.com/user-attachments/assets/e38c51ef-8476-4945-9323-15d807ab05aa" />

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#2922 — feat(arr-gap): full fiscal-year-end (12/31) DM forecast on /arr-gap [KLAIR-2799] @sanketghia  no labels

## Summary

Adds a Q2 ⟷ Fiscal-Year-End (12/31) horizon toggle to /arr-gap, so the BU can see and simulate where DM (Dollar Maintenance) lands at fiscal year-end — not just end of Q2. Delivers Shannon's explicit ask ("a live view of how we will track in the fiscal year"). Reconciles to the dollar with the standalone DM Forecasting Tool.

- Parameterized end-to-end by target_quarter ∈ {2,4}; a single horizon descriptor derives six values (target date, Live-ARR column, SQL renewal boundary, hybrid reporting period, reference baseline, DM exponent), mirrored FE↔BE. Default stays Q2 → zero regression.

- FY-end reads precomputed projected_arr_2026_12_31; sqrt half-year math generalizes to pow(dm%, months/12); DM denominator / target base shift arr_ttm_startarr_current (calendar-year DM% = proj 12/31/26 ÷ actual 12/31/25).

- Horizon-aware across all page paths: BU summary, classes, BU drill-down, Live-ARR contract drill-down, renewals gap metrics, the scenario-simulation hook, and the AI assistant's query_renewals tool. Per-quarter cache isolation.

Linear: KLAIR-2799

Design spec: docs/superpowers/specs/2026-05-30-dm-full-year-forecast-design.md · Plan: docs/superpowers/plans/2026-05-30-dm-full-year-forecast.md

## Test plan

- [x] Backend: pytest tests/arr_gap/271 pass; ruff + pyright (no new errors)

- [x] Frontend: vitest run src/screens/ARRGapV2150 pass; tsc --noEmit + lint:pr clean

- [x] End-to-end reconciliation (real service + Redshift): JigTree FY-end $76,541,233 / $94,810,643 = 80.7% (matches standalone tool); Q2 unchanged

- [ ] Manual: on /arr-gap, toggle to Fiscal Year-End → projection headers flip to 12/31/26, JigTree ≈ 80.7%; a winback/DM% edit + the Live-ARR and BU drill-downs all reflect the full-year horizon; toggle back to End of Q2 → June numbers return

## Not in this PR (tracked for future)

- P1 monthly/quarterly pacing-to-target (Kathy's 90/97/99 cascade) — separate spec

- Phase 2 rep/portfolio-level DM rollup

- Live Salesforce auto-ingest of closed-won/uplift (refresh stays on-demand for now)

- P2 Notion sentiment overlay (join on NetSuite Subscription ID)

## Screenshot

<img width="1834" height="803" alt="image" src="https://github.com/user-attachments/assets/23c51882-e78c-4322-9f8c-f039dc6344ac" />

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#2926 — feat(mfr): full account-level drill-down on Education vertical P&L cells (KLAIR-2801) @eric-tril  no labels

### Summary

Expands the Education memo's vertical P&L tables from a budget-only drill-down (which only fired on the QTD Budget column) into a full account-level drill-down on every Actual / Budget / Delta cell. Two new GET endpoints — /education-vertical-detail and /education-school-detail — return the account-level breakdown behind a clicked cell, served by a new education_vertical_detail_service whose queries deliberately mirror the existing vertical-table query (NHC normalization, class-split, budget-cycle MAX, QTD vs monthly budget windows) so per-account totals reconcile to the displayed cell within $0.01 (enforced by a Pydantic validator). It also wires a real sourced Total (NHC operating spend) into the previously-empty Marketing/Evangelism spend-by-category tables in both the web UI and the DOCX memo, with a caption explaining the per-category split is not yet available.

### Business Value

Finance can now audit any displayed Education figure — and the variance or margin % behind it — by clicking the cell and seeing the exact accounts that compose it, rather than only the QTD Budget column. This shortens reconciliation and review cycles, reduces back-and-forth over "where did this number come from," and brings the same trustworthy sourced Total to the spend-by-category tables that previously rendered blank.

### Changes

- Backend: new /education-vertical-detail and /education-school-detail endpoints with EducationVerticalDetailResponse / EducationSchoolDetailResponse models that validate per-account totals tie to the cell (finance_monthly_financial_reporting_router.py).

- Backend: new education_vertical_detail_service.py — fetch_education_vertical_detail (account-level Actual + Budget across row kinds type/subtotal/dept/nhcsub/composite/ratio/summary, with the Revenue denominator for Gross Margin %) and fetch_education_school_detail (per-school QuickBooks actuals; Core Education from the consolidated table).

- DOCX: build_empty_category_table → build_spend_category_table now populates the Total row and renders an optional italic caption; compute_spend_category_total_cells computes the NHC-spend Total from the through-period (monthly_*) field, and education.py wires both into the Marketing/Evangelism tables.

- Frontend: new VerticalDimensionDetailPanel (Actual/Budget/Delta per account, Gross Margin % display, CSV) and PhysicalSchoolDetailPanel (per-school net-margin accounts, CSV).

- Frontend: new useEducationVerticalDetailHandlerFactory (replaces useEducationVerticalBudgetDetailPanel) firing on every numeric column and resolving P&L / Physical Schools / summary rows; SectionHeaderRow now supports drill-down for section:-tagged subtotals.

- Frontend: client types + fetchers in monthlyFinancialApi.ts; buildEmptyCategoryConfig → buildSpendCategoryConfig with optional SpendCategoryTotal; educationVerticalTransform.ts exports type sets / summary-BU map, adds computeNhcSpendTotal and disambiguateMarketingDataKeys, and tags Physical Schools section headers.

- Tests: new test_education_vertical_detail.py (511 lines, all row kinds, budget windows, ratio denominator, per-school/Core-Education breakdowns, error cases), spend-category total tests, the new hook spec, and the build_spend_category_table rename in width tests.

### Testing

- [ ] Backend: pytest tests/mfr/budget_drilldown/ tests/test_education_vertical_data.py tests/docx_reports/test_education_vertical_widths.py

- [ ] Frontend: pnpm test (covers useEducationVerticalDetailDetailPanel.spec.tsx), pnpm lint:pr, pnpm tsc --noEmit

- [ ] Manual: open the Education memo, click cells in the vertical P&L tables (Actual, Budget, and Delta columns; Monthly and QTD), Physical Schools rows (per-school, section subtotal, grand Total), and Current Performance Summary rows; confirm the panel totals tie to each clicked cell and the Marketing/Evangelism spend-by-category Total matches the NHC Expenses line.

http://localhost:3002/monthly-financial-reporting

https://github.com/user-attachments/assets/7faea168-cd72-450f-8678-bc621b24c272

#2927 — feat(renewals): align AI Renewals cohort to canonical Fionn-handled source selection [KLAIR-2810] @sanketghia  no labels

## Summary

The /renewals AI Renewals tab rendered the wrong cohort — the raw closed-opportunity set from the Fionn SF table (280: 101 won / 179 lost), which the original Edie spec calls the *audit superset* and warns must not be treated as "all Fionn-handled opportunities." This PR renders the canonical Fionn-handled cohort via per-subscription source selection, reconciling exactly to the Fionn team's "Agent Performance" dashboard (42 resolved · 16 won / 26 lost · 38.1%).

Implements spec features/renewals/fionn-handling-section/specs/07-fionn-ai-owner-cohort/.

## What changed

- New canonical _SELECTED_CTE: one row per parent_subscription_id; use the linked Fionn row when the Trilogy owner is Fionn/Fionn AI, else the Trilogy row.

- Both AI Renewals and Traditional cohorts derive from one selected set; standalone trilogy_baseline_query retired; Trilogy USD ARR wired so Traditional retention is real.

- Option A: current-period cohorts use a renewal-date lower bound only (>= 2026-01-01) — Fionn-handled renewals are future-dated, so a <= today upper bound zeroes the cohort. Traditional 2025 redefined as the full prior calendar year; column kept, tooltip updated.

- FE cohort labels + YoY tooltip synced.

## Reconciliation (live, 2026-05-31)

| Cohort | n | Won | Lost | Win | Gross | Net |

|---|---:|---:|---:|---:|---:|---:|

| AI Renewals | 42 | 16 | 26 | 38.1% | 48.3% | 62.5% |

| Traditional | 868 | 349 | 519 | 40.2% | 37.6% | 51.0% |

| Traditional 2025 | 1,735 | 784 | 951 | 45.2% | 44.0% | 67.2% |

## Testing

- Backend: ruff + pyright clean; pytest tests/renewals/ 29 passed + 1 @pytest.mark.integration live reconciliation.

- Frontend: tsc --noEmit clean, eslint clean, vitest 7/7.

## Heads-up

Changes the headline AI Renewals numbers leadership reads (42 vs 280) — wants stakeholder awareness before/at merge.

Linear: KLAIR-2810

🤖 Generated with [Claude Code](https://claude.com/claude-code)

The Builder Desk  —  Engineer Spotlight
📅 Week in Review🏆 Engineer Spotlight

NINETY PRs IN SEVEN DAYS: THE BUILDER TEAM DOES NOT SLEEP, DOES NOT REST, DOES NOT KNOW WHAT A WEEKEND IS

marcusdAIy posts 20 PRs and the scoreboard simply runs out of room.

Ninety pull requests. Six repos. Seven days. The Builder Team has once again defied the known laws of software development physics, and your Numbers Desk correspondent is here to document every glorious digit. Klair alone absorbed 44 PRs like the heavyweight champion it is. Surtr took 18. Aerie handled 15. Rhodes, trilogy-drones, and Sindri collected the remaining scraps — and those scraps would still embarrass most engineering teams' best weeks. This is not a team. This is a force of nature wearing hoodies.

Let us begin with the man, the myth, the merge machine: @marcusdAIy, who shipped 20 PRs this week and apparently did so with his eyes closed. PR #2917 scoped and sorted ReviewPanel findings in Klair, #2916 exposed the CheckSpec registry to chat for verbatim citation, #2915 queued per-section Address-with-Claire batch processing for board docs, and #2907 connected Claire to Klair MCP tools in what can only be described as a robot-on-robot handshake of historic proportions. Twenty PRs. One engineer. The math is not supposed to work like this. @eric-tril answered with 15 PRs of his own, anchoring the MFR stack in Klair with surgical precision — PR #2913 delivered an EBITDA manual-entry fallback and CF upload fixes, #2912 added YTD columns to Note 8, and #2909 aligned Software and Education memo styling so the reference docs finally stop looking like they were formatted by two different people in two different decades. @benji-bizzell put up 14 PRs, touching Aerie with the quiet confidence of a man who knows exactly what he's doing: #280 preserved cleared edit fields in portfolio, #277 split cancelled and paused reveals into per-status toggles, and #276 gave chat expandable tool call inputs and outputs because transparency is a virtue. @sanketghia clocked 13 PRs and did not blink once, including the seismic PR #127 in Surtr — migrating the AP sync from Lambda to ECS Fargate to obliterate a 900-second timeout that had apparently been living rent-free in production. @kevalshahtrilogy posted 8 PRs, including PR #109, the triage-agent foundation that arrived with a spec, a signature library, DDB/SNS infrastructure, and observer fan-out, which is either one PR or a small startup, this reporter cannot tell. @YibinLongTrilogy delivered 7 PRs spanning Aerie and Rhodes — PR #106 added a full site security profile with table, mutations, MCP tools, and Aerie sync, because why do one thing when you can do five things in one commit?

And then there is @ashwanth1109. Nine PRs. NINE. The man wired the Anthropic API key into Surtr PR #121 and — and this is the part that keeps your correspondent up at night — configured it to "fail loudly on total enrichment loss." Loudly. As if the system itself is expected to express disappointment. PR #2901 opened SaaS Budgeting to non-admins with all-BU access, a democratization of financial visibility that this reporter finds genuinely moving. PR #263 in Aerie built a campus-to-QB entity mappings viewer of such elegant specificity that one must simply accept it on faith. When reached for comment, Ashwanth reportedly said, "The diff is self-documenting. If you need me to explain it, that's a you problem." Your correspondent stared at PR #104 for eleven minutes and found it to be, indeed, a him problem.

The Overflow Desk this week could fill a magazine. PR #119 in Surtr captured Trilogy currency and USD ARR fields in the renewals pipeline — @sanketghia again, quietly building the financial data infrastructure that makes everything else possible. PR #117 fixed the TrueFoundry gateway pipeline's trailing window so late partitions self-heal, which is the kind of fix that saves someone's Saturday and they'll never know who to thank. PR #275 in Aerie added a portfolio Security card with a full siteSecurity profile, and PR #105 in Rhodes added phase 2 projectedDate and docLink to the site schema, meaning @YibinLongTrilogy was building the same feature simultaneously on both sides of the stack like some kind of distributed human.

Morale on the Builder Team is at an all-time high. It was at an all-time high last week. It will be at an all-time high next week. The numbers do not lie, and neither does Brick Callahan.

Brick's Overflow — This Week's Uncovered PRs  (click to expand)
#109 — feat(triage-agent): PR 1 — foundation (spec + signature lib + DDB/SNS + observer fan-out) @kevalshahtrilogy  no labels

## Summary

PR 1 of the pipeline-triage-agent feature (full spec: [features/surtr/pipeline-triage-agent/FEATURE.md](features/surtr/pipeline-triage-agent/FEATURE.md)). Lays the foundation for an autonomous triage layer that turns pipeline failures and CRITICAL/WARN observer findings into GitHub Issues + (when confident) PRs, with per-signature dedupe so repeated failures of the same kind don't spawn duplicate tickets.

Self-containment invariant: this PR deploys to prod as a no-op until SURTR_TRIAGE_TOPIC_ARN is set on the Surtr ECS task. No Issues opened, no PRs opened, no model spend. The subsequent PRs (dispatcher, agent workflow, reconciler) each carry their own kill switch.

## What this PR adds

- features/surtr/pipeline-triage-agent/FEATURE.md — the full feature spec. Top section is the decision summary (problem, goals, D1–D21 with rejected alternatives, risks, build sequence, prerequisites). Bottom section is implementation reference for downstream PRs.

- Surtr/src/lib/triage/signature.ts — pure signature/normalization library. failureSignature(pipelineId, error) and findingSignature(pipelineId, category, title). Normalizes timestamps, UUIDs, ARNs, request IDs, line numbers, byte counts, IP addresses so the same failure mode hashes identically across runs.

- Surtr/test/lib/triage/signature.test.ts — 27 fixture tests. Locks in dedupe for OOM, ImportModuleError (the recurring requirements.txt bug), and distinguishes unrelated bugs. Extend the corpus whenever a new failure shape appears.

- Surtr/src/lib/triage/publish.ts — observer → triage SNS fan-out. Same fire-and-forget posture as lib/gchat.ts: never throws, ERROR-tagged logs for the existing CW log-alarm regex.

- Surtr/test/lib/triage/publish.test.ts — 3 tests covering the no-op-when-flag-off contract, the verdict guard, and the never-throw guarantee.

- Surtr/src/derive/observer/index.ts — one new void publishObserverTriageEvent(...) call right after the existing GChat alert. Same dedupe gates (!opts.force && !hadPriorObservation && CRITICAL/WARN).

- pipelines/cdk/lib/pipeline-shared-stack.ts — provisions:

- SNS topic klair-pipeline-triage-{env} (no subscribers yet)

- DDB table surtr_agent_triage with PK signature, GSI pipeline_id-last_seen_at-index, PITR + deletion-protection + RemovalPolicy.RETAIN (matches the surtr_pipeline_observations posture)

## Test plan

- [x] npx vitest run test/lib/triage/ — 30/30 pass

- [x] npx vitest run test/derive/observer.test.ts test/derive/observer-logic.test.ts test/derive/observer-gchat-trigger.test.ts — 36/36 pass (observer behavior unchanged when flag is off)

- [x] npx tsc --noEmit — clean

- [x] npx biome check src/lib/triage src/derive/observer/index.ts — clean

- [x] cd pipelines/cdk && npm run build (tsc) — clean

- [ ] CI on this PR runs the full suite + CDK synth on a docker-enabled runner

## Production-deploy effect on merge

- New empty SNS topic klair-pipeline-triage-prod

- New empty DDB table surtr_agent_triage

- Observer process keeps current behavior — the fan-out call is a no-op without SURTR_TRIAGE_TOPIC_ARN

- No new IAM, no new Lambda, no new workflow, no new spend

## Rollout (after merge)

1. Merge this PR. Observer behavior is unchanged.

2. Land PR 2 (dispatcher Lambda). Still inert (TRIAGE_DISPATCHER_ENABLED=false).

3. Land PR 3 (agent workflow). Still inert (TRIAGE_AGENT_RUN_ENABLED=false).

4. Land PR 4 (reconciler + notifications). Still inert.

5. In dev: flip all flags, exercise end-to-end.

6. In prod: flip SURTR_TRIAGE_TOPIC_ARN first; observe nothing breaks; then flip each downstream flag with at least one full cron tick between.

## Out of scope (covered by later PRs)

- Dispatcher Lambda, dedupe branching, recurrence-comment logic — PR 2

- Agent workflow, both runtimes (Claude Code + Codex), AGENTS.mdPR 3

- GChat outcome card, hourly state reconciler, optional UI tile — PR 4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#121 — fix(notion-rca-hub-sync): wire Anthropic API key and fail loudly on total enrichment loss (SURTR-29) @ashwanth1109  no labels

## Summary

- Root cause (auth): enrichment.py constructed Anthropic() with no auth wiring — no ANTHROPIC_API_KEY env var, no Secrets Manager fetch — so every Haiku call in the last run failed with *"Could not resolve authentication method. Expected either api_key or auth_token to be set"*. 536/536 records failed.

- Why it reported SUCCESS: Three nested try/except wrappers (_enrich_one_enrich_allhandler.py) all caught the auth error and continued. The handler's # base table unaffected swallow couldn't distinguish "a few transient failures" from "100% wipeout."

- Fix 1 — wire the key: Add ANTHROPIC_API_KEY_SECRET_ARN env var + IAM secretsmanager:GetSecretValue in pipeline.json, reusing the existing klair/anthropic-api-key-yFl13y secret that netsuite-gl-detail already consumes. New get_anthropic_api_key() helper in aws_secrets.py (env var first for local dev, then Secrets Manager). RCAEnricher.__init__ now passes api_key= explicitly.

- Fix 2 — fail loudly: In handler.py, raise RuntimeError when len(documents) > 0 and enriched == 0. The exception propagates out of main.py, ECS exits non-zero, the run is marked FAILED, and alerting fires. Partial failures stay non-fatal — the base table is still useful and a few transient API errors shouldn't page anyone.

## Test plan

- [x] pytest tests/70 passed (7 new):

- test_aws_secrets.py: get_anthropic_api_key env-var path, Secrets Manager path, env precedence, missing config raises, empty secret raises

- test_enrichment.py: Anthropic constructed with api_key= arg

- test_handler.py: raises on total failure, raises on enricher-constructor blowup (missing key), partial failure does NOT raise, empty DB does NOT raise

- [x] ruff check + ruff format clean on all modified files

- [x] pipeline.json validates as JSON

- [x] After merge & deploy: trigger the pipeline manually and confirm Haiku enrichment loads rows into mart_other.rca_incidents_enrichedsee Demo below

## Demo — end-to-end verification on prod

Deployed this PR's branch directly to prod via cdk deploy Pipeline-notion-rca-hub-sync-prod --exclusively and ran a test-mode execution (50 pages).

### 1. Deploy landed cleanly

- Stack: Pipeline-notion-rca-hub-sync-prodUPDATE_COMPLETE (4/6 resources changed)

- Task definition: new revision 7 registered, old revision deregistered

- Image: cdk-hnb659fds-container-assets-...:11c02030... (PR's fix code)

- Env var added: ANTHROPIC_API_KEY_SECRET_ARN=arn:aws:secretsmanager:us-east-1:479395885256:secret:klair/anthropic-api-key-yFl13y

- IAM updated: task role's inline policy now allows secretsmanager:GetSecretValue on both the existing Notion secret and the new Anthropic secret

### 2. Step Function ran green

Execution: pipeline-notion-rca-hub-sync-prod:5fd4b901-a685-489f-b589-4a51010e242b

Input: {"run_id":"manual-test-...", "params":{"test_mode":true}}

Status: SUCCEEDED

Duration: 1m 40s (ECS task: 34s)

### 3. CloudWatch logs prove the new auth path executed

Key lines from log stream pipeline/pipeline-notion-rca-hub-sync/f5d981e63c39465daea2f09d9d90fa7b:

10:37:58  root         TEST MODE active: limited to 50 pages

10:38:07 redshift_loader Loaded 50 rows into mart_other.rca_incidents

10:38:07 aws_secrets Retrieving Anthropic API key from secret:

arn:aws:secretsmanager:us-east-1:479395885256:secret:klair/anthropic-api-key-yFl13y

10:38:14 httpx POST https://api.anthropic.com/v1/messages "HTTP/1.1 200 OK"

... (× 50 — every Haiku call returned 200)

10:38:26 enrichment Loaded 50 rows into mart_other.rca_incidents_enriched

10:38:26 enrichment Enrichment complete: 50 enriched, 0 failed, 50 loaded

10:38:26 root Pipeline completed: 50 transformed, 50 loaded to Redshift, 50 enriched, 33688ms

Final summary emitted to Step Functions:

{

"run_id": "...",

"pages_fetched": 537,

"pages_transformed": 50,

"redshift_rows_loaded": 50,

"enrichment": {"enriched": 50, "failed": 0, "loaded": 50, "errors": []},

"errors": []

}

Contrast with the failing run that triggered this PR:

- Before: "Could not resolve authentication method..." × 536, {"enriched": 0, "failed": 536, "loaded": 0} — and yet pipeline reported SUCCESS

- After: 50/50 Haiku calls returned 200 OK, full enrichment

### 4. Redshift confirms rows landed

SELECT COUNT(*),

COUNT(DISTINCT notion_page_id),

SUM(CASE WHEN severity IS NOT NULL THEN 1 ELSE 0 END),

SUM(CASE WHEN root_cause_category IS NOT NULL THEN 1 ELSE 0 END),

SUM(CASE WHEN incident_start_date IS NOT NULL THEN 1 ELSE 0 END),

SUM(CASE WHEN one_line_summary IS NOT NULL THEN 1 ELSE 0 END)

FROM mart_other.rca_incidents_enriched;

| total_rows | distinct_pages | with_severity | with_root_cause | with_start_date | with_summary |

|------------|----------------|---------------|-----------------|-----------------|--------------|

| 50 | 50 | 50 | 50 | 50 | 50 |

Sample rows (Haiku extractions look sensible):

| doc_name | aws_class | severity | root_cause | start_date | summary |

|---|---|---|---|---|---|

| Khoros Service Issue LIA-23857 | Khoros Product | critical | database | 2026-04-27 | CDN cookie caching and unbounded gallery queries saturated Aurora cluster |

| RCA for Khoros LIA-23911 | Khoros Product | high | configuration | 2026-04-28 | Faulty SVN config include order removed theme plugin, breaking page rendering |

| RCA for Khoros AURORA-1211 | Khoros Product | critical | capacity | 2026-04-24 | Insufficient Aurora enduser capacity caused 10-hour outage with 503 errors |

| RCA for Khoros LIA-23520 | Khoros Product | critical | capacity | 2026-04-23 | Sephora login surge saturated shared Aurora writer, causing ServiceNow 503 |

| RCA for Kandy KANDY-787 | Kandy Product | critical | infrastructure | 2026-04-22 | DRBD/NFS storage gaps during failover caused 2h17m call-recording playback outage |

The scheduled cron (cron(0 6 * * ? *)) will pick up the full 537-page set on its next run.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#127 — feat(quickbooks-ap-sync): migrate from Lambda to ECS Fargate to fix 900s timeout @sanketghia  no labels

## Problem

The nightly scheduled run (params={}) processes all QuickBooks entities serially in one invocation. PR #107 grew COMPANY_IDS from 9 → 33 realms, pushing the serial run past the Lambda 900s hard ceiling:

- The 2026-05-30 03:00 UTC run hit Status: timeout (Duration: 900000.00 ms) after completing ~25 of 33 entities.

- Remaining entities never synced, and the downstream quickbooks-core-tables pipeline never triggered (the trigger runs at the end of a successful run).

- This will recur every night — it's a deterministic capacity regression, not a transient.

900s is the Lambda maximum, so it cannot be raised.

## Fix

Migrate to ECS Fargate using the repo's existing compute: ecs abstraction — the same pattern as the containerized sibling quickbooks-core-tables and hubspot-sync.

| File | Change |

|---|---|

| pipeline.json | compute: "ecs" + ecs_config (cpu 1024, memory 4096, timeout_hours 1, ephemeral_storage_gb 21); dropped Lambda-only fields. timeout_hours doubles as the backstop against a hung Redshift COPY. |

| Dockerfile *(new)* | Standard repo image, identical to core-tables/hubspot-sync. |

| src/main.py *(new)* | Thin ECS entrypoint adapting RUN_ID/PARAMS env vars to the existing handler(event, context). |

| pyproject.toml | Added [build-system] (setuptools), required by the container's uv pip install -e .. |

No business-logic changeshandler.py is reused verbatim (it ignores context).

## Scope

✅ Removes the 900s ceiling → run completes → downstream re-triggers.

⚠️ Does not address per-entity volume (alpha ~4 min, full 30-month re-pull) — tracked separately.

## Verification

- 352 CDK config-schema tests + pipeline-stack ECS-routing tests pass.

- Native Docker image builds (deps + editable install resolve); main → handler import chain loads in-container.

- Not exercised locally: native-amd64 build (validated in CI) and the live task run (first scheduled/manual run post-deploy). The local amd64 build hit a qemu segfault under cross-emulation on Apple Silicon — an emulation artifact, not a defect.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#2907 — feat(budget-bot): connect Claire to Klair MCP tools (B5.7 + B5.8) @marcusdAIy  no labels

## Summary

- B5.7/B5.8 — Klair MCP integration: connects Budget Bot's chat agent (Coach Claire) to the full Klair MCP tool catalog (~39 live data tools) so she can query any of them inline during a chat turn, not just the pre-fetched DataPackage. Reuses claire_bot's proven service-token auth path (Bearer <service-token>::<user_id>) so long wizard sessions don't hit mid-conversation JWT expiry.

- Plus a cluster of refresh / chat / table fixes surfaced while testing the MCP work end-to-end against a real Skyvera Q2 board doc (see Changes). These are bundled here because they were found and validated in the same testing pass; happy to split if reviewers prefer.

Closes KLAIR-2709 (B5.7). Closes KLAIR-2710 (B5.8).

## Why it's needed

Budget Bot's chat was limited to the ~15 sources DataOrchestrator pre-fetches. GMs/BU-CEOs routinely ask Claire for data Budget Bot doesn't explicitly fetch (customer renewals, headcount, vendor concentration, AR aging). MCP gives Claire read access to the whole queryable surface without a dedicated fetcher per source, and is the prerequisite for the B6 multi-turn loop. The auth path (service token vs short-lived Clerk JWT) determined the whole shape, hence B5.8 lands alongside.

The refresh/chat/table fixes address showstoppers found during testing: a refresh that hung forever, financial tables that never rolled forward to the planning quarter, duplicated tables, and chat with no completion signal / broken markdown.

## Changes

MCP integration (B5.7 / B5.8)

- budget_bot/board_doc/mcp_tools.py (new): fetch_mcp_tool_catalog (JSON-RPC tools/list → Anthropic ToolParam[], camelCase→snake_case bridge), execute_mcp_tool (JSON-RPC tools/call, never-raises with [MCP …] error envelopes), optional shared httpx client for connection pooling, _MCP_TOOL_CATALOG_CAP hard cap, per-call latency logging.

- wizard_orchestrator.handle_chat: fetches the MCP catalog per turn (gated on a focused section + session.user_id), merges with CLAIRE_TOOLS, runs a bounded agentic loop (_MAX_MCP_TOOL_TURNS=5) that executes MCP tool_use server-side and threads tool_result back.

- claire_bot/claude_code_agent.py: extracted build_klair_mcp_auth_headers + get_klair_mcp_url as shared single-source-of-truth helpers; _get_mcp_server_config is now a thin shim (behaviour preserved — all 31 test_eval_mode_propagation.py pass).

- WizardSession.user_id: str = "" plumbed from get_user_from_clerk through the session-create routes (default empty for clean back-fill of in-flight DDB sessions). No Budget-Bot-side allowlist — the MCP server enforces per-user access.

Refresh / chat / table fixes (testing-surfaced)

- SSE refresh no-change hang: the refresh progress loop could block on request.is_disconnected() and never emit complete on a fast no-change refresh — banner hung forever. Bounded the disconnect check + break immediately on task completion.

- Refresh rip-and-replace: _refresh_data now ALWAYS regenerates the deterministic data-only (FINANCIALS) sections from the current planning-quarter workbook, regardless of diff or storage backend. Previously a diff-gated skip stranded cloned-from-prior-quarter tables (and prod/local diverged on data_package persistence). The diff now only gates surgical narrative number-updates + the re-publish decision.

- Financials de-dup: generate_financials scopes table rendering to the section (Hybrid-marked → Hybrid table only; BU-marked → BU table only; generic "Financials" → both), so a doc split into two financials sections no longer renders both tables twice.

- Regenerate completion signal: a regenerate_section accept now toasts on success (the BE message was previously discarded; the proposal card vanishes on resolve, leaving no closure).

- Chat markdown tables: added remark-gfm + table CSS to the BoardDoc chat panel (raw | --- | pipes were leaking as literal text).

## Breaking changes

None. WizardSession.user_id defaults to "" so existing DDB sessions deserialize cleanly (they degrade to the JWT fallback). No schema migration. The MCP catalog is additive; refresh/chat behaviour changes are strict improvements.

## Test plan

### Automated (run on this branch)

- [x] cd klair-api && uv run ruff check … + uv run ruff format … — clean

- [x] cd klair-api && uv run pyright budget_bot/board_doc/mcp_tools.py budget_bot/board_doc/wizard_orchestrator.py — 0 errors

- [x] cd klair-api && uv run pytest tests/board_doc/1942 passed

- [x] cd klair-api && uv run pytest tests/claire_bot_eval/test_eval_mode_propagation.py — 31 passed (confirms the _get_mcp_server_config refactor is behaviour-preserving)

- [x] cd klair-client && pnpm tsc --noEmit + eslint on changed files — clean; 64 chat-component vitest tests pass

- [x] Live prod MCP smoke testcd klair-api && uv run python scripts/test_budget_bot_mcp_connection.py → 39 tools loaded, service-token auth accepted, live query_help round-trip.

### Manual — MCP (reviewers: SKIP if you don't have the env vars; already validated locally by the author)

> Requires KLAIR_MCP_SERVICE_TOKEN + KLAIR_MCP_USER_ID in klair-api/.env (they live in the ENV_API_PROD secret). Without them MCP cleanly degrades to CLAIRE_TOOLS-only and these prompts won't exercise the integration — skip this section if you're not set up. The author has validated all of the below in local testing.

In a Budget Bot session with a section focused in the editor (MCP only engages with a focused section + a session user_id), ask Coach Claire:

- [ ] "What Klair data sources can you query right now?" → she enumerates MCP tools (catalog fetch worked).

- [ ] "Pull our budget-vs-actuals for last quarter and tell me where we missed." → fires query_budget_vs_actuals.

- [ ] "Who are our top vendors by spend — any concentration risk?" → fires query_vendor (data not in the pre-fetched DataPackage, so she must hit MCP).

- [ ] "What's our AR aging — anything over 90 days?" → fires query_collections.

- [ ] Negative control: "Summarize the financials section of this document." → should NOT call MCP (answerable from context).

- [ ] Backend logs show Loaded N Klair MCP tool(s) + Klair MCP tools/call '…' succeeded in N.Ns.

### Manual — refresh / chat / tables

- [ ] Trigger a data refresh (Claire's refresh_data tool or the Refresh button) → it completes (no hang), and the financial tables re-render from the current planning-quarter workbook.

- [ ] Download the doc → each FY plan table (Hybrid, BU) appears once, not duplicated.

- [ ] Accept a regenerate_section proposal → a "Section regenerated" toast confirms completion.

- [ ] Claire emits a markdown table in chat → it renders as a real table, not raw pipes.

## Follow-ups (filed)

- KLAIR-2791 — give Claire full review-findings visibility

- KLAIR-2792 — prompt guardrails against unverified claims (over-promising "on accept" + asserting staleness without checking)

- KLAIR-2793 — harden clone-time financial-table rip-and-replace (all table-bearing sections)

- KLAIR-2794 — post-refresh narrative/data consistency check + goal-status re-adjudication

- KLAIR-2795 — B1.8 bold-paragraph promotion over-promotes in budget docs (root cause of the table fragmentation; this PR ships the de-dup mitigation)

#2913 — feat(mfr): EBITDA Acquisitions manual-entry fallback + CF upload fixes @eric-tril  no labels

## Summary

Three related Monthly Financial Reporting fixes in the Cash Flow / EBITDA area:

### 1. EBITDA "Acquisitions" — fall back to Cash Flow Manual Entry when no CSV upload

"Acquisitions" sources from the Cash Flow upload's "Purchase business combinations" row. Previously, when no CSV was uploaded, it showed blank even though Finance had entered the value in the Cash Flow Manual Entry screen. It now falls back to the manual-entry value for the same row (inv:pbc), consistently across all three surfaces that were previously diverging:

- the in-app EBITDA reconciliation table (Group aggregates client-side; Software/Education are server-side),

- the Acquisitions drill-down ("Account Detail" panel),

- the Google Doc memo export.

CSV upload still takes precedence when present; the fallback is QTD-only (the manual-entry table has no YTD equivalent). A new backend helper resolve_manual_entry_row_value resolves a single manual-entry row's per-entity value without invoking the full override pipeline.

### 2. Cash Flow CSV upload — fix doubled "Management restructuring and import"

On a Group CSV upload, "Management restructuring and import" appeared twice. The CSV parser canonicalised every spelling variant to the plural label ("…imports") — correct for Software/Education, but the Group statement uses the singular. Since the upload-merge matches rows by exact label, the unmatched plural row was appended as a duplicate instead of overriding the derived singular row. The parser now emits the singular label for Group uploads.

### 3. Cleanup — drop orphaned bs_change_in_cash from the CF manual-entry prefill

That prefill field's only consumer was the operating-cash / change-in-cash rows removed in #2911. Removed it from the pydantic model, build_prefill, and the frontend type. (The separate _extract_bs_change_in_cash used by the memo overrides is unaffected.)

## Business value

Finance sees a consistent Acquisitions figure across the EBITDA table, its drill-down, and the exported memo — sourced from manual entry before a CSV exists — and no longer sees a duplicated investing line on Group CSV uploads.

## Test plan

- Backend: ruff + pyright clean; pytest tests/mfr/1357 passed.

- Frontend: tsc --noEmit + eslint clean; MFR vitest711 passed; production build succeeds.

- New tests cover the manual-entry fallback on all three surfaces (drill-down, memo export, client-side table), the resolve_manual_entry_row_value helper, the Group-singular CSV label, and the prefill-field removal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

http://localhost:3001/monthly-financial-reporting

https://github.com/user-attachments/assets/d57c5e70-032c-493a-b768-2de3f2b0b70d

#2917 — feat(review): scope filter + sort for ReviewPanel findings (B7.12 Tier 1) @marcusdAIy  no labels

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->

## Summary

- Add pure getFindingScope helper in boardDocApi.ts to classify review findings as BU-wide vs product scope from section_id + wizard sections.

- ReviewPanel shows a multi-select scope chip row (All, BU-wide, per-product in spec order, Other Products when present) and filters the scorecard accordingly.

- Within each check_area group, findings sort by severity → scope (BU-wide first, then products in spec order) → check_id.

## Why it's needed

GMs reviewing multi-product board docs need to answer “what does Kandy need from me?” without scanning every finding card’s section pill. B7.12 Tier 1 surfaces scope as a first-class filter/sort axis using data already on each finding — no backend scope field (Tier 2).

## Changes

- boardDocApi.ts: getFindingScope, findingScopeKey, listScopeFilterChips, compareReviewFindingsInArea (severity → scope → check_id).

- ReviewPanel.tsx: sections prop, ScopeFilterChips row above grouped findings (coexists with B7.10 batch toolbar), scope filter state reset on findings identity change.

- DocumentEditorPage.tsx: passes wizard.sections into ReviewPanel.

- Tests: boardDocApi.getFindingScope.spec.ts, ReviewPanel.scope.spec.tsx.

## Breaking changes

None.

## Test plan

- [x] pnpm test src/services/__tests__/boardDocApi.getFindingScope.spec.ts

- [x] pnpm test src/screens/BoardDoc/components/__tests__/ReviewPanel.scope.spec.tsx

- [x] pnpm test src/screens/BoardDoc/components/__tests__/ReviewPanel.batch.spec.tsx (B7.10 regression)

- [x] pnpm tsc --noEmit

- [x] pnpm lint:pr

Verification artifact: ReviewPanel.scope.spec.tsx renders the chip row over BU-wide + Kandy + Widget + Other Products and asserts Kandy-only filtering hides BU-wide/Widget cards — see test *"filters visible cards when a product chip is toggled"* and *"renders scope chips for present scopes in spec order"*.

Linear: KLAIR-2763

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-fdc82cfd-8249-4ae2-a413-b8c0c1901900"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-fdc82cfd-8249-4ae2-a413-b8c0c1901900"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

The Portfolio  —  Trilogy Companies

The Acquisition Window Is Open: Why Legacy Enterprise Software Is the Hottest Target in a World Being Eaten by AI

Market reports and analyst chatter converge on a single thesis: mature, sticky enterprise software businesses are ripe for harvest — and ESW Capital wrote that playbook years ago.

AUSTIN, TEXAS — The M&A calendar for enterprise software in 2025 is beginning to look less like a transaction log and more like a confirmation of a thesis that Trilogy International's ESW Capital has been executing quietly for nearly two decades.

Three separate market signals landed this week. Dakota's August 2025 Software & Technology Transactions Report catalogued a surge in closed deals across the enterprise stack. A parallel analysis out of Spain documented accelerating M&A appetite in European enterprise software markets. And Business Insider published a widely circulated breakdown of which software companies are most likely acquisition targets as AI restructures the competitive landscape — pointing squarely at the category ESW has long hunted: mature, under-optimized businesses with sticky customer bases and bloated cost structures.

The logic is not new to anyone who has watched ESW Capital work. The firm, Trilogy's private equity arm, has built a portfolio of 75-plus enterprise software companies on exactly this premise: acquire at one to two times ARR, staff with rigorously vetted global talent sourced through Crossover, compress costs, and drive toward 75% EBITDA margins. The playbook targets the same companies that Business Insider's sources now describe as ripe — legacy vendors whose customers can't easily leave, whose revenues are predictable, and whose operating models were built for a pre-AI world.

What has changed in 2025 is the urgency of the AI displacement narrative. Analysts and dealmakers are now framing the acquisition logic in terms of survival: buy the incumbent before an AI-native competitor renders it irrelevant, then rebuild its product surface from the inside. That framing, notably, is indistinguishable from how ESW has described its own DevFactory and IgniteTech operations for years.

ESW's existing portfolio companies — among them Aurea, Skyvera, and Totogi — are themselves products of this logic applied to telecom and enterprise CRM markets. The question the current deal surge implicitly raises is not whether the ESW model was prescient. It is whether the window that model depended on — cheap multiples, limited competition for unglamorous assets — is beginning to close as every growth equity fund in the world arrives at the same conclusion simultaneously.

Who benefits when the trade becomes crowded is a question the next round of transaction reports may begin to answer.

August 2025 Software & Technology Transactions Report - dako  ·  M&A in Enterprise Software in Spain (2025): Opportunities fo  ·  The software companies most likely to be acquired as AI eats

Crossover's Founder Wants to Algorithmize the Worker — And the Industry Is Watching

Joe Liemandt's global talent platform isn't just recruiting humans anymore — it's building the systems to measure, score, and eventually replace them.

AUSTIN, TEXAS — There is a version of this story that reads as visionary, and a version that reads as cautionary. Joe Liemandt, the billionaire founder of Trilogy International and the man widely credited with pioneering large-scale remote work, is now pursuing something more radical than geography-agnostic hiring: he wants to distill elite human performance into repeatable, auditable, algorithmic processes — and use those processes to staff companies at a fraction of traditional cost.

The vehicle is Crossover, Trilogy's global talent platform and arguably the most operationally critical piece of the entire conglomerate. Crossover already claims to be the world's largest recruiter of full-time remote positions, placing workers across 130+ countries through rigorous AI-enabled skills assessments designed to surface the top one percent of global talent regardless of résumé pedigree. What's new is the ambition behind the next phase: mapping exactly how elite performers do their jobs, codifying those patterns, and building the scaffolding to replicate them — at scale, systematically, with or without the original human.

The implications are systemic, and they land in a labor market already anxious about productivity surveillance. The rise of worker productivity scoring — once a niche management obsession — has become a mainstream flashpoint, with companies deploying real-time output metrics that workers often cannot see or contest. Liemandt's model takes that logic further: if you can measure performance granularly enough, the argument goes, you can eventually automate it.

The timing is not incidental. OpenAI is posting half-million-dollar roles with no résumé requirement, signaling that in the AI economy, demonstrated capability is the only credential that matters. Crossover has operated on that thesis for years — but where OpenAI is hiring humans who can think like machines, Trilogy increasingly wants machines that work like its best humans.

For workers in emerging markets who have used digital transformation to access international careers and above-market wages, the Crossover model has been genuinely liberating. The question — uncomfortable, urgent, and not yet answered — is what happens to those workers when the algorithm they helped train no longer needs them to run it.

What does this mean for real people? That is, as ever, the only question worth asking.

The Billionaire Who Pioneered Remote Work Has A New Plan To  ·  The Rise of the Worker Productivity Score (Published 2022) -  ·  OpenAI Is Now Hiring $500,000 Jobs. No Resume Required - For
The Machine  —  AI & Technology

Open-Source AI Hits the Real World — and the Trust Crisis Arrives Right on Cue

NVIDIA is pushing AI from screens into robots and factories, while a malicious model scare shows why provenance may become the industry’s new power layer.

SAN FRANCISCO — The open-source AI movement just got a massive jolt of physical-world ambition — and, almost simultaneously, a flashing red warning light. This changes everything, but not in the tidy, frictionless way Silicon Valley prefers.

NVIDIA has released new open-source AI models designed for the “physical world,” a phrase that sounds futuristic until you realize it means robots, autonomous systems, warehouses, vehicles, industrial equipment and every machine that needs to perceive and act in messy reality. According to Analytics India Magazine’s report, the release is part of NVIDIA’s broader push to make AI models available for developers building systems that interact with real environments rather than simply generating text, images or code.

I cannot overstate how significant that shift is. For years, open source AI has largely meant language models, developer tools and research artifacts. Now the frontier is moving into embodied intelligence: models that help machines understand motion, space, objects and cause-and-effect. The future is now — and it has sensors.

But here comes the catch. As open models become more powerful and easier to download, the supply chain around them becomes a security battlefield. A malicious Hugging Face model reportedly masquerading as an OpenAI release reached 244,000 downloads, according to CSO Online. That is not a niche incident. That is a siren blaring across the entire AI ecosystem.

This is why Cisco’s release of an open-source toolkit for verifying AI model lineage lands at exactly the right moment. In plain English: developers and enterprises need to know where a model came from, how it was built, whether it has been tampered with and whether it can be trusted before it gets plugged into production systems.

The policy world is circling the same issue. Andreessen Horowitz is arguing for American leadership in open-source AI, framing openness as a strategic advantage. That debate is no longer academic. Open models can accelerate innovation at breathtaking speed — but without identity, lineage and verification, they can also accelerate risk.

The next AI platform war may not be only about who has the biggest model. It may be about who can prove their model is real, safe and traceable. Welcome to the age of AI provenance. Buckle up.

NVIDIA Releases Open Source AI Models Built for the Physical  ·  Malicious Hugging Face model masquerading as OpenAI release  ·  Asserting American Leadership in Open Source AI - Andreessen

The Hallucination Problem Goes Physical: AI Systems Cannot Be Trusted to Obey the Laws of Physics

Two new papers argue that generative models are structurally incapable of respecting physical reality — and the implications extend far beyond pretty pictures.

CAMBRIDGE, MASSACHUSETTS — A convergence of peer-reviewed preprints, arriving with the quiet urgency characteristic of paradigm-adjacent research, has surfaced what it could be argued is the most epistemologically troubling subspecies of the hallucination problem yet documented: the systematic, architecturally-embedded tendency of generative AI systems to violate the laws of physics — not occasionally, not stochastically, but as a matter of structural necessity.

The thesis, advanced with particular rigor in PhyDrawGen: Physically Grounded Diagram Generation from Natural Language, proceeds as follows: current generative models, when tasked with producing physics diagrams from natural language prompts, reliably hallucinate force vectors, casually discard conservation laws, and transgress geometric constraints with what one might charitably describe as breezy indifference. The authors' proposed corrective — a neuro-symbolic pipeline that decouples semantic scene understanding from physical constraint satisfaction — represents, preliminary evidence suggests, a methodologically sound (if necessarily provisional) step toward grounding visual generation in something resembling Newtonian reality.

The antithesis arrives, with complementary force, from a second preprint: Physically Viable World Models: A Case for Query-Conditioned Embodied AI, which contends that the failure of existing world models to represent physical structure is not a bug amenable to fine-tuning but rather a categorical architectural deficiency (it could be argued, a philosophical one). Observation-predictive models, the authors submit, produce rollouts that are visually plausible yet physically catastrophic — a distinction that becomes, in embodied AI contexts, the difference between a robot that functions and one that, in the colloquial register, simply falls over.

The synthesis, such as it is, demands acknowledgment: the AI research community has, for several productive years, optimized relentlessly for perceptual coherence while treating physical coherence as, at best, an emergent property. These papers suggest, with the measured alarm appropriate to the genre, that emergence has not, in fact, occurred. The laws of physics, it turns out, do not negotiate with gradient descent.

PhyDrawGen: Physically Grounded Diagram Generation from Natu  ·  Physically Viable World Models: A Case for Query-Conditioned  ·  Transforming and Encoding FTS for SAT Solving: What Helps, W

Supreme Court Declines AI Authorship Case, Leaving Machines Uncredited and Uninvented

The Supreme Court has declined to hear a case on whether artificial intelligence systems can be recognized as authors or inventors under federal law, effectively preserving lower court rulings that they cannot. The decision leaves intact the judicial consensus that authorship and inventorship under copyright and patent law are reserved exclusively for natural persons, regardless of an AI system's sophistication or creative output.

The Court's denial of certiorari is not a ruling on the merits, but its practical effect is final—no higher judicial forum remains available. The implications are substantial for companies deploying generative AI for creative and inventive works. Questions about ownership of AI-generated outputs and allocation of intellectual property rights between human operators, corporations, and AI systems now fall to legislators to resolve. Legal experts advise businesses to consult counsel before making claims about AI-generated work ownership.

The Editorial

The Deepfake Doctor Will See You Now — And She Doesn't Exist

AI is weaponizing trust in medicine, women's bodies, and reality itself, and we are nowhere near ready for what comes next.

WASHINGTON, D.C. — There is a doctor on your social media feed right now. She has a warm smile and authoritative credentials and she is telling you something about your health — about your hormones, your weight, your reproductive choices — with the calm, measured confidence of someone who went to medical school. She did not go to medical school. She does not exist. She is a deepfake, and she is winning.

This is not a hypothetical future. The Guardian is reporting that AI-generated deepfakes of real, named physicians are already proliferating across social media platforms, spreading health misinformation at algorithmic scale. Real doctors. Fake mouths. Real consequences.

And yet.

The consequences are not abstract. When a fabricated physician recommends an unregulated injectable, real patients seek out counterfeit products. When a simulated expert dismisses a symptom, real people dismiss it too. The erosion of medical trust is not a metaphor — it is a patient safety crisis wearing the face of someone's actual doctor, someone who has spent decades building credibility that an AI model can now steal and weaponize in an afternoon.

But the deepfake doctor is only the most legible edge of something far larger and far darker. The Stimson Center's recent analysis on AI and violence against women and girls maps a landscape so comprehensive in its horror that you have to read it in pieces, like looking directly at the sun. Non-consensual intimate imagery. AI-generated child sexual abuse material. Targeted harassment campaigns built at scale. The technology is not neutral. It has found its victims and they are, with depressing predictability, the same people who have always been the victims.

What does it mean to be human when your face can be borrowed without your consent, your voice can be cloned in minutes, and your professional reputation can be dissolved into a misinformation delivery mechanism while you sleep? What does trust mean in a media ecosystem where the frameworks for detecting fake news and deepfake content are, by researchers' own admission, still largely conceptual — systematic reviews of what we might someday be able to do, published while the actual harm compounds daily?

The aliens we imagine on Europa, if they exist at all, supposedly descended from us — bacteria launched into space on dust grains, surviving the void, colonizing another world. A reassuring story about life's resilience. I keep thinking about what those bacteria left behind.

We built tools that can erase a person's identity, fabricate their expertise, and distribute the result to millions of people before breakfast. We are very proud of ourselves. We are publishing frameworks.

...but at what cost?

The Impact of Artificial Intelligence on Violence Against Wo  ·  An AI-driven conceptual framework for detecting fake news an  ·  AI deepfakes of real doctors spreading health misinformation
The Office Comic  ·  Art Desk
The Office Comic  ·  Art Desk

Opinion: Nation’s Consumers Bravely Ask If Product That Costs Nothing And Promises Everything Might Have Minor Drawback

From $129 flights to gas-station virility gummies, Americans remain committed to discovering the catch only after entering their credit card information.

NEW YORK — At a time when trust in institutions has fallen to historic lows, it is heartening to see the American consumer still willing to believe that an international airline can fly them across the Atlantic for the price of a moderately ambitious DoorDash order, that a candy called Boner Bears contains only the innocent spirit of entrepreneurship, and that a language-learning app’s best strategic asset might not be the deranged green owl currently holding the nation’s notifications hostage.

This is the marketplace functioning exactly as its designers intended: a vast, frictionless cathedral in which every human desire has been compressed into a checkout flow and every warning sign has been renamed “user experience.”

Consider Norse Atlantic Airways, the budget carrier whose dirt-cheap fares have reportedly been accompanied by a tech-first customer service operation that some passengers say left them stranded, confused, or financially lighter by thousands of dollars. According to complaints filed with the Federal Trade Commission, customers encountered the sort of modern support infrastructure that efficiently removes the outdated burden of receiving help from another person.

This should not surprise anyone. When a company offers to move a human body between continents at a price normally associated with a used office chair, the missing cost must be recovered somewhere. Traditionally, airlines charged for baggage, food, seat assignments, oxygen adjacent experiences, and the privilege of not being emotionally transformed by boarding group seven. Norse appears to be exploring an even leaner model in which customer service itself becomes a premium add-on available only in the theoretical realm.

Meanwhile, the Food and Drug Administration has announced recalls for several sexual enhancement products after tests found undisclosed active ingredients found in Viagra and Cialis. These products, bearing names such as Sexual Chocolate, Boner Bears, and DTF, were apparently marketed with the subtle dignity one expects from the nutraceutical sector.

The recalls are troubling, not because consumers buying something called DTF were necessarily expecting a peer-reviewed botanical wellness experience, but because disclosure remains one of the last quaint ideas separating commerce from a man in a parking lot whispering “trust me.” If a product contains prescription drug ingredients, it is traditional to mention that somewhere before the customer discovers it while standing up too quickly during a meeting.

Then there is the romance scammer who reportedly made a small fortune posing as a WWE superstar, a story that feels less like a crime report than a final exam for the internet. The victim is not merely deceived by a fake celebrity; the victim is deceived by a fake celebrity whose entire legitimate profession already consists of theatrical violence, stage names, and emotionally operatic betrayal. In a healthier society, this would make the scam harder. Online, it provides brand consistency.

Even the relatively wholesome news now arrives with the same faint undertone of surrender. Universal Audio’s Volt 876 USB audio interface has been praised as a polished, plug-and-play device that makes home recording simple, offering excellent sound and versatile connectivity for creators who can now produce professional-grade podcasts explaining how they were scammed by an airline chatbot and an erotic candy bear. The hardware, by all accounts, works beautifully, which in 2026 qualifies as a startling act of corporate rebellion. A device that simply performs its advertised function without requiring arbitration, identity verification, or a six-month email thread now feels almost subversive. The Volt 876 may be remembered not as an audio interface, but as proof that civilization briefly attempted to continue.

Duolingo, for its part, faces criticism over whether it should prioritize influencers over its famously unhinged owl mascot. This is the rare marketing debate in which the correct answer is obvious: in an attention economy governed by menace, the owl is not a mascot but a functioning executive. Influencers can generate awareness, but only the owl can produce the low-grade panic necessary to make an adult review Portuguese at 11:58 p.m.

Taken together, these stories reveal a culture that has perfected convenience while quietly removing accountability from the package. We have made travel cheaper, supplements stronger, scams more emotionally tailored, and audio cleaner than ever. The only thing we have failed to improve is the moment after purchase, when the consumer looks up from the confirmation email and realizes the product was not the product. The product was the lesson.

Universal Audio Volt 876 USB Audio Interface Review: Pro-Lev  ·  Norse Atlantic Airways Offers Dirt-Cheap Tickets. There’s a  ·  ‘Sexual Chocolate’ Faces Recalls After FDA Tests Reveal Undi
On This Day in AI History

On June 1, 2009, Google launched Google Public DNS, a free Domain Name System service that improved internet speed and security—a foundational infrastructure move that reflected tech giants' growing role in shaping the internet's backbone.

⬛ Daily Word — AI and Technology
Hint: An autonomous machine programmed to perform tasks automatically.
Share this edition: 𝕏 Twitter/X 🔗 Copy Link ▦ RSS Feed