code wiki / _hdl_build / nx_mgmt_promote_gate.nx
nx_mgmt_promote_gate.nx
buildroot/runtime/_hdl_build/nx_mgmt_promote_gate.nx
about
nx_mgmt_promote_gate.nx -- referee for POST /api/promote: fail-closed, never-brick, AND lease-clean.
In-process (no socket): crafts request BYTES and feeds the pure handler ma_do_promote, with the whole
filesystem rooted at /tmp/nx_promote_gt so nothing it does can touch the serving root. mau_build_path
builds BARE relative paths, which is what makes that isolation total rather than hopeful.
MIGRATED ONTO nx_gate_verdict 2026-08-20 (was D001: a hand-rolled `fails` counter and a bare sys_exit,
so /api/gate_run could read a verdict but nx_gate_green could not, and the declared tooth count could
drift from the executed one). Every tooth is now a gv_check, so declared == executed by construction.
WHAT IS NEW HERE, AND WHY. /api/promote took its 300s lease and then RETURNED THROUGH THREE EXITS THAT
NEVER RELEASED IT: the stage-rename failure, and both md_promote_staged_ex failure exits (one of which
answers 200 NOTHING-STAGED, so it reads as a harmless no-op while holding the lane shut for five
minutes). The release was written once, at the bottom of the success path. That defect was independently
rediscovered five times in three weeks because NOTHING WATCHED THE LEASE PLANE -- every existing tooth
read the RESPONSE TEXT, and the response of a leaking call is indistinguishable from a clean one.
So these teeth ask the PLANE, not the handler: they try to take the lease afterwards. Busy means leaked.
The second defect on the same endpoint was a magic number pair: the lease is held for 300s and the
refusal advertised retry_after_s:30, so a caller obeying the advice hammered a lock that could not be
free for another 270. Both now render from MA_PROMOTE_LEASE_TTL_S, and a tooth below builds its
expected substring FROM THAT SAME CONST -- so the two cannot drift apart again without this going RED.
license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26).
dependencies 3 imports · 0 importers
imports: nx_mgmt_api.nxnx_syscalls.nxnx_gate_verdict.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 28 | const PG_MODE_FILE: i64 = 0x1a4 |
| 29 | const PG_MODE_DIR: i64 = 0x1ed |
| 30 | const PG_BUF: i64 = 262144 |
| 31 | const PG_SMALL: i64 = 256 |
functions
| 33 | func glen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 34 | func 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 } |
| 35 | func 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 } called by 1: mkreq |
| 36 | func gcontains(hay: *u8, n: i64, needle: *u8) -> i64 |
| 49 | func mkreq(dst: *u8, line: *u8, body: *u8) -> i64 |
| 64 | func wfile(path: *u8, buf: *u8, n: i64) -> i64 |
| 71 | func bytes_eq(path: *u8, want: *u8, wn: i64) -> i64 |
| 80 | func file_absent(path: *u8) -> i64 |
| 93 | func lease_state(lname: *u8) -> i64 |
| 101 | func lease_free_for(target: *u8) -> i64 |
| 117 | func lease_clear(target: *u8) -> i64 |
| 127 | func tc_refusal_gate() -> i64 |
| 192 | func main(argc: i64, argv: *i64) -> i64 |
| 202 | func legacy_promote_gate() -> i64 |