nx_staging_guard_candidate_t296.nx
buildroot/runtime/nx_staging_guard_candidate_t296.nx
about
nx_staging_guard.nx -- REFUSE A PROMOTE THAT WOULD WALK A TARGET BACKWARDS.
WHY (measured 2026-08-06): the deploy queue is armed to destroy work. md_promote_staged already
carries md_prov_ok, which refuses a promote whose CONTENT this target ALREADY HELD (seq1484).
That guard is blind to the COMMON CASE: a staged .new that was never itself promoted is a FIRST
SIGHTING, so md_prov_ok records it as the baseline and returns ALLOW -- even when the staged file
PREDATES the live binary it would replace. A .new older than its live .elf is not a pending
upgrade, it is A ROLLBACK WEARING AN UPGRADE FILENAME, and nothing in the staging area marks it
as one. It sits there looking deployable forever.
*SIZE IS A SCREEN, NOT PROOF (nx_treecanon_gate:15, and the banked law: a byte DECREASE after an
addition is a REVERT). A legitimate refactor can shrink a binary, and an operator-swap fix is
BYTE-IDENTICAL. So this module never decides on size alone: it also measures CAPABILITY-TOKEN
LOSS -- how many long printable tokens present in the LIVE binary are ABSENT from the staged one.
That is the honest reading of would-lose-capability; size is only the cheap first screen.
(2026-08-18: that sentence became TRUE IN CODE, not only in prose -- see the SHRINK note in
sg_classify. Whole-program DCE in nx_cc made "smaller" the normal direction of every rebuild.)
*IT REFUSES, IT NEVER DELETES. Every verdict is fail-closed and reversible: a refusal leaves live,
.prev and the staged file untouched (Rule 13 additive-only, Rule 26 never-brick). Nothing in this
module writes persistent state of any kind.
*THRESHOLDS ARE DATA (Rule 11), read from knowledge/staging_hygiene.conf; the consts below are
bootstrap defaults only, and every one of them is overridable without a rebuild (Rule 17).
DIALECT: this module is IMPORTED by nx_mgmt_data -> nx_mgmt_api, so it must compile under today's
hub compiler: plain-if (NO else -- seq533 desyncs the imported parser), no empty string literals
(seq907 aliases the next literal), <=6 params (seq239). ONE implementation, every consumer
(Rule 15) -- a guard that only the CLI imports is not a guard the deploy plane has.
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_contentdiff_lib.nx
imported by: nobody (leaf or entry point)
structs
| 256 | struct SgContentEvidence { state:i64, checked:i64, missing:i64, gained:i64, live_runs:i64, candidate_runs:i64, source_removed:i64, permil:i64 } |
| 310 | struct SgFileEvidence { state:i64,size:i64,mtime:i64 } |
consts
| 34 | const SG_STAT_SIZE_OFF: i64 = 48 |
| 35 | const SG_STAT_MTIME_OFF: i64 = 88 |
| 36 | const SG_STATBUF: i64 = 256 |
| 37 | const SG_DAY: i64 = 86400 |
| 38 | const SG_FILECAP: i64 = 8388608 |
| 39 | const SG_CONFCAP: i64 = 8192 |
| 42 | const SG_NONE: i64 = 0 |
| 43 | const SG_FORWARD: i64 = 1 |
| 44 | const SG_ORPHAN: i64 = 2 |
| 45 | const SG_BACKDATE: i64 = 3 |
| 46 | const SG_EXPIRED: i64 = 4 |
| 47 | const SG_SHRINK: i64 = 5 |
| 48 | const SG_CAPLOSS: i64 = 6 |
| 49 | const SG_UNQUALIFIED:i64=7 |
| 52 | const SG_O_LIVESZ: i64 = 0 |
| 53 | const SG_O_NEWSZ: i64 = 1 |
| 54 | const SG_O_LIVEMT: i64 = 2 |
| 55 | const SG_O_NEWMT: i64 = 3 |
| 56 | const SG_O_LOSTPERMIL: i64 = 4 |
| 57 | const SG_O_AGEDAYS: i64 = 5 |
| 58 | const SG_O_FLAGS: i64 = 6 |
| 59 | const SG_O_CHECKED: i64 = 7 |
| 60 | const SG_O_MISSING: i64 = 8 |
| 61 | const SG_O_SHRINKPERMIL: i64 = 9 |
| 64 | const SG_O_IDENTICAL: i64 = 10 |
| 65 | const SG_O_GAINED: i64 = 11 // tokens staged has that live lacks -- the half a loss detector cannot see |
| 66 | const SG_O_LIVERUNS: i64 = 12 // qualifying runs in live |
| 67 | const SG_O_CANDRUNS: i64 = 13 // qualifying runs in staged; >= liveruns means nothing was net-removed |
| 68 | const SG_O_CONTENT_STATE:i64=14 |
| 69 | const SG_O_SOURCE_REMOVED:i64=15 |
| 70 | const SG_OUT_SLOTS: i64 = 16 |
| 73 | const SG_C_MAXAGE: i64 = 0 |
| 74 | const SG_C_SHRINKTOL: i64 = 1 |
| 75 | const SG_C_CAPLOSS: i64 = 2 |
| 76 | const SG_C_MINTOK: i64 = 3 |
| 77 | const SG_C_MAXTOK: i64 = 4 |
| 78 | const SG_C_CAPLOSSABS: i64 = 5 |
| 79 | const SG_C_CAPLOSSMIN: i64 = 6 |
| 80 | const SG_C_SLOTS: i64 = 8 |
| 83 | const SG_D_MAXAGE: i64 = 14 |
| 84 | const SG_D_SHRINKTOL: i64 = 20 |
| 88 | const SG_D_CAPLOSS: i64 = 25 |
| 96 | const SG_D_CAPLOSSABS: i64 = 5 |
| 103 | const SG_D_CAPLOSSMIN: i64 = 2 |
| 104 | const SG_D_MINTOK: i64 = 12 |
| 105 | const SG_D_MAXTOK: i64 = 256 |
| 108 | const SG_MAXTOKLEN: i64 = 255 |
| 308 | const SG_ERR_NOENT:i64=-2 |
| 309 | const SG_IO:i64=-204 |
functions
| 110 | func sg_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } called by 1: sg_conf_get |
| 113 | func sg_size(path: *u8) -> i64 |
| 123 | func sg_mtime(path: *u8) -> i64 |
| 132 | func sg_slurp(path: *u8, buf: *u8, cap: i64) -> i64 |
| 154 | func sg_contains(b: *u8, bn: i64, nd: *u8, nl: i64) -> i64 |
| 175 | func sg_eq_at(b: *u8, pos: i64, key: *u8, kl: i64) -> i64 called by 1: sg_conf_get |
| 184 | func sg_parse_int_at(b: *u8, n: i64, pos: i64) -> i64 called by 1: sg_conf_get |
| 203 | func sg_conf_get(b: *u8, n: i64, key: *u8) -> i64 |
| 226 | func sg_conf_load(cfg: *i64) -> i64 |
| 257 | func sg_measure_bytes(lb:*u8,ln:i64,nb:*u8,nn:i64,cfg:*i64,e:*SgContentEvidence)->i64 called by 1: sg_content_paths calls 6: cds_initsys_mmap_trycds_measurecds_permilcds_closesys_munmap_direct |
| 278 | func sg_caploss(lb: *u8, ln: i64, nb: *u8, nn: i64, cfg: *i64, out2: *i64) -> i64 |
| 311 | func sg_file_evidence(path:*u8,e:*SgFileEvidence)->i64 |
| 319 | func sg_content_paths(livep:*u8,newp:*u8,lsz:i64,nsz:i64,cfg:*i64,out:*i64)->i64 |
| 343 | func sg_classify(livep:*u8,newp:*u8,now:i64,cfg:*i64,out:*i64)->i64 |
| 379 | func sg_allows(c: i64) -> i64 called by 1: sg_allows_override |
| 385 | func sg_class_name(c: i64) -> *u8 |
| 396 | func sg_allows_override(c:i64,explicitApproval:i64)->i64 calls 1: sg_allows |
| 410 | func sg_scratch() -> i64 calls 1: sys_mmap |