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}