nx_refusal_shape_lib.nx source
↩ module page · 1087 lines · 49186 B
1// nx_refusal_shape_lib.nx -- DOES THIS REFUSAL NAME ITS SUBJECT AND ITS REMEDY?
2//
3// FM11 of /compare/failmodes: the MECHANIZABLE half of the tenth cell, LOUD-AND-CORRECT. The
4// taxonomy lane established that EMITTER QUALITY and CALLER RESPONSE are two INDEPENDENT AXES and
5// proved it with one instance that scores on both at once: nx_fs read declares its truncation cap
6// AND its remedy in its last line (emitter CORRECT) while the reader filtered that line out and
7// filed it as a critical defect (caller IGNORED). A single axis keyed on "did the caller obey"
8// would file that instance under LOUD-AND-IGNORED and exclude the very case that motivated the
9// cell.
10//
11// THIS LIBRARY OWNS THE EMITTER HALF ONLY, AND THAT SCOPE IS DELIBERATE.
12// The caller half -- did anyone act on the refusal -- is CENSUS-ONLY over the call record and is
13// explicitly OUT OF SCOPE here, because a refusal that callers routinely retry through is
14// LOUD-AND-IGNORED however well written it is. No function in this file may be read as evidence
15// about a caller.
16//
17// WHY A LIBRARY AND NOT A SECOND ORGAN. The census verb lives in nx_failclass (fc_refusal_shape)
18// and the gate lives in nx_refusal_shape_gate, and the two MUST agree about what "shaped" means.
19// They agree BY CONSTRUCTION rather than by discipline: there is exactly ONE classifier
20// (rs_bucket), ONE block builder and ONE vocabulary loader, and both consumers import them.
21//
22// THE UNIT IS THE COMPOSED EMISSION, NOT THE STRING LITERAL, AND THAT IS THE WHOLE DESIGN.
23// Measured on the canonical guard before a line was written: nx_tools_api.nx builds the truncation
24// envelope from THREE pieces --
25// b = ta_cat(jb, b, " [NX-TRUNCATED OUTPUT-IS-PARTIAL capture_cap=" as *u8)
26// b = ta_catn(jb, b, xcap)
27// b = ta_cat(jb, b, " -- narrow the query or re-run with _async=1]" as *u8)
28// -- so the refusal marker and its remedy live in DIFFERENT LITERALS with a non-literal line
29// between them. A per-literal checker would flag a KNOWN-GOOD GUARD, and
30// * A SHAPE CHECKER THAT FLAGS A KNOWN-GOOD GUARD IS WORSE THAN NONE, BECAUSE SOMEONE WILL ACT ON
31// IT. *
32// So a MESSAGE here is a BLOCK: a run of consecutive source lines carrying string literals,
33// tolerating up to max_gap_lines literal-free lines, broken HARD by a blank line, a comment line,
34// or a line whose first non-space character closes a scope. The brace break is what stops a block
35// borrowing an unrelated neighbour's remedy, and it is bite-proven in the gate.
36//
37// COMMENTS ARE NOT SCANNED. A source-scanning detector finds the PROSE DESCRIBING the defect and
38// accuses the author who documented it -- prose is source bytes too. Comment lines are hard breaks
39// and no literal is ever harvested from one.
40//
41// IT EMITS NUMBERS AND NO VERDICT. The distribution is published BEFORE any bar is chosen, because
42// a detector that fires on everything discriminates nothing and an uncalibrated classifier that
43// votes is a false-alarm generator with an authoritative name. The bar lives in the RATCHET
44// (rs_ratchet), which is a set of NAMES, self-baselining, and prints its bar in every verdict.
45//
46// UNKNOWN IS ITS OWN BUCKET. A block too short to carry either signal has not been judged, it has
47// been skipped: an axis that cannot see must abstain, not acquit. UNKNOWN is never an offender and
48// never folded into a known bucket.
49//
50// DECLARED IMPRECISION, so the next reader does not trust this as exact:
51// 1. A literal that spans a source line (a bare newline inside a string) is read as two.
52// 2. A line that both closes a scope and emits (`} else { fw("...") }`) breaks before its own
53// literal, so that literal starts the next block.
54// 3. SUBJECT is witnessed structurally (a key= pair, a path segment, a <placeholder>), never
55// semantically. It answers "is this bound to a particular", not "is it the RIGHT particular".
56// 4. The population is source literals, so a refusal assembled entirely from runtime values
57// carries no marker and is invisible here. Every per-bucket count is a FLOOR.
58//
59// license_tier: ORIGINAL
60// module: nishi-core.quality.refusal_shape
61// capability: REFUSAL_EMITTER_SHAPE
62import "nx_syscalls.nx"
63import "nx_gatekit_lib.nx"
64
65// ---- byte names: a raw code inside a comparison is a literal no reader recognises and no grep
66// ---- finds, which is the same defect as spelling a string as character codes.
67const RS_TAB: i64 = 9
68const RS_NL: i64 = 10
69const RS_SP: i64 = 32
70const RS_QUOTE: i64 = 34
71const RS_HASH: i64 = 35
72const RS_MINUS: i64 = 45
73const RS_SLASH: i64 = 47
74const RS_COLON: i64 = 58
75const RS_SEMI: i64 = 59
76const RS_LT: i64 = 60
77const RS_EQ: i64 = 61
78const RS_GT: i64 = 62
79const RS_USCORE: i64 = 95
80const RS_BSLASH: i64 = 92
81const RS_BAR: i64 = 124
82const RS_RBRACE: i64 = 125
83const RS_DOT: i64 = 46
84const RS_MODE_0644: i64 = 420
85
86// ---- buckets. The partition is over the POPULATION (blocks carrying a refusal marker) and its
87// ---- members are printed with their sum, because a partition you cannot reconcile is a leak.
88const RS_B_SHAPED: i64 = 0
89const RS_B_NOREMEDY: i64 = 1
90const RS_B_NOSUBJECT: i64 = 2
91const RS_B_BARE: i64 = 3
92const RS_B_UNKNOWN: i64 = 4
93// TWO BUCKETS THAT EXIST BECAUSE THE FIRST RUN'S HITS WERE READ INSTEAD OF SUPPRESSED.
94// v1 flagged 920 permil of the population as offenders, and reading the flagged rows -- not
95// assuming them -- found two whole kinds of thing in there that are not refusals a caller reads:
96// TEST-ASSERTION: a gate tooth or selftest line. Signature: it states BOTH outcomes at once
97// ("verdict=GREEN ... verdict=RED", "PASS ... FAIL"). A refusal never announces its own success
98// alternative in the same breath, so this discriminates without naming any file or organ.
99// USAGE-BANNER: a call-grammar banner. It IS its own remedy -- it states the correct form -- so
100// scoring it NO-REMEDY indicts the most helpful message an organ emits.
101// Both are PARTITION MEMBERS (they sum) and neither is an OFFENDER. Naming them is the point: a
102// detector that suppresses its false positives instead of classifying them has thrown away its own
103// taxonomy, and a headline computed over the un-split population is a number nobody should plan
104// against.
105const RS_B_TEST: i64 = 5
106const RS_B_USAGE: i64 = 6
107const RS_NBUCKET: i64 = 7
108
109// ---- results vector. A caller ASSERTS the scan rather than parsing its printout: a gate that
110// ---- reads its subject's stdout is testing the formatter as much as the ruler.
111const RS_R_FILES: i64 = 0
112const RS_R_BYTES: i64 = 1
113const RS_R_BLOCKS: i64 = 2
114const RS_R_POP: i64 = 3
115const RS_R_BUCKET0: i64 = 4 // .. RS_R_BUCKET0 + RS_NBUCKET - 1
116const RS_R_TRUNC: i64 = 11
117const RS_R_UNREADABLE: i64 = 12
118const RS_R_WANTBUCKET: i64 = 13
119const RS_R_WANTLINE: i64 = 14
120const RS_R_WANTHITS: i64 = 15
121const RS_R_OFFENDERS: i64 = 16
122const RS_RLEN: i64 = 20
123
124// ---- vocabulary block. ONE i64 array so every consumer passes ONE pointer: a twelve-parameter
125// ---- signature is where a caller starts passing the wrong array.
126const RS_MAXVOC: i64 = 512
127const RS_V_NMARK: i64 = 0
128const RS_V_NREM: i64 = 1
129const RS_V_MAXGAP: i64 = 2
130const RS_V_MINMSG: i64 = 3
131const RS_V_REJECT: i64 = 4
132const RS_V_OVER: i64 = 5 // vocabulary cap reached -> the vocabulary is a FLOOR
133const RS_V_NEXCL: i64 = 6 // declared self-fixture exclusions
134const RS_V_EXCOUNT: i64 = 7 // files actually skipped by those exclusions
135const RS_V_BASE: i64 = 8 // markers: (off,len) pairs from here
136const RS_V_REMBASE: i64 = 1032 // RS_V_BASE + 2 * RS_MAXVOC
137const RS_V_MKHIT: i64 = 2056 // RS_V_REMBASE + 2 * RS_MAXVOC: per-marker first-fired tally
138const RS_V_RMHIT: i64 = 2568 // RS_V_MKHIT + RS_MAXVOC: per-remedy first-fired tally
139const RS_V_EXBASE: i64 = 3080 // RS_V_RMHIT + RS_MAXVOC: exclusion (off,len) pairs
140const RS_V_PASSBASE: i64 = 4104 // RS_V_EXBASE + 2 * RS_MAXVOC: positive-outcome tokens
141const RS_V_FAILBASE: i64 = 5128 // RS_V_PASSBASE + 2 * RS_MAXVOC: negative-outcome tokens
142const RS_V_SRBASE: i64 = 6152 // RS_V_FAILBASE + 2 * RS_MAXVOC: self-remedy (usage) phrases
143const RS_V_NPASS: i64 = 7176 // scalars appended after the tables, never wedged before them
144const RS_V_NFAIL: i64 = 7177
145const RS_V_NSR: i64 = 7178
146const RS_V_LEN: i64 = 7184
147
148// Corpus bounds. Each ANNOUNCES when it binds; gk_corpus_scan REFUSES rather than under-reporting,
149// which is why these are generous rather than tuned. Measured 2026-08-20: the compile corpus is
150// 22,923 files, so RS_MAXF has 1.4x headroom and a breach is a refusal, never a silent prefix.
151const RS_MAXF: i64 = 32768
152const RS_STRIDE: i64 = 256
153const RS_SLACK: i64 = 65536
154const RS_PREVIEW: i64 = 160
155const RS_ROW_MAX: i64 = 1024
156const RS_OUT_PER_FILE: i64 = 8192 // worklist headroom per source file; a bind ANNOUNCES
157const RS_NUMBUF: i64 = 64
158const RS_PATHBUF: i64 = 4096
159const RS_SMALL: i64 = 16
160
161func rs_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
162func rs_err(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 }
163
164func rs_putn(v: i64) -> i64 {
165 let b: *u8 = sys_mmap(RS_NUMBUF)
166 let o: i64 = gk_catn(b, 0, v)
167 sys_write(1, b, o)
168 sys_munmap(b, RS_NUMBUF)
169 return 0
170}
171
172func rs_lower(c: i64) -> i64 {
173 if c >= 65 { if c <= 90 { return c + 32 } }
174 return c
175}
176
177// A word character AFTER case folding: a-z 0-9 underscore. Everything else is a boundary.
178func rs_wordch(c: i64) -> i64 {
179 if c >= 97 { if c <= 122 { return 1 } }
180 if c >= 48 { if c <= 57 { return 1 } }
181 if c == RS_USCORE { return 1 }
182 return 0
183}
184
185// Case-folded, WORD-BOUNDED search of nee[0,nlen) inside hay[hs,he). Returns the offset or -1.
186// The needle must already be lower case -- only the haystack is folded.
187// The inner compare uses a SEPARATE `scanning` flag rather than clobbering `j`: a loop that breaks
188// by writing its own cursor destroys the position it would have to report.
189func rs_find_word(hay: *u8, hs: i64, he: i64, nee: *u8, nlen: i64) -> i64 {
190 if nlen <= 0 { return 0 - 1 }
191 var i: i64 = hs
192 while i + nlen <= he {
193 var j: i64 = 0
194 var ok: i64 = 1
195 var scanning: i64 = 1
196 while scanning == 1 {
197 if j >= nlen { scanning = 0 } else {
198 if rs_lower(hay[i + j] as i64) != (nee[j] as i64) { ok = 0; scanning = 0 } else { j = j + 1 }
199 }
200 }
201 if ok == 1 {
202 var lb: i64 = 1
203 if i > hs { if rs_wordch(rs_lower(hay[i - 1] as i64)) == 1 { lb = 0 } }
204 var rb: i64 = 1
205 if i + nlen < he { if rs_wordch(rs_lower(hay[i + nlen] as i64)) == 1 { rb = 0 } }
206 if lb == 1 { if rb == 1 { return i } }
207 }
208 i = i + 1
209 }
210 return 0 - 1
211}
212
213// ---------------------------------------------------------------------------------------------
214// SUBJECT: is the refusal bound to a concrete PARTICULAR the reader can act on?
215//
216// Three INDEPENDENT structural witnesses, any one of which suffices. They are structural on
217// purpose: "does this name the RIGHT subject" is semantic and belongs to the LOUD-BUT-WRONG-SUBJECT
218// class, which has no mechanical detector anywhere and does not acquire one by being asserted here.
219// 1. a key=value pair -- an identifier character immediately before an `=` that is not `==`.
220// Comparison operators cannot match: `>=` `<=` `!=` all carry a non-word character before the
221// `=`, and `==` carries an `=`.
222// 2. a path segment -- a `/` with word characters on both sides.
223// 3. a placeholder -- `<name>` naming the particular the caller must supply.
224// ---------------------------------------------------------------------------------------------
225func rs_has_particular(buf: *u8, s: i64, e: i64) -> i64 {
226 var i: i64 = s
227 while i < e {
228 let c: i64 = buf[i] as i64
229 if c == RS_EQ {
230 if i > s {
231 if rs_wordch(rs_lower(buf[i - 1] as i64)) == 1 {
232 var nxt: i64 = 0
233 if i + 1 < e { nxt = buf[i + 1] as i64 }
234 if nxt != RS_EQ { return 1 }
235 }
236 }
237 }
238 if c == RS_SLASH {
239 if i > s {
240 if i + 1 < e {
241 if rs_wordch(rs_lower(buf[i - 1] as i64)) == 1 {
242 if rs_wordch(rs_lower(buf[i + 1] as i64)) == 1 { return 1 }
243 }
244 }
245 }
246 }
247 if c == RS_LT {
248 if i + 1 < e {
249 if rs_wordch(rs_lower(buf[i + 1] as i64)) == 1 {
250 var j: i64 = i + 1
251 var scanning: i64 = 1
252 while scanning == 1 {
253 if j >= e { scanning = 0 } else {
254 let d: i64 = buf[j] as i64
255 if d == RS_GT { return 1 }
256 var cont: i64 = 0
257 if rs_wordch(rs_lower(d)) == 1 { cont = 1 }
258 if d == RS_MINUS { cont = 1 }
259 if cont == 1 { j = j + 1 } else { scanning = 0 }
260 }
261 }
262 }
263 }
264 }
265 i = i + 1
266 }
267 return 0
268}
269
270// ---------------------------------------------------------------------------------------------
271// THE RULER. Pure: same inputs, same bucket, no I/O, no globals. The gate tests THIS directly, so
272// a change to the decision cannot hide behind a formatter.
273// UNKNOWN is decided FIRST and is its own bucket: a block too short to carry either signal has not
274// been judged.
275// ---------------------------------------------------------------------------------------------
276// ORDER IS PART OF THE CONTRACT AND IS STATED HERE RATHER THAN LEFT TO THE READER:
277// 1. TEST-ASSERTION -- a tooth that also carries a usage word is still a tooth.
278// 2. USAGE-BANNER -- the call grammar IS the remedy.
279// 3. UNKNOWN -- too short to carry either signal: abstain, do not acquit.
280// 4. the subject / remedy ruler.
281func rs_bucket(blen: i64, minmsg: i64, subj: i64, rem: i64, testassert: i64, usage: i64) -> i64 {
282 if testassert == 1 { return RS_B_TEST }
283 if usage == 1 { return RS_B_USAGE }
284 if blen < minmsg { return RS_B_UNKNOWN }
285 if subj == 1 {
286 if rem == 1 { return RS_B_SHAPED }
287 return RS_B_NOREMEDY
288 }
289 if rem == 1 { return RS_B_NOSUBJECT }
290 return RS_B_BARE
291}
292
293func rs_bucket_name(b: i64) -> *u8 {
294 if b == RS_B_SHAPED { return "SHAPED" as *u8 }
295 if b == RS_B_NOREMEDY { return "NO-REMEDY" as *u8 }
296 if b == RS_B_NOSUBJECT { return "NO-SUBJECT" as *u8 }
297 if b == RS_B_BARE { return "BARE" as *u8 }
298 if b == RS_B_UNKNOWN { return "UNKNOWN" as *u8 }
299 if b == RS_B_TEST { return "TEST-ASSERTION" as *u8 }
300 if b == RS_B_USAGE { return "USAGE-BANNER" as *u8 }
301 return "UNRECOGNISED" as *u8
302}
303
304// An OFFENDER is a judged refusal that is not SHAPED. UNKNOWN can never be one: folding an
305// unobservable into a known bucket is how a census invents findings. TEST-ASSERTION and
306// USAGE-BANNER can never be one either: neither is a refusal a caller reads, and flagging them
307// would send a campaign at the wrong subject with the numbers apparently behind it.
308func rs_is_offender(b: i64) -> i64 {
309 if b == RS_B_SHAPED { return 0 }
310 if b == RS_B_UNKNOWN { return 0 }
311 if b == RS_B_TEST { return 0 }
312 if b == RS_B_USAGE { return 0 }
313 return 1
314}
315
316// ---------------------------------------------------------------------------------------------
317// VOCABULARY, loaded from data. Adding a marker or a remedy phrase is a conf edit, never a rebuild.
318// Grammar: marker|<lower-case word or phrase>
319// remedy|<lower-case word or phrase>
320// param|<name>|<integer>
321// Comment rows start with `;` or the hash byte. A row it cannot parse is COUNTED, never ignored.
322// ---------------------------------------------------------------------------------------------
323func rs_range_eq(buf: *u8, s: i64, e: i64, lit: *u8) -> i64 {
324 let n: i64 = gk_len(lit)
325 if e - s != n { return 0 }
326 var i: i64 = 0
327 while i < n {
328 if buf[s + i] != lit[i] { return 0 }
329 i = i + 1
330 }
331 return 1
332}
333
334func rs_find_byte(buf: *u8, s: i64, e: i64, b: i64) -> i64 {
335 var i: i64 = s
336 while i < e {
337 if (buf[i] as i64) == b { return i }
338 i = i + 1
339 }
340 return 0 - 1
341}
342
343func rs_atoi(buf: *u8, s: i64, e: i64) -> i64 {
344 var v: i64 = 0
345 var any: i64 = 0
346 var i: i64 = s
347 while i < e {
348 let c: i64 = buf[i] as i64
349 if c >= 48 {
350 if c <= 57 { v = v * 10 + (c - 48); any = 1 }
351 }
352 i = i + 1
353 }
354 if any == 0 { return 0 - 1 }
355 return v
356}
357
358// Returns the conf buffer (its bytes back the vocabulary offsets), or 0 when the conf cannot be
359// used. It REFUSES on a conf that yields no markers, no remedies, or an unset parameter rather
360// than publishing a distribution of noise.
361func rs_conf_load(confp: *u8, clen: *i64, voc: *i64) -> *u8 {
362 var z: i64 = 0
363 while z < RS_V_LEN { voc[z] = 0; z = z + 1 }
364 voc[RS_V_MAXGAP] = 0 - 1
365 voc[RS_V_MINMSG] = 0 - 1
366 let conf: *u8 = sys_read_file(confp, clen)
367 if clen[0] <= 0 {
368 rs_err("nx_refusal_shape: cannot read the vocabulary conf at " as *u8)
369 rs_err(confp)
370 rs_err(" -- pass the path to knowledge/refusal_shape.conf\n" as *u8)
371 return 0 as *u8
372 }
373 var p: i64 = 0
374 while p < clen[0] {
375 let eol: i64 = gk_eol(conf, p, clen[0])
376 if eol > p {
377 let f: i64 = conf[p] as i64
378 var skip: i64 = 0
379 if f == RS_SEMI { skip = 1 }
380 if f == RS_HASH { skip = 1 }
381 if skip == 0 {
382 let b1: i64 = rs_find_byte(conf, p, eol, RS_BAR)
383 if b1 < 0 { voc[RS_V_REJECT] = voc[RS_V_REJECT] + 1 } else {
384 var handled: i64 = 0
385 if rs_range_eq(conf, p, b1, "marker" as *u8) == 1 {
386 handled = 1
387 if voc[RS_V_NMARK] >= RS_MAXVOC { voc[RS_V_OVER] = voc[RS_V_OVER] + 1 } else {
388 let k: i64 = voc[RS_V_NMARK]
389 voc[RS_V_BASE + k * 2] = b1 + 1
390 voc[RS_V_BASE + k * 2 + 1] = eol - b1 - 1
391 voc[RS_V_NMARK] = k + 1
392 }
393 }
394 if rs_range_eq(conf, p, b1, "remedy" as *u8) == 1 {
395 handled = 1
396 if voc[RS_V_NREM] >= RS_MAXVOC { voc[RS_V_OVER] = voc[RS_V_OVER] + 1 } else {
397 let k2: i64 = voc[RS_V_NREM]
398 voc[RS_V_REMBASE + k2 * 2] = b1 + 1
399 voc[RS_V_REMBASE + k2 * 2 + 1] = eol - b1 - 1
400 voc[RS_V_NREM] = k2 + 1
401 }
402 }
403 if rs_range_eq(conf, p, b1, "passtoken" as *u8) == 1 {
404 handled = 1
405 if voc[RS_V_NPASS] >= RS_MAXVOC { voc[RS_V_OVER] = voc[RS_V_OVER] + 1 } else {
406 let kp: i64 = voc[RS_V_NPASS]
407 voc[RS_V_PASSBASE + kp * 2] = b1 + 1
408 voc[RS_V_PASSBASE + kp * 2 + 1] = eol - b1 - 1
409 voc[RS_V_NPASS] = kp + 1
410 }
411 }
412 if rs_range_eq(conf, p, b1, "failtoken" as *u8) == 1 {
413 handled = 1
414 if voc[RS_V_NFAIL] >= RS_MAXVOC { voc[RS_V_OVER] = voc[RS_V_OVER] + 1 } else {
415 let kf: i64 = voc[RS_V_NFAIL]
416 voc[RS_V_FAILBASE + kf * 2] = b1 + 1
417 voc[RS_V_FAILBASE + kf * 2 + 1] = eol - b1 - 1
418 voc[RS_V_NFAIL] = kf + 1
419 }
420 }
421 if rs_range_eq(conf, p, b1, "selfremedy" as *u8) == 1 {
422 handled = 1
423 if voc[RS_V_NSR] >= RS_MAXVOC { voc[RS_V_OVER] = voc[RS_V_OVER] + 1 } else {
424 let ks: i64 = voc[RS_V_NSR]
425 voc[RS_V_SRBASE + ks * 2] = b1 + 1
426 voc[RS_V_SRBASE + ks * 2 + 1] = eol - b1 - 1
427 voc[RS_V_NSR] = ks + 1
428 }
429 }
430 if rs_range_eq(conf, p, b1, "exclude_file" as *u8) == 1 {
431 handled = 1
432 if voc[RS_V_NEXCL] >= RS_MAXVOC { voc[RS_V_OVER] = voc[RS_V_OVER] + 1 } else {
433 let k3: i64 = voc[RS_V_NEXCL]
434 voc[RS_V_EXBASE + k3 * 2] = b1 + 1
435 voc[RS_V_EXBASE + k3 * 2 + 1] = eol - b1 - 1
436 voc[RS_V_NEXCL] = k3 + 1
437 }
438 }
439 if rs_range_eq(conf, p, b1, "param" as *u8) == 1 {
440 handled = 1
441 let b2: i64 = rs_find_byte(conf, b1 + 1, eol, RS_BAR)
442 if b2 < 0 { voc[RS_V_REJECT] = voc[RS_V_REJECT] + 1 } else {
443 let v: i64 = rs_atoi(conf, b2 + 1, eol)
444 var known: i64 = 0
445 if rs_range_eq(conf, b1 + 1, b2, "max_gap_lines" as *u8) == 1 { voc[RS_V_MAXGAP] = v; known = 1 }
446 if rs_range_eq(conf, b1 + 1, b2, "min_msg_bytes" as *u8) == 1 { voc[RS_V_MINMSG] = v; known = 1 }
447 if known == 0 { voc[RS_V_REJECT] = voc[RS_V_REJECT] + 1 }
448 }
449 }
450 if handled == 0 { voc[RS_V_REJECT] = voc[RS_V_REJECT] + 1 }
451 }
452 }
453 }
454 p = eol + 1
455 }
456 if voc[RS_V_NMARK] <= 0 {
457 rs_err("nx_refusal_shape: the conf yielded ZERO refusal markers -- refusing to publish a distribution of noise. Add marker| rows to the conf.\n" as *u8)
458 return 0 as *u8
459 }
460 if voc[RS_V_NREM] <= 0 {
461 rs_err("nx_refusal_shape: the conf yielded ZERO remedy phrases -- every refusal would score NO-REMEDY. Add remedy| rows to the conf.\n" as *u8)
462 return 0 as *u8
463 }
464 if voc[RS_V_NPASS] <= 0 {
465 rs_err("nx_refusal_shape: the conf yielded ZERO positive-outcome tokens -- the TEST-ASSERTION bucket could never fire and every gate tooth would be counted as a refusal. Add passtoken| rows to the conf.\n" as *u8)
466 return 0 as *u8
467 }
468 if voc[RS_V_NFAIL] <= 0 {
469 rs_err("nx_refusal_shape: the conf yielded ZERO negative-outcome tokens -- the TEST-ASSERTION bucket could never fire. Add failtoken| rows to the conf.\n" as *u8)
470 return 0 as *u8
471 }
472 if voc[RS_V_NSR] <= 0 {
473 rs_err("nx_refusal_shape: the conf yielded ZERO self-remedy phrases -- every usage banner would be scored NO-REMEDY. Add selfremedy| rows to the conf.\n" as *u8)
474 return 0 as *u8
475 }
476 if voc[RS_V_MAXGAP] < 0 {
477 rs_err("nx_refusal_shape: the conf sets no max_gap_lines -- add param|max_gap_lines|<n> to the conf.\n" as *u8)
478 return 0 as *u8
479 }
480 if voc[RS_V_MINMSG] < 0 {
481 rs_err("nx_refusal_shape: the conf sets no min_msg_bytes -- add param|min_msg_bytes|<n> to the conf.\n" as *u8)
482 return 0 as *u8
483 }
484 return conf
485}
486
487// Index of the first vocabulary entry that matches, or -1. The INDEX is returned rather than a
488// boolean because the reason must travel with the count: the worklist prints which marker fired.
489func rs_vocab_hit(conf: *u8, voc: *i64, base: i64, n: i64, hay: *u8, hs: i64, he: i64) -> i64 {
490 var i: i64 = 0
491 while i < n {
492 let w: i64 = rs_find_word(hay, hs, he, ((conf as i64) + voc[base + i * 2]) as *u8, voc[base + i * 2 + 1])
493 if w >= 0 { return i }
494 i = i + 1
495 }
496 return 0 - 1
497}
498
499// Harvest the string literals on ONE source line into the block buffer. Tracks in-string state, so
500// a `//` INSIDE a literal (every url in the tree) does not end the line and a `"` inside a comment
501// does not open one. Every escape becomes a SPACE: an escaped newline must not glue the last word
502// of one sentence to the first word of the next, or a word-bounded matcher reads a word nobody
503// wrote. Returns the new block offset.
504func rs_line_lits(buf: *u8, s: i64, e: i64, blk: *u8, bo0: i64, blkcap: i64) -> i64 {
505 var bo: i64 = bo0
506 var i: i64 = s
507 var ins: i64 = 0
508 var scanning: i64 = 1
509 while scanning == 1 {
510 if i >= e { scanning = 0 } else {
511 let c: i64 = buf[i] as i64
512 if ins == 0 {
513 if c == RS_QUOTE {
514 ins = 1
515 if bo + 1 < blkcap { blk[bo] = RS_SP as u8; bo = bo + 1 }
516 } else {
517 if c == RS_SLASH {
518 if i + 1 < e {
519 if (buf[i + 1] as i64) == RS_SLASH { scanning = 0 }
520 }
521 }
522 }
523 } else {
524 if c == RS_BSLASH {
525 i = i + 1
526 if bo + 1 < blkcap { blk[bo] = RS_SP as u8; bo = bo + 1 }
527 } else {
528 if c == RS_QUOTE { ins = 0 } else {
529 if bo + 1 < blkcap { blk[bo] = buf[i]; bo = bo + 1 }
530 }
531 }
532 }
533 i = i + 1
534 }
535 }
536 return bo
537}
538
539// Classify ONE assembled block and, when it is in the population, write its worklist row.
540// `want` is an OPTIONAL lower-case witness string: when the block carries it, its bucket and line
541// are reported in the results vector so a caller can ask about ONE known guard instead of parsing
542// a whole distribution. Pass 0 to skip.
543func rs_block_emit(blk: *u8, bo: i64, bline: i64, path: *u8, conf: *u8, voc: *i64, res: *i64, out: *u8, outcap: i64, oo: *i64, want: *u8) -> i64 {
544 res[RS_R_BLOCKS] = res[RS_R_BLOCKS] + 1
545 let mi: i64 = rs_vocab_hit(conf, voc, RS_V_BASE, voc[RS_V_NMARK], blk, 0, bo)
546 if mi < 0 { return 0 }
547 res[RS_R_POP] = res[RS_R_POP] + 1
548 voc[RS_V_MKHIT + mi] = voc[RS_V_MKHIT + mi] + 1
549
550 var rem: i64 = 0
551 let ri: i64 = rs_vocab_hit(conf, voc, RS_V_REMBASE, voc[RS_V_NREM], blk, 0, bo)
552 if ri >= 0 { rem = 1; voc[RS_V_RMHIT + ri] = voc[RS_V_RMHIT + ri] + 1 }
553 let subj: i64 = rs_has_particular(blk, 0, bo)
554 // A message that states BOTH outcomes at once is a test assertion, not a refusal.
555 var testassert: i64 = 0
556 if rs_vocab_hit(conf, voc, RS_V_PASSBASE, voc[RS_V_NPASS], blk, 0, bo) >= 0 {
557 if rs_vocab_hit(conf, voc, RS_V_FAILBASE, voc[RS_V_NFAIL], blk, 0, bo) >= 0 { testassert = 1 }
558 }
559 var usage: i64 = 0
560 if rs_vocab_hit(conf, voc, RS_V_SRBASE, voc[RS_V_NSR], blk, 0, bo) >= 0 { usage = 1 }
561 let b: i64 = rs_bucket(bo, voc[RS_V_MINMSG], subj, rem, testassert, usage)
562 res[RS_R_BUCKET0 + b] = res[RS_R_BUCKET0 + b] + 1
563 if rs_is_offender(b) == 1 { res[RS_R_OFFENDERS] = res[RS_R_OFFENDERS] + 1 }
564
565 if (want as i64) != 0 {
566 if rs_find_word(blk, 0, bo, want, gk_len(want)) >= 0 {
567 res[RS_R_WANTBUCKET] = b
568 res[RS_R_WANTLINE] = bline
569 res[RS_R_WANTHITS] = res[RS_R_WANTHITS] + 1
570 }
571 }
572
573 if (out as i64) == 0 { return 0 }
574 if oo[0] + RS_ROW_MAX >= outcap { res[RS_R_TRUNC] = 1; return 0 }
575 var o: i64 = oo[0]
576 o = gk_cat(out, o, rs_bucket_name(b))
577 out[o] = RS_TAB as u8; o = o + 1
578 o = gk_cat(out, o, path)
579 out[o] = RS_COLON as u8; o = o + 1
580 o = gk_catn(out, o, bline)
581 out[o] = RS_TAB as u8; o = o + 1
582 var k: i64 = 0
583 while k < voc[RS_V_BASE + mi * 2 + 1] {
584 out[o] = conf[voc[RS_V_BASE + mi * 2] + k]
585 o = o + 1
586 k = k + 1
587 }
588 out[o] = RS_TAB as u8; o = o + 1
589 var pe: i64 = RS_PREVIEW
590 if pe > bo { pe = bo }
591 var q: i64 = 0
592 while q < pe {
593 // Never let a raw control byte into the worklist: a forged newline turns one row into two.
594 let cb: i64 = blk[q] as i64
595 if cb < RS_SP { out[o] = RS_DOT as u8 } else { out[o] = blk[q] }
596 o = o + 1
597 q = q + 1
598 }
599 out[o] = RS_NL as u8; o = o + 1
600 oo[0] = o
601 return 0
602}
603
604// Scan ONE source file: build blocks, classify each, tally into `res`, append worklist rows.
605// The read mapping is RELEASED per file. A census that walks 20,000 sources and leaks each
606// mapping is a resource bug even when its numbers are right.
607func rs_file_scan(path: *u8, conf: *u8, voc: *i64, res: *i64, out: *u8, outcap: i64, oo: *i64, want: *u8) -> i64 {
608 let flen: *i64 = sys_mmap(RS_SMALL) as *i64
609 let buf: *u8 = sys_read_file(path, flen)
610 if flen[0] <= 0 {
611 res[RS_R_UNREADABLE] = res[RS_R_UNREADABLE] + 1
612 sys_munmap(flen as *u8, RS_SMALL)
613 return 0
614 }
615 res[RS_R_FILES] = res[RS_R_FILES] + 1
616 res[RS_R_BYTES] = res[RS_R_BYTES] + flen[0]
617
618 // The block buffer is sized FROM THE FILE: block text is a subset of the file's own bytes, so
619 // this is a bound, not a guess.
620 let blkcap: i64 = flen[0] + RS_SLACK
621 let blk: *u8 = sys_mmap(blkcap)
622 var bo: i64 = 0
623 var bline: i64 = 0
624 var gap: i64 = 0
625 var line: i64 = 1
626 var p: i64 = 0
627 var running: i64 = 1
628 while running == 1 {
629 var eol: i64 = flen[0]
630 var hard: i64 = 0
631 if p >= flen[0] { hard = 1; running = 0 } else {
632 eol = gk_eol(buf, p, flen[0])
633 var f: i64 = p
634 var sc: i64 = 1
635 while sc == 1 {
636 if f >= eol { sc = 0 } else {
637 let wc: i64 = buf[f] as i64
638 if wc == RS_SP { f = f + 1 } else {
639 if wc == RS_TAB { f = f + 1 } else { sc = 0 }
640 }
641 }
642 }
643 if f >= eol { hard = 1 }
644 if hard == 0 {
645 if (buf[f] as i64) == RS_RBRACE { hard = 1 }
646 if (buf[f] as i64) == RS_SLASH {
647 if f + 1 < eol {
648 if (buf[f + 1] as i64) == RS_SLASH { hard = 1 }
649 }
650 }
651 }
652 if hard == 0 {
653 let before: i64 = bo
654 bo = rs_line_lits(buf, f, eol, blk, bo, blkcap)
655 if bo > before {
656 if bline == 0 { bline = line }
657 gap = 0
658 } else {
659 gap = gap + 1
660 if gap > voc[RS_V_MAXGAP] { hard = 1 }
661 }
662 }
663 }
664 if hard == 1 {
665 if bo > 0 { rs_block_emit(blk, bo, bline, path, conf, voc, res, out, outcap, oo, want) }
666 bo = 0
667 bline = 0
668 gap = 0
669 }
670 line = line + 1
671 p = eol + 1
672 }
673 sys_munmap(blk, blkcap)
674 sys_free_file(buf, flen[0])
675 sys_munmap(flen as *u8, RS_SMALL)
676 return 0
677}
678
679// Does `path` end with "/" followed by this declared basename?
680func rs_path_is(path: *u8, conf: *u8, off: i64, len: i64) -> i64 {
681 let pl: i64 = gk_len(path)
682 if pl < len + 1 { return 0 }
683 let s: i64 = pl - len
684 if (path[s - 1] as i64) != RS_SLASH { return 0 }
685 var i: i64 = 0
686 while i < len {
687 if path[s + i] != conf[off + i] { return 0 }
688 i = i + 1
689 }
690 return 1
691}
692
693// A DECLARED, COUNTED, NAMED exclusion -- never a silent suppression.
694// This detector scans source, so it finds ITS OWN test fixtures and accuses its author first. The
695// answer is a list of exactly the files that exist to TEST this detector, declared in the conf,
696// counted here and NAMED in the report. It is not a place to hide findings: excluding a real organ
697// would remove its refusals from the population and the report would say so out loud.
698func rs_excluded(path: *u8, conf: *u8, voc: *i64) -> i64 {
699 var i: i64 = 0
700 while i < voc[RS_V_NEXCL] {
701 if rs_path_is(path, conf, voc[RS_V_EXBASE + i * 2], voc[RS_V_EXBASE + i * 2 + 1]) == 1 { return 1 }
702 i = i + 1
703 }
704 return 0
705}
706
707// Scan an EXPLICIT list of source paths (stride-packed, absolute). Shared by the corpus census and
708// by the gate, so the gate exercises the same walker the census uses rather than a test-only twin.
709func rs_scan_list(names: *u8, n: i64, conf: *u8, voc: *i64, res: *i64, out: *u8, outcap: i64, oo: *i64, want: *u8) -> i64 {
710 var i: i64 = 0
711 while i < n {
712 let pth: *u8 = ((names as i64) + i * RS_STRIDE) as *u8
713 if rs_excluded(pth, conf, voc) == 1 { voc[RS_V_EXCOUNT] = voc[RS_V_EXCOUNT] + 1 } else {
714 rs_file_scan(pth, conf, voc, res, out, outcap, oo, want)
715 }
716 i = i + 1
717 }
718 return 0
719}
720
721func rs_res_new() -> *i64 {
722 let r: *i64 = sys_mmap(RS_RLEN * 8) as *i64
723 var i: i64 = 0
724 while i < RS_RLEN { r[i] = 0; i = i + 1 }
725 r[RS_R_WANTBUCKET] = 0 - 1
726 r[RS_R_WANTLINE] = 0 - 1
727 return r
728}
729
730// Print the distribution. NUMBERS AND NO VERDICT, and the partition PRINTS ITS OWN SUM.
731func rs_report(conf: *u8, res: *i64, voc: *i64, outp: *u8, wrote: i64) -> i64 {
732 rs_puts("nx_refusal_shape census -- EMITTER SHAPE ONLY (the caller-heeded axis is census-only over the call record and is NOT measured here)\n" as *u8)
733 rs_puts("markers=" as *u8); rs_putn(voc[RS_V_NMARK])
734 rs_puts(" remedies=" as *u8); rs_putn(voc[RS_V_NREM])
735 rs_puts(" passtokens=" as *u8); rs_putn(voc[RS_V_NPASS])
736 rs_puts(" failtokens=" as *u8); rs_putn(voc[RS_V_NFAIL])
737 rs_puts(" selfremedies=" as *u8); rs_putn(voc[RS_V_NSR])
738 rs_puts(" max_gap_lines=" as *u8); rs_putn(voc[RS_V_MAXGAP])
739 rs_puts(" min_msg_bytes=" as *u8); rs_putn(voc[RS_V_MINMSG])
740 rs_puts(" conf_rows_rejected=" as *u8); rs_putn(voc[RS_V_REJECT])
741 rs_puts("\n" as *u8)
742 rs_puts("declared_self_fixture_exclusions=" as *u8); rs_putn(voc[RS_V_NEXCL])
743 rs_puts(" files_skipped_by_them=" as *u8); rs_putn(voc[RS_V_EXCOUNT])
744 rs_puts("\n" as *u8)
745 var xi: i64 = 0
746 while xi < voc[RS_V_NEXCL] {
747 rs_puts("EXCLUDED-BY-DECLARATION " as *u8)
748 sys_write(1, ((conf as i64) + voc[RS_V_EXBASE + xi * 2]) as *u8, voc[RS_V_EXBASE + xi * 2 + 1])
749 rs_puts(" -- this file exists to TEST this detector; its literals are fixtures and tooth names, not emitted refusals\n" as *u8)
750 xi = xi + 1
751 }
752 rs_puts("files=" as *u8); rs_putn(res[RS_R_FILES])
753 rs_puts(" unreadable=" as *u8); rs_putn(res[RS_R_UNREADABLE])
754 rs_puts(" bytes=" as *u8); rs_putn(res[RS_R_BYTES])
755 rs_puts(" blocks=" as *u8); rs_putn(res[RS_R_BLOCKS])
756 rs_puts(" population=" as *u8); rs_putn(res[RS_R_POP])
757 rs_puts("\n" as *u8)
758 rs_puts("-- PARTITION over the population (every refusal block lands in exactly one) --\n" as *u8)
759 var sum: i64 = 0
760 var b: i64 = 0
761 while b < RS_NBUCKET {
762 rs_puts("BUCKET " as *u8)
763 rs_puts(rs_bucket_name(b))
764 rs_puts(" = " as *u8)
765 rs_putn(res[RS_R_BUCKET0 + b])
766 rs_puts("\n" as *u8)
767 sum = sum + res[RS_R_BUCKET0 + b]
768 b = b + 1
769 }
770 rs_puts("PARTITION SUM = " as *u8); rs_putn(sum)
771 rs_puts(" population = " as *u8); rs_putn(res[RS_R_POP])
772 if sum == res[RS_R_POP] { rs_puts(" RECONCILES\n" as *u8) } else { rs_puts(" DOES NOT RECONCILE -- do not publish these numbers\n" as *u8) }
773 rs_puts("offenders (judged and not SHAPED; UNKNOWN is never an offender) = " as *u8)
774 rs_putn(res[RS_R_OFFENDERS]); rs_puts("\n" as *u8)
775 if voc[RS_V_OVER] > 0 {
776 rs_puts("VOCABULARY-TABLE-FULL rows_dropped=" as *u8); rs_putn(voc[RS_V_OVER])
777 rs_puts(" -- the counts above are a FLOOR, not a total\n" as *u8)
778 }
779 // THE VOCABULARY'S OWN DISTRIBUTION, published so the bar is chosen on evidence. A phrase that
780 // fires on nearly the whole population discriminates nothing and must be cut, and a phrase that
781 // never fires is dead weight -- neither is visible from a bucket count alone.
782 // ⚠ FIRST-FIRED, NOT A TOTAL: a block is charged to the first entry that matched, so these are
783 // order-dependent shares of the population and they SUM to it. They are not per-phrase recall.
784 rs_puts("-- VOCABULARY first-fired shares (order-dependent; they SUM to the population) --\n" as *u8)
785 var mi2: i64 = 0
786 while mi2 < voc[RS_V_NMARK] {
787 rs_puts("MARKER " as *u8)
788 sys_write(1, ((conf as i64) + voc[RS_V_BASE + mi2 * 2]) as *u8, voc[RS_V_BASE + mi2 * 2 + 1])
789 rs_puts(" = " as *u8); rs_putn(voc[RS_V_MKHIT + mi2]); rs_puts("\n" as *u8)
790 mi2 = mi2 + 1
791 }
792 var ri2: i64 = 0
793 while ri2 < voc[RS_V_NREM] {
794 rs_puts("REMEDY " as *u8)
795 sys_write(1, ((conf as i64) + voc[RS_V_REMBASE + ri2 * 2]) as *u8, voc[RS_V_REMBASE + ri2 * 2 + 1])
796 rs_puts(" = " as *u8); rs_putn(voc[RS_V_RMHIT + ri2]); rs_puts("\n" as *u8)
797 ri2 = ri2 + 1
798 }
799 if (outp as i64) != 0 {
800 rs_puts("worklist=" as *u8); rs_puts(outp)
801 rs_puts(" bytes=" as *u8); rs_putn(wrote)
802 if res[RS_R_TRUNC] == 1 { rs_puts(" WORKLIST-TRUNCATED=1 -- the COUNTS above are complete, the LIST is not" as *u8) }
803 rs_puts("\n" as *u8)
804 }
805 rs_puts("note: NUMBERS AND NO VERDICT. The bar lives in the ratchet (a set of NAMES that self-baselines and prints its bar), because a detector that fires on everything discriminates nothing.\n" as *u8)
806 return 0
807}
808
809// THE CENSUS. Full compile corpus, never a sample: gk_corpus_scan enumerates _hdl_build over
810// runtime exactly as the builder resolves them, and REFUSES rather than reporting a prefix.
811func rs_scan(confp: *u8, outp: *u8, res: *i64, want: *u8) -> i64 {
812 let clen: *i64 = sys_mmap(RS_SMALL) as *i64
813 let voc: *i64 = sys_mmap(RS_V_LEN * 8) as *i64
814 let conf: *u8 = rs_conf_load(confp, clen, voc)
815 if (conf as i64) == 0 { return 3 }
816
817 let names: *u8 = sys_mmap(RS_MAXF * RS_STRIDE)
818 let n: i64 = gk_corpus_scan(names, RS_STRIDE, RS_MAXF)
819 if n < 0 {
820 rs_err("nx_refusal_shape: the corpus enumerator REFUSED rc=" as *u8)
821 rs_puts("CORPUS-REFUSED rc=" as *u8); rs_putn(n)
822 rs_puts(" (-1 root unresolvable on this host, -2 file cap reached, -3 a path exceeds the stride) -- no census is published from a partial enumeration\n" as *u8)
823 return 3
824 }
825 let outcap: i64 = n * RS_OUT_PER_FILE + RS_SLACK
826 let out: *u8 = sys_mmap(outcap)
827 let oo: *i64 = sys_mmap(RS_SMALL) as *i64
828 oo[0] = 0
829
830 rs_scan_list(names, n, conf, voc, res, out, outcap, oo, want)
831
832 var wrote: i64 = 0
833 let fd: i64 = sys_openat_wr(outp, RS_MODE_0644)
834 if fd >= 0 { wrote = sys_write(fd, out, oo[0]); sys_close(fd) }
835 if fd < 0 { rs_puts("WORKLIST-WRITE-FAILED path=" as *u8); rs_puts(outp); rs_puts("\n" as *u8) }
836 rs_puts("corpus_files_enumerated=" as *u8); rs_putn(n); rs_puts("\n" as *u8)
837 rs_report(conf, res, voc, outp, wrote)
838 return 0
839}
840
841// ---------------------------------------------------------------------------------------------
842// THE RATCHET -- a SET OF NAMES, never a count.
843//
844// A count-only ratchet on a shared tree reports a regression without saying whose. This one names
845// every new offender, prints its bar in EVERY verdict, and SELF-BASELINES on first sight so
846// adopting it cannot turn the fleet red on day one -- a detector that is permanently red is one
847// everyone learns to ignore.
848//
849// It is deliberately wrong in the direction of DOING NOTHING: on a rise it REFUSES and does NOT
850// touch the baseline, because a ratchet that rewrites its baseline on a rise launders itself green
851// and can never fire twice.
852// ---------------------------------------------------------------------------------------------
853const RS_RATCHET_ALLOW: i64 = 0
854const RS_RATCHET_REFUSE: i64 = 1
855const RS_RATCHET_UNMEAS: i64 = 3
856
857// The bucket NAME back to its id. This exists so the ratchet and the census cannot disagree about
858// what an offender is.
859// 🔴 MEASURED 2026-08-20, IN THIS ORGAN, BY RUNNING IT: the first version of rs_row_name tested two
860// bucket names BY HAND -- "SHAPED" and "UNKNOWN" -- and when the TEST-ASSERTION and USAGE-BANNER
861// buckets were added it silently kept counting them as offenders. The census said 9,039 offenders
862// and the ratchet baselined 13,286 in the same run, over the same file. Neither number is wrong on
863// its own terms; there were simply TWO RULERS for one concept, kept in step by discipline until the
864// day a bucket was added. ★ WHEN TWO PIECES OF CODE MUST AGREE, MAKE DISAGREEMENT IMPOSSIBLE BY
865// CONSTRUCTION -- the ratchet now asks rs_is_offender, the same function the census asks.
866// An unrecognised label is its OWN outcome, never folded into a known one: the bucket a stranger
867// lands in becomes a number somebody plans against.
868func rs_bucket_id(buf: *u8, s: i64, e: i64) -> i64 {
869 var b: i64 = 0
870 while b < RS_NBUCKET {
871 if rs_range_eq(buf, s, e, rs_bucket_name(b)) == 1 { return b }
872 b = b + 1
873 }
874 return 0 - 1
875}
876
877// Returns 1 (an offender, span filled), 0 (a judged non-offender), -2 (malformed row), or
878// -3 (an unrecognised bucket label).
879func rs_row_name(buf: *u8, s: i64, e: i64, span: *i64) -> i64 {
880 // row = <BUCKET> TAB <file:line> TAB <marker> TAB <preview>
881 let t1: i64 = rs_find_byte(buf, s, e, RS_TAB)
882 if t1 < 0 { return 0 - 2 }
883 let t2: i64 = rs_find_byte(buf, t1 + 1, e, RS_TAB)
884 var ne: i64 = e
885 if t2 >= 0 { ne = t2 }
886 let b: i64 = rs_bucket_id(buf, s, t1)
887 if b < 0 { return 0 - 3 }
888 if rs_is_offender(b) == 0 { return 0 }
889 span[0] = t1 + 1
890 span[1] = ne
891 return 1
892}
893
894// MEMBERSHIP BY HASH SET, NOT BY RESCAN.
895// ⚠ MEASURED 2026-08-20 ON THE REAL BASELINE: the first version answered "is this name in the
896// baseline" by walking the whole baseline for EVERY current name -- 9,039 names against a
897// 1.1 MB baseline is ~10 GB of byte scanning and it took roughly 90 seconds per run. It was
898// CORRECT and it was still a bug: this runs on a beat, and a change that burns the array is a
899// defect even when its answer is right. Budget the complexity before writing, not after.
900// The table is sized FROM THE INPUT (the next power of two above 4x the name count), so there is
901// no capacity to guess and no cap to reach in silence.
902const RS_FNV_OFFSET: i64 = 2166136261
903const RS_FNV_PRIME: i64 = 16777619
904const RS_HASH_MASK32: i64 = 4294967295
905const RS_HASH_LOAD: i64 = 4 // 4x headroom keeps linear probing short
906
907func rs_name_hash(buf: *u8, s: i64, e: i64) -> i64 {
908 var h: i64 = RS_FNV_OFFSET
909 var i: i64 = s
910 while i < e {
911 h = h ^ (buf[i] as i64)
912 h = (h * RS_FNV_PRIME) & RS_HASH_MASK32
913 i = i + 1
914 }
915 return h
916}
917
918func rs_name_eq(a: *u8, as0: i64, ae: i64, b: *u8, bs: i64, be: i64) -> i64 {
919 if ae - as0 != be - bs { return 0 }
920 var i: i64 = 0
921 while i < ae - as0 {
922 if a[as0 + i] != b[bs + i] { return 0 }
923 i = i + 1
924 }
925 return 1
926}
927
928// Fill the table from the baseline's lines. Returns the number of names inserted.
929func rs_name_index(bl: *u8, blen: i64, hs: *i64, he: *i64, hcap: i64) -> i64 {
930 var i: i64 = 0
931 while i < hcap { hs[i] = 0 - 1; he[i] = 0 - 1; i = i + 1 }
932 var n: i64 = 0
933 var p: i64 = 0
934 while p < blen {
935 let eol: i64 = gk_eol(bl, p, blen)
936 if eol > p {
937 var slot: i64 = rs_name_hash(bl, p, eol) & (hcap - 1)
938 var placing: i64 = 1
939 while placing == 1 {
940 if hs[slot] < 0 { hs[slot] = p; he[slot] = eol; n = n + 1; placing = 0 } else {
941 if rs_name_eq(bl, hs[slot], he[slot], bl, p, eol) == 1 { placing = 0 } else {
942 slot = slot + 1
943 if slot >= hcap { slot = 0 }
944 }
945 }
946 }
947 }
948 p = eol + 1
949 }
950 return n
951}
952
953func rs_name_in(bl: *u8, hs: *i64, he: *i64, hcap: i64, needle: *u8, ns: i64, ne: i64) -> i64 {
954 var slot: i64 = rs_name_hash(needle, ns, ne) & (hcap - 1)
955 var probing: i64 = 1
956 while probing == 1 {
957 if hs[slot] < 0 { return 0 }
958 if rs_name_eq(bl, hs[slot], he[slot], needle, ns, ne) == 1 { return 1 }
959 slot = slot + 1
960 if slot >= hcap { slot = 0 }
961 }
962 return 0
963}
964
965func rs_ratchet(worklistp: *u8, baselinep: *u8) -> i64 {
966 let wlen: *i64 = sys_mmap(RS_SMALL) as *i64
967 let wl: *u8 = sys_read_file(worklistp, wlen)
968 if wlen[0] <= 0 {
969 rs_puts("RATCHET UNMEASURED -- no worklist at " as *u8); rs_puts(worklistp)
970 rs_puts(" . Run the census first: nx_failclass refusal <conf> <worklist>\n" as *u8)
971 return RS_RATCHET_UNMEAS
972 }
973 // Current offender set, as offsets into the worklist buffer. Capacity derived from the file:
974 // the shortest possible row is well over 8 bytes, so wlen/8 can never be exceeded.
975 let cap: i64 = wlen[0] / 8 + 8
976 let cs: *i64 = sys_mmap(cap * 8) as *i64
977 let ce: *i64 = sys_mmap(cap * 8) as *i64
978 let span: *i64 = sys_mmap(RS_SMALL) as *i64
979 var ncur: i64 = 0
980 var nmalformed: i64 = 0
981 var nunrecognised: i64 = 0
982 var nrows: i64 = 0
983 var p: i64 = 0
984 while p < wlen[0] {
985 let eol: i64 = gk_eol(wl, p, wlen[0])
986 if eol > p {
987 nrows = nrows + 1
988 let r: i64 = rs_row_name(wl, p, eol, span)
989 if r == 1 {
990 if ncur < cap {
991 cs[ncur] = span[0]
992 ce[ncur] = span[1]
993 ncur = ncur + 1
994 }
995 }
996 if r == 0 - 2 { nmalformed = nmalformed + 1 }
997 if r == 0 - 3 { nunrecognised = nunrecognised + 1 }
998 }
999 p = eol + 1
1000 }
1001 rs_puts("RATCHET-INPUT rows=" as *u8); rs_putn(nrows)
1002 rs_puts(" offenders=" as *u8); rs_putn(ncur)
1003 rs_puts(" malformed_rows=" as *u8); rs_putn(nmalformed)
1004 rs_puts(" unrecognised_bucket_labels=" as *u8); rs_putn(nunrecognised)
1005 rs_puts(" (offenders here MUST equal the census offenders line -- both ask rs_is_offender)\n" as *u8)
1006
1007 let blen: *i64 = sys_mmap(RS_SMALL) as *i64
1008 let bl: *u8 = sys_read_file(baselinep, blen)
1009 if blen[0] <= 0 {
1010 // SELF-BASELINE. Adopting the ratchet is non-breaking BY CONSTRUCTION.
1011 let fd0: i64 = sys_openat_wr(baselinep, RS_MODE_0644)
1012 var w0: i64 = 0
1013 if fd0 >= 0 {
1014 var i0: i64 = 0
1015 while i0 < ncur {
1016 w0 = w0 + sys_write(fd0, ((wl as i64) + cs[i0]) as *u8, ce[i0] - cs[i0])
1017 let nlb: *u8 = sys_mmap(RS_SMALL)
1018 nlb[0] = RS_NL as u8
1019 sys_write(fd0, nlb, 1)
1020 sys_munmap(nlb, RS_SMALL)
1021 i0 = i0 + 1
1022 }
1023 sys_close(fd0)
1024 }
1025 rs_puts("RATCHET SELF-BASELINED baseline=" as *u8); rs_putn(0)
1026 rs_puts(" current=" as *u8); rs_putn(ncur)
1027 rs_puts(" new=0 file=" as *u8); rs_puts(baselinep)
1028 rs_puts("\n" as *u8)
1029 return RS_RATCHET_ALLOW
1030 }
1031
1032 var nbase: i64 = 0
1033 var q: i64 = 0
1034 while q < blen[0] {
1035 let e2: i64 = gk_eol(bl, q, blen[0])
1036 if e2 > q { nbase = nbase + 1 }
1037 q = e2 + 1
1038 }
1039
1040 var hcap: i64 = 1
1041 while hcap < nbase * RS_HASH_LOAD + RS_HASH_LOAD { hcap = hcap * 2 }
1042 let hs: *i64 = sys_mmap(hcap * 8) as *i64
1043 let he: *i64 = sys_mmap(hcap * 8) as *i64
1044 let nidx: i64 = rs_name_index(bl, blen[0], hs, he, hcap)
1045
1046 var nnew: i64 = 0
1047 var i: i64 = 0
1048 while i < ncur {
1049 if rs_name_in(bl, hs, he, hcap, wl, cs[i], ce[i]) == 0 {
1050 nnew = nnew + 1
1051 rs_puts("RATCHET-NEW " as *u8)
1052 sys_write(1, ((wl as i64) + cs[i]) as *u8, ce[i] - cs[i])
1053 rs_puts("\n" as *u8)
1054 }
1055 i = i + 1
1056 }
1057
1058 rs_puts("RATCHET baseline=" as *u8); rs_putn(nbase)
1059 rs_puts(" distinct_baseline_names=" as *u8); rs_putn(nidx)
1060 rs_puts(" current=" as *u8); rs_putn(ncur)
1061 rs_puts(" new=" as *u8); rs_putn(nnew)
1062 if nnew > 0 {
1063 rs_puts(" REFUSE -- the baseline is NOT rewritten: a ratchet that rewrites on a rise launders itself green. Fix the named refusals, or accept them deliberately by editing " as *u8)
1064 rs_puts(baselinep)
1065 rs_puts("\n" as *u8)
1066 return RS_RATCHET_REFUSE
1067 }
1068 if ncur < nbase {
1069 let fd1: i64 = sys_openat_wr(baselinep, RS_MODE_0644)
1070 if fd1 >= 0 {
1071 var i1: i64 = 0
1072 while i1 < ncur {
1073 sys_write(fd1, ((wl as i64) + cs[i1]) as *u8, ce[i1] - cs[i1])
1074 let nlb1: *u8 = sys_mmap(RS_SMALL)
1075 nlb1[0] = RS_NL as u8
1076 sys_write(fd1, nlb1, 1)
1077 sys_munmap(nlb1, RS_SMALL)
1078 i1 = i1 + 1
1079 }
1080 sys_close(fd1)
1081 }
1082 rs_puts(" TIGHTENED -- the baseline now records the smaller set\n" as *u8)
1083 return RS_RATCHET_ALLOW
1084 }
1085 rs_puts(" ALLOW\n" as *u8)
1086 return RS_RATCHET_ALLOW
1087}