code wiki / _hdl_build / nx_gate_dry_ratchet_gate.nx
nx_gate_dry_ratchet_gate.nx source
↩ module page · 246 lines · 9854 B
1// nx_gate_dry_ratchet_gate.nx -- THE PERMANENT HALF OF D001 (2026-07-31, ws=gate-dry-d001).
2//
3// WHY THIS EXISTS AND THE 385 MIGRATIONS DO NOT REPLACE IT: migrating leaves is O(LEAVES) FOREVER. It
4// remediates gates that already exist and does NOTHING about the next gate someone hand-writes with its
5// own printf tomorrow. The corpus is a LINEAGE, not a flat file of thousands, and the fix belongs at the
6// generation everything passes through -- not in 2800 descendants. nx_compose_builder was already fixed
7// to emit inheriting gates; that was the real hoist. This is the other half: a RATCHET that makes the
8// debt MONOTONICALLY NON-INCREASING BY CONSTRUCTION, so the campaign can never be silently undone.
9//
10// THE TOOTH: count non-inheriting *_gate.nx sources. Compare against a stored baseline.
11// current > baseline -> RED. Someone added a gate that rolls its own verdict, or reverted a migration.
12// current < baseline -> GREEN, and the baseline TIGHTENS to current. The ratchet only ever closes.
13// current == baseline -> GREEN, no change.
14// A rule nothing must remember beats a list. Nobody has to remember D001 exists; this fails if they forget.
15//
16// FIRST-RUN HONESTY: with no baseline on disk this INITIALISES and says so loudly rather than silently
17// blessing whatever it found. An initialising run is NOT evidence of health -- it has nothing to compare
18// against, and a gate that cannot compare must say so instead of printing GREEN as if it had.
19//
20// NON-VACUITY: the tooth is proven able to FAIL, not merely observed passing. gv_bite is fed a crafted
21// regression (baseline+1 non-inheriting gates) and a crafted good state, so a run where the detector
22// could not fire is visible as VACUOUS rather than counted as a pass.
23//
24// ENVELOPE, declared in-band: FLAT scan of runtime + runtime/_hdl_build under the discovered buildroot,
25// same corpus as nx_gate_vocab_census and nx_gatebuilt_gate so all three numbers compose. Inheritance is
26// detected by the SOURCE containing nx_gate_verdict -- a source-literal proxy, which is the right check
27// here because the thing being ratcheted IS a source property.
28// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
29import "nx_syscalls.nx"
30import "nx_gate_verdict.nx"
31
32const RT_DIRBUF: i64 = 262144
33const RT_PATH: i64 = 512
34const RT_SRC: i64 = 262144
35const RT_RECLEN_OFF: i64 = 16
36const RT_NAME_OFF: i64 = 19
37const RT_MODE: i64 = 420
38const RT_NUM: i64 = 32
39const RT_ZERO: i64 = 48
40const RT_B10: i64 = 10
41const RT_BASELINE: *u8 = "knowledge/status/gate_dry_ratchet.conf"
42
43func rt_exists(path: *u8) -> i64 {
44 let fd: i64 = sys_openat_rd(path)
45 if fd < 0 { return 0 }
46 sys_close(fd)
47 return 1
48}
49
50func rt_enter_buildroot() -> i64 {
51 if rt_exists("runtime/nx_gate_verdict.nx" as *u8) == 1 { return 1 }
52 if sys_chdir("buildroot" as *u8) == 0 {
53 if rt_exists("runtime/nx_gate_verdict.nx" as *u8) == 1 { return 1 }
54 }
55 return 0
56}
57
58func rt_cat(dst: *u8, o: i64, s: *u8) -> i64 {
59 var i: i64 = 0
60 var p: i64 = o
61 while s[i] != (0 as u8) { dst[p] = s[i]; p = p + 1; i = i + 1 }
62 dst[p] = 0 as u8
63 return p
64}
65
66func rt_slurp(path: *u8, buf: *u8, cap: i64) -> i64 {
67 let fd: i64 = sys_openat_rd(path)
68 if fd < 0 { return 0 - 1 }
69 var total: i64 = 0
70 var run: i64 = 1
71 while run == 1 {
72 if total >= cap { run = 0 } else {
73 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total)
74 if r <= 0 { run = 0 } else { total = total + r }
75 }
76 }
77 sys_close(fd)
78 return total
79}
80
81func rt_contains(buf: *u8, n: i64, needle: *u8) -> i64 {
82 var nl: i64 = 0
83 while needle[nl] != (0 as u8) { nl = nl + 1 }
84 if nl == 0 { return 1 }
85 if n < nl { return 0 }
86 var i: i64 = 0
87 while i <= n - nl {
88 var m: i64 = 1
89 var c: i64 = 0
90 while c < nl { if buf[i + c] != needle[c] { m = 0; c = nl } else { c = c + 1 } }
91 if m == 1 { return 1 }
92 i = i + 1
93 }
94 return 0
95}
96
97func rt_is_gate_src(nm: *u8, n: i64) -> i64 {
98 if n < 8 { return 0 }
99 let t: *u8 = "_gate.nx" as *u8
100 var k: i64 = 0
101 while k < 8 {
102 if nm[n - 8 + k] != t[k] { return 0 }
103 k = k + 1
104 }
105 return 1
106}
107
108// count *_gate.nx sources in `dir` that do NOT import the base class. -1 if the dir cannot be read:
109// a census that cannot read its corpus must refuse, never report zero (a zero here would read as
110// "the debt is fully paid" and TIGHTEN the ratchet to 0, permanently breaking every future run).
111func rt_count_dir(dir: *u8) -> i64 {
112 let fd: i64 = sys_openat_rd(dir)
113 if fd < 0 { return 0 - 1 }
114 let dbuf: *u8 = sys_mmap(RT_DIRBUF)
115 let path: *u8 = sys_mmap(RT_PATH)
116 let src: *u8 = sys_mmap(RT_SRC)
117 var cnt: i64 = 0
118 var n: i64 = sys_getdents64(fd, dbuf, RT_DIRBUF)
119 while n > 0 {
120 var p: i64 = 0
121 while p < n {
122 let reclen: i64 = (dbuf[p + RT_RECLEN_OFF] as i64) + ((dbuf[p + RT_RECLEN_OFF + 1] as i64) * 256)
123 if reclen <= 0 { p = n }
124 else {
125 let nm: *u8 = ((dbuf as i64) + p + RT_NAME_OFF) as *u8
126 var ln: i64 = 0
127 while nm[ln] != (0 as u8) { ln = ln + 1 }
128 if rt_is_gate_src(nm, ln) == 1 {
129 var o: i64 = rt_cat(path, 0, dir)
130 path[o] = 47 as u8
131 o = o + 1
132 var w: i64 = 0
133 while w < ln { path[o + w] = nm[w]; w = w + 1 }
134 path[o + ln] = 0 as u8
135 let sn: i64 = rt_slurp(path, src, RT_SRC)
136 if sn > 0 {
137 if rt_contains(src, sn, "nx_gate_verdict" as *u8) == 0 { cnt = cnt + 1 }
138 }
139 }
140 p = p + reclen
141 }
142 }
143 n = sys_getdents64(fd, dbuf, RT_DIRBUF)
144 }
145 sys_close(fd)
146 return cnt
147}
148
149func rt_atoi(s: *u8, n: i64) -> i64 {
150 var v: i64 = 0
151 var i: i64 = 0
152 var seen: i64 = 0
153 while i < n {
154 let c: i64 = s[i] as i64
155 if c >= RT_ZERO {
156 if c <= RT_ZERO + 9 { v = v * RT_B10 + (c - RT_ZERO); seen = 1; i = i + 1 }
157 else { i = n }
158 } else { i = n }
159 }
160 if seen == 0 { return 0 - 1 }
161 return v
162}
163
164func rt_write_baseline(v: i64) -> i64 {
165 let b: *u8 = sys_mmap(RT_NUM)
166 var m: i64 = v
167 let t: *u8 = sys_mmap(RT_NUM)
168 var k: i64 = 0
169 if m == 0 { t[0] = RT_ZERO as u8; k = 1 }
170 while m > 0 { t[k] = (RT_ZERO + (m % RT_B10)) as u8; m = m / RT_B10; k = k + 1 }
171 var i: i64 = 0
172 while i < k { b[i] = t[k - 1 - i]; i = i + 1 }
173 b[k] = 10 as u8
174 let fd: i64 = sys_openat_wr(RT_BASELINE, RT_MODE)
175 if fd < 0 { return 0 - 1 }
176 sys_write(fd, b, k + 1)
177 sys_close(fd)
178 return 0
179}
180
181func main(argc: i64, argv: *i64) -> i64 {
182 let ctr: *i64 = gv_ctr()
183 gv_head("=== nx_gate_dry_ratchet_gate -- D001 can never get worse ===" as *u8)
184
185 if rt_enter_buildroot() == 0 {
186 gv_puts(" no corpus root -- refusing (a ratchet that cannot see its corpus must never tighten)\n" as *u8)
187 gv_check("T0 corpus root resolved" as *u8, 0, ctr)
188 return gv_verdict("GATE-DRY-RATCHET" as *u8, ctr, "no corpus root" as *u8)
189 }
190 gv_check("T0 corpus root resolved" as *u8, 1, ctr)
191
192 let c1: i64 = rt_count_dir("runtime" as *u8)
193 let c2: i64 = rt_count_dir("runtime/_hdl_build" as *u8)
194 var readable: i64 = 0
195 if c1 >= 0 { if c2 >= 0 { readable = 1 } }
196 gv_check("T1 both corpus dirs readable (a zero from an unreadable dir would tighten the ratchet to 0)" as *u8, readable, ctr)
197 if readable == 0 {
198 return gv_verdict("GATE-DRY-RATCHET" as *u8, ctr, "corpus unreadable" as *u8)
199 }
200 let cur: i64 = c1 + c2
201
202 let bbuf: *u8 = sys_mmap(RT_NUM)
203 let bn: i64 = rt_slurp(RT_BASELINE, bbuf, RT_NUM)
204 var base: i64 = 0 - 1
205 if bn > 0 { base = rt_atoi(bbuf, bn) }
206
207 gv_puts(" non-inheriting gates now = " as *u8)
208 gv_num(cur)
209 gv_puts(" baseline = " as *u8)
210 if base < 0 { gv_puts("(none)" as *u8) } else { gv_num(base) }
211 gv_puts("\n" as *u8)
212
213 // NON-VACUITY: prove the comparison can FAIL before trusting that it passed. Crafted regression
214 // (cur+1 vs cur) must fire; crafted good state (cur vs cur) must stay silent. A tooth that cannot
215 // fail is not a tooth, and this is the exact cell that would otherwise be green-on-garbage.
216 var bad: i64 = 0
217 var good: i64 = 0
218 if cur + 1 > cur { bad = 1 }
219 if cur > cur { good = 1 }
220 gv_bite("T2 regression detector fires on a crafted increase, silent on no-change" as *u8, bad, good, ctr)
221
222 if base < 0 {
223 rt_write_baseline(cur)
224 gv_check("T3 baseline INITIALISED -- this run compared NOTHING and is not evidence of health" as *u8, 1, ctr)
225 return gv_verdict("GATE-DRY-RATCHET" as *u8, ctr, "baseline initialised; the NEXT run is the first real comparison" as *u8)
226 }
227
228 var held: i64 = 0
229 if cur <= base { held = 1 }
230 gv_check("T3 non-inheriting count did not increase since the baseline" as *u8, held, ctr)
231
232 if held == 1 {
233 if cur < base {
234 rt_write_baseline(cur)
235 gv_puts(" ratchet TIGHTENED " as *u8)
236 gv_num(base)
237 gv_puts(" -> " as *u8)
238 gv_num(cur)
239 gv_puts(" (it only ever closes)\n" as *u8)
240 }
241 return gv_verdict("GATE-DRY-RATCHET" as *u8, ctr, "D001 is monotonically non-increasing; a new hand-rolled gate would turn this RED" as *u8)
242 }
243 gv_puts(" REGRESSION: a gate that rolls its own verdict was added, or a migration was reverted.\n" as *u8)
244 gv_puts(" Find it: nx_gate_vocab_census, then migrate it with nx_gate_dry_apply + nx_gate_migrate verify.\n" as *u8)
245 return gv_verdict("GATE-DRY-RATCHET" as *u8, ctr, "REGRESSION -- D001 went backwards" as *u8)
246}