Delta and generations
A review does not sit still. The branch moves, a work-order round lands commits, and the code under the boards changes. This page describes the two deterministic records Rennet keeps straight when that happens: Delta, the change-versus- baseline context the lens drafters read, and the successor account, the comparison of one generation with the next.
Neither is the reviewer-facing round report. A landed coding round builds that report from a separate classification of the exact worker receipt and dispatched asks. Keeping those artifacts distinct matters because they answer different questions with different inputs.
Patchsets stay immutable
Section titled “Patchsets stay immutable”A working tree, branch, or pull request head can change. A captured patchset cannot. Recapture creates a successor instead of replacing the old input.
That is what keeps every mark attached to the material the reviewer actually saw.
Append-then-freeze
Section titled “Append-then-freeze”One visit to a review’s boards over one patchset is a generation. Patchset
identity is content identity, not visit identity: returning to the same patchset
mints a new generation rather than reopening the frozen earlier visit. Inside a
generation the boards are live append-only logs: re-running a lens appends, and
board-native data on surviving element ids persists. When the code moves, the
whole generation freezes immutable and a successor generation is minted. The
frozen generation stays readable as drill-down; nothing is ever edited in
place. The session URL selects a frozen generation with ?generation=<id>;
without that parameter the client reads the live generation. The drill-down is
therefore reloadable and directly addressable, not local switcher state.
Generation documents live in <dataDir>/generations/generations.sqlite. Every
replacement increments the durable revision. Reveal and terminal writes verify the
attempt identity and condition their update on that observed revision in the same
SQLite write. A competing claim between the read and write leaves the newer attempt
intact and suppresses the stale attempt’s progress and completion events. A round
observes its predecessor
before saving the successor, then freezes only that exact revision with one
conditional database update. A competing write leaves the newer generation
intact; the losing round records no frozen-predecessor pointer and emits no
predecessor transition. Old per-generation JSON files are imported on first read
and retained; the database becomes authoritative for that generation. The rounds
ledger stays in its existing store.
What carries
Section titled “What carries”Carry is decided by evidence, never by resemblance.
| What moves | Rule |
|---|---|
| Board content on an element id a regenerated lens keeps | Carried verbatim, with no delta stamp |
| Board-native data — marks, groupings, arrangement, notes | Carried with the element id it sits on |
| An ask, thread, or highlight anchored into board prose | Re-anchored only by one exact quote match in the corresponding successor lens; zero or multiple matches preserve the thread as detached and suppress its stale highlight |
| A code ref whose cited bytes are identical, including through a Git-proven rename | Resolves against the successor patchset |
| A code ref whose cited content changed | Redrafted, and its section carries a new or reworked stamp |
| A code ref whose source is gone | Orphaned, kept with its reason rather than reattached nearby |
Occurrence lineage
Section titled “Occurrence lineage”An occurrence is a reviewable unit inside one patchset. Its ID is scoped to that
patchset. The lineage graph can describe a successor relationship as exact,
one-to-one, move, split, merge, ambiguous, or terminated.
These labels describe a relationship. They do not all authorize carry.
AUTO_CARRY_LINEAGES in @rennet/protocol contains only exact, and
resolveAnchor() reports whether the relationship carries state.
packages/core/src/lineage-matcher.ts computes a graph from content, normalized
content, path, symbol, and surrounding context. It uses global matching and emits
ambiguity when competing candidates are too close. Its unit and measurement
tests exercise the classifier without client repositories.
The classifier is implemented, but the live carry path does not call it. It remains a measured source of lineage data, not evidence that similar code has already inherited a human judgment.
The synthetic corpus keeps the classifier’s trade-offs executable. TP means a correct carry, FP a wrong carry, and FN a missed carry.
| Class | Obs | Fixture pairs | TP | FP | FN | Fixture pass rate | Recall |
|---|---|---|---|---|---|---|---|
exact | 21 | 7 | 21 | 0 | 0 | 100.0% | 100.0% |
move | 5 | 4 | 4 | 2 | 1 | 66.7% | 80.0% |
one-to-one | 6 | 5 | 6 | 0 | 0 | 100.0% | 100.0% |
split | 1 | 1 | 1 | 0 | 0 | 100.0% | 100.0% |
merge | 2 | 1 | 2 | 0 | 0 | 100.0% | 100.0% |
ambiguous | 7 | 2 | 7 | 0 | 0 | 100.0% | 100.0% |
terminated | 3 | 3 | 2 | 0 | 1 | 100.0% | 66.7% |
Exact-only carry. The fixture pass rate is 100.0% over 21 observations from 7 independent fixture pairs. The 95% Wilson lower bound is 84.5%. Wrong carries under the live policy: 0. This must remain 0.
Move stays excluded. Enabling move auto-carry would produce 2 wrong carries on this corpus against 4 correct moves. One delete-plus-copy case reads as relocation, and one decoy keeps the old context and steals the lineage. exact carry safety rests on structure, not this synthetic-corpus pass rate. A byte-identical body at a unique body and path can only be mismatched by a SHA-256 collision. A duplicated body and path fails closed to ambiguous.
The measurement test pins this block verbatim. The corpus uses 19 synthetic patchset pairs and no client code.
The live carry path
Section titled “The live carry path”The review fold in packages/core/src/index.ts compares deterministic file and
span evidence in the successor patchset:
- A byte-identical item at the same path carries.
- A changed item reopens and leaves the active set.
- A disappeared item moves to the orphaned set.
- A whole-file mark on a rename reopens.
- A span mark can carry through a Git-proven rename when its span bytes remain identical.
The distinction between the fuzzy graph and this byte comparison is deliberate and visible in the code. Similarity can help describe a successor; it does not stand in for byte evidence.
The successor account
Section titled “The successor account”The successor account is the bridge from generation N to generation N+1. When a
successor patchset has prior asks, buildSuccessorAccount() builds it
deterministically. It records:
addressed,partially-addressed, oruntouchedfor each ask;- changed paths not covered by an ask;
- new hunks outside the asked spans;
- handoff task attribution when present.
The account feeds the deterministic successor and Delta machinery. It does not become the artifact the reviewer reads. A landed-round report uses one separate, Council-routed classification turn over the successor patchset id, durable dispatched asks, and exact coding-turn receipt. The host copies the durable ask identity and text into a report board, then verifies every claimed evidence anchor against the measured worker diff. That verified report greets the reviewer while the boards regenerate and becomes additional round context for each lens drafter.
At path grain, every changed path is either covered by an ask or listed beyond the asks. When both patchsets are available, the account also compares hunks by their added and deleted line bytes. Context and hunk line numbers do not define a new hunk, so line-number drift alone does not appear as new work.
An unasked hunk is labeled unasked-file when no ask targeted its file, or
asked-file when the file was targeted but the hunk falls outside every asked
span. A truncated patch cannot support a complete hunk comparison, so that file
stays at path grain.
Handoff task attribution applies to asks, not hunks. One harness turn executes the complete work order, so Rennet has no evidence that a particular task caused a particular hunk.
On re-review rounds the account also feeds buildDeltaPacket() in
packages/core/src/delta/ — the folder that owns the hunk index with stable
content-derived ids and the element differ, and assembles the lens drafters’
input from them. Drafters receive those deterministic facts plus the separately
verified report when the round produced one.
Delta marks
Section titled “Delta marks”Composition stamps each touched section of a regenerated lens board new or
reworked; absence of a stamp means the section carried. Which section is
which is decided by content first, then by a shared citation keyed on
(path, side, start line, end line) — never on the optional symbol anchor a
seat may rename or drop over the same lines, and never on the patchset the
citation was minted under. A section that cites nothing falls back to its title;
one that cites code does not, because two generations both writing a “Findings”
section about different code is ordinary, and matching on the shared word made a
new section read as a rework while hiding the old one’s removal. The marks read as
unread state rather than as a changelog. Every section folds to its preview on
arrival, touched or carried alike (Rai, 2026-09-04) — the reader takes the
summaries first and opens what they want. A small transient accent dot per
touched section is what marks it as new; it rolls up to the lens segment, clears
on interaction, and is replaced wholesale by the next round’s stamps.
Code map
Section titled “Code map”| Concern | Owner |
|---|---|
| Lineage types and exact-only carry policy | packages/protocol/src/domain.ts |
| Anchor resolution over a lineage graph | packages/protocol/src/delta/rsp.ts |
| Generation, round record, and session shapes | packages/protocol/src/session/model.ts |
| Section delta stamps and the lens board projection | packages/protocol/src/board/schema.ts, packages/protocol/src/board/lens-board.ts |
| Fuzzy occurrence classifier | packages/core/src/lineage-matcher.ts |
| Hunk index, element differ, and Delta-packet assembly | packages/core/src/delta/ |
| Live carry and successor fold | packages/core/src/index.ts |
| Path and hunk successor account | packages/core/src/successor-account.ts |
| Optional delta digest turn | packages/server/src/delta-digest-live.ts |
| Successor account rendering | packages/app-ui/src/components/successor-account-panel.tsx |
See hand off and the exits for the rounds loop that mints each generation, and architecture contracts for the patchset invariants.