nx_comparestale_dependency_t138.nx source
↩ module page · 352 lines · 18189 B
1// nx_comparestale.nx -- IS EVERY PUBLISHED COMPARE PAGE STILL TRUE TO ITS SOURCE?
2//
3// WHY THIS EXISTS. /compare/doctor sat a MONTH stale, publishing "THE root blocker: emitted ELFs
4// carry no line mapping" long after that stopped being true -- with a perfectly valid
5// generated_unix. Nothing could see it, because nothing compared the page to the thing it was
6// generated from. A TIMESTAMP TELLS YOU WHEN A PAGE WAS MADE, NEVER WHETHER IT IS CURRENT.
7//
8// HOW. Both generators now stamp "source_hash" = djb2-48 over the .matrix/.sota they read. This
9// organ recomputes that hash from the source on disk and compares it to the published page.
10// Deliberately CLOCK-FREE: mtimes lie across copies (this tree has TWO knowledge/compare
11// directories and I lost an edit to exactly that) and clocks lie across machines. Content does not.
12//
13// VERDICTS, and the distinction is the point:
14// FRESH page hash == source hash
15// STALE page exists and disagrees with its source -> the source of truth moved, publish did not
16// UNPUBLISHED source exists, no page -> never generated, or gate-refused
17// NOSTAMP page predates the provenance stamp -> republish once and it becomes decidable
18// Exit 1 if any STALE. UNPUBLISHED does NOT fail the gate: a domain whose matrix gate legitimately
19// refuses (legal, present>=5) must not be reported as drift -- CONFLATING "REFUSED" WITH "DRIFTED"
20// IS THE SAME DEFECT THIS ESTATE KEEPS PAYING FOR.
21//
22// license_tier: ORIGINAL expect_exit: 0
23import "nx_syscalls.nx"
24const CS_MAGIC_5381: i64 = 5381
25const CS_MAGIC_8192: i64 = 8192
26const CS_MAGIC_1024: i64 = 1024
27const CS_MAGIC_8000: i64 = 8000
28
29const CS_CAP: i64 = 262144
30
31func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
32func wn(v: i64) -> i64 {
33 var m: i64 = v
34 if m < 0 { w("-" as *u8); m = 0 - m }
35 let t: *u8 = sys_mmap(24)
36 var k: i64 = 0
37 if m == 0 { t[0] = 48 as u8; k = 1 }
38 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
39 let o: *u8 = sys_mmap(24)
40 var i: i64 = 0
41 while i < k { o[i] = t[k-1-i]; i = i + 1 }
42 sys_write(1, o, k)
43 return 0
44}
45func cs_read(path: *u8, buf: *u8, cap: i64) -> i64 {
46 let fd: i64 = sys_openat_rd(path)
47 if fd < 0 { return 0 - 1 }
48 var tot: i64 = 0
49 while tot < cap {
50 let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot)
51 if r <= 0 { break }
52 tot = tot + r
53 }
54 sys_close(fd)
55 return tot
56}
57func cs_cat(dst: *u8, off: i64, s: *u8) -> i64 {
58 var i: i64 = 0
59 while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 }
60 return off + i
61}
62// MUST match the generators byte for byte. If this ever diverges the organ reports drift that is
63// not there -- a comparator and a writer that disagree on canonical form never converge.
64func cs_hash(b: *u8, n: i64) -> i64 {
65 var h: i64 = CS_MAGIC_5381
66 var i: i64 = 0
67 while i < n { h = h * 33 + (b[i] as i64); i = i + 1 }
68 if h < 0 { h = 0 - h }
69 return h & 0xFFFFFFFFFFFF
70}
71func cs_find(b: *u8, n: i64, pat: *u8) -> i64 {
72 var pl: i64 = 0
73 while pat[pl] != (0 as u8) { pl = pl + 1 }
74 var i: i64 = 0
75 while i + pl <= n {
76 var j: i64 = 0
77 var ok: i64 = 1
78 while j < pl { if b[i+j] != pat[j] { ok = 0; j = pl } else { j = j + 1 } }
79 if ok == 1 { return i }
80 i = i + 1
81 }
82 return 0 - 1
83}
84func cs_int_at(b: *u8, n: i64, off: i64) -> i64 {
85 var i: i64 = off
86 var v: i64 = 0
87 while i < n {
88 let c: i64 = b[i] as i64
89 if c < 48 { break }
90 if c > 57 { break }
91 v = v * 10 + (c - 48)
92 i = i + 1
93 }
94 return v
95}
96
97func cs_legacy_main(argc: i64, argv: *i64) -> i64 {
98 var srcd: *u8 = "buildroot/knowledge/compare/" as *u8
99 var docd: *u8 = "sites/nishifamily/compare/" as *u8
100 if argc >= 2 { srcd = argv[1] as *u8 }
101 if argc >= 3 { docd = argv[2] as *u8 }
102
103 let lst: *u8 = sys_mmap(CS_MAGIC_8192)
104 let lp: *u8 = sys_mmap(CS_MAGIC_1024)
105 var lo: i64 = cs_cat(lp, 0, srcd)
106 lo = cs_cat(lp, lo, "regen.list" as *u8)
107 lp[lo] = 0 as u8
108 let ln: i64 = cs_read(lp, lst, CS_MAGIC_8000)
109 if ln <= 0 { w("FAIL: cannot read " as *u8); w(lp); w("\n" as *u8); sys_exit(2); return 2 }
110
111 let sbuf: *u8 = sys_mmap(CS_CAP)
112 let pbuf: *u8 = sys_mmap(CS_CAP)
113 let path: *u8 = sys_mmap(CS_MAGIC_1024)
114 var fresh: i64 = 0
115 var stale: i64 = 0
116 var unpub: i64 = 0
117 var nostamp: i64 = 0
118
119 w("=== NX-COMPARESTALE -- does each published page still match the source it came from? ===\n" as *u8)
120 var p: i64 = 0
121 while p < ln {
122 var e: i64 = p
123 while e < ln { if lst[e] == (10 as u8) { break } e = e + 1 }
124 lst[e] = 0 as u8
125 let dom: *u8 = (lst as i64 + p) as *u8
126 p = e + 1
127 if dom[0] != (0 as u8) {
128 if dom[0] != (35 as u8) {
129 // WHICH SOURCE DID THIS PAGE COME FROM? THE PAGE SAYS. WE DO NOT INFER.
130 // This organ first guessed ".matrix, else .sota". llm and lang each have BOTH, and both
131 // are published from their .sota -- so the guess hashed the wrong file and reported two
132 // perfectly current pages as STALE. AN ARTEFACT MUST DECLARE ITS OWN PROVENANCE; A
133 // CONSUMER THAT INFERS IT IS GUESSING, AND A GUESS THAT IS RIGHT 44 TIMES IN 46 STILL
134 // LIES TWICE -- and lies in the direction of a false alarm, which is the expensive one.
135 var o: i64 = cs_cat(path, 0, docd); o = cs_cat(path, o, dom); o = cs_cat(path, o, "/api.json" as *u8); path[o] = 0 as u8
136 let pn: i64 = cs_read(path, pbuf, CS_CAP)
137 if pn <= 0 {
138 unpub = unpub + 1
139 w(" UNPUBLISHED " as *u8); w(dom); w("\n" as *u8)
140 } else {
141 let sf: i64 = cs_find(pbuf, pn, "\"source_file\":\"" as *u8)
142 let ha: i64 = cs_find(pbuf, pn, "\"source_hash\":" as *u8)
143 if sf < 0 {
144 nostamp = nostamp + 1
145 w(" NOSTAMP " as *u8); w(dom); w(" (no source_file stamp -- republish once and it becomes decidable)\n" as *u8)
146 } else { if ha < 0 {
147 nostamp = nostamp + 1
148 w(" NOSTAMP " as *u8); w(dom); w(" (no source_hash stamp -- republish once)\n" as *u8)
149 } else {
150 // lift the declared basename straight out of the page
151 var bo: i64 = cs_cat(path, 0, srcd)
152 var i: i64 = sf + 15
153 while i < pn { if pbuf[i] == (34 as u8) { break } path[bo] = pbuf[i]; bo = bo + 1; i = i + 1 }
154 path[bo] = 0 as u8
155 let sn: i64 = cs_read(path, sbuf, CS_CAP)
156 if sn <= 0 {
157 // The page names a source that is not there. That is a REAL defect -- either the
158 // source was deleted out from under a live page or the stamp is wrong -- so it
159 // must fail, and it must not be silently folded into "stale".
160 stale = stale + 1
161 w(" MISSINGSRC " as *u8); w(dom); w(" page names a source that does not exist: " as *u8); w(path); w("\n" as *u8)
162 } else {
163 let want: i64 = cs_hash(sbuf, sn)
164 let got: i64 = cs_int_at(pbuf, pn, ha + 14)
165 if got == want { fresh = fresh + 1 } else {
166 stale = stale + 1
167 w(" STALE " as *u8); w(dom)
168 w(" page=" as *u8); wn(got)
169 w(" source=" as *u8); wn(want)
170 w(" src=" as *u8); w(path)
171 w("\n" as *u8)
172 }
173 }
174 } }
175 }
176 } }
177 }
178 w("COMPARESTALE fresh=" as *u8); wn(fresh)
179 w(" stale=" as *u8); wn(stale)
180 w(" unpublished=" as *u8); wn(unpub)
181 w(" nostamp=" as *u8); wn(nostamp)
182 if stale > 0 {
183 w(" verdict=STALE (a published page no longer matches its source -- rerun nishi_compare_regen)\n" as *u8)
184 sys_exit(1)
185 return 1
186 }
187 w(" verdict=GREEN (every published page matches the source it was generated from)\n" as *u8)
188 return 0
189}
190// Consumer and dependency artifacts must be nonempty; matching empty digests are not usable artifacts.
191// One explicitly selected edge. Expected hashes are caller assertions, not qualification receipts.
192// Literal occurrence binds an observation to consumer bytes; it cannot prove runtime reachability.
193import "nx_fio.nx"
194import "nx_sha256.nx"
195import "json_emit.nx"
196
197// Current Nishi ABI: each declared pointer/i64 field occupies one8-byte slot.
198const CD_ABI_WORD_BYTES: i64=8
199const CD_REGION_BYTES: i64=9*CD_ABI_WORD_BYTES
200const CD_FILE_BYTES: i64=6*CD_ABI_WORD_BYTES
201const CD_WRITER_BYTES: i64=5*CD_ABI_WORD_BYTES
202const CD_HASH_CHARS: i64=64
203const CD_I64_MAX: i64=9223372036854775807
204const CD_SCHEMA_FIELDS: i64=32
205const CD_JSON_INTEGER_CHARS: i64=21
206const CD_JSON_ESCAPE_EXPANSION: i64=6
207struct CsDependencyFile {
208 code: i64, bytes: i64, read_bytes: i64, reference_found: i64,
209 hash: *u8, stage: *u8,
210}
211func cd_len(s: *u8) -> i64 { var n: i64=0;while s[n]!=(0 as u8){n=n+1};return n }
212func cd_equal(a: *u8,b: *u8) -> i64 { var i: i64=0;while a[i]==b[i]{if a[i]==(0 as u8){return 1};i=i+1};return 0 }
213func cd_hash_valid(s: *u8) -> i64 {
214 if cd_len(s)!=CD_HASH_CHARS {return 0};var i: i64=0
215 while i<CD_HASH_CHARS {let c: i64=s[i] as i64;if (c<48||c>57)&&(c<97||c>102){return 0};i=i+1};return 1
216}
217func cd_file_init(o: *CsDependencyFile,hex: *u8) -> i64 {
218 o.code=0;o.bytes=0-1;o.read_bytes=0;o.reference_found=0;o.hash=hex;o.hash[0]=0;o.stage="not-observed";return 0
219}
220// KMP keeps literal matching linear and preserves partial matches across transport windows.
221func cd_prefix(token: *u8,n: i64,pi: *i64) -> i64 {
222 if n==0{return 0};pi[0]=0;var i: i64=1;var j: i64=0
223 while i<n {while j>0&&token[i]!=token[j]{j=pi[j-1]};if token[i]==token[j]{j=j+1};pi[i]=j;i=i+1};return 0
224}
225func cd_scan(path: *u8,token: *u8,o: *CsDependencyFile) -> i64 {
226 cd_file_init(o,o.hash)
227 let wn: i64=sha256_workspace_bytes();let tn: i64=cd_len(token)
228 if tn>(CD_I64_MAX-8)/8 {o.code=0-22;o.stage="reference-length";return o.code}
229 let ws: *u8=sys_mmap_shared(wn);let buffer: *u8=sys_mmap_shared(wn)
230 let region: *NxFileReadRegion=sys_mmap_shared(CD_REGION_BYTES) as *NxFileReadRegion
231 let pi: *i64=sys_mmap_shared((tn+1)*8) as *i64
232 if (ws as i64)<=0||(buffer as i64)<=0||(region as i64)<=0||(pi as i64)<=0 {
233 if (ws as i64)>0{sys_munmap(ws,wn)};if (buffer as i64)>0{sys_munmap(buffer,wn)}
234 if (region as i64)>0{sys_munmap(region as *u8,CD_REGION_BYTES)};if (pi as i64)>0{sys_munmap(pi as *u8,(tn+1)*8)}
235 o.code=0-12;o.stage="allocation";return o.code
236 }
237 fio_region_init(region);var rc: i64=sha256_init_workspace(ws,wn)
238 if rc==0 {rc=fio_region_open(path,region)} else {o.stage="hash-workspace"}
239 if rc==0 {
240 o.bytes=region.total
241 if region.total>CD_I64_MAX/SHA256_BITS_PER_BYTE {rc=0-75;o.stage="hash-bit-length-overflow"}
242 }
243 cd_prefix(token,tn,pi);var matched: i64=0
244 while rc==0 {
245 let got: i64=fio_region_next(region,buffer,wn)
246 if got<0{rc=got;break};if got==0{break}
247 sha256_update(ws as *Sha256,buffer,got)
248 var i: i64=0
249 while i<got&&tn>0 {
250 while matched>0&&buffer[i]!=token[matched]{matched=pi[matched-1]}
251 if buffer[i]==token[matched]{matched=matched+1}
252 if matched==tn{o.reference_found=1;matched=pi[matched-1]};i=i+1
253 }
254 }
255 if rc==0 {
256 // Digest output reuses the transport workspace after the last checked read.
257 sha256_final(ws as *Sha256,buffer);let digits: *u8="0123456789abcdef";var i: i64=0
258 while i<SHA256_DIGEST_BYTES {let c: i64=buffer[i] as i64;o.hash[i*2]=digits[c/16];o.hash[i*2+1]=digits[c%16];i=i+1};o.hash[CD_HASH_CHARS]=0
259 }
260 fio_region_close(region);o.read_bytes=region.read_bytes
261 if rc==0&®ion.code!=0{rc=region.code}
262 if cd_equal(o.stage,"not-observed")==1{o.stage=region.stage}
263 var released: i64=0
264 if sys_munmap(pi as *u8,(tn+1)*8)!=0{released=1};if sys_munmap(region as *u8,CD_REGION_BYTES)!=0{released=1}
265 if sys_munmap(buffer,wn)!=0{released=1};if sys_munmap(ws,wn)!=0{released=1}
266 if rc==0&&released==1{rc=0-5;o.stage="workspace-release"}
267 o.code=rc;return rc
268}
269func cd_file_state(o: *CsDependencyFile,expected: *u8) -> *u8 {
270 if o.code==(0-2)&&cd_equal(o.stage,"open")==1{return "MISSING"}
271 if o.code!=0{return "UNOBSERVABLE"}
272 if o.bytes==0{return "EMPTY"}
273 if cd_equal(o.hash,expected)==0{return "IDENTITY_MISMATCH"}
274 return "IDENTITY_MATCH"
275}
276func cd_pair(j: *JsonWriter,key: *u8,value: *u8) -> i64 {
277 if json_emit_key(j,key,cd_len(key))<0{return 0-1};return json_emit_string(j,value,cd_len(value))
278}
279func cd_num(j: *JsonWriter,key: *u8,value: i64) -> i64 {
280 if json_emit_key(j,key,cd_len(key))<0{return 0-1};return json_emit_int(j,value)
281}
282func cd_file_json(j: *JsonWriter,key: *u8,path: *u8,expected: *u8,o: *CsDependencyFile) -> i64 {
283 var rc: i64=json_emit_key(j,key,cd_len(key));rc=rc|json_begin_object(j)
284 rc=rc|cd_pair(j,"path",path);rc=rc|cd_pair(j,"expected_sha256_caller_assertion",expected)
285 rc=rc|cd_pair(j,"observed_sha256",o.hash);rc=rc|cd_pair(j,"state",cd_file_state(o,expected))
286 rc=rc|cd_num(j,"bytes",o.bytes);rc=rc|cd_num(j,"read_bytes",o.read_bytes)
287 rc=rc|cd_pair(j,"io_stage",o.stage);rc=rc|cd_num(j,"io_code",o.code)
288 return rc|json_end_object(j)
289}
290func cd_report(cp: *u8,ch: *u8,dp: *u8,dh: *u8,token: *u8,c: *CsDependencyFile,d: *CsDependencyFile,buffer: *u8,capacity: i64,j: *JsonWriter,prior: *u8) -> i64 {
291 j.buf=buffer;j.pos=0;j.cap=capacity;j.depth=0;j.prior=prior
292 var i: i64=0;while i<JE_MAX_DEPTH{prior[i]=0;i=i+1}
293 var matched: i64=0
294 if cd_equal(cd_file_state(c,ch),"IDENTITY_MATCH")==1&&cd_equal(cd_file_state(d,dh),"IDENTITY_MATCH")==1&&c.reference_found==1{matched=1}
295 var rc: i64=json_begin_object(j)
296 rc=rc|cd_pair(j,"schema","estate-dependency-observation/v1")
297 rc=rc|cd_pair(j,"overall_state","PARTIAL_OBSERVATION")
298 rc=rc|cd_pair(j,"artifact_contract","consumer and dependency must be nonempty")
299 rc=rc|cd_pair(j,"scope","one caller-selected edge; literal presence is not runtime reachability or complete dependency coverage")
300 rc=rc|cd_num(j,"observed_unix",sys_now_realtime_sec())
301 rc=rc|cd_pair(j,"expected_identity_authority","caller assertion; qualified receipt linkage unknown")
302 rc=rc|cd_pair(j,"reference_token",token);rc=rc|cd_num(j,"literal_reference_present",c.reference_found)
303 rc=rc|cd_file_json(j,"consumer",cp,ch,c);rc=rc|cd_file_json(j,"dependency",dp,dh,d)
304 rc=rc|cd_num(j,"eligible_selected_edges",1);rc=rc|cd_num(j,"identity_and_literal_matches",matched)
305 var unknown: i64=0;if cd_equal(cd_file_state(c,ch),"UNOBSERVABLE")==1||cd_equal(cd_file_state(d,dh),"UNOBSERVABLE")==1{unknown=1}
306 rc=rc|cd_num(j,"unobservable_selected_edges",unknown)
307 rc=rc|cd_num(j,"observed_selected_edge_failures",1-matched-unknown)
308 rc=rc|cd_pair(j,"source_build_qualification","UNKNOWN")
309 rc=rc|cd_pair(j,"runtime_compatibility","UNKNOWN")
310 rc=rc|cd_pair(j,"configured_selection","UNKNOWN")
311 rc=rc|cd_pair(j,"executed_identity","UNKNOWN")
312 rc=rc|cd_pair(j,"served_identity","UNKNOWN")
313 rc=rc|cd_pair(j,"complete_dependency_coverage","UNKNOWN")
314 rc=rc|cd_pair(j,"freshness_policy","none applied; no timestamp-derived desired version")
315 rc=rc|cd_pair(j,"next_action","resolve named identity/I/O failures; join qualified receipt and observe actual consumer execution and served dependency")
316 rc=rc|json_end_object(j);if rc<0{return 0-1};return j.pos
317}
318func cd_command(argc: i64,argv: *i64) -> i64 {
319 if argc!=7{w("{\"error\":\"usage: nx_comparestale dependency-check consumer-file expected-consumer-sha256 dependency-file expected-dependency-sha256 literal-reference\"}\n");return 2}
320 let cp: *u8=argv[2] as *u8;let ch: *u8=argv[3] as *u8;let dp: *u8=argv[4] as *u8;let dh: *u8=argv[5] as *u8;let token: *u8=argv[6] as *u8
321 if cd_hash_valid(ch)==0||cd_hash_valid(dh)==0||cd_len(token)==0||cd_len(cp)==0||cd_len(dp)==0{w("{\"error\":\"invalid dependency boundary\"}\n");return 2}
322 let total: i64=cd_len(cp)+cd_len(dp)+cd_len(token)
323 if total>(CD_I64_MAX/2){return 2}
324 // Worst-case escaped input plus fixed schema field/key/value envelope, not a file-size ceiling.
325 let fixed: i64=CD_SCHEMA_FIELDS*(CD_HASH_CHARS+CD_JSON_INTEGER_CHARS)*CD_JSON_ESCAPE_EXPANSION
326 if total>(CD_I64_MAX-fixed)/CD_JSON_ESCAPE_EXPANSION{return 2}
327 let cap: i64=total*CD_JSON_ESCAPE_EXPANSION+fixed
328 let buffer: *u8=sys_mmap_shared(cap);let j: *JsonWriter=sys_mmap_shared(CD_WRITER_BYTES) as *JsonWriter
329 let prior: *u8=sys_mmap_shared(JE_MAX_DEPTH);let c: *CsDependencyFile=sys_mmap_shared(CD_FILE_BYTES) as *CsDependencyFile
330 let d: *CsDependencyFile=sys_mmap_shared(CD_FILE_BYTES) as *CsDependencyFile
331 let hashes: *u8=sys_mmap_shared((CD_HASH_CHARS+1)*2)
332 var exitcode: i64=4
333 if (buffer as i64)>0&&(j as i64)>0&&(prior as i64)>0&&(c as i64)>0&&(d as i64)>0&&(hashes as i64)>0 {
334 cd_file_init(c,hashes);cd_file_init(d,hashes+CD_HASH_CHARS+1)
335 cd_scan(cp,token,c);cd_scan(dp,"",d)
336 let n: i64=cd_report(cp,ch,dp,dh,token,c,d,buffer,cap,j,prior)
337 if n>=0 {
338 var wrote: i64=0;while wrote<n{let rc: i64=sys_write(1,buffer+wrote,n-wrote);if rc<=0{break};wrote=wrote+rc}
339 if wrote==n{if sys_write(1,"\n",1)==1{
340 exitcode=0
341 if cd_equal(cd_file_state(c,ch),"UNOBSERVABLE")==1||cd_equal(cd_file_state(d,dh),"UNOBSERVABLE")==1{exitcode=4}else{if cd_equal(cd_file_state(c,ch),"IDENTITY_MATCH")==0||cd_equal(cd_file_state(d,dh),"IDENTITY_MATCH")==0||c.reference_found==0{exitcode=1}}
342 }}
343 }
344 } else {w("{\"error\":\"dependency observation allocation failed\"}\n")}
345 if (hashes as i64)>0{if sys_munmap(hashes,(CD_HASH_CHARS+1)*2)!=0{exitcode=4}}
346 if (d as i64)>0{if sys_munmap(d as *u8,CD_FILE_BYTES)!=0{exitcode=4}}
347 if (c as i64)>0{if sys_munmap(c as *u8,CD_FILE_BYTES)!=0{exitcode=4}}
348 if (prior as i64)>0{if sys_munmap(prior,JE_MAX_DEPTH)!=0{exitcode=4}}
349 if (j as i64)>0{if sys_munmap(j as *u8,CD_WRITER_BYTES)!=0{exitcode=4}}
350 if (buffer as i64)>0{if sys_munmap(buffer,cap)!=0{exitcode=4}}
351 return exitcode
352}