nx_adoptgate_typed_candidate_t310.nx source
↩ module page · 199 lines · 9845 B
1// nx_adoptgate.nx -- THE ENFORCED GATE BETWEEN /api/build AND /api/promote (2026-08-07).
2//
3// WHY: the 4-step adoption recipe exists and is correct, and I ran it BY HAND and therefore ran it
4// INCONSISTENTLY -- 35 organs promoted on step 2 alone because step 3 was something I had to remember.
5// *A LAW I HAVE TO REMEMBER IS A LAW I WILL SKIP; PUT IT IN THE PATH.*
6// This collapses steps 2 and 3 into ONE call that emits ONE machine-checkable token, so a promote can be
7// gated on `APPROVED` instead of on my good intentions.
8//
9// IT DOES NOT BUILD AND IT DOES NOT PROMOTE, and that is deliberate:
10// - build must go through /api/build because only that stages the artifact where /api/promote LOOKS
11// (a direct nx_sov_build_run drops it in /tmp or _build -- provably the wrong place, and exactly the
12// artifacts-never-placed-where-consumers-look defect this whole campaign is about).
13// - promotion stays an explicit operator act. A gate that also performs the action it authorises cannot
14// be re-run to audit its own decision.
15//
16// COMPOSES THE INCUMBENTS, never reimplements them (one duplicate ruler was already built and retracted
17// this session): nx_contentdiff = NECESSARY half, nx_behaveprobe = SUFFICIENCY half.
18//
19// nx_adoptgate <target> [workload-args...]
20// -> ADOPT-GATE <target> verdict=APPROVED|REFUSED reason=... content=... behave=... evidence=...
21// exit 0 APPROVED | 1 REFUSED | 2 missing artifact | 3 usage
22// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
23import "nx_tool_run.nx"
24import "nx_contentdiff.nx"
25
26const AG_HOST: *u8 = "/volume1/homes/elderwesto/nishihost"
27const AG_CDIFF: *u8 = "/volume1/homes/elderwesto/nishihost/nx_contentdiff.elf"
28const AG_BPROBE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_behaveprobe.elf"
29const AG_CAP: i64 = 262144
30const AG_TMO: i64 = 180000
31
32func agp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
33func age(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(2,s,n); return 0 }
34func agcat(d: *u8, o: i64, s: *u8) -> i64 { var x: i64=o; var i: i64=0; while s[i]!=(0 as u8){d[x]=s[i];x=x+1;i=i+1} return x }
35func agexists(p: *u8) -> i64 { let fd: i64 = sys_openat_rd(p); if fd < 0 { return 0 } sys_close(fd); return 1 }
36
37// does buf contain needle? Scans the WHOLE buffer -- never offset 0. A captured stream merges stdout and
38// stderr, so the line you want is routinely not first; classifying by the first bytes read every SUCCESS
39// as unparsed once already this session.
40func agfind(buf: *u8, n: i64, ndl: *u8) -> i64 {
41 var ln: i64 = 0
42 while ndl[ln] != (0 as u8) { ln = ln + 1 }
43 if ln == 0 { return 0 }
44 if n < ln { return 0 }
45 var i: i64 = 0
46 while i <= n - ln {
47 var k: i64 = 0
48 var hit: i64 = 1
49 while k < ln { if buf[i+k] != ndl[k] { hit = 0; k = ln } else { k = k + 1 } }
50 if hit == 1 { return 1 }
51 i = i + 1
52 }
53 return 0
54}
55
56// ---- THE REASON TRAVELS WITH THE COUNT (debt 1787235038, fixed 2026-08-20) --------------------------
57// This gate FORKED the ruler, held its ENTIRE output in `out`, and published one verdict WORD. So every
58// downstream reader had to re-run nx_contentdiff BY HAND to learn WHICH run a candidate drops -- and
59// nx_organ_ship's allow_loss leg, which counts "LOST:" rows in THIS gate's output, therefore measured
60// nlost=0 for every candidate that has ever existed and refused every named-loss ship as "a ruler that
61// did not speak". The evidence was in hand at judge time and thrown away. (Same defect class as
62// nx_artifactdrift judge, which forked nx_contentdiff per row and kept only the verdict BIT.)
63// WARNING CARRIED FORWARD: the ruler NAMES at most CD_SHOW(=8) lost runs while COUNTING all of them, so
64// the echoed list is a PREFIX OF ITS OWN COUNT -- lost_from_live travels beside it, or a reader
65// republishes a prefix as a population.
66func agpos(buf: *u8, n: i64, ndl: *u8, from: i64) -> i64 {
67 var ln: i64 = 0
68 while ndl[ln] != (0 as u8) { ln = ln + 1 }
69 if ln == 0 { return 0 - 1 }
70 var i: i64 = from
71 if i < 0 { i = 0 }
72 while i + ln <= n {
73 var k: i64 = 0
74 var hit: i64 = 1
75 while k < ln { if buf[i+k] != ndl[k] { hit = 0; k = ln } else { k = k + 1 } }
76 if hit == 1 { return i }
77 i = i + 1
78 }
79 return 0 - 1
80}
81func agnum(v: i64) -> i64 {
82 var m: i64 = v
83 if m < 0 { agp("-" as *u8); m = 0 - m }
84 let t: *u8 = sys_mmap(32)
85 var k: i64 = 0
86 if m == 0 { t[0] = 48 as u8; k = 1 }
87 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
88 while k > 0 { k = k - 1; sys_write(1, ((t as i64) + k) as *u8, 1) }
89 return 0
90}
91// The integer the RULER published, read back from the ruler's own field name. This is not a second
92// measurement of loss -- it is the first one, quoted. A third state is REQUIRED: -1 means the ruler
93// printed no count at all, which must never read as zero.
94func agint_after(buf: *u8, n: i64, ndl: *u8) -> i64 {
95 let p: i64 = agpos(buf, n, ndl, 0)
96 if p < 0 { return 0 - 1 }
97 var ln: i64 = 0
98 while ndl[ln] != (0 as u8) { ln = ln + 1 }
99 var j: i64 = p + ln
100 var v: i64 = 0
101 var any: i64 = 0
102 var go: i64 = 1
103 while go == 1 {
104 if j >= n { go = 0 } else {
105 let c: i64 = buf[j] as i64
106 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; j = j + 1 } else { go = 0 } } else { go = 0 }
107 }
108 }
109 if any == 0 { return 0 - 1 }
110 return v
111}
112// Echo VERBATIM every line of the captured ruler output containing `ndl`, one echo per LINE (the cursor
113// advances past the line end, so a needle occurring twice inside one run cannot double-print it).
114// Returns how many lines were echoed -- the NAMED count, which is a floor on the true one.
115func ag_echo_lines(buf: *u8, n: i64, ndl: *u8) -> i64 {
116 var shown: i64 = 0
117 var i: i64 = 0
118 var go: i64 = 1
119 while go == 1 {
120 let p: i64 = agpos(buf, n, ndl, i)
121 if p < 0 { go = 0 } else {
122 var s: i64 = p
123 var back: i64 = 1
124 while back == 1 { if s == 0 { back = 0 } else { if buf[s-1] == (10 as u8) { back = 0 } else { s = s - 1 } } }
125 var e: i64 = p
126 var fwd: i64 = 1
127 while fwd == 1 { if e >= n { fwd = 0 } else { if buf[e] == (10 as u8) { fwd = 0 } else { e = e + 1 } } }
128 sys_write(1, ((buf as i64) + s) as *u8, e - s)
129 agp("\n" as *u8)
130 shown = shown + 1
131 i = e + 1
132 if i >= n { go = 0 }
133 }
134 }
135 return shown
136}
137
138// Native composition: counts and qualification travel as data, not parsed stdout.
139func ag_content_paths(live:*u8,staged:*u8,target:*u8)->i64{
140 var measured:NxCdResult
141 let rc:i64=cd_structured_paths_result(live,staged,CD_SHOW,&measured)
142 agp("ADOPT-GATE ");agp(target);agp(" structured_state=");agnum(measured.state)
143 agp(" content_runs_scanned=");agnum(measured.runs)
144 agp(" lost_from_live=");agnum(measured.lost)
145 agp(" lost_permil=")
146 if measured.state==0{agnum(cds_permil(measured.lost,measured.checked))}else{agnum(-1)}
147 agp(" source_removed=");agnum(measured.source_removed)
148 agp(" source_added=");agnum(measured.source_added);agp("\n")
149 if rc!=0||measured.state!=0{
150 agp("ADOPT-GATE ");agp(target)
151 agp(" verdict=REFUSED reason=content-loss-or-unqualified content=RED\n");return 1
152 }
153 return 0
154}
155
156func main(argc: i64, argv: *i64) -> i64 {
157 if argc < 2 { age("usage: nx_adoptgate <target> [workload-args...]\n" as *u8); sys_exit(3); return 3 }
158 let tgt: *u8 = argv[1] as *u8
159
160 let live: *u8 = sys_mmap(512)
161 var lo: i64 = agcat(live, 0, AG_HOST); lo = agcat(live, lo, "/" as *u8); lo = agcat(live, lo, tgt); lo = agcat(live, lo, ".elf" as *u8)
162 live[lo] = 0 as u8
163 let stag: *u8 = sys_mmap(512)
164 var so: i64 = agcat(stag, 0, AG_HOST); so = agcat(stag, so, "/" as *u8); so = agcat(stag, so, tgt); so = agcat(stag, so, ".sov.elf.new" as *u8)
165 stag[so] = 0 as u8
166
167 if agexists(live) == 0 { agp("ADOPT-GATE " as *u8); agp(tgt); agp(" verdict=REFUSED reason=no-live-elf\n" as *u8); sys_exit(2); return 2 }
168 if agexists(stag) == 0 { agp("ADOPT-GATE " as *u8); agp(tgt); agp(" verdict=REFUSED reason=nothing-staged (run /api/build first)\n" as *u8); sys_exit(2); return 2 }
169
170 if ag_content_paths(live,stag,tgt)!=0{return 1}
171 let out: *u8 = sys_mmap(AG_CAP)
172 let ol: *i64 = sys_mmap(16) as *i64
173 let av: *i64 = sys_mmap(256) as *i64
174
175 // ---- STEP 3 (SUFFICIENCY): do the two binaries actually BEHAVE the same on a real workload?
176 av[0] = AG_BPROBE as i64
177 av[1] = tgt as i64
178 var n: i64 = 2
179 var s: i64 = 2
180 while s < argc { av[n] = argv[s] as i64; n = n + 1; s = s + 1 }
181 av[n] = 0
182 ol[0] = 0
183 tr_run_capture_cwd(AG_BPROBE, av, out, AG_CAP, ol, AG_TMO, AG_HOST)
184 let identical: i64 = agfind(out, ol[0], "IDENTICAL" as *u8)
185 let strong: i64 = agfind(out, ol[0], "STRONG" as *u8)
186
187 agp("ADOPT-GATE " as *u8); agp(tgt)
188 agp(" verdict=APPROVED content=GREEN behave=" as *u8)
189 if identical == 1 { agp("IDENTICAL" as *u8) } else { agp("DIFFERS" as *u8) }
190 agp(" evidence=" as *u8)
191 if strong == 1 { agp("STRONG" as *u8) } else { agp("WEAK" as *u8) }
192 // DIFFERS is NOT a refusal: a STALE-SAFE organ has source AHEAD of the deployed elf, so new behaviour
193 // is the POINT. What the caller needs is the difference stated, and its evidence GRADED, so a WEAK
194 // match cannot pass itself off as verification -- an identical usage line is not an identical organ.
195 if strong == 0 { agp(" note=WEAK-EVIDENCE:probe compared a usage line or an error path; pass real workload args before trusting this" as *u8) }
196 if identical == 0 { agp(" note=behaviour CHANGED (expected when source is ahead) -- read the diff before promoting" as *u8) }
197 agp("\n" as *u8)
198 return 0
199}