code wiki / _hdl_build / _wiki_archive_gate.nx
_wiki_archive_gate.nx source
↩ module page · 253 lines · 12441 B
1// _wiki_archive_gate.nx -- THE NO-LINK-ROT GATE.
2//
3// Re-proves, from a REAL run (no fabricated GREEN), that the sovereign wiki
4// archive makes link rot structurally impossible via content-addressing:
5//
6// by_cid_ok archive pages -> each retrievable by its CID, and the
7// readback bytes are BYTE-EQUAL to the original
8// idempotent_ok re-archive identical bytes -> SAME CID, and the store does
9// not grow a NEW distinguishable blob (content-address dedup)
10// rot_clean rot audit over a clean inter-linked set -> rot == 0
11// rot_detected LIAR-KILL: a page with [[ZzqNonexistentTarget]] -> rot >= 1
12// (a real broken internal link MUST be caught, else RED)
13// noloss_ok archive a page, DROP its slug pointer (tombstone), retrieve
14// by CID -> STILL returns the exact bytes (nothing is lost)
15//
16// EXCEED vs Wikipedia: Wikipedia depends on the Internet Archive to paper over
17// rot after links die. Here a CID is derived from the content, so a CID link
18// resolves to the SAME bytes forever -- rot is a non-event by construction.
19//
20// Determinism: each run uses a FRESH store prefix keyed by the wall-clock epoch
21// so the gate starts from an empty archive (idempotent re-run, Rule 10). The
22// PRODUCTION archive prefix is knowledge/store/wikiarchive- (in nx_wiki_archive).
23//
24// Verdict line (judged by this marker, to stdout + knowledge/status):
25// WIKIARCHIVE archived=<n> by_cid_ok=<0|1> idempotent_ok=<0|1>
26// rot_clean=<0|1> rot_detected=<0|1> noloss_ok=<0|1> verdict=GREEN|RED
27//
28// Pure NishiLang, NO SQL, NO .sh/.py/.js, no new .tsv/.conf. nx_sites_daemon
29// UNTOUCHED. Imports the proven trio + the archive module under test.
30// license_tier: ORIGINAL
31import "nx_syscalls.nx"
32import "nx_canon_cid.nx"
33import "nx_seg_store.nx"
34import "nx_wiki_archive.nx"
35
36const WAG_LOG: *u8 = "knowledge/status/wiki_archive_gate.log"
37const WAG_CAP: i64 = 64 // manifest segment-scan cap (>> our few commits)
38
39// ---- io helpers ----
40func wag_w(fd: i64, s: *u8) -> i64 {
41 var n: i64 = 0
42 while s[n] != (0 as u8) { n = n + 1 }
43 sys_write(fd, s, n)
44 return 0
45}
46func wag_n(fd: i64, v: i64) -> i64 {
47 let bb: *u8 = sys_mmap(28)
48 var m: i64 = v
49 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
50 let t: *u8 = sys_mmap(28)
51 var k: i64 = 0
52 if m == 0 { t[0] = 48 as u8; k = 1 }
53 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
54 var i: i64 = 0
55 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
56 sys_write(fd, bb, k)
57 return 0
58}
59// write to BOTH stdout (gate evidence) and the durable log
60func wag_w2(lfd: i64, s: *u8) -> i64 { wag_w(1, s); if lfd >= 0 { wag_w(lfd, s) } return 0 }
61func wag_n2(lfd: i64, v: i64) -> i64 { wag_n(1, v); if lfd >= 0 { wag_n(lfd, v) } return 0 }
62
63func wag_p(s: *u8) -> i64 { wag_w(1, s); return 0 }
64func wag_pn(v: i64) -> i64 { wag_n(1, v); return 0 }
65
66func wag_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
67
68// concat NUL-terminated s into dst at off; returns new off
69func wag_cat(dst: *u8, off: i64, s: *u8) -> i64 {
70 var i: i64 = 0
71 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
72 return off + i
73}
74// append decimal of v into dst at off; returns new off
75func wag_catn(dst: *u8, off: i64, v: i64) -> i64 {
76 var m: i64 = v
77 var o: i64 = off
78 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m }
79 let t: *u8 = sys_mmap(28)
80 var k: i64 = 0
81 if m == 0 { t[0] = 48 as u8; k = 1 }
82 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
83 var i: i64 = 0
84 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
85 return o + k
86}
87
88// byte-equal compare of two buffers of equal length n
89func wag_bytes_eq(a: *u8, b: *u8, n: i64) -> i64 {
90 var i: i64 = 0
91 var eq: i64 = 1
92 while i < n {
93 if a[i] != b[i] { eq = 0 }
94 i = i + 1
95 }
96 return eq
97}
98
99func main() -> i64 {
100 wag_p("WIKIARCHIVE-GATE: start (no-link-rot, content-addressed archive)\n" as *u8)
101
102 // ---- FRESH per-run store prefix: knowledge/store/wikiarchive-gate-<epoch>- ----
103 // (empty archive each run => deterministic; idempotent re-run.)
104 let prefix: *u8 = sys_mmap(256)
105 var po: i64 = 0
106 po = wag_cat(prefix, po, "knowledge/store/wikiarchive-gate-" as *u8)
107 po = wag_catn(prefix, po, sys_now_realtime_sec())
108 po = wag_cat(prefix, po, "-" as *u8)
109 prefix[po] = 0 as u8
110 wag_p(" store prefix = " as *u8); wag_p(prefix); wag_p("\n" as *u8)
111
112 // ===== STEP 1: seed a small inter-linked corpus and ARCHIVE it =====
113 // Three clean pages whose [[..]] links all point at archived slugs.
114 let s_alpha: *u8 = "alpha" as *u8
115 let s_beta: *u8 = "beta" as *u8
116 let s_gamma: *u8 = "gamma" as *u8
117 let b_alpha: *u8 = "# Alpha\nSee [[beta]] and [[gamma]]. Ref http://example.com/x here.\n" as *u8
118 let b_beta: *u8 = "# Beta\nBack to [[alpha]]. Also https://nishi.example/y external.\n" as *u8
119 let b_gamma: *u8 = "# Gamma\nLinks to [[alpha]] and [[beta]] only.\n" as *u8
120
121 let w: *i64 = ss_begin()
122 let cid_a: *u8 = sys_mmap(80)
123 let cid_b: *u8 = sys_mmap(80)
124 let cid_g: *u8 = sys_mmap(80)
125 let ra: i64 = war_archive_page(w, s_alpha, b_alpha, wag_slen(b_alpha), cid_a)
126 let rb: i64 = war_archive_page(w, s_beta, b_beta, wag_slen(b_beta), cid_b)
127 let rg: i64 = war_archive_page(w, s_gamma, b_gamma, wag_slen(b_gamma), cid_g)
128 let c1: i64 = ss_commit(prefix, w, 0)
129 var archived: i64 = 0
130 if ra == 0 { archived = archived + 1 }
131 if rb == 0 { archived = archived + 1 }
132 if rg == 0 { archived = archived + 1 }
133 wag_p(" archived pages = " as *u8); wag_pn(archived); wag_p(" (commit rc=" as *u8); wag_pn(c1); wag_p(")\n" as *u8)
134 wag_p(" cid(alpha) = " as *u8); wag_p(cid_a); wag_p("\n" as *u8)
135
136 // ===== STEP 2: BY-CID retrieval + BYTE-EQUALITY (ground truth) =====
137 // Read each page back by its CID; the bytes MUST equal the originals.
138 var by_cid_ok: i64 = 1
139 let pa: *i64 = sys_mmap(16) as *i64
140 let pb: *i64 = sys_mmap(16) as *i64
141 let pg: *i64 = sys_mmap(16) as *i64
142 let la: i64 = war_get_by_cid(prefix, cid_a, pa, WAG_CAP)
143 let lb: i64 = war_get_by_cid(prefix, cid_b, pb, WAG_CAP)
144 let lg: i64 = war_get_by_cid(prefix, cid_g, pg, WAG_CAP)
145 if la != wag_slen(b_alpha) { by_cid_ok = 0 }
146 if lb != wag_slen(b_beta) { by_cid_ok = 0 }
147 if lg != wag_slen(b_gamma) { by_cid_ok = 0 }
148 if la == wag_slen(b_alpha) { if wag_bytes_eq(pa[0] as *u8, b_alpha, la) != 1 { by_cid_ok = 0 } }
149 if lb == wag_slen(b_beta) { if wag_bytes_eq(pb[0] as *u8, b_beta, lb) != 1 { by_cid_ok = 0 } }
150 if lg == wag_slen(b_gamma) { if wag_bytes_eq(pg[0] as *u8, b_gamma, lg) != 1 { by_cid_ok = 0 } }
151 wag_p(" by_cid_ok = " as *u8); wag_pn(by_cid_ok); wag_p(" (lens " as *u8); wag_pn(la); wag_p("/" as *u8); wag_pn(lb); wag_p("/" as *u8); wag_pn(lg); wag_p(")\n" as *u8)
152
153 // ===== STEP 3: IDEMPOTENT re-archive (content-address dedup) =====
154 // Re-archive alpha's identical bytes -> SAME cid; readback still byte-equal;
155 // a DIFFERENT body for the same slug -> DIFFERENT cid (content drives the key).
156 var idempotent_ok: i64 = 1
157 let w2: *i64 = ss_begin()
158 let cid_a2: *u8 = sys_mmap(80)
159 war_archive_page(w2, s_alpha, b_alpha, wag_slen(b_alpha), cid_a2)
160 let c2: i64 = ss_commit(prefix, w2, 1)
161 if c2 != 0 { idempotent_ok = 0 }
162 // same bytes -> identical cid string
163 if wag_bytes_eq(cid_a, cid_a2, 69) != 1 { idempotent_ok = 0 }
164 // and the blob is still retrievable + byte-equal (no shadow/duplication broke it)
165 let pa2: *i64 = sys_mmap(16) as *i64
166 let la2: i64 = war_get_by_cid(prefix, cid_a2, pa2, WAG_CAP)
167 if la2 != wag_slen(b_alpha) { idempotent_ok = 0 }
168 if la2 == wag_slen(b_alpha) { if wag_bytes_eq(pa2[0] as *u8, b_alpha, la2) != 1 { idempotent_ok = 0 } }
169 // content-address PROOF: different content -> different cid
170 let cid_diff: *u8 = sys_mmap(80)
171 let b_diff: *u8 = "# Alpha (edited)\nDifferent bytes entirely.\n" as *u8
172 war_cid_of_body(b_diff, wag_slen(b_diff), cid_diff)
173 if wag_bytes_eq(cid_a, cid_diff, 69) == 1 { idempotent_ok = 0 } // MUST differ
174 wag_p(" idempotent_ok = " as *u8); wag_pn(idempotent_ok); wag_p("\n" as *u8)
175
176 // ===== STEP 4: ROT AUDIT -- clean set (rot==0) =====
177 // All three clean pages link only to archived slugs => zero rot. External
178 // http(s) links are COUNTED as external_pending, never as rot.
179 let oa: *i64 = sys_mmap(8 * 8) as *i64
180 let brok: *u8 = sys_mmap(128)
181 var rot_total: i64 = 0
182 var ext_total: i64 = 0
183 nx_wiki_rot_audit_page(prefix, b_alpha, wag_slen(b_alpha), WAG_CAP, oa, brok)
184 rot_total = rot_total + oa[1]; ext_total = ext_total + oa[2]
185 nx_wiki_rot_audit_page(prefix, b_beta, wag_slen(b_beta), WAG_CAP, oa, brok)
186 rot_total = rot_total + oa[1]; ext_total = ext_total + oa[2]
187 nx_wiki_rot_audit_page(prefix, b_gamma, wag_slen(b_gamma), WAG_CAP, oa, brok)
188 rot_total = rot_total + oa[1]; ext_total = ext_total + oa[2]
189 var rot_clean: i64 = 0
190 if rot_total == 0 { rot_clean = 1 }
191 wag_p(" rot_clean = " as *u8); wag_pn(rot_clean); wag_p(" (rot=" as *u8); wag_pn(rot_total); wag_p(" external_pending=" as *u8); wag_pn(ext_total); wag_p(")\n" as *u8)
192
193 // ===== STEP 5: NEGATIVE CONTROL / LIAR-KILL -- a real broken link =====
194 // A page that links to a slug NEVER archived MUST report rot>=1; if the
195 // audit fails to catch it, the gate goes RED (no false-green).
196 let b_bad: *u8 = "# Broken\nThis points at [[ZzqNonexistentTarget]] which was never archived.\n" as *u8
197 let ob: *i64 = sys_mmap(8 * 8) as *i64
198 let brok2: *u8 = sys_mmap(128)
199 nx_wiki_rot_audit_page(prefix, b_bad, wag_slen(b_bad), WAG_CAP, ob, brok2)
200 var rot_detected: i64 = 0
201 if ob[1] >= 1 { rot_detected = 1 }
202 wag_p(" rot_detected = " as *u8); wag_pn(rot_detected); wag_p(" (broken_rot=" as *u8); wag_pn(ob[1]); wag_p(" first_broken=" as *u8); wag_p(brok2); wag_p(")\n" as *u8)
203
204 // ===== STEP 6: NO-LOSS -- drop the slug pointer, retrieve by CID =====
205 // Tombstone wikicid:<gamma> (slug pointer GONE) then prove the blob is STILL
206 // retrievable by its CID -- the content is never lost, only the name pointer.
207 var noloss_ok: i64 = 1
208 let w3: *i64 = ss_begin()
209 let ck_g: *u8 = sys_mmap(128)
210 var cko: i64 = 0
211 cko = wag_cat(ck_g, cko, "wikicid:" as *u8)
212 cko = wag_cat(ck_g, cko, s_gamma)
213 ck_g[cko] = 0 as u8
214 ss_add(w3, 2, ck_g, "" as *u8, 0) // kind 2 = tombstone the pointer
215 let c3: i64 = ss_commit(prefix, w3, 2)
216 if c3 != 0 { noloss_ok = 0 }
217 // slug pointer must now read as GONE...
218 let cid_chk: *u8 = sys_mmap(80)
219 let slug_now: i64 = war_cid_of_slug(prefix, s_gamma, cid_chk, WAG_CAP)
220 if slug_now != (0 - 1) { noloss_ok = 0 } // expect -1 (dropped)
221 // ...yet the CONTENT is still retrievable by its CID, byte-equal.
222 let pg2: *i64 = sys_mmap(16) as *i64
223 let lg2: i64 = war_get_by_cid(prefix, cid_g, pg2, WAG_CAP)
224 if lg2 != wag_slen(b_gamma) { noloss_ok = 0 }
225 if lg2 == wag_slen(b_gamma) { if wag_bytes_eq(pg2[0] as *u8, b_gamma, lg2) != 1 { noloss_ok = 0 } }
226 wag_p(" noloss_ok = " as *u8); wag_pn(noloss_ok); wag_p(" (slug_gamma_now=" as *u8); wag_pn(slug_now); wag_p(" cid_readback_len=" as *u8); wag_pn(lg2); wag_p(")\n" as *u8)
227
228 // ===== VERDICT =====
229 var green: i64 = 1
230 if archived != 3 { green = 0 }
231 if by_cid_ok != 1 { green = 0 }
232 if idempotent_ok != 1 { green = 0 }
233 if rot_clean != 1 { green = 0 }
234 if rot_detected != 1 { green = 0 }
235 if noloss_ok != 1 { green = 0 }
236
237 let lfd: i64 = sys_openat_append(WAG_LOG, 420)
238 wag_w2(lfd, "WIKIARCHIVE archived=" as *u8); wag_n2(lfd, archived)
239 wag_w2(lfd, " by_cid_ok=" as *u8); wag_n2(lfd, by_cid_ok)
240 wag_w2(lfd, " idempotent_ok=" as *u8); wag_n2(lfd, idempotent_ok)
241 wag_w2(lfd, " rot_clean=" as *u8); wag_n2(lfd, rot_clean)
242 wag_w2(lfd, " rot_detected=" as *u8); wag_n2(lfd, rot_detected)
243 wag_w2(lfd, " noloss_ok=" as *u8); wag_n2(lfd, noloss_ok)
244 if green == 1 { wag_w2(lfd, " verdict=GREEN\n" as *u8) } else { wag_w2(lfd, " verdict=RED\n" as *u8) }
245 if lfd >= 0 { sys_close(lfd) }
246
247 if green == 1 {
248 sys_exit(0)
249 return 0
250 }
251 sys_exit(1)
252 return 1
253}