code wiki / (root) / nx_store_janitor.nx

nx_store_janitor.nx

buildroot/runtime/nx_store_janitor.nx

19855 B428 linesdepth 2pulls 2 transitivereach 0 importersview sourcekind tooltopic store
docsdependenciesstructsconstsfunctions

about

nx_store_janitor.nx -- reclaim SUPERSEDED seg-store segment files. The janitor pass that seq724/RK013 named as reclaimable and nobody had built. MEASURED 2026-07-30 BEFORE WRITING THIS: knowledge/store holds 63,669 MB across 14,585 segment .docs files, while all 1,088 manifests TOGETHER reference only 3,031 live segments. debt-manifest.txt lists exactly TWO segments while 2,055 debt-seg-*.docs exist on disk, ~2.4 GB for one plane. So roughly 79 percent of the information plane's bytes are segments no reader can ever reach: the seg-store is append-only and every commit writes a NEW segment, so superseded generations simply accumulate forever. WHY THIS IS SAFE BY CONSTRUCTION: the manifest IS the definition of live. ss_manifest/ss_scan_seglist read ONLY segments listed there, so a file whose segid is absent from its own plane's manifest is unreachable BY DEFINITION, not by inference. That is the whole safety argument and it is why this tool refuses to run when it cannot read the manifest -- an unknown live set means an unknown safe set. RULE 13 -- MOVE ASIDE, NEVER DELETE. Superseded files are RENAMED into knowledge/store/retired/ (the convention already present on the box). Nothing is unlinked, so a mistake is reversible by renaming back. DRY RUN IS THE DEFAULT: it reports what it WOULD move and touches nothing. Pass 'apply' to actually move. 2026-08-18 -- THE BIRTH RACE, MEASURED TWICE AND CLOSED BY CONSTRUCTION. ss_commit_body writes the four segment files (.docs/.idx/.pos/.imp) FIRST and appends the manifest line SECOND, and every new segid is max(manifest)+1 under the plane lock. So a segment being born is, for a moment, an unlisted file set with an id ABOVE the manifest's max. This janitor read the manifest, then walked, and moved exactly such a birth (comparewatch-seg-1786828803 on 08-16/17, debt-seg-1787064612 on 08-18); its post-sweep race check then put back ONLY the .docs, so every reader saw a live segment with no index: ss_get still answered q:n while sts_load resolved zero rows -> "lossy load"/SHORTFALL refusals on the estate's debt board and hive plane, and one sibling seat's debt row was overwritten during the hand repair. Two fixes, both here: 1. AN UNLISTED SEGMENT WHOSE ID IS ABOVE THE MANIFEST'S MAX IS A BIRTH IN FLIGHT, NEVER GARBAGE. It is counted (inflight_files=, in files like every other counter on the line) and left alone; once the plane's max passes it, it is either listed (live) or a crashed birth (swept on a later beat). No lock is needed: the ordering is the store's own commit protocol. 2. THE POST-SWEEP RESTORE PUTS BACK EVERY FILE OF A LIVE SEGMENT, not the record alone: it walks retired/ and renames back each <base>seg-<id>.<ext> whose id is live NOW and whose live path is absent -- no extension list here (the store owns its layout), and never a clobber. The same routine is the `heal` verb, so the two hand repairs above become one call: nx_store_janitor <prefix> heal. And the manifest is read whole via sys_read_file -- the old fixed 256 KiB read plus an 8192-line table were SILENT CAPS: a manifest past either would have swept live segments as dead. EXIT: 0 clean/reported - 2 usage - 3 REFUSED (manifest unreadable or empty live set) - 4 nothing superseded license_tier: ORIGINAL No hw writes (Rule 26).

dependencies 1 imports · 0 importers

nx_syscalls.nx nx_store_janitor.nx

imports: nx_syscalls.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main sys_mmap nxa_die sys_write sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ sj_cat sys_write ↻ sj_len sys_read_file sys_openat_rd sys_lseek sys_mmap ↻ sys_read sys_munmap sys_close sj_lines sj_index sj_segid sj_num sys_mmap ↻ sys_munmap ↻ sj_heal sys_mmap ↻ sj_cat ↻ sys_openat_rd ↻ sys_munmap ↻ sys_getdents64 dirent_reclen dirent_name

structs

none

consts

42const SJ_DBUF: i64 = 262144
43const SJ_PATH: i64 = 1024
44const SJ_MSG: i64 = 8192
45const SJ_STAT: i64 = 256
46const SJ_STAT_SIZE_OFF: i64 = 48
47const SJ_NL: i64 = 10
48const SJ_DOT: i64 = 46
49const SJ_SLASH: i64 = 47
50const SJ_STDOUT: i64 = 1
51const SJ_EXIT_USAGE: i64 = 2
52const SJ_EXIT_REFUSED: i64 = 3
53const SJ_EXIT_NONE: i64 = 4
54const SJ_MB: i64 = 1048576
55const SJ_D0: i64 = 48 // '0'
56const SJ_D9: i64 = 57 // '9'
57const SJ_BASE10: i64 = 10
58const SJ_SEGPFX: i64 = 4 // "seg-" -- the token prefix every segment name carries
59const SJ_CH_S: i64 = 115 // 's'
60const SJ_CH_E: i64 = 101 // 'e'
61const SJ_CH_G: i64 = 103 // 'g'
62const SJ_CH_DASH: i64 = 45 // '-'
63const SJ_CH_A: i64 = 97 // 'a' (apply)
64const SJ_CH_H: i64 = 104 // 'h' (heal)
65const SJ_I64: i64 = 8

functions

67func sj_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
called by 1: main
68func sj_cat(d: *u8, off: i64, s: *u8) -> i64
called by 2: sj_healmain
73func sj_num(d: *u8, off: i64, v: i64) -> i64
called by 1: main calls 2: sys_mmapsys_munmap
87func sj_size(path: *u8) -> i64
96func sj_segid(tok: *u8, toklen: i64) -> i64
called by 2: sj_indexmain
112func sj_lines(b: *u8, n: i64) -> i64
called by 1: main
126func sj_index(b: *u8, n: i64, loff: *i64, llen: *i64, maxout: *i64) -> i64
called by 1: main calls 1: sj_segid
151func sj_is_segfile(nm: *u8, base: *u8, blen: i64) -> i64
called by 2: sj_healmain
161func sj_toklen(nm: *u8, blen: i64) -> i64
called by 2: sj_healmain
167func sj_islive(nm: *u8, blen: i64, seglen: i64, mbuf: *u8, loff: *i64, llen: *i64, nlive: i64) -> i64
called by 2: sj_healmain
183func sj_heal(dir: *u8, base: *u8, blen: i64, mbuf: *u8, loff: *i64, llen: *i64, nlive: i64, out: *i64) -> i64
234func main(argc: i64, argv: *i64) -> i64