Skip to content

feat(review): producer runtime — generations, bounded resource serving, intent completion (Phase 2) - #721

Open
benvinegar wants to merge 5 commits into
claude/review-rebuild-phase-1-pr3from
claude/review-rebuild-phase-2
Open

feat(review): producer runtime — generations, bounded resource serving, intent completion (Phase 2)#721
benvinegar wants to merge 5 commits into
claude/review-rebuild-phase-1-pr3from
claude/review-rebuild-phase-2

Conversation

@benvinegar

Copy link
Copy Markdown
Member

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 session agent surface only (no HTTP, no browser). Five commits: core primitives → intent completion → producer runtime → conformance → audit records.

  • Ordering contract (core/review/generationOrder.ts): one named module answering "is this publication acceptable given what I have" — classifyReviewPublication → accepted | stale | gap plus the producer's self-check assertReviewPublicationAdvance. 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.
  • Resource serving (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 into unknown-resource.
  • Intent completion: expansion/toggle (validated via reviewGapAddress) and notes/start-draft landed; SEMANTIC_COMMANDS_WITHOUT_REVIEW_EFFECT is now empty — every semantic command has a declared, wire-lowerable review effect (closes F2).
  • Validation single-sourcing (D1/D4/D5 producer sites): reviewNoteWithinBounds measured one way; assertCanonicalFileMatchesManifest comparing 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 as ReviewDigestFn. The node-debt map is now empty.
  • Conformance: the producer registered as a harness consumer; 10 hand-written ordering-classification fixtures (non-contiguous revisions, generation swap with lower revision, retired-generation replay, foreign producer) plus note-bounds adversarial cases.

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()"| GEN
Loading
sequenceDiagram
    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)
    end
Loading

Design decisions worth review

  • Publication is layered, not merged: ReviewDocumentV1 gained no generation/resource/digest fields — those live in ReviewPublication at the producer tier. The document stays a document.
  • Resource ids are derived (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.
  • Width-only republication is a replay decision at the emitter, not a loosened comparison — Phase 3's broker must not reintroduce the prototype's "equal revision allowed sometimes" special case into classifyReviewPublication.
  • The producer attaches to the controller-owned store rather than taking ownership — relocating state ownership is a behavior change, flagged for Phase 3 instead of smuggled in.
  • One deliberately removed conformance consumer, reported honestly: producer-side navigation fixtures would have required a store.replace API 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 through producer.applyIntent.

Behavior

hunk session output is byte-identical (registration/snapshot project from the publication; existing assertions pass unchanged). test/session/ and test/pty/ untouched — verified empty diff. One deliberate tightening: expansion/toggle now 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.
  • Boundary suite green; node-debt map empty; real render smoke of bun run src/main.tsx -- diff verified.

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

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hunk-web Ignored Ignored Preview Aug 13, 2026 12:49am

Request Review

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This pull request adds the producer-side browser-review runtime, including ordered generations, bounded and digest-verified resources, semantic intent completion, and session registration integration.

  • Adds generation ordering and publication primitives shared by future producer, broker, and browser layers.
  • Adds single-flight resource materialization, chunked reads, digest verification, cache bounds, and typed failures.
  • Adds draft-start and gap-expansion intents plus shared note, canonical-file, and digest validation.
  • Integrates producer publications into initial session registration and reload handling, with conformance coverage.

Confidence Score: 4/5

The 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

Filename Overview
src/app/review/producer.ts Introduces publication generation ownership, resource serving, and semantic-intent application; no blocking defect was established.
src/app/review/resourceStore.ts Adds single-flight production, bounded bulk loading, digest verification, and cache eviction; direct reads do not use the bulk concurrency gate, but that path currently lacks a production caller.
src/core/review/generationOrder.ts Centralizes publication ordering and producer advance validation with comprehensive boundary tests.
src/core/review/intents.ts Adds draft-start and expansion-toggle intents; explicit draft targets are trusted, but currently reachable UI callers derive them from valid rendered geometry.
src/ui/AppHost.tsx Publishes a new producer generation before replacing session registration during reload and preserves existing soft/full reload behavior.
src/session/app/registration.ts Projects producer publications into the existing session registration and snapshot shapes without an established compatibility regression.

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
Loading
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

Comment thread src/app/review/digest.ts
Comment on lines +10 to +11
import { createHash } from "node:crypto";
import { REVIEW_DIGEST_ALGORITHM, type ReviewDigestFn } from "../../core/review/validation";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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!

claude added 5 commits August 13, 2026 00:48
…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
@benvinegar
benvinegar force-pushed the claude/review-rebuild-phase-2 branch from cf89bb7 to aa2bfa0 Compare August 13, 2026 00:49
@benvinegar
benvinegar force-pushed the claude/review-rebuild-phase-1-pr3 branch from f9d9245 to 9ebd8ed Compare August 13, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants