Skip to main content

Module counter

Module counter 

Source
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 (rev moved): the counted bytes may have changed. The epoch is dropped (NULL), never patched, so the next one gets a new gen and no line index is ever reused for different content.
  • Only appended to (rev unchanged, the size grew; or just modts moved): the counted bytes are provably the same — anything else would have bumped rev — so the epoch is caught up: only the bytes from the start of its last open line are counted, and gen is 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 new gen (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§

AppendPos
Where an append landed.
Behind 🔒
An epoch that can be caught up (Row::behind).
Counted
Where a counted file stands.
CountedAppend
LineState
A file’s size and, if it is counted, its epoch.
Row 🔒
A db_wave_file row’s size and counter columns.
ScanResult 🔒
A finished scan, and the row identity it was taken against.

Enums§

AppendMode 🔒
How FileStore::append_inner treats the data.
InitScan 🔒
Outcome of FileStore::init_scan.
ScanFrom 🔒
Where FileStore::init_scan_from may 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_counter re-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 behind caught up to row’s size, counting only the bytes from its last open line on, and whether it may keep its generation (catch_up_keeps_gen). None if 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 appended bytes 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 🔒
data as complete, non-blank lines, each ending in \n; prefixed with a \n when close_tail, so it can’t continue a torn last line.
normalized_records
The records FileStore::append_lines writes for data: 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 at AppendPos::records_offset.
read_bytes 🔒
Bytes [offset, offset + len) of a file, straight from its parts (never the cache). A missing part reads as zeros, as append_data_at pads it.
read_bytes_covered 🔒
The bytes, and how many of them came from stored parts.
read_bytes_exact 🔒
read_bytes, but None if 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 raises size before inserting parts, Codex on #3631); the counter must never count bytes that read as zeros only because they aren’t there yet.
read_row 🔒