code wiki / _hdl_build / nx_pkg.nx
nx_pkg.nx source
↩ module page · 740 lines · 38041 B
1// nx_pkg.nx -- THE SOVEREIGN CONTENT-ADDRESSED PACKAGE LANE (LR3 of /compare/lang), carrying the original
2// dependency-resolution self-test it grew from (verb `selftest`, still the NO-ARG DEFAULT so no existing
3// caller loses behaviour).
4//
5// WHY THIS EXISTS. The 2025 registry worms self-replicated across 500+ npm packages by exploiting exactly
6// two assumptions: install-time code execution, and blind trust that a NAME resolves to whatever `latest`
7// happens to be. Go answered with a module proxy, a signed transparency log (sumdb), and no install
8// scripts. This lane does not GUARD that attack surface, it REMOVES it: THE IDENTITY OF A MODULE IS THE
9// HASH OF ITS BYTES. There is no version negotiation, so there is no `latest` to poison. The resolve
10// primitive takes a digest, not a name, so there is nothing to squat. And resolution never interprets,
11// forks or executes one byte of module content -- it reads, verifies, and writes.
12//
13// THE ONE LAW: THE STORE PATH IS A HINT, THE HASH IS THE IDENTITY.
14// A blob at <store>/<digest> is RE-HASHED on every resolve and REFUSED if it does not hash to the digest
15// that was asked for. An attacker who can write the store cannot make it serve different bytes; the most
16// they achieve is a named, loud refusal.
17//
18// EXTENDS THE ESTATE'S EXISTING CONTENT-ADDRESSING, DOES NOT ADD A SECOND ONE:
19// sha256_digest (nx_sha256.nx) -- the same digest nx_filehash / nx_closurehash / nx_content_put use
20// sys_read_file (nx_syscalls.nx) -- sizes its buffer from the file, so a module CANNOT short-read
21// lcf_* (nx_lineconf_lib.nx) -- line-anchored conf, LCF_MISS -> REFUSE, never a silent default
22// There is no new hash, no new store format, no second ruler.
23//
24// VERBS (every path and policy value comes from a conf file; an absent key REFUSES, it never defaults):
25// nx_pkg cas_put <file> [conf] -> address a file by content; prints its digest
26// nx_pkg cas_resolve <digest64> <dest> [conf] -> THE CONTRACT PRIMITIVE (pkg_cas_resolve)
27// nx_pkg pin <name> <digest64> [conf] -> bind a logical name to a digest (THE TRUST BOUNDARY)
28// nx_pkg resolve <name> <dest> [conf] -> exact-match pinned name -> digest -> cas_resolve
29// nx_pkg logverify [conf] -> walk the hash-chained transparency journal
30// nx_pkg selftest -> the original dep-resolution teeth (T1..T4)
31// An UNRECOGNISED verb prints usage and exits 3. It never falls through to a permissive default -- the
32// route-mode defect (an unknown token silently selecting the permissive branch) is live in this estate
33// today and this is the one place it must not be repeated.
34//
35// WHAT THE TRANSPARENCY JOURNAL PROVES, STATED HONESTLY. Every put/resolve appends
36// <seq>|<op>|<digest>|<bytes>|<prev_chain>|<chain> with chain = sha256(prev|digest|bytes)
37// so a retroactive edit to any past row breaks every chain link after it, and an append REFUSES on a
38// broken chain rather than extending it. That is TAMPER-EVIDENCE against an editor who does not rewrite
39// the whole file. It is NOT Go's sumdb: there is one writer, no signature, no gossiped tree head and no
40// inclusion proofs, so an attacker holding write access to the entire journal can recompute it from
41// genesis and it will verify. Only a copy of an earlier chain head, held elsewhere, detects that.
42//
43// RESIDUAL -- WHAT THIS DOES NOT PROTECT AGAINST (read this before trusting the lane):
44// R1 PIN TIME. `pin` is the trust boundary. CAS proves you got the bytes you asked for; it can never
45// prove you asked for the right ones. A poisoned digest, pinned, resolves perfectly forever.
46// R2 WHOLE-JOURNAL REWRITE (above). Single writer, unwitnessed, unsigned.
47// R3 HONEST DELIVERY OF MALICIOUS CODE. Content addressing is integrity, never trustworthiness.
48// Zero-install-execution shrinks the blast radius to `it runs when you run it`; it does not make
49// the module safe.
50// R4 NO SIGNATURE LAYER IS WIRED HERE. nx_install_attest_signed.nx (Ed25519) exists in this estate and
51// is deliberately NOT composed yet -- naming the next rung beats claiming it.
52// R5 THIS IS THE PACKAGE LANE, NOT THE COMPILER FRONT END. nx_import.nx still resolves
53// `import \x22x.nx\x22` by filesystem name. Requiring a pinned digest at import time is a further rung.
54// R6 sha256 preimage/collision resistance is ASSUMED, not proven here.
55//
56// exit: 0 ok | 3 usage | 4 conf | 5 malformed-digest | 6 digest-absent | 7 digest-mismatch |
57// 8 dest-refused | 9 name-not-pinned | 10 pin-conflict | 11 chain-break | 12 io | 13 pin-ambiguous
58// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
59import "nx_syscalls.nx"
60import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
61import "nx_g_puts_lib.nx"
62import "nx_sha256.nx"
63import "nx_lineconf_lib.nx"
64
65// ---- named constants; no bare literal carries meaning -------------------------------------------
66const PK_DIGEST_BYTES: i64 = 32 // SHA-256 output width, fixed by the algorithm
67const PK_SHAHEX: i64 = 64 // = PK_DIGEST_BYTES * 2, asserted at runtime below
68const PK_I64_DIGITS: i64 = 20 // widest decimal i64 incl sign, fixed by the type
69const PK_BAR: i64 = 124 // '|' field separator
70const PK_NL: i64 = 10
71const PK_SLASH: i64 = 47
72const PK_DOT: i64 = 46
73const PK_ASCII_0: i64 = 48
74const PK_ASCII_9: i64 = 57
75const PK_ASCII_a: i64 = 97
76const PK_ASCII_f: i64 = 102
77const PK_FD_OUT: i64 = 1
78const PK_FD_ERR: i64 = 2
79const PK_NFIELDS: i64 = 6
80const PK_F_SEQ: i64 = 0
81const PK_F_OP: i64 = 1
82const PK_F_DIG: i64 = 2
83const PK_F_BYTES: i64 = 3
84const PK_F_PREV: i64 = 4
85const PK_F_CHAIN: i64 = 5
86const PK_CONF_DEFAULT: *u8 = "knowledge/pkg_cas.conf" as *u8
87
88// refusal codes -- ONE owner for the rule NAME and ONE owner for the exit code, so a message and a
89// status can never drift apart.
90const PK_OK: i64 = 0
91const PK_R_MALFORMED: i64 = 1
92const PK_R_ABSENT: i64 = 2
93const PK_R_MISMATCH: i64 = 3
94const PK_R_DEST: i64 = 4
95const PK_R_IO: i64 = 5
96const PK_R_CHAIN: i64 = 6
97const PK_R_NOTPINNED: i64 = 7
98const PK_R_PINCONFLICT: i64 = 8
99const PK_R_PINAMBIG: i64 = 9
100const PK_R_CONF: i64 = 10
101
102const PK_EXIT_USAGE: i64 = 3
103const PK_EXIT_CONF: i64 = 4
104const PK_EXIT_MALFORMED: i64 = 5
105const PK_EXIT_ABSENT: i64 = 6
106const PK_EXIT_MISMATCH: i64 = 7
107const PK_EXIT_DEST: i64 = 8
108const PK_EXIT_NOTPINNED: i64 = 9
109const PK_EXIT_PINCONFLICT: i64 = 10
110const PK_EXIT_CHAIN: i64 = 11
111const PK_EXIT_IO: i64 = 12
112const PK_EXIT_PINAMBIG: i64 = 13
113
114func pk_rule(code: i64) -> *u8 {
115 if code == PK_R_MALFORMED { return "malformed-digest" as *u8 }
116 if code == PK_R_ABSENT { return "digest-absent" as *u8 }
117 if code == PK_R_MISMATCH { return "digest-mismatch" as *u8 }
118 if code == PK_R_DEST { return "dest-refused" as *u8 }
119 if code == PK_R_IO { return "io-error" as *u8 }
120 if code == PK_R_CHAIN { return "chain-break" as *u8 }
121 if code == PK_R_NOTPINNED { return "name-not-pinned" as *u8 }
122 if code == PK_R_PINCONFLICT { return "pin-conflict" as *u8 }
123 if code == PK_R_PINAMBIG { return "pin-ambiguous" as *u8 }
124 if code == PK_R_CONF { return "conf-missing-key" as *u8 }
125 return "unclassified-refusal" as *u8
126}
127func pk_exit_for(code: i64) -> i64 {
128 if code == PK_OK { return 0 }
129 if code == PK_R_MALFORMED { return PK_EXIT_MALFORMED }
130 if code == PK_R_ABSENT { return PK_EXIT_ABSENT }
131 if code == PK_R_MISMATCH { return PK_EXIT_MISMATCH }
132 if code == PK_R_DEST { return PK_EXIT_DEST }
133 if code == PK_R_IO { return PK_EXIT_IO }
134 if code == PK_R_CHAIN { return PK_EXIT_CHAIN }
135 if code == PK_R_NOTPINNED { return PK_EXIT_NOTPINNED }
136 if code == PK_R_PINCONFLICT { return PK_EXIT_PINCONFLICT }
137 if code == PK_R_PINAMBIG { return PK_EXIT_PINAMBIG }
138 if code == PK_R_CONF { return PK_EXIT_CONF }
139 return PK_EXIT_IO
140}
141
142// ---- small helpers (pk_ prefixed: a NishiLang import OVERRIDES a same-name local def) --------------
143func pk_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
144func pk_eputs(s: *u8) -> i64 { sys_write(PK_FD_ERR, s, pk_slen(s)); return 0 }
145func pk_num(v: i64) -> i64 { nxi_out(v); return 0 }
146func pk_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 } d[p] = 0 as u8; return p }
147func pk_streq(a: *u8, b: *u8) -> i64 {
148 var i: i64 = 0
149 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
150 if b[i] != (0 as u8) { return 0 }
151 return 1
152}
153func pk_starts(p: *u8, pre: *u8) -> i64 { var i: i64 = 0; while pre[i] != (0 as u8) { if p[i] != pre[i] { return 0 } i = i + 1 } return 1 }
154func pk_dotdot(p: *u8) -> i64 { var i: i64 = 0; while p[i] != (0 as u8) { if (p[i] as i64) == PK_DOT { if (p[i+1] as i64) == PK_DOT { return 1 } } i = i + 1 } return 0 }
155func pk_atoi(s: *u8) -> i64 {
156 var v: i64 = 0
157 var i: i64 = 0
158 var seen: i64 = 0
159 while s[i] != (0 as u8) {
160 let c: i64 = s[i] as i64
161 if c < PK_ASCII_0 { return 0 - 1 }
162 if c > PK_ASCII_9 { return 0 - 1 }
163 v = v * 10 + (c - PK_ASCII_0)
164 seen = 1
165 i = i + 1
166 }
167 if seen == 0 { return 0 - 1 }
168 return v
169}
170func pk_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 }
171// RUNTIME-COMPUTED syscall number: a literal passes through the rv64->x86 constant-translate path, so
172// storing it first is the documented escape hatch (same body as nx_mkdirp.mp_mkdir_one).
173func pk_mkdir(path: *u8) -> i64 {
174 let nbox: *i64 = sys_mmap(16) as *i64
175 nbox[0] = 258
176 let rc: i64 = __syscall(nbox[0], 0 - 100, path as i64, 0x1ed, 0, 0, 0)
177 sys_munmap(nbox as *u8, 16)
178 return rc
179}
180func pk_hex_into(dig: *u8, out: *u8) -> i64 {
181 let h: *u8 = "0123456789abcdef" as *u8
182 var i: i64 = 0
183 while i < PK_DIGEST_BYTES {
184 let v: i64 = dig[i] as i64
185 out[i*2] = h[(v >> 4) & 15]
186 out[i*2+1] = h[v & 15]
187 i = i + 1
188 }
189 out[PK_SHAHEX] = 0 as u8
190 return PK_SHAHEX
191}
192func pk_sha_hex(buf: *u8, n: i64, outhex: *u8) -> i64 {
193 let d: *u8 = sys_mmap(PK_DIGEST_BYTES + 8)
194 sha256_digest(buf, n, d)
195 pk_hex_into(d, outhex)
196 sys_munmap(d, PK_DIGEST_BYTES + 8)
197 return 0
198}
199// STRICT digest form: exactly PK_SHAHEX bytes, lowercase hex, NUL-terminated. Anything else is
200// MALFORMED and refuses -- an unparseable input must fail CLOSED, never select a permissive branch.
201func pk_is_hex64(s: *u8) -> i64 {
202 var i: i64 = 0
203 while i < PK_SHAHEX {
204 let c: i64 = s[i] as i64
205 var ok: i64 = 0
206 if c >= PK_ASCII_0 { if c <= PK_ASCII_9 { ok = 1 } }
207 if c >= PK_ASCII_a { if c <= PK_ASCII_f { ok = 1 } }
208 if ok == 0 { return 0 }
209 i = i + 1
210 }
211 if s[PK_SHAHEX] != (0 as u8) { return 0 }
212 return 1
213}
214// destination allowlist -- the same safe-root set nx_mkdirp and nx_content_put enforce.
215func pk_dest_ok(p: *u8) -> i64 {
216 if pk_dotdot(p) == 1 { return 0 }
217 if pk_starts(p, "knowledge/" as *u8) == 1 { return 1 }
218 if pk_starts(p, "sites/" as *u8) == 1 { return 1 }
219 if pk_starts(p, "world/" as *u8) == 1 { return 1 }
220 if pk_starts(p, "/tmp/" as *u8) == 1 { return 1 }
221 return 0
222}
223// <store>/<digest> ; buffer DERIVED from the inputs, never a guessed cap
224func pk_blob_path(store: *u8, dighex: *u8) -> *u8 {
225 let need: i64 = pk_slen(store) + 1 + PK_SHAHEX + 8
226 let b: *u8 = sys_mmap(need)
227 var o: i64 = pk_cat(b, 0, store)
228 b[o] = PK_SLASH as u8; o = o + 1
229 o = pk_cat(b, o, dighex)
230 return b
231}
232func pk_write_atomic(finalpath: *u8, buf: *u8, n: i64) -> i64 {
233 let need: i64 = pk_slen(finalpath) + 16
234 let tmp: *u8 = sys_mmap(need)
235 var o: i64 = pk_cat(tmp, 0, finalpath)
236 o = pk_cat(tmp, o, ".pkgtmp" as *u8)
237 let fd: i64 = sys_openat_wr(tmp, MODE_0644)
238 if fd < 0 { sys_munmap(tmp, need); return 0 - 1 }
239 var w: i64 = 0
240 while w < n {
241 let r: i64 = sys_write(fd, ((buf as i64) + w) as *u8, n - w)
242 if r <= 0 { sys_close(fd); sys_unlinkat(tmp); sys_munmap(tmp, need); return 0 - 1 }
243 w = w + r
244 }
245 sys_fsync(fd)
246 sys_close(fd)
247 if sys_renameat(tmp, finalpath) < 0 { sys_unlinkat(tmp); sys_munmap(tmp, need); return 0 - 1 }
248 sys_munmap(tmp, need)
249 return n
250}
251
252// ---- the hash-chained transparency journal --------------------------------------------------------
253func pk_genesis(out: *u8) -> i64 { var i: i64 = 0; while i < PK_SHAHEX { out[i] = PK_ASCII_0 as u8; i = i + 1 } out[PK_SHAHEX] = 0 as u8; return PK_SHAHEX }
254// chain_i = sha256( prev | digest | bytes ). Input buffer size DERIVED from the field widths.
255func pk_chain_step(prevhex: *u8, dighex: *u8, nbytes: i64, outhex: *u8) -> i64 {
256 let need: i64 = PK_SHAHEX + 1 + PK_SHAHEX + 1 + PK_I64_DIGITS + 8
257 let b: *u8 = sys_mmap(need)
258 var o: i64 = pk_cat(b, 0, prevhex)
259 b[o] = PK_BAR as u8; o = o + 1
260 o = pk_cat(b, o, dighex)
261 b[o] = PK_BAR as u8; o = o + 1
262 o = ccz_cat_num(b, o, nbytes)
263 pk_sha_hex(b, o, outhex)
264 sys_munmap(b, need)
265 return 0
266}
267// field `idx` of the line starting at `st` (len `ln`) into out (cap bytes); returns length or -1
268func pk_field(buf: *u8, st: i64, ln: i64, idx: i64, out: *u8, cap: i64) -> i64 {
269 var f: i64 = 0
270 var i: i64 = 0
271 var w: i64 = 0
272 var started: i64 = 0
273 while i < ln {
274 let c: i64 = buf[st + i] as i64
275 if c == PK_BAR { f = f + 1; if f > idx { out[w] = 0 as u8; return w } }
276 else { if f == idx { if w < cap - 1 { out[w] = buf[st + i]; w = w + 1; started = 1 } } }
277 i = i + 1
278 }
279 out[w] = 0 as u8
280 if f < idx { return 0 - 1 }
281 if started == 0 { if w == 0 { return 0 - 1 } }
282 return w
283}
284// Walk the whole journal from genesis. Returns -1 when every link verifies, else the 0-based index of
285// the FIRST row that does not. entries[0] receives the row count, head[] the final chain value.
286// A row is bad when: it has fewer than PK_NFIELDS fields, its seq is out of order, its prev is not the
287// running chain, or its chain is not sha256(prev|digest|bytes).
288func pk_log_verify(logpath: *u8, entries: *i64, head: *u8) -> i64 {
289 pk_genesis(head)
290 entries[0] = 0
291 let lp: *i64 = sys_mmap(16) as *i64
292 let buf: *u8 = sys_read_file(logpath, lp)
293 if (buf as i64) == 0 { return 0 - 1 } // absent journal == empty chain, not a break
294 let n: i64 = lp[0]
295 let fcap: i64 = n + 2 // a field can never exceed the whole file: DERIVED
296 let fdig: *u8 = sys_mmap(fcap)
297 let fprev: *u8 = sys_mmap(fcap)
298 let fchain: *u8 = sys_mmap(fcap)
299 let fbytes: *u8 = sys_mmap(fcap)
300 let fseq: *u8 = sys_mmap(fcap)
301 let calc: *u8 = sys_mmap(PK_SHAHEX + 8)
302 var st: i64 = 0
303 var idx: i64 = 0
304 var bad: i64 = 0 - 1
305 while st < n {
306 var e: i64 = st
307 while e < n { if (buf[e] as i64) == PK_NL { break } e = e + 1 }
308 let ln: i64 = e - st
309 if ln > 0 {
310 if bad < 0 {
311 var rowok: i64 = 1
312 if pk_field(buf, st, ln, PK_F_SEQ, fseq, fcap) < 0 { rowok = 0 }
313 if pk_field(buf, st, ln, PK_F_DIG, fdig, fcap) < 0 { rowok = 0 }
314 if pk_field(buf, st, ln, PK_F_BYTES, fbytes, fcap) < 0 { rowok = 0 }
315 if pk_field(buf, st, ln, PK_F_PREV, fprev, fcap) < 0 { rowok = 0 }
316 if pk_field(buf, st, ln, PK_F_CHAIN, fchain, fcap) < 0 { rowok = 0 }
317 if rowok == 1 {
318 if pk_atoi(fseq) != idx { rowok = 0 }
319 if pk_streq(fprev, head) == 0 { rowok = 0 }
320 let nb: i64 = pk_atoi(fbytes)
321 if nb < 0 { rowok = 0 }
322 if rowok == 1 {
323 pk_chain_step(fprev, fdig, nb, calc)
324 if pk_streq(calc, fchain) == 0 { rowok = 0 }
325 }
326 }
327 if rowok == 0 { bad = idx } else { pk_cat(head, 0, fchain) }
328 }
329 idx = idx + 1
330 }
331 st = e + 1
332 }
333 entries[0] = idx
334 sys_free_file(buf, n)
335 return bad
336}
337// Append one row. REFUSES on a broken chain rather than extending it: you cannot append to a tampered
338// journal. Returns PK_OK or PK_R_CHAIN / PK_R_IO.
339func pk_log_append(logpath: *u8, op: *u8, dighex: *u8, nbytes: i64) -> i64 {
340 let entries: *i64 = sys_mmap(16) as *i64
341 let head: *u8 = sys_mmap(PK_SHAHEX + 8)
342 let bad: i64 = pk_log_verify(logpath, entries, head)
343 if bad >= 0 { return PK_R_CHAIN }
344 let chain: *u8 = sys_mmap(PK_SHAHEX + 8)
345 pk_chain_step(head, dighex, nbytes, chain)
346 let need: i64 = PK_I64_DIGITS + 1 + pk_slen(op) + 1 + PK_SHAHEX + 1 + PK_I64_DIGITS + 1 + PK_SHAHEX + 1 + PK_SHAHEX + 8
347 let ln: *u8 = sys_mmap(need)
348 var o: i64 = ccz_cat_num(ln, 0, entries[0])
349 ln[o] = PK_BAR as u8; o = o + 1
350 o = pk_cat(ln, o, op)
351 ln[o] = PK_BAR as u8; o = o + 1
352 o = pk_cat(ln, o, dighex)
353 ln[o] = PK_BAR as u8; o = o + 1
354 o = ccz_cat_num(ln, o, nbytes)
355 ln[o] = PK_BAR as u8; o = o + 1
356 o = pk_cat(ln, o, head)
357 ln[o] = PK_BAR as u8; o = o + 1
358 o = pk_cat(ln, o, chain)
359 ln[o] = PK_NL as u8; o = o + 1
360 let fd: i64 = sys_openat_append(logpath, MODE_0644)
361 if fd < 0 { sys_munmap(ln, need); return PK_R_IO }
362 sys_write(fd, ln, o)
363 sys_close(fd)
364 sys_munmap(ln, need)
365 return PK_OK
366}
367
368// ================= THE CONTRACT PRIMITIVE =========================================================
369// pkg_cas_resolve -- resolve a module BY CONTENT HASH.
370//
371// It takes a DIGEST, never a name: there is no version negotiation and nothing to typosquat at this
372// layer. The blob's location is only a hint; its bytes are re-hashed and compared before anything is
373// written, so a poisoned store yields a named refusal, never different code.
374//
375// IT PERFORMS NO EXECUTION AND NO INTERPRETATION. The module's bytes are read, hashed and written.
376// They are never parsed, never scanned for a manifest, never handed to fork/execve. There is no
377// install hook to honour because there is no code path that could honour one.
378//
379// outbytes[0] receives the byte count on success. Returns PK_OK or a PK_R_* code whose rule NAME comes
380// from pk_rule() and whose exit code comes from pk_exit_for() -- one owner each, so they cannot drift.
381func pkg_cas_resolve(store: *u8, dighex: *u8, dest: *u8, outbytes: *i64) -> i64 {
382 outbytes[0] = 0
383 if pk_is_hex64(dighex) == 0 { return PK_R_MALFORMED }
384 if pk_dest_ok(dest) == 0 { return PK_R_DEST }
385 let bp: *u8 = pk_blob_path(store, dighex)
386 let lp: *i64 = sys_mmap(16) as *i64
387 let buf: *u8 = sys_read_file(bp, lp)
388 if (buf as i64) == 0 { return PK_R_ABSENT }
389 let n: i64 = lp[0]
390 let got: *u8 = sys_mmap(PK_SHAHEX + 8)
391 pk_sha_hex(buf, n, got)
392 // THE ONE LAW: the store path is a hint, the hash is the identity.
393 if pk_streq(got, dighex) == 0 { sys_free_file(buf, n); return PK_R_MISMATCH }
394 if pk_write_atomic(dest, buf, n) < 0 { sys_free_file(buf, n); return PK_R_IO }
395 sys_free_file(buf, n)
396 outbytes[0] = n
397 return PK_OK
398}
399
400// ---- the pin lockfile: <name>|<digest>\n ----------------------------------------------------------
401// EXACT name match at a line start, terminated by the separator. A lookalike name shares no row with
402// the real one, and a prefix cannot match. Returns 1 found / 0 not found / 2 AMBIGUOUS (the same name
403// declared twice) -- a declaration file that permits two answers for one key is a coin flip wearing a
404// schema, so a duplicate REFUSES instead of picking first-match or last-match.
405func pk_lock_lookup(buf: *u8, n: i64, name: *u8, outdig: *u8) -> i64 {
406 let nl: i64 = pk_slen(name)
407 var st: i64 = 0
408 var hits: i64 = 0
409 while st < n {
410 var e: i64 = st
411 while e < n { if (buf[e] as i64) == PK_NL { break } e = e + 1 }
412 let ln: i64 = e - st
413 if ln > nl {
414 var same: i64 = 1
415 var i: i64 = 0
416 while i < nl { if buf[st + i] != name[i] { same = 0; i = nl } else { i = i + 1 } }
417 if same == 1 {
418 if (buf[st + nl] as i64) == PK_BAR {
419 var w: i64 = 0
420 var j: i64 = st + nl + 1
421 while j < e { if w < PK_SHAHEX { outdig[w] = buf[j]; w = w + 1 } j = j + 1 }
422 outdig[w] = 0 as u8
423 hits = hits + 1
424 }
425 }
426 }
427 st = e + 1
428 }
429 if hits == 0 { return 0 }
430 if hits > 1 { return 2 }
431 return 1
432}
433
434// ---- conf: every path is data, an absent key REFUSES --------------------------------------------
435struct PkConf { store: *u8, lock: *u8, log: *u8, ok: i64 }
436func pk_conf_load(confpath: *u8) -> *PkConf {
437 let c: *PkConf = sys_mmap(64) as *PkConf
438 c.ok = 0
439 let lp: *i64 = sys_mmap(16) as *i64
440 let buf: *u8 = sys_read_file(confpath, lp)
441 if (buf as i64) == 0 { return c }
442 let n: i64 = lp[0]
443 let cap: i64 = n + 2 // a value can never exceed the file: DERIVED, not capped
444 c.store = sys_mmap(cap)
445 c.lock = sys_mmap(cap)
446 c.log = sys_mmap(cap)
447 var good: i64 = 1
448 if lcf_str(buf, n, "store_root" as *u8, c.store, cap) < 0 { good = 0 }
449 if lcf_str(buf, n, "lock_path" as *u8, c.lock, cap) < 0 { good = 0 }
450 if lcf_str(buf, n, "log_path" as *u8, c.log, cap) < 0 { good = 0 }
451 c.ok = good
452 return c
453}
454
455// ---- ORIGINAL DEPENDENCY-RESOLUTION SELF-TEST (unchanged behaviour, now the `selftest` verb) -------
456func g_pn(v: i64) -> i64 { nxi_out(v); return 0 }
457func ck(name: *u8, c: i64) -> i64 { if c==1 { g_puts(" PASS " as *u8) } else { g_puts(" FAIL " as *u8) } g_puts(name); g_puts("\n" as *u8); return c }
458func slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
459func psum(s: *u8) -> i64 { var acc: i64=0; var i: i64=0; while s[i]!=(0 as u8){ acc=acc+(s[i] as i64); i=i+1 } return acc }
460
461const MAXD: i64 = 4
462
463func pkg_needed(target: i64, ndep: *i64, dep: *i64, N: i64, needed: *i64) -> i64 {
464 var i: i64=0; while i<N { needed[i]=0; i=i+1 }
465 needed[target]=1; var changed: i64=1
466 while changed==1 { changed=0; var u: i64=0; while u<N { if needed[u]==1 { var k: i64=0; while k<ndep[u] { let dpk: i64=dep[u*MAXD+k]; if needed[dpk]==0 { needed[dpk]=1; changed=1 } k=k+1 } } u=u+1 } }
467 return 0
468}
469func pkg_topo(ndep: *i64, dep: *i64, N: i64, order: *i64) -> i64 {
470 let indeg: *i64 = sys_mmap(N*8) as *i64; let done: *i64 = sys_mmap(N*8) as *i64
471 var i: i64=0; while i<N { indeg[i]=ndep[i]; done[i]=0; i=i+1 }
472 var oc: i64=0; var changed: i64=1
473 while changed==1 { changed=0; var u: i64=0
474 while u<N { if done[u]==0 { if indeg[u]==0 {
475 order[oc]=u; oc=oc+1; done[u]=1; changed=1
476 var v: i64=0; while v<N { if done[v]==0 { var k: i64=0; while k<ndep[v] { if dep[v*MAXD+k]==u { indeg[v]=indeg[v]-1 } k=k+1 } } v=v+1 } } }
477 u=u+1 } }
478 return oc
479}
480func pkg_verify(content: *u8, expected: i64) -> i64 { if psum(content)==expected { return 1 } return 0 }
481
482func pk_selftest() -> i64 {
483 g_puts("nx_pkg selftest (dependency resolution: transitive closure + topo order + integrity)\n" as *u8)
484 var pass: i64=0; var total: i64=0
485 let N: i64=6
486 let ndep: *i64 = sys_mmap(N*8) as *i64; let dep: *i64 = sys_mmap(N*MAXD*8) as *i64
487 let nm: *i64 = sys_mmap(N*8) as *i64; let ct: *i64 = sys_mmap(N*8) as *i64; let sha: *i64 = sys_mmap(N*8) as *i64
488 nm[0]=("syscalls" as *u8) as i64; nm[1]=("alloc" as *u8) as i64; nm[2]=("net" as *u8) as i64; nm[3]=("blockfs" as *u8) as i64; nm[4]=("tls" as *u8) as i64; nm[5]=("shell" as *u8) as i64
489 ct[0]=("pkg:syscalls:v1:payload" as *u8) as i64; ct[1]=("pkg:alloc:v1:payload" as *u8) as i64; ct[2]=("pkg:net:v1:payload" as *u8) as i64; ct[3]=("pkg:blockfs:v1:payload" as *u8) as i64; ct[4]=("pkg:tls:v1:payload" as *u8) as i64; ct[5]=("pkg:shell:v1:payload" as *u8) as i64
490 ndep[0]=0
491 ndep[1]=1; dep[1*MAXD+0]=0
492 ndep[2]=1; dep[2*MAXD+0]=0
493 ndep[3]=2; dep[3*MAXD+0]=1; dep[3*MAXD+1]=0
494 ndep[4]=1; dep[4*MAXD+0]=2
495 ndep[5]=2; dep[5*MAXD+0]=3; dep[5*MAXD+1]=1
496 var i: i64=0; while i<N { sha[i]=psum(ct[i] as *u8); i=i+1 }
497
498 let needed: *i64 = sys_mmap(N*8) as *i64; pkg_needed(5, ndep, dep, N, needed)
499 let order: *i64 = sys_mmap(N*8) as *i64; let oc: i64 = pkg_topo(ndep, dep, N, order)
500 g_puts(" install shell -> order: "); var nneed: i64=0; var ii: i64=0
501 while ii<oc { if needed[order[ii]]==1 { g_puts(nm[order[ii]] as *u8); g_puts(" " as *u8); nneed=nneed+1 } ii=ii+1 } g_puts("\n" as *u8)
502 let posneeded: *i64 = sys_mmap(N*8) as *i64; var pp: i64=0; var seq: i64=0
503 while pp<oc { if needed[order[pp]]==1 { posneeded[order[pp]]=seq; seq=seq+1 } pp=pp+1 }
504 var depok: i64=1; var u: i64=0
505 while u<N { if needed[u]==1 { var k: i64=0; while k<ndep[u] { if posneeded[dep[u*MAXD+k]] >= posneeded[u] { depok=0 } k=k+1 } } u=u+1 }
506 var t1: i64=0; if nneed==4 { if needed[2]==0 { if needed[4]==0 { if depok==1 { t1=1 } } } }
507 pass=pass+ck("T1: install 'shell' -> only transitive deps needed (4: syscalls/alloc/blockfs/shell), deps-ordered" as *u8, t1); total=total+1
508
509 var allok: i64=1; i=0; while i<N { if pkg_verify(ct[i] as *u8, sha[i])==0 { allok=0 } i=i+1 }
510 var t2: i64=0; if allok==1 { t2=1 }
511 pass=pass+ck("T2: every package verifies (content sum == manifest sum)" as *u8, t2); total=total+1
512
513 var t3: i64=0; if pkg_verify(ct[3] as *u8, sha[3] ^ 0x55)==0 { if pkg_verify(ct[3] as *u8, sha[3])==1 { t3=1 } }
514 pass=pass+ck("T3 (teeth): a checksum MISMATCH is rejected while the true one passes" as *u8, t3); total=total+1
515
516 let cn: i64=2; let cnd: *i64 = sys_mmap(cn*8) as *i64; let cdp: *i64 = sys_mmap(cn*MAXD*8) as *i64; let cord: *i64 = sys_mmap(cn*8) as *i64
517 cnd[0]=1; cdp[0*MAXD+0]=1; cnd[1]=1; cdp[1*MAXD+0]=0
518 let coc: i64 = pkg_topo(cnd, cdp, cn, cord)
519 var t4: i64=0; if coc<cn { t4=1 }
520 pass=pass+ck("T4 (teeth): a dependency cycle is detected (cannot resolve install order)" as *u8, t4); total=total+1
521
522 // T5: the SHA-256 lane the CAS resolver actually uses -- the byte-sum above is a toy and is kept
523 // only so the original teeth stay runnable. This asserts the real width the whole lane depends on.
524 let probe: *u8 = sys_mmap(PK_SHAHEX + 8)
525 pk_sha_hex("abc" as *u8, 3, probe)
526 var t5: i64=0
527 if pk_is_hex64(probe) == 1 { if PK_SHAHEX == PK_DIGEST_BYTES * 2 { t5 = 1 } }
528 pass=pass+ck("T5: the CAS digest lane emits a well-formed sha256 hex of the declared width" as *u8, t5); total=total+1
529
530 var okall: i64=0; if pass==total { okall=1 }
531 g_puts("---- nx_pkg selftest: passed "); g_pn(pass); g_puts(" / "); g_pn(total); g_puts(" ----\n" as *u8)
532 if okall==1 { g_puts("verdict=GREEN\n" as *u8); return 0 }
533 g_puts("verdict=RED\n" as *u8); return 1
534}
535
536func pk_usage() -> i64 {
537 pk_eputs("usage: nx_pkg <verb> ...\n" as *u8)
538 pk_eputs(" cas_put <file> [conf]\n" as *u8)
539 pk_eputs(" cas_resolve <digest64> <dest> [conf]\n" as *u8)
540 pk_eputs(" pin <name> <digest64> [conf]\n" as *u8)
541 pk_eputs(" resolve <name> <dest> [conf]\n" as *u8)
542 pk_eputs(" logverify [conf]\n" as *u8)
543 pk_eputs(" selftest\n" as *u8)
544 pk_eputs("an unrecognised verb REFUSES here; it never falls through to a permissive default.\n" as *u8)
545 // Every refusal names a rule AND carries a distinct status, so a caller can branch on the reason
546 // rather than on the fact of refusal. MEASURED 2026-08-25 by a mutation of the pin lookup: with the
547 // typosquat guard disabled the lookalike name was STILL REFUSED -- as malformed-digest, exit 5 --
548 // and a test that only asked `was it refused?` would have scored that broken build a PASS.
549 pk_eputs("exit: 0 ok | 3 usage/unknown-verb | 4 conf-missing-key | 5 malformed-digest | 6 digest-absent\n" as *u8)
550 pk_eputs(" 7 digest-mismatch | 8 dest-refused | 9 name-not-pinned | 10 pin-conflict | 11 chain-break | 12 io | 13 pin-ambiguous\n" as *u8)
551 return PK_EXIT_USAGE
552}
553func pk_refuse(prefix: *u8, code: i64) -> i64 {
554 g_puts(prefix); g_puts(" rule=" as *u8); g_puts(pk_rule(code)); g_puts("\n" as *u8)
555 return pk_exit_for(code)
556}
557
558func main(argc: i64, argv: *i64) -> i64 {
559 // NO ARGS = the original self-test, byte-for-byte the behaviour this organ shipped with.
560 if argc < 2 { let rc: i64 = pk_selftest(); sys_exit(rc); return rc }
561 let verb: *u8 = argv[1] as *u8
562 if pk_streq(verb, "selftest" as *u8) == 1 { let rc: i64 = pk_selftest(); sys_exit(rc); return rc }
563
564 // THE VERB IS VALIDATED BEFORE ANYTHING ELSE IS TOUCHED. Measured 2026-08-25 by this organ's own
565 // gate: an unrecognised verb used to fall through to the conf loader and refuse with exit 4
566 // (conf-missing-key), so a TYPO reported as a CONFIGURATION FAULT and sent the reader at the wrong
567 // subject entirely. A refusal must name the conjunct that actually fired.
568 var known: i64 = 0
569 if pk_streq(verb, "cas_put" as *u8) == 1 { known = 1 }
570 if pk_streq(verb, "cas_resolve" as *u8) == 1 { known = 1 }
571 if pk_streq(verb, "pin" as *u8) == 1 { known = 1 }
572 if pk_streq(verb, "resolve" as *u8) == 1 { known = 1 }
573 if pk_streq(verb, "logverify" as *u8) == 1 { known = 1 }
574 if known == 0 {
575 pk_eputs("PKG-REFUSED rule=unknown-verb verb=" as *u8); pk_eputs(verb); pk_eputs("\n" as *u8)
576 let u0: i64 = pk_usage(); sys_exit(u0); return u0
577 }
578
579 // which conf this run reads is positional per verb; a non-default run therefore touches ONLY the
580 // store and journal its own arguments name -- it can never write the production journal.
581 var confpath: *u8 = PK_CONF_DEFAULT
582 if pk_streq(verb, "cas_put" as *u8) == 1 { if argc >= 4 { confpath = argv[3] as *u8 } }
583 if pk_streq(verb, "cas_resolve" as *u8) == 1 { if argc >= 5 { confpath = argv[4] as *u8 } }
584 if pk_streq(verb, "pin" as *u8) == 1 { if argc >= 5 { confpath = argv[4] as *u8 } }
585 if pk_streq(verb, "resolve" as *u8) == 1 { if argc >= 5 { confpath = argv[4] as *u8 } }
586 if pk_streq(verb, "logverify" as *u8) == 1 { if argc >= 3 { confpath = argv[2] as *u8 } }
587
588 let cf: *PkConf = pk_conf_load(confpath)
589 if cf.ok == 0 {
590 g_puts("PKG-REFUSED rule=" as *u8); g_puts(pk_rule(PK_R_CONF))
591 g_puts(" conf=" as *u8); g_puts(confpath)
592 g_puts(" (needs store_root, lock_path, log_path -- an absent key REFUSES, it never defaults)\n" as *u8)
593 sys_exit(PK_EXIT_CONF); return PK_EXIT_CONF
594 }
595
596 // ---- cas_put ---------------------------------------------------------------------------------
597 if pk_streq(verb, "cas_put" as *u8) == 1 {
598 if argc < 3 { let u: i64 = pk_usage(); sys_exit(u); return u }
599 let src: *u8 = argv[2] as *u8
600 let lp: *i64 = sys_mmap(16) as *i64
601 let buf: *u8 = sys_read_file(src, lp)
602 if (buf as i64) == 0 { let r: i64 = pk_refuse("PKG-CAS-PUT-REFUSED" as *u8, PK_R_IO); sys_exit(r); return r }
603 let n: i64 = lp[0]
604 let dig: *u8 = sys_mmap(PK_SHAHEX + 8)
605 pk_sha_hex(buf, n, dig)
606 pk_mkdir(cf.store)
607 let bp: *u8 = pk_blob_path(cf.store, dig)
608 var isnew: i64 = 1
609 if pk_exists(bp) == 1 { isnew = 0 }
610 if pk_write_atomic(bp, buf, n) < 0 { sys_free_file(buf, n); let r: i64 = pk_refuse("PKG-CAS-PUT-REFUSED" as *u8, PK_R_IO); sys_exit(r); return r }
611 sys_free_file(buf, n)
612 let lg: i64 = pk_log_append(cf.log, "put" as *u8, dig, n)
613 if lg != PK_OK { let r: i64 = pk_refuse("PKG-CAS-PUT-REFUSED" as *u8, lg); sys_exit(r); return r }
614 g_puts("PKG-CAS-PUT ok digest=" as *u8); g_puts(dig)
615 g_puts(" bytes=" as *u8); pk_num(n)
616 g_puts(" new=" as *u8); pk_num(isnew)
617 g_puts(" store=" as *u8); g_puts(cf.store); g_puts("\n" as *u8)
618 sys_exit(0); return 0
619 }
620
621 // ---- cas_resolve : THE CONTRACT PRIMITIVE ----------------------------------------------------
622 if pk_streq(verb, "cas_resolve" as *u8) == 1 {
623 if argc < 4 { let u: i64 = pk_usage(); sys_exit(u); return u }
624 let dig: *u8 = argv[2] as *u8
625 let dest: *u8 = argv[3] as *u8
626 let nb: *i64 = sys_mmap(16) as *i64
627 let code: i64 = pkg_cas_resolve(cf.store, dig, dest, nb)
628 if code != PK_OK {
629 g_puts("PKG-CAS-REFUSED rule=" as *u8); g_puts(pk_rule(code))
630 g_puts(" digest=" as *u8); g_puts(dig)
631 g_puts(" dest=" as *u8); g_puts(dest); g_puts("\n" as *u8)
632 let r: i64 = pk_exit_for(code); sys_exit(r); return r
633 }
634 let lg: i64 = pk_log_append(cf.log, "resolve" as *u8, dig, nb[0])
635 if lg != PK_OK { let r: i64 = pk_refuse("PKG-CAS-REFUSED" as *u8, lg); sys_exit(r); return r }
636 g_puts("PKG-CAS-RESOLVE ok digest=" as *u8); g_puts(dig)
637 g_puts(" bytes=" as *u8); pk_num(nb[0])
638 g_puts(" dest=" as *u8); g_puts(dest); g_puts("\n" as *u8)
639 sys_exit(0); return 0
640 }
641
642 // ---- pin : THE TRUST BOUNDARY ----------------------------------------------------------------
643 if pk_streq(verb, "pin" as *u8) == 1 {
644 if argc < 4 { let u: i64 = pk_usage(); sys_exit(u); return u }
645 let name: *u8 = argv[2] as *u8
646 let dig: *u8 = argv[3] as *u8
647 if pk_is_hex64(dig) == 0 { let r: i64 = pk_refuse("PKG-PIN-REFUSED" as *u8, PK_R_MALFORMED); sys_exit(r); return r }
648 let lp: *i64 = sys_mmap(16) as *i64
649 let lb: *u8 = sys_read_file(cf.lock, lp)
650 var have: i64 = 0
651 let cur: *u8 = sys_mmap(PK_SHAHEX + 8)
652 cur[0] = 0 as u8
653 if (lb as i64) != 0 { have = pk_lock_lookup(lb, lp[0], name, cur) }
654 if have == 2 { let r: i64 = pk_refuse("PKG-PIN-REFUSED" as *u8, PK_R_PINAMBIG); sys_exit(r); return r }
655 if have == 1 {
656 if pk_streq(cur, dig) == 1 {
657 g_puts("PKG-PIN ok name=" as *u8); g_puts(name); g_puts(" digest=" as *u8); g_puts(dig); g_puts(" new=0\n" as *u8)
658 sys_exit(0); return 0
659 }
660 g_puts("PKG-PIN-REFUSED rule=" as *u8); g_puts(pk_rule(PK_R_PINCONFLICT))
661 g_puts(" name=" as *u8); g_puts(name)
662 g_puts(" pinned=" as *u8); g_puts(cur)
663 g_puts(" requested=" as *u8); g_puts(dig); g_puts("\n" as *u8)
664 sys_exit(PK_EXIT_PINCONFLICT); return PK_EXIT_PINCONFLICT
665 }
666 let need: i64 = pk_slen(name) + PK_SHAHEX + 8
667 let row: *u8 = sys_mmap(need)
668 var o: i64 = pk_cat(row, 0, name)
669 row[o] = PK_BAR as u8; o = o + 1
670 o = pk_cat(row, o, dig)
671 row[o] = PK_NL as u8; o = o + 1
672 let fd: i64 = sys_openat_append(cf.lock, MODE_0644)
673 if fd < 0 { let r: i64 = pk_refuse("PKG-PIN-REFUSED" as *u8, PK_R_IO); sys_exit(r); return r }
674 sys_write(fd, row, o)
675 sys_close(fd)
676 g_puts("PKG-PIN ok name=" as *u8); g_puts(name); g_puts(" digest=" as *u8); g_puts(dig); g_puts(" new=1\n" as *u8)
677 sys_exit(0); return 0
678 }
679
680 // ---- resolve <name> : exact-match pin, then the digest lane ----------------------------------
681 if pk_streq(verb, "resolve" as *u8) == 1 {
682 if argc < 4 { let u: i64 = pk_usage(); sys_exit(u); return u }
683 let name: *u8 = argv[2] as *u8
684 let dest: *u8 = argv[3] as *u8
685 let lp: *i64 = sys_mmap(16) as *i64
686 let lb: *u8 = sys_read_file(cf.lock, lp)
687 let dig: *u8 = sys_mmap(PK_SHAHEX + 8)
688 dig[0] = 0 as u8
689 var have: i64 = 0
690 if (lb as i64) != 0 { have = pk_lock_lookup(lb, lp[0], name, dig) }
691 if have == 2 {
692 g_puts("PKG-RESOLVE-REFUSED rule=" as *u8); g_puts(pk_rule(PK_R_PINAMBIG))
693 g_puts(" name=" as *u8); g_puts(name); g_puts("\n" as *u8)
694 sys_exit(PK_EXIT_PINAMBIG); return PK_EXIT_PINAMBIG
695 }
696 if have == 0 {
697 // THE TYPOSQUAT KILL: an unpinned name fails CLOSED. There is no store scan, no nearest
698 // match, no `latest` -- a lookalike name simply has no row, so it resolves to nothing.
699 g_puts("PKG-RESOLVE-REFUSED rule=" as *u8); g_puts(pk_rule(PK_R_NOTPINNED))
700 g_puts(" name=" as *u8); g_puts(name)
701 g_puts(" (no exact pin; resolution never guesses a near name)\n" as *u8)
702 sys_exit(PK_EXIT_NOTPINNED); return PK_EXIT_NOTPINNED
703 }
704 let nb: *i64 = sys_mmap(16) as *i64
705 let code: i64 = pkg_cas_resolve(cf.store, dig, dest, nb)
706 if code != PK_OK {
707 g_puts("PKG-RESOLVE-REFUSED rule=" as *u8); g_puts(pk_rule(code))
708 g_puts(" name=" as *u8); g_puts(name)
709 g_puts(" digest=" as *u8); g_puts(dig); g_puts("\n" as *u8)
710 let r: i64 = pk_exit_for(code); sys_exit(r); return r
711 }
712 let lg: i64 = pk_log_append(cf.log, "resolve" as *u8, dig, nb[0])
713 if lg != PK_OK { let r: i64 = pk_refuse("PKG-RESOLVE-REFUSED" as *u8, lg); sys_exit(r); return r }
714 g_puts("PKG-RESOLVE ok name=" as *u8); g_puts(name)
715 g_puts(" digest=" as *u8); g_puts(dig)
716 g_puts(" bytes=" as *u8); pk_num(nb[0])
717 g_puts(" dest=" as *u8); g_puts(dest); g_puts("\n" as *u8)
718 sys_exit(0); return 0
719 }
720
721 // ---- logverify -------------------------------------------------------------------------------
722 if pk_streq(verb, "logverify" as *u8) == 1 {
723 let entries: *i64 = sys_mmap(16) as *i64
724 let head: *u8 = sys_mmap(PK_SHAHEX + 8)
725 let bad: i64 = pk_log_verify(cf.log, entries, head)
726 g_puts("PKG-LOG entries=" as *u8); pk_num(entries[0])
727 if bad >= 0 {
728 g_puts(" chain=BREAK at=" as *u8); pk_num(bad)
729 g_puts(" rule=" as *u8); g_puts(pk_rule(PK_R_CHAIN)); g_puts("\n" as *u8)
730 sys_exit(PK_EXIT_CHAIN); return PK_EXIT_CHAIN
731 }
732 g_puts(" chain=OK head=" as *u8); g_puts(head)
733 g_puts(" (tamper-evident against a partial edit; NOT sumdb -- one writer, unsigned, unwitnessed)\n" as *u8)
734 sys_exit(0); return 0
735 }
736
737 let u: i64 = pk_usage()
738 sys_exit(u)
739 return u
740}