nx_store_janitor.nx
buildroot/runtime/nx_store_janitor.nx
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
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
structs
| none |
consts
| 42 | const SJ_DBUF: i64 = 262144 |
| 43 | const SJ_PATH: i64 = 1024 |
| 44 | const SJ_MSG: i64 = 8192 |
| 45 | const SJ_STAT: i64 = 256 |
| 46 | const SJ_STAT_SIZE_OFF: i64 = 48 |
| 47 | const SJ_NL: i64 = 10 |
| 48 | const SJ_DOT: i64 = 46 |
| 49 | const SJ_SLASH: i64 = 47 |
| 50 | const SJ_STDOUT: i64 = 1 |
| 51 | const SJ_EXIT_USAGE: i64 = 2 |
| 52 | const SJ_EXIT_REFUSED: i64 = 3 |
| 53 | const SJ_EXIT_NONE: i64 = 4 |
| 54 | const SJ_MB: i64 = 1048576 |
| 55 | const SJ_D0: i64 = 48 // '0' |
| 56 | const SJ_D9: i64 = 57 // '9' |
| 57 | const SJ_BASE10: i64 = 10 |
| 58 | const SJ_SEGPFX: i64 = 4 // "seg-" -- the token prefix every segment name carries |
| 59 | const SJ_CH_S: i64 = 115 // 's' |
| 60 | const SJ_CH_E: i64 = 101 // 'e' |
| 61 | const SJ_CH_G: i64 = 103 // 'g' |
| 62 | const SJ_CH_DASH: i64 = 45 // '-' |
| 63 | const SJ_CH_A: i64 = 97 // 'a' (apply) |
| 64 | const SJ_CH_H: i64 = 104 // 'h' (heal) |
| 65 | const SJ_I64: i64 = 8 |
functions
| 67 | func sj_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } called by 1: main |
| 68 | func sj_cat(d: *u8, off: i64, s: *u8) -> i64 |
| 73 | func sj_num(d: *u8, off: i64, v: i64) -> i64 |
| 87 | func sj_size(path: *u8) -> i64 |
| 96 | func sj_segid(tok: *u8, toklen: i64) -> i64 |
| 112 | func sj_lines(b: *u8, n: i64) -> i64 called by 1: main |
| 126 | func sj_index(b: *u8, n: i64, loff: *i64, llen: *i64, maxout: *i64) -> i64 |
| 151 | func sj_is_segfile(nm: *u8, base: *u8, blen: i64) -> i64 |
| 161 | func sj_toklen(nm: *u8, blen: i64) -> i64 |
| 167 | func sj_islive(nm: *u8, blen: i64, seglen: i64, mbuf: *u8, loff: *i64, llen: *i64, nlive: i64) -> i64 |
| 183 | func sj_heal(dir: *u8, base: *u8, blen: i64, mbuf: *u8, loff: *i64, llen: *i64, nlive: i64, out: *i64) -> i64 |
| 234 | func main(argc: i64, argv: *i64) -> i64 |