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}