feat(session): review wire protocol and daemon mirror derived from the intent vocabulary (Phase 3) - #722
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Greptile SummaryThe PR introduces the review wire protocol and daemon-side mirror that expose producer publications and digest-verified resources to session clients.
Confidence Score: 5/5The PR appears safe to merge; no concrete changed-code failure remained after checking action concurrency, resource bounds, and mirror lifecycle behavior. Remote stale-action checks use the publication classifier correctly, resource sizes remain below the production cache ceiling with eviction preserving the completed-byte budget, and failure or generation-change paths clean up reservations and prevent obsolete resources from being served. Important Files Changed
Sequence DiagramsequenceDiagram
participant Producer as ReviewProducer
participant Mirror as Daemon ReviewMirror
participant Cache as Resource Cache
participant Client as Session Client
Producer->>Mirror: Publish generation, revision, catalog
Client->>Mirror: Request review with patch
Mirror->>Cache: Load generation-qualified resource
alt cache miss
Cache->>Producer: Request bounded chunks
Producer-->>Cache: Chunk envelopes
Cache->>Cache: Assemble and verify digest
end
Cache-->>Client: Reconstructed patch
Client->>Mirror: Apply review action
Mirror->>Producer: Validated ReviewIntent
Producer-->>Mirror: Updated publication
Reviews (1): Last reviewed commit: "refactor(session): name the action resul..." | Re-trigger Greptile |
f0be5f1 to
546e4f4
Compare
1dad608 to
cf89bb7
Compare
A browser client needs a wire schema before it needs a transport, and the prototype's schema was a hand-copy of the intent union restated in three more places — so an intent added to one was silently unreachable from the others (browser-review-seam-audit.md, B12). This lands the schema by derivation instead: `REVIEW_INTENT_TYPES` is the vocabulary, made total in both directions by type assertions, and the wire is that list minus a named exclusion list that is empty and says why. The wire type is derived the same way, so an action is validated and narrowed rather than restated, and `toReviewIntent` lowers it back. Two fields the browser cannot work without arrive from version one. `expandedLineProof` (B10) makes a line inside an expanded gap addressable at all — the prototype's browser had no field for it and its clicks in expanded regions were rejected or mis-sided — and it is exactly the claim core resolves rather than a wire-local twin. An actor tag (G2) records who acted, with no policy attached, so deciding one later changes behavior rather than the schema. Also lands the reader half of the resource model: one `ReviewChunkAssembler` in core that holds a stream to what it declares, requires bounded progress, and verifies the assembled bytes with an injected digest function (C2). The resource-read request moves into core so the producer and the wire parse one shape, and the failure vocabulary is composed from the codes that already exist rather than restated as a third list (D5). `scripts/review-vocabulary.test.ts` is the plan's rung 5: the wire vocabulary equals the intent vocabulary minus its exclusions, every type in it reaches a parser, no session module re-declares a name the review model exports, no module writes its own digest pattern, and the transport frame the browser-safe protocol deliberately does not import still fits a complete envelope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
…as resources The daemon knew a session's files but not which review it was serving, and it learned a file's patch text by being handed a copy of it in the registration — so every patch of every live session sat in daemon memory for as long as those sessions were open, and a review whose patches exceeded the registration budget failed to register at all rather than appearing without them. The daemon now mirrors what a session publishes: a position and a resource catalog. Ordering that mirror is one `classifyReviewPublication` call and nothing else (C1) — the prototype's mirror had its own comparison, which is one of the five disagreeing acceptance rules the audit found. A generation change retires the old generation's cached bytes and in-flight reads with it. Bulky content is read back on demand instead: bounded chunks, assembled and verified by the shared assembler, single-flight per resource, and reserved against a daemon-wide in-flight budget before any bytes are requested (C2). There is one load loop where the prototype had two near-verbatim copies inside one file that already disagreed about progress and end-of-stream rules. An unmeasured resource reserves one chunk and grows to what the writer declares rather than reserving its kind's ceiling, so ordinary patches still load in parallel. Review actions are forwarded to the producer that owns the review state and planned through the same intent path the keyboard uses. The daemon decides nothing semantic — in particular it never re-derives where a note hangs, which is what the prototype's broker copy got wrong by dropping the fallback branch and rejecting legal expanded-gap notes (D3). The producer attaches to the terminal's own store at mount, so a remote action and a key press move the same review, and the controller now projects its document with the source label the producer publishes under so both address files by the same keys. `hunk session review --include-patch` produces exactly the same text as before, now reconstructed from the generation that published it under the shared load concurrency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
`review --include-patch` now reads patch bodies from the live session rather than from a copy the daemon was handed, so it has a failure mode it did not have before: the session can reload or close mid-read. That message goes through the same catalog every other agent-facing message uses, so the generated skill quotes wording the CLI really throws, and it carries the remedy — re-run, or drop `--include-patch` when only file and hunk structure is needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
…nce harness Import gates prove a tier may use a shared primitive; only the harness proves it does. The mirror answers the Phase 2 ordering corpus by being driven through it — seed a position, hand it an arriving publication, read the verdict out of what it did — which is what shows it has no comparison of its own. The wire answers a new corpus of every action in the vocabulary paired with the intent it lowers to, written by hand, so a wire type drifting from the semantics it carries fails here rather than at a client. The adversarial cases are the two this phase owes. B10: a draft and a note on a line inside an expanded gap, plus evidence that accompanies no line, which is malformed rather than tolerated. D1: the wire runs the note-bounds corpus as a consumer, so the note whose summary, rationale, and markup each pass a per-field check while the whole note is triple the bound is now refused at the wire instead of being admitted and then failing at the publisher. The audit records the repayments and opens with a run boundary: this run stops after Phase 3, so the findings left for Phases 4-6 are listed where the next run will look for them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
The action result was spelled out twice — as an alias in the session tier and as a union at the broker's dispatch site — while the resource result already had a name in the protocol. Giving it the same treatment leaves one spelling of what a producer answers an action with, which is the point of the module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
546e4f4 to
89826ed
Compare
cf89bb7 to
aa2bfa0
Compare
Phase 3 of the browser-review rebuild (
docs/browser-review-rebuild.md) — the final phase of this run. Stacked on Phase 2 (#721). Phases 4–6 (HTTP surface, browser client, entry points) are deferred; the audit doc now carries a run-boundary note listing exactly what remains open.What
The wire protocol and the daemon's review mirror — the layer that makes the producer's publications consumable beyond the terminal process, serving the existing
hunk sessionagent surface. Five commits: wire schema → mirror + resources → agent errors → conformance → naming cleanup.src/session/reviewProtocol.ts— the wire schema derived from the intent vocabulary (B12):HUNK_REVIEW_ACTION_TYPES = deriveReviewActionTypes(REVIEW_INTENT_TYPES, exclusions)with an empty, documented exclusion list — all twelve intents are wire-reachable, and a wire-reachable intent without a parser fails to compile. CarriesexpandedLineProof(B10) and a required actor tag (clientId+terminal|browser|agent, G2 wire fields — policy deliberately not applied) from version one, so the browser never needs a schema break. Passes the Phase 0 browser-safe boundary gate.src/session/broker/reviewMirror.ts— the daemon mirrors each session's publication with exactly oneclassifyReviewPublicationcall (C1 broker site): a publication address plus a resource catalog — no notes, no manifest. The prototype mirrored notes, which is what forced its broker to re-derive anchors (D3, the dropped-fallback-branch bug); not mirroring them makes that duplication impossible rather than merely fixed.core/review/resourceAssembly.ts— the single chunk-assembly/verification path (C2 broker site) with an injected hasher; the broker's loop only fetches and decodes.reviewResourceCache.tsbounds daemon-wide bytes with reservations taken before bytes are requested (genuinely bounded in-flight — the prototype bounded only completed bytes), and reservation resize so unmeasured resources don't serialize loads meant to run in parallel.hunk session review --include-patchreconstructs patches through bounded resource reads; registrations no longer embed patch text at all (the memory win the resource path exists for), with an older in-process session's inline patches still served unchanged.scripts/review-vocabulary.test.tsasserts wire vocabulary ≡ intent vocabulary minus named exclusions, every wire type actually reaches a parser (probed to distinguish "rejected" from "unrouted"), no session module re-declares a review-model constant or hand-writes a digest regex, andMAX_WS_MESSAGE_BYTES ≥ MAX_HUNK_REVIEW_ENVELOPE_BYTES— the one coupling the browser-safe protocol can't express as an import.The shape, in one diagram
flowchart LR subgraph term["Terminal session (producer)"] STORE["review store"] --> PROD["ReviewProducer"] end subgraph daemon["Daemon (loopback only)"] MIR["reviewMirror<br/>publication address + resource catalog<br/>ONE classify() call — no notes, no anchors"] CACHE["reviewResourceCache<br/>reserved-before-read byte budget"] ASM["core resourceAssembly<br/>single verification path, injected hasher"] MIR --- CACHE --- ASM end PROD -- "publishes generation:producer:seq" --> MIR AGENT["hunk session CLI<br/>review --include-patch"] -- "bounded chunk reads" --> MIR AGENT -- "apply_review_action<br/>{actor, expandedLineProof, …}" --> MIR MIR -- "toReviewIntent → producer.applyIntent<br/>(same planner as the keyboard)" --> PROD WEB["browser — Phase 5"] -.-> MIR subgraph derive["Wire schema is derived, not restated"] IV["REVIEW_INTENT_TYPES (core)"] -->|"deriveReviewActionTypes<br/>exclusions: [] (documented)"| WT["HUNK_REVIEW_ACTION_TYPES"] WT -->|"parser table keyed by vocabulary<br/>missing parser = compile error"| P["strict parsers on shared validation"] endFindings repaid (recorded inline in the audit doc)
B10 (wire+producer:
ReviewExpandedLineClaim, resolved only byresolveReviewExpandedLine), B12 (derived vocabulary), C1 broker site (one classify), C2 broker site (one assembler), D1 wire site (isTransportableReviewNoteisreviewNoteWithinBounds— the every-field-fits/whole-note-fails case now rejected at the wire, pinned by the harness), D3 broker site (repaid by absence; the prototype's rejected-registration case pinned from the wire end), D5 broker/wire sites, G2 wire fields. Wire round-trip and broker mirror registered as conformance consumers over the Phase 2 ordering fixtures.Behavior notes
hunk sessioncommand output verified byte-identical against a Phase 2 worktree (review --json,--include-patch --json, text mode), via a real TUI session + daemon exercise.--include-patchnow round-trips to the session; a session that closes mid-read fails with a new, specific agent-facing message (generated skill regenerated viabun run generate:skill).hunk session list(previously silently rejected at registration) — net improvement, recorded.apply_review_actionhas no production caller yet by design (Phase 4/5); it is implemented and tested end-to-end (broker → socket → bridge → producer → intent), not stubbed.Gates
bun run typecheck,bun run lint(0/0),bun run format— clean; empty changeset.bun test: 2695 pass / 5 fail — strict subset of the Phase 2 baseline's failures (dev-dep ×2 + three known flakes).bun run test:integration(PTY): 106/106 (baseline had 1 flake).bun test test/session/: 11/11, files untouched (new integration coverage colocated atsrc/session/broker/reviewResources.integration.test.tsto honor that literally).reviewProtocol.tspasses the browser-safe gate written for it in Phase 0; vocabulary-derivation suite green.Generated by Claude Code