code wiki / _hdl_build / nx_doctor_apply.nx
nx_doctor_apply.nx source
↩ module page · 98 lines · 5913 B
1// nx_doctor_apply.nx -- THE NISHI DOCTOR's "graceful apply WITHOUT competition" core rung.
2//
3// Operator: route a fix through the Doctor so it is applied gracefully without racing a workstream that is
4// concurrently editing the same shared organ. Synthesized from the Nishi Researcher's S-class corpus
5// (knowledge/fetched/doc_*.raw): LOCK (mutual exclusion, axis A: nx_arbiter fl_acquire) + EXACT-CONTEXT-MATCH
6// apply (axes B optimistic/CAS + C 3-way-merge/patch context: apply ONLY where the target region still matches
7// the CURRENT file exactly-once -> a concurrent edit that touched my region makes the match FAIL = REFUSE, never
8// clobber; an edit elsewhere leaves the match intact = apply = a clean merge for non-overlapping edits) +
9// ATOMIC tmp+rename (axis F: no torn write). Fail-closed: any ambiguity (no match / multi-match) -> REFUSE.
10//
11// This does NOT verify or admit its own work (RACI: the Engineer re-verifies, the Warden admits) -- it is the
12// safe APPLY primitive the Doctor's fix queue (next rung) drives one-at-a-time.
13//
14// usage: nx_doctor_apply <target> <oldstr-file> <newstr-file> [requester]
15// reads oldstr/newstr from files so multi-line patches work (NUL-free byte spans).
16// license_tier: ORIGINAL
17import "nx_syscalls.nx"
18import "nx_arbiter.nx" // fl_acquire / fl_release (the mutual-exclusion lease)
19const DA_MAGIC_100000: i64 = 100000
20const DA_MAGIC_1024: i64 = 1024
21
22const DA_APPLIED: i64 = 0
23const DA_CONFLICT: i64 = 1 // target region not found -> already-applied OR changed by another -> NOT clobbered
24const DA_AMBIG: i64 = 2 // oldstr matches >1 -> refuse (will not guess which)
25const DA_ERR: i64 = 3
26const DA_CAP: i64 = 8388608 // 8 MiB bound -- source files are small; NEVER sys_read_file's 4 GiB (fork-hazard)
27const DA_LOCK: *u8 = "doctor_apply"
28
29func da_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
30func da_w(s: *u8) -> i64 { return sys_write(1, s, da_slen(s)) }
31
32// bounded read (NOT sys_read_file). returns bytes, or -1.
33func da_read(path: *u8, buf: *u8, cap: i64) -> i64 {
34 let fd: i64 = sys_openat_rd(path)
35 if fd < 0 { return 0 - 1 }
36 var off: i64 = 0; var go: i64 = 1
37 while go == 1 { if off >= cap { go = 0 } else { let r: i64 = sys_read(fd, buf + off, cap - off); if r <= 0 { go = 0 } else { off = off + r } } }
38 sys_close(fd); return off
39}
40func da_match_at(hay: *u8, i: i64, needle: *u8, nlen: i64) -> i64 {
41 var j: i64 = 0; while j < nlen { if hay[i + j] != needle[j] { return 0 } j = j + 1 } return 1
42}
43// non-overlapping occurrence count of needle in hay[0..hn)
44func da_count(hay: *u8, hn: i64, needle: *u8, nlen: i64) -> i64 {
45 if nlen <= 0 { return 0 }
46 var c: i64 = 0; var i: i64 = 0
47 while i + nlen <= hn { if da_match_at(hay, i, needle, nlen) == 1 { c = c + 1; i = i + nlen } else { i = i + 1 } }
48 return c
49}
50func da_find(hay: *u8, hn: i64, needle: *u8, nlen: i64) -> i64 {
51 if nlen <= 0 { return 0 - 1 }
52 var i: i64 = 0
53 while i + nlen <= hn { if da_match_at(hay, i, needle, nlen) == 1 { return i } i = i + 1 }
54 return 0 - 1
55}
56
57// THE core: apply oldstr->newstr in `target`, conflict-safe + atomic, under the lock. Returns DA_*.
58func doc_apply_mem(target: *u8, oldstr: *u8, oldlen: i64, newstr: *u8, newlen: i64) -> i64 {
59 if oldlen <= 0 { return DA_ERR }
60 let lk: i64 = fl_acquire(DA_LOCK, DA_MAGIC_100000, 1) // mutual exclusion (serialize Doctor-vs-Doctor applies)
61 let buf: *u8 = sys_mmap(DA_CAP)
62 let n: i64 = da_read(target, buf, DA_CAP)
63 if n < 0 { fl_release(lk); return DA_ERR }
64 let cnt: i64 = da_count(buf, n, oldstr, oldlen)
65 if cnt == 0 { fl_release(lk); return DA_CONFLICT } // region gone/changed -> REFUSE (never clobber)
66 if cnt > 1 { fl_release(lk); return DA_AMBIG } // ambiguous -> REFUSE
67 let idx: i64 = da_find(buf, n, oldstr, oldlen)
68 let out: *u8 = sys_mmap(DA_CAP + newlen + 16)
69 var o: i64 = 0; var i: i64 = 0
70 while i < idx { out[o] = buf[i]; o = o + 1; i = i + 1 } // prefix
71 i = 0; while i < newlen { out[o] = newstr[i]; o = o + 1; i = i + 1 } // replacement
72 i = idx + oldlen; while i < n { out[o] = buf[i]; o = o + 1; i = i + 1 } // suffix
73 // atomic publish: write <target>.datmp then rename over target (no torn write; an interrupted write never
74 // leaves a half-file at the live path).
75 let tmp: *u8 = sys_mmap(DA_MAGIC_1024); var t: i64 = 0
76 while target[t] != (0 as u8) { tmp[t] = target[t]; t = t + 1 }
77 let sfx: *u8 = ".datmp"; var s: i64 = 0; while sfx[s] != (0 as u8) { tmp[t] = sfx[s]; t = t + 1; s = s + 1 } tmp[t] = 0 as u8
78 let wfd: i64 = sys_openat_wr(tmp, 0x1a4)
79 if wfd < 0 { fl_release(lk); return DA_ERR }
80 sys_write(wfd, out, o); sys_close(wfd)
81 sys_renameat(tmp, target)
82 fl_release(lk)
83 return DA_APPLIED
84}
85
86func main(argc: i64, argv: *i64) -> i64 {
87 if argc < 4 { da_w("usage: nx_doctor_apply <target> <oldstr-file> <newstr-file> [requester]\n" as *u8); return 2 }
88 let target: *u8 = argv[1] as *u8
89 let oldbuf: *u8 = sys_mmap(DA_CAP); let oldlen: i64 = da_read(argv[2] as *u8, oldbuf, DA_CAP)
90 let newbuf: *u8 = sys_mmap(DA_CAP); let newlen: i64 = da_read(argv[3] as *u8, newbuf, DA_CAP)
91 if oldlen <= 0 { da_w("DOCTOR-APPLY: ERROR empty/unreadable oldstr-file\n" as *u8); return 3 }
92 let code: i64 = doc_apply_mem(target, oldbuf, oldlen, newbuf, newlen)
93 if code == DA_APPLIED { da_w("DOCTOR-APPLY: APPLIED " as *u8); da_w(target); da_w("\n" as *u8) }
94 if code == DA_CONFLICT { da_w("DOCTOR-APPLY: CONFLICT (region not found in current file -> already-applied or changed by another; NOT clobbered)\n" as *u8) }
95 if code == DA_AMBIG { da_w("DOCTOR-APPLY: AMBIGUOUS (oldstr matches >1 -> refused, will not guess)\n" as *u8) }
96 if code == DA_ERR { da_w("DOCTOR-APPLY: ERROR\n" as *u8) }
97 return code
98}