code wiki / _hdl_build / nx_atomic_publish.nx

nx_atomic_publish.nx

buildroot/runtime/_hdl_build/nx_atomic_publish.nx

40212 B762 linesdepth 5pulls 5 transitivereach 0 importersview sourcekind tooltopic atomic
docsdependenciesstructsconstsfunctions

about

nx_atomic_publish.nx -- ALL-OR-NOTHING FILE PUBLISH WITH COMPARE-AND-SWAP, IN NISHILANG. WHY IT EXISTS. A multi-part source change applied as N sequential anchored edits is N chances to half-land. Measured twice on 2026-08-07: the MCP daemon wedged mid-sequence, edit 1 landed and edits 2-3 did not, leaving a live source with an UNCLOSED BRACE -- a broken file that blocks every build of that target, found only because the artefact was verified rather than the response believed. ★★★★★★A MULTI-PART EDIT OVER A FLAKY TRANSPORT IS AN ATOMICITY PROBLEM, NOT A RETRY PROBLEM: retrying makes a half-applied file MORE likely, not less. Stage the whole file, publish once. WHY IT IS AN ORGAN AND NOT A SHELL HELPER. The first version of this was a .ps1, and it JUDGED: it parsed a refusal to recover a token, classified transient-vs-deterministic, and decided refuse-vs-retry. That is judgement, and judgement belongs in NishiLang -- shell is the wire and nothing else. Here the only laptop-side act left is moving bytes to a scratch path; every decision happens on this side. usage: nx_atomic_publish <staged-src> <target> <expect> expect = "any" -- publish regardless (NAME IT OUT LOUD when you use it) | "absent" -- publish only if the target does not exist yet | <64-hex sha256> -- publish only if the target's CURRENT content hashes to exactly this nx_atomic_publish landed <writelist> -- the READ half: adjudicate ambiguous writes (ap_landed) CAS SEMANTICS, and the direction of failure is the point: on mismatch it REFUSES and PRINTS the live hash, so the caller can re-read, merge, and retry with the right token. It never re-resolves the token itself -- that would turn a concurrent writer's edit into a silent clobber, which is the exact defect compare-and-swap exists to prevent. ★A CAS THAT RESOLVES ITS OWN EXPECTED VALUE IS NOT A CAS, IT IS AN OVERWRITE WITH EXTRA STEPS. ATOMIC BY renameat, NOT BY WRITE ORDER: content goes to <target>.aptmp then renames over the target, so a reader never sees a half-written file and a crash mid-publish leaves the original intact. VERIFY AFTER: the published bytes are re-read and re-hashed before GREEN is printed. license_tier: ORIGINAL expect_exit: 0

dependencies 2 imports · 0 importers

nx_syscalls.nx nx_sha256.nx nx_atomic_publish.nx

imports: nx_syscalls.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 ap_streq ap_puts sys_write sys_exit ap_landed 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 ↻ sys_read_file sys_openat_rd sys_lseek sys_mmap ↻ sys_read sys_munmap sys_close ap_puts ↻ ap_len apl_kv apl_atoi apl_conf_settle_ms sys_mmap ↻ sys_read_file ↻ ap_len ↻ apl_scan sys_mmap ↻ sys_read_file ↻ sha256_digest

structs

none

consts

35const AP_STDOUT: i64 = 1
36const AP_PATHCAP: i64 = 1024
37const AP_MODE: i64 = 420 // 0644
170const APL_TAB: i64 = 9
171const APL_NL: i64 = 10
172const APL_CR: i64 = 13
173const APL_EQ: i64 = 61
174const APL_HCHAR: i64 = 104 // the 'h' prefix of an h<hex> compare-and-swap token
175const APL_SLASH: i64 = 47 // '/' -- a doubled slash starts a comment row in the grammar
176const APL_D0: i64 = 48 // ASCII '0' -- the base of decimal parsing, structural
177const APL_D9: i64 = 57
178const APL_DEC_BASE: i64 = 10 // every count in the row grammar is written in decimal
179const APL_NUL_SLACK: i64 = 2 // room to NUL-terminate the working copy AND its final line
180const APL_RC_LIST_UNREADABLE: i64 = 3 // the writelist itself could not be read: not a verdict about any row
181const APL_HEXSLOT: i64 = 80 // 64 hex + NUL, rounded up to a comfortable per-row slot
182const APL_PTRW: i64 = 8 // one i64 per parallel-array element
183const APL_DEFAULT_MARK_ADDS: i64 = 1 // one anchored apply introduces its marker once; stated, not assumed
187const APL_SETTLE_MS_DEFAULT: i64 = 1200
188const APL_CONF: *u8 = "knowledge/status/atomic_publish.conf"
190const APL_V_LANDED: i64 = 0
191const APL_V_NOT: i64 = 1
192const APL_V_DOUBLE: i64 = 2
193const APL_V_UNKNOWN: i64 = 3
196const APL_R_NONE: i64 = 0
197const APL_R_UNSETTLED: i64 = 1
198const APL_R_UNREADABLE: i64 = 2
199const APL_R_WANT_EQ_PRE: i64 = 3
200const APL_R_THIRD_STATE: i64 = 4
201const APL_R_MARK_FELL: i64 = 5
202const APL_R_MARK_PARTIAL: i64 = 6
203const APL_R_CHANGED_UNDECLARED: i64 = 7
204const APL_R_SIZE_ONLY: i64 = 8
205const APL_R_REPLY_ONLY: i64 = 9
206const APL_R_NO_EXPECTATION: i64 = 10
207const APL_R_MALFORMED: i64 = 11
208const APL_R_MARK_NO_PRE: i64 = 12
209const APL_R_COUNT: i64 = 13 // one past the highest reason code; the counter table size

functions

39func ap_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(AP_STDOUT, s, n); return 0 }
called by 2: ap_landedmain calls 1: sys_write
41func ap_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
43func ap_num(v: i64) -> i64
called by 2: ap_landedmain calls 2: sys_mmapsys_write
58func ap_hex(dig: *u8, out: *u8) -> i64
71func ap_streq(a: *u8, b: *u8) -> i64
78func ap_cat2(out: *u8, a: *u8, b: *u8) -> i64
called by 1: main
95func ap_hash_file(path: *u8, outp: *i64, hex: *u8) -> i64
211func apl_reason_name(r: i64) -> *u8
called by 1: ap_landed
235func apl_advice(kind: *u8) -> *u8
called by 1: ap_landed calls 1: ap_streq
248func apl_verdict_name(v: i64) -> *u8
called by 1: ap_landed
255func apl_slot(base: *u8, i: i64, w: i64) -> *u8 { return ((base as i64) + i * w) as *u8 }
called by 1: ap_landed
259func apl_kv(tok: *u8, key: *u8) -> *u8
called by 1: ap_landed
268func apl_dehex(s: *u8) -> *u8
called by 1: ap_landed
273func apl_atoi(s: *u8) -> i64
called by 1: ap_landed
289func apl_count_occ(buf: *u8, n: i64, needle: *u8) -> i64
called by 1: apl_scan calls 1: ap_len
310func apl_scan(path: *u8, hexout: *u8, needle: *u8, occ_out: *i64) -> i64
324func apl_conf_settle_ms() -> i64
371func ap_landed(listpath: *u8) -> i64
644func main(argc: i64, argv: *i64) -> i64