code wiki / (root) / nx_toolreg_reconcile.nx

nx_toolreg_reconcile.nx source

↩ module page · 125 lines · 9311 B

1// nx_toolreg_reconcile.nx -- CLI half of the discovery reconciler / drift auditor. Run from nishihost CWD: 2// nx_toolreg_reconcile [allowlist] [schemas] reconcile (register missing rows); default paths 3// nx_toolreg_reconcile reconcile [allowlist] [schemas] same, with the verb stated (2026-08-16) 4// nx_toolreg_reconcile check [allowlist] [schemas] AUDIT: register NOTHING, exit 1 if drift>0 5// nx_toolreg_reconcile heal [allowlist] [schemas] SELF-MANAGING: auto-register everything that HAS 6// a schema, then RED only on the no-schema residue a human must author. This is the verb the 7// standing sweep runs, and it was missing from this banner while `reconcile` -- which the parser 8// did not even accept -- was listed. THE MOST IMPORTANT VERB WAS THE ONE NOT DOCUMENTED. 9// Reconcile registers every GREEN allowlisted tool missing from discovery using the author's own schema 10// row; skips (reports) tools with no schema row. Idempotent. `check` is the read-only regression tooth: 11// it exits non-zero if ANY GREEN tool is unregistered or schema-less -- run it in the standing sweep so 12// the discovery-complete state (allowlist == tools/list) can never silently regrow. 13// exit: 0 clean | 1 io / failed rows / (check) drift>0 license_tier: ORIGINAL 14import "nx_toolreg_reconcile_lib.nx" 15 16const RC_ARG_ALLOW: i64 = 1 // argv: optional allowlist path (reconcile mode) 17const RC_ARG_SCH: i64 = 2 // argv: optional schemas path (reconcile mode) 18const RC_ARGC_A: i64 = 2 // argc with allowlist 19const RC_ARGC_S: i64 = 3 // argc with both 20const RC_CK_ARG_ALLOW: i64 = 2 // argv: optional allowlist path (check mode, after the verb) 21const RC_CK_ARG_SCH: i64 = 3 // argv: optional schemas path (check mode) 22const RC_CK_ARGC_A: i64 = 3 // argc: check + allowlist 23const RC_CK_ARGC_S: i64 = 4 // argc: check + allowlist + schemas 24const RC_ASCII_0: i64 = 48 // '0' 25const RC_DEC: i64 = 10 // decimal base 26 27func rc_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 28func rc_putn(v: i64) -> i64 { 29 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 30 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 31 let d: *u8 = sys_mmap(32); var k: i64 = 0 32 while m > 0 { d[k] = (RC_ASCII_0 + (m % RC_DEC)) as u8; m = m / RC_DEC; k = k + 1 } 33 let o: *u8 = sys_mmap(32); var i: i64 = 0 34 while i < k { o[i] = d[k-1-i]; i = i + 1 } sys_write(1, o, k) 35 return 0 36} 37 38func rc_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 } 39 40func main(argc: i64, argv: *i64) -> i64 { 41 var allow: *u8 = "tool_allowlist.conf" as *u8 42 var sch: *u8 = "knowledge/tool_schemas.conf" as *u8 43 let counts: *i64 = sys_mmap(RR_COUNTS) as *i64 44 // AUDIT MODE: `check [allow] [schemas]` -- register nothing, exit 1 if drift>0 (the regression tooth) 45 if argc >= RC_ARGC_A { if rc_seq(argv[RC_ARG_ALLOW] as *u8, "check" as *u8) == 1 { 46 if argc >= RC_CK_ARGC_A { allow = argv[RC_CK_ARG_ALLOW] as *u8 } 47 if argc >= RC_CK_ARGC_S { sch = argv[RC_CK_ARG_SCH] as *u8 } 48 let drift: i64 = rr_drift(TOOL_PREFIX, allow, sch, counts) 49 if drift < 0 { rc_puts("NX-TOOLREG-AUDIT IO: cannot read allowlist/schemas\n" as *u8); return 1 } 50 // Called BEFORE the audit line is emitted: rr_missing_rows PRINTS its per-row worklist, and a 51 // worklist flushed mid-line would corrupt the one line every downstream parser anchors on. 52 let missing: i64 = rr_missing_rows(allow, sch, 1) 53 rc_puts("NX-TOOLREG-AUDIT drift=" as *u8); rc_putn(drift) 54 rc_puts(" (would-register=" as *u8); rc_putn(counts[RR_C_REG]) 55 rc_puts(" no-schema=" as *u8); rc_putn(counts[RR_C_NOSCHEMA]) 56 // Scaffold is REPORTED but not counted as drift: a row pointing at `<t>.elf.new` is staged, 57 // never promoted, and cannot be "finished" by writing it a contract. Keeping it in drift made 58 // this gate permanently RED for 14 rows that were never going to move. 59 rc_puts(" scaffold=" as *u8); rc_putn(counts[RR_C_SCAFFOLD]) 60 rc_puts(" no-authored-row=" as *u8); rc_putn(counts[RR_C_NOAUTH]) 61 rc_puts(" dup-schema=" as *u8); rc_putn(counts[RR_C_DUPSCHEMA]) 62 // STALE-DESC printed 2026-08-14. It was counted internally (RR_C_UPDATED) and printed NOWHERE in 63 // audit mode, so a row whose description had drifted left EVERY visible bucket at zero: MEASURED 64 // on a one-row fixture, rows=1 and all six printed buckets read 0, including already-discoverable. 65 // A PARTITION THAT DOES NOT SUM IS A LEAK, AND THE LEAKED ROW WAS THE ONLY DEFECT PRESENT. 66 rc_puts(" stale-desc=" as *u8); rc_putn(counts[RR_C_UPDATED]) 67 rc_puts(" already-discoverable=" as *u8); rc_putn(counts[RR_C_ALREADY]) 68 // THE REVERSE JOIN, DECLARED A SEPARATE AXIS (2026-08-20). Every bucket above is a fact about a 69 // row that IS in the allowlist; this is the only one that can see a row that ISN'T. It does NOT 70 // vote in drift (rr_missing_rows says why the overlap forbids folding it in), and -1 means the 71 // census could not read its inputs -- UNMEASURED, never zero. 72 rc_puts(" missing-row=" as *u8); rc_putn(missing) 73 rc_puts(") verdict=" as *u8) 74 if drift == 0 { rc_puts("GREEN (allowlist == tools/list; every tool contracted)\n" as *u8); return 0 } 75 rc_puts("RED (run reconcile: it registers missing rows AND rewrites stale descriptions / add missing tool_schemas.conf rows)\n" as *u8) 76 return 1 77 } } 78 // HEAL MODE: `heal [allow] [schemas]` -- SELF-MANAGING. Auto-register all has-schema tools (the 79 // mechanical drift that needs no human), then report ONLY the genuine no-schema residue (named). Exit 0 80 // when the residue is empty (discovery self-healed), exit 1 when tools are allowlisted with NO contract 81 // (real AUTHOR debt a human must fix). This is what the standing sweep runs: it FIXES what it can and 82 // reds only on the un-authorable -- retiring the manual-reconcile treadmill. 83 if argc >= RC_ARGC_A { if rc_seq(argv[RC_ARG_ALLOW] as *u8, "heal" as *u8) == 1 { 84 if argc >= RC_CK_ARGC_A { allow = argv[RC_CK_ARG_ALLOW] as *u8 } 85 if argc >= RC_CK_ARGC_S { sch = argv[RC_CK_ARG_SCH] as *u8 } 86 if rr_reconcile(TOOL_PREFIX, allow, sch, counts) < 0 { rc_puts("NX-TOOLREG-HEAL IO: cannot read allowlist/schemas\n" as *u8); return 1 } 87 let names: *u8 = sys_mmap(RR_FILE_CAP) 88 let ns: i64 = rr_no_schema_names(TOOL_PREFIX, allow, sch, names, RR_FILE_CAP) 89 rc_puts("NX-TOOLREG-HEAL auto-registered=" as *u8); rc_putn(counts[RR_C_REG]) 90 rc_puts(" already=" as *u8); rc_putn(counts[RR_C_ALREADY]) 91 rc_puts(" no-schema-residue=" as *u8); rc_putn(ns) 92 rc_puts(" verdict=" as *u8) 93 if ns == 0 { rc_puts("GREEN (discovery self-healed; every tool contracted)\n" as *u8); return 0 } 94 rc_puts("RED needs-author-schema: " as *u8); rc_puts(names); rc_puts("\n" as *u8) 95 return 1 96 } } 97 // THE DOCUMENTED TOKEN THE PARSER NEVER ACCEPTED. The banner at the top of this file shows 98 // `reconcile` as a verb, but only `check` and `heal` had cases -- so typing the word the banner 99 // prints made it the ALLOWLIST PATH, and the run died with "cannot read allowlist/schemas", a 100 // message about the wrong subject entirely. MEASURED 2026-08-16: that is exactly what happened, and 101 // finding the cause took a source read. 102 // ★A USAGE LINE THAT SHOWS A TOKEN THE PARSER HAS NO CASE FOR IS A COMMENT ASSERTING A CONTRACT THE 103 // CODE DOES NOT HONOUR -- and the reader trusts the banner, because that is what a banner is for. 104 // ADDITIVE ON PURPOSE: the no-arg default is UNCHANGED, so every existing call site keeps working. 105 // This organ has NINE call-site candidates (nx_wiredclaim_gate, same day), which is precisely why 106 // the fix teaches the parser the documented word rather than changing what no arguments means. 107 var vbase: i64 = 0 108 if argc >= RC_ARGC_A { if rc_seq(argv[RC_ARG_ALLOW] as *u8, "reconcile" as *u8) == 1 { vbase = 1 } } 109 // RECONCILE MODE (default): register missing rows 110 if argc >= RC_ARGC_A + vbase { allow = argv[RC_ARG_ALLOW + vbase] as *u8 } 111 if argc >= RC_ARGC_S + vbase { sch = argv[RC_ARG_SCH + vbase] as *u8 } 112 let rows: i64 = rr_reconcile(TOOL_PREFIX, allow, sch, counts) 113 if rows < 0 { rc_puts("NX-TOOLREG-RECONCILE IO: cannot read allowlist/schemas\n" as *u8); return 1 } 114 rc_puts("NX-TOOLREG-RECONCILE rows=" as *u8); rc_putn(rows) 115 rc_puts(" registered=" as *u8); rc_putn(counts[RR_C_REG]) 116 rc_puts(" already=" as *u8); rc_putn(counts[RR_C_ALREADY]) 117 // UPDATED is reported separately from REGISTERED so a description CORRECTION is visible as 118 // work done, not hidden inside 'already' (debt seq1526). 0 on a no-change run = idempotence. 119 rc_puts(" updated=" as *u8); rc_putn(counts[RR_C_UPDATED]) 120 rc_puts(" skipped-no-schema=" as *u8); rc_putn(counts[RR_C_NOSCHEMA]) 121 rc_puts(" failed=" as *u8); rc_putn(counts[RR_C_FAIL]) 122 rc_puts("\n" as *u8) 123 if counts[RR_C_FAIL] > 0 { return 1 } 124 return 0 125}