code wiki / (root) / nx_toolreg_reconcile_lib.nx

nx_toolreg_reconcile_lib.nx source

↩ module page · 546 lines · 30921 B

1// nx_toolreg_reconcile_lib.nx -- DISCOVERY RECONCILER, library half. Closes the WIRE class forever: 2// every tool that is GREEN in tool_allowlist.conf (vetted + callable) but ABSENT from the discovery 3// registry (toolreg -> tools/list) gets registered using ITS AUTHOR'S OWN schema row from 4// knowledge/tool_schemas.conf -- no invented contracts. A tool with NO schema row is SKIPPED and 5// reported (fail-closed: never register a contract-less tool). Idempotent by construction (tool_get 6// first). Re-running after any future allowlist growth wires the newcomers -- the allowlist stays the 7// SSOT and discovery reconciles to it, so the built-not-discoverable bucket cannot silently regrow. 8// license_tier: ORIGINAL 9import "nx_tool_registry.nx" 10import "nx_vsz_watchdog_core.nx" // vw_read -- bounded file reads 11 12const RR_FILE_CAP: i64 = 262144 // allowlist/schemas read cap 13const RR_LINE_CAP: i64 = 4096 // one row (name/title/fields/desc) 14const RR_INV_CAP: i64 = 512 // generated invoke line 15const RR_TAB: i64 = 9 // '\t' 16const RR_NL: i64 = 10 // '\n' 17const RR_HASH: i64 = 35 // '#' (comment rows) 18const RR_C_REG: i64 = 0 // counts slot: newly registered 19const RR_C_ALREADY: i64 = 1 // counts slot: already discoverable 20const RR_C_NOSCHEMA: i64 = 2 // counts slot: skipped, no schema row (fail-closed) 21const RR_C_FAIL: i64 = 3 // counts slot: register call failed 22const RR_C_UPDATED: i64 = 4 // counts slot: stored description REWRITTEN from the author's schema 23const RR_C_SCAFFOLD: i64 = 5 // counts slot: row points at a STAGED .elf.new -- not a shipped tool 24const RR_C_NOAUTH: i64 = 6 // counts slot: REGISTERED but its tool_schemas.conf row is GONE 25const RR_C_DUPSCHEMA: i64 = 7 // counts slot: a NAME with more than one schema row 26const RR_COUNTS: i64 = 64 // counts buffer bytes 27const RR_COMMA: i64 = 44 // ',' join separator (name list) 28const RR_SPC: i64 = 32 // ' ' 29const RR_SEP_LEN: i64 = 2 // ", " separator width 30// EXPOSURE POLICY (2026-07-17): tools deliberately kept OUT of tools/list discovery -- security-sensitive 31// (vault/secret CLIs), internal-only census metrics, or explicitly-not-an-inline-MCP-call organs. These are 32// callable-by-cap but intentionally un-advertised, so their absence from discovery is CORRECT, not drift. 33// One TAB-first-field name per line (# comments ok); absent/empty file -> no exemptions (fail-open). 34const RR_DENY_PATH: *u8 = "knowledge/mcp/exposure_deny.txt" 35 36func rr_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 37// AUDIT MODE NAMES THE DRIFT. `drift=9` tells an operator that work exists and nothing about what it 38// is; the nine names ARE the worklist, and they cost one write each. This organ shipped reporting only 39// the count, so "9 tools have no contract" sat unactioned as a number -- the same shape as a 2,800-row 40// conflict census nobody could start on until it was ranked and named. 41// ⇒ ★★★★★A COUNT IS NOT A WORKLIST. AN AUDIT THAT WON'T NAME ITS ROWS DELEGATES THE SEARCH BACK. 42// Emitted ONLY under dry==1, so the mutating reconcile path is byte-for-byte as quiet as before, and 43// the lines precede the machine-readable `drift=` summary a sweep greps for. 44func rr_drift_name(kind: *u8, name: *u8) -> i64 { 45 sys_write(1, " DRIFT " as *u8, 8) 46 sys_write(1, kind, rr_len(kind)) 47 sys_write(1, name, rr_len(name)) 48 sys_write(1, "\n" as *u8, 1) 49 return 0 50} 51func rr_seq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 52// 1 if this allowlist row points at a STAGED artifact (`<t>.elf.new`) rather than a promoted one. 53// WHY THIS IS ITS OWN CLASS AND NOT DRIFT. Measured 2026-08-07: 14 allowlist rows point at `.elf.new` 54// -- three timestamp-suffixed witness probes, two `_new`, four `_n`, one `_herm`. Ten of them had no 55// schema row, so the audit counted them as missing CONTRACTS and reported drift=10 RED. That reading 56// is wrong and the remedy it implies (write ten contracts) is wrong: these are development scaffolding 57// pointing at binaries that were STAGED AND NEVER PROMOTED -- the estate's own RESTAGE-IS-NOT-INSTALL 58// signature. They cannot be finished, so the gate could never reach GREEN, and a gate that is 59// permanently RED is one nobody reads. 60// ⇒ ★★★★★★A REGISTRATION POINTING AT A STAGING PATH IS NOT A SHIPPED CAPABILITY. That is a RULE, so 61// it self-maintains as new scratch rows appear -- unlike the hand-kept exposure_deny list. 62// ⇒ ★★★★★DO NOT SILENCE WHAT YOU RECLASSIFY: scaffold is counted and NAMED, just not called drift. 63// !!NOT auto-reaped. The `.elf.new` targets all EXIST and are callable, and `segrace_herm` tracks 64// another seat's live hermetic-fixture work from the same morning. Reclassifying someone's 65// work-in-progress is correct; deleting it is not. 66func rr_is_stagepath(line: *u8, ll: i64) -> i64 { 67 let pat: *u8 = ".elf.new" as *u8 68 var i: i64 = 0 69 while i + 8 <= ll { 70 var k: i64 = 0 71 var hit: i64 = 1 72 while k < 8 { if line[i + k] != pat[k] { hit = 0; k = 8 } else { k = k + 1 } } 73 if hit == 1 { return 1 } 74 i = i + 1 75 } 76 return 0 77} 78// first TAB field of line[0..ll) -> out; returns len 79func rr_f0(line: *u8, ll: i64, out: *u8) -> i64 { 80 var i: i64 = 0 81 while i < ll { if line[i] == (RR_TAB as u8) { i = ll } else { out[i] = line[i]; i = i + 1 } } 82 var n: i64 = 0 83 while n < ll { if line[n] == (RR_TAB as u8) { out[n] = 0 as u8; return n } n = n + 1 } 84 out[ll] = 0 as u8 85 return ll 86} 87// LAST TAB field of line[0..ll) -> out (the schema desc column); returns len 88func rr_flast(line: *u8, ll: i64, out: *u8) -> i64 { 89 var s: i64 = 0 90 var i: i64 = 0 91 while i < ll { if line[i] == (RR_TAB as u8) { s = i + 1 } i = i + 1 } 92 var o: i64 = 0 93 while s < ll { out[o] = line[s]; o = o + 1; s = s + 1 } 94 out[o] = 0 as u8 95 return o 96} 97// field #1 of a TAB registry record -> out. The record is name\tdesc\tinvoke\tstatus, so field 1 98// is the DESCRIPTION consumers actually read in GET /api/tools and MCP tools/list. Needed so the 99// reconciler can COMPARE what is stored against what the author wrote (debt seq1526). 100func rr_rec_desc(rec: *u8, ln: i64, out: *u8) -> i64 { 101 var i: i64 = 0 102 var tabs: i64 = 0 103 var s: i64 = 0 - 1 104 var e: i64 = ln 105 while i < ln { 106 if rec[i] == (RR_TAB as u8) { 107 tabs = tabs + 1 108 if tabs == 1 { s = i + 1 } else { if tabs == 2 { e = i; i = ln } } 109 } 110 i = i + 1 111 } 112 if s < 0 { out[0] = 0 as u8; return 0 } 113 var o: i64 = 0 114 var k: i64 = s 115 while k < e { out[o] = rec[k]; o = o + 1; k = k + 1 } 116 out[o] = 0 as u8 117 return o 118} 119// find the schema row for `name` in schemas buf[0..n); copy its LAST field into desc. 1=found. 120func rr_schema_desc(buf: *u8, n: i64, name: *u8, desc: *u8) -> i64 { 121 let f0: *u8 = sys_mmap(RR_LINE_CAP) 122 var ls: i64 = 0 123 var i: i64 = 0 124 while i <= n { 125 var eol: i64 = 0 126 if i == n { eol = 1 } else { if buf[i] == (RR_NL as u8) { eol = 1 } } 127 if eol == 1 { 128 if i > ls { if buf[ls] != (RR_HASH as u8) { 129 rr_f0(((buf as i64 + ls) as *u8), i - ls, f0) 130 if rr_seq(f0, name) == 1 { 131 rr_flast(((buf as i64 + ls) as *u8), i - ls, desc) 132 return 1 133 } 134 } } 135 ls = i + 1 136 } 137 i = i + 1 138 } 139 return 0 140} 141// build "over /mcp: tools/call name=<t> arguments={argv:[...]} (cap granting <t>)" into inv 142func rr_invoke(name: *u8, inv: *u8) -> i64 { 143 var o: i64 = 0 144 let a: *u8 = "over /mcp: tools/call name=" as *u8 145 var i: i64 = 0 146 while a[i] != (0 as u8) { inv[o] = a[i]; o = o + 1; i = i + 1 } 147 i = 0 148 while name[i] != (0 as u8) { inv[o] = name[i]; o = o + 1; i = i + 1 } 149 let b: *u8 = " arguments={argv:[...]} with a capability granting the tool; args per the description" as *u8 150 i = 0 151 while b[i] != (0 as u8) { inv[o] = b[i]; o = o + 1; i = i + 1 } 152 inv[o] = 0 as u8 153 return o 154} 155// 1 if `name` is on the deny-advertise policy list (deliberately un-discoverable). First TAB field match; 156// a '#' comment or absent/empty deny buffer -> NOT denied (fail-open, so default behavior is unchanged). 157func rr_is_deny(name: *u8, db: *u8, dn: i64) -> i64 { 158 if dn <= 0 { return 0 } 159 let f0: *u8 = sys_mmap(RR_LINE_CAP) 160 var ls: i64 = 0 161 var i: i64 = 0 162 while i <= dn { 163 var eol: i64 = 0 164 if i == dn { eol = 1 } else { if db[i] == (RR_NL as u8) { eol = 1 } } 165 if eol == 1 { 166 if i > ls { if db[ls] != (RR_HASH as u8) { 167 rr_f0(((db as i64 + ls) as *u8), i - ls, f0) 168 if rr_seq(f0, name) == 1 { return 1 } 169 } } 170 ls = i + 1 171 } 172 i = i + 1 173 } 174 return 0 175} 176// DUPLICATE SCHEMA ROWS -- the class that was invisible until it broke an edit. 177// MEASURED 2026-08-07: five names carried TWO rows each, one hand-authored and one appended by 178// nx_schema_backfill. `rr_schema_desc` returns the FIRST match, so the good contract survived by FILE 179// ORDER ALONE -- an invariant nobody chose and nothing enforced. The harm is not tidiness: the two 180// rows disagreed on the SAFETY QUAD. nx_staghyg's authored row says 0/0/1/0 and its duplicate says 181// 1/1/0/0 -- simultaneously readOnly AND destructive, which is not a profile any tool can have. A 182// consumer that happened to read the second row would advertise a contradiction as fact. 183// => ★★★★★A DUPLICATE IS NOT REDUNDANCY WHEN THE COPIES DISAGREE -- IT IS A COIN FLIP WITH AN 184// AUTHORITATIVE NAME. 185// => ★★★★★THE PRODUCER OF THIS DEFECT COULD NOT BE SAFELY REBUILT (nx_contentdiff RED, 9 strings 186// lost), SO THE DETECTOR LIVES WITH THE CONSUMER. WHEN YOU CANNOT FIX THE WRITER, MAKE THE 187// READER SEE. 188// Reported as its own count, NOT folded into drift -- same restraint as no-authored-row. 189func rr_dup_census(sb: *u8, sn: i64, dry: i64) -> i64 { 190 let f0: *u8 = sys_mmap(RR_LINE_CAP) 191 let f1: *u8 = sys_mmap(RR_LINE_CAP) 192 var dups: i64 = 0 193 var ls: i64 = 0 194 var i: i64 = 0 195 while i <= sn { 196 var eol: i64 = 0 197 if i == sn { eol = 1 } else { if sb[i] == (RR_NL as u8) { eol = 1 } } 198 if eol == 1 { 199 if i > ls { if sb[ls] != (RR_HASH as u8) { 200 rr_f0(((sb as i64 + ls) as *u8), i - ls, f0) 201 if f0[0] != (0 as u8) { 202 // count occurrences of this name from the TOP; only report on the SECOND sighting 203 // so each duplicated name is named exactly once however many copies exist. 204 var seen: i64 = 0 205 var ls2: i64 = 0 206 var j: i64 = 0 207 while j <= i { 208 var e2: i64 = 0 209 if j == i { e2 = 1 } else { if sb[j] == (RR_NL as u8) { e2 = 1 } } 210 if e2 == 1 { 211 if j > ls2 { if sb[ls2] != (RR_HASH as u8) { 212 rr_f0(((sb as i64 + ls2) as *u8), j - ls2, f1) 213 if rr_seq(f0, f1) == 1 { seen = seen + 1 } 214 } } 215 ls2 = j + 1 216 } 217 j = j + 1 218 } 219 if seen == 2 { 220 dups = dups + 1 221 if dry == 1 { rr_drift_name("dup-schema-row " as *u8, f0) } 222 } 223 } 224 } } 225 ls = i + 1 226 } 227 i = i + 1 228 } 229 return dups 230} 231// RECONCILE/AUDIT under `prefix`: for each GREEN allowlist row missing from the registry, register with 232// the author's schema desc. counts: [reg, already, noschema, fail]. Returns rows examined. 233// dry==1 = AUDIT MODE: register NOTHING; RR_C_REG counts rows that WOULD register (the drift signal), so 234// a caller can assert drift=0 (reg + noschema == 0) without mutating -- the standing regression tooth. 235// does `name` have a row in the allowlist buf[0..an)? 1=yes. `scratch` is the caller's field-0 buffer: 236// this is called once per schema row, and allocating RR_LINE_CAP inside it would mmap megabytes for one 237// census (never allocate in a hot loop -- pass the buffer in). 238func rr_allow_has(ab: *u8, an: i64, name: *u8, scratch: *u8) -> i64 { 239 var ls: i64 = 0 240 var i: i64 = 0 241 while i <= an { 242 var eol: i64 = 0 243 if i == an { eol = 1 } else { if ab[i] == (RR_NL as u8) { eol = 1 } } 244 if eol == 1 { 245 if i > ls { if ab[ls] != (RR_HASH as u8) { 246 rr_f0(((ab as i64 + ls) as *u8), i - ls, scratch) 247 if rr_seq(scratch, name) == 1 { return 1 } 248 } } 249 ls = i + 1 250 } 251 i = i + 1 252 } 253 return 0 254} 255// THE REVERSE JOIN (2026-08-20, lane A of the adoption campaign; the fix for the class debt 1787175603 256// names). rr_run below walks the ALLOWLIST and asks of each row "is it discoverable?" -- so ITS 257// POPULATION IS THE ALLOWLIST, and a row DELETED from that file is not drift, it is simply absent from 258// the question being asked. MEASURED that day: the Aug-19 gateclobber restored an Aug-10 copy of 259// tool_allowlist.conf and 20 exec-proven tools lost their rows; `check` reported 260// `drift=0 verdict=GREEN (allowlist == tools/list; every tool contracted)` the entire time. The proof it 261// was BLIND rather than RIGHT is that already-discoverable rose 954 -> 973 the instant those rows were 262// restored by hand -- i.e. 19 of the 20 still had their tool_schemas.conf rows all along, so the estate 263// was holding the evidence of what SHOULD be in the allowlist in a sibling file and nothing joined them. 264// ⇒ ★★★★★★A RECONCILER WHOSE POPULATION IS THE FILE THAT GOT CLOBBERED CANNOT SEE THE CLOBBER, AND IT 265// READS GREEN PRECISELY IN PROPORTION TO HOW TOTAL THE DAMAGE WAS. 266// This walks the SCHEMAS file instead: a name whose author WROTE A CONTRACT but which has NO allowlist 267// row is a tool NOBODY CAN CALL, and no forward-direction count can ever see it. 268// DECLARED A SEPARATE AXIS AND DELIBERATELY NOT FOLDED INTO rr_drift: a new bucket that overlaps an 269// existing partition must be declared separately or the reconciliation that makes that partition 270// trustworthy breaks silently -- and redefining another lane's RED unilaterally is not mine to do (the 271// same reason RR_C_NOAUTH stayed out). It REPORTS, with the per-row worklist beside the count, because a 272// count without a worklist is not actionable and the reason is already in hand at measure time. 273// Returns the count, or -1 if either file is unreadable -- a THIRD STATE, because "I could not look" 274// must never be published as "I looked and found none". 275func rr_missing_rows(allow_path: *u8, schemas_path: *u8, report: i64) -> i64 { 276 let al: *i64 = sys_mmap(16) as *i64 277 let ab: *u8 = sys_read_file(allow_path, al) 278 if (ab as i64) == 0 { return 0 - 1 } 279 let an: i64 = al[0] 280 if an <= 0 { return 0 - 1 } 281 let sl: *i64 = sys_mmap(16) as *i64 282 let sb: *u8 = sys_read_file(schemas_path, sl) 283 if (sb as i64) == 0 { return 0 - 1 } 284 let sn: i64 = sl[0] 285 if sn <= 0 { return 0 - 1 } 286 let f0: *u8 = sys_mmap(RR_LINE_CAP) 287 let f1: *u8 = sys_mmap(RR_LINE_CAP) 288 let f2: *u8 = sys_mmap(RR_LINE_CAP) 289 var miss: i64 = 0 290 var ls: i64 = 0 291 var i: i64 = 0 292 while i <= sn { 293 var eol: i64 = 0 294 if i == sn { eol = 1 } else { if sb[i] == (RR_NL as u8) { eol = 1 } } 295 if eol == 1 { 296 if i > ls { if sb[ls] != (RR_HASH as u8) { 297 rr_f0(((sb as i64 + ls) as *u8), i - ls, f0) 298 if f0[0] != (0 as u8) { 299 // FIRST SIGHTING ONLY. The schemas file is KNOWN to carry duplicate name rows -- 300 // rr_dup_census measures them and this run reports one -- so counting a name once per 301 // row would publish a count that does not match the worklist printed beside it. 302 var seen: i64 = 0 303 var ls2: i64 = 0 304 var j: i64 = 0 305 while j <= i { 306 var e2: i64 = 0 307 if j == i { e2 = 1 } else { if sb[j] == (RR_NL as u8) { e2 = 1 } } 308 if e2 == 1 { 309 if j > ls2 { if sb[ls2] != (RR_HASH as u8) { 310 rr_f0(((sb as i64 + ls2) as *u8), j - ls2, f1) 311 if rr_seq(f0, f1) == 1 { seen = seen + 1 } 312 } } 313 ls2 = j + 1 314 } 315 j = j + 1 316 } 317 if seen == 1 { 318 if rr_allow_has(ab, an, f0, f2) == 0 { 319 miss = miss + 1 320 if report == 1 { rr_drift_name("MISSING-ROW " as *u8, f0) } 321 } 322 } 323 } 324 } } 325 ls = i + 1 326 } 327 i = i + 1 328 } 329 return miss 330} 331func rr_run(prefix: *u8, allow_path: *u8, schemas_path: *u8, counts: *i64, dry: i64) -> i64 { 332 counts[RR_C_REG] = 0 333 counts[RR_C_ALREADY] = 0 334 counts[RR_C_NOSCHEMA] = 0 335 counts[RR_C_FAIL] = 0 336 counts[RR_C_UPDATED] = 0 337 counts[RR_C_SCAFFOLD] = 0 338 counts[RR_C_NOAUTH] = 0 339 counts[RR_C_DUPSCHEMA] = 0 340 // THE CAP WAS FABRICATING AUTHOR DEBT. MEASURED 2026-08-16: RR_FILE_CAP is 262144 and 341 // knowledge/tool_schemas.conf is 266590 bytes, so the last 4,446 bytes were INVISIBLE to this 342 // reader. Every tool whose schema row sits past that byte read as "no schema" and was published as 343 // debt its author had supposedly never paid -- nx_msg_sync at line 1129 has a perfectly good row. 344 // ★A CAPPED READ TURNS "PRESENT BUT PAST THE CAP" INTO "ABSENT", AND AN AUDIT THEN PUBLISHES THAT 345 // ABSENCE AS SOMEONE'S FAULT -- wrong in the direction that sends a human to author contracts that 346 // already exist. 347 // ★★A BUFFER CAP IS NOT A NUMBER TO TUNE -- REMOVE IT. Raising 262144 only moves the cliff to the 348 // next row someone appends, and nothing announces the crossing. sys_read_file sizes its buffer from 349 // the file itself via lseek END and cannot short-read, so there is no ceiling left to outgrow. 350 let al: *i64 = sys_mmap(16) as *i64 351 let ab: *u8 = sys_read_file(allow_path, al) 352 if (ab as i64) == 0 { return 0 - 1 } 353 let an: i64 = al[0] 354 if an <= 0 { return 0 - 1 } 355 let sl: *i64 = sys_mmap(16) as *i64 356 let sb: *u8 = sys_read_file(schemas_path, sl) 357 if (sb as i64) == 0 { return 0 - 1 } 358 let sn: i64 = sl[0] 359 if sn <= 0 { return 0 - 1 } 360 counts[RR_C_DUPSCHEMA] = rr_dup_census(sb, sn, dry) 361 // The deny policy is OPTIONAL: absent is not an error, it means no exposure policy exists. 362 let dl: *i64 = sys_mmap(16) as *i64 363 let db: *u8 = sys_read_file(RR_DENY_PATH, dl) 364 var dn: i64 = 0 365 if (db as i64) != 0 { dn = dl[0] } 366 let name: *u8 = sys_mmap(RR_LINE_CAP) 367 let desc: *u8 = sys_mmap(RR_LINE_CAP) 368 let cur: *u8 = sys_mmap(RR_LINE_CAP) 369 let inv: *u8 = sys_mmap(RR_INV_CAP) 370 let po: *i64 = sys_mmap(16) as *i64 371 let lo: *i64 = sys_mmap(16) as *i64 372 var rows: i64 = 0 373 var ls: i64 = 0 374 var i: i64 = 0 375 while i <= an { 376 var eol: i64 = 0 377 if i == an { eol = 1 } else { if ab[i] == (RR_NL as u8) { eol = 1 } } 378 if eol == 1 { 379 if i > ls { if ab[ls] != (RR_HASH as u8) { 380 rows = rows + 1 381 rr_f0(((ab as i64 + ls) as *u8), i - ls, name) 382 if name[0] != (0 as u8) { if rr_is_deny(name, db, dn) == 0 { 383 if tool_get_pfx(prefix, name, po, lo) == 1 { 384 // UPSERT, NOT INSERT-ONLY (debt seq1526). This branch used to just count 385 // ALREADY and move on, so a description CORRECTED in tool_schemas.conf never 386 // reached consumers: the corpus and the registry silently disagreed and every 387 // stale or misleading tool contract was PERMANENT by construction. Compare 388 // stored-vs-authored and re-put on drift. A reconciler that cannot reconcile 389 // a CHANGE is a seeder. reg_put is keyed by name, so this replaces in place. 390 var updated: i64 = 0 391 // A REGISTERED TOOL WHOSE SCHEMA ROW IS GONE IS INVISIBLE TO THIS AUDIT. 392 // Found 2026-08-07 while testing the gate for NON-VACUITY: deleting a schema 393 // row for an ALREADY-registered tool produced NO drift at all, because this 394 // branch reads a missing row as "nothing to upsert" rather than "the authored 395 // contract is missing". The registry then serves its stale description forever 396 // and the gate says GREEN. Reported as its own count, NOT folded into drift -- 397 // redefining another lane's RED unilaterally is not mine to do. 398 // ⇒ ★★★★★★AN UPSERT THAT TREATS AN ABSENT SOURCE AS "NO CHANGE" CANNOT 399 // DETECT A DELETION -- IT MEASURES EDITS AND CALLS ITSELF A RECONCILER. 400 // ⇒ ★★★★★A NON-VACUITY TEST EARNS ITS KEEP BY WHAT IT FINDS THAT YOU WERE 401 // NOT TESTING FOR. 402 if rr_schema_desc(sb, sn, name, desc) == 0 { 403 if rr_is_stagepath(((ab as i64 + ls) as *u8), i - ls) == 0 { 404 counts[RR_C_NOAUTH] = counts[RR_C_NOAUTH] + 1 405 if dry == 1 { rr_drift_name("no-authored-row " as *u8, name) } 406 } 407 } 408 if rr_schema_desc(sb, sn, name, desc) == 1 { 409 rr_rec_desc(po[0] as *u8, lo[0], cur) 410 if rr_seq(cur, desc) == 0 { 411 if dry == 1 { updated = 1 } else { 412 rr_invoke(name, inv) 413 let rc2: i64 = tool_register_pfx(prefix, name, desc, 414 inv, "GREEN (allowlisted+vetted; discovery row reconciled from the author's tool_schemas.conf entry by nx_toolreg_reconcile)" as *u8) 415 if rc2 == 0 { updated = 1 } else { counts[RR_C_FAIL] = counts[RR_C_FAIL] + 1 } 416 } 417 } 418 } 419 if updated == 1 { counts[RR_C_UPDATED] = counts[RR_C_UPDATED] + 1 } 420 else { counts[RR_C_ALREADY] = counts[RR_C_ALREADY] + 1 } 421 } else { 422 if rr_schema_desc(sb, sn, name, desc) == 1 { 423 if dry == 1 { 424 if rr_is_stagepath(((ab as i64 + ls) as *u8), i - ls) == 1 { 425 counts[RR_C_SCAFFOLD] = counts[RR_C_SCAFFOLD] + 1 426 rr_drift_name("scaffold(.elf.new) " as *u8, name) 427 } else { 428 counts[RR_C_REG] = counts[RR_C_REG] + 1 // WOULD register (drift) 429 rr_drift_name("would-register " as *u8, name) 430 } 431 } else { 432 rr_invoke(name, inv) 433 let rc: i64 = tool_register_pfx(prefix, name, desc, 434 inv, "GREEN (allowlisted+vetted; discovery row reconciled from the author's tool_schemas.conf entry by nx_toolreg_reconcile)" as *u8) 435 if rc == 0 { counts[RR_C_REG] = counts[RR_C_REG] + 1 } else { counts[RR_C_FAIL] = counts[RR_C_FAIL] + 1 } 436 } 437 } else { 438 if rr_is_stagepath(((ab as i64 + ls) as *u8), i - ls) == 1 { 439 counts[RR_C_SCAFFOLD] = counts[RR_C_SCAFFOLD] + 1 440 if dry == 1 { rr_drift_name("scaffold(.elf.new) " as *u8, name) } 441 } else { 442 counts[RR_C_NOSCHEMA] = counts[RR_C_NOSCHEMA] + 1 443 if dry == 1 { rr_drift_name("no-schema " as *u8, name) } 444 } 445 } 446 } 447 } } 448 } } 449 ls = i + 1 450 } 451 i = i + 1 452 } 453 return rows 454} 455// back-compat: reconcile = the mutating run (dry=0). 456func rr_reconcile(prefix: *u8, allow_path: *u8, schemas_path: *u8, counts: *i64) -> i64 { 457 return rr_run(prefix, allow_path, schemas_path, counts, 0) 458} 459// DRIFT = tools that need a registry row (missing+has-schema) PLUS tools with no schema contract at 460// all PLUS tools whose STORED DESCRIPTION has gone stale against its author's schema row. 461// 0 = discovery is complete: every GREEN allowlisted tool is discoverable with a CURRENT contract. 462// Read-only. 463// RR_C_UPDATED ADDED 2026-08-14. It was computed in dry mode all along (rr_run: `if dry == 1 { 464// updated = 1 }`) and then dropped from this sum, so a registry row whose description had drifted 465// reported drift=0 verdict=GREEN -- this audit was blind to the exact defect its own update path 466// exists to repair. MEASURED LIVE: nx_fs published 3 of its 5 verbs (`outline` and `size` were dark 467// to every caller) while `nx_toolreg_reconcile check` answered drift=0 GREEN. 468// A GREEN THAT CANNOT SEE THE DEFECT ITS OWN REPAIR PATH EXISTS FOR IS NOT A MEASUREMENT. 469// Direction of the change is additive-only: a clean estate still returns 0 (gate T8 is the 470// neg-control), so this can raise an alarm that was missing but can never silence one that fired. 471func rr_drift(prefix: *u8, allow_path: *u8, schemas_path: *u8, counts: *i64) -> i64 { 472 let rows: i64 = rr_run(prefix, allow_path, schemas_path, counts, 1) 473 if rows < 0 { return rows } 474 return counts[RR_C_REG] + counts[RR_C_NOSCHEMA] + counts[RR_C_UPDATED] 475} 476// UNDISCOVERABLE RESIDUE: GREEN allowlist tools that are NEITHER in the discovery registry NOR have a 477// schema row -- the genuine debt reconcile cannot auto-fix (not discoverable AND nothing to register from). 478// A tool registered via its OWN organ (discoverable with an organ-supplied description) is NOT residue even 479// without a schemas.conf row -- it works. Comma-joins the residue names into `out`, returns the count. This 480// is what stays RED after a self-heal: a human must author a contract (or the owning register organ run). 481func rr_no_schema_names(prefix: *u8, allow_path: *u8, schemas_path: *u8, out: *u8, outcap: i64) -> i64 { 482 // SAME UNCAPPING AS rr_drift ABOVE, and it matters MORE here: this function produces the NAMED 483 // worklist a human is asked to act on, so a row past the old 262144 ceiling became a person's name 484 // on a list of contracts to write that were already written. 485 let al: *i64 = sys_mmap(16) as *i64 486 let ab: *u8 = sys_read_file(allow_path, al) 487 if (ab as i64) == 0 { out[0] = 0 as u8; return 0 - 1 } 488 let an: i64 = al[0] 489 if an <= 0 { out[0] = 0 as u8; return 0 - 1 } 490 let sl: *i64 = sys_mmap(16) as *i64 491 let sb: *u8 = sys_read_file(schemas_path, sl) 492 if (sb as i64) == 0 { out[0] = 0 as u8; return 0 - 1 } 493 let sn: i64 = sl[0] 494 if sn <= 0 { out[0] = 0 as u8; return 0 - 1 } 495 let dl: *i64 = sys_mmap(16) as *i64 496 let db: *u8 = sys_read_file(RR_DENY_PATH, dl) 497 var dn: i64 = 0 498 if (db as i64) != 0 { dn = dl[0] } 499 let name: *u8 = sys_mmap(RR_LINE_CAP) 500 let desc: *u8 = sys_mmap(RR_LINE_CAP) 501 let po: *i64 = sys_mmap(16) as *i64 502 let lo: *i64 = sys_mmap(16) as *i64 503 var cnt: i64 = 0 504 var o: i64 = 0 505 var ls: i64 = 0 506 var i: i64 = 0 507 while i <= an { 508 var eol: i64 = 0 509 if i == an { eol = 1 } else { if ab[i] == (RR_NL as u8) { eol = 1 } } 510 if eol == 1 { 511 if i > ls { if ab[ls] != (RR_HASH as u8) { 512 rr_f0(((ab as i64 + ls) as *u8), i - ls, name) 513 if name[0] != (0 as u8) { if rr_is_deny(name, db, dn) == 0 { 514 // residue iff BOTH: not discoverable (tool_get != 1 -> absent -1 or tombstoned 0) AND 515 // no schema (unfixable). A registered tool (==1) is discoverable -> never residue. 516 // Deny-listed (deliberately un-advertised by policy) tools are exempt -- not residue. 517 if tool_get_pfx(prefix, name, po, lo) != 1 { if rr_schema_desc(sb, sn, name, desc) == 0 { 518 // SCAFFOLD IS NOT AUTHOR DEBT. rr_drift already separates a row pointing at a STAGED 519 // <t>.elf.new from a genuine missing contract, and states why in its own comment: such 520 // a row "cannot be finished by writing it a contract", and counting it kept the audit 521 // permanently RED for rows that were never going to move. 522 // heal did NOT apply that rule. MEASURED 2026-08-16: the SELF-MANAGING verb -- the one 523 // the standing sweep runs -- reported no-schema-residue=41 where check reported 524 // no-schema=17 plus scaffold=24. So the sweep RED nobody could clear was 24 rows that 525 // are unfixable BY CONSTRUCTION, and the fixable 17 were hidden inside that number. 526 // ★A LAW BANKED IN ONE VERB AND ABSENT FROM ITS SIBLING IS THE SAME DEFECT AS ONE 527 // BANKED IN ONE ORGAN AND ABSENT FROM THE NEXT -- and these two verbs live in the SAME 528 // file and read the SAME allowlist while disagreeing about what counts. 529 // Composes rr_is_stagepath, the predicate rr_drift already uses, so there stays exactly 530 // ONE definition of scaffold rather than two free to drift apart again. 531 if rr_is_stagepath(((ab as i64 + ls) as *u8), i - ls) == 0 { 532 if cnt > 0 { if o < outcap - RR_SEP_LEN { out[o] = RR_COMMA as u8; o = o + 1; out[o] = RR_SPC as u8; o = o + 1 } } 533 var j: i64 = 0 534 while name[j] != (0 as u8) { if o < outcap - 1 { out[o] = name[j]; o = o + 1 } j = j + 1 } 535 cnt = cnt + 1 536 } 537 } } 538 } } 539 } } 540 ls = i + 1 541 } 542 i = i + 1 543 } 544 out[o] = 0 as u8 545 return cnt 546}