nx_measurement_binding_artifact_gate.nx source
↩ module page · 186 lines · 10332 B
1// Capture the actual staged owner without changing its command/API contract.
2// Only caller-named exclusive /tmp files are written. Existing board artifacts are compared.
3// license_tier: ORIGINAL; expect_exit: 0
4import "nx_gatekit_lib.nx"
5import "nx_gate_verdict.nx"
6import "nx_estate_path.nx"
7import "nx_json_lib.nx"
8
9const BC_FILE_MODE: i64 = 0x180 // POSIX owner-read/write temporary artifact mode
10const BC_EXEC_FAILED: i64 = 127
11const BC_CAPTURE_FAILED: i64 = 125
12const BC_EINTR: i64 = 4
13const BC_BOARD_JSON: *u8 = "knowledge/status/sota_board.json"
14const BC_BOARD_LEDGER: *u8 = "knowledge/status/sota_board.ledger"
15
16func bc_prefix_ok(prefix: *u8) -> i64 {
17 let n: i64 = gk_len(prefix)
18 if n <= gk_len("/tmp/" as *u8) { return 0 }
19 if n+gk_len(".invalid-domain-artifact.json" as *u8)+gk_len("/tmp/../" as *u8)+1 > SYS_PATH_MAX { return 0 }
20 let root: *u8 = "/tmp/" as *u8
21 var i: i64 = 0
22 while i < gk_len(root) { if prefix[i] != root[i] { return 0 } i = i+1 }
23 while i < n {
24 let c: i64 = prefix[i] as i64
25 var ok: i64 = 0
26 if c >= 97 { if c <= 122 { ok = 1 } }
27 if c >= 65 { if c <= 90 { ok = 1 } }
28 if c >= 48 { if c <= 57 { ok = 1 } }
29 if c == 95 { ok = 1 }
30 if c == 45 { ok = 1 }
31 if ok == 0 { return 0 }
32 i = i+1
33 }
34 return 1
35}
36func bc_equal(before: *u8, bn: i64, after: *u8, an: i64) -> i64 {
37 if bn <= 0 { return 0 }
38 if bn != an { return 0 }
39 if (before as i64) <= 0 { return 0 }
40 if (after as i64) <= 0 { return 0 }
41 var i: i64 = 0
42 while i < bn { if before[i] != after[i] { return 0 } i = i+1 }
43 return 1
44}
45func bc_capture(elf: *u8, domain: *u8, out: *u8) -> i64 {
46 let argv: *i64 = sys_mmap(4*8) as *i64
47 let envp: *i64 = sys_mmap(8) as *i64
48 let status: *i64 = sys_mmap(8) as *i64
49 if (argv as i64) <= 0 { return BC_CAPTURE_FAILED }
50 if (envp as i64) <= 0 { return BC_CAPTURE_FAILED }
51 if (status as i64) <= 0 { return BC_CAPTURE_FAILED }
52 let mode: *u8 = "bindings" as *u8
53 argv[0] = elf as i64; argv[1] = mode as i64; argv[2] = domain as i64; argv[3] = 0
54 envp[0] = 0; status[0] = 0
55 let fd: i64 = sys_openat_exclusive(out, BC_FILE_MODE)
56 if fd < 0 { return BC_CAPTURE_FAILED }
57 let pid: i64 = sys_fork()
58 if pid < 0 { sys_close(fd); return BC_CAPTURE_FAILED }
59 if pid == 0 {
60 if fd != 1 { if sys_dup3(fd, 1, 0) < 0 { sys_exit(BC_CAPTURE_FAILED) } }
61 sys_execve_clean(elf, argv, envp)
62 sys_exit(BC_EXEC_FAILED)
63 }
64 var waited: i64 = sys_wait4(pid, status, 0)
65 while waited == 0-BC_EINTR { waited = sys_wait4(pid, status, 0) }
66 let synced: i64 = sys_fsync(fd)
67 sys_close(fd)
68 if waited != pid { return BC_CAPTURE_FAILED }
69 if synced < 0 { return BC_CAPTURE_FAILED }
70 return gk_wait_code(status[0])
71}
72func bc_case(elf: *u8, prefix: *u8, suffix: *u8, domain: *u8, expected_rc: i64, marker: *u8, ctr: *i64) -> i64 {
73 let path: *u8 = sys_mmap(SYS_PATH_MAX)
74 let size: *i64 = sys_mmap(8) as *i64
75 var allocated: i64 = 0
76 if (path as i64) > 0 { if (size as i64) > 0 { allocated = 1 } }
77 gv_check("capture-case-buffers-allocated" as *u8, allocated, ctr)
78 if allocated == 0 { return 0 }
79 let used: i64 = gk_cat(path, 0, prefix)
80 gk_cat(path, used, suffix)
81 let rc: i64 = bc_capture(elf, domain, path)
82 gv_puts("capture_path=" as *u8); gv_puts(path); gv_puts(" exit=" as *u8); gv_num(rc)
83 let output: *u8 = sys_read_file(path, size)
84 gv_puts(" complete_file_bytes=" as *u8); gv_num(size[0]); gv_puts("\n" as *u8)
85 gv_check("staged-owner-exit-matches-contract" as *u8, rc == expected_rc, ctr)
86 var complete: i64 = 0
87 if (output as i64) > 0 { if size[0] > 1 {
88 if output[0] == (123 as u8) { if output[size[0]-1] == (10 as u8) {
89 if output[size[0]-2] == (125 as u8) { complete = gk_has(output, marker) }
90 } }
91 } }
92 gv_check("complete-file-json-envelope-and-case-marker" as *u8, complete, ctr)
93 sys_free_file(output, size[0])
94 return complete
95}
96func bc_artifact(elf: *u8, prefix: *u8, ctr: *i64) -> i64 {
97 let path: *u8 = sys_mmap(SYS_PATH_MAX)
98 let invalid: *u8 = sys_mmap(SYS_PATH_MAX)
99 let traversal: *u8 = sys_mmap(SYS_PATH_MAX)
100 let log: *u8 = sys_mmap(GK_BUF)
101 let ln: *i64 = sys_mmap(8) as *i64
102 let bn: *i64 = sys_mmap(8) as *i64
103 let an: *i64 = sys_mmap(8) as *i64
104 let reported: *i64 = sys_mmap(8) as *i64
105 var ready: i64 = 0
106 if (path as i64) > 0 { if (invalid as i64) > 0 { if (traversal as i64) > 0 { if (log as i64) > 0 {
107 if (ln as i64) > 0 { if (bn as i64) > 0 { if (an as i64) > 0 { if (reported as i64) > 0 { ready = 1 } } } }
108 } } } }
109 gv_check("artifact-case-buffers-allocated" as *u8, ready, ctr)
110 if ready == 0 { return 0 }
111 let used: i64 = gk_cat(path, 0, prefix)
112 gk_cat(path, used, ".gameengine-artifact.json" as *u8)
113 let badused: i64 = gk_cat(invalid, 0, prefix)
114 gk_cat(invalid, badused, ".invalid-domain-artifact.json" as *u8)
115 let rc: i64 = gk_run_capture(elf, "bindings" as *u8, "gameengine" as *u8, path, 0 as *u8, log, GK_BUF, ln)
116 gv_check("artifact-command-exits-zero" as *u8, rc == 0, ctr)
117 var receipt: i64 = 0
118 if gk_has(log, "\"resource\":\"domain-bindings-artifact\"" as *u8) == 1 {
119 if gk_has(log, "\"ready\":true" as *u8) == 1 { if gk_has(log, "\"retained_partial\":false" as *u8) == 1 { receipt = 1 } }
120 }
121 gv_check("artifact-has-completion-receipt" as *u8, receipt, ctr)
122 gv_puts("artifact_receipt=" as *u8); sys_write(1, log, ln[0])
123 let before: *u8 = sys_read_file(path, bn)
124 let got: i64 = jx_get_int(log, ln[0], 0, "bytes_written" as *u8, reported)
125 var same_size: i64 = 0
126 if got > 0 { if bn[0] > 0 { if reported[0] == bn[0] { same_size = 1 } } }
127 gv_check("artifact-receipt-counts-complete-file-bytes" as *u8, same_size, ctr)
128 var full: i64 = 0
129 if (before as i64) > 0 { if bn[0] > 1 { if before[bn[0]-2] == (125 as u8) {
130 if before[bn[0]-1] == (10 as u8) { full = gk_has(before, "\"domain\":\"gameengine\"" as *u8) }
131 } } }
132 gv_check("artifact-retains-complete-report-envelope" as *u8, full, ctr)
133 let again: i64 = gk_run_capture(elf, "bindings" as *u8, "gameengine" as *u8, path, 0 as *u8, log, GK_BUF, ln)
134 gv_check("existing-artifact-path-refused" as *u8, again == 3, ctr)
135 gv_check("existing-artifact-refusal-is-explicit" as *u8, gk_has(log, "\"reason\":\"output_open_refused\"" as *u8), ctr)
136 let after: *u8 = sys_read_file(path, an)
137 gv_check("existing-artifact-byte-identical-after-refused-reuse" as *u8, bc_equal(before, bn[0], after, an[0]), ctr)
138 sys_free_file(after, an[0]); sys_free_file(before, bn[0])
139 let bad_domain: i64 = gk_run_capture(elf, "bindings" as *u8, "../" as *u8, invalid, 0 as *u8, log, GK_BUF, ln)
140 gv_check("bad-domain-refused-before-artifact-creation" as *u8, bad_domain == 3, ctr)
141 gv_check("bad-domain-created-no-artifact" as *u8, gk_exists(invalid) == 0, ctr)
142 // Even a broken validator resolves this spelling to the existing caller-owned /tmp artifact.
143 let tused: i64 = gk_cat(traversal, 0, "/tmp/../" as *u8)
144 gk_cat(traversal, tused, path)
145 let bad_path: i64 = gk_run_capture(elf, "bindings" as *u8, "gameengine" as *u8, traversal, 0 as *u8, log, GK_BUF, ln)
146 gv_check("output-traversal-refused" as *u8, bad_path == 3, ctr)
147 gv_check("output-traversal-refusal-is-explicit" as *u8, gk_has(log, "bad_output_path" as *u8), ctr)
148 let too_many: i64 = gk_run_capture(elf, "bindings" as *u8, "gameengine" as *u8, invalid, "unexpected" as *u8, log, GK_BUF, ln)
149 gv_check("five-argument-command-remains-refused" as *u8, too_many == 3, ctr)
150 gv_check("bad-arity-created-no-artifact" as *u8, gk_exists(invalid) == 0, ctr)
151 return full
152}
153func main(argc: i64, argv: *i64) -> i64 {
154 if argc != 3 { gv_puts("usage: capture-gate <staged-owner-path> </tmp/unique-basename>\n" as *u8); return 3 }
155 let elf: *u8 = argv[1] as *u8
156 let prefix: *u8 = argv[2] as *u8
157 if bc_prefix_ok(prefix) == 0 { gv_puts("REFUSE temporary basename must remain directly under /tmp\n" as *u8); return 3 }
158 if ep_anchor() < 0 { gv_puts("REFUSE estate anchor unavailable\n" as *u8); return 3 }
159 let ctr: *i64 = gv_ctr()
160 gv_head("Actual staged binding command: complete capture and unchanged board artifacts" as *u8)
161 let jn: *i64 = sys_mmap(8) as *i64
162 let ln: *i64 = sys_mmap(8) as *i64
163 let an: *i64 = sys_mmap(8) as *i64
164 var allocated: i64 = 0
165 if (jn as i64) > 0 { if (ln as i64) > 0 { if (an as i64) > 0 { allocated = 1 } } }
166 gv_check("board-comparison-buffers-allocated" as *u8, allocated, ctr)
167 if allocated == 0 { return gv_verdict("NX-BINDINGS-CAPTURE" as *u8, ctr, "allocation failed before baseline reads" as *u8) }
168 let jb: *u8 = sys_read_file(BC_BOARD_JSON, jn)
169 let lb: *u8 = sys_read_file(BC_BOARD_LEDGER, ln)
170 var ready: i64 = 0
171 if (jb as i64) > 0 { if jn[0] > 0 { if (lb as i64) > 0 { if ln[0] > 0 { ready = 1 } } } }
172 gv_check("existing-board-artifacts-readable-before-command" as *u8, ready, ctr)
173 if ready == 0 { return gv_verdict("NX-BINDINGS-CAPTURE" as *u8, ctr, "no board side-effect claim without baseline bytes" as *u8) }
174 bc_case(elf, prefix, ".charsim.json" as *u8, "charsim" as *u8, 0, "\"domain\":\"charsim\"" as *u8, ctr)
175 bc_case(elf, prefix, ".gameengine.json" as *u8, "gameengine" as *u8, 0, "\"domain\":\"gameengine\"" as *u8, ctr)
176 bc_case(elf, prefix, ".bad-domain.json" as *u8, "../" as *u8, 3, "\"reason\":\"bad_domain\"" as *u8, ctr)
177 bc_case(elf, prefix, ".missing-argument.json" as *u8, 0 as *u8, 3, "usage: nx_sota_status bindings" as *u8, ctr)
178 bc_artifact(elf, prefix, ctr)
179 let ja: *u8 = sys_read_file(BC_BOARD_JSON, an)
180 gv_check("existing-board-json-byte-identical-after-all-cases" as *u8, bc_equal(jb, jn[0], ja, an[0]), ctr)
181 sys_free_file(ja, an[0])
182 let la: *u8 = sys_read_file(BC_BOARD_LEDGER, an)
183 gv_check("existing-board-ledger-byte-identical-after-all-cases" as *u8, bc_equal(lb, ln[0], la, an[0]), ctr)
184 sys_free_file(la, an[0]); sys_free_file(jb, jn[0]); sys_free_file(lb, ln[0])
185 return gv_verdict("NX-BINDINGS-CAPTURE" as *u8, ctr, "complete artifacts retained at printed paths; JSON structure and semantic checker must also pass before promotion" as *u8)
186}