nx_artifactdrift.nx
buildroot/runtime/nx_artifactdrift.nx
about
nx_artifactdrift.nx -- IS THE BINARY WE ARE SERVING THE ONE THE LAST BUILD PRODUCED?
⚠THIS ORGAN WAS NAMED WRONG TWICE, AND BOTH GUARDS WERE RIGHT. It shipped as `nx_promotestale` and
nx_job_run's launch guard refused it: that guard splits the name on '_' and denies any SEGMENT
STARTING WITH a control-plane stem, so "promotestale" reads as promote. Renamed to `nx_servedrift`,
/api/promote then refused it as a DAEMON and nx_job_run refused it again -- "servedrift" starts with
"serve", and that guard's header states it chose stem-prefix DELIBERATELY so `nx_server` could never
escape, calling an escaped server "the brick".
-- BOTH REFUSALS WERE CORRECT. A launch guard must be wrong in the direction of REFUSING, and a name
that starts with `serve` genuinely looks like a server. The tempting fix -- patch an allow-path into a
never-brick guard so it consults organ_kind.conf -- would trade a real safety property for a bad name.
-- SO THE NAME MOVED, NOT THE GUARD: this measures DRIFT BETWEEN THE BUILT AND SERVED ARTIFACT, which
`artifactdrift` says exactly and claims to be no kind of server.
★★AN ORGAN'S NAME IS PART OF ITS INTERFACE, BECAUSE THE ESTATE'S OWN SAFETY RULES READ IT -- and when a
guard and a name disagree, the guard is the thing with a proof behind it.
WHY IT EXISTS, MEASURED 2026-08-14: two organs were found SERVING STALE CODE purely because a human
happened to notice nx_catalog's BUILT and PROMOTED columns disagree -- nx_vizsla_digest (47,402 served
vs 47,596 built) and nx_vizsla_plan (176,882 served vs 217,561 built: ~40 KB, a FIFTH of the organ,
missing from the running binary). Both were invisible to every health check the estate runs.
-- A RUNNING BINARY THAT IS NOT WHAT ITS SOURCE BUILT IS A SILENT CORRECTNESS AND SECURITY DEFECT:
every fix, every audit and every gate verdict then describes code that is not the code being executed.
-- SPOTTING IT BY EYE DOES NOT SCALE. nx_catalog answers this one NAME at a time; this sweeps.
nx_artifactdrift [judge] [registry] [buildroot] [srcroot_primary] [srcroot_secondary] [declare_conf] [trend_log]
THE OWNER-SIGNAL AXIS (DM5 ps_owner_scan, 2026-08-20). Every BEHIND row carries an OWNER SIGNAL, so a
promote list built from this census can never omit the check. BEHIND alone is NECESSARY BUT NOT
SUFFICIENT for "promote me": a BEHIND row can be ANOTHER SEAT'S WORK IN FLIGHT -- measured live
2026-08-15 on nx_oo_extract_gate, where the only thing that stopped the promote was a human looking.
STATES, a partition over the BEHIND rows and NOTHING ELSE:
DECLARED a row in the declaration conf claims the target -- a STATEMENT, so it outranks the
inferences below and is the mechanism for the standing "declare any build you do
not promote" law
IN-FLIGHT-SUSPECT scratch siblings <target>.nx.* beside the source, or a same-day source touch
SRC-UNRESOLVED no source under either root -- the axis ABSTAINS; it does not acquit
UNDECLARED no claim and no signal: the ONLY class eligible to be CONSIDERED, never an order
⚠IT IS A SEPARATE AXIS, exactly like the INERT exec-bit axis and for the same reason -- folding owner
states into the IDENTICAL/STALE/NO-BUILD/NO-SERVED/STAGED-POINTER partition would break the
reconciliation that makes that partition worth trusting. It reconciles against BEHIND on its own, and
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_heavyio_lib.nxnx_stubmain_lib.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
| 88 | const PS_MAXROW: i64 = 4096 |
| 89 | const PS_NAMEMAX: i64 = 256 |
| 90 | const PS_STAT_SIZE_OFF: i64 = 48 |
| 93 | const PS_STAT_MODE_OFF: i64 = 24 |
| 94 | const PS_MODE_OWNER_X: i64 = 64 |
| 98 | const PS_ASCII_v: i64 = 118 // 'v' -- first byte of the ruler's `verdict=` token, a cheap prefilter |
| 99 | const PS_BYTE_RADIX: i64 = 256 // combining two little-endian bytes of st_mode |
| 100 | const PS_BIT_SET: i64 = 2 // `% 2` on a shifted mode = is that permission bit set |
| 101 | const PS_I64_BYTES: i64 = 8 // sizeof(i64) -- the row tables hold i64 handles, not bytes |
| 102 | const PS_MODE_0644: i64 = 420 // 0644 in decimal, the estate's standard file mode |
| 103 | const PS_EXIT_NOTFOUND: i64 = 127 // exec convention: command not found |
| 104 | const PS_ASCII_SLASH: i64 = 47 // '/' -- path separator, for finding a basename |
| 105 | const PS_ASCII_HASH: i64 = 35 // the comment byte that opens a skipped line in every conf this organ reads |
| 106 | const PS_STATBUF_BYTES: i64 = 160 // struct stat is 144 B on x86-64; 160 is the rounded buffer three stat helpers share |
| 107 | const PS_DEFAULT_REG: *u8 = "tool_allowlist.conf" |
| 108 | const PS_DEFAULT_BROOT: *u8 = "buildroot/_build/" |
| 111 | const PS_JUDGE_TMP: *u8 = "/tmp/nx_artifactdrift_judge.tmp" |
| 112 | const PS_TAB: i64 = 9 |
| 113 | const PS_NL: i64 = 10 |
| 114 | const PS_LOGLINE_CAP: i64 = 1536 // the durable trend row's buffer -- named because it grew when the owner axis landed, and RAISED AGAIN when the fork-root axis added five fields. Raised BEFORE those fields were appended, deliberately: a trend row that overruns its buffer corrupts the one durable record this census leaves behind. |
| 121 | const PS_SRCROOT_A: *u8 = "buildroot/runtime/_hdl_build/" |
| 122 | const PS_SRCROOT_B: *u8 = "buildroot/runtime/" |
| 123 | const PS_SRC_EXT: *u8 = ".nx" |
| 127 | const PS_DECLARE_CONF: *u8 = "knowledge/status/drift_owner_declare.conf" |
| 128 | const PS_DRIFT_LOG: *u8 = "knowledge/status/artifactdrift.log" |
| 152 | const PS_FORKROOT: *u8 = "buildroot/_offc/" |
| 155 | const PS_FORK_MAX: i64 = 1024 |
| 162 | const PS_SCRATCH_MARK: *u8 = ".nx." |
| 163 | const PS_SCRATCH_MAX: i64 = 8192 // >10x the measured 762; the loader ANNOUNCES if it ever fills (no silent cap) |
| 164 | const PS_OWN_DIRBUF: i64 = 65536 // getdents read window, the estate's standard directory buffer |
| 165 | const PS_OWN_NAMES_SHOWN: i64 = 4 // scratch names printed per row; the COUNT is always exact and says PREFIX above this |
| 166 | const PS_SECS_PER_DAY: i64 = 86400 // a DEFINITION, not a tunable: the UTC day the mtime is bucketed into |
| 167 | const PS_STAT_MTIME_OFF: i64 = 88 // st_mtime.tv_sec at byte 88 of x86-64 struct stat (size 48, blksize 56, blocks 64, atime 72) |
| 170 | const PS_OWN_DECLARED: i64 = 0 |
| 171 | const PS_OWN_INFLIGHT: i64 = 1 |
| 172 | const PS_OWN_UNDECLARED: i64 = 2 |
| 173 | const PS_OWN_UNRESOLVED: i64 = 3 |
| 190 | const PS_REF_FRESH: i64 = 0 |
| 191 | const PS_REF_FOSSIL: i64 = 1 |
| 192 | const PS_REF_UNKNOWN: i64 = 2 |
functions
| 194 | func ps_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 199 | func ps_endswith(s: *u8, slen: i64, sfx: *u8) -> i64 |
| 204 | func ps_puts(s: *u8) -> i64 { sys_write(1, s, ps_len(s)); return 0 } |
| 205 | func ps_pn(v: i64) -> i64 |
| 219 | func ps_size(path: *u8) -> i64 |
| 236 | func ps_execbit(path: *u8) -> i64 |
| 249 | func ps_srcpath(root: *u8, elf: *u8, bs: i64, te: i64, out: *u8) -> i64 called by 1: ps_srcclass |
| 265 | func ps_srcclass(srca: *u8, srcb: *u8, elf: *u8, bs: i64, te: i64) -> i64 |
| 275 | func ad_cat(d: *u8, o: i64, s: *u8) -> i64 { var p: i64 = o; var i: i64 = 0; while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 } return p } |
| 276 | func ad_catn(d: *u8, o: i64, v: i64) -> i64 |
| 288 | func ps_streq(a: *u8, b: *u8) -> i64 called by 1: main |
| 300 | func ps_same(a: *u8, b: *u8) -> i64 |
| 328 | func ps_line_at(b: *u8, n: i64, key: *u8, klen: i64) -> i64 |
| 348 | func ps_val_in_line(b: *u8, n: i64, ls: i64, key: *u8, klen: i64) -> i64 |
| 383 | func ps_explain(tmp: *u8, out: *i64) -> i64 |
| 448 | func ps_judge(live: *u8, built: *u8, tmp: *u8) -> i64 |
| 486 | func ps_streq_n(a: *u8, b: *u8, n: i64) -> i64 |
| 494 | func ps_contains(s: *u8, sub: *u8) -> i64 |
| 505 | func ps_startswith(s: *u8, pfx: *u8) -> i64 |
| 519 | func ps_load_scratch(dir: *u8, tab: *i64, cap: i64, full: *i64) -> i64 |
| 564 | func ps_dir_elfs(dir: *u8, tab: *i64, cap: i64, full: *i64) -> i64 |
| 600 | func ps_mtime(path: *u8) -> i64 |
| 610 | func ps_decl_has(b: *u8, n: i64, name: *u8) -> i64 |
| 642 | func ps_scratch_count(tab: *i64, ntab: i64, pfx: *u8, show: i64) -> i64 |
| 663 | func ps_owner_scan(stm: *u8, srca: *u8, srcb: *u8, tab: *i64, ntab: i64, declb: *u8, decln: i64, now: i64, out: *i64) -> i64 |
| 697 | func main(argc: i64, argv: *i64) -> i64 |