nx_memindex_put.nx
buildroot/runtime/nx_memindex_put.nx
about
nx_memindex_put.nx -- the ATOMIC, SLUG-KEYED upsert for the HAND-CURATED half of the memory index
(F826 / debt seq273; the contention half of the class whose budget half shipped as F825).
WHY THIS EXISTS. The COINDEX block is a conflict-free rail: sessions APPEND to a journal and one
writer splices the derived block. The curated pointer block below it had no such path, so every
session mutated the shared index with a read-modify-write Edit -- which raced siblings FIVE times in
one session and once produced a MANGLED CONCATENATED LINE. The defect is structural: a stale read
plus a blind overwrite. This organ removes both -- it re-reads the file INSIDE the lock and rewrites
exactly ONE slug-owned line, so a sibling's line can never be clobbered by a stale view.
WHY NOT MAKE THE CURATED BLOCK JOURNAL-DERIVED TOO? Because it is genuinely EDITORIAL: the pointers
are grouped thematically (DEPLOY/MGMT, COORDINATION/PM, ...) by human judgment, and deriving it
per-slug would destroy that grouping. So authorship stays human; only the MUTATION becomes atomic.
nx_memindex_put <journal> <md> <slug> <line> [ovfl]
Locks <journal>.lock -- the SAME lock nx_coindex append and nx_memindex_emit take -- so put, append
and splice are fully serialized against each other, not merely against other puts.
---- BUDGET + AUTO-EVICT (added 2026-07-20 for debt seq255 sev7 / F847; ADDITIVE, rule-19) --------
The upsert above kills the CLOBBER half of seq255. It does NOT kill the SCALE half, which the debt
row names precisely: "unbounded growth (20.4->21.2KB mid-session, over the 17.1KB soft target,
siblings add faster than anyone can hand-compact) + no session can safely compact". A WARNING does
not fix that -- warnings are addressed to a librarian who, by the row's own account, cannot keep up.
So with an [ovfl] file the zone becomes a true RAIL, symmetric with the COINDEX block: the budget is
DERIVED (whole-file target minus everything outside the zone, so it self-adjusts as the rest of the
file moves) and the OLDEST lines auto-evict into the overflow file's CURATED-OVFL block.
Orientation matters and is not arbitrary: this organ APPENDS new lines at the END of the block, so
the zone is oldest-first and ci_split_at_budget -- which keeps the newest SUFFIX -- is exactly the
right splitter, reused rather than re-derived.
NO-LOSS beats budget: if the overflow file is missing or markerless, NOTHING is evicted and the
organ says so. Omitting [ovfl] preserves the old behavior byte-for-byte (warn, never evict).
Structural lines (thematic groups, LAWS, DOCTRINE, the gotcha tail) live OUTSIDE the markers and are
unreachable here, so the round-4 injury -- silently dropping the file's own laws -- cannot recur.
Each managed line carries an invisible owner marker `<!--s:SLUG-->` at its end. Upsert = replace the
line carrying that marker IN PLACE (position preserved, so the editorial grouping survives), else
append just before the CURATED:END marker.
FAIL-CLOSED: missing/misordered CURATED markers -> file untouched and says so (never guesses where
to write). ATOMIC: tmp + fsync + renameat, so a concurrent reader sees whole-old or whole-new.
IDEMPOTENT: putting identical text twice leaves the file byte-identical.
It also REPORTS the resulting curated size against the whole-file target, so a writer learns it is
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_coindex_core.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 46 | const MP_BUF: i64 = 262144 |
| 47 | const MP_NL: i64 = 10 |
| 48 | const MP_TARGET: i64 = 17100 |
| 51 | const MP_BLOCK_MIN: i64 = 2000 |
| 58 | const MP_ENTRY_CAP: i64 = 700 |
| 59 | const MP_PATHCAP: i64 = 1024 |
| 60 | const MP_MSGCAP: i64 = 1024 |
| 61 | const MP_MODE: i64 = 420 |
| 62 | const MP_EXIT_USAGE: i64 = 2 |
| 63 | const MP_EXIT_MARKERS: i64 = 3 |
| 64 | const MP_EXIT_IO: i64 = 1 |
functions
| 67 | func mp_parse_num(s: *u8) -> i64 called by 1: main |
| 79 | func mp_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 80 | func mp_putn(v: i64) -> i64 { nxi_out(v); return 0 } |
| 83 | func mp_find(hay: *u8, n: i64, needle: *u8) -> i64 |
| 97 | func mp_span_has(hay: *u8, a: i64, b: i64, needle: *u8) -> i64 |
| 122 | func mp_ovfl_bank(ovp: *u8, lines: *u8, n: i64) -> i64 |
| 172 | func main(argc: i64, argv: *i64) -> i64 |