fix(host): keep the inbox warning's Realtime report to the attempt it names

Merged
DA
6d ago
+145-38

Summary

This addresses Devin's three findings on #4064, which merged through the queue as the review arrived. #4064 added what Realtime's client last reported to the account inbox's 30-second warning. This keeps that report accurate and tests it the way the repository's rules ask.

  • No stale report after a seal failure (thread). What the client reported was cleared only when a new seal arrived. So if the next seal fetch failed, a warning saying "the service sealed nothing" could also carry a closed client's diagnostic. It is now cleared on a failed seal too, so the report always describes the attempt whose end stopped names.

  • The event name is pattern-checked (thread). A diagnostic's event is now held to the same pattern as its code (TROUBLE_WORD, lowercase words and underscores) before it reaches the host's line and Sentry. An entry whose event doesn't match is dropped. Realtime's client draws its events from a fixed catalog, so this is defence in depth, and it has no test: no entry the real client can emit fails the pattern, and troubleOf is private.

  • The refused-seal test checks what a caller sees (thread). It used to check only the fake service's call log. It now shows recovery:

    • Realtime's real client throws on the first seal (not a compact JWE).
    • The service then seals properly, and a stubbed socket admits every part with a result of no rows.
    • The follow publishes one empty account inbox.

    This also covers "an empty part counts as delivered" with the real client.

Evidence

  • pnpm exec vitest run src/compose-coding-agents.test.ts (packages/host): 13 passed, against 12 on main.
    • New test: no stale report. It fails on main's account-inbox-follow.ts (1 failed, 12 passed): its warning carried ; Realtime's client last reported connection_lost beside "the service sealed nothing (unanswered)".
    • The real-client tests (admission refused, and recovery after a refused seal) both fail on the file from before #4064.
  • pnpm check:affected:
    • lint, oxlint, knip and discarded-effect passed.
    • Tests passed for host (418), panel (all three shards) and web (502).
    • Desktop's app-menu.test.ts fails only because the sandbox can't download the Electron binary. CI runs it.
    • Typecheck passed for host, panel, web and desktop, run one package at a time after the concurrent run was killed for memory.

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged (LUKE_UPDATE_FIXTURES not run, or diff explained line by line). (CI)
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added. The event-name pattern is the exception: no entry the real client can emit fails it, so there is no test (see Summary). (manual; root AGENTS.md "Testing")
  • Gateway envelope goldens unchanged. (CI)
  • No new as type assertion outside the allowlisted files. (CI via anti-slop)
  • No Effect.runPromise/runSync/runFork outside the runtime edges and the shims tools/anti-slop/effect-edges.json records. (CI via anti-slop/no-run-promise-outside-edges)
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch outside the files tools/anti-slop/effect-edges.json records. (CI via anti-slop/no-raw-async-primitives)
  • No Effect, Stream, or Layer written as a statement and dropped; a description a caller walks past runs nothing. (CI via pnpm discarded-effect)
  • Test count equals the pre-PR count or the delta is listed: +1 in packages/host/src/compose-coding-agents.test.ts (12 โ†’ 13).
  • Each hand-rolled file the PR replaces is deleted, or the part that remains is a permanent adaptor named in root AGENTS.md's "The permanent adaptors" with the reason it stays.
  • Every CLAUDE.md/AGENTS.md sentence naming a changed part is edited; root pair stays byte-identical. (CI for pair existence; manual for wording)
  • PRIVACY.md unchanged, or the change is a product decision called out in the PR body.
  • Package package.json deps match what the sources import by bare specifier; effect via catalog:. (CI)
  • Barrel/door rule: no Node-reaching Effect module (@effect/platform-node, effect/sql) behind a barrel the renderer or a web function opens. (CI: existing renderer node: grep plus a new grep for those specifiers in renderer bundles)

Notes

  • Neon Realtime on production is still down (see #4064). Nothing here changes that.

Made with Luke


Devin Review