code wiki / _hdl_build / nx_treecanon_gate.nx

nx_treecanon_gate.nx

buildroot/runtime/_hdl_build/nx_treecanon_gate.nx

31742 B612 linesdepth 5pulls 6 transitivereach 0 importersview sourcekind gate/proof
docsdependenciesstructsconstsfunctions

about

nx_treecanon_gate.nx -- THE DIVERGENCE GATE THAT REFUSES (debt 1785622769, operator directive 2026-08-01: "ONE canonical tree per generation with a lineage manifest ... a divergence gate that REFUSES a build when the deploying tree's copy diverges from canonical"). MEASURED 2026-08-03, and it is why this exists: NAS buildroot vs laptop nxc2 diverge on 8,258 files -- 3,705 NAS-newer, 1,838 laptop-newer, 739 NAS-only, 1,976 laptop-only. NEITHER TREE IS A SUPERSET IN EITHER DIRECTION. A name-level diff saw only 969 of those, because the dangerous class is files present in BOTH with DIFFERENT BYTES (5,543 of them). ⇒ ★A NAME-LEVEL TREE DIFF IS NOT A DIVERGENCE CHECK. It cannot see the failure that actually bites -- editing a file in one tree and compiling the other copy, which is precisely how the unfreed-mmap fix had to be applied twice (the incident that opened this debt). SCOPE, DELIBERATE AND STATED: this gate does NOT try to reconcile the fork. Blocking on all 8,258 would block every build, and mass-merging on a size screen would be DESTRUCTIVE -- byte size is a SCREEN, not proof of newer (banked law: newer mtime + FEWER bytes = REVERT). It enforces a CANON LIST from knowledge/tree_canon.conf (rule 11: the policy is DATA), starting at the crown jewels and growing by measurement. Stopping the bleeding beats an unfinished cleanup. T0 LOADED both manifests parse and index (non-vacuity floor on the inputs) T1 CANON READ the canon list loads and is non-empty (an empty list would pass vacuously) T2 CANON CLEAN every canon path is present in BOTH trees and byte-identical -- REFUSES otherwise T3 BITE the detector fires on a REAL divergent path and stays silent on a REAL identical one, both discovered from the manifests at runtime (no hardcoded fixture) T4 SETTLED OPTIONAL, needs argv[4]: resamples every listed canon file on the LIVE tree TC_SETTLE_MS later and REFUSES if any moved -- T1..T3 read manifests, which are claims about a moment already past and so cannot see a snapshot taken mid-write T5 CONFIRMED OPTIONAL, needs argv[4]: for every canon row T2 called divergent, hashes the LIVE file and asks whether the manifests or the bytes are right -- separating a REAL fork from a STALE MANIFEST. The two manifests are refreshed on DIFFERENT CLOCKS (NAS side hourly by nx_treediverge_beat, laptop side per SessionStart), so manifest lag alone can make identical trees look forked and block every seat's build. usage: nx_treecanon_gate <manifestCANON> <manifestOTHER> <canon.conf> [liveTreeRoot] liveTreeRoot must be the tree manifestCANON describes (e.g. buildroot/runtime on the NAS). license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0

dependencies 3 imports · 0 importers

nx_syscalls.nx nx_gate_verdict.nx nx_sha256.nx nx_treecanon_gate.nx

imports: nx_syscalls.nxnx_gate_verdict.nxnx_sha256.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main gv_ctr sys_mmap nxa_die sys_write sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ gv_head gv_puts sys_write ↻ gv_puts ↻ sys_mmap ↻ tc_readall sys_openat_rd sys_mmap ↻ sys_read sys_close tc_index sys_mmap ↻ tc_parse tc_is_hashrow tc_hexval tc_hexval ↻ tc_hash gv_num sys_mmap ↻ sys_write ↻ sys_munmap gv_check gv_puts ↻

structs

none

consts

41const TC_HBUCKETS: i64 = 65536
42const TC_MAXENT: i64 = 200000
43const TC_BUFCAP: i64 = 16777216
44const TC_CONFCAP: i64 = 262144
45const TC_HASH_SEED: i64 = 5381
46const TC_SPACE: i64 = 32
47const TC_NL: i64 = 10
48const TC_CR: i64 = 13
49const TC_HASHCH: i64 = 35
50const TC_BANG: i64 = 33 // '!' row prefix: the BUILD LANE freezes every build while that row
53const TC_PATHCAP: i64 = 1024
54const TC_FILECAP: i64 = 4194304 // per-file read cap for T4; largest canon source is ~290KB
55const TC_MAXCANON: i64 = 256
56const TC_DIGEST_BYTES: i64 = 32
57const TC_EMPTY_RESERVE: i64 = 4294967296 // sys_read_file's reserve for a 0-byte file (see nx_treehash)
58const TC_ID_FULLBYTES: i64 = 7 // 15 hex chars of identity = 7 whole bytes + 1 high nibble
59const TC_SETTLE_MS: i64 = 4000 // sample window for T4; long enough to catch an active writer
60const TC_SLASH: i64 = 47
61const TC_D0: i64 = 48
62const TC_D9: i64 = 57
74const TC_HEXID_CHARS: i64 = 15
75const TC_SHA_CHARS: i64 = 64
76const TC_LC_A: i64 = 97
77const TC_LC_F: i64 = 102

functions

89func tc_readall(path: *u8, outn: *i64, cap: i64) -> *u8
118func tc_filehash(root: *u8, rel: *u8, nbox: *i64, outh: *i64) -> i64
153func tc_sample_all(cb: *u8, lc: i64, root: *u8, out: *i64, st: *i64, cap: i64, nb2: *i64) -> i64
called by 1: main calls 1: tc_filehash
196func tc_live_id(root: *u8, rel: *u8, outid: *i64) -> i64
233func tc_hash(s: *u8) -> i64
called by 2: tc_indextc_lookup
239func tc_streq(a: *u8, b: *u8) -> i64
called by 1: tc_lookup
246func tc_hexval(c: i64) -> i64
254func tc_is_hashrow(buf: *u8, start: i64, end: i64) -> i64
called by 1: tc_parse calls 1: tc_hexval
268func tc_parse(buf: *u8, start: i64, end: i64, szout: *i64) -> *u8
called by 1: tc_index calls 2: tc_is_hashrowtc_hexval
308func tc_index(buf: *u8, n: i64, htab: *i64, keys: *i64, sizes: *i64) -> i64
called by 1: main calls 3: sys_mmaptc_parsetc_hash
346func tc_lookup(rel: *u8, htab: *i64, keys: *i64, sizes: *i64) -> i64
called by 2: tc_divergesmain calls 2: tc_hashtc_streq
361func tc_diverges(rel: *u8) -> i64
called by 1: main calls 1: tc_lookup
370func main(argc: i64, argv: *i64) -> i64