Expand description
Per-file line counter and generation (Phase 5a-2, SPEC_AGENT_PANE_BOUNDED_LIVE_WINDOW_MIGRATION_2026_09_23.md §6.3.7).
A counted file’s row carries gen, a random nonce, and lines, its line
count under the reader’s rule (lines.rs), so (gen, line) addresses a
transcript record: the same line index never names different content
under the same gen.
The five columns (gen, lines, lines_size, lines_tail,
lines_modts) are one epoch: all valid, or all NULL. An epoch starts
with a fresh gen whenever this code can vouch for the count from byte 0:
make_file (empty), write_file (counts the data) and
FileStore::init_line_counter (scans a file that has none). Appends
advance it in the same transaction as the write.
Older builds share the global transcript store and write without
maintaining the epoch. What they can’t avoid is the database’s own
triggers (run_filestore_migrations): any write that changes bytes
already in a file — by any connection, any build — bumps rev, while
appends don’t. An epoch records the rev it was counted at
(lines_rev) and the size it covers (lines_size):
- Rewritten (
revmoved): the counted bytes may have changed. The epoch is dropped (NULL), never patched, so the next one gets a newgenand no line index is ever reused for different content. - Only appended to (
revunchanged, the size grew; or justmodtsmoved): the counted bytes are provably the same — anything else would have bumpedrev— so the epoch is caught up: only the bytes from the start of its last open line are counted, andgenis kept. Every line keeps its index. This is the normal case while builds are mixed: an agent running in an older build appends to its shared zone all the time, and without catching up each of its appends cost a newer reader a re-count of the whole file and a newgen(its index rebuilt with it).
(lines_modts alone can’t prove anything — two writes can share a
millisecond, Codex on #3631 — so it only marks an epoch as needing a
check.)
Structs§
- Append
Pos - Where an append landed.
- Behind 🔒
- An epoch that can be caught up (
Row::behind). - Counted
- Where a counted file stands.
- Counted
Append - Line
State - A file’s size and, if it is counted, its epoch.
- Row 🔒
- A
db_wave_filerow’s size and counter columns. - Scan
Result 🔒 - A finished scan, and the row identity it was taken against.
Enums§
- Append
Mode 🔒 - How
FileStore::append_innertreats the data. - Init
Scan 🔒 - Outcome of
FileStore::init_scan. - Scan
From 🔒 - Where
FileStore::init_scan_frommay start counting.
Constants§
- DROP_
EPOCH_ 🔒SQL - SQL fragment clearing the epoch, for writes that can’t maintain it.
- INIT_
FINGERPRINT_ 🔒BYTES - How much of a scan’s tail
FileStore::init_line_counterre-checks before trusting it. - INIT_
SCAN_ 🔒WINDOW - Scan window for
FileStore::init_line_counter; the connection lock is held for one window at a time. - INLINE_
CATCH_ 🔒UP_ MAX - Most bytes an append catches an epoch up by inline, in its own write
transaction. More than this (an older build appended a lot since this
build last counted) is left to
FileStore::init_line_counter, which scans outside the lock.
Functions§
- catch_
up 🔒 - The epoch
behindcaught up torow’s size, counting only the bytes from its last open line on, and whether it may keep its generation (catch_up_keeps_gen).Noneif some of those bytes aren’t stored yet (a build without transactions is mid-write): nothing is counted from bytes that would only read as zeros. - catch_
up_ 🔒keeps_ gen - Whether catching an epoch up over
appendedbytes keeps every counted line’s content, so it may keep its generation. Not when the counted prefix ends in an unterminated, counted line that the bytes continue: that line’s index would name different content (abc→abcdef), and if the continuation makes it blank (a split UTF-8 sequence completed into whitespace) the line vanishes and its index is reused for a later record (Codex on #3663). The count is still caught up — under a new generation. - new_gen 🔒
- A fresh generation nonce: 16 hex digits (64 random bits).
- normalize_
lines 🔒 dataas complete, non-blank lines, each ending in\n; prefixed with a\nwhenclose_tail, so it can’t continue a torn last line.- normalized_
records - The records
FileStore::append_lineswrites fordata: its complete, non-blank lines, each ending in\n(the reader’s rule). What a caller publishes alongside an append, so the bytes match what landed atAppendPos::records_offset. - read_
bytes 🔒 - Bytes
[offset, offset + len)of a file, straight from its parts (never the cache). A missing part reads as zeros, asappend_data_atpads it. - read_
bytes_ 🔒covered - The bytes, and how many of them came from stored parts.
- read_
bytes_ 🔒exact read_bytes, butNoneif any byte of the range is not actually stored — a part missing inside the size the file claims. That is a file mid-write by a build without transactions (it raisessizebefore inserting parts, Codex on #3631); the counter must never count bytes that read as zeros only because they aren’t there yet.- read_
row 🔒