stacksfix(chat): an approval card that names what it is about to delete
The delete-document card read "Delete document" over a bare UUID — the payload
carried a documentId and the renderer never looked at it, so a cleanup that
proposed two hundred deletions was two hundred identical cards. The tool now
reads the document's name before deleting (afterwards the row is gone) and sends
it as documentTitle + documentPath; cards persisted before that resolve the name
from the live document. All eleven proposed-mutation types now name their
subject through one descriptor table, laid out like the calendar proposal.
A card with no pendingMutationId says so rather than offering an Approve that can
only toast — off the Agent-SDK harness nothing persists one, and pending
mutations expire from KV after an hour.
document.delete stays auto-apply, deliberately: it is a tombstone, and this adds
the other half of that promise — purgeExpiredDeletedDocuments hard-deletes
tombstones past a 30-day window, on the nightly cron, with a dry-run-by-default
CLI for operators. It never purges a tombstone that still has a LIVE descendant:
parent_id cascades on a real DELETE, and a "no live direct child" check misses
the grandchild two levels down.
Also gates the conversation a chat thread claims to be about. primaryConversation
is committed automatically from whatever the shell has open, and the thread-row
seed wrote the client's snapshot verbatim — which is how a /home chat ended up
committed to a deal in another org, invisible in the UI and handed to every tool
in the turn as its operating deal.
And email-thread context chips wear the real Gmail mark, from one shared
ContextKindIcon the rail and the chips both draw from instead of each deciding.
Co-Authored-By: Claude Opus 5 <<email>>Sep 7, 2026, 2:08 PM11 defectscedar-aws-stack.tsfeat: adding cloudtrailApr 13, 2026, 10:36 AMcedar-environment-stack.tsfix: address code review findings
- Fix jq .alarms[] → $alarms[] so firing alarm details are included
in the AI summary (was always empty due to null identity input)
- Remove duplicate --argjson alarms flag
- Add --max-time 30 --connect-timeout 10 to Anthropic API curl call
- Add SQS depth alarms to criticalAlarms so queue backlogs block
promotion (were notification-only)
- Fix lint: prefix unused observabilityStack variable with underscore
Co-Authored-By: Claude Opus 4.6 (1M context) <<email>>Apr 7, 2026, 6:07 PMconfig.tsfeat(marketing): /bookdemo, an honest page title, and the apex-domain scaffolding
Google was showing "CedarMail — Experience email the way you want", which is
email-client positioning we left behind; the title and description now match the
landing page's own headline. The generated robots.txt was a bare `Allow: /`, so
the signed-in app, /playground, /cedarAdmin and tokenised /share/ links were all
crawlable — the SEO plugin now disallows the app surface and lists /bookdemo.
"Book a demo" opened a modal, which has no URL to link, share or measure. It is
now a page, so the ask can carry its own social proof: a scrolling customer
marquee under "Join the fastest growing sales teams". Extracting the fields into
DemoRequestForm left BookDemoForm with nothing to do, so it goes.
The landing page is moving to the cedarcopilot.com apex with mail.* reserved for
the app. Both hosts keep serving one distribution and one bundle; the split is
decided client-side and gated entirely on VITE_PUBLIC_MARKETING_URL, so until
that names a host differing from the app's, routing is unchanged. Shipping it
inert is deliberate — the apex still 301s back to mail.*, and pointing canonical
or the redirects at it before the DNS cutover would loop. The runbook, and why
the apex is stuck (a Cloudflare edge rule on a zone whose hosting has long since
moved to AWS), are in aws/docs/marketing-domain-split.md.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 20, 2026, 11:16 PMruntime-contract.tsMerge origin/staging into feat/mcp-oauth-foundation
Two conflicts, both in the Circleback driver, both where staging improved the
code this branch was replacing.
circleback-search.ts: staging centralised the CLI-version constants that its
private `callMcp`/`parseMcpBody` used. This branch deletes those functions
outright, since the transport moved to the shared MCP helper and the caller now
injects it along with the header. Staging's change is subsumed rather than lost,
and the now-unused imports it added are dropped.
circleback.ts: staging added an `if (!response.ok)` branch logging a structured
warning on an HTTP failure, with a note that a 426 there means the CLI-version
floor moved and every transcript fetch is failing. That block reads `response`,
which no longer exists here because the raw fetch moved into
`callMcpToolForUser`. Its intent is preserved: the catch below logs
`circleback.transcript.fetch_failed` to Axiom with the thrown McpToolCallError's
message, which carries the status. Resolved to this branch's shape, and staging's
`MeetingRecordingUrlResult` import kept alongside.
Verified the auto-merge did not silently revert this branch: the recording-URL
path staging restructured still calls `callCirclebackTool`, not a raw fetch.
Two test updates follow from staging, not from the conflicts:
- the CLI-version assertion pinned the literal '0.2.3'; staging bumped it to
'0.3.0', so the test now asserts against the exported constant and cannot break
on the next bump, which is itself the fix
- `fetchCirclebackRecordingUrl` now returns `MeetingRecordingUrlResult` rather
than a bare string, so a caller can tell "no recording" from "not ready"; the
assertions compare the whole object and pin the status
2,206 server tests and 6 frontend tests pass, both typechecks clean, no
dependency violations.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 1:45 PM1 defect