archived-thread-guard.tsfix(threads): a thread marked done stays gone, whatever writes the list
Round 1 treated the optimistic removal as an edit and repaired the one
window it could name -- a listThreads response issued inside the archive
round-trip -- by re-applying the removal once, the instant the mutation
settled. That assumed a 227-380ms round-trip. Staging measures
mail.markDone at up to 7381ms, and all three resurrections prod ever
recorded landed 15.7-18.2s after the click, every one a fetch-response:
seconds after the one-shot repair had already run, with nothing left to
oppose them.
So the removal becomes an invariant instead of an edit. While a thread is
guarded, any write to a mail.listThreads query that reintroduces it is
repaired immediately -- late fetch response, IndexedDB restore, side-inbox
prefetch and fetchNextPage alike -- so we no longer have to name the
guilty write. The guard is time-boxed at 60s; markActive, undo and
mutation rollback all release first, so a legitimate return still lands.
Suppressions now report to the server, not PostHog. The previous round's
diagnostic was PostHog-only and the browser reporting the bug sends
PostHog nothing -- 66 archives on staging produced zero client events --
so the fix could never be verified. mail.reportListSuppression writes a
structured log readable in CloudWatch.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 24, 2026, 3:51 PMuse-optimistic-actions.tsMerge origin/staging into fix/dm-container-kind
Both branches built org-shared MCP connections independently, so the merge
picks ONE mechanism and layers the other work on top of it.
KEPT (staging) `metadata.scope` / `organizationId` + callability.ts as the
single visibility rule for every MCP surface. It resolves
name collisions deterministically, narrows in SQL and
enforces in JS, and it is what the discovery-based OAuth
callback and the credential vault already write.
DROPPED (ours) `metadata.orgShared` and the peer-scan in
visible-connections.ts. That module survives as the
agent-facing ADAPTER over findMcpConnections — `ownedByMe`
and the explicit-grant name set — so nothing re-implements
a cross-tenant WHERE.
KEPT (ours) `toolPolicy` (the per-tool ceiling) and `requiresAgentGrant`
(which of a person's agents reach a connection). Staging has
no equivalent; they are a different axis from scope, and the
types now say so.
Settings loses mcp-integration-card.tsx to staging's Systems and credentials
section, and the tool-permissions checklist is ported onto system-row.tsx so
the ceiling is still editable. The agent's own picker keeps its shape and now
reads staging's KNOWN_MCP_PROVIDERS, resolving a provider id through discovery
at click time instead of from a second hard-coded catalogue. `probeMcpServer`
became discovery, so the pre-save tools/list probe is `probeMcpServerTools`.
Also reconciled outside MCP: the Slack Connect webhook scoping (staging's, which
handles externally shared channels) keeping this branch's stronger workspace
resolver; the agent-archive and subagent-write metadata stamps, chained rather
than chosen between; buildAgentDocs' generic folder model with this branch's
`listed` on top; and the conferencing-aware event popover with the extracted
RSVP hook.
Fixes carried by the merge: coach-meeting and coach-weekly had no folder and
would have landed in Active; a duplicate source_workspace_id column; a duplicate
import; the dead org-admin catalogue bypass.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 4:15 PM1 defect