nx_site_publish_prepare_t198.nx source
↩ module page · 731 lines · 39535 B
1// nx_site_publish_lib.nx -- ARTIFACT-PUBLISH, LIBRARY half (sp_main dispatcher) capability (closes the coverage BUILD-queue gap "artifact-publish";
2// retires the ssh-cat content lane for pages). Places a NAS-local file into the live sites docroot the
3// SAFE way, composing the proven IO layer:
4// publish <src-file> <site> <relpath> -> sites/<site>/<relpath>
5// - ATOMIC placement via fsx_write (tmp+fsync+rename -- a browser never sees a torn page)
6// - PRIOR VERSION PRESERVED: an existing target is first copied to <relpath>.prev (rename-not-delete
7// doctrine; one-deep history -- generated artifacts, git holds sources)
8// - DENY BY CONSTRUCTION (fsx_write_denied: secrets/device-ns/allowlist) PLUS path-traversal refusal:
9// any ".." segment or absolute site/relpath is DENIED (an MCP caller must not escape the docroot)
10// The organ prints the live URL; verification is the SEPARATE browser-grade organ (composition, not a
11// built-in fallback): run nx_page_verify <url> from a WAN vantage (on-NAS bare-domain fetches hit DSM --
12// the documented trap). The publish->verify recipe is the contract; this organ does the placement half.
13// - THE PUBLISH GATEWAY (F763 second half, wired 2026-07-30): for a site listed in
14// knowledge/pub_sites.conf, a <relpath> absent from that site's publishing REGISTRY -- or
15// carrying a non-publishable status (withdrawn/redirect/debris) -- is REFUSED. This is the
16// half that makes "random and arbitrary" impossible instead of merely visible: nx_pub_desk
17// stated the rule in 2026-07-19 and its own header recorded that enforcement inside the
18// publish organ was never wired, so anything could be dropped into any docroot and become
19// site truth. MEASURED before wiring: 54 pages served on nishifamily.com were in NO registry.
20// Sites NOT in the site table publish UNGATED (with a warning) -- onboarding is a config row,
21// and a control that refuses everything on day one is a control someone disables by lunchtime.
22// exit: 0 ok | 2 usage | 3 src absent | 4 io | 5 DENIED license_tier: ORIGINAL
23import "nx_fsops_lib.nx" // fsx_write (atomic + write-deny), vw_read, vw_slen, fsx_puts, fsx_putn
24import "nx_pub_lib.nx" // pl_site_lookup + pl_check -- the registry gateway
25import "nx_pub_release_lib.nx"
26import "nx_sha256.nx"
27import "nx_comparestale_lib.nx"
28import "nx_json.nx"
29
30const PB_ARG_VERB: i64 = 1 // argv: verb ("publish")
31const PB_RC_USAGE: i64 = 2 // exit: usage
32const PB_ARG_SRC: i64 = 2 // argv: source file (NAS-local)
33const PB_ARG_SITE: i64 = 3 // argv: site dir under sites/
34const PB_ARG_REL: i64 = 4 // argv: page path under the site
35const PB_ARGC: i64 = 5 // publish src site relpath
36const PB_STAGE_ARGC: i64 = 6 // stage src site releases/<path> sha256
37const PB_GUARDED_ARGC: i64 = PB_STAGE_ARGC + 1 // adds expected live digest
38const PB_GUARDED_SIZED_ARGC: i64 = PB_GUARDED_ARGC + 1 // optional exact artifact extent
39const PB_PATH: i64 = 1024 // destination path buffer
40const PB_CONFCAP: i64 = 65536 // site-table read buffer (the gateway)
41const PB_SPANCAP: i64 = 128 // site-table span vector
42const PB_DOT: i64 = 46 // '.'
43const PB_SLASH: i64 = 47 // '/'
44
45// any ".." path segment, or a leading '/', or empty -> traversal refusal (1 = refuse)
46func pb_traversal(s: *u8) -> i64 {
47 if s[0] == (0 as u8) { return 1 }
48 if s[0] == (PB_SLASH as u8) { return 1 }
49 var i: i64 = 0
50 while s[i] != (0 as u8) {
51 if s[i] == (PB_DOT as u8) { if s[i+1] == (PB_DOT as u8) { return 1 } }
52 // Reject aliases before deriving the target lock: ./ and // could name the same file
53 // using different lock paths or conceal the reserved release namespace.
54 if s[i] == (PB_SLASH as u8) {
55 if s[i+1] == (PB_SLASH as u8) { return 1 }
56 if s[i+1] == (0 as u8) { return 1 }
57 }
58 var segment_start: i64 = 0
59 if i == 0 { segment_start = 1 } else {
60 if s[i-1] == (PB_SLASH as u8) { segment_start = 1 }
61 }
62 if segment_start == 1 {
63 if s[i] == (PB_DOT as u8) {
64 if (s[i+1] == (PB_SLASH as u8)) || (s[i+1] == (0 as u8)) { return 1 }
65 }
66 }
67 i = i + 1
68 }
69 return 0
70}
71// append NUL-terminated src to out at *op
72func pb_app(out: *u8, op: *i64, s: *u8) -> i64 {
73 var i: i64 = 0
74 var o: i64 = op[0]
75 while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 }
76 out[o] = 0 as u8
77 op[0] = o
78 return 0
79}
80
81// Artifact equality is byte-counted: WASM and other binary assets contain NUL bytes.
82func pb_same_bytes(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
83 if an != bn { return 0 }
84 if an < 0 { return 0 }
85 var i: i64 = 0
86 while i < an { if a[i] != b[i] { return 0 }; i = i + 1 }
87 return 1
88}
89
90const PB_LOCK_EX_NB: i64 = 6
91const PB_RC_CONFLICT: i64 = 8
92const PB_RC_BUSY: i64 = 9
93const PB_SHA_BYTES: i64 = 32
94const PB_SHA_HEX: i64 = 64
95func pb_hash(body: *u8, n: i64, hex: *u8) -> i64 {
96 let raw: *u8 = sys_mmap(PB_SHA_BYTES)
97 sha256_digest(body,n,raw)
98 let digits: *u8 = "0123456789abcdef" as *u8
99 var i: i64 = 0
100 while i < PB_SHA_BYTES {
101 let c: i64 = raw[i] as i64
102 hex[i*2] = digits[c/16]; hex[i*2+1] = digits[c%16]
103 i = i+1
104 }
105 hex[PB_SHA_HEX] = 0 as u8
106 sys_munmap(raw,PB_SHA_BYTES)
107 return 0
108}
109
110const PB_SIZED_ARGC: i64 = 7
111const PB_I64_MAX: i64 = 9223372036854775807
112func pb_sized_bytes(s: *u8) -> i64 {
113 var value: i64=0
114 var i: i64=0
115 while s[i] != (0 as u8) {
116 let c: i64=s[i] as i64
117 if c < 48 || c > 57 { return 0-1 }
118 if value > (PB_I64_MAX/2-1-(c-48))/10 { return 0-1 }
119 value=value*10+c-48; i=i+1
120 }
121 if value <= 0 { return 0-1 }
122 return value
123}
124func pb_read_bounded(path: *u8,b: *u8,cap: i64) -> i64 {
125 let fd: i64=sys_openat_rd(path)
126 if fd < 0 { return fd }
127 var n: i64=0
128 var go: i64=1
129 while n < cap && go == 1 {
130 let got: i64=sys_read(fd,((b as i64)+n) as *u8,cap-n)
131 if got < 0 { sys_close(fd); return got }
132 if got == 0 { go=0 } else { n=n+got }
133 }
134 sys_close(fd)
135 return n
136}
137
138func pb_main_edge(argc: i64, argv: *i64, edge_rel: *u8, edge_sha: *u8) -> i64 {
139 if argc < PB_ARGC { return pb_publish_locked_edge(argc,argv,edge_rel,edge_sha) }
140 let verb: *u8 = argv[PB_ARG_VERB] as *u8
141 if fsx_seq(verb,"publish" as *u8) == 0 {
142 if fsx_seq(verb,"stage" as *u8) == 1 {
143 if argc != PB_STAGE_ARGC { return PB_RC_USAGE }
144 } else {
145 if fsx_seq(verb,"publish-guarded" as *u8) == 0 { if fsx_seq(verb,"stage-sized" as *u8) == 0 { return PB_RC_USAGE } }
146 if argc != PB_GUARDED_ARGC {
147 if fsx_seq(verb,"publish-guarded" as *u8) == 0 || argc != PB_GUARDED_SIZED_ARGC { return PB_RC_USAGE }
148 }
149 }
150 }
151 let site: *u8 = argv[PB_ARG_SITE] as *u8
152 let rel: *u8 = argv[PB_ARG_REL] as *u8
153 var si: i64 = 0
154 while site[si] != (0 as u8) {
155 if site[si] == (PB_SLASH as u8) { return FSX_RC_DENIED }
156 si = si + 1
157 }
158 if fsx_seq(verb,"stage" as *u8) == 1 || fsx_seq(verb,"stage-sized" as *u8) == 1 {
159 if pb_release_path(rel) == 0 {
160 fsx_puts("NX-PUBLISH DENIED stage requires releases/<candidate-path>; live URL is not a staging target\n" as *u8)
161 return FSX_RC_DENIED
162 }
163 }
164 if pb_traversal(site) == 1 { return FSX_RC_DENIED }
165 if pb_traversal(rel) == 1 { return FSX_RC_DENIED }
166 if vw_slen(site)+vw_slen(rel)+vw_slen("sites//.publish-lock" as *u8)+1 >= PB_PATH { return FSX_RC_DENIED }
167 let path: *u8 = sys_mmap(PB_PATH)
168 let pos: *i64 = sys_mmap(8) as *i64
169 pos[0] = 0
170 pb_app(path,pos,"sites/" as *u8);pb_app(path,pos,site)
171 pb_app(path,pos,"/" as *u8);pb_app(path,pos,rel)
172 if fsx_write_denied(path) == 1 { return FSX_RC_DENIED }
173 // Keep one stable inode per target; unlinking a lock lets contenders lock different inodes.
174 pb_app(path,pos,".publish-lock" as *u8)
175 let fd: i64 = sys_openat_wr(path,FSX_MODE_RW)
176 if fd < 0 { fsx_puts("NX-PUBLISH IO lock-parent-unavailable\n" as *u8);return FSX_RC_IO }
177 if sys_flock(fd,PB_LOCK_EX_NB) != 0 {
178 sys_close(fd)
179 fsx_puts("NX-PUBLISH BUSY target-owned retry-after-owner-completes\n" as *u8)
180 return PB_RC_BUSY
181 }
182 let rc: i64 = pb_publish_locked_edge(argc,argv,edge_rel,edge_sha)
183 sys_close(fd)
184 return rc
185}
186// Every publisher verb enforces this namespace, so a legacy publish cannot replace a tested candidate.
187// The site registry still authorizes each release path; staging does not bypass publication policy.
188func pb_release_path(rel: *u8) -> i64 {
189 let prefix: *u8 = "releases/" as *u8
190 var i: i64 = 0
191 while prefix[i] != (0 as u8) {
192 if rel[i] != prefix[i] { return 0 }
193 i = i + 1
194 }
195 return (rel[i] != (0 as u8)) as i64
196}
197func pb_publish_locked_edge(argc: i64, argv: *i64, edge_rel: *u8, edge_sha: *u8) -> i64 {
198 var okverb: i64 = 0
199 if argc >= PB_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8, "publish" as *u8) == 1 { okverb = 1 } }
200 if argc == PB_GUARDED_ARGC || argc == PB_GUARDED_SIZED_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8,"publish-guarded" as *u8) == 1 { okverb = 1 } }
201 if argc == PB_STAGE_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage" as *u8) == 1 { okverb = 1 } }
202 if argc == PB_SIZED_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage-sized" as *u8) == 1 { okverb = 1 } }
203 if okverb == 0 {
204 fsx_puts("usage: nx_site_publish publish <src-file> <site> <relpath> (-> sites/<site>/<relpath>; then verify: nx_page_verify <url>)\n" as *u8)
205 fsx_puts(" nx_site_publish publish-guarded <src-file> <site> <relpath> <sha256> <expected-live-sha256|absent> [exact-bytes]\n nx_site_publish stage <src-file> <site> releases/<candidate-path> <sha256>\nStaging verifies file identity only; browser and dependency acceptance are separate evidence.\n" as *u8)
206 return PB_RC_USAGE
207 }
208 let site: *u8 = argv[PB_ARG_SITE] as *u8
209 let rel: *u8 = argv[PB_ARG_REL] as *u8
210 if pb_traversal(site) == 1 {
211 fsx_puts("NX-PUBLISH DENIED: site escapes the docroot ('..'/absolute/empty)\n" as *u8)
212 return FSX_RC_DENIED
213 }
214 if pb_traversal(rel) == 1 {
215 fsx_puts("NX-PUBLISH DENIED: relpath escapes the docroot ('..'/absolute/empty)\n" as *u8)
216 return FSX_RC_DENIED
217 }
218 // ---- THE PUBLISH GATEWAY: the registry decides what may exist at this URL, not the caller ----
219 let gw_conf: *u8 = sys_mmap(PB_CONFCAP)
220 let gw_spans: *i64 = sys_mmap(PB_SPANCAP) as *i64
221 var release_prefix: *u8 = 0 as *u8
222 var registry_checked: i64 = 0
223 if pl_site_lookup("knowledge/pub_sites.conf" as *u8, site, gw_conf, gw_spans) == 1 {
224 registry_checked = 1
225 let gw_pfx: *u8 = sys_mmap(PB_PATH)
226 pl_span_cstr(gw_conf, gw_spans[2], gw_spans[3], gw_pfx)
227 var row_status: i64 = 0
228 if pb_release_path(rel) == 1 { release_prefix = gw_pfx } else { row_status = pl_check(gw_pfx,rel) }
229 if row_status != 0 {
230 fsx_puts("NX-PUBLISH REFUSED: " as *u8); fsx_puts(rel)
231 fsx_puts(" is not a publishable row in this site's registry.\n" as *u8)
232 fsx_puts(" The registry -- not the caller -- decides what may exist at a URL. Either the path is\n" as *u8)
233 fsx_puts(" unregistered, or its status is withdrawn/redirect/debris.\n" as *u8)
234 fsx_puts(" Register it (id/title/owner/status/section/path/note, status=live|asset|draft) in " as *u8)
235 fsx_puts(gw_pfx); fsx_puts(" ,\n" as *u8)
236 fsx_puts(" or run: nx_pub_plane adopt " as *u8); fsx_puts(site)
237 fsx_puts(" to bring existing docroot entries in. Then re-run this publish.\n" as *u8)
238 return FSX_RC_DENIED
239 }
240 } else {
241 fsx_puts("NX-PUBLISH WARN: site '" as *u8); fsx_puts(site)
242 fsx_puts("' is not in knowledge/pub_sites.conf -- publishing UNGATED (add a row to gate it).\n" as *u8)
243 }
244 // stage-sized declares its exact artifact size; the read budget is that size plus one
245 // to distinguish complete bytes from truncation. Legacy verbs retain their existing cap.
246 var readcap: i64 = FSX_READ_CAP
247 var wanted: i64 = 0
248 var size_arg: i64 = 0
249 if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage-sized" as *u8) == 1 {
250 size_arg=6
251 if (release_prefix as i64) == 0 { return FSX_RC_DENIED }
252 }
253 if argc == PB_GUARDED_SIZED_ARGC && fsx_seq(argv[PB_ARG_VERB] as *u8,"publish-guarded" as *u8) == 1 {
254 size_arg=7
255 if registry_checked != 1 { return FSX_RC_DENIED }
256 }
257 if size_arg > 0 {
258 wanted=pb_sized_bytes(argv[size_arg] as *u8)
259 if wanted < 0 { return PB_RC_USAGE }
260 readcap=wanted+1
261 }
262 let body: *u8 = sys_mmap(readcap + 1)
263 let n: i64 = pb_read_bounded(argv[PB_ARG_SRC] as *u8, body, readcap)
264 if wanted > 0 { if n != wanted { fsx_puts("NX-PUBLISH REFUSED source-size-mismatch\n" as *u8); return PB_RC_CONFLICT } }
265 if n <= 0 {
266 fsx_puts("NX-PUBLISH ABSENT: cannot read source " as *u8); fsx_puts(argv[PB_ARG_SRC] as *u8); fsx_puts("\n" as *u8)
267 return FSX_RC_ABSENT
268 }
269 if n == readcap { fsx_puts("NX-PUBLISH REFUSED: source at/over the 1MiB organ cap -- use the mgmt upload lane\n" as *u8); return FSX_RC_IO }
270 // The owned body is checked once here and later passed unchanged to fsx_write.
271 if (edge_rel as i64)>0 {
272 let edge_rc: i64=pe_check(body,n,argv,edge_rel,edge_sha)
273 if edge_rc!=0{return edge_rc}
274 }
275 // Every verb checks the registered content, including legacy publish without a caller hash.
276 if (release_prefix as i64) != 0 {
277 let release_hash: *u8 = sys_mmap(PB_SHA_HEX+1)
278 pb_hash(body,n,release_hash)
279 let registered: i64 = pr_check(release_prefix,rel,release_hash)
280 sys_munmap(release_hash,PB_SHA_HEX+1)
281 if registered != PR_OK {
282 fsx_puts("NX-PUBLISH REFUSED release-registration code=" as *u8); fsx_putn(registered)
283 fsx_puts(" path=" as *u8); fsx_puts(rel)
284 fsx_puts("; verify catalogue integrity and registered digest before retrying\n" as *u8)
285 if registered == PR_IO { return FSX_RC_IO }
286 return PB_RC_CONFLICT
287 }
288 }
289 // build dst = sites/<site>/<relpath>
290 let dst: *u8 = sys_mmap(PB_PATH)
291 let op: *i64 = sys_mmap(8) as *i64
292 op[0] = 0
293 pb_app(dst, op, "sites/" as *u8)
294 pb_app(dst, op, site)
295 pb_app(dst, op, "/" as *u8)
296 pb_app(dst, op, rel)
297 // preserve the prior version (one-deep; rename-not-delete doctrine)
298 // The predecessor has its own extent: a smaller incoming artifact must still retain it whole.
299 var priorcap: i64 = readcap
300 if wanted > 0 {
301 let priorfd: i64 = sys_openat_rd(dst)
302 if priorfd >= 0 {
303 let extent: i64 = sys_lseek(priorfd,0,FSX_SEEK_END)
304 let closed: i64 = sys_close(priorfd)
305 if extent < 0 || extent > PB_I64_MAX/2-1 || closed != 0 {
306 fsx_puts("NX-PUBLISH REFUSED prior-extent-unavailable-or-overflow\n" as *u8)
307 return FSX_RC_IO
308 }
309 if extent >= priorcap { priorcap=extent+1 }
310 } else {
311 if priorfd != (0-2) { fsx_puts("NX-PUBLISH REFUSED prior-open-error\n" as *u8);return FSX_RC_IO }
312 }
313 }
314 let old: *u8 = sys_mmap(priorcap + 1)
315 if (old as i64) <= 0 { fsx_puts("NX-PUBLISH REFUSED prior-allocation-failed\n" as *u8);return FSX_RC_IO }
316 let on: i64 = pb_read_bounded(dst, old, priorcap)
317 // A retry must retain the last DIFFERENT version, not replace it with the current one.
318 if fsx_write_denied(dst) == 1 {
319 fsx_puts("NX-PUBLISH DENIED: destination rejected before retry comparison\n" as *u8)
320 return FSX_RC_DENIED
321 }
322 if on >= priorcap { fsx_puts("NX-PUBLISH REFUSED prior-at-read-cap\n" as *u8);return FSX_RC_IO }
323 let staged: i64 = fsx_seq(argv[PB_ARG_VERB] as *u8,"stage" as *u8) | fsx_seq(argv[PB_ARG_VERB] as *u8,"stage-sized" as *u8)
324 if (fsx_seq(argv[PB_ARG_VERB] as *u8,"publish-guarded" as *u8) == 1) || (staged == 1) {
325 let incoming: *u8 = sys_mmap(PB_SHA_HEX+1)
326 pb_hash(body,n,incoming)
327 if fsx_seq(incoming,argv[5] as *u8) == 0 {
328 fsx_puts("NX-PUBLISH CONFLICT artifact-sha256-mismatch\n" as *u8)
329 return PB_RC_CONFLICT
330 }
331 // A retry with the same result succeeds after its predecessor has changed.
332 if (staged == 0) && (pb_same_bytes(body,n,old,on) == 0) {
333 let current: *u8 = sys_mmap(PB_SHA_HEX+1)
334 if on < 0 {
335 let stat: *u8 = sys_mmap(160)
336 if sys_fstatat(dst,stat) != (0-2) { return FSX_RC_IO }
337 if fsx_seq(argv[6] as *u8,"absent" as *u8) == 0 { return PB_RC_CONFLICT }
338 } else {
339 pb_hash(old,on,current)
340 if fsx_seq(current,argv[6] as *u8) == 0 {
341 fsx_puts("NX-PUBLISH CONFLICT live-sha256-mismatch\n" as *u8)
342 return PB_RC_CONFLICT
343 }
344 }
345 }
346 }
347 if (pb_release_path(rel) == 1) && (pb_same_bytes(body,n,old,on) == 0) {
348 // A failed read is not absence. Only kernel ENOENT permits a new release path.
349 let stat: *u8 = sys_mmap(160)
350 let observed: i64 = sys_fstatat(dst,stat)
351 if observed != (0-2) {
352 if observed == 0 {
353 fsx_puts("NX-PUBLISH CONFLICT immutable-release-path; retain tested bytes and stage a new candidate path\n" as *u8)
354 return PB_RC_CONFLICT
355 }
356 fsx_puts("NX-PUBLISH IO release-target-stat errno=" as *u8);fsx_putn(observed)
357 fsx_puts(" target=" as *u8);fsx_puts(dst);fsx_puts("; resolve target access before retry\n" as *u8)
358 return FSX_RC_IO
359 }
360 }
361 if pb_same_bytes(body, n, old, on) == 1 {
362 fsx_puts("NX-PUBLISH UNCHANGED bytes=" as *u8); fsx_putn(n)
363 fsx_puts(" -> " as *u8); fsx_puts(dst)
364 fsx_puts(" prior_version=retained writes=0\n" as *u8)
365 return 0
366 }
367 if on > 0 {
368 let prev: *u8 = sys_mmap(PB_PATH)
369 let pp: *i64 = sys_mmap(8) as *i64
370 pp[0] = 0
371 pb_app(prev, pp, dst)
372 pb_app(prev, pp, ".prev" as *u8)
373 let pw: i64 = fsx_write(prev, old, on)
374 if pw < 0 { fsx_puts("NX-PUBLISH IO: could not preserve .prev; refusing to overwrite\n" as *u8); return FSX_RC_IO }
375 }
376 // atomic placement through the IO layer (write-deny classes included by construction)
377 let w: i64 = fsx_write(dst, body, n)
378 if w < 0 {
379 // ⚠THE SILENT EXIT, FIXED (debt seq910). Every other failure path in sp_main prints
380 // a diagnostic; this one returned an error code and NOT ONE WORD. Measured 2026-07-25:
381 // `publish <src> nishifamily research/rt004.html` produced EMPTY output and published
382 // nothing, which reads exactly like success to a caller that only sees stdout.
383 // ROOT CAUSE (read, not guessed): fsx_write does NOT create parent directories --
384 // sys_openat_wr fails and it returns -3 -- so any relpath containing a subdirectory
385 // fails here when sites/<site>/<subdir>/ is absent. Auto-creating docroot directories
386 // is a deliberate NON-goal (deny-by-construction), so the fix is to FAIL LOUD and name
387 // the cause rather than to start creating directories.
388 if w == 0 - (2 as i64) {
389 fsx_puts("NX-PUBLISH DENIED: the IO layer refused this destination -> " as *u8)
390 fsx_puts(dst); fsx_puts("\n" as *u8)
391 return FSX_RC_DENIED
392 }
393 fsx_puts("NX-PUBLISH IO-FAIL: NOTHING WAS PUBLISHED -> " as *u8); fsx_puts(dst); fsx_puts("\n" as *u8)
394 fsx_puts(" likely cause: the parent directory does not exist (this organ never creates docroot dirs).\n" as *u8)
395 fsx_puts(" if <relpath> contains a subdirectory, create it first or publish to a flat relpath.\n" as *u8)
396 return FSX_RC_IO
397 }
398 fsx_puts("NX-PUBLISH OK bytes=" as *u8); fsx_putn(w)
399 fsx_puts(" -> " as *u8); fsx_puts(dst)
400 fsx_puts("\nVERIFY (browser-grade, WAN vantage): nx_page_verify https://<site-domain>/" as *u8); fsx_puts(rel)
401 fsx_puts(" (site->domain map: surfaces.reg)\n" as *u8)
402 return 0
403}
404
405const PE_ARGC: i64=10
406// This lock coordinates only publishers honoring the immutable registered release contract.
407func pe_main(argc: i64,argv: *i64) -> i64 {
408 if argc!=PE_ARGC{return PB_RC_USAGE}
409 // Refuse a missing/nonwritable caller output before open can reuse descriptor1 for a lock.
410 if sys_write(1,"",0)!=0{return FSX_RC_IO}
411 let site: *u8=argv[3] as *u8;let rel: *u8=argv[8] as *u8
412 if pb_traversal(site)==1||pb_traversal(rel)==1||pr_path_ok(rel)==0||pr_hash_ok(argv[9] as *u8)==0{return FSX_RC_DENIED}
413 var i: i64=0;while site[i]!=0{if site[i]==47{return FSX_RC_DENIED};i=i+1}
414 if fsx_seq(rel,argv[4] as *u8)==1{return FSX_RC_DENIED}
415 if vw_slen(site)+vw_slen(rel)+vw_slen("sites//.publish-lock")+1>=PB_PATH{return FSX_RC_DENIED}
416 let lock: *u8=sys_mmap_shared(PB_PATH);let pos: *i64=sys_mmap_shared(8) as *i64
417 let normalized: *i64=sys_mmap_shared((PB_GUARDED_SIZED_ARGC+1)*8) as *i64
418 var rc: i64=FSX_RC_IO
419 if (lock as i64)>0&&(pos as i64)>0&&(normalized as i64)>0 {
420 pos[0]=0;pb_app(lock,pos,"sites/");pb_app(lock,pos,site);pb_app(lock,pos,"/");pb_app(lock,pos,rel)
421 if fsx_write_denied(lock)==0 {
422 pb_app(lock,pos,".publish-lock");let fd: i64=sys_openat_wr(lock,FSX_MODE_RW)
423 if fd>=0 {
424 if sys_flock(fd,PB_LOCK_EX_NB)==0 {
425 i=0;while i<PB_GUARDED_SIZED_ARGC{normalized[i]=argv[i];i=i+1};normalized[i]=0;normalized[1]="publish-guarded" as *u8 as i64
426 rc=pb_main_edge(PB_GUARDED_SIZED_ARGC,normalized,rel,argv[9] as *u8)
427 } else {rc=PB_RC_BUSY;fsx_puts("NX-PUBLISH BUSY selected-dependency-owned\n")}
428 if sys_close(fd)!=0{rc=FSX_RC_IO;fsx_puts("NX-PUBLISH IO dependency-lock-close; inspect placement outcome\n")}
429 }
430 } else {rc=FSX_RC_DENIED}
431 }
432 if (normalized as i64)>0{if sys_munmap(normalized as *u8,(PB_GUARDED_SIZED_ARGC+1)*8)!=0{rc=FSX_RC_IO}}
433 if (pos as i64)>0{if sys_munmap(pos as *u8,8)!=0{rc=FSX_RC_IO}}
434 if (lock as i64)>0{if sys_munmap(lock,PB_PATH)!=0{rc=FSX_RC_IO}}
435 return rc
436}
437func sp_main(argc: i64,argv: *i64) -> i64 {
438 if argc>1{if fsx_seq(argv[1] as *u8,"prepare-path")==1{
439 if argc!=4{return PB_RC_USAGE}
440 let rc:i64=pb_prepare_path(argv[2] as *u8,argv[3] as *u8)
441 fsx_puts("NX-PUBLISH PREPARE rc=");fsx_putn(rc);fsx_puts(" registered-path-only=1 published=0\n")
442 return rc
443 }}
444 if argc>1{if fsx_seq(argv[1] as *u8,"release-manifest")==1{return rm_main(argc,argv)}}
445 if argc>1{if fsx_seq(argv[1] as *u8,"publish-guarded-edge")==1{return pe_main(argc,argv)}}
446 return pb_main_edge(argc,argv,0 as *u8,0 as *u8)
447}
448func pe_check(body: *u8,n: i64,argv: *i64,rel: *u8,sha: *u8) -> i64 {
449 let conf: *u8=sys_mmap_shared(PB_CONFCAP);let spans: *i64=sys_mmap_shared(PB_SPANCAP) as *i64
450 let prefix: *u8=sys_mmap_shared(PB_PATH);let path: *u8=sys_mmap_shared(PB_PATH);let token: *u8=sys_mmap_shared(PB_PATH)
451 let av: *i64=sys_mmap_shared(8*8) as *i64;let pos: *i64=sys_mmap_shared(8) as *i64
452 var rc: i64=FSX_RC_IO
453 if (conf as i64)>0&&(spans as i64)>0&&(prefix as i64)>0&&(path as i64)>0&&(token as i64)>0&&(av as i64)>0&&(pos as i64)>0 {
454 if pl_site_lookup("knowledge/pub_sites.conf",argv[3] as *u8,conf,spans)==1 {
455 if spans[3]-spans[2]<PB_PATH {
456 pl_span_cstr(conf,spans[2],spans[3],prefix)
457 let registered: i64=pr_check(prefix,rel,sha)
458 if registered==PR_OK {
459 pos[0]=0;pb_app(path,pos,"sites/");pb_app(path,pos,argv[3] as *u8);pb_app(path,pos,"/");pb_app(path,pos,rel)
460 pos[0]=0;pb_app(token,pos,"/");pb_app(token,pos,rel)
461 av[0]="nx_comparestale" as *u8 as i64;av[1]="dependency-check" as *u8 as i64;av[2]=argv[2];av[3]=argv[5];av[4]=path as i64;av[5]=sha as i64;av[6]=token as i64;av[7]=0
462 let observed: i64=cd_buffer_command(7,av,body,n)
463 if observed==0{rc=0}else{if observed==1{rc=PB_RC_CONFLICT}else{rc=FSX_RC_IO}}
464 } else {if registered==PR_IO{rc=FSX_RC_IO}else{rc=PB_RC_CONFLICT};fsx_puts("NX-PUBLISH REFUSED selected-dependency-registration\n")}
465 }
466 } else {rc=FSX_RC_DENIED;fsx_puts("NX-PUBLISH DENIED selected-edge requires registered site\n")}
467 }
468 if (pos as i64)>0{if sys_munmap(pos as *u8,8)!=0{rc=FSX_RC_IO}}
469 if (av as i64)>0{if sys_munmap(av as *u8,64)!=0{rc=FSX_RC_IO}}
470 if (token as i64)>0{if sys_munmap(token,PB_PATH)!=0{rc=FSX_RC_IO}}
471 if (path as i64)>0{if sys_munmap(path,PB_PATH)!=0{rc=FSX_RC_IO}}
472 if (prefix as i64)>0{if sys_munmap(prefix,PB_PATH)!=0{rc=FSX_RC_IO}}
473 if (spans as i64)>0{if sys_munmap(spans as *u8,PB_SPANCAP)!=0{rc=FSX_RC_IO}}
474 if (conf as i64)>0{if sys_munmap(conf,PB_CONFCAP)!=0{rc=FSX_RC_IO}}
475 return rc
476}
477
478// Compatibility entry: callers retain the original two-argument API.
479func pb_publish_locked(argc: i64,argv: *i64) -> i64 {return pb_publish_locked_edge(argc,argv,0 as *u8,0 as *u8)}
480
481// One selected immutable dependency, matching the existing guarded-edge contract.
482// Registration is opt-in on this verb only; legacy publication authority is unchanged.
483const RM_FIELDS: i64=12
484const RM_VALUES_BYTES: i64=RM_FIELDS*8
485const RM_SCHEMA: *u8="site-release/v1"
486const RM_RECEIPT_ROOT: *u8="knowledge/gates/"
487func rm_key(k: *u8) -> i64 {
488 if fsx_seq(k,"schema")==1{return 0};if fsx_seq(k,"site")==1{return 1}
489 if fsx_seq(k,"consumer_source")==1{return 2};if fsx_seq(k,"consumer_path")==1{return 3}
490 if fsx_seq(k,"consumer_sha256")==1{return 4};if fsx_seq(k,"consumer_bytes")==1{return 5}
491 if fsx_seq(k,"expected_live")==1{return 6};if fsx_seq(k,"dependency_source")==1{return 7}
492 if fsx_seq(k,"dependency_path")==1{return 8};if fsx_seq(k,"dependency_sha256")==1{return 9}
493 if fsx_seq(k,"dependency_bytes")==1{return 10};if fsx_seq(k,"registration")==1{return 11}
494 return 0-1
495}
496// This path-oriented schema deliberately refuses escapes rather than interpreting them differently.
497func rm_string(t: *NxJsonTok) -> *u8 {
498 if nx_json_next(t)!=NX_JSON_STRING{return 0 as *u8}
499 let n: i64=t.tok_len-2
500 if n<=0||n>=PB_PATH{return 0 as *u8}
501 let v: *u8=sys_mmap(n+1);if (v as i64)<=0{return 0 as *u8}
502 var i: i64=0
503 while i<n{let c: i64=t.src[t.tok_off+1+i] as i64;if c==92||c<32{sys_munmap(v,n+1);return 0 as *u8};v[i]=c as u8;i=i+1}
504 v[n]=0;return v
505}
506func rm_parse_tokens(t: *NxJsonTok,v: *i64) -> i64 {
507 if nx_json_next(t)!=NX_JSON_LBRACE{return PB_RC_USAGE}
508 var count: i64=0
509 while count<RM_FIELDS {
510 let k: *u8=rm_string(t);if (k as i64)<=0{return PB_RC_USAGE}
511 let idx: i64=rm_key(k);sys_munmap(k,vw_slen(k)+1)
512 if idx<0{return PB_RC_USAGE};if v[idx]!=0{return PB_RC_USAGE}
513 if nx_json_next(t)!=NX_JSON_COLON{return PB_RC_USAGE}
514 let value: *u8=rm_string(t);if (value as i64)<=0{return PB_RC_USAGE}
515 v[idx]=value as i64;count=count+1
516 let separator: i64=nx_json_next(t)
517 if separator==NX_JSON_RBRACE{if count!=RM_FIELDS{return PB_RC_USAGE};if nx_json_next(t)!=NX_JSON_EOF{return PB_RC_USAGE};return 0}
518 if separator!=NX_JSON_COMMA{return PB_RC_USAGE}
519 }
520 return PB_RC_USAGE
521}
522func rm_parse(body: *u8,n: i64,v: *i64) -> i64 {
523 let t: *NxJsonTok=nx_json_new(body,n);if (t as i64)<=0{return FSX_RC_IO}
524 let rc: i64=rm_parse_tokens(t,v);sys_munmap(t as *u8,NX_JSONTOK_BYTES);return rc
525}
526
527func rm_num(out: *u8,pos: *i64,value: i64) -> i64 {
528 // Internal epoch/PID/exit values are bounded; digits use the caller-owned receipt buffer.
529 var n: i64=value
530 if n<0{pb_app(out,pos,"-");n=0-n}
531 let start: i64=pos[0]
532 if n==0{pb_app(out,pos,"0")}else{
533 while n>0{out[pos[0]]=(48+n%10) as u8;pos[0]=pos[0]+1;n=n/10}
534 var left: i64=start;var right: i64=pos[0]-1
535 while left<right{let ch: u8=out[left];out[left]=out[right];out[right]=ch;left=left+1;right=right-1}
536 out[pos[0]]=0
537 }
538 return 0
539}
540
541func rm_receipt(path: *u8,digest: *u8,phase: *u8,state: *u8,rc: i64) -> i64 {
542 let b: *u8=sys_mmap(PB_PATH);let p: *i64=sys_mmap(8) as *i64
543 if (b as i64)<=0||(p as i64)<=0{return FSX_RC_IO}
544 p[0]=0;pb_app(b,p,"{\"schema\":\"site-release-phase/v1\",\"manifest_sha256\":\"");pb_app(b,p,digest)
545 pb_app(b,p,"\",\"epoch\":");rm_num(b,p,sys_now_realtime_sec())
546 pb_app(b,p,",\"attempt_pid\":");rm_num(b,p,osp_selfpid())
547 pb_app(b,p,",\"phase\":\"");pb_app(b,p,phase);pb_app(b,p,"\",\"state\":\"");pb_app(b,p,state)
548 pb_app(b,p,"\",\"rc\":");rm_num(b,p,rc);pb_app(b,p,"}\n")
549 fsx_puts(b)
550 let written: i64=fsx_append(path,b,p[0]);var result: i64=FSX_RC_IO
551 if written>=p[0]{result=rm_sync(path)}
552 sys_munmap(b,PB_PATH);sys_munmap(p as *u8,8)
553 if result!=0{fsx_puts("NX-RELEASE receipt-io; inspect last recorded phase and actual artifact identity; no further phase executed\n")}
554 return result
555}
556func rm_finish(receipt: *u8,digest: *u8,phase: *u8,rc: i64) -> i64 {
557 var state: *u8="complete"
558 if rc!=0{state="refused";if rc==PB_RC_BUSY{state="waiting-owner-retry-required"}}
559 if rm_receipt(receipt,digest,phase,state,rc)!=0{return FSX_RC_IO}
560 return rc
561}
562func rm_artifact(src: *u8,size: *u8,hash: *u8,out: *i64) -> i64 {
563 let n: i64=pb_sized_bytes(size);if n<0||pr_hash_ok(hash)!=1{return PB_RC_USAGE}
564 let b: *u8=sys_mmap(n+1);if (b as i64)<=0{return FSX_RC_IO}
565 let got: i64=pb_read_bounded(src,b,n+1)
566 if got!=n{sys_munmap(b,n+1);return PB_RC_CONFLICT}
567 let h: *u8=sys_mmap(PB_SHA_HEX+1);if (h as i64)<=0{sys_munmap(b,n+1);return FSX_RC_IO}
568 pb_hash(b,n,h);let same: i64=fsx_seq(h,hash);sys_munmap(h,PB_SHA_HEX+1)
569 if same!=1{sys_munmap(b,n+1);return PB_RC_CONFLICT}
570 out[0]=b as i64;out[1]=n;return 0
571}
572// CLI/job scope: early preflight refusals and incumbent publisher allocations are reclaimed at process exit.
573// This entry is not a memory-stable long-lived daemon API.
574func rm_preflight(v: *i64,prefix: *u8) -> i64 {
575 if fsx_seq(v[0] as *u8,RM_SCHEMA)!=1{return PB_RC_USAGE}
576 if fsx_seq(v[11] as *u8,"allow")!=1&&fsx_seq(v[11] as *u8,"existing-only")!=1{return PB_RC_USAGE}
577 if pr_hash_ok(v[6] as *u8)!=1&&fsx_seq(v[6] as *u8,"absent")!=1{return PB_RC_USAGE}
578 let site: *u8=v[1] as *u8;let target: *u8=v[3] as *u8;let dep: *u8=v[8] as *u8
579 if pb_traversal(site)!=0||pb_traversal(target)!=0||pr_path_ok(dep)!=1||pb_release_path(target)==1{return FSX_RC_DENIED}
580 var i: i64=0;while site[i]!=0{if site[i]==47{return FSX_RC_DENIED};i=i+1}
581 if vw_slen(site)+vw_slen(target)+vw_slen("sites//.publish-lock")+1>=PB_PATH{return FSX_RC_DENIED}
582 if vw_slen(site)+vw_slen(dep)+vw_slen("sites//.publish-lock")+1>=PB_PATH{return FSX_RC_DENIED}
583 let conf: *u8=sys_mmap(PB_CONFCAP);let spans: *i64=sys_mmap(PB_SPANCAP) as *i64
584 if (conf as i64)<=0||(spans as i64)<=0{return FSX_RC_IO}
585 if pl_site_lookup("knowledge/pub_sites.conf",site,conf,spans)!=1{return FSX_RC_DENIED}
586 if spans[3]-spans[2]>=PB_PATH{return FSX_RC_DENIED}
587 pl_span_cstr(conf,spans[2],spans[3],prefix)
588 if pl_check(prefix,target)!=0{return FSX_RC_DENIED}
589 sys_munmap(conf,PB_CONFCAP);sys_munmap(spans as *u8,PB_SPANCAP)
590 let artifact: *i64=sys_mmap(16) as *i64;let asset: *i64=sys_mmap(16) as *i64
591 if (artifact as i64)<=0||(asset as i64)<=0{return FSX_RC_IO}
592 let cr: i64=rm_artifact(v[2] as *u8,v[5] as *u8,v[4] as *u8,artifact);if cr!=0{return cr}
593 let dr: i64=rm_artifact(v[7] as *u8,v[10] as *u8,v[9] as *u8,asset);if dr!=0{return dr}
594 let args: *i64=sys_mmap(8*8) as *i64;let token: *u8=sys_mmap(PB_PATH);let pos: *i64=sys_mmap(8) as *i64
595 if (args as i64)<=0||(token as i64)<=0||(pos as i64)<=0{return FSX_RC_IO}
596 pos[0]=0;pb_app(token,pos,"/");pb_app(token,pos,dep)
597 args[0]="nx_comparestale" as *u8 as i64;args[1]="dependency-check" as *u8 as i64;args[2]=v[2];args[3]=v[4]
598 args[4]=v[7];args[5]=v[9];args[6]=token as i64;args[7]=0
599 let checked: i64=cd_buffer_command(7,args,artifact[0] as *u8,artifact[1])
600 sys_munmap(artifact[0] as *u8,artifact[1]+1);sys_munmap(asset[0] as *u8,asset[1]+1)
601 sys_munmap(args as *u8,64);sys_munmap(token,PB_PATH);sys_munmap(pos as *u8,8)
602 sys_munmap(artifact as *u8,16);sys_munmap(asset as *u8,16)
603 if checked!=0{return PB_RC_CONFLICT};return 0
604}
605func rm_execute(v: *i64,prefix: *u8,receipt: *u8,digest: *u8) -> i64 {
606 if rm_receipt(receipt,digest,"preflight","started",0)!=0{return FSX_RC_IO}
607 var rc: i64=rm_preflight(v,prefix);rc=rm_finish(receipt,digest,"preflight",rc);if rc!=0{return rc}
608 if rm_receipt(receipt,digest,"registration","started",0)!=0{return FSX_RC_IO}
609 if fsx_seq(v[11] as *u8,"allow")==1{rc=pr_register(prefix,v[8] as *u8,v[9] as *u8);if rc==PR_CREATED{rc=0}}else{rc=pr_check(prefix,v[8] as *u8,v[9] as *u8)}
610 rc=rm_finish(receipt,digest,"registration",rc);if rc!=0{return rc}
611 let args: *i64=sys_mmap((PE_ARGC+1)*8) as *i64;if (args as i64)<=0{return FSX_RC_IO}
612 args[0]="nx_site_publish" as *u8 as i64;args[1]="stage-sized" as *u8 as i64;args[2]=v[7];args[3]=v[1]
613 args[4]=v[8];args[5]=v[9];args[6]=v[10];args[7]=0
614 if rm_receipt(receipt,digest,"stage","started",0)!=0{return FSX_RC_IO}
615 rc=pb_main_edge(PB_SIZED_ARGC,args,0 as *u8,0 as *u8);rc=rm_finish(receipt,digest,"stage",rc);if rc!=0{return rc}
616 // The staging lock has been released. pe_main owns dependency then consumer locks itself.
617 args[1]="publish-guarded-edge" as *u8 as i64;args[2]=v[2];args[3]=v[1];args[4]=v[3];args[5]=v[4]
618 args[6]=v[6];args[7]=v[5];args[8]=v[8];args[9]=v[9];args[10]=0
619 if rm_receipt(receipt,digest,"publish","started",0)!=0{return FSX_RC_IO}
620 rc=pe_main(PE_ARGC,args);rc=rm_finish(receipt,digest,"publish",rc);if rc!=0{return rc}
621 sys_munmap(args as *u8,(PE_ARGC+1)*8)
622 return rm_receipt(receipt,digest,"delivery","published-served-review-pending",0)
623}
624func rm_main(argc: i64,argv: *i64) -> i64 {
625 if argc!=4{return PB_RC_USAGE};if pr_hash_ok(argv[3] as *u8)!=1{return PB_RC_USAGE}
626 let body: *u8=sys_mmap(PB_CONFCAP+1);let digest: *u8=sys_mmap(PB_SHA_HEX+1)
627 let v: *i64=sys_mmap(RM_VALUES_BYTES) as *i64;let receipt: *u8=sys_mmap(PB_PATH)
628 let pos: *i64=sys_mmap(8) as *i64;let prefix: *u8=sys_mmap(PB_PATH)
629 if (body as i64)<=0||(digest as i64)<=0||(v as i64)<=0||(receipt as i64)<=0||(pos as i64)<=0||(prefix as i64)<=0{rm_cleanup(body,digest,v,receipt,pos,prefix);return FSX_RC_IO}
630 let n: i64=pb_read_bounded(argv[2] as *u8,body,PB_CONFCAP+1)
631 if n<=0||n>PB_CONFCAP{rm_cleanup(body,digest,v,receipt,pos,prefix);return PB_RC_USAGE}
632 pb_hash(body,n,digest);if fsx_seq(digest,argv[3] as *u8)!=1{rm_cleanup(body,digest,v,receipt,pos,prefix);return PB_RC_CONFLICT}
633 pos[0]=0;pb_app(receipt,pos,RM_RECEIPT_ROOT);pb_app(receipt,pos,"site-release-");pb_app(receipt,pos,digest);pb_app(receipt,pos,".jsonl")
634 var rc: i64=rm_parse(body,n,v)
635 if rc!=0{rc=rm_finish(receipt,digest,"manifest",rc);rm_cleanup(body,digest,v,receipt,pos,prefix);return rc}
636 fsx_puts("NX-RELEASE receipt=");fsx_puts(receipt);fsx_puts(" registration=");fsx_puts(v[11] as *u8);fsx_puts(" scope=one-selected-dependency no-build-no-render\n")
637 rc=rm_bank(body,n,digest)
638 if rc!=0{rc=rm_finish(receipt,digest,"manifest-bank",rc);rm_cleanup(body,digest,v,receipt,pos,prefix);return rc}
639 rc=rm_execute(v,prefix,receipt,digest)
640 rm_cleanup(body,digest,v,receipt,pos,prefix)
641 return rc
642}
643
644func rm_sync(path: *u8) -> i64 {
645 let fd: i64=sys_openat_rd(path);if fd<0{return FSX_RC_IO}
646 let sync: i64=sys_fsync(fd);let closed: i64=sys_close(fd)
647 if sync!=0||closed!=0{return FSX_RC_IO}
648 let dir: i64=sys_openat_rd(RM_RECEIPT_ROOT);if dir<0{return FSX_RC_IO}
649 let ds: i64=sys_fsync(dir);let dc: i64=sys_close(dir)
650 if ds!=0||dc!=0{return FSX_RC_IO};return 0
651}
652// The caller's authorization and original precondition remain available after temporary input expires.
653func rm_bank(body: *u8,n: i64,digest: *u8) -> i64 {
654 let path: *u8=sys_mmap(PB_PATH);let lock: *u8=sys_mmap(PB_PATH);let p: *i64=sys_mmap(8) as *i64
655 if (path as i64)<=0||(lock as i64)<=0||(p as i64)<=0{return FSX_RC_IO}
656 p[0]=0;pb_app(path,p,RM_RECEIPT_ROOT);pb_app(path,p,"site-release-");pb_app(path,p,digest);pb_app(path,p,".manifest.json")
657 p[0]=0;pb_app(lock,p,path);pb_app(lock,p,".lock")
658 if fsx_write_denied(path)==1||fsx_write_denied(lock)==1{return FSX_RC_DENIED}
659 let fd: i64=sys_openat_wr(lock,FSX_MODE_RW);if fd<0{return FSX_RC_IO}
660 if sys_flock(fd,PB_LOCK_EX_NB)!=0{sys_close(fd);return PB_RC_BUSY}
661 let old: *u8=sys_mmap(n+1);var rc: i64=FSX_RC_IO
662 if (old as i64)>0 {
663 let got: i64=pb_read_bounded(path,old,n+1)
664 if got==(0-2){if fsx_write(path,body,n)==n{rc=rm_sync(path)}}else{if pb_same_bytes(body,n,old,got)==1{rc=rm_sync(path)}else{if got>=0{rc=PB_RC_CONFLICT}}}
665 sys_munmap(old,n+1)
666 }
667 if sys_close(fd)!=0{rc=FSX_RC_IO}
668 sys_munmap(path,PB_PATH);sys_munmap(lock,PB_PATH);sys_munmap(p as *u8,8)
669 return rc
670}
671func rm_cleanup(body: *u8,digest: *u8,v: *i64,receipt: *u8,pos: *i64,prefix: *u8) -> i64 {
672 if (v as i64)>0{var i: i64=0;while i<RM_FIELDS{if v[i]!=0{sys_munmap(v[i] as *u8,vw_slen(v[i] as *u8)+1)};i=i+1};sys_munmap(v as *u8,RM_VALUES_BYTES)}
673 if (body as i64)>0{sys_munmap(body,PB_CONFCAP+1)};if (digest as i64)>0{sys_munmap(digest,PB_SHA_HEX+1)}
674 if (receipt as i64)>0{sys_munmap(receipt,PB_PATH)};if (pos as i64)>0{sys_munmap(pos as *u8,8)};if (prefix as i64)>0{sys_munmap(prefix,PB_PATH)}
675 return 0
676}
677
678 // Prepare only registered paths. Each directory is opened relative to a retained
679 // parent descriptor without following symlinks; file publication remains separate.
680func pb_prepare_path(site:*u8,rel:*u8)->i64 {
681 if pb_traversal(site)!=0||pb_traversal(rel)!=0{return FSX_RC_DENIED}
682 var i:i64=0
683 while site[i]!=0 {if site[i]==47{return FSX_RC_DENIED};i=i+1}
684 if i==0||vw_slen(rel)==0||vw_slen(rel)>=PB_PATH{return PB_RC_USAGE}
685 let conf:*u8=sys_mmap(PB_CONFCAP);let spans:*i64=sys_mmap(PB_SPANCAP) as *i64
686 let prefix:*u8=sys_mmap(PB_PATH);let component:*u8=sys_mmap(PB_PATH)
687 if (conf as i64)<=0||(spans as i64)<=0||(prefix as i64)<=0||(component as i64)<=0{return FSX_RC_IO}
688 var rc:i64=FSX_RC_DENIED
689 if pl_site_lookup("knowledge/pub_sites.conf",site,conf,spans)==1 {
690 if spans[3]-spans[2]<PB_PATH{
691 pl_span_cstr(conf,spans[2],spans[3],prefix)
692 if pl_check(prefix,rel)==PL_OK{rc=0}
693 }
694 }
695 sys_munmap(conf,PB_CONFCAP);sys_munmap(spans as *u8,PB_SPANCAP);sys_munmap(prefix,PB_PATH)
696 if rc!=0{sys_munmap(component,PB_PATH);return rc}
697 // Validate every component before creating any directories.
698 i=0;var start:i64=0
699 while 1 {
700 if rel[i]==47||rel[i]==0{
701 let n:i64=i-start
702 if n==0||(n==1&&rel[start]==46)||(n==2&&rel[start]==46&&rel[start+1]==46){sys_munmap(component,PB_PATH);return FSX_RC_DENIED}
703 if rel[i]==0{break};start=i+1
704 };i=i+1
705 }
706 var parent:i64=__syscall(SYS_OPENAT,AT_FDCWD,"sites" as *u8,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0)
707 if parent<0{sys_munmap(component,PB_PATH);return FSX_RC_IO}
708 let sitefd:i64=__syscall(SYS_OPENAT,parent,site,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0)
709 sys_close(parent)
710 if sitefd<0{sys_munmap(component,PB_PATH);return FSX_RC_IO}
711 parent=sitefd;i=0;start=0
712 while rel[i]!=0{
713 if rel[i]==47{
714 var j:i64=0;while start+j<i{component[j]=rel[start+j];j=j+1};component[j]=0 as u8
715 var child:i64=__syscall(SYS_OPENAT,parent,component,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0)
716 if child==(0-2){
717 // Same Nishi mkdirat ABI as sys_mkdir; descriptor-relative variant.
718 let made:i64=__syscall(34,parent,component,0x1ed,0,0,0)
719 if made!=0&&made!=(0-17){rc=FSX_RC_IO;break}
720 if sys_fsync(parent)!=0{rc=FSX_RC_IO;break}
721 child=__syscall(SYS_OPENAT,parent,component,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0)
722 }
723 if child<0{rc=FSX_RC_IO;break}
724 sys_close(parent);parent=child;start=i+1
725 };i=i+1
726 }
727 if sys_close(parent)!=0{rc=FSX_RC_IO}
728 sys_munmap(component,PB_PATH)
729 return rc
730}
731