nx_paper_audit_cli.nx source
↩ module page · 172 lines · 8002 B
1// nx_paper_audit_cli.nx -- callable MCP/API surface for citation resolution (AS-3).
2//
3// nx_paper_audit <manuscript.md> <evidence.tsv>
4// resolves every [ev:ID] against the ledger and checks the adjacent number matches.
5// dangling = the row named does not exist
6// contradicted= the printed number differs from the row it names
7// unanchored = the citation supports prose, not a figure (reported, not an error)
8//
9// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 (1 if NOT clean)
10import "nx_paper_audit.nx"
11import "nx_syscalls.nx"
12const K_MAGIC_1048576: i64 = 1048576
13const K_MAGIC_1048560: i64 = 1048560
14const K_MAGIC_262144: i64 = 262144
15const K_MAGIC_262128: i64 = 262128
16const K_MAGIC_4096: i64 = 4096
17const K_MAGIC_1024: i64 = 1024
18const K_MAGIC_4000: i64 = 4000
19
20func pac_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
21func pac_q() -> i64 { let b: *u8 = sys_mmap(1); b[0] = 34 as u8; sys_write(1, b, 1); return 0 }
22func pac_key(name: *u8) -> i64 { pac_q(); pac_puts(name); pac_q(); pac_puts(":" as *u8); return 0 }
23func pac_num(v: i64) -> i64 {
24 let bb: *u8 = sys_mmap(28); var m: i64 = v
25 if m < 0 { m = 0 - m }
26 let t: *u8 = sys_mmap(28); var k: i64 = 0
27 if m == 0 { t[0] = 48 as u8; k = 1 }
28 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
29 var i: i64 = 0
30 while i < k { bb[i] = t[k-1-i]; i = i + 1 }
31 sys_write(1, bb, k); return 0
32}
33func pac_read(path: *u8, buf: *u8, cap: i64) -> i64 {
34 let fd: i64 = sys_openat_rd(path)
35 if fd < 0 { return 0 - 1 }
36 var n: i64 = 0
37 var go: i64 = 1
38 while go == 1 {
39 let base: i64 = buf as i64
40 let r: i64 = sys_read(fd, (base + n) as *u8, cap - n)
41 if r <= 0 { go = 0 } else { n = n + r }
42 if n >= cap { go = 0 }
43 }
44 sys_close(fd)
45 return n
46}
47// does s start with pfx? offset past pfx, or -1. Named optional args stay position-free
48// so adding them cannot disturb existing callers (rule 19).
49func pac_pfx(s: *u8, pfx: *u8) -> i64 {
50 var i: i64 = 0
51 while pfx[i] != (0 as u8) {
52 if s[i] != pfx[i] { return 0 - 1 }
53 i = i + 1
54 }
55 return i
56}
57func main(argc: i64, argv: *i64) -> i64 {
58 if argc < 3 {
59 pac_puts("{" as *u8); pac_key("error" as *u8); pac_q()
60 pac_puts("usage: nx_paper_audit <manuscript.md> <evidence.tsv>" as *u8)
61 pac_q(); pac_puts("}\n" as *u8); sys_exit(2); return 2
62 }
63 let doc: *u8 = sys_mmap(K_MAGIC_1048576)
64 let dn: i64 = pac_read(argv[1] as *u8, doc, K_MAGIC_1048560)
65 if dn < 0 {
66 pac_puts("{" as *u8); pac_key("error" as *u8); pac_q()
67 pac_puts("manuscript unreadable -- refusing to audit (an unreadable paper is not a dishonest one)" as *u8)
68 pac_q(); pac_puts("}\n" as *u8); sys_exit(3); return 3
69 }
70 let led: *u8 = sys_mmap(K_MAGIC_262144)
71 let ln: i64 = pac_read(argv[2] as *u8, led, K_MAGIC_262128)
72 if ln <= 0 {
73 pac_puts("{" as *u8); pac_key("error" as *u8); pac_q()
74 pac_puts("ledger unreadable -- refusing to audit (without a ledger every citation would look dangling)" as *u8)
75 pac_q(); pac_puts("}\n" as *u8); sys_exit(3); return 3
76 }
77 let ledp: *i64 = sys_mmap(2 * 8) as *i64
78 ledp[0] = led as i64
79 ledp[1] = ln
80 let out: *i64 = sys_mmap(8 * 8) as *i64
81 pa_audit(doc, dn, ledp, out)
82 let clean: i64 = pa_clean(out)
83
84 // ---- optional: verify each LEDGER value against the durable log it names ---------------
85 // This is the last link in manuscript -> ledger -> log. Off by default because it reads
86 // arbitrary source paths relative to CWD; on, it is the only thing standing between a
87 // typo'd ledger row and a published number nobody can trace.
88 var dosrc: i64 = 0
89 var ai: i64 = 3
90 while ai < argc {
91 if pac_pfx(argv[ai] as *u8, "sources=1" as *u8) >= 0 { dosrc = 1 }
92 ai = ai + 1
93 }
94 var s_rows: i64 = 0
95 var s_ok: i64 = 0
96 var s_absent: i64 = 0
97 var s_unread: i64 = 0
98 // NAME THE OFFENDERS: "3 absent" is a number; "E23,E24,E25" is a work order.
99 let absids: *u8 = sys_mmap(K_MAGIC_4096)
100 var abo: i64 = 0
101 if dosrc == 1 {
102 let rw: *i64 = sys_mmap(8 * 8) as *i64
103 let sbuf: *u8 = sys_mmap(K_MAGIC_1048576)
104 let spath: *u8 = sys_mmap(K_MAGIC_1024)
105 s_rows = pa_rows(led, ln)
106 var r: i64 = 0
107 while r < s_rows {
108 if pa_row(led, ln, r, rw) == 1 {
109 var k: i64 = 0
110 let sl: i64 = rw[5] - rw[4]
111 while k < sl { spath[k] = led[rw[4]+k]; k = k + 1 }
112 spath[sl] = 0 as u8
113 let sn: i64 = pac_read(spath, sbuf, K_MAGIC_1048560)
114 if sn <= 0 { s_unread = s_unread + 1 }
115 else {
116 let vr: *i64 = sys_mmap(2 * 8) as *i64
117 vr[0] = rw[2]; vr[1] = rw[3]
118 if pa_contains(sbuf, sn, led, vr) == 1 { s_ok = s_ok + 1 }
119 else {
120 s_absent = s_absent + 1
121 if abo > 0 { if abo < K_MAGIC_4000 { absids[abo] = 44 as u8; abo = abo + 1 } }
122 var m: i64 = rw[0]
123 while m < rw[1] { if abo < K_MAGIC_4000 { absids[abo] = led[m]; abo = abo + 1 } m = m + 1 }
124 }
125 }
126 }
127 r = r + 1
128 }
129 }
130
131 // ⚠THE HEADLINE MUST NOT HIDE A RED SUB-CHECK. First run of sources=1 reported
132 // "verdict":"CLEAN" while source_verdict said a ledger row was invented -- the manuscript
133 // matched the ledger perfectly, and the ledger was wrong. That is the same defect class as
134 // a composite masking a zero axis, and as a 200 masking the wrong page. A verdict that
135 // summarises must be at least as pessimistic as anything it summarises.
136 var overall: i64 = clean
137 if dosrc == 1 {
138 if s_absent > 0 { overall = 0 }
139 }
140 pac_puts("{" as *u8)
141 pac_key("tool" as *u8); pac_q(); pac_puts("nx_paper_audit" as *u8); pac_q(); pac_puts("," as *u8)
142 pac_key("manuscript" as *u8); pac_q(); pac_puts(argv[1] as *u8); pac_q(); pac_puts("," as *u8)
143 pac_key("bytes" as *u8); pac_num(dn); pac_puts("," as *u8)
144 pac_key("citations" as *u8); pac_num(out[0]); pac_puts("," as *u8)
145 pac_key("resolved_and_matching" as *u8); pac_num(out[1]); pac_puts("," as *u8)
146 pac_key("dangling" as *u8); pac_num(out[2]); pac_puts("," as *u8)
147 pac_key("contradicted" as *u8); pac_num(out[3]); pac_puts("," as *u8)
148 pac_key("unanchored" as *u8); pac_num(out[4]); pac_puts("," as *u8)
149 pac_key("integrity_permil" as *u8); pac_num(pa_integrity(out)); pac_puts("," as *u8)
150 pac_key("clean" as *u8); pac_num(clean); pac_puts("," as *u8)
151 if dosrc == 1 {
152 pac_key("ledger_rows" as *u8); pac_num(s_rows); pac_puts("," as *u8)
153 pac_key("source_confirmed" as *u8); pac_num(s_ok); pac_puts("," as *u8)
154 pac_key("source_absent" as *u8); pac_num(s_absent); pac_puts("," as *u8)
155 pac_key("source_unreadable" as *u8); pac_num(s_unread); pac_puts("," as *u8)
156 absids[abo] = 0 as u8
157 pac_key("source_absent_ids" as *u8); pac_q(); pac_puts(absids); pac_q(); pac_puts("," as *u8)
158 pac_key("source_verdict" as *u8); pac_q()
159 if s_absent > 0 { pac_puts("LEDGER-VALUE-NOT-IN-SOURCE" as *u8) }
160 else { if s_unread > 0 { pac_puts("SOURCE-UNREADABLE" as *u8) } else { pac_puts("TRACED" as *u8) } }
161 pac_q(); pac_puts("," as *u8)
162 }
163 pac_key("verdict" as *u8); pac_q()
164 if overall == 1 { pac_puts("CLEAN" as *u8) } else { pac_puts("DEFECTIVE" as *u8) }
165 pac_q(); pac_puts("," as *u8)
166 pac_key("method" as *u8); pac_q()
167 pac_puts("resolves each [ev:ID] against the ledger and compares the adjacent number; a counter cannot see a dangling or contradicted citation" as *u8)
168 pac_q(); pac_puts("}\n" as *u8)
169 if overall == 1 { sys_exit(0); return 0 }
170 sys_exit(1)
171 return 1
172}