nx_deployjrnl_gate.nx source
↩ module page · 220 lines · 12812 B
1// nx_deployjrnl_gate.nx -- THE REFEREE for the deploy-audit reconstruction.
2//
3// It tests the SHIPPING ruler, not a paraphrase of it: every judgement nx_deployjrnl makes lives in
4// nx_deployjrnl_lib as a pure function, and this gate imports that same lib. There is one copy, so a
5// tooth here cannot pass while the organ does something else.
6//
7// Fixtures are assembled at RUNTIME under /tmp/<gate>/ and never share a path with a production
8// beat: a gate whose RED tracks its fixture rather than its code has been measured in this estate,
9// and a detector that scans source will otherwise find its own test data.
10//
11// license_tier: ORIGINAL
12import "nx_syscalls.nx"
13import "nx_gate_verdict.nx"
14import "nx_deployjrnl_lib.nx"
15
16const G_DIR: *u8 = "/tmp/nx_deployjrnl_gate" as *u8
17const G_BANK: *u8 = "/tmp/nx_deployjrnl_gate/bank" as *u8
18const G_MODE_DIR: i64 = 511
19const G_A_LIVE: *u8 = "/tmp/nx_deployjrnl_gate/a.elf" as *u8
20const G_A_PREV: *u8 = "/tmp/nx_deployjrnl_gate/a.elf.prev" as *u8
21const G_B_LIVE: *u8 = "/tmp/nx_deployjrnl_gate/b.elf" as *u8
22const G_B_PREV: *u8 = "/tmp/nx_deployjrnl_gate/b.elf.prev" as *u8
23const G_BANKED: *u8 = "/tmp/nx_deployjrnl_gate/bank/a.elf.pre-tag" as *u8
24const G_MISSING: *u8 = "/tmp/nx_deployjrnl_gate/definitely-absent" as *u8
25
26func g_eq(a: i64, b: i64) -> i64 { if a == b { return 1 } return 0 }
27func g_not(a: i64) -> i64 { if a == 1 { return 0 } return 1 }
28func g_and(a: i64, b: i64) -> i64 { if a == 1 { if b == 1 { return 1 } } return 0 }
29func g_ge0(v: i64) -> i64 { if v >= 0 { return 1 } return 0 }
30
31func g_write(path: *u8, s: *u8) -> i64 {
32 let fd: i64 = sys_openat_wr(path, DJ_MODE_644)
33 if fd < 0 { return 0 }
34 sys_write(fd, s, dj_slen(s))
35 sys_close(fd)
36 return 1
37}
38
39func main(argc: i64, argv: *i64) -> i64 {
40 let ctr: *i64 = gv_ctr()
41 gv_head("NX-DEPLOYJRNL-GATE -- deploy reconstruction + rollback survivability" as *u8)
42
43 // ---- SETUP. Created at setup, not teardown: a teardown does not run when a run crashes. ----
44 sys_mkdir(G_DIR, G_MODE_DIR)
45 sys_mkdir(G_BANK, G_MODE_DIR)
46 // a.elf.prev content is ALSO in the bank -> banked rollback.
47 // b.elf.prev content is banked NOWHERE -> the hazard that actually fired on 2026-08-25.
48 var setup: i64 = 1
49 if g_write(G_A_LIVE, "AAAA-live" as *u8) == 0 { setup = 0 }
50 if g_write(G_A_PREV, "AAAA-rollback" as *u8) == 0 { setup = 0 }
51 if g_write(G_BANKED, "AAAA-rollback" as *u8) == 0 { setup = 0 }
52 if g_write(G_B_LIVE, "BBBB-live" as *u8) == 0 { setup = 0 }
53 if g_write(G_B_PREV, "BBBB-rollback" as *u8) == 0 { setup = 0 }
54 gv_check("fixture-assembled" as *u8, setup, ctr)
55
56 // ---- 1. THE PURE TRANSITION RULER ----
57 gv_check("transition-first-sight-is-BASELINE" as *u8,
58 g_eq(dj_transition(0, 0, 0, 0), DJ_T_NEW), ctr)
59 gv_check("transition-nothing-moved-is-UNCHANGED" as *u8,
60 g_eq(dj_transition(1, 0, 0, 0), DJ_T_UNCHANGED), ctr)
61 // ONE deploy satisfies the invariant: the new prev IS the live we recorded last scan.
62 gv_check("transition-single-deploy-is-WITNESSED" as *u8,
63 g_eq(dj_transition(1, 1, 1, 1), DJ_T_DEPLOY_WITNESSED), ctr)
64 // The invariant BROKEN means a binary passed through the serving slot we never hashed.
65 gv_check("transition-broken-invariant-is-MULTI-UNWITNESSED" as *u8,
66 g_eq(dj_transition(1, 1, 0, 1), DJ_T_DEPLOY_MULTI), ctr)
67 gv_check("transition-prev-moved-alone-is-PREV-ONLY" as *u8,
68 g_eq(dj_transition(1, 0, 0, 1), DJ_T_PREV_ONLY), ctr)
69
70 // THE TOOTH THAT MATTERS MOST FOR HONESTY: a multi-deploy must NEVER be reported as a clean
71 // single deploy. If this ever passes silently the instrument is claiming to have witnessed
72 // something it did not.
73 gv_bite("bite-unwitnessed-multi-deploy-not-laundered-as-witnessed" as *u8,
74 g_eq(dj_transition(1, 1, 0, 1), DJ_T_DEPLOY_MULTI),
75 g_eq(dj_transition(1, 1, 1, 1), DJ_T_DEPLOY_MULTI), ctr)
76
77 // ---- 2. RISK + THE HAZARD PREDICATE ----
78 gv_check("risk-no-prev-is-NO-ROLLBACK" as *u8, g_eq(dj_risk(0, 0), DJ_R_NO_ROLLBACK), ctr)
79 gv_check("risk-prev-banked-is-OK" as *u8, g_eq(dj_risk(1, 1), DJ_R_OK), ctr)
80 gv_check("risk-prev-unbanked-is-AT-RISK" as *u8, g_eq(dj_risk(1, 0), DJ_R_AT_RISK), ctr)
81
82 // THE HAZARD THAT FIRED LIVE: a second deploy destroying the only rollback.
83 gv_bite("bite-second-deploy-would-destroy-only-rollback" as *u8,
84 dj_would_destroy_only_rollback(1, 0),
85 dj_would_destroy_only_rollback(1, 1), ctr)
86 // neg-control: with NO rollback slot there is nothing to destroy, so the alarm must stay silent.
87 // A guard that refuses everything passes every deny-test; this is the input that must be ALLOWED.
88 gv_check("neg-control-absent-prev-raises-no-destroy-alarm" as *u8,
89 g_eq(dj_would_destroy_only_rollback(0, 0), 0), ctr)
90
91 // ---- 3. VERDICT ORDERING ----
92 // UNPROVEN outranks everything: an axis that could not look must not be spelled like a clean run.
93 gv_check("verdict-unobserved-is-UNPROVEN-not-GREEN" as *u8,
94 g_eq(dj_verdict(0, 0, 0, 0, 0), DJ_V_UNPROVEN), ctr)
95 gv_check("verdict-irreplaceable-eviction-is-RED" as *u8,
96 g_eq(dj_verdict(1, 0, 0, 0, 1), DJ_V_RED), ctr)
97 gv_check("verdict-unwitnessed-deploy-is-RED" as *u8,
98 g_eq(dj_verdict(0, 1, 0, 0, 1), DJ_V_RED), ctr)
99 gv_check("verdict-atrisk-above-floor-is-AMBER" as *u8,
100 g_eq(dj_verdict(0, 0, 5, 3, 1), DJ_V_AMBER), ctr)
101 // AT-RISK at or below the floor is the NORMAL post-deploy state. If this went RED the board
102 // would be permanently red and everyone would learn to ignore it.
103 gv_check("verdict-atrisk-at-floor-is-GREEN-not-permanently-red" as *u8,
104 g_eq(dj_verdict(0, 0, 3, 3, 1), DJ_V_GREEN), ctr)
105
106 // ---- 4. THE RATCHET, ALL THREE DIRECTIONS ----
107 gv_check("ratchet-tightens-on-a-fall" as *u8, g_eq(dj_new_floor(1, 3), 1), ctr)
108 gv_check("ratchet-holds-when-flat" as *u8, g_eq(dj_new_floor(3, 3), 3), ctr)
109 // The direction that matters: a ratchet that raised its own floor on a rise would launder every
110 // regression green -- the exact failure a ratchet exists to prevent.
111 gv_check("ratchet-refuses-to-rewrite-baseline-on-a-rise" as *u8, g_eq(dj_new_floor(9, 3), 3), ctr)
112 gv_bite("bite-ratchet-holds-on-rise-and-moves-on-fall" as *u8,
113 g_eq(dj_new_floor(9, 3), 3),
114 g_eq(dj_new_floor(1, 3), 3), ctr)
115
116 // ---- 5. HASHING: FAIL LOUD, NEVER THE SHA OF NOTHING ----
117 let h1: *u8 = sys_mmap(DJ_HEXSLOT)
118 let h2: *u8 = sys_mmap(DJ_HEXSLOT)
119 let hm: *u8 = sys_mmap(DJ_HEXSLOT)
120 let na: i64 = dj_hash_file(G_A_PREV, h1)
121 let nb: i64 = dj_hash_file(G_BANKED, h2)
122 let nm: i64 = dj_hash_file(G_MISSING, hm)
123 gv_check("fixture-reached-condition-both-files-hashed" as *u8,
124 g_and(g_ge0(na), g_ge0(nb)), ctr)
125 gv_check("identical-content-hashes-identical" as *u8, dj_streq(h1, h2), ctr)
126 // A hasher that digested an empty buffer would return a real-looking 64-hex answer for a file
127 // that does not exist. That is a lying instrument, and it is the shape a bank lookup would
128 // silently trust.
129 gv_check("neg-control-missing-file-returns-minus-one-not-a-digest" as *u8, g_eq(nm, 0 - 1), ctr)
130
131 // ---- 5b. THE STAT PRIMITIVE ----
132 // THIS TOOTH EXISTS BECAUSE ITS ABSENCE LET A REAL DEFECT SHIP. On 2026-08-25 this gate read
133 // GREEN 42/42 while dj_stat was failing for EVERY path in the estate: a syscall number split by
134 // target left both consts in the build, the wrong one won, and on x86_64 it dispatched getcwd.
135 // Every tooth here tested a PURE ruler; nothing tested the one impure primitive, so the gate had
136 // no opinion about the only thing that was broken. A GREEN GATE THAT HAS NEVER EXERCISED THE
137 // SYSCALL IT DEPENDS ON IS NOT EVIDENCE ABOUT THAT SYSCALL.
138 let stt: *i64 = sys_mmap(DJ_LENSLOT * 2) as *i64
139 let statok: i64 = dj_stat(G_A_LIVE, stt)
140 gv_check("stat-succeeds-on-a-real-file" as *u8, g_eq(statok, 1), ctr)
141 // The size is asserted against the fixture this gate WROTE, so it cannot pass on a stale buffer.
142 gv_check("stat-returns-the-true-size" as *u8, g_eq(stt[1], 9), ctr)
143 gv_check("stat-returns-a-nonzero-mtime" as *u8, g_ge0(stt[0] - 1), ctr)
144 let stmiss: i64 = dj_stat(G_MISSING, stt)
145 gv_check("neg-control-stat-fails-on-an-absent-file" as *u8, g_eq(stmiss, 0), ctr)
146 gv_bite("bite-stat-discriminates-present-from-absent" as *u8,
147 g_eq(statok, 1), g_eq(stmiss, 1), ctr)
148
149 // ---- 6. BANK MEMBERSHIP IS BY CONTENT, NOT BY NAME ----
150 let bank: *u8 = sys_mmap(DJ_HEXSLOT * 4)
151 dj_cat(bank, 0, h2)
152 let e0: *u8 = ((bank as i64) + 0) as *u8
153 e0[DJ_HEX_BYTES] = 0 as u8
154 let hb: *u8 = sys_mmap(DJ_HEXSLOT)
155 let nbp: i64 = dj_hash_file(G_B_PREV, hb)
156 gv_check("fixture-reached-condition-b-prev-hashed" as *u8, g_ge0(nbp), ctr)
157 // a.elf.prev is banked under a DIFFERENT filename (a.elf.pre-tag) and must still be found.
158 gv_check("bank-hit-despite-unpredicted-filename-tag" as *u8, dj_bank_has(bank, 1, h1), ctr)
159 gv_check("neg-control-unbanked-content-is-not-found" as *u8, g_eq(dj_bank_has(bank, 1, hb), 0), ctr)
160 gv_bite("bite-bank-membership-discriminates" as *u8,
161 dj_bank_has(bank, 1, h1),
162 dj_bank_has(bank, 1, hb), ctr)
163
164 // ---- 7. THE DIRECTORY WALK ----
165 let nm1: *u8 = sys_mmap(64 * DJ_NAME_SLOT)
166 let cn: *i64 = sys_mmap(DJ_LENSLOT) as *i64
167 let sk: *i64 = sys_mmap(DJ_LENSLOT) as *i64
168 let wrc: i64 = dj_walk(G_DIR, ".elf.prev" as *u8, nm1, DJ_NAME_SLOT, 64, cn, sk)
169 gv_check("walk-returns-OK-on-a-real-directory" as *u8, g_eq(wrc, DJ_W_OK), ctr)
170 gv_subjects("walk-found-rollback-slots" as *u8, cn[0], ctr)
171 gv_check("walk-found-both-planted-prev-files" as *u8, g_eq(cn[0], 2), ctr)
172
173 // A suffix that matches nothing must return OK with zero rows -- that is a real, proven empty.
174 let wrc0: i64 = dj_walk(G_DIR, ".no-such-suffix" as *u8, nm1, DJ_NAME_SLOT, 64, cn, sk)
175 gv_check("walk-genuine-empty-is-OK-with-zero-rows" as *u8,
176 g_and(g_eq(wrc0, DJ_W_OK), g_eq(cn[0], 0)), ctr)
177
178 // THE CONFIDENT-EMPTY DEFECT. Pointed at a FILE, getdents returns negative. If that were folded
179 // into end-of-directory the walk would report a serene "zero entries" for a path it never read,
180 // and every absence conclusion downstream would be false.
181 let wrcf: i64 = dj_walk(G_A_LIVE, "" as *u8, nm1, DJ_NAME_SLOT, 64, cn, sk)
182 gv_check("neg-control-walking-a-file-does-not-return-OK" as *u8, g_not(g_eq(wrcf, DJ_W_OK)), ctr)
183 gv_bite("bite-error-and-empty-directory-are-distinguishable" as *u8,
184 g_not(g_eq(wrcf, DJ_W_OK)),
185 g_not(g_eq(wrc0, DJ_W_OK)), ctr)
186
187 // The cap must REFUSE, not silently return a prefix: a cap reached in silence becomes a
188 // measurement nobody knows is partial.
189 let wrcc: i64 = dj_walk(G_DIR, ".elf.prev" as *u8, nm1, DJ_NAME_SLOT, 1, cn, sk)
190 gv_check("walk-refuses-at-cap-instead-of-truncating-silently" as *u8, g_eq(wrcc, DJ_W_CAP), ctr)
191
192 // ---- 8. STATUS ROUND-TRIP (the producer parses its own format) ----
193 let line: *u8 = sys_mmap(DJ_PATH_SLOT)
194 var o: i64 = dj_cat(line, 0, "ROW target=a.elf live=" as *u8)
195 o = dj_cat(line, o, h1)
196 o = dj_cat(line, o, " lbytes=13 lmtime=1787680659 prev=" as *u8)
197 o = dj_cat(line, o, hb)
198 o = dj_cat(line, o, " pbytes=13 banked=0 risk=1\n" as *u8)
199 let fv: *u8 = sys_mmap(DJ_HEXSLOT)
200 gv_check("roundtrip-target-parses" as *u8,
201 g_eq(dj_field(line, 0, o, "target=" as *u8, fv, DJ_HEXSLOT), 1), ctr)
202 gv_check("roundtrip-target-value-correct" as *u8, dj_streq(fv, "a.elf" as *u8), ctr)
203 dj_field(line, 0, o, "live=" as *u8, fv, DJ_HEXSLOT)
204 gv_check("roundtrip-live-sha-survives" as *u8, dj_streq(fv, h1), ctr)
205 dj_field(line, 0, o, "prev=" as *u8, fv, DJ_HEXSLOT)
206 gv_check("roundtrip-prev-sha-survives" as *u8, dj_streq(fv, hb), ctr)
207 // ANCHORING. "live=" must not match inside "lbytes=" or any longer key, and a key that is not
208 // present must report absent rather than returning the neighbouring field's value.
209 gv_check("neg-control-absent-key-reports-absent" as *u8,
210 g_eq(dj_field(line, 0, o, "nosuchkey=" as *u8, fv, DJ_HEXSLOT), 0), ctr)
211
212 // ---- 9. NUMERIC PARSE REFUSES GARBAGE ----
213 gv_check("atoi-parses-a-number" as *u8, g_eq(dj_atoi("42" as *u8), 42), ctr)
214 // A corrupt ratchet conf read as 0 would refuse every future scan. Refuse the parse instead.
215 gv_check("neg-control-atoi-refuses-non-numeric" as *u8, g_eq(dj_atoi("x9" as *u8), 0 - 1), ctr)
216 gv_check("neg-control-atoi-refuses-empty" as *u8, g_eq(dj_atoi("" as *u8), 0 - 1), ctr)
217
218 return gv_verdict("DEPLOYJRNL-GATE" as *u8, ctr,
219 "reconstruction rulers, rollback-destruction hazard, walk coverage, ratchet direction" as *u8)
220}