nx_buildpath_lib.nx source
↩ module page · 127 lines · 7285 B
1// nx_buildpath_lib.nx -- THE ONE ANSWER TO "WHERE DID THE BUILDER PUT THE ARTIFACT?"
2//
3// WHY IT EXISTS (measured 2026-08-16, coverage_complete=1 / corpus_complete=1 over 23,232 sources):
4// nx_sov_build_run anchors its CWD to buildroot/ and writes _build/<name>.sov.elf. It has not
5// written /tmp since 2026-07-30, when the flock change moved artifacts to a per-target
6// _build/<n>.lock+.s+.sov.elf. Every consumer that wants the freshly-built artifact must know that,
7// and the knowledge had NO CALLABLE HOME -- so each one re-derived it, and they disagree:
8// - nx_stale_check probes /tmp THEN _build. Fixed under seq1554, whose own comment records that
9// probing ONE location "made this guard answer BUILDFAIL for EVERY target, so
10// the instrument that gates a risky promote was DEAD".
11// - nx_drift_watch INLINED nx_stale_check's logic ("the SAME logic as nx_stale_check, INLINED
12// to stay single-nest") and NEVER RECEIVED THAT FIX. It logged BUILDFAIL
13// against all four of its subjects for ~32h while reporting verdict=GREEN.
14// - 13 further organs probe /tmp ONLY, with no fallback at all.
15// *AN INLINED COPY DOES NOT RECEIVE ITS ORIGINAL'S FIXES -- the duplicate-ruler defect with a
16// delay fuse, because the copy looks correct on the day it is written.
17// *A BUG YOU FIX BY REWRITING THE LINE, RATHER THAN BY EXTRACTING THE FIX, IS A BUG YOU WILL
18// WRITE AGAIN. A correction with no callable home fixes exactly one site.
19//
20// WHY THE PROBE ORDER IS WHAT IT IS -- every root is here for a NAMED reason, never for luck:
21// BP_AT_BUILD "_build/" the caller already runs from buildroot/ (nx_drift_watch
22// chdirs there before checking; so does the builder itself).
23// BP_AT_BUILDROOT "buildroot/_build/" the caller runs from the SERVING ROOT -- every gate invoked
24// by /api/gate_run, and the 48 gates repointed on 2026-08-16.
25// BP_AT_TMP "/tmp/" LEGACY, pre-2026-07-30. Kept so an older host still
26// resolves, and reported SEPARATELY so that resolving only
27// via the legacy root is VISIBLE AS DRIFT instead of silent.
28//
29// THE RETURN IS THE ROOT THAT MATCHED, NOT A BOOLEAN. A caller that can say WHICH root answered can
30// distinguish "found where the builder writes" from "found only in the legacy location" -- and one of
31// those is a warning. A two-state answer collapses them, which is how this class stayed invisible.
32// *AN AXIS THAT CANNOT SAY *WHICH* CANNOT REPORT DRIFT, ONLY PRESENCE.
33//
34// `out` ALWAYS holds a path, including on failure, where it holds the PRIMARY probe -- so a caller
35// reports what it LOOKED FOR rather than printing an empty string. A guard whose failure message
36// names no path is the reason this defect took three lanes to find.
37//
38// NO GUESSED CAP: bp_needed(name) derives the exact bytes required from the longest prefix, the
39// caller's name and the suffix, and bp_artifact REFUSES with BP_OUTCAP_TOO_SMALL rather than
40// truncating. A ceiling that has to be guessed is a defect generator in both directions.
41//
42// PROVEN BY nx_buildpath_gate (7/7, bite-proven with 3 mutants each killing exactly one tooth:
43// drop-primary-probe-restore -> T4, outcap-guard-never-fires -> T2, collapse-legacy-into-success ->
44// T6). Its binding tooth resolves the GATE'S OWN artifact, which the builder wrote in order to run it,
45// so lib-vs-builder disagreement fails loudly instead of becoming a sixteenth stale copy.
46//
47// LAYERING: lives in runtime/ so BOTH runtime/ and _hdl_build/ can import it -- the same rule
48// nx_itoa_lib.nx states for itself. NO main: this is a library and has no binary BY DESIGN.
49// license_tier: ORIGINAL No hw writes (Rule 26).
50import "nx_syscalls.nx"
51
52const BP_OUTCAP_TOO_SMALL: i64 = 0 - 1
53const BP_NOTFOUND: i64 = 0
54const BP_AT_BUILD: i64 = 1
55const BP_AT_BUILDROOT: i64 = 2
56const BP_AT_TMP: i64 = 3
57
58func bp_slen(s: *u8) -> i64 {
59 var n: i64 = 0
60 while s[n] != (0 as u8) { n = n + 1 }
61 return n
62}
63func bp_cat(dst: *u8, off: i64, src: *u8) -> i64 {
64 var o: i64 = off
65 var j: i64 = 0
66 while src[j] != (0 as u8) { dst[o] = src[j]; o = o + 1; j = j + 1 }
67 return o
68}
69// The three roots and the suffix, as named accessors so no caller ever retypes a literal.
70func bp_root_build() -> *u8 { return "_build/" as *u8 }
71func bp_root_buildroot() -> *u8 { return "buildroot/_build/" as *u8 }
72func bp_root_tmp() -> *u8 { return "/tmp/" as *u8 }
73func bp_suffix() -> *u8 { return ".sov.elf" as *u8 }
74
75// Exact bytes bp_artifact needs, DERIVED: longest root + name + suffix + NUL. No guessing, and a
76// caller that sizes from this can never be truncated.
77func bp_needed(name: *u8) -> i64 {
78 var longest: i64 = bp_slen(bp_root_build())
79 let b: i64 = bp_slen(bp_root_buildroot())
80 if b > longest { longest = b }
81 let t: i64 = bp_slen(bp_root_tmp())
82 if t > longest { longest = t }
83 return longest + bp_slen(name) + bp_slen(bp_suffix()) + 1
84}
85
86func bp_exists(path: *u8) -> i64 {
87 let fd: i64 = sys_openat_rd(path)
88 if fd < 0 { return 0 }
89 sys_close(fd)
90 return 1
91}
92
93// Compose <root><name><suffix> into out. Returns the length written (NUL-terminated).
94func bp_compose(root: *u8, name: *u8, out: *u8) -> i64 {
95 var o: i64 = bp_cat(out, 0, root)
96 o = bp_cat(out, o, name)
97 o = bp_cat(out, o, bp_suffix())
98 out[o] = 0 as u8
99 return o
100}
101
102// THE ONE RESOLVER. Returns BP_AT_* for the root that matched, BP_NOTFOUND if none does, or
103// BP_OUTCAP_TOO_SMALL if the caller's buffer cannot hold the longest probe.
104// On BP_NOTFOUND, out holds the PRIMARY probe (_build/) so the caller can name what it looked for.
105func bp_artifact(name: *u8, out: *u8, outcap: i64) -> i64 {
106 if outcap < bp_needed(name) { return BP_OUTCAP_TOO_SMALL }
107 bp_compose(bp_root_build(), name, out)
108 if bp_exists(out) == 1 { return BP_AT_BUILD }
109 bp_compose(bp_root_buildroot(), name, out)
110 if bp_exists(out) == 1 { return BP_AT_BUILDROOT }
111 bp_compose(bp_root_tmp(), name, out)
112 if bp_exists(out) == 1 { return BP_AT_TMP }
113 // Restore the PRIMARY probe: the failure message should name where the builder WRITES, not the
114 // legacy root that happened to be probed last.
115 bp_compose(bp_root_build(), name, out)
116 return BP_NOTFOUND
117}
118
119// Human-readable reason, so every caller prints the SAME words for the same state and a reader can
120// grep one vocabulary across the fleet instead of N spellings of the same outcome.
121func bp_reason(code: i64) -> *u8 {
122 if code == BP_AT_BUILD { return "found in _build/ (where the builder writes)" as *u8 }
123 if code == BP_AT_BUILDROOT { return "found in buildroot/_build/ (caller ran from the serving root)" as *u8 }
124 if code == BP_AT_TMP { return "found ONLY in legacy /tmp/ -- the builder has not written there since 2026-07-30; this is DRIFT, not a healthy resolve" as *u8 }
125 if code == BP_OUTCAP_TOO_SMALL { return "REFUSED: caller buffer smaller than bp_needed(name) -- refusing to truncate a path" as *u8 }
126 return "NOT FOUND in any known build root" as *u8
127}