code wiki / _hdl_build / nx_ale_verify.nx
nx_ale_verify.nx source
↩ module page · 188 lines · 8077 B
1// nx_ale_verify.nx -- sovereign ALE agent-core SELF-VERIFY phase organ (rung ALE-R2c).
2// THE honest self-score step the whole ALE-R2 agent-core routes through: AFTER the executor
3// (ALE-R2b) produces an artifact, the agent grades its OWN deliverable against the task's
4// DECLARED grading reference, so it cannot lie about its own score. PURE function of:
5// argv[1] = TASK spec path (ALE-format "contract|field|value" lines; ale_format.spec grammar)
6// argv[2] = ARTIFACT path (the agent-produced deliverable to self-grade)
7// argv[3] = scoreout path (the milli-score is written here as decimal+newline; also stdout)
8// CONTRACT (data-driven; no magic numbers):
9// The reference path is read from the task spec's DECLARED grading reference: the
10// "grader|reference|<path>" field. This is a grader| field, NOT a task| field -- the planner
11// (ALE-R2a) and executor (ALE-R2b) read ONLY task| fields, so the reference NEVER leaks to the
12// agent phase. SELF-VERIFY is a POST-completion phase (the agent has already finished), so it
13// is ALLOWED the reference -- mirrors ALE staging the reference AFTER the agent completes.
14// If the grader|reference| field is ABSENT the organ REFUSES: exit 1, writes NO score (a self-
15// verify with no declared rubric cannot certify anything = grounding bite).
16// Otherwise it forks/execve nx_ale_grade.elf <ref> <artifact> <scoreout> EXACTLY as the
17// ALE-R2b gate's g_run_grade does, reads the milli-score back from scoreout, and re-emits it to
18// stdout (so the self-score is the SAME number an independent grade of the same pair yields --
19// the agent does not lie). NO-LEAKAGE: the reference path comes ONLY from the task spec's
20// declared field; nx_ale_verify never scans the sandbox for a decoy reference, so the score is
21// byte-identical whether or not a decoy reference.txt is planted nearby. DETERMINISTIC: the
22// grader is a pure function of (reference, artifact); no clock, no rand here -> two runs on the
23// same (task, artifact) yield a byte-identical milli-score.
24// Landmines respected: nested ifs (no &&/||), flat exprs, <=6 args/func, no empty-string literal,
25// strings via Write, openat_wr no O_TRUNC (the scoreout/grade scratch is a fresh gate path).
26// Helpers mirror the ALE-R2a field-grammar (av_*) + the gate's fork/execve discipline.
27// license_tier: ORIGINAL
28import "nx_syscalls.nx"
29import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
30const K_MAGIC_262144: i64 = 262144
31const K_MAGIC_8192: i64 = 8192
32
33// read whole file at path into buf (cap), return byte count (0 on open-fail)
34func av_read(path: *u8, buf: *u8, cap: i64) -> i64 {
35 let fd: i64 = sys_openat_rd(path)
36 if fd < 0 { return 0 }
37 var n: i64 = 0
38 var go: i64 = 1
39 while go == 1 {
40 let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n)
41 if r <= 0 { go = 0 } else { n = n + r }
42 if n >= cap - 1 { go = 0 }
43 }
44 sys_close(fd)
45 return n
46}
47
48// length of a C string
49func av_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
50
51// does buf[pos..] match pat (len pl)? 1/0
52func av_match(buf: *u8, n: i64, pos: i64, pat: *u8, pl: i64) -> i64 {
53 if pos + pl > n { return 0 }
54 var k: i64 = 0
55 while k < pl {
56 if buf[pos + k] != pat[k] { return 0 }
57 k = k + 1
58 }
59 return 1
60}
61
62// is pos a line start? (pos==0 or previous byte is newline)
63func av_is_bol(buf: *u8, pos: i64) -> i64 {
64 if pos == 0 { return 1 }
65 if buf[pos - 1] == (10 as u8) { return 1 }
66 return 0
67}
68
69// find a line beginning with prefix; return its start index, or -1
70func av_find_line(buf: *u8, n: i64, prefix: *u8) -> i64 {
71 let pl: i64 = av_len(prefix)
72 var i: i64 = 0
73 while i < n {
74 if av_is_bol(buf, i) == 1 {
75 if av_match(buf, n, i, prefix, pl) == 1 { return i }
76 }
77 i = i + 1
78 }
79 return 0 - 1
80}
81
82// Extract the VALUE of the field whose line starts with prefix into out (NUL-terminated),
83// copying from after the prefix up to (not including) the newline. Returns value length, or -1
84// if the field line is absent (the GROUNDING bite). cap bounds the copy.
85func av_field_val(buf: *u8, n: i64, prefix: *u8, out: *u8, cap: i64) -> i64 {
86 let start: i64 = av_find_line(buf, n, prefix)
87 if start < 0 { return 0 - 1 }
88 let pl: i64 = av_len(prefix)
89 var p: i64 = start + pl
90 var k: i64 = 0
91 var go: i64 = 1
92 while go == 1 {
93 if p >= n { go = 0 } else {
94 if buf[p] == (10 as u8) { go = 0 } else {
95 if k < cap - 1 { out[k] = buf[p]; k = k + 1 }
96 p = p + 1
97 }
98 }
99 }
100 out[k] = 0 as u8
101 return k
102}
103
104// write decimal v (>=0) + newline to fd
105// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
106// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
107// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
108// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
109func av_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 }
110
111// fork/execve nx_ale_grade.elf <ref> <art> <scoreout>; return child exit code.
112// IDENTICAL discipline to the ALE-R2b gate's g_run_grade (silence child stdout/stderr).
113func av_run_grade(ref: *u8, art: *u8, scoreout: *u8) -> i64 {
114 let pid: i64 = sys_fork()
115 if pid == 0 {
116 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
117 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) }
118 let argv: *i64 = sys_mmap(48) as *i64
119 argv[0] = "_offc/nx_ale_grade.elf" as *u8 as i64
120 argv[1] = ref as i64
121 argv[2] = art as i64
122 argv[3] = scoreout as i64
123 argv[4] = 0
124 let envp: *i64 = sys_mmap(16) as *i64
125 envp[0] = 0
126 sys_execve("_offc/nx_ale_grade.elf" as *u8, argv, envp)
127 sys_exit(127)
128 }
129 let st: *i64 = sys_mmap(16) as *i64
130 sys_wait4(pid, st, 0)
131 return (st[0] >> 8) & 0xff
132}
133
134// parse the leading non-negative decimal from buf (len n); returns its value (0 if none)
135func av_parse_score(buf: *u8, n: i64) -> i64 {
136 var v: i64 = 0
137 var i: i64 = 0
138 var seen: i64 = 0
139 var go: i64 = 1
140 while go == 1 {
141 if i >= n { go = 0 } else {
142 if buf[i] >= (48 as u8) {
143 if buf[i] <= (57 as u8) {
144 v = v * 10 + (buf[i] - 48)
145 seen = 1
146 i = i + 1
147 } else {
148 if seen == 1 { go = 0 } else { i = i + 1 }
149 }
150 } else {
151 if seen == 1 { go = 0 } else { i = i + 1 }
152 }
153 }
154 }
155 return v
156}
157
158func main(argc: i64, argv: *i64) -> i64 {
159 if argc < 4 { sys_exit(2); return 2 }
160 let taskp: *u8 = argv[1] as *u8
161 let artp: *u8 = argv[2] as *u8
162 let scoreout: *u8 = argv[3] as *u8
163
164 let tbuf: *u8 = sys_mmap(K_MAGIC_262144)
165 let tn: i64 = av_read(taskp, tbuf, K_MAGIC_262144)
166 if tn <= 0 { sys_exit(3); return 3 }
167
168 // Resolve the reference path ONLY from the task spec's DECLARED grading reference.
169 // grader| field, not task| -> never read during plan/exec -> no leakage. Absent -> REFUSE.
170 let refp: *u8 = sys_mmap(K_MAGIC_8192)
171 let lr: i64 = av_field_val(tbuf, tn, "grader|reference|" as *u8, refp, K_MAGIC_8192)
172 if lr < 0 { sys_exit(1); return 1 }
173
174 // SELF-GRADE: run the INDEPENDENT deterministic grader (ALE-R1a) over the declared
175 // reference and the agent's OWN artifact, into scoreout. Same code path an independent
176 // grade uses -> the self-score is the SAME number (the agent does not lie about its score).
177 let rcg: i64 = av_run_grade(refp, artp, scoreout)
178 if rcg != 0 { sys_exit(4); return 4 }
179
180 // Read the milli-score back from scoreout and re-emit it to stdout (machine-readable echo).
181 let scbuf: *u8 = sys_mmap(64)
182 let scn: i64 = av_read(scoreout, scbuf, 64)
183 let score: i64 = av_parse_score(scbuf, scn)
184 av_wn(1, score)
185
186 sys_exit(0)
187 return 0
188}