nx_failclass.nx source
↩ module page · 718 lines · 34355 B
1// nx_failclass.nx -- CLASSIFY THE ESTATE'S OWN FAILURE RECORD AGAINST A PUBLISHED TAXONOMY.
2//
3// The estate banks ~70,100 distilled law rows and every one of them is failure-shaped, but they
4// were never CLASSIFIED and never COUNTED -- so nobody could say which failure class bites most,
5// which is chronic, or whether any class is extinct. Anecdotes without a taxonomy.
6//
7// This organ answers that question over the WHOLE population, never a sample:
8// nx_failclass scan <signatures.conf> <learnings.jsonl> <worklist.out>
9// nx_failclass selftest
10//
11// AND, since 2026-08-20, THE FM11 RUNG OF /compare/failmodes -- a second census over a DIFFERENT
12// population, the refusals this estate actually EMITS:
13// nx_failclass refusal <refusal_shape.conf> <worklist.out>
14// nx_failclass refusal_ratchet <worklist> <baseline>
15// The ruler lives in nx_refusal_shape_lib so this verb and nx_refusal_shape_gate cannot disagree,
16// and it scores the EMITTER only: whether the message names its subject and its remedy. Whether a
17// caller HEEDED it is a second, independent axis, census-only over the call record, and no counter
18// in this organ may be read as evidence about it.
19//
20// THREE PROPERTIES IT REFUSES TO GIVE UP:
21//
22// 1. u2605 IT IS A CENSUS, NOT A GATE. An uncalibrated classifier must report NUMBERS, never
23// verdicts. There is deliberately no verdict= line and no RED. Exit is 0 for a completed
24// scan, 3 for an unreadable input, 2 for usage. A reader who wants a bar must set it
25// themselves against numbers they can see.
26//
27// 2. u2605 THE REASON TRAVELS WITH THE COUNT. Every counted row is also WRITTEN to the worklist
28// with the exact signature that classified it. A count without a worklist is not actionable,
29// and a worklist without the reason is still one step short -- the reason is already in hand
30// at measure time and is normally thrown away.
31//
32// 3. u2605 UNCLASSIFIED IS ITS OWN BUCKET AND MULTI IS ITS OWN AXIS. A row matching no signature
33// is never folded into a known class; a row matching two classes genuinely names two, so it is
34// counted once in the MULTI partition member AND once per class on the overlapping per-class
35// axis, which is labelled as an axis and never summed into the partition.
36// The partition PRINTS ITS OWN SUM: single + multi + unclassified must equal rows.
37//
38// WORD BOUNDARIES ARE LOAD-BEARING, NOT COSMETIC. Measured 2026-08-20 over 70,100 rows, the bare
39// substring "cap" hits 7,802 rows and the WORD cap/caps/capped hits 2,992 -- the residue is
40// "capability", "capture", "capsearch", "capacity". A substring matcher would have published a
41// LIMIT incidence inflated by 160 percent and nothing downstream could have seen it.
42//
43// license_tier: ORIGINAL
44// module: nishi-core.quality.failclass
45// capability: FAILURE_TAXONOMY_CENSUS
46import "nx_estate_path.nx"
47import "nx_itoa_lib.nx"
48import "nx_refusal_shape_lib.nx"
49import "nx_verdictlog_lib.nx"
50
51const FC_MODE_0644: i64 = 420
52
53// Structural bounds. Each one ANNOUNCES when it binds -- a cap reached in silence becomes a
54// measurement nobody knows is partial.
55const FC_MAX_CLASS: i64 = 32
56const FC_MAX_MONTH: i64 = 64
57const FC_MONTH_LEN: i64 = 7
58const FC_TEXT_PREVIEW: i64 = 160
59
60func fw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
61func fe(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 }
62
63func fc_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
64
65func fc_streq(a: *u8, b: *u8) -> i64 {
66 var i: i64 = 0
67 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
68 if b[i] != (0 as u8) { return 0 }
69 return 1
70}
71
72// Case-folded, WORD-BOUNDED search of nee[0,nlen) inside hay[hs,he). Returns the offset or -1.
73//
74// THE IMPLEMENTATION MOVED TO nx_refusal_shape_lib (rs_find_word) ON 2026-08-20 AND THERE IS NOW
75// EXACTLY ONE OF IT. The FM11 rung needed the same word-bounded matcher, and a second copy is the
76// duplicate-ruler defect: two matchers that must agree, kept in step by discipline instead of by
77// construction. The call sites here are untouched and the behaviour is the same bytes -- this is a
78// delegation, not a rewrite, so `selftest` remains the proof that it did not change.
79// ★ WHEN TWO ORGANS MUST AGREE, MAKE DISAGREEMENT IMPOSSIBLE BY CONSTRUCTION.
80func fc_find_word(hay: *u8, hs: i64, he: i64, nee: *u8, nlen: i64) -> i64 {
81 return rs_find_word(hay, hs, he, nee, nlen)
82}
83
84// Plain (NOT word-bounded) forward search -- used only for structural JSON field markers such as
85// the literal "text":" , never for classification.
86func fc_find_raw(hay: *u8, hs: i64, he: i64, nee: *u8, nlen: i64) -> i64 {
87 if nlen <= 0 { return 0 - 1 }
88 var i: i64 = hs
89 while i + nlen <= he {
90 var j: i64 = 0
91 var ok: i64 = 1
92 var scanning: i64 = 1
93 while scanning == 1 {
94 if j >= nlen { scanning = 0 } else {
95 if hay[i + j] != nee[j] { ok = 0; scanning = 0 } else { j = j + 1 }
96 }
97 }
98 if ok == 1 { return i }
99 i = i + 1
100 }
101 return 0 - 1
102}
103
104// ---------------------------------------------------------------------------------------------
105// Class-name registry. Names are discovered FROM THE CONF, never hardcoded here -- adding a class
106// is a data edit, not a rebuild.
107// ---------------------------------------------------------------------------------------------
108func fc_class_id(names: *u8, nlens: *i64, ncls: *i64, s: *u8, slen: i64) -> i64 {
109 var i: i64 = 0
110 while i < ncls[0] {
111 if nlens[i] == slen {
112 var j: i64 = 0
113 var ok: i64 = 1
114 var scanning: i64 = 1
115 while scanning == 1 {
116 if j >= slen { scanning = 0 } else {
117 if names[i * 32 + j] != s[j] { ok = 0; scanning = 0 } else { j = j + 1 }
118 }
119 }
120 if ok == 1 { return i }
121 }
122 i = i + 1
123 }
124 if ncls[0] >= FC_MAX_CLASS { return 0 - 1 }
125 let id: i64 = ncls[0]
126 var k: i64 = 0
127 while k < slen { if k < 32 { names[id * 32 + k] = s[k] } k = k + 1 }
128 nlens[id] = slen
129 ncls[0] = id + 1
130 return id
131}
132
133func fc_put_class(names: *u8, nlens: *i64, id: i64) -> i64 {
134 sys_write(1, ((names as i64) + id * 32) as *u8, nlens[id])
135 return 0
136}
137
138func fc_buf_class(dst: *u8, off: i64, names: *u8, nlens: *i64, id: i64) -> i64 {
139 var k: i64 = 0
140 while k < nlens[id] { dst[off + k] = names[id * 32 + k]; k = k + 1 }
141 return off + nlens[id]
142}
143
144func fc_buf_bytes(dst: *u8, off: i64, src: *u8, s: i64, e: i64) -> i64 {
145 var o: i64 = off
146 var i: i64 = s
147 while i < e {
148 let c: i64 = src[i] as i64
149 // Never let a raw control byte into the worklist: a forged newline turns one row into two.
150 if c < 32 { dst[o] = 46 as u8 } else { dst[o] = src[i] }
151 o = o + 1
152 i = i + 1
153 }
154 return o
155}
156
157func fc_buf_lit(dst: *u8, off: i64, s: *u8) -> i64 {
158 var o: i64 = off
159 var i: i64 = 0
160 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 }
161 return o
162}
163
164// ---------------------------------------------------------------------------------------------
165// Month registry -- the record date, taken from the slug's trailing YYYY-MM-DD. A slug without
166// one is NO-DATE, which is its own month bucket for the same reason UNCLASSIFIED is its own
167// class bucket.
168// ---------------------------------------------------------------------------------------------
169func fc_month_id(mons: *u8, nmon: *i64, src: *u8, s: i64, e: i64) -> i64 {
170 var mb: *u8 = sys_mmap(16)
171 var have: i64 = 0
172 if e - s >= 10 {
173 let p: i64 = e - 10
174 var digits_ok: i64 = 1
175 if src[p + 4] != (45 as u8) { digits_ok = 0 }
176 if src[p + 7] != (45 as u8) { digits_ok = 0 }
177 var q: i64 = 0
178 while q < 10 {
179 if q != 4 { if q != 7 {
180 let c: i64 = src[p + q] as i64
181 if c < 48 { digits_ok = 0 }
182 if c > 57 { digits_ok = 0 }
183 } }
184 q = q + 1
185 }
186 if digits_ok == 1 {
187 var k: i64 = 0
188 while k < FC_MONTH_LEN { mb[k] = src[p + k]; k = k + 1 }
189 have = 1
190 }
191 }
192 if have == 0 {
193 mb[0] = 78 as u8; mb[1] = 79 as u8; mb[2] = 45 as u8
194 mb[3] = 68 as u8; mb[4] = 65 as u8; mb[5] = 84 as u8; mb[6] = 69 as u8
195 }
196 var i: i64 = 0
197 while i < nmon[0] {
198 var j: i64 = 0
199 var ok: i64 = 1
200 var scanning: i64 = 1
201 while scanning == 1 {
202 if j >= FC_MONTH_LEN { scanning = 0 } else {
203 if mons[i * FC_MONTH_LEN + j] != mb[j] { ok = 0; scanning = 0 } else { j = j + 1 }
204 }
205 }
206 if ok == 1 { sys_munmap(mb, 16); return i }
207 i = i + 1
208 }
209 if nmon[0] >= FC_MAX_MONTH { sys_munmap(mb, 16); return 0 - 1 }
210 let id: i64 = nmon[0]
211 var k2: i64 = 0
212 while k2 < FC_MONTH_LEN { mons[id * FC_MONTH_LEN + k2] = mb[k2]; k2 = k2 + 1 }
213 nmon[0] = id + 1
214 sys_munmap(mb, 16)
215 return id
216}
217
218// ---------------------------------------------------------------------------------------------
219// selftest -- the bite teeth. u2605 A DETECTOR THAT HAS ONLY EVER SEEN CORRECT DATA HAS NOT BEEN
220// SHOWN TO FIRE, and its inverse: a matcher that fires on everything discriminates nothing. Both
221// directions are asserted here, and the NEGATIVE CONTROLS are named so a census can find them.
222// ---------------------------------------------------------------------------------------------
223func fc_expect(name: *u8, got: i64, want_hit: i64, passed: *i64, total: *i64) -> i64 {
224 total[0] = total[0] + 1
225 var hit: i64 = 0
226 if got >= 0 { hit = 1 }
227 if hit == want_hit {
228 passed[0] = passed[0] + 1
229 fw(" PASS " as *u8); fw(name); fw("\n" as *u8)
230 } else {
231 fw(" FAIL " as *u8); fw(name)
232 fw(" got_offset=" as *u8); nxi_out(got)
233 fw(" want_hit=" as *u8); nxi_out(want_hit); fw("\n" as *u8)
234 }
235 return 0
236}
237
238func fc_eq(name: *u8, got: i64, want: i64, passed: *i64, total: *i64) -> i64 {
239 total[0] = total[0] + 1
240 if got == want {
241 passed[0] = passed[0] + 1
242 fw(" PASS " as *u8); fw(name); fw(" = " as *u8); nxi_out(got); fw("\n" as *u8)
243 } else {
244 fw(" FAIL " as *u8); fw(name)
245 fw(" got=" as *u8); nxi_out(got)
246 fw(" want=" as *u8); nxi_out(want); fw("\n" as *u8)
247 }
248 return 0
249}
250
251func fc_writef(path: *u8, s: *u8) -> i64 {
252 let fd: i64 = sys_openat_wr(path, FC_MODE_0644)
253 if fd < 0 { return 0 - 1 }
254 sys_write(fd, s, fc_strlen(s))
255 sys_close(fd)
256 return 0
257}
258
259// END-TO-END tooth over a fixture the organ ASSEMBLES AT RUNTIME. Scratch lives under /tmp so it
260// can never collide with a production corpus, and it is rewritten every run so the tooth is
261// idempotent -- a gate that is not idempotent reports on its first run and lies about every run
262// after. The fixture is built so the trivial wrong implementations CANNOT pass it: one row must
263// classify SILENT, one must classify LIMIT, one must be MULTI, and one is a NEGATIVE CONTROL that
264// contains the near-miss words "capability" and "absence" and must stay UNCLASSIFIED.
265func fc_e2e(passed: *i64, total: *i64) -> i64 {
266 fw("nx_failclass selftest -- end to end over a runtime fixture\n" as *u8)
267 sys_mkdir("/tmp/nx_failclass" as *u8, 493)
268 let cf: *u8 = "/tmp/nx_failclass/sig.conf" as *u8
269 let xf: *u8 = "/tmp/nx_failclass/corpus.jsonl" as *u8
270 let of: *u8 = "/tmp/nx_failclass/worklist.tsv" as *u8
271
272 if fc_writef(cf, "SILENT|silent|w\nLIMIT|cap|w\n" as *u8) < 0 {
273 fw(" FAIL e2e-fixture-unwritable\n" as *u8)
274 total[0] = total[0] + 1
275 return 1
276 }
277 fc_writef(xf, "{\"slug\":\"a-2026-08-01\",\"text\":\"it failed silent and nobody saw\"}\n{\"slug\":\"b-2026-08-01\",\"text\":\"the read cap was reached\"}\n{\"slug\":\"c-2026-08-01\",\"text\":\"a silent truncation at the cap\"}\n{\"slug\":\"d-2026-08-01\",\"text\":\"a capability whose absence was proven\"}\n" as *u8)
278
279 let res: *i64 = sys_mmap((FC_MAX_CLASS + 8) * 8) as *i64
280 var i: i64 = 0
281 while i < FC_MAX_CLASS + 8 { res[i] = 0 - 999; i = i + 1 }
282
283 fc_scan(cf, xf, of, res)
284
285 // ASSERT THE FIXTURE REACHED THE CONDITION before asserting the outcome: if the scan did not
286 // read four rows, every count below is about something other than the fixture.
287 fc_eq("e2e-fixture-rows-were-actually-read" as *u8, res[0], 4, passed, total)
288 fc_eq("e2e-single-SILENT" as *u8, res[3], 1, passed, total)
289 fc_eq("e2e-single-LIMIT" as *u8, res[4], 1, passed, total)
290 fc_eq("e2e-MULTI-row-counted-once-in-multi" as *u8, res[2], 1, passed, total)
291 fc_eq("neg-control-capability-and-absence-stay-UNCLASSIFIED" as *u8, res[1], 1, passed, total)
292 return 0
293}
294
295func fc_selftest() -> i64 {
296 let passed: *i64 = sys_mmap(16) as *i64
297 let total: *i64 = sys_mmap(16) as *i64
298 passed[0] = 0
299 total[0] = 0
300 fw("nx_failclass selftest -- word-boundary matcher\n" as *u8)
301
302 let cap: *u8 = "cap" as *u8
303 let h1: *u8 = "the read cap was reached" as *u8
304 fc_expect("fires-on-the-real-word-cap" as *u8,
305 fc_find_word(h1, 0, fc_strlen(h1), cap, 3), 1, passed, total)
306
307 let h2: *u8 = "a capability nobody wired" as *u8
308 fc_expect("neg-control-capability-is-not-cap" as *u8,
309 fc_find_word(h2, 0, fc_strlen(h2), cap, 3), 0, passed, total)
310
311 let h3: *u8 = "capture the artifact before retry" as *u8
312 fc_expect("neg-control-capture-is-not-cap" as *u8,
313 fc_find_word(h3, 0, fc_strlen(h3), cap, 3), 0, passed, total)
314
315 let h4: *u8 = "the buffer was capped at 4 MiB" as *u8
316 fc_expect("neg-control-capped-is-not-the-word-cap" as *u8,
317 fc_find_word(h4, 0, fc_strlen(h4), cap, 3), 0, passed, total)
318
319 let capped: *u8 = "capped" as *u8
320 fc_expect("fires-on-capped-when-capped-is-the-signature" as *u8,
321 fc_find_word(h4, 0, fc_strlen(h4), capped, 6), 1, passed, total)
322
323 let ind: *u8 = "indistinguishable from" as *u8
324 let h5: *u8 = "A STALE COPY IS INDISTINGUISHABLE FROM A BROKEN ONE" as *u8
325 fc_expect("phrase-matches-case-folded" as *u8,
326 fc_find_word(h5, 0, fc_strlen(h5), ind, 22), 1, passed, total)
327
328 let h6: *u8 = "tall vertical line never compressed never short torso" as *u8
329 fc_expect("neg-control-content-row-matches-no-limit-signature" as *u8,
330 fc_find_word(h6, 0, fc_strlen(h6), cap, 3), 0, passed, total)
331
332 let unw: *u8 = "unwired" as *u8
333 fc_expect("neg-control-content-row-matches-no-absent-signature" as *u8,
334 fc_find_word(h6, 0, fc_strlen(h6), unw, 7), 0, passed, total)
335
336 // A signature must not match across a field boundary it was never given.
337 let h7: *u8 = "silent" as *u8
338 fc_expect("bounded-window-excludes-text-outside-it" as *u8,
339 fc_find_word(h5, 0, 4, h7, 6), 0, passed, total)
340
341 fc_e2e(passed, total)
342
343 fw("passed " as *u8); nxi_out(passed[0])
344 fw("/" as *u8); nxi_out(total[0]); fw("\n" as *u8)
345 if passed[0] == total[0] { return 0 }
346 return 1
347}
348
349// ---------------------------------------------------------------------------------------------
350// `res` is an OPTIONAL results vector so a caller can ASSERT the scan, not merely read its
351// printout. Pass 0 to skip. Layout: res[0]=rows, res[1]=unclassified, res[2]=multi,
352// res[3+c]=single count for class c.
353//
354// u2605 THIS PARAMETER EXISTS BECAUSE THE UNIT TEETH COULD NOT SEE A TOTAL FAILURE. The first
355// build of this organ passed 9/9 on its matcher while the scan path resolved every signature
356// pointer into the WRONG BUFFER, so SILENT and LIMIT both censused as ZERO over 70,100 rows. The
357// matcher was perfect and the wiring was broken, and no tooth in the organ could tell. What
358// caught it was an INDEPENDENT instrument disagreeing -- a plain substring count that said 2,163
359// rows contain "silent". A gate cannot test a conjunct its subject has no evidence for.
360func fc_scan(confp: *u8, corpp: *u8, outp: *u8, res: *i64) -> i64 {
361 let clen: *i64 = sys_mmap(16) as *i64
362 let conf: *u8 = sys_read_file(confp, clen)
363 if clen[0] <= 0 { fe("nx_failclass: cannot read signature conf\n" as *u8); return 3 }
364
365 let xlen: *i64 = sys_mmap(16) as *i64
366 let corp: *u8 = sys_read_file(corpp, xlen)
367 if xlen[0] <= 0 { fe("nx_failclass: cannot read corpus\n" as *u8); return 3 }
368
369 // Signature table capacity derived FROM THE CONF, not guessed: the shortest possible row is
370 // about 8 bytes, so conf_len/8 can never be exceeded by the rows the conf actually holds.
371 let sigcap: i64 = clen[0] / 8 + 8
372 let sig_off: *i64 = sys_mmap(sigcap * 8) as *i64
373 let sig_len: *i64 = sys_mmap(sigcap * 8) as *i64
374 let sig_cls: *i64 = sys_mmap(sigcap * 8) as *i64
375 let nsig: *i64 = sys_mmap(16) as *i64
376 nsig[0] = 0
377
378 let cnames: *u8 = sys_mmap(FC_MAX_CLASS * 32)
379 let cnlens: *i64 = sys_mmap(FC_MAX_CLASS * 8) as *i64
380 let ncls: *i64 = sys_mmap(16) as *i64
381 ncls[0] = 0
382
383 // ---- parse the conf: CLASS|signature|kind, comments start with the hash byte 35 ----------
384 var p: i64 = 0
385 var conf_bad: i64 = 0
386 while p < clen[0] {
387 var q: i64 = p
388 var eol: i64 = clen[0]
389 var looking: i64 = 1
390 while looking == 1 {
391 if q >= clen[0] { eol = clen[0]; looking = 0 } else {
392 if conf[q] == (10 as u8) { eol = q; looking = 0 } else { q = q + 1 }
393 }
394 }
395 if eol > p {
396 if conf[p] != (35 as u8) {
397 let bar1: *u8 = "|" as *u8
398 let b1: i64 = fc_find_raw(conf, p, eol, bar1, 1)
399 if b1 > p {
400 let b2: i64 = fc_find_raw(conf, b1 + 1, eol, bar1, 1)
401 if b2 > b1 {
402 let cid: i64 = fc_class_id(cnames, cnlens, ncls, ((conf as i64) + p) as *u8, b1 - p)
403 if cid < 0 { conf_bad = conf_bad + 1 } else {
404 if nsig[0] < sigcap {
405 sig_off[nsig[0]] = b1 + 1
406 sig_len[nsig[0]] = b2 - b1 - 1
407 sig_cls[nsig[0]] = cid
408 nsig[0] = nsig[0] + 1
409 } else { conf_bad = conf_bad + 1 }
410 }
411 } else { conf_bad = conf_bad + 1 }
412 } else { conf_bad = conf_bad + 1 }
413 }
414 }
415 p = eol + 1
416 }
417
418 if nsig[0] == 0 { fe("nx_failclass: signature conf yielded ZERO signatures -- refusing to publish a distribution of noise\n" as *u8); return 3 }
419
420 // ---- counters ---------------------------------------------------------------------------
421 let single: *i64 = sys_mmap(FC_MAX_CLASS * 8) as *i64
422 let anyc: *i64 = sys_mmap(FC_MAX_CLASS * 8) as *i64
423 let mons: *u8 = sys_mmap(FC_MAX_MONTH * FC_MONTH_LEN)
424 let nmon: *i64 = sys_mmap(16) as *i64
425 nmon[0] = 0
426 let mcnt: *i64 = sys_mmap(FC_MAX_MONTH * FC_MAX_CLASS * 8) as *i64
427 let mrows: *i64 = sys_mmap(FC_MAX_MONTH * 8) as *i64
428 var z: i64 = 0
429 while z < FC_MAX_CLASS { single[z] = 0; anyc[z] = 0; z = z + 1 }
430 z = 0
431 while z < FC_MAX_MONTH * FC_MAX_CLASS { mcnt[z] = 0; z = z + 1 }
432 z = 0
433 while z < FC_MAX_MONTH { mrows[z] = 0; z = z + 1 }
434
435 let hits: *i64 = sys_mmap(FC_MAX_CLASS * 8) as *i64
436 let firstsig: *i64 = sys_mmap(FC_MAX_CLASS * 8) as *i64
437
438 // Worklist buffer sized FROM THE INPUT: every emitted line is a prefix of the row that
439 // produced it plus a short tag, so 1x the corpus plus a per-row tag allowance is a bound,
440 // not a guess. If it ever binds, it ANNOUNCES -- it does not silently stop.
441 let outcap: i64 = xlen[0] + xlen[0] / 2 + 65536
442 let out: *u8 = sys_mmap(outcap)
443 var oo: i64 = 0
444 var out_trunc: i64 = 0
445
446 var rows: i64 = 0
447 var multi: i64 = 0
448 var unclass: i64 = 0
449 var month_overflow: i64 = 0
450
451 let tmark: *u8 = "\"text\":\"" as *u8
452 let smark: *u8 = "\"slug\":\"" as *u8
453 let tab: *u8 = "\t" as *u8
454 let nl: *u8 = "\n" as *u8
455
456 var lp: i64 = 0
457 while lp < xlen[0] {
458 var q2: i64 = lp
459 var le2: i64 = xlen[0]
460 var look2: i64 = 1
461 while look2 == 1 {
462 if q2 >= xlen[0] { le2 = xlen[0]; look2 = 0 } else {
463 if corp[q2] == (10 as u8) { le2 = q2; look2 = 0 } else { q2 = q2 + 1 }
464 }
465 }
466 if le2 > lp {
467 rows = rows + 1
468 // text field: from the marker to the closing quote-brace that ends every row
469 let tm: i64 = fc_find_raw(corp, lp, le2, tmark, 8)
470 var ts: i64 = lp
471 var te: i64 = le2
472 if tm >= 0 { ts = tm + 8; te = le2 - 2 }
473 if te < ts { te = ts }
474 // slug field
475 let sm: i64 = fc_find_raw(corp, lp, le2, smark, 8)
476 var ss: i64 = lp
477 var se: i64 = lp
478 if sm >= 0 {
479 ss = sm + 8
480 var r: i64 = ss
481 var look3: i64 = 1
482 while look3 == 1 {
483 if r >= le2 { se = le2; look3 = 0 } else {
484 if corp[r] == (34 as u8) { se = r; look3 = 0 } else { r = r + 1 }
485 }
486 }
487 }
488
489 var c: i64 = 0
490 while c < FC_MAX_CLASS { hits[c] = 0; firstsig[c] = 0 - 1; c = c + 1 }
491
492 var s: i64 = 0
493 while s < nsig[0] {
494 let cid: i64 = sig_cls[s]
495 if hits[cid] == 0 {
496 let where: i64 = fc_find_word(corp, ts, te, ((conf as i64) + sig_off[s]) as *u8, sig_len[s])
497 if where >= 0 { hits[cid] = 1; firstsig[cid] = s }
498 }
499 s = s + 1
500 }
501
502 var nclshit: i64 = 0
503 c = 0
504 while c < ncls[0] { if hits[c] == 1 { nclshit = nclshit + 1 } c = c + 1 }
505
506 let mid: i64 = fc_month_id(mons, nmon, corp, ss, se)
507 if mid < 0 { month_overflow = month_overflow + 1 } else { mrows[mid] = mrows[mid] + 1 }
508
509 if nclshit == 0 { unclass = unclass + 1 } else {
510 if nclshit == 1 { multi = multi + 0 } else { multi = multi + 1 }
511 c = 0
512 while c < ncls[0] {
513 if hits[c] == 1 {
514 anyc[c] = anyc[c] + 1
515 if nclshit == 1 { single[c] = single[c] + 1 }
516 if mid >= 0 { mcnt[mid * FC_MAX_CLASS + c] = mcnt[mid * FC_MAX_CLASS + c] + 1 }
517 // ---- the reason travels with the count ----
518 if oo + 512 < outcap {
519 oo = fc_buf_class(out, oo, cnames, cnlens, c)
520 oo = fc_buf_lit(out, oo, tab)
521 let sg: i64 = firstsig[c]
522 oo = fc_buf_bytes(out, oo, conf, sig_off[sg], sig_off[sg] + sig_len[sg])
523 oo = fc_buf_lit(out, oo, tab)
524 oo = fc_buf_bytes(out, oo, corp, ss, se)
525 oo = fc_buf_lit(out, oo, tab)
526 var pe: i64 = ts + FC_TEXT_PREVIEW
527 if pe > te { pe = te }
528 oo = fc_buf_bytes(out, oo, corp, ts, pe)
529 oo = fc_buf_lit(out, oo, nl)
530 } else { out_trunc = 1 }
531 }
532 c = c + 1
533 }
534 }
535 }
536 lp = le2 + 1
537 }
538
539 let fd: i64 = sys_openat_wr(outp, FC_MODE_0644)
540 var wrote: i64 = 0
541 if fd >= 0 { wrote = sys_write(fd, out, oo); sys_close(fd) }
542
543 // ---- report -----------------------------------------------------------------------------
544 fw("nx_failclass census\n" as *u8)
545 fw("signatures=" as *u8); nxi_out(nsig[0])
546 fw(" classes=" as *u8); nxi_out(ncls[0])
547 fw(" conf_rows_rejected=" as *u8); nxi_out(conf_bad)
548 fw(" corpus_bytes=" as *u8); nxi_out(xlen[0])
549 fw(" rows=" as *u8); nxi_out(rows); fw("\n" as *u8)
550
551 fw("-- PARTITION (every row lands in exactly one member) --\n" as *u8)
552 var sum: i64 = 0
553 var c3: i64 = 0
554 while c3 < ncls[0] {
555 fw("SINGLE " as *u8); fc_put_class(cnames, cnlens, c3)
556 fw(" = " as *u8); nxi_out(single[c3]); fw("\n" as *u8)
557 sum = sum + single[c3]
558 c3 = c3 + 1
559 }
560 fw("MULTI (matched more than one class) = " as *u8); nxi_out(multi); fw("\n" as *u8)
561 fw("UNCLASSIFIED (matched no signature) = " as *u8); nxi_out(unclass); fw("\n" as *u8)
562 sum = sum + multi + unclass
563 fw("PARTITION SUM = " as *u8); nxi_out(sum)
564 fw(" rows = " as *u8); nxi_out(rows)
565 if sum == rows { fw(" RECONCILES\n" as *u8) } else { fw(" DOES NOT RECONCILE -- do not publish these numbers\n" as *u8) }
566
567 fw("-- AXIS (overlapping: a row naming two classes is counted in BOTH; NEVER sum this) --\n" as *u8)
568 c3 = 0
569 while c3 < ncls[0] {
570 fw("ANY " as *u8); fc_put_class(cnames, cnlens, c3)
571 fw(" = " as *u8); nxi_out(anyc[c3]); fw("\n" as *u8)
572 c3 = c3 + 1
573 }
574
575 fw("-- TREND by record month (a level cannot express a trajectory) --\n" as *u8)
576 var m: i64 = 0
577 while m < nmon[0] {
578 sys_write(1, ((mons as i64) + m * FC_MONTH_LEN) as *u8, FC_MONTH_LEN)
579 fw(" rows=" as *u8); nxi_out(mrows[m])
580 c3 = 0
581 while c3 < ncls[0] {
582 fw(" " as *u8); fc_put_class(cnames, cnlens, c3)
583 fw("=" as *u8); nxi_out(mcnt[m * FC_MAX_CLASS + c3])
584 c3 = c3 + 1
585 }
586 fw("\n" as *u8)
587 m = m + 1
588 }
589 if month_overflow > 0 {
590 fw("MONTH-TABLE-FULL rows_unbucketed=" as *u8); nxi_out(month_overflow)
591 fw(" -- the trend above is a FLOOR, not a total\n" as *u8)
592 }
593
594 fw("worklist=" as *u8); fw(outp)
595 fw(" bytes=" as *u8); nxi_out(wrote)
596 fw(" of=" as *u8); nxi_out(oo)
597 if fd < 0 { fw(" WORKLIST-WRITE-FAILED" as *u8) }
598 if out_trunc == 1 { fw(" WORKLIST-TRUNCATED=1 -- the counts above are complete, the LIST is not" as *u8) }
599 fw("\n" as *u8)
600 fw("note: this organ emits NUMBERS and NO verdict -- an uncalibrated classifier that votes is a false-alarm generator with an authoritative name\n" as *u8)
601
602 if (res as i64) != 0 {
603 res[0] = rows
604 res[1] = unclass
605 res[2] = multi
606 var rc: i64 = 0
607 while rc < ncls[0] { res[3 + rc] = single[rc]; rc = rc + 1 }
608 }
609 return 0
610}
611
612// ---------------------------------------------------------------------------------------------
613// FM11 -- THE EMITTER HALF OF THE TENTH CELL, LOUD-AND-CORRECT.
614//
615// The classifier above answers "which failure class does this law row describe". THIS answers a
616// different question over a DIFFERENT population: of the refusals this estate actually emits, how
617// many name their SUBJECT and their REMEDY. It is deliberately a sibling verb rather than a second
618// organ, and the ruler itself lives in nx_refusal_shape_lib so this verb and its gate cannot drift
619// apart.
620//
621// ⚠ EMITTER AXIS ONLY, AND THAT BOUNDARY IS LOAD-BEARING. Emitter quality and caller response are
622// two INDEPENDENT axes -- nx_fs read declares its truncation cap AND its remedy (emitter CORRECT)
623// while the reader filtered that line out and filed a critical defect (caller IGNORED). The
624// caller-heeded half is census-only over the call record and is NOT measured here; a refusal that
625// callers routinely retry through is LOUD-AND-IGNORED however well written it is.
626//
627// ⚠ IT DOES NOT MOVE THE 70,100-ROW LAW CENSUS AND NOBODY SHOULD RE-RUN THAT EXPECTING MOVEMENT.
628// That instrument classifies failure-shaped law rows; this one scores emitted messages. Different
629// population, different question, deliberately separate counters.
630func fc_refusal_shape(confp: *u8, outp: *u8) -> i64 {
631 let res: *i64 = rs_res_new()
632 return rs_scan(confp, outp, res, 0 as *u8)
633}
634
635// ONE BEAT, ONE PROCESS, IN ORDER. The census writes the worklist and the ratchet reads it, so two
636// clock rows would make the ORDER a race: the ratchet would judge whichever worklist happened to be
637// on disk, which on a slow array is yesterday's. Exit carries the RATCHET's verdict
638// (0 ALLOW / 1 REFUSE / 3 UNMEASURED) because that is the part a watcher acts on; the census's
639// numbers are on stdout above it, unchanged and still verdict-free.
640func fc_refusal_beat(confp: *u8, outp: *u8, basep: *u8) -> i64 {
641 let res: *i64 = rs_res_new()
642 let rc: i64 = rs_scan(confp, outp, res, 0 as *u8)
643 if rc != 0 { return rc }
644 return rs_ratchet(outp, basep)
645}
646
647// FM3 -- ALERT QUALITY SCORED AS A CLASSIFIER. Every scoring decision lives in nx_verdictlog_lib so
648// this verb and nx_verdictlog_gate cannot drift about what a verdict is or what an offender is.
649// NUMBERS AND NO VERDICT: exit 0 whenever the census RAN, because an uncalibrated classifier that
650// votes is a false-alarm generator with an authoritative name. The bar lives in the ratchet.
651func fc_alert_score(confp: *u8, dirp: *u8, outp: *u8) -> i64 {
652 let tot: *i64 = sys_mmap(VL_T_LEN * 8) as *i64
653 return vl_alert_scan(confp, dirp, outp, tot)
654}
655
656// ONE BEAT, ONE PROCESS, IN ORDER -- the same argument the refusal beat already makes: the census
657// WRITES the worklist and the ratchet READS it, so two clock rows would make the ORDER a race and
658// the ratchet would judge whichever worklist happened to be on disk. Exit carries the RATCHET's
659// verdict (0 ALLOW / 1 REFUSE / 3 UNMEASURED) because that is the part a watcher acts on.
660func fc_alert_beat(confp: *u8, dirp: *u8, outp: *u8, basep: *u8) -> i64 {
661 let tot: *i64 = sys_mmap(VL_T_LEN * 8) as *i64
662 let rc: i64 = vl_alert_scan(confp, dirp, outp, tot)
663 if rc != 0 { return rc }
664 return vl_alert_ratchet(outp, basep)
665}
666
667// FM4 -- GRAY FAILURE, TWO VANTAGES COMPARED, NEVER ONE. Exit 0 when every declared subject was
668// adjudicated, 3 when the ruler could not be loaded or no subject is declared -- an organ that
669// examined nothing must not return the same code as one that examined everything and found no
670// disagreement. A DISAGREE is a finding to publish, not an exit code to trip a beat on, so it does
671// NOT change the exit: this verb reports, it does not adjudicate.
672func fc_two_vantage(confp: *u8, outp: *u8) -> i64 {
673 let res: *i64 = sys_mmap(VL_NVOUT * 8) as *i64
674 return vl_vantage_scan(confp, outp, res)
675}
676
677func main(argc: i64, argv: *i64) -> i64 {
678 if argc < 2 {
679 fw("usage: nx_failclass scan <signatures.conf> <corpus.jsonl> <worklist.out> | selftest | refusal <refusal_shape.conf> <worklist.out> | refusal_ratchet <worklist> <baseline> | refusal_beat <refusal_shape.conf> <worklist.out> <baseline> | alert_score <verdictlog.conf> <status-dir> <worklist.out> | alert_ratchet <worklist> <baseline> | alert_beat <verdictlog.conf> <status-dir> <worklist.out> <baseline> | two_vantage <verdictlog.conf> <worklist.out>\n" as *u8)
680 return 2
681 }
682 let cmd: *u8 = argv[1] as *u8
683 if fc_streq(cmd, "selftest" as *u8) == 1 { return fc_selftest() }
684 if fc_streq(cmd, "scan" as *u8) == 1 {
685 if argc < 5 { fe("nx_failclass scan: missing arguments. Pass the three it needs, in order: nx_failclass scan knowledge/failclass.conf <corpus.jsonl> /tmp/failclass_worklist.tsv\n" as *u8); return 2 }
686 return fc_scan(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, 0 as *i64)
687 }
688 if fc_streq(cmd, "refusal" as *u8) == 1 {
689 if argc < 4 { fe("nx_failclass refusal: missing arguments. Pass the vocabulary and the worklist: nx_failclass refusal buildroot/knowledge/refusal_shape.conf /tmp/refusal_worklist.tsv\n" as *u8); return 2 }
690 return fc_refusal_shape(argv[2] as *u8, argv[3] as *u8)
691 }
692 if fc_streq(cmd, "refusal_beat" as *u8) == 1 {
693 if argc < 5 { fe("nx_failclass refusal_beat: missing arguments. Pass the vocabulary, the worklist and the baseline: nx_failclass refusal_beat buildroot/knowledge/refusal_shape.conf knowledge/status/refusal_shape_worklist.tsv knowledge/status/refusal_shape.baseline\n" as *u8); return 2 }
694 return fc_refusal_beat(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8)
695 }
696 if fc_streq(cmd, "refusal_ratchet" as *u8) == 1 {
697 if argc < 4 { fe("nx_failclass refusal_ratchet: missing arguments. Pass the census worklist and the baseline: nx_failclass refusal_ratchet /tmp/refusal_worklist.tsv knowledge/status/refusal_shape.baseline\n" as *u8); return 2 }
698 return rs_ratchet(argv[2] as *u8, argv[3] as *u8)
699 }
700 if fc_streq(cmd, "alert_score" as *u8) == 1 {
701 if argc < 5 { fe("nx_failclass alert_score: missing arguments. Pass the ruler, the directory of verdict streams and the worklist, in order: nx_failclass alert_score buildroot/knowledge/verdictlog.conf knowledge/status/ /tmp/alert_score_worklist.tsv\n" as *u8); return 2 }
702 return fc_alert_score(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8)
703 }
704 if fc_streq(cmd, "alert_beat" as *u8) == 1 {
705 if argc < 6 { fe("nx_failclass alert_beat: missing arguments. Pass the ruler, the directory, the worklist and the baseline: nx_failclass alert_beat buildroot/knowledge/verdictlog.conf knowledge/status/ knowledge/status/alert_score_worklist.tsv knowledge/status/alert_score.baseline\n" as *u8); return 2 }
706 return fc_alert_beat(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8)
707 }
708 if fc_streq(cmd, "alert_ratchet" as *u8) == 1 {
709 if argc < 4 { fe("nx_failclass alert_ratchet: missing arguments. Pass the census worklist and the baseline: nx_failclass alert_ratchet /tmp/alert_score_worklist.tsv knowledge/status/alert_score.baseline\n" as *u8); return 2 }
710 return vl_alert_ratchet(argv[2] as *u8, argv[3] as *u8)
711 }
712 if fc_streq(cmd, "two_vantage" as *u8) == 1 {
713 if argc < 4 { fe("nx_failclass two_vantage: missing arguments. Pass the ruler and the worklist: nx_failclass two_vantage buildroot/knowledge/verdictlog.conf /tmp/two_vantage.tsv\n" as *u8); return 2 }
714 return fc_two_vantage(argv[2] as *u8, argv[3] as *u8)
715 }
716 fe("nx_failclass: unknown mode. Use one of scan, selftest, refusal, refusal_ratchet, refusal_beat, alert_score, alert_ratchet, alert_beat, two_vantage -- run nx_failclass with no arguments to see the full call grammar.\n" as *u8)
717 return 2
718}