feat(review): producer runtime — generations, bounded resource serving, intent completion (Phase 2) - #721
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Greptile SummaryThis pull request adds the producer-side browser-review runtime, including ordered generations, bounded and digest-verified resources, semantic intent completion, and session registration integration.
Confidence Score: 4/5The pull request appears safe to merge, with only a non-blocking repository style inconsistency across the changed TypeScript files. The producer, resource, ordering, intent, and reload paths have no established reachable functional failure, but the changed code systematically violates the required single-quote convention. Files Needing Attention: src/app/review/digest.ts and the other changed TypeScript/TSX files Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
Diff[Diff files] --> Producer[ReviewProducer]
Producer --> Publication[Generation-versioned publication]
Producer --> Store[ReviewResourceStore]
Store --> Resources[Digest-verified resource chunks]
Publication --> Session[Session registration and snapshot]
Resources --> Session
Intent[Semantic review intent] --> Producer
Producer -. requires attached ReviewStore .-> ReviewState[Controller-owned review state]
Reload[AppHost reload] --> Producer
Reload --> Publication
Prompt To Fix All With AI### Issue 1
src/app/review/digest.ts:10-11
**Double-quoted strings violate style**
The newly added TypeScript and TSX code uses double-quoted imports and literals throughout the producer, core review, session, UI, and test files, contrary to the repository's required single-quote convention. This establishes a conflicting formatting pattern and creates avoidable maintenance churn.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "docs(rebuild): record the Phase 2 repaym..." | Re-trigger Greptile |
| import { createHash } from "node:crypto"; | ||
| import { REVIEW_DIGEST_ALGORITHM, type ReviewDigestFn } from "../../core/review/validation"; |
There was a problem hiding this comment.
Double-quoted strings violate style
The newly added TypeScript and TSX code uses double-quoted imports and literals throughout the producer, core review, session, UI, and test files, contrary to the repository's required single-quote convention. This establishes a conflicting formatting pattern and creates avoidable maintenance churn.
Context Used: guidelines.mdc Cursor rule (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/review/digest.ts
Line: 10-11
Comment:
**Double-quoted strings violate style**
The newly added TypeScript and TSX code uses double-quoted imports and literals throughout the producer, core review, session, UI, and test files, contrary to the repository's required single-quote convention. This establishes a conflicting formatting pattern and creates avoidable maintenance churn.
**Context Used:** guidelines.mdc Cursor rule ([source](https://github.com/modem-dev/modem/blob/main/.cursor/rules/guidelines.mdc))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
4165b52 to
f9d9245
Compare
1dad608 to
cf89bb7
Compare
…dation primitives The producer runtime needs three things core did not have: a way to say which snapshot a publication belongs to and whether an arriving one is ahead, a way to address the bulky content a review refers to without inlining it, and one answer to the questions every review boundary asks about untrusted input. The prototype answered each of these several times over. Generation and state- revision acceptance had five implementations with three rules, including a client requiring contiguous revisions the server never promised. Note size was measured per field in one place and per whole note in another, so a note could pass the check that admitted it and then poison the snapshot that published it. Digest patterns disagreed on case, and the canonical-file consistency check existed three times with three field lists, none of which compared hunk content. So: `generationOrder.ts` states the ordering invariant once and classifies an arriving publication as accepted, stale, or a gap; `resources.ts` owns resource addressing, bounds, and distinct failure codes, where integrity never collapses into unknown-resource; `validation.ts` owns byte measurement, exact-key checks, and digest identity, taking hashing as an injected function rather than importing one; `noteBounds.ts` measures a whole note, once; and `canonicalFile.ts` checks a serialized file by projecting it into a content manifest entry and comparing by value, so the field list cannot drift from what the model says a file is. The manifest gains the content that check needs — patch text, hunk blocks, source identity — and `ReviewFileV1` now carries its patch, which is content rather than publication and which identity already hashes. Repays: D1, D4, D5 core sites. The last node-debt entry goes with them: serializing and hashing resources belongs to the producer tier, so the shared model never needs node:crypto and the debt map is now empty. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
…ents The command catalog carried two semantic commands with no declared effect, listed by name so the gap stayed a decision instead of an oversight. Both now have one: `notes/start-draft`, whose identity the caller owns the way it already owns a note id, and `expansion/toggle`, validated against the same gap addressing every renderer draws from and reporting the side, ranges, and source identity a caller needs to fill the gap it just opened. Which gap "toggle unchanged context" reaches is a review rule rather than a terminal one, so `selectReviewGapForSelection` states it in core — the selected hunk's own leading gap, then later hunks', then the file's trailing gap — and the terminal's copy is deleted. Whether a file can be expanded at all is now read from its semantic source identity rather than from the renderer's knowledge of a fetcher. `SEMANTIC_COMMANDS_WITHOUT_REVIEW_EFFECT` is consequently empty: every semantic command lowers to something any attached surface can resolve, which is what lets these two eventually reach a remote client. The terminal behaves identically, with one deliberate tightening: expanding a gap the file does not have is now refused rather than recorded. Only real gap ids are reachable through the UI — the row builder and the shared policy both derive them from core — so this is unreachable in practice, but two unit tests were addressing gaps their fixtures never had and now use fixtures that do. Repays: F2's two remaining commands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
… the session layer Hunk had no answer to "which version of this review are we looking at?" — the session registration was re-derived from the changeset on every reload, and the bulky content an agent might want (patches, expanded source) was either inlined whole or unreachable. This adds the producer that owns that question. `ReviewProducer` publishes one generation per load, asserts its own advance against the shared ordering contract before swapping anything in, and serves each generation's resources — a file's canonical form, its patch, the full source text behind its gaps — in bounded, digest-verified chunks. Every failure has its own code, so an integrity mismatch can never be read as a routine miss. Resource production is single-flight by construction, with a read having no path to the underlying reader except through the in-flight map, and bulk loads run under an explicit concurrency limit rather than an unbounded `Promise.all`. The session surface becomes a consumer of the published document rather than a second reading of the changeset: `hunk session` file and hunk metadata is projected from the generation, byte for byte what it was, and a reload publishes the next generation before the registration is refreshed from it. Hashing is injected at the edge so the shared model keeps needing no hashing runtime. The producer supplies the same annotation index the terminal does, which is why the index moves to `core/review/annotations.ts` and the terminal's copy is deleted: annotated navigation planned at the producer and planned by the keyboard now walk the same stops by construction rather than by coincidence. Repays: D4 and D5 producer sites; the producer side of C1 and C2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
…rdering fixtures The producer joins the corpus every other consumer runs, reaching the same answers by a different route: geometry read back out of the published content manifest rather than from the primitives, and expanded rows resolved from the address the `expansion/toggle` intent settled on. Each fixture also self-checks the boundary the producer really serves, comparing every canonical file it would hand out against the manifest entry for it. Two adversarial corpora land with it. Publication ordering pins the C1 rule from both ends — the classification itself, including the non-contiguous revision the prototype's client wrongly rejected and the generation swap whose revision goes backwards, and the transitions a real producer emits across state changes and reloads. Note bounds pin D1 at the boundary that split the prototype's two measurements apart: a note whose every field fits while the note itself does not. Every expectation is hand-written from the semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
C1 and C2 are repaid on the producer side, D1/D4/D5 on their core and producer sites, and F2's two remaining commands are closed. Each entry names the decisions that came with it rather than only the module: revision skips are legal and a width-only republication is a replay rather than a looser comparison; the canonical-file check reuses the manifest projection instead of carrying its own field list; digests have one canonical spelling and hashing is injected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018L6h5GBz6RAxRXbgUS4mx4
cf89bb7 to
aa2bfa0
Compare
f9d9245 to
9ebd8ed
Compare
Phase 2 of the browser-review rebuild (
docs/browser-review-rebuild.md). Stacked on Phase 1 PR 3 (#720).What
The producer runtime: the terminal session now publishes the semantic review document — generation-versioned, digest-verified, chunk-addressable — serving the existing
hunk sessionagent surface only (no HTTP, no browser). Five commits: core primitives → intent completion → producer runtime → conformance → audit records.core/review/generationOrder.ts): one named module answering "is this publication acceptable given what I have" —classifyReviewPublication → accepted | stale | gapplus the producer's self-checkassertReviewPublicationAdvance. The prototype had five inconsistent implementations of this rule (finding C1); the broker and browser sides will consume this same module in Phase 3/5.core/review/resources.ts+src/app/review/): derived resource ids stable across generations, descriptors that start unmeasured, single-flight materialization with an explicit concurrency bound — the unbounded-Promise.all-with-double-reads bug from the original PR 707 review is now structurally impossible. Distinct error codes; integrity failures can never collapse intounknown-resource.expansion/toggle(validated viareviewGapAddress) andnotes/start-draftlanded;SEMANTIC_COMMANDS_WITHOUT_REVIEW_EFFECTis now empty — every semantic command has a declared, wire-lowerable review effect (closes F2).reviewNoteWithinBoundsmeasured one way;assertCanonicalFileMatchesManifestcomparing by value with no field list (it reuses the manifest projection — a fourth field list can't drift because there is nothing to drift); one canonical digest spelling with hashing injected asReviewDigestFn. The node-debt map is now empty.How publication works now
flowchart LR subgraph terminal["Terminal session"] STORE["review store<br/>(controller-owned)"] PROD["ReviewProducer<br/>src/app/review/producer.ts"] RES["resourceStore<br/>single-flight · concurrency 4<br/>digest-verified chunks"] STORE -- "attachStore" --> PROD PROD --- RES end subgraph core["core/review (renderer-free)"] GEN["generationOrder<br/>accepted | stale | gap"] DOC["document + contentManifest<br/>(now carries patch)"] end PROD -- "publishes ReviewPublication<br/>generation:producerId:sequence" --> SESSION["session layer<br/>(existing hunk session surface)"] PROD -- "self-checks every emission" --> GEN SESSION -- "hunk session review --include-patch<br/>bounded materializeResources()" --> AGENT["agents"] BROKER["broker mirror — Phase 3"] -.->|"same classify()"| GEN WEB["browser — Phase 5"] -.->|"same classify()"| GENsequenceDiagram participant A as agent (hunk session) participant P as producer participant S as resourceStore A->>P: read resource {generation, resourceId, offset, length} P->>P: classify against current generation alt stale generation P-->>A: stale-generation + currentGeneration (resync without a round trip) else current P->>S: materialize (single-flight, bounded) S-->>P: verified chunk (whole-resource digest, eof flag) P-->>A: chunk — or a DISTINCT error code<br/>(integrity ≠ unknown-resource, by construction) endDesign decisions worth review
ReviewDocumentV1gained no generation/resource/digest fields — those live inReviewPublicationat the producer tier. The document stays a document.resource:kind[:side]:fileKey), so the same file keeps its id across generations and cross-generation caching stays expressible — the prototype's per-generation ids made that impossible.classifyReviewPublication.store.replaceAPI invented purely for tests; requirement met structurally instead (the annotation index moved into core — producer and terminal call the same function) plus a unit test throughproducer.applyIntent.Behavior
hunk sessionoutput is byte-identical (registration/snapshot project from the publication; existing assertions pass unchanged).test/session/andtest/pty/untouched — verified empty diff. One deliberate tightening:expansion/togglenow rejects gap ids the file's geometry doesn't address (unreachable through the UI; two unit-test fixtures that toggled nonexistent gaps were corrected and the change disclosed).Gates
bun run typecheck,bun run lint(0/0),bun run format— clean; empty changeset.bun test: 2555 pass / 4 fail — failures a subset of baseline's known set (dev-dep ×2 aside, each verified against a parent-branch worktree, incl. repeated PTY runs on both branches to confirm flake parity).bun run test:integration(PTY): 105/106, the one failure reproducing on base.bun test test/session/: 11/11, files untouched.bun run src/main.tsx -- diffverified.Repays: C1/C2 producer sides, D1/D4/D5 producer sites, F2 closed (recorded inline in
docs/browser-review-seam-audit.md). Phase 3 handoff (ordering contract shape, chunking constants, error-code vocabulary, intent coverage) is recorded in the audit and this stack's task notes.Generated by Claude Code