nx_paper_audit.nx source
↩ module page · 204 lines · 7519 B
1// nx_paper_audit.nx -- RESOLVE every citation in a manuscript against its ledger (AS-3).
2//
3// ★WHY THIS EXISTS -- A MEASURED BLIND SPOT IN OUR OWN RULER, NOT A SUSPECTED ONE.
4// nx_paperbench COUNTS provenance tokens; it never checks that a token RESOLVES, nor that
5// the number printed next to it MATCHES the ledger row it names. Proven with a fixture
6// (knowledge/papers/rt004_negcontrol_liar.md): a manuscript citing "999 [ev:E02]" when the
7// ledger says 254, and "[ev:E99]" which does not exist at all, scored composite 750 and
8// publishable=1. A well-formed token is indistinguishable from a true one to a counter.
9//
10// This organ closes that hole by a DIFFERENT METHOD: it resolves. For each "[ev:ID]" it
11// 1. looks ID up in the ledger -> absent = DANGLING
12// 2. reads the digit run immediately before -> differs = CONTRADICTED
13// 3. no adjacent number -> UNANCHORED (reported, not an error:
14// a citation may legitimately support a sentence rather than a figure)
15//
16// ★It audits the ARTIFACT, not the process. nx_paper_forge guarantees correctness at EMIT
17// time; this checks the file as it now stands, so a hand-edited, post-processed, or
18// forge-bugged manuscript is still caught. That independence is the whole point.
19//
20// DRY: ledger lookup is nx_paper_forge's pf_lookup and marker scanning is nx_paperbench's
21// pb_find -- reused, not reimplemented (rule 15). Imports are transitive through the forge.
22//
23// Integer, deterministic. No hw writes (Rule 26). license_tier: ORIGINAL
24// module: nishi-core.research.paper_audit
25// depends: nx_paper_forge.nx (-> nx_paperbench.nx -> nx_syscalls.nx)
26// genealogy_id: paperbench_token_count_blindspot + forge_ledger_lookup
27import "nx_paper_forge.nx"
28
29// The digit run immediately preceding position p, skipping intervening spaces.
30// out[0],out[1] = [start,end). Returns 1 if found, 0 if the token is UNANCHORED.
31func pa_prev_num(buf: *u8, p: i64, out: *i64) -> i64 {
32 var i: i64 = p - 1
33 var s: i64 = 1
34 while s == 1 {
35 if i < 0 { s = 0 }
36 else { if buf[i] == (32 as u8) { i = i - 1 } else { s = 0 } }
37 }
38 if i < 0 { return 0 }
39 let c: i64 = buf[i] as i64
40 if c < 48 { return 0 }
41 if c > 57 { return 0 }
42 let e: i64 = i + 1
43 var s2: i64 = 1
44 while s2 == 1 {
45 if i < 0 { s2 = 0 }
46 else {
47 let d: i64 = buf[i] as i64
48 if d >= 48 {
49 if d <= 57 { i = i - 1 } else { s2 = 0 }
50 } else { s2 = 0 }
51 }
52 }
53 out[0] = i + 1
54 out[1] = e
55 return 1
56}
57
58// byte-equality of a[a0,a1) and b[b0,b1)
59func pa_range_eq(a: *u8, ar: *i64, b: *u8, br: *i64) -> i64 {
60 let la: i64 = ar[1] - ar[0]
61 let lb: i64 = br[1] - br[0]
62 if la != lb { return 0 }
63 var k: i64 = 0
64 var same: i64 = 1
65 while k < la {
66 if a[ar[0]+k] != b[br[0]+k] { same = 0; k = la } else { k = k + 1 }
67 }
68 return same
69}
70
71// THE AUDIT. ledp[0]=ledger buffer as i64, ledp[1]=ledger length.
72// out[0]=tokens out[1]=ok out[2]=dangling out[3]=contradicted out[4]=unanchored
73func pa_audit(doc: *u8, dn: i64, ledp: *i64, out: *i64) -> i64 {
74 let led: *u8 = ledp[0] as *u8
75 let ln: i64 = ledp[1]
76 let idbuf: *u8 = sys_mmap(256)
77 let vr: *i64 = sys_mmap(2 * 8) as *i64
78 let nr: *i64 = sys_mmap(2 * 8) as *i64
79 var t: i64 = 0
80 var ok: i64 = 0
81 var dang: i64 = 0
82 var contra: i64 = 0
83 var unanch: i64 = 0
84 var i: i64 = 0
85 while i + 4 <= dn {
86 var hit: i64 = 0
87 if doc[i] == (91 as u8) {
88 if doc[i+1] == (101 as u8) {
89 if doc[i+2] == (118 as u8) {
90 if doc[i+3] == (58 as u8) { hit = 1 }
91 }
92 }
93 }
94 if hit == 1 {
95 t = t + 1
96 var j: i64 = i + 4
97 var idn: i64 = 0
98 var s: i64 = 1
99 while s == 1 {
100 if j >= dn { s = 0 }
101 else {
102 if doc[j] == (93 as u8) { s = 0 }
103 else { if idn < 250 { idbuf[idn] = doc[j]; idn = idn + 1 } j = j + 1 }
104 }
105 }
106 idbuf[idn] = 0 as u8
107 if pf_lookup(led, ln, idbuf, idn, vr) == 0 {
108 dang = dang + 1
109 } else {
110 if pa_prev_num(doc, i, nr) == 0 {
111 unanch = unanch + 1
112 } else {
113 if pa_range_eq(doc, nr, led, vr) == 1 { ok = ok + 1 } else { contra = contra + 1 }
114 }
115 }
116 i = j + 1
117 } else { i = i + 1 }
118 }
119 out[0] = t; out[1] = ok; out[2] = dang; out[3] = contra; out[4] = unanch
120 return 0
121}
122
123// ---------------------------------------------- ledger <-> SOURCE (the last unverified link)
124//
125// ★THE HOLE THIS CLOSES. The chain is manuscript -> ledger -> durable log. pa_audit proves
126// manuscript<->ledger. NOTHING proved ledger<->log: a mistyped or invented ledger value would
127// pass every gate in this lane, because every check downstream trusts the ledger as ground
128// truth. A provenance chain is only as strong as its weakest unchecked link, and this was it.
129// (Adjacent but distinct: nx_research_verify checks claims by FETCHING a remote URL; this
130// checks a measurement value against the local log the ledger itself names.)
131//
132// number of rows in the ledger
133func pa_rows(led: *u8, ln: i64) -> i64 {
134 var i: i64 = 0
135 var r: i64 = 0
136 while i < ln {
137 let le: i64 = pf_eol(led, ln, i)
138 if le > i { r = r + 1 }
139 i = le + 1
140 }
141 return r
142}
143
144// parse ledger row `idx` into out: [0,1]=id range, [2,3]=value range, [4,5]=source range.
145// Returns 1 on success, 0 if idx is past the end.
146func pa_row(led: *u8, ln: i64, idx: i64, out: *i64) -> i64 {
147 var i: i64 = 0
148 var r: i64 = 0
149 while i < ln {
150 let le: i64 = pf_eol(led, ln, i)
151 if le > i {
152 if r == idx {
153 var f: i64 = 0
154 var p: i64 = i
155 while f < 3 {
156 var q: i64 = p
157 var s: i64 = 1
158 while s == 1 {
159 if q >= le { s = 0 } else { if led[q] == (9 as u8) { s = 0 } else { q = q + 1 } }
160 }
161 out[f*2] = p
162 out[f*2+1] = q
163 p = q + 1
164 f = f + 1
165 }
166 return 1
167 }
168 r = r + 1
169 }
170 i = le + 1
171 }
172 return 0
173}
174
175// is the byte range ndl[nr[0]..nr[1]) present anywhere in hay[0..hn)?
176func pa_contains(hay: *u8, hn: i64, ndl: *u8, nr: *i64) -> i64 {
177 let l: i64 = nr[1] - nr[0]
178 if l <= 0 { return 0 }
179 var i: i64 = 0
180 while i + l <= hn {
181 var k: i64 = 0
182 var hit: i64 = 1
183 while k < l { if hay[i+k] != ndl[nr[0]+k] { hit = 0; k = l } else { k = k + 1 } }
184 if hit == 1 { return 1 }
185 i = i + 1
186 }
187 return 0
188}
189
190// integrity in permille = anchored citations that RESOLVE AND MATCH, over all citations.
191// A manuscript with no citations scores 0: unverifiable is not the same as verified, and
192// the ruler must never reward the absence of evidence.
193func pa_integrity(out: *i64) -> i64 {
194 if out[0] <= 0 { return 0 }
195 return (out[1] * 1000) / out[0]
196}
197
198// a manuscript is CLEAN only if nothing dangles and nothing contradicts the ledger.
199func pa_clean(out: *i64) -> i64 {
200 if out[2] > 0 { return 0 }
201 if out[3] > 0 { return 0 }
202 if out[0] <= 0 { return 0 }
203 return 1
204}