nx_closurehash.nx
buildroot/runtime/nx_closurehash.nx
about
nx_closurehash.nx -- PROVENANCE AT PRODUCTION, GENERALISED FROM ONE FILE TO THE WHOLE IMPORT CLOSURE.
THE DEFECT THIS RETIRES (measured all day 2026-08-07): the build lane decides "has this tree forked?"
by comparing the LIVE NAS tree against `treecanon_laptop_hash.mf` -- a manifest generated from the
LAPTOP mirror and pushed across. But edits are made with nx_fs_write, which writes DIRECTLY to the NAS,
and builds compile FROM the NAS. So the newest truth is on the NAS and the judge is the stale mirror:
*THE CANON GUARD RUNS BACKWARDS RELATIVE TO HOW EDITS ACTUALLY FLOW, AND CALLS THE TRUTH A FORK.*
Consequences seen today: builds blocked by my own newer edit · a manifest push losing a file-lock race ·
four different hashes for one header inside ten minutes · ~3,800 NAS-ahead files nobody can reconcile.
Every guard involved was working correctly on a bad premise.
nx_mgmt_api already states the right principle, for ONE file:
"PROVENANCE, CAPTURED AT THE MOMENT OF PRODUCTION ... A CLOCK CANNOT ESTABLISH PROVENANCE.
RECORD IT WHERE IT IS CHEAP: HERE. LATER IT IS UNRECOVERABLE."
It emits src_sha256/src_stable for the target's own source. That is the whole idea, one file wide.
This widens it to every source the compiler will actually read.
*A MANIFEST IS A CLAIM ABOUT A TREE AT A PAST INSTANT; A CLOSURE HASH IS A FACT ABOUT THE BINARY IN
YOUR HAND.* With the closure recorded at build time you can answer "was this artifact built from these
exact bytes?" with no manifest, no push, no watermark, and nothing to go stale -- and the question a
census can never answer, "which of the two trees was I compiled from", becomes trivial.
SOURCE RESOLUTION ORDER IS LOAD-BEARING and copied from nx_stalesweep's header rather than guessed:
_hdl_build/<t>.nx FIRST, then runtime/<t>.nx -- because buildrun compiles the _hdl_build twin when BOTH
exist, so hashing the runtime twin would describe a file the compiler never opened.
UNRESOLVED IMPORTS ARE THEIR OWN BUCKET AND ARE COUNTED. An import that resolves nowhere is exactly the
kind of thing this must not silently omit -- omitting it would make a BROKEN closure hash the same
shape as a healthy one. *A PROVENANCE RECORD THAT DROPS WHAT IT COULD NOT FIND IS A FORGERY.*
nx_closurehash <target> [root] root default "." (run from the buildroot)
-> one line per source, then: closure_sha=<64hex> files=N unresolved=M bytes=B
exit 0 complete | 1 closure has unresolved imports | 2 target source not found | 3 usage
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_sha256.nxnx_incclosure_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
| 44 | const CH_MAGIC_262144: i64 = 262144 |
| 45 | const CH_MAGIC_1024: i64 = 1024 |
| 47 | const CH_MAXF: i64 = 1024 // closure entries; refuses past this rather than silently truncating |
| 48 | const CH_NAMEW: i64 = 192 // bytes per stored name |
| 49 | const CH_FCAP: i64 = 4194304 // 4MB per source file |
| 50 | const CH_ACC: i64 = 262144 // accumulator for the combined digest input |
functions
| 52 | func chp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } |
| 53 | func che(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(2,s,n); return 0 } |
| 54 | func chn(v: i64) -> i64 |
| 67 | func chcat(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 } called by 1: main |
| 68 | func chlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } |
| 69 | func chslot(b: *u8, i: i64) -> *u8 { return ((b as i64) + i*CH_NAMEW) as *u8 } called by 1: main |
| 70 | func chseq(a: *u8, b: *u8) -> i64 called by 1: main |
| 82 | func chhex(dig: *u8, out: *u8) -> i64 called by 1: main |
| 94 | func chexists(p: *u8) -> i64 { let fd: i64 = sys_openat_rd(p); if fd < 0 { return 0 } sys_close(fd); return 1 } |
| 95 | func chread(path: *u8, buf: *u8, cap: i64) -> i64 |
| 119 | func chresolve(root: *u8, name: *u8, out: *u8) -> i64 { return icl_resolve(root, name, out) } |
| 121 | func main(argc: i64, argv: *i64) -> i64 |