code wiki / (root) / nx_planepeek.nx

nx_planepeek.nx source

↩ module page · 393 lines · 19208 B

1// nx_planepeek.nx -- READ ONE RAW KEY OUT OF A SEG-STORE PLANE. Read-only, writes nothing. 2// 3// WHY IT EXISTS. The debt- plane is refusing every add/eat because ONE row is reachable past the 4// declared q:n (nx_plane_check: rows=3696 identical=3696 declared_qn=3696 beyond_qn=1), and 5// nx_plane_repair correctly refuses to reconcile it: "an older seeding generation -- reseeding would 6// revive eaten rows and duplicate live ones. That needs a generation diff, not a reconcile." 7// To DO that generation diff somebody has to look at the row. And nothing in the estate could: 8// nx_store_put reads its own N-column planes, nx_plane_check verifies round-trips, nx_store_hist is a 9// column histogram -- none of them will hand you the value at an arbitrary key. 10// ★★★★★A PLANE YOU CAN VERIFY AND REPAIR BUT CANNOT READ A SINGLE KEY OUT OF FORCES EVERY DIAGNOSIS 11// TO BE A GUESS. I made two wrong guesses about this exact row before building this. 12// 13// It is deliberately the smallest thing that closes that gap: resolve a key through the SAME 14// production ss_get every reader uses, print the bytes, exit. No writes, no repair, no opinion. 15// 16// =========================================================================================== 17// THE FALSE-ABSENCE DEFECT (debt 1787282525, sev 6, fixed 2026-08-21). 18// =========================================================================================== 19// v1 printed exactly one word for every non-hit: `if ss_get(...) != 1 { MISS }`. 20// ss_get's OWN contract (nx_seg_store.nx: "1=found, 0=tombstoned, -1=absent") already carried a 21// state v1 threw away, and the scan underneath it carried three more: 22// 1. ss_get==0 -> the key EXISTS and was explicitly DELETED. v1 said MISS. A tombstone is a 23// POSITIVE fact about a key, and reporting it as absence inverts its meaning. 24// 2. no manifest -> ss_manifest_dyn returns 0, ss_scan returns 0, ss_get returns -1. So a 25// MISTYPED PREFIX printed the same MISS as a real absence. Nothing could tell them apart. 26// 3. a segment NAMED in manifest.txt but UNREADABLE on disk -> ss_readall sets sz=-1 and 27// ss_scan_seglist's `while i + 9 <= sz` loop never executes. The segment contributes ZERO 28// rows and NO error. A plane half of which could not be opened answered a confident "absent". 29// 4. a caller passing a COLUMN VALUE where the plane's keys are q:<n> (every N-col plane written 30// by nx_store_put) got MISS, which reads as "your row is gone" rather than "wrong grammar". 31// FIVE distinguishable conditions, ONE word, and that word is a NEGATIVE ASSERTION. Measured cost: 32// a seat nearly filed "the enqueue lane is dead" on it, and a build-refusal message shipped 33// `verify with nx_planepeek deployq- BQ-<target>` -- pointing every caller at a false-absence 34// generator. ★★★★★★AN UNBOUNDED SEARCH THAT GIVES UP MUST NOT REPORT THE SAME WORD AS AN 35// EXHAUSTIVE ONE THAT FOUND NOTHING -- otherwise every quote of it silently rewrites the 36// evidential status of the claim that quotes it. 37// ⇒ MISS now means ONE thing: the plane resolved, EVERY declared segment was read, the key is not 38// there. Everything else gets its own word and its own exit code, and every answer -- hit or 39// miss -- prints the coverage it was computed from. 40// ★PRESENCE NEEDS ONE WITNESS; ABSENCE NEEDS PROVEN COVERAGE. So the HIT path stays a cheap point 41// lookup and only the NEGATIVE answer pays for the enumeration that proves it. 42// 43// usage: nx_planepeek <prefix> <key> -- print the value at that key, or MISS 44// nx_planepeek <prefix> range <a> <b> -- print q:<a> .. q:<b> (inclusive), MISS lines kept 45// exits: 0 HIT | 1 MISS (proven exhaustive) | 2 usage | 3 UNPROVEN (coverage incomplete) 46// 4 NO-SUCH-PLANE | 5 TOMBSTONED (key present, deleted) | 6 NOT-A-KEY (it is a column value) 47// ⚠`range` is bounded by PK_MAXRANGE and SAYS SO when it clamps -- a peek that silently stopped early 48// would be the same defect class this lane spent the day measuring. 49// ⚠THE ARCHIVE IS A DECLARED HORIZON, NOT A SCANNED ONE. ss_get reads manifest.txt only; compaction 50// retires segments into manifest-archive.txt (the clocksched- plane: 5 live, 460+ archived). This 51// organ PRINTS archive_segments= so the horizon is visible, and deliberately does not read them -- 52// ★AN INSTRUMENT'S SAMPLING WINDOW MUST BE PUBLISHED OR IT READS AS COMPLETENESS. 53// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). 54import "nx_seg_store.nx" 55import "nx_syscalls.nx" 56 57const PK_KEYCAP: i64 = 256 58const PK_MAXRANGE: i64 = 200 59const PK_ZERO: i64 = 48 60const PK_NINE: i64 = 57 61// Column separator in every N-col plane row: nx_store_put joins fields with a TAB, so column 0 of a 62// row value is the bytes up to the first TAB. That is what makes the NOT-A-KEY diagnosis DERIVED 63// from the data rather than assumed from a naming convention. 64const PK_TAB: i64 = 9 65// Path buffer width. NOT a fresh guess: this is the same 512 ss_scan_seglist and ss_cur_next use to 66// build "<prefix><seg>.docs", so the peek cannot overflow where the production scan does not. 67const PK_PATHCAP: i64 = 512 68 69// ---- EXIT CONTRACT. Every state the lookup can be in gets its own code, because a caller that 70// ---- cannot branch on the reason will branch on MISS and be wrong. 71const PK_EX_HIT: i64 = 0 72const PK_EX_MISS: i64 = 1 73const PK_EX_USAGE: i64 = 2 74const PK_EX_UNPROVEN: i64 = 3 75const PK_EX_NOPLANE: i64 = 4 76const PK_EX_TOMBSTONED: i64 = 5 77const PK_EX_NOTAKEY: i64 = 6 78 79// ss_get's return alphabet, named at the call site so the mapping is readable (nx_seg_store.nx: 80// "latest state of key: 1=found, 0=tombstoned, -1=absent"). 81const PK_SS_TOMB: i64 = 0 82const PK_SS_HIT: i64 = 1 83 84// ---- COVERAGE RECORD. Filled before any verdict; printed with every verdict. 85const PK_COV_SLOTS: i64 = 8 86const PK_COV_DECLARED: i64 = 0 87const PK_COV_READABLE: i64 = 1 88const PK_COV_UNREADABLE: i64 = 2 89const PK_COV_BYTES: i64 = 3 90const PK_COV_ARCHIVE: i64 = 4 91const PK_COV_KEYS: i64 = 5 92const PK_COV_SEGS: i64 = 6 93 94func pk_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 95func pk_puts(s: *u8) -> i64 { sys_write(1, s, pk_len(s)); return 0 } 96func pk_putn(v: i64) -> i64 { 97 if v == 0 { pk_puts("0" as *u8); return 0 } 98 var x: i64 = v 99 if x < 0 { pk_puts("-" as *u8); x = 0 - x } 100 let b: *u8 = sys_mmap(32) 101 var i: i64 = 0 102 while x > 0 { b[i] = ((x % 10) + PK_ZERO) as u8; x = x / 10; i = i + 1 } 103 while i > 0 { i = i - 1; sys_write(1, ((b as i64) + i) as *u8, 1) } 104 return 0 105} 106func pk_atoi(s: *u8) -> i64 { 107 var v: i64 = 0 108 var i: i64 = 0 109 var done: i64 = 0 110 while done == 0 { 111 let c: i64 = s[i] as i64 112 if c >= PK_ZERO { if c <= PK_NINE { v = v * 10 + (c - PK_ZERO); i = i + 1 } else { done = 1 } } else { done = 1 } 113 } 114 return v 115} 116func pk_streq(a: *u8, b: *u8) -> i64 { 117 var i: i64 = 0 118 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 119 if b[i] == (0 as u8) { return 1 } 120 return 0 121} 122// build "q:<n>" exactly as sts_rowkey does 123func pk_rowkey(seq: i64, out: *u8) -> i64 { 124 out[0] = 113 as u8 // 'q' 125 out[1] = 58 as u8 // ':' 126 var o: i64 = ss_catn(out, 2, seq) 127 out[o] = 0 as u8 128 return o 129} 130 131// --------------------------------------------------------------------------------------------- 132// COVERAGE. The whole point of the fix: establish WHAT COULD BE READ before saying what is absent. 133// --------------------------------------------------------------------------------------------- 134// Size of one segment file, or -1 if it cannot be opened. Deliberately NOT ss_readall: readability 135// is the question, and reading a 1.6 GB segment to answer it would make proving absence cost more 136// than the lookup it qualifies. openat+lseek+close is the whole probe. 137func pk_segsize(prefix: *u8, segname: *u8) -> i64 { 138 let path: *u8 = sys_mmap(PK_PATHCAP) 139 var o: i64 = ss_cat(path, 0, prefix) 140 o = ss_cat(path, o, segname) 141 o = ss_cat(path, o, ".docs" as *u8) 142 path[o] = 0 as u8 143 let fd: i64 = sys_openat_rd(path) 144 if fd < 0 { sys_munmap(path, PK_PATHCAP); return 0 - 1 } 145 let sz: i64 = sys_lseek(fd, 0, 2) 146 sys_close(fd) 147 sys_munmap(path, PK_PATHCAP) 148 return sz 149} 150 151// Fill cov from the LIVE manifest, and count (never read) the archive so the horizon is declared. 152// Returns the number of segments the live manifest declares. 153func pk_coverage(prefix: *u8, cov: *i64) -> i64 { 154 var i: i64 = 0 155 while i < PK_COV_SLOTS { cov[i] = 0; i = i + 1 } 156 let sp: *i64 = sys_mmap(16) as *i64 157 let ns: i64 = ss_manifest_dyn(prefix, sp) 158 let segs: *i64 = sp[0] as *i64 159 cov[PK_COV_DECLARED] = ns 160 cov[PK_COV_SEGS] = segs as i64 161 var s: i64 = 0 162 while s < ns { 163 let sz: i64 = pk_segsize(prefix, segs[s] as *u8) 164 if sz < 0 { 165 cov[PK_COV_UNREADABLE] = cov[PK_COV_UNREADABLE] + 1 166 } else { 167 cov[PK_COV_READABLE] = cov[PK_COV_READABLE] + 1 168 cov[PK_COV_BYTES] = cov[PK_COV_BYTES] + sz 169 } 170 s = s + 1 171 } 172 let ap: *i64 = sys_mmap(16) as *i64 173 cov[PK_COV_ARCHIVE] = ss_manifest_file_dyn(prefix, "manifest-archive.txt" as *u8, ap) 174 return ns 175} 176 177// A COUNT WITHOUT A WORKLIST IS NOT ACTIONABLE: name every segment that could not be opened. 178func pk_name_unreadable(prefix: *u8, cov: *i64) -> i64 { 179 let segs: *i64 = cov[PK_COV_SEGS] as *i64 180 let ns: i64 = cov[PK_COV_DECLARED] 181 var s: i64 = 0 182 while s < ns { 183 if pk_segsize(prefix, segs[s] as *u8) < 0 { 184 pk_puts(" UNREADABLE-SEGMENT " as *u8) 185 pk_puts(segs[s] as *u8) 186 pk_puts(".docs\n" as *u8) 187 } 188 s = s + 1 189 } 190 return 0 191} 192 193// The coverage line every verdict carries. A PARTITION IS A CLAIM: declared == readable+unreadable, 194// and the sum is PRINTED so a reader can check it rather than trust it. 195func pk_put_coverage(cov: *i64) -> i64 { 196 pk_puts("COVERAGE segments_declared=" as *u8); pk_putn(cov[PK_COV_DECLARED]) 197 pk_puts(" read=" as *u8); pk_putn(cov[PK_COV_READABLE]) 198 pk_puts(" unreadable=" as *u8); pk_putn(cov[PK_COV_UNREADABLE]) 199 pk_puts(" sum=" as *u8); pk_putn(cov[PK_COV_READABLE] + cov[PK_COV_UNREADABLE]) 200 pk_puts(" bytes=" as *u8); pk_putn(cov[PK_COV_BYTES]) 201 pk_puts(" keys_scanned=" as *u8); pk_putn(cov[PK_COV_KEYS]) 202 pk_puts(" archive_segments=" as *u8); pk_putn(cov[PK_COV_ARCHIVE]) 203 pk_puts(" (archive NOT scanned -- declared horizon)\n" as *u8) 204 return 0 205} 206 207// --------------------------------------------------------------------------------------------- 208// THE ABSENCE PROOF. Only ever runs when ss_get already said "not here", so the hit path is 209// untouched. Composes the incumbent enumerator ss_cur_next rather than adding a THIRD copy of the 210// record-framing walk (ss_scan_seglist and ss_cur_next already carry one each). 211// Returns 1 if `key` was found as COLUMN 0 of some row (=> NOT-A-KEY), 0 otherwise. 212// Writes the real key of that row into realkey/realkeylen when it returns 1, and counts every key 213// it walked into cov[PK_COV_KEYS] -- that count IS the evidence the scan was exhaustive. 214func pk_scan_for_colvalue(prefix: *u8, key: *u8, cov: *i64, rk: *i64, rkl: *i64) -> i64 { 215 let kl0: i64 = pk_len(key) 216 let st: *i64 = ss_cur_open(prefix) 217 let kp: *i64 = sys_mmap(16) as *i64 218 let klp: *i64 = sys_mmap(16) as *i64 219 let vp: *i64 = sys_mmap(16) as *i64 220 let vlp: *i64 = sys_mmap(16) as *i64 221 var found: i64 = 0 222 var more: i64 = 1 223 while more == 1 { 224 if ss_cur_next(prefix, st, kp, klp, vp, vlp) != 1 { 225 more = 0 226 } else { 227 cov[PK_COV_KEYS] = cov[PK_COV_KEYS] + 1 228 if found == 0 { 229 // column 0 = value bytes up to the first TAB (or the whole value if none). 230 // Separate cursor and flag: a loop that exits by clobbering its own cursor cannot 231 // also report where it stopped. 232 let vb: *u8 = vp[0] as *u8 233 let vl: i64 = vlp[0] 234 var col0len: i64 = 0 235 var scanning: i64 = 1 236 while scanning == 1 { 237 if col0len >= vl { 238 scanning = 0 239 } else { 240 if (vb[col0len] as i64) == PK_TAB { scanning = 0 } else { col0len = col0len + 1 } 241 } 242 } 243 if col0len == kl0 { 244 var t: i64 = 0 245 var eq: i64 = 1 246 while t < col0len { if vb[t] != key[t] { eq = 0 } t = t + 1 } 247 if eq == 1 { 248 found = 1 249 rk[0] = kp[0] 250 rkl[0] = klp[0] 251 } 252 } 253 } 254 } 255 } 256 return found 257} 258 259// print the value bytes of a hit 260func pk_put_value(pp: *i64, lp: *i64) -> i64 { 261 pk_puts(" len=" as *u8); pk_putn(lp[0]) 262 pk_puts("\n " as *u8) 263 sys_write(1, pp[0] as *u8, lp[0]) 264 pk_puts("\n" as *u8) 265 return 0 266} 267 268// Resolve ONE key to a fully-qualified state. Returns the exit code for that state and prints the 269// headline. Coverage must already be filled. 270func pk_resolve(prefix: *u8, key: *u8, cov: *i64) -> i64 { 271 let pp: *i64 = sys_mmap(16) as *i64 272 let lp: *i64 = sys_mmap(16) as *i64 273 let r: i64 = ss_get(prefix, key, pp, lp) 274 if r == PK_SS_HIT { 275 pk_puts("HIT " as *u8); pk_puts(key) 276 pk_put_value(pp, lp) 277 return PK_EX_HIT 278 } 279 if r == PK_SS_TOMB { 280 // A TOMBSTONE IS A POSITIVE FACT ABOUT A KEY. v1 printed MISS here, inverting its meaning: 281 // "this row was deleted" and "this row never existed" demand opposite remedies. 282 pk_puts("TOMBSTONED " as *u8); pk_puts(key) 283 pk_puts(" -- the key IS present and carries a DELETE marker (ss_get=0). This is NOT absence.\n" as *u8) 284 return PK_EX_TOMBSTONED 285 } 286 // Absent from every LIVE segment ss_get could read. Before that may be published as absence, 287 // it has to survive two questions v1 never asked. 288 let rk: *i64 = sys_mmap(16) as *i64 289 let rkl: *i64 = sys_mmap(16) as *i64 290 let isval: i64 = pk_scan_for_colvalue(prefix, key, cov, rk, rkl) 291 if isval == 1 { 292 // PRESENCE NEEDS ONE WITNESS: this is valid even under incomplete coverage, so it is 293 // checked BEFORE the UNPROVEN branch. 294 pk_puts("NOT-A-KEY " as *u8); pk_puts(key) 295 pk_puts(" -- that string is COLUMN 0 OF A ROW, not a key. The key of that row is: " as *u8) 296 sys_write(1, rk[0] as *u8, rkl[0]) 297 pk_puts("\n Seg-store keys are assigned by the writer (nx_store_put uses q:0..q:n).\n" as *u8) 298 pk_puts(" Retry with that key, or dump the plane: nx_store_put <prefix> load\n" as *u8) 299 return PK_EX_NOTAKEY 300 } 301 if cov[PK_COV_UNREADABLE] > 0 { 302 // ★AN UNBOUNDED SEARCH THAT GIVES UP MUST NOT REPORT THE SAME WORD AS AN EXHAUSTIVE ONE. 303 pk_puts("UNPROVEN " as *u8); pk_puts(key) 304 pk_puts(" -- COVERAGE INCOMPLETE: " as *u8); pk_putn(cov[PK_COV_UNREADABLE]) 305 pk_puts(" of " as *u8); pk_putn(cov[PK_COV_DECLARED]) 306 pk_puts(" declared segments could not be opened, so absence CANNOT be concluded.\n" as *u8) 307 pk_name_unreadable(prefix, cov) 308 return PK_EX_UNPROVEN 309 } 310 pk_puts("MISS " as *u8); pk_puts(key) 311 pk_puts(" -- PROVEN ABSENT: every declared segment was read and no such key exists.\n" as *u8) 312 return PK_EX_MISS 313} 314 315func main(argc: i64, argv: *i64) -> i64 { 316 if argc < 3 { 317 pk_puts("usage: nx_planepeek <prefix> <key>\n" as *u8) 318 pk_puts(" nx_planepeek <prefix> range <a> <b> (q:<a>..q:<b>, inclusive)\n" as *u8) 319 pk_puts("exits: 0 HIT | 1 MISS(proven) | 2 usage | 3 UNPROVEN | 4 NO-SUCH-PLANE | 5 TOMBSTONED | 6 NOT-A-KEY\n" as *u8) 320 sys_exit(PK_EX_USAGE) 321 } 322 let prefix: *u8 = argv[1] as *u8 323 let a2: *u8 = argv[2] as *u8 324 325 // COVERAGE FIRST, ALWAYS. Nothing may be said about a key before it is known what could be read. 326 let cov: *i64 = sys_mmap(8 * PK_COV_SLOTS) as *i64 327 pk_coverage(prefix, cov) 328 if cov[PK_COV_DECLARED] == 0 { 329 // v1 reported this as MISS -- a mistyped prefix was indistinguishable from a real absence. 330 pk_puts("NO-SUCH-PLANE " as *u8); pk_puts(prefix) 331 pk_puts("\n No live manifest, or it declares zero segments: " as *u8) 332 pk_puts(prefix); pk_puts("manifest.txt\n" as *u8) 333 pk_puts(" Nothing can be concluded about ANY key under this prefix.\n" as *u8) 334 pk_put_coverage(cov) 335 sys_exit(PK_EX_NOPLANE) 336 } 337 338 if pk_streq("range" as *u8, a2) == 1 { 339 if argc < 5 { pk_puts("range needs <a> <b>\n" as *u8); sys_exit(PK_EX_USAGE) } 340 let lo: i64 = pk_atoi(argv[3] as *u8) 341 var hi: i64 = pk_atoi(argv[4] as *u8) 342 // DECLARE THE CLAMP. A peek that silently shortened its own range would be exactly the defect 343 // this whole lane has been measuring all day. 344 if hi - lo + 1 > PK_MAXRANGE { 345 hi = lo + PK_MAXRANGE - 1 346 pk_puts(">>RANGE CLAMPED to " as *u8); pk_putn(PK_MAXRANGE) 347 pk_puts(" keys; the request was wider and the tail is NOT shown.<<\n" as *u8) 348 } 349 let key: *u8 = sys_mmap(PK_KEYCAP) 350 var i: i64 = lo 351 var hits: i64 = 0 352 var tombs: i64 = 0 353 let pp: *i64 = sys_mmap(16) as *i64 354 let lp: *i64 = sys_mmap(16) as *i64 355 while i <= hi { 356 pk_rowkey(i, key) 357 let r: i64 = ss_get(prefix, key, pp, lp) 358 if r == PK_SS_HIT { 359 pk_puts("HIT " as *u8); pk_puts(key); pk_put_value(pp, lp) 360 hits = hits + 1 361 } else { 362 if r == PK_SS_TOMB { 363 pk_puts("TOMBSTONED " as *u8); pk_puts(key); pk_puts("\n" as *u8) 364 tombs = tombs + 1 365 } else { 366 pk_puts("MISS " as *u8); pk_puts(key); pk_puts("\n" as *u8) 367 } 368 } 369 i = i + 1 370 } 371 pk_puts("\nPEEK-RANGE lo=" as *u8); pk_putn(lo) 372 pk_puts(" hi=" as *u8); pk_putn(hi) 373 pk_puts(" hits=" as *u8); pk_putn(hits) 374 pk_puts(" tombstoned=" as *u8); pk_putn(tombs) 375 pk_puts(" miss=" as *u8); pk_putn(hi - lo + 1 - hits - tombs) 376 pk_puts("\n" as *u8) 377 pk_put_coverage(cov) 378 // A range MISS is "no row at that index", which is legitimate; but if the plane could not be 379 // fully read even that is unproven, and the range verb must say so rather than imply a gap. 380 if cov[PK_COV_UNREADABLE] > 0 { 381 pk_puts("UNPROVEN -- " as *u8); pk_putn(cov[PK_COV_UNREADABLE]) 382 pk_puts(" declared segments unreadable; MISS lines above are NOT proven absent.\n" as *u8) 383 pk_name_unreadable(prefix, cov) 384 sys_exit(PK_EX_UNPROVEN) 385 } 386 sys_exit(PK_EX_HIT) 387 } 388 389 let code: i64 = pk_resolve(prefix, a2, cov) 390 pk_put_coverage(cov) 391 sys_exit(code) 392 return 0 393}