nx_evoracle_emit.nx source
↩ module page · 168 lines · 7589 B
1// nx_evoracle_emit.nx -- THE MISSING PRODUCER for EV_CLASS_ORACLE (closes M3).
2//
3// EV_CLASS_ORACLE had exactly ONE consumer (nx_sota_status.nx:220) and NO producer, so an oracle
4// row was hand-typed prose: an agent could write `class=oracle ref=RFC7748` having compared
5// nothing. Without a second class no mechanical domain can reach min_classes=2, which is why the
6// board reads PROVEN 0/41 -- pinned by an EMPTY TABLE, not by any failed measurement.
7//
8// ★★★★★THE LAW THIS ENFORCES (nx_evoracle.nx):
9// AN ORACLE ATTESTATION MUST BE DERIVED FROM AN EXECUTION, NEVER DECLARED IN PROSE.
10// A signature buys attribution and tamper-evidence; it does NOT buy truth, because whoever holds
11// the key can sign a false claim. What buys truth is RE-DERIVABILITY. So this organ REFUSES to
12// accept a verdict as an argument -- it RUNS the named gate and reads the verdict off the actual
13// exit code, and it pins WHICH external vector block was compared by digesting that file.
14//
15// Anti-gaming properties, by construction rather than by promise:
16// - verdict is DERIVED (exit code), never passed in. There is no argument that can assert "pass".
17// - a missing or unreadable vector file REFUSES (exit 3). "It matched" is not evidence unless it
18// says what it matched.
19// - a gate that cannot be executed REFUSES (exit 4). A row is never written for a run that did
20// not happen -- the failure mode the whole evidence lane exists to kill.
21// - ref= carries authority + digest, so a later reader re-runs the comparison and gets the same
22// answer. The digest is FNV-1a 64: it pins WHICH block was used and detects drift. It is NOT
23// cryptographic and is not claimed to be -- tamper-resistance here comes from re-derivability,
24// not from the hash.
25//
26// nx_evoracle_emit <domain> <gate_elf> <vector_file> <authority> <scope>
27// license_tier: ORIGINAL layer: evidence module: nishi-core.evidence.evoracle_emit
28import "nx_syscalls.nx"
29
30const EO_MAXV: i64 = 4194304
31
32static EO_T: i64
33static EO_O: i64
34static EO_LINE: i64
35
36func eo_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
37func eo_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
38func eo_n(v: i64) -> i64 {
39 if EO_T == 0 { EO_T = sys_mmap(64) as i64; EO_O = sys_mmap(64) as i64 }
40 let t: *u8 = EO_T as *u8
41 let o: *u8 = EO_O as *u8
42 var m: i64=v; var k: i64=0
43 if m==0 { t[0]=48 as u8; k=1 }
44 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
45 var i: i64=0; while i<k { o[i]=t[k-1-i]; i=i+1 }
46 sys_write(1,o,k); return 0
47}
48
49func eo_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){ dst[o]=s[i]; o=o+1; i=i+1 } return o }
50func eo_catn(dst: *u8, off: i64, v: i64) -> i64 {
51 if v == 0 { dst[off]=48 as u8; return off+1 }
52 if EO_T == 0 { EO_T = sys_mmap(64) as i64; EO_O = sys_mmap(64) as i64 }
53 let t: *u8 = EO_T as *u8
54 var m: i64=v; var k: i64=0
55 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
56 var o: i64=off; var i: i64=k-1; while i>=0 { dst[o]=t[i]; o=o+1; i=i-1 }
57 return o
58}
59// FNV-1a 64. Pins WHICH vector block was compared and detects drift. Not cryptographic; see header.
60func eo_catx(dst: *u8, off: i64, v: i64) -> i64 {
61 var o: i64 = off
62 var i: i64 = 60
63 while i >= 0 {
64 let nib: i64 = (v >> i) & 15
65 if nib < 10 { dst[o] = (48+nib) as u8 } else { dst[o] = (87+nib) as u8 }
66 o = o + 1
67 i = i - 4
68 }
69 return o
70}
71func eo_fnv(b: *u8, n: i64) -> i64 {
72 var h: i64 = 0x0CBF29CE484222325
73 var i: i64 = 0
74 while i < n { h = h ^ (b[i] as i64); h = h * 0x100000001B3; i = i + 1 }
75 return h
76}
77
78func main(argc: i64, argv: *i64) -> i64 {
79 if argc < 6 {
80 eo_p("usage: nx_evoracle_emit <domain> <gate_elf> <vector_file> <authority> <scope>\n" as *u8)
81 eo_p(" verdict is DERIVED from the gate's exit code and cannot be supplied as an argument.\n" as *u8)
82 sys_exit(2); return 2
83 }
84 let domain: *u8 = argv[1] as *u8
85 let gate: *u8 = argv[2] as *u8
86 let vecp: *u8 = argv[3] as *u8
87 let auth: *u8 = argv[4] as *u8
88 let scope: *u8 = argv[5] as *u8
89
90 // ---- 1. the external vector block must exist and be readable, or REFUSE ----
91 let vfd: i64 = sys_openat_rd(vecp)
92 if vfd < 0 {
93 eo_p("REFUSED(3): vector file unreadable: " as *u8); eo_p(vecp)
94 eo_p("\n an oracle row must name WHAT it compared against; a missing block is not evidence.\n" as *u8)
95 sys_exit(3); return 3
96 }
97 let vbuf: *u8 = sys_mmap(EO_MAXV)
98 let vn: i64 = sys_read(vfd, vbuf, EO_MAXV)
99 sys_close(vfd)
100 if vn < 1 {
101 eo_p("REFUSED(3): vector file empty: " as *u8); eo_p(vecp); eo_p("\n" as *u8)
102 sys_exit(3); return 3
103 }
104 let dig: i64 = eo_fnv(vbuf, vn)
105
106 // ---- 2. RUN the gate. The verdict comes from here and nowhere else. ----
107 eo_p("running gate: " as *u8); eo_p(gate); eo_p("\n" as *u8)
108 let pid: i64 = sys_fork()
109 if pid == 0 {
110 let av: *i64 = sys_mmap(32) as *i64
111 av[0] = gate as i64; av[1] = 0
112 let ev: *i64 = sys_mmap(16) as *i64
113 ev[0] = "PATH=/usr/bin:/bin" as *u8 as i64; ev[1] = 0
114 sys_execve(gate, av, ev)
115 sys_exit(127)
116 }
117 if pid < 0 {
118 eo_p("REFUSED(4): cannot fork to run the gate\n" as *u8); sys_exit(4); return 4
119 }
120 let st: *i64 = sys_mmap(64) as *i64
121 sys_wait4(pid, st, 0)
122 let rc: i64 = wait_exit_code(st[0])
123 if rc == 127 {
124 eo_p("REFUSED(4): gate could not be executed: " as *u8); eo_p(gate)
125 eo_p("\n a row is NEVER written for a run that did not happen.\n" as *u8)
126 sys_exit(4); return 4
127 }
128 eo_p("gate exit code: " as *u8); eo_n(rc); eo_p("\n" as *u8)
129
130 // ---- 3. build the row. verdict DERIVED from rc. ----
131 if EO_LINE == 0 { EO_LINE = sys_mmap(4096) as i64 }
132 let ln: *u8 = EO_LINE as *u8
133 var o: i64 = 0
134 o = eo_cat(ln, o, "class=oracle verdict=" as *u8)
135 if rc == 0 { o = eo_cat(ln, o, "pass" as *u8) } else { o = eo_cat(ln, o, "fail" as *u8) }
136 o = eo_cat(ln, o, " scope=" as *u8); o = eo_cat(ln, o, scope)
137 o = eo_cat(ln, o, " signer=" as *u8); o = eo_cat(ln, o, gate)
138 o = eo_cat(ln, o, " ref=" as *u8); o = eo_cat(ln, o, auth)
139 o = eo_cat(ln, o, "#fnv1a64:" as *u8); o = eo_catx(ln, o, dig)
140 o = eo_cat(ln, o, " vector=" as *u8); o = eo_cat(ln, o, vecp)
141 o = eo_cat(ln, o, " vbytes=" as *u8); o = eo_catn(ln, o, vn)
142 o = eo_cat(ln, o, " rc=" as *u8); o = eo_catn(ln, o, rc)
143 o = eo_cat(ln, o, " epoch=" as *u8); o = eo_catn(ln, o, sys_now_realtime_sec())
144 ln[o] = 10 as u8; o = o + 1
145
146 // ---- 4. append to knowledge/status/evclass_<domain>.conf ----
147 let path: *u8 = sys_mmap(512)
148 var po: i64 = 0
149 po = eo_cat(path, po, "knowledge/status/evclass_" as *u8)
150 po = eo_cat(path, po, domain)
151 po = eo_cat(path, po, ".conf" as *u8)
152 path[po] = 0 as u8
153
154 let ofd: i64 = sys_openat_append(path, 0x1a4)
155 if ofd < 0 {
156 eo_p("REFUSED(5): cannot open for append: " as *u8); eo_p(path)
157 eo_p("\n (does knowledge/status/ exist in this cwd?)\n" as *u8)
158 sys_exit(5); return 5
159 }
160 sys_write(ofd, ln, o)
161 sys_close(ofd)
162
163 eo_p("WROTE " as *u8); eo_p(path); eo_p("\n " as *u8)
164 sys_write(1, ln, o)
165 if rc == 0 { eo_p("EV-ORACLE-EMIT ok verdict=pass (DERIVED from exit code)\n" as *u8); sys_exit(0); return 0 }
166 eo_p("EV-ORACLE-EMIT ok verdict=fail (DERIVED from exit code -- recorded as DISSENT)\n" as *u8)
167 sys_exit(0); return 0
168}