code wiki / _hdl_build / nx_mgmt_promote_gate.nx

nx_mgmt_promote_gate.nx source

↩ module page · 455 lines · 26898 B

1// nx_mgmt_promote_gate.nx -- referee for POST /api/promote: fail-closed, never-brick, AND lease-clean. 2// In-process (no socket): crafts request BYTES and feeds the pure handler ma_do_promote, with the whole 3// filesystem rooted at /tmp/nx_promote_gt so nothing it does can touch the serving root. mau_build_path 4// builds BARE relative paths, which is what makes that isolation total rather than hopeful. 5// 6// MIGRATED ONTO nx_gate_verdict 2026-08-20 (was D001: a hand-rolled `fails` counter and a bare sys_exit, 7// so /api/gate_run could read a verdict but nx_gate_green could not, and the declared tooth count could 8// drift from the executed one). Every tooth is now a gv_check, so declared == executed by construction. 9// 10// WHAT IS NEW HERE, AND WHY. /api/promote took its 300s lease and then RETURNED THROUGH THREE EXITS THAT 11// NEVER RELEASED IT: the stage-rename failure, and both md_promote_staged_ex failure exits (one of which 12// answers 200 NOTHING-STAGED, so it reads as a harmless no-op while holding the lane shut for five 13// minutes). The release was written once, at the bottom of the success path. That defect was independently 14// rediscovered five times in three weeks because NOTHING WATCHED THE LEASE PLANE -- every existing tooth 15// read the RESPONSE TEXT, and the response of a leaking call is indistinguishable from a clean one. 16// So these teeth ask the PLANE, not the handler: they try to take the lease afterwards. Busy means leaked. 17// 18// The second defect on the same endpoint was a magic number pair: the lease is held for 300s and the 19// refusal advertised retry_after_s:30, so a caller obeying the advice hammered a lock that could not be 20// free for another 270. Both now render from MA_PROMOTE_LEASE_TTL_S, and a tooth below builds its 21// expected substring FROM THAT SAME CONST -- so the two cannot drift apart again without this going RED. 22// 23// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26). 24import "nx_mgmt_api.nx" 25import "nx_syscalls.nx" 26import "nx_gate_verdict.nx" 27 28const PG_MODE_FILE: i64 = 0x1a4 29const PG_MODE_DIR: i64 = 0x1ed 30const PG_BUF: i64 = 262144 31const PG_SMALL: i64 = 256 32 33func glen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 34func gcat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o+i] = s[i]; i = i + 1 } return o + i } 35func gcatn(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[o+i] = s[i]; i = i + 1 } return o + n } 36func gcontains(hay: *u8, n: i64, needle: *u8) -> i64 { 37 let nn: i64 = glen(needle) 38 var i: i64 = 0 39 while i + nn <= n { 40 var m: i64 = 1 41 var j: i64 = 0 42 while j < nn { if (hay[i+j] as i64) != (needle[j] as i64) { m = 0; j = nn } else { j = j + 1 } } 43 if m == 1 { return 1 } 44 i = i + 1 45 } 46 return 0 47} 48// build "POST <line> HTTP/1.1\r\n...\r\n\r\n<body>" into dst; returns total length. 49func mkreq(dst: *u8, line: *u8, body: *u8) -> i64 { 50 let bn: i64 = glen(body) 51 var o: i64 = gcat(dst, 0, line) 52 o = gcat(dst, o, " HTTP/1.1\r\nHost: x\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " as *u8) 53 let t: *u8 = sys_mmap(24) 54 var m: i64 = bn 55 var k: i64 = 0 56 if m == 0 { t[0] = 48 as u8; k = 1 } 57 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 58 var z: i64 = 0 59 while z < k { dst[o] = t[k-1-z]; o = o + 1; z = z + 1 } 60 o = gcat(dst, o, "\r\n\r\n" as *u8) 61 o = gcatn(dst, o, body, bn) 62 return o 63} 64func wfile(path: *u8, buf: *u8, n: i64) -> i64 { 65 let fd: i64 = sys_openat_wr(path, PG_MODE_FILE) 66 if fd < 0 { return 0 - 1 } 67 sys_write(fd, buf, n) 68 sys_close(fd) 69 return 0 70} 71func bytes_eq(path: *u8, want: *u8, wn: i64) -> i64 { 72 let szp: *i64 = sys_mmap(16) as *i64 73 let b: *u8 = sys_read_file(path, szp) 74 if (b as i64) == 0 { return 0 } 75 if szp[0] != wn { return 0 } 76 var i: i64 = 0 77 while i < wn { if (b[i] as i64) != (want[i] as i64) { return 0 } i = i + 1 } 78 return 1 79} 80func file_absent(path: *u8) -> i64 { 81 let fd: i64 = sys_openat_rd(path) 82 if fd < 0 { return 1 } 83 sys_close(fd) 84 return 0 85} 86 87// ---- THE LEASE PROBE: ASK THE PLANE, NEVER THE RESPONSE ------------------------------------------ 88// A handler that leaks its lease answers EXACTLY the same bytes as one that does not, so no assertion 89// over the response can see this class at all. The only witness is the lease plane itself, and the only 90// question it answers honestly is "can I take this now". Returns 0 FREE, 1 HELD, 2 UNOBSERVABLE -- 91// three states, because a fork failure or an absent nx_lease.elf must not read as either verdict. 92// The probe TTL is 1 second, so even a crashed probe cannot hold the name it just tested. 93func lease_state(lname: *u8) -> i64 { 94 let rc: i64 = md_lease_run("acquire" as *u8, lname, "nx-promote-gate-probe" as *u8, "1" as *u8, 4, "/tmp/nx_promote_gt/probe.out" as *u8) 95 if rc == 3 { return 1 } 96 if rc != 0 { return 2 } 97 md_lease_run("release" as *u8, lname, "nx-promote-gate-probe" as *u8, "0" as *u8, 3, "/tmp/nx_promote_gt/probe.out" as *u8) 98 return 0 99} 100// 1 iff the promote lease for <target> is demonstrably NOT held. 101func lease_free_for(target: *u8) -> i64 { 102 let lb: *u8 = sys_mmap(PG_SMALL) 103 md_lease_name_pfx("promote-" as *u8, target, lb) 104 if lease_state(lb) == 0 { return 1 } 105 return 0 106} 107 108// ---- CLEAR THE FIXTURE LEASES AT SETUP, NEVER ONLY AT TEARDOWN ------------------------------- 109// A teardown does not run when a run crashes -- and worse here than usual, because THE VERY DEFECT 110// THIS GATE EXISTS TO CATCH LEAKS A 300 SECOND LEASE. So a RED run poisons the next five minutes of 111// runs with a lease-busy refusal that is indistinguishable from a second, unrelated failure. 112// MEASURED 2026-08-20 during this gate's own bite proof: the mutant run leaked promote-nxpromoteleak, 113// and the NEXT run reported four failures where its mutation could only cause two. A GATE THAT IS NOT 114// IDEMPOTENT REPORTS ON ITS FIRST RUN AND LIES ABOUT EVERY RUN AFTER, and a gate whose subject can 115// leak state must clear that state BEFORE it measures, not after. Owner is required by nx_lease, so 116// all three owners this file can produce are cleared; a release that does not apply is a no-op. 117func lease_clear(target: *u8) -> i64 { 118 let lb: *u8 = sys_mmap(PG_SMALL) 119 md_lease_name_pfx("promote-" as *u8, target, lb) 120 md_lease_run("release" as *u8, lb, "mgmt-api-promote" as *u8, "0" as *u8, 3, "/tmp/nx_promote_gt/probe.out" as *u8) 121 md_lease_run("release" as *u8, lb, "neg-control-holder" as *u8, "0" as *u8, 3, "/tmp/nx_promote_gt/probe.out" as *u8) 122 md_lease_run("release" as *u8, lb, "nx-promote-gate-probe" as *u8, "0" as *u8, 3, "/tmp/nx_promote_gt/probe.out" as *u8) 123 return 0 124} 125 126 127func tc_refusal_gate() -> i64 { 128 sys_mkdir("/tmp/nx_toolchain_refusal_gate" as *u8, 0x1ed) 129 if sys_chdir("/tmp/nx_toolchain_refusal_gate" as *u8) != 0 { return 2 } 130 sys_mkdir("buildroot" as *u8, 0x1ed) 131 sys_mkdir("buildroot/_offc" as *u8, 0x1ed) 132 sys_mkdir("knowledge" as *u8, 0x1ed) 133 let nm: *u8 = "nx_sov_build_run.elf" as *u8 134 let live: *u8 = "buildroot/_offc/nx_sov_build_run.elf" as *u8 135 let prev: *u8 = "buildroot/_offc/nx_sov_build_run.elf.prev" as *u8 136 let staged: *u8 = "nx_sov_build_run.elf.new" as *u8 137 let older: *u8 = sys_mmap(MD_TC_MIN_ELF) 138 let newer: *u8 = sys_mmap(MD_TC_MIN_ELF) 139 older[0]=127 as u8; older[1]=69 as u8; older[2]=76 as u8; older[3]=70 as u8 140 newer[0]=127 as u8; newer[1]=69 as u8; newer[2]=76 as u8; newer[3]=70 as u8 141 older[4]=1 as u8; newer[4]=2 as u8 142 if wfile(live, newer, MD_TC_MIN_ELF) != 0 { return 2 } 143 if wfile(prev, newer, MD_TC_MIN_ELF) != 0 { return 2 } 144 if wfile(staged, older, MD_TC_MIN_ELF) != 0 { return 2 } 145 if wfile(MD_PROV_HIST, older, 0) != 0 { return 2 } 146 let oldhash: i64 = md_prov_hash(staged) 147 let newhash: i64 = md_prov_hash(live) 148 if oldhash == 0 { return 2 } 149 if newhash == 0 { return 2 } 150 if oldhash == newhash { return 2 } 151 if md_prov_record(live, oldhash) != 0 { return 2 } 152 if md_prov_record(live, newhash) != 0 { return 2 } 153 let history: i64 = md_prov_hash(MD_PROV_HIST) 154 let out: *u8 = sys_mmap(262144) 155 let installed: *i64 = sys_mmap(8) as *i64 156 let ctr: *i64 = gv_ctr() 157 var attempt: i64 = 0 158 while attempt < 2 { 159 let n: i64 = ma_tc_install_checked(nm, out, installed) 160 gv_check("toolchain-refusal-1" as *u8, 1 - (n <= 0), ctr) 161 gv_check("toolchain-refusal-2" as *u8, 1 - (installed[0] != (0 - 2)), ctr) 162 gv_check("toolchain-refusal-3" as *u8, 1 - (gcontains(out,n,"HTTP/1.1 400" as *u8) != 1), ctr) 163 gv_check("toolchain-refusal-4" as *u8, 1 - (gcontains(out,n,"TOOLCHAIN_PROVENANCE_REFUSED" as *u8) != 1), ctr) 164 gv_check("toolchain-refusal-5" as *u8, 1 - (gcontains(out,n,"TOOLCHAIN-PROMOTED" as *u8) != 0), ctr) 165 gv_check("toolchain-refusal-6" as *u8, 1 - (bytes_eq(live,newer,MD_TC_MIN_ELF) != 1), ctr) 166 gv_check("toolchain-refusal-7" as *u8, 1 - (bytes_eq(prev,newer,MD_TC_MIN_ELF) != 1), ctr) 167 gv_check("toolchain-refusal-8" as *u8, 1 - (bytes_eq(staged,older,MD_TC_MIN_ELF) != 1), ctr) 168 gv_check("toolchain-refusal-9" as *u8, 1 - (md_prov_hash(MD_PROV_HIST) != history), ctr) 169 attempt = attempt + 1 170 } 171 if wfile(staged,older,4) != 0 { return 2 } 172 let bad: i64 = ma_tc_install_checked(nm,out,installed) 173 gv_check("toolchain-refusal-10" as *u8, 1 - (bad <= 0), ctr) 174 gv_check("toolchain-refusal-11" as *u8, 1 - (installed[0] != 0), ctr) 175 gv_check("toolchain-refusal-12" as *u8, 1 - (gcontains(out,bad,"no valid staged" as *u8) != 1), ctr) 176 gv_check("toolchain-refusal-13" as *u8, 1 - (bytes_eq(live,newer,MD_TC_MIN_ELF) != 1), ctr) 177 gv_check("toolchain-refusal-14" as *u8, 1 - (bytes_eq(prev,newer,MD_TC_MIN_ELF) != 1), ctr) 178 gv_check("toolchain-refusal-15" as *u8, 1 - (bytes_eq(staged,older,4) != 1), ctr) 179 gv_check("toolchain-refusal-16" as *u8, 1 - (md_prov_hash(MD_PROV_HIST) != history), ctr) 180 older[4] = 3 as u8 181 if wfile(staged,older,MD_TC_MIN_ELF) != 0 { return 2 } 182 let accepted: i64 = ma_tc_install_checked(nm,out,installed) 183 gv_check("accepted-install-continues-to-canary-boundary" as *u8,accepted == 0,ctr) 184 gv_check("accepted-install-reports-real-size" as *u8,installed[0] == MD_TC_MIN_ELF,ctr) 185 gv_check("accepted-install-live-is-candidate" as *u8,bytes_eq(live,older,MD_TC_MIN_ELF),ctr) 186 gv_check("accepted-install-banks-previous-live" as *u8,bytes_eq(prev,newer,MD_TC_MIN_ELF),ctr) 187 gv_check("accepted-install-consumes-stage" as *u8,file_absent(staged),ctr) 188 gv_check("rollback-reports-restoration" as *u8,md_tc_rollback(nm),ctr) 189 gv_check("rollback-restores-exact-previous-bytes" as *u8,bytes_eq(live,newer,MD_TC_MIN_ELF),ctr) 190 return gv_verdict("TOOLCHAIN-REFUSAL" as *u8,ctr,"refusal preserves artifacts; accepted install and rollback restore exact bytes" as *u8) 191} 192func main(argc: i64, argv: *i64) -> i64 { 193 if argc == 1 { return legacy_promote_gate() } 194 if argc == 2 { 195 if glen(argv[1] as *u8) == 17 { 196 if gcontains(argv[1] as *u8,17,"toolchain-refusal" as *u8) == 1 { return tc_refusal_gate() } 197 } 198 } 199 return 2 200} 201 202func legacy_promote_gate() -> i64 { 203 let ctr: *i64 = gv_ctr() 204 gv_head("nx_mgmt_promote_gate -- POST /api/promote: fail-closed, never-brick, and LEASE-CLEAN on every exit" as *u8) 205 sys_mkdir("/tmp/nx_promote_gt" as *u8, PG_MODE_DIR) 206 sys_chdir("/tmp/nx_promote_gt" as *u8) 207 let out: *u8 = sys_mmap(PG_BUF) 208 let req: *u8 = sys_mmap(PG_BUF) 209 210 // fixture bytes: a valid ELF header, a distinct "old live", and a non-ELF blob. 211 let elfnew: *u8 = sys_mmap(16) 212 elfnew[0] = 0x7f as u8 213 elfnew[1] = 69 as u8 214 elfnew[2] = 76 as u8 215 elfnew[3] = 70 as u8 216 elfnew[4] = 78 as u8 217 elfnew[5] = 69 as u8 218 elfnew[6] = 87 as u8 219 let oldlive: *u8 = sys_mmap(16) 220 oldlive[0] = 79 as u8 221 oldlive[1] = 76 as u8 222 oldlive[2] = 68 as u8 223 let garbage: *u8 = sys_mmap(16) 224 garbage[0] = 71 as u8 225 garbage[1] = 65 as u8 226 garbage[2] = 82 as u8 227 228 // ---- FIXTURE: A DECLARED-KIND CONF THE HANDLER CAN ACTUALLY READ ---------------------------- 229 // ok_kind_of_path opens knowledge/status/organ_kind.conf RELATIVE TO CWD. Under /tmp that file did 230 // not exist, so every "daemon target" tooth here was silently testing the UNDECLARED fall-through 231 // instead of the daemon refusal it was named for -- a tooth measuring a different subject than its 232 // name claims. Assembling the conf at runtime restores the tooth's meaning and keeps it hermetic. 233 sys_mkdir("knowledge" as *u8, PG_MODE_DIR) 234 sys_mkdir("knowledge/status" as *u8, PG_MODE_DIR) 235 let kconf: *u8 = sys_mmap(PG_SMALL) 236 var kn: i64 = gcat(kconf, 0, "sites daemon\n" as *u8) 237 kn = gcat(kconf, kn, "nx_tools_api_serve daemon\n" as *u8) 238 wfile("knowledge/status/organ_kind.conf" as *u8, kconf, kn) 239 240 // clean slate (idempotent: a previous run's artefacts must not decide this one's verdict) 241 fio_unlink("sites.sov.elf.new" as *u8) 242 fio_unlink("sites.elf" as *u8) 243 fio_unlink("nxpromotepos.sov.elf.new" as *u8) 244 fio_unlink("nxpromotepos.elf" as *u8) 245 fio_unlink("nxpromotepos.elf.prev" as *u8) 246 fio_unlink("nxpromotepos.elf.new" as *u8) 247 fio_unlink("nxpromoteleak.sov.elf.new" as *u8) 248 fio_unlink("nxpromoteleak.elf" as *u8) 249 fio_unlink("nx_ecomat_seed.elf" as *u8) 250 fio_unlink("nx_ecomat_seed.sov.elf.new" as *u8) 251 fio_unlink("nx_ecomat_beat.sov.elf.new" as *u8) 252 fio_unlink("nx_ecomat_beat.elf" as *u8) 253 fio_unlink("nx_ecomat_beat.elf.prev" as *u8) 254 fio_unlink("nxpromotebusy.sov.elf.new" as *u8) 255 fio_unlink("nxpromotebusy.elf" as *u8) 256 fio_unlink("nxpromotedig.sov.elf.new" as *u8) 257 fio_unlink("nxpromotedig.elf" as *u8) 258 fio_unlink("nxpromotenoconf.sov.elf.new" as *u8) 259 fio_unlink("nxpromotenoconf.elf" as *u8) 260 261 lease_clear("nxpromotepos" as *u8) 262 lease_clear("nxpromoteleak" as *u8) 263 lease_clear("nxpromotebusy" as *u8) 264 lease_clear("nxpromotedig" as *u8) 265 lease_clear("nxpromotenoconf" as *u8) 266 lease_clear("nx_ecomat_seed" as *u8) 267 lease_clear("nx_ecomat_beat" as *u8) 268 lease_clear("sites" as *u8) 269 270 // ---- PRECONDITION: the lease plane must answer, or every lease tooth below is unobservable ---- 271 // A round trip on a name nothing else uses. If this cannot be done the gate SKIPs rather than 272 // reporting the subject broken -- "I could not look" is not "I looked and it is broken". 273 var planeok: i64 = 0 274 if lease_state("promote-nxpromoteplaneprobe" as *u8) == 0 { planeok = 1 } 275 gv_need("lease plane answers an acquire/release round trip" as *u8, planeok, ctr) 276 277 // ---- T1 ANTI-VACUITY, FIRST: A GENUINE PROMOTE STILL WORKS ----------------------------------- 278 // This tooth is what a lazy "fix" cannot pass. Never acquiring the lease at all, or refusing every 279 // request, would satisfy every lease-is-free assertion below and silently break promotion. The 280 // positive control has to come first for exactly that reason. 281 wfile("nxpromotepos.sov.elf.new" as *u8, elfnew, 7) 282 wfile("nxpromotepos.elf" as *u8, oldlive, 3) 283 let n1: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nxpromotepos&confirm=yes&allow_unverified_bytes=yes&allow_capability_loss=yes" as *u8) 284 let r1: i64 = ma_do_promote(req, n1, out) 285 var t1: i64 = 1 286 if gcontains(out, r1, "200 OK" as *u8) == 0 { t1 = 0 } 287 if gcontains(out, r1, "PROMOTED" as *u8) == 0 { t1 = 0 } 288 if bytes_eq("nxpromotepos.elf" as *u8, elfnew, 7) == 0 { t1 = 0 } 289 if bytes_eq("nxpromotepos.elf.prev" as *u8, oldlive, 3) == 0 { t1 = 0 } 290 if file_absent("nxpromotepos.sov.elf.new" as *u8) == 0 { t1 = 0 } 291 gv_check("anti-vacuity-a-genuine-promote-still-installs-and-banks-prev" as *u8, t1, ctr) 292 293 // ---- T2 the success path releases what it took ------------------------------------------------ 294 gv_check("lease-released-after-a-successful-promote" as *u8, lease_free_for("nxpromotepos" as *u8), ctr) 295 296 // ---- T3 THE PRE-LEASE NO-OP MUST NOT TOUCH THE PLANE ------------------------------------------ 297 // Corrects the record: the FIRST NOTHING-STAGED sits BEFORE the acquire, so a bare probe never 298 // leaked. Pinning that here stops the next reader re-deriving it from the same wrong hypothesis. 299 // THE TARGET NAME IS PART OF THE FIXTURE. An UNDECLARED name with nothing staged is refused by the 300 // promotable check LONG before the branch this tooth is named for, so the first draft of it passed 301 // judgement on name policy instead. A legacy-allowlisted name reaches the staged-artefact check. 302 wfile("nx_ecomat_seed.elf" as *u8, oldlive, 3) 303 let n3: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nx_ecomat_seed&confirm=yes" as *u8) 304 let r3: i64 = ma_do_promote(req, n3, out) 305 var t3: i64 = 1 306 if gcontains(out, r3, "NOTHING-STAGED" as *u8) == 0 { t3 = 0 } 307 if lease_free_for("nx_ecomat_seed" as *u8) == 0 { t3 = 0 } 308 gv_check("neg-control-the-pre-lease-nothing-staged-no-op-never-takes-the-lease" as *u8, t3, ctr) 309 310 // ---- T4 THE DECISIVE ONE: A POST-ACQUIRE FAILURE EXIT MUST RELEASE ---------------------------- 311 // The lever is deterministic and needs no timing: make <t>.elf.new a DIRECTORY, so the very first 312 // mutating step after the acquire -- renaming the staged file onto it -- fails with EISDIR. That is 313 // one of the three exits that used to return while still holding the lease. 314 wfile("nxpromoteleak.sov.elf.new" as *u8, elfnew, 7) 315 wfile("nxpromoteleak.elf" as *u8, oldlive, 3) 316 sys_mkdir("nxpromoteleak.elf.new" as *u8, PG_MODE_DIR) 317 let n4: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nxpromoteleak&confirm=yes&allow_unverified_bytes=yes&allow_capability_loss=yes" as *u8) 318 let r4: i64 = ma_do_promote(req, n4, out) 319 gv_check("lease-released-after-a-post-acquire-failure-exit" as *u8, lease_free_for("nxpromoteleak" as *u8), ctr) 320 321 // ---- T5 ...AND THAT EXIT STILL REFUSES, so T4 cannot be passed by promoting anyway ------------- 322 var t5: i64 = 1 323 if gcontains(out, r4, "stage-rename failed" as *u8) == 0 { t5 = 0 } 324 if bytes_eq("nxpromoteleak.elf" as *u8, oldlive, 3) == 0 { t5 = 0 } 325 gv_check("neg-control-that-same-failure-exit-still-refuses-and-leaves-live-untouched" as *u8, t5, ctr) 326 327 // ---- T6 THE ADVERTISED BACKOFF IS THE CONFIGURED TTL ------------------------------------------ 328 // The expected substring is BUILT FROM MA_PROMOTE_LEASE_TTL_S, not typed here. That is the whole 329 // point: a reader who changes the lock duration moves this tooth with it, and a reader who hardcodes 330 // a different number into the message moves the message away from this tooth. One source or RED. 331 wfile("nxpromotebusy.sov.elf.new" as *u8, elfnew, 7) 332 wfile("nxpromotebusy.elf" as *u8, oldlive, 3) 333 let hb: *u8 = sys_mmap(PG_SMALL) 334 md_lease_name_pfx("promote-" as *u8, "nxpromotebusy" as *u8, hb) 335 let heldrc: i64 = md_lease_run("acquire" as *u8, hb, "neg-control-holder" as *u8, "60" as *u8, 4, "/tmp/nx_promote_gt/probe.out" as *u8) 336 var heldok: i64 = 0 337 if heldrc == 0 { heldok = 1 } 338 gv_need("a foreign holder can be planted on the promote lease" as *u8, heldok, ctr) 339 let n6: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nxpromotebusy&confirm=yes&allow_unverified_bytes=yes&allow_capability_loss=yes" as *u8) 340 let r6: i64 = ma_do_promote(req, n6, out) 341 // TS9 (2026-08-30): a 300 s exclusive lock is NOT a retry situation -- the refusal advertises the 342 // DISTINCT CLASS instead of a wait scalar (ts_retry_after_derived; RFC 9110 puts no accuracy 343 // obligation on Retry-After, so any scalar synchronises the herd -- measured on this very lease). 344 var t6: i64 = 1 345 if gcontains(out, r6, "lease-busy" as *u8) == 0 { t6 = 0 } 346 if gcontains(out, r6, "neg-control-holder" as *u8) == 0 { t6 = 0 } 347 if gcontains(out, r6, "LEASE-HELD-DO-NOT-RETRY" as *u8) == 0 { t6 = 0 } 348 gv_check("lease-busy-refusal-advertises-the-do-not-retry-class-not-a-wait-scalar" as *u8, t6, ctr) 349 350 // ---- T7 and the stale 10x-short advice is gone ------------------------------------------------ 351 var t7: i64 = 1 352 if gcontains(out, r6, "\"retry_after_s\":" as *u8) == 1 { t7 = 0 } 353 if bytes_eq("nxpromotebusy.sov.elf.new" as *u8, elfnew, 7) == 0 { t7 = 0 } 354 gv_check("neg-control-no-wait-scalar-of-ANY-value-rides-the-lease-refusal-and-a-busy-refusal-stages-nothing" as *u8, t7, ctr) 355 md_lease_run("release" as *u8, hb, "neg-control-holder" as *u8, "0" as *u8, 3, "/tmp/nx_promote_gt/probe.out" as *u8) 356 357 // ---- T8 a DECLARED daemon must be sent to the health-checked /api/deploy ---------------------- 358 wfile("sites.sov.elf.new" as *u8, elfnew, 7) 359 let n8: i64 = mkreq(req, "POST /api/promote" as *u8, "target=sites&confirm=yes" as *u8) 360 let r8: i64 = ma_do_promote(req, n8, out) 361 var t8: i64 = 1 362 if gcontains(out, r8, "400" as *u8) == 0 { t8 = 0 } 363 if gcontains(out, r8, "/api/deploy" as *u8) == 0 { t8 = 0 } 364 if bytes_eq("sites.sov.elf.new" as *u8, elfnew, 7) == 0 { t8 = 0 } 365 if file_absent("sites.elf" as *u8) == 0 { t8 = 0 } 366 gv_check("neg-control-declared-daemon-refused-to-api-deploy-staged-untouched" as *u8, t8, ctr) 367 368 // ---- T9 path escape --------------------------------------------------------------------------- 369 let n9: i64 = mkreq(req, "POST /api/promote" as *u8, "target=../etc/x&confirm=yes" as *u8) 370 let r9: i64 = ma_do_promote(req, n9, out) 371 var t9: i64 = 1 372 if gcontains(out, r9, "400" as *u8) == 0 { t9 = 0 } 373 if gcontains(out, r9, "invalid target name" as *u8) == 0 { t9 = 0 } 374 gv_check("neg-control-path-escape-refused-by-sanitize" as *u8, t9, ctr) 375 376 // ---- T10 nothing staged and nothing live ------------------------------------------------------ 377 let n10: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nx_ecomat_beat&confirm=yes" as *u8) 378 let r10: i64 = ma_do_promote(req, n10, out) 379 var t10: i64 = 1 380 if gcontains(out, r10, "400" as *u8) == 0 { t10 = 0 } 381 // PHRASE MOVED 2026-08-20 (lane J per-conjunct refusals): the organ path's no-staged-no-live answer 382 // is its own named text now; "no valid staged" survives only on the toolchain-upload path. 383 if gcontains(out, r10, "no staged .elf.new and no live artefact" as *u8) == 0 { t10 = 0 } 384 gv_check("neg-control-no-staged-and-no-live-artefact-refused" as *u8, t10, ctr) 385 386 // ---- T11 a non-ELF staged blob is never installed --------------------------------------------- 387 // NOTE the ordering dependency with the tooth above: it needs nx_ecomat_beat to have NO artefacts, 388 // this one stages them. Sequential, not incidental. 389 wfile("nx_ecomat_beat.sov.elf.new" as *u8, garbage, 3) 390 wfile("nx_ecomat_beat.elf" as *u8, oldlive, 3) 391 let n11: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nx_ecomat_beat&confirm=yes&allow_unverified_bytes=yes" as *u8) 392 let r11: i64 = ma_do_promote(req, n11, out) 393 var t11: i64 = 1 394 if gcontains(out, r11, "PROMOTED" as *u8) == 1 { t11 = 0 } 395 if bytes_eq("nx_ecomat_beat.elf" as *u8, oldlive, 3) == 0 { t11 = 0 } 396 if lease_free_for("nx_ecomat_beat" as *u8) == 0 { t11 = 0 } 397 gv_check("neg-control-non-elf-staged-never-installed-live-untouched-lease-untouched" as *u8, t11, ctr) 398 399 // ---- T12 confirm=yes is mandatory ------------------------------------------------------------- 400 wfile("nxpromotenoconf.sov.elf.new" as *u8, elfnew, 7) 401 wfile("nxpromotenoconf.elf" as *u8, oldlive, 3) 402 let n12: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nxpromotenoconf" as *u8) 403 let r12: i64 = ma_do_promote(req, n12, out) 404 var t12: i64 = 1 405 if gcontains(out, r12, "400" as *u8) == 0 { t12 = 0 } 406 if gcontains(out, r12, "confirm=yes" as *u8) == 0 { t12 = 0 } 407 if bytes_eq("nxpromotenoconf.sov.elf.new" as *u8, elfnew, 7) == 0 { t12 = 0 } 408 if bytes_eq("nxpromotenoconf.elf" as *u8, oldlive, 3) == 0 { t12 = 0 } 409 gv_check("neg-control-missing-confirm-refused-staged-and-live-untouched" as *u8, t12, ctr) 410 411 // ---- T13 the digest is required, and refusing for it must not take the lease ------------------ 412 wfile("nxpromotedig.sov.elf.new" as *u8, elfnew, 7) 413 wfile("nxpromotedig.elf" as *u8, oldlive, 3) 414 let n13: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nxpromotedig&confirm=yes" as *u8) 415 let r13: i64 = ma_do_promote(req, n13, out) 416 var t13: i64 = 1 417 if gcontains(out, r13, "digest-required" as *u8) == 0 { t13 = 0 } 418 if bytes_eq("nxpromotedig.elf" as *u8, oldlive, 3) == 0 { t13 = 0 } 419 if lease_free_for("nxpromotedig" as *u8) == 0 { t13 = 0 } 420 gv_check("neg-control-expect-sha256-required-live-untouched-lease-untouched" as *u8, t13, ctr) 421 422 // ---- T14 a declared daemon in the TOOLS family is refused the same way ------------------------ 423 let n14: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nx_tools_api_serve&confirm=yes" as *u8) 424 let r14: i64 = ma_do_promote(req, n14, out) 425 var t14: i64 = 1 426 if gcontains(out, r14, "/api/deploy" as *u8) == 0 { t14 = 0 } 427 let n14b: i64 = mkreq(req, "POST /api/promote" as *u8, "target=nx_shelltool&confirm=yes" as *u8) 428 let r14b: i64 = ma_do_promote(req, n14b, out) 429 // PHRASE MOVED 2026-08-20: a tool with a LIVE artefact and nothing staged now gets the recoverable 430 // NOTHING-STAGED receipt (live_sha reported so a lost promote response can be reconciled). 431 if gcontains(out, r14b, "NOTHING-STAGED" as *u8) == 0 { t14 = 0 } 432 gv_check("neg-control-declared-tools-daemon-refused-while-a-tool-organ-reaches-the-staging-check" as *u8, t14, ctr) 433 434 // cleanup -- leaves the fixture dir reusable; the .elf.new DIRECTORY is deliberately left in place 435 // because sys_rmdir is not composed here and re-creating it is idempotent anyway. 436 fio_unlink("sites.sov.elf.new" as *u8) 437 fio_unlink("sites.elf" as *u8) 438 fio_unlink("nxpromotepos.elf" as *u8) 439 fio_unlink("nxpromotepos.elf.prev" as *u8) 440 fio_unlink("nxpromoteleak.sov.elf.new" as *u8) 441 fio_unlink("nxpromoteleak.elf" as *u8) 442 fio_unlink("nx_ecomat_seed.elf" as *u8) 443 fio_unlink("nx_ecomat_beat.sov.elf.new" as *u8) 444 fio_unlink("nx_ecomat_beat.elf" as *u8) 445 fio_unlink("nxpromotebusy.sov.elf.new" as *u8) 446 fio_unlink("nxpromotebusy.elf" as *u8) 447 fio_unlink("nxpromotedig.sov.elf.new" as *u8) 448 fio_unlink("nxpromotedig.elf" as *u8) 449 fio_unlink("nxpromotenoconf.sov.elf.new" as *u8) 450 fio_unlink("nxpromotenoconf.elf" as *u8) 451 452 let rc: i64 = gv_verdict("MGMT-PROMOTE-GATE" as *u8, ctr, "the promote lease is released on every exit that takes it, and the backoff the endpoint advertises is rendered from the same constant that sets the lock duration" as *u8) 453 sys_exit(rc) 454 return rc 455}