nx_buildecho.nx source
↩ module page · 108 lines · 4809 B
1// nx_buildecho.nx -- READ WHAT THE BUILDER SAID IT COMPILED (2026-08-07).
2//
3// Extracted from nx_mgmt_api's ma_do_build so the cross-target guard can be BITE-TESTED. It was written
4// inline inside a 296KB daemon that binds a port and needs a live estate, which made the one piece of
5// logic standing between "wrong organ promoted under the right name" and "refused" the piece nobody
6// could exercise.
7// *A GUARD BURIED IN A DAEMON IS A GUARD THAT ONLY GETS TESTED IN PRODUCTION.*
8//
9// WHAT IT PARSES -- the builder's own echo, the ONLY witness to what was actually compiled:
10// [hostctl] buildrun: compile 'nx_seg_store_gate' on the NAS (nx_cc -> nxasm)
11//
12// WHY THE ECHO AND NOT THE OBVIOUS FIELDS (all three measured 2026-08-07):
13// expect_sha256 -- proves the staged bytes are the ones just built. When the WRONG source was compiled
14// that digest IS the wrong binary's digest, so promote installs it and says PROMOTED.
15// A GUARD THAT CHECKS THE ARTIFACT AGAINST THE BUILD CANNOT TELL YOU THE BUILD WAS OF
16// THE WRONG THING.
17// src_path -- ma_gate_src DERIVES it from the REQUESTED name, so it agrees BY CONSTRUCTION
18// whatever the compiler read. A FIELD COMPUTED FROM THE QUESTION CANNOT CORROBORATE
19// THE ANSWER.
20//
21// ABSENCE IS NOT A MISMATCH. A capture without the marker returns 0 and the caller proceeds exactly as
22// before. *A GUARD THAT CANNOT SEE ITS EVIDENCE MUST ABSTAIN, NEVER ACCUSE* -- turning "I could not look"
23// into "it is wrong" would break every build the day the echo format changes.
24// license_tier: ORIGINAL No hw writes (Rule 26).
25import "nx_syscalls.nx"
26
27const BE_NAMEMAX: i64 = 250
28
29// Returns 1 and fills `out` (NUL-terminated) with the compiled target name, or 0 when the marker is
30// absent/empty. Scans the WHOLE buffer: the echo is never at offset 0, and a capture that merges stdout
31// with stderr moves it again -- classifying by the first bytes of a merged stream read every SUCCESS as
32// unparsed once already today.
33func be_compiled_name(buf: *u8, n: i64, out: *u8) -> i64 {
34 let k: *u8 = "compile '" as *u8
35 var i: i64 = 0
36 while i + 9 <= n {
37 var m: i64 = 0
38 var hit: i64 = 1
39 while m < 9 { if buf[i+m] != k[m] { hit = 0; m = 9 } else { m = m + 1 } }
40 if hit == 1 {
41 var s: i64 = i + 9
42 var o: i64 = 0
43 while s < n {
44 let c: i64 = buf[s] as i64
45 if c == 39 { s = n } else {
46 if o < BE_NAMEMAX { out[o] = buf[s]; o = o + 1 }
47 s = s + 1
48 }
49 }
50 out[o] = 0 as u8
51 if o == 0 { return 0 }
52 return 1
53 }
54 i = i + 1
55 }
56 return 0
57}
58
59// Extract the 64 hex chars following `key` (e.g. "closure_sha=") from a captured stream into `out`.
60// Returns 1 on success, 0 when the key is absent OR the value is short/non-hex.
61// *A TRUNCATED DIGEST MUST NOT BE PUBLISHED AS A DIGEST* -- accepting a short value would let a receipt
62// carry a number that looks authoritative and identifies nothing, which is worse than carrying none.
63// Scans the whole buffer: captured streams merge stdout and stderr, so the value is not at offset 0.
64func be_after_hex(buf: *u8, n: i64, key: *u8, out: *u8) -> i64 {
65 var ln: i64 = 0
66 while key[ln] != (0 as u8) { ln = ln + 1 }
67 if n < ln + 64 { return 0 }
68 var i: i64 = 0
69 while i + ln + 64 <= n {
70 var m: i64 = 0
71 var hit: i64 = 1
72 while m < ln { if buf[i+m] != key[m] { hit = 0; m = ln } else { m = m + 1 } }
73 if hit == 1 {
74 let s: i64 = i + ln
75 var o: i64 = 0
76 var ok: i64 = 1
77 while o < 64 {
78 let c: i64 = buf[s+o] as i64
79 var ishex: i64 = 0
80 if c >= 48 { if c <= 57 { ishex = 1 } }
81 if c >= 97 { if c <= 102 { ishex = 1 } }
82 if ishex == 0 { ok = 0; o = 64 } else { out[o] = buf[s+o]; o = o + 1 }
83 }
84 if ok == 0 { return 0 }
85 out[64] = 0 as u8
86 return 1
87 }
88 i = i + 1
89 }
90 return 0
91}
92
93// 1 when the builder's echo names exactly `want`. 0 on mismatch. Callers must treat be_compiled_name()==0
94// (no evidence) differently from this returning 0 (contradicted evidence) -- they are not the same claim.
95func be_echo_matches(buf: *u8, n: i64, want: *u8) -> i64 {
96 let got: *u8 = sys_mmap(BE_NAMEMAX + 8)
97 if be_compiled_name(buf, n, got) == 0 { return 0 }
98 var i: i64 = 0
99 var go: i64 = 1
100 while go == 1 {
101 let a: i64 = got[i] as i64
102 let b: i64 = want[i] as i64
103 if a != b { return 0 }
104 if a == 0 { go = 0 }
105 i = i + 1
106 }
107 return 1
108}