The key itself, its domain, and the witnesses for both. No gating and no
replay yet.
**DERIVED FROM A FINGERPRINT, NOT BUMPED AT MUTATION SITES.**
`panel_mapping_fingerprint` hashes what actually decides which byte a
cell means --- buffer identity, grid rows and columns, `view_top`,
`view_left`, wrap mode, content columns, fold POLICY and fold CONTENT,
and the buffer's content revision --- and the generation advances
whenever that changes. This makes the changing/stable split
STRUCTURAL: an input that is hashed moves the key by construction, and
one that is not cannot. Bumping by hand at each mutation site would
have made "advances after any mapping mutation" a promise about
someone remembering.
Folds are hashed at their SOURCE, the registry's ranges, rather than
through the derived `VisibleLineMap`, whose only public summary is
`is_identity()` --- too coarse, since a fold edit that leaves the map
non-identity still changes which source line a row shows.
**ONE SEAM, READ BY BOTH SIDES.** `panel_mapping_generation` advances
if the fingerprint changed and returns the current value; projection
will stamp with it and inbound validation will compare against it, so
"what the frontend was shown" and "what the daemon checks" cannot
drift. Computed ON DEMAND, deliberately: a mutation not yet painted has
still changed the inverse, and a gesture arriving in that gap must be
refused. Deriving from the last emitted frame recreates the hole.
**Nondecreasing, and never cleared.** `Absent` yields no key to stamp
--- which is not a key of zero --- but the high-water mark survives, so
a frame delayed across a hide cannot roll authority backward. First
establishment takes 1; zero is the wire's invalid value.
**MUTATION TESTING FOUND MY WITNESSES UNDER-SPECIFIED, TWICE.**
With only the content-edit row present, dropping `view_left` from the
key stayed GREEN, and so did collapsing the grid to `rows * cols`. That
is exactly what the closure predicted --- "a key that ignores
`view_left` passes every row that only scrolls vertically" --- and it
is why G2 is enumerated per input rather than asserted in aggregate.
Six legs now, one per input, each touching only its own.
Mutations that bite: omit the content revision (3 rows), omit
`view_left`, omit wrap, omit fold policy, and include the CURSOR --- a
stable input, caught by G3.
**One mutation does NOT bite, and the row says so rather than
pretending.** Collapsing rows and columns to a product stays green,
because `last_content_cols` co-varies with a column change and the row
count co-varies with a resize: the key moves by another input either
way. Only a transposition (2x6 -> 6x2, identical product) would isolate
it, and no production path reaches one --- rows come from the band's
height, columns from the frame declaration, and nothing swaps them. The
key hashes them separately anyway; hashing a product because no test
can currently tell the difference would be choosing the weaker
construction for the suite's convenience.
G4b --- that an in-flight drag survives a selection repaint through
real replay --- stays owed by the rebased replay lane, which is the only
branch where replay exists.
Verified: focused suite 34/34, `cargo test --lib` green, clippy clean.
Per the standing procedure the eleven-stage `--protocol` gate is
reserved for the next coherent checkpoint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai