nx_site_publish_edge_lib_t139.nx source
↩ module page · 471 lines · 24714 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_edge_lib_t139.nx"
28
29const PB_ARG_VERB: i64 = 1 // argv: verb ("publish")
30const PB_RC_USAGE: i64 = 2 // exit: usage
31const PB_ARG_SRC: i64 = 2 // argv: source file (NAS-local)
32const PB_ARG_SITE: i64 = 3 // argv: site dir under sites/
33const PB_ARG_REL: i64 = 4 // argv: page path under the site
34const PB_ARGC: i64 = 5 // publish src site relpath
35const PB_STAGE_ARGC: i64 = 6 // stage src site releases/<path> sha256
36const PB_GUARDED_ARGC: i64 = PB_STAGE_ARGC + 1 // adds expected live digest
37const PB_GUARDED_SIZED_ARGC: i64 = PB_GUARDED_ARGC + 1 // optional exact artifact extent
38const PB_PATH: i64 = 1024 // destination path buffer
39const PB_CONFCAP: i64 = 65536 // site-table read buffer (the gateway)
40const PB_SPANCAP: i64 = 128 // site-table span vector
41const PB_DOT: i64 = 46 // '.'
42const PB_SLASH: i64 = 47 // '/'
43
44// any ".." path segment, or a leading '/', or empty -> traversal refusal (1 = refuse)
45func pb_traversal(s: *u8) -> i64 {
46 if s[0] == (0 as u8) { return 1 }
47 if s[0] == (PB_SLASH as u8) { return 1 }
48 var i: i64 = 0
49 while s[i] != (0 as u8) {
50 if s[i] == (PB_DOT as u8) { if s[i+1] == (PB_DOT as u8) { return 1 } }
51 // Reject aliases before deriving the target lock: ./ and // could name the same file
52 // using different lock paths or conceal the reserved release namespace.
53 if s[i] == (PB_SLASH as u8) {
54 if s[i+1] == (PB_SLASH as u8) { return 1 }
55 if s[i+1] == (0 as u8) { return 1 }
56 }
57 var segment_start: i64 = 0
58 if i == 0 { segment_start = 1 } else {
59 if s[i-1] == (PB_SLASH as u8) { segment_start = 1 }
60 }
61 if segment_start == 1 {
62 if s[i] == (PB_DOT as u8) {
63 if (s[i+1] == (PB_SLASH as u8)) || (s[i+1] == (0 as u8)) { return 1 }
64 }
65 }
66 i = i + 1
67 }
68 return 0
69}
70// append NUL-terminated src to out at *op
71func pb_app(out: *u8, op: *i64, s: *u8) -> i64 {
72 var i: i64 = 0
73 var o: i64 = op[0]
74 while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 }
75 out[o] = 0 as u8
76 op[0] = o
77 return 0
78}
79
80// Artifact equality is byte-counted: WASM and other binary assets contain NUL bytes.
81func pb_same_bytes(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
82 if an != bn { return 0 }
83 if an < 0 { return 0 }
84 var i: i64 = 0
85 while i < an { if a[i] != b[i] { return 0 }; i = i + 1 }
86 return 1
87}
88
89const PB_LOCK_EX_NB: i64 = 6
90const PB_RC_CONFLICT: i64 = 8
91const PB_RC_BUSY: i64 = 9
92const PB_SHA_BYTES: i64 = 32
93const PB_SHA_HEX: i64 = 64
94func pb_hash(body: *u8, n: i64, hex: *u8) -> i64 {
95 let raw: *u8 = sys_mmap(PB_SHA_BYTES)
96 sha256_digest(body,n,raw)
97 let digits: *u8 = "0123456789abcdef" as *u8
98 var i: i64 = 0
99 while i < PB_SHA_BYTES {
100 let c: i64 = raw[i] as i64
101 hex[i*2] = digits[c/16]; hex[i*2+1] = digits[c%16]
102 i = i+1
103 }
104 hex[PB_SHA_HEX] = 0 as u8
105 sys_munmap(raw,PB_SHA_BYTES)
106 return 0
107}
108
109const PB_SIZED_ARGC: i64 = 7
110const PB_I64_MAX: i64 = 9223372036854775807
111func pb_sized_bytes(s: *u8) -> i64 {
112 var value: i64=0
113 var i: i64=0
114 while s[i] != (0 as u8) {
115 let c: i64=s[i] as i64
116 if c < 48 || c > 57 { return 0-1 }
117 if value > (PB_I64_MAX/2-1-(c-48))/10 { return 0-1 }
118 value=value*10+c-48; i=i+1
119 }
120 if value <= 0 { return 0-1 }
121 return value
122}
123func pb_read_bounded(path: *u8,b: *u8,cap: i64) -> i64 {
124 let fd: i64=sys_openat_rd(path)
125 if fd < 0 { return fd }
126 var n: i64=0
127 var go: i64=1
128 while n < cap && go == 1 {
129 let got: i64=sys_read(fd,((b as i64)+n) as *u8,cap-n)
130 if got < 0 { sys_close(fd); return got }
131 if got == 0 { go=0 } else { n=n+got }
132 }
133 sys_close(fd)
134 return n
135}
136
137func pb_main_edge(argc: i64, argv: *i64, edge_rel: *u8, edge_sha: *u8) -> i64 {
138 if argc < PB_ARGC { return pb_publish_locked_edge(argc,argv,edge_rel,edge_sha) }
139 let verb: *u8 = argv[PB_ARG_VERB] as *u8
140 if fsx_seq(verb,"publish" as *u8) == 0 {
141 if fsx_seq(verb,"stage" as *u8) == 1 {
142 if argc != PB_STAGE_ARGC { return PB_RC_USAGE }
143 } else {
144 if fsx_seq(verb,"publish-guarded" as *u8) == 0 { if fsx_seq(verb,"stage-sized" as *u8) == 0 { return PB_RC_USAGE } }
145 if argc != PB_GUARDED_ARGC {
146 if fsx_seq(verb,"publish-guarded" as *u8) == 0 || argc != PB_GUARDED_SIZED_ARGC { return PB_RC_USAGE }
147 }
148 }
149 }
150 let site: *u8 = argv[PB_ARG_SITE] as *u8
151 let rel: *u8 = argv[PB_ARG_REL] as *u8
152 var si: i64 = 0
153 while site[si] != (0 as u8) {
154 if site[si] == (PB_SLASH as u8) { return FSX_RC_DENIED }
155 si = si + 1
156 }
157 if fsx_seq(verb,"stage" as *u8) == 1 || fsx_seq(verb,"stage-sized" as *u8) == 1 {
158 if pb_release_path(rel) == 0 {
159 fsx_puts("NX-PUBLISH DENIED stage requires releases/<candidate-path>; live URL is not a staging target\n" as *u8)
160 return FSX_RC_DENIED
161 }
162 }
163 if pb_traversal(site) == 1 { return FSX_RC_DENIED }
164 if pb_traversal(rel) == 1 { return FSX_RC_DENIED }
165 if vw_slen(site)+vw_slen(rel)+vw_slen("sites//.publish-lock" as *u8)+1 >= PB_PATH { return FSX_RC_DENIED }
166 let path: *u8 = sys_mmap(PB_PATH)
167 let pos: *i64 = sys_mmap(8) as *i64
168 pos[0] = 0
169 pb_app(path,pos,"sites/" as *u8);pb_app(path,pos,site)
170 pb_app(path,pos,"/" as *u8);pb_app(path,pos,rel)
171 if fsx_write_denied(path) == 1 { return FSX_RC_DENIED }
172 // Keep one stable inode per target; unlinking a lock lets contenders lock different inodes.
173 pb_app(path,pos,".publish-lock" as *u8)
174 let fd: i64 = sys_openat_wr(path,FSX_MODE_RW)
175 if fd < 0 { fsx_puts("NX-PUBLISH IO lock-parent-unavailable\n" as *u8);return FSX_RC_IO }
176 if sys_flock(fd,PB_LOCK_EX_NB) != 0 {
177 sys_close(fd)
178 fsx_puts("NX-PUBLISH BUSY target-owned retry-after-owner-completes\n" as *u8)
179 return PB_RC_BUSY
180 }
181 let rc: i64 = pb_publish_locked_edge(argc,argv,edge_rel,edge_sha)
182 sys_close(fd)
183 return rc
184}
185// Every publisher verb enforces this namespace, so a legacy publish cannot replace a tested candidate.
186// The site registry still authorizes each release path; staging does not bypass publication policy.
187func pb_release_path(rel: *u8) -> i64 {
188 let prefix: *u8 = "releases/" as *u8
189 var i: i64 = 0
190 while prefix[i] != (0 as u8) {
191 if rel[i] != prefix[i] { return 0 }
192 i = i + 1
193 }
194 return (rel[i] != (0 as u8)) as i64
195}
196func pb_publish_locked_edge(argc: i64, argv: *i64, edge_rel: *u8, edge_sha: *u8) -> i64 {
197 var okverb: i64 = 0
198 if argc >= PB_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8, "publish" as *u8) == 1 { okverb = 1 } }
199 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 } }
200 if argc == PB_STAGE_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage" as *u8) == 1 { okverb = 1 } }
201 if argc == PB_SIZED_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage-sized" as *u8) == 1 { okverb = 1 } }
202 if okverb == 0 {
203 fsx_puts("usage: nx_site_publish publish <src-file> <site> <relpath> (-> sites/<site>/<relpath>; then verify: nx_page_verify <url>)\n" as *u8)
204 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)
205 return PB_RC_USAGE
206 }
207 let site: *u8 = argv[PB_ARG_SITE] as *u8
208 let rel: *u8 = argv[PB_ARG_REL] as *u8
209 if pb_traversal(site) == 1 {
210 fsx_puts("NX-PUBLISH DENIED: site escapes the docroot ('..'/absolute/empty)\n" as *u8)
211 return FSX_RC_DENIED
212 }
213 if pb_traversal(rel) == 1 {
214 fsx_puts("NX-PUBLISH DENIED: relpath escapes the docroot ('..'/absolute/empty)\n" as *u8)
215 return FSX_RC_DENIED
216 }
217 // ---- THE PUBLISH GATEWAY: the registry decides what may exist at this URL, not the caller ----
218 let gw_conf: *u8 = sys_mmap(PB_CONFCAP)
219 let gw_spans: *i64 = sys_mmap(PB_SPANCAP) as *i64
220 var release_prefix: *u8 = 0 as *u8
221 var registry_checked: i64 = 0
222 if pl_site_lookup("knowledge/pub_sites.conf" as *u8, site, gw_conf, gw_spans) == 1 {
223 registry_checked = 1
224 let gw_pfx: *u8 = sys_mmap(PB_PATH)
225 pl_span_cstr(gw_conf, gw_spans[2], gw_spans[3], gw_pfx)
226 var row_status: i64 = 0
227 if pb_release_path(rel) == 1 { release_prefix = gw_pfx } else { row_status = pl_check(gw_pfx,rel) }
228 if row_status != 0 {
229 fsx_puts("NX-PUBLISH REFUSED: " as *u8); fsx_puts(rel)
230 fsx_puts(" is not a publishable row in this site's registry.\n" as *u8)
231 fsx_puts(" The registry -- not the caller -- decides what may exist at a URL. Either the path is\n" as *u8)
232 fsx_puts(" unregistered, or its status is withdrawn/redirect/debris.\n" as *u8)
233 fsx_puts(" Register it (id/title/owner/status/section/path/note, status=live|asset|draft) in " as *u8)
234 fsx_puts(gw_pfx); fsx_puts(" ,\n" as *u8)
235 fsx_puts(" or run: nx_pub_plane adopt " as *u8); fsx_puts(site)
236 fsx_puts(" to bring existing docroot entries in. Then re-run this publish.\n" as *u8)
237 return FSX_RC_DENIED
238 }
239 } else {
240 fsx_puts("NX-PUBLISH WARN: site '" as *u8); fsx_puts(site)
241 fsx_puts("' is not in knowledge/pub_sites.conf -- publishing UNGATED (add a row to gate it).\n" as *u8)
242 }
243 // stage-sized declares its exact artifact size; the read budget is that size plus one
244 // to distinguish complete bytes from truncation. Legacy verbs retain their existing cap.
245 var readcap: i64 = FSX_READ_CAP
246 var wanted: i64 = 0
247 var size_arg: i64 = 0
248 if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage-sized" as *u8) == 1 {
249 size_arg=6
250 if (release_prefix as i64) == 0 { return FSX_RC_DENIED }
251 }
252 if argc == PB_GUARDED_SIZED_ARGC && fsx_seq(argv[PB_ARG_VERB] as *u8,"publish-guarded" as *u8) == 1 {
253 size_arg=7
254 if registry_checked != 1 { return FSX_RC_DENIED }
255 }
256 if size_arg > 0 {
257 wanted=pb_sized_bytes(argv[size_arg] as *u8)
258 if wanted < 0 { return PB_RC_USAGE }
259 readcap=wanted+1
260 }
261 let body: *u8 = sys_mmap(readcap + 1)
262 let n: i64 = pb_read_bounded(argv[PB_ARG_SRC] as *u8, body, readcap)
263 if wanted > 0 { if n != wanted { fsx_puts("NX-PUBLISH REFUSED source-size-mismatch\n" as *u8); return PB_RC_CONFLICT } }
264 if n <= 0 {
265 fsx_puts("NX-PUBLISH ABSENT: cannot read source " as *u8); fsx_puts(argv[PB_ARG_SRC] as *u8); fsx_puts("\n" as *u8)
266 return FSX_RC_ABSENT
267 }
268 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 }
269 // The owned body is checked once here and later passed unchanged to fsx_write.
270 if (edge_rel as i64)>0 {
271 let edge_rc: i64=pe_check(body,n,argv,edge_rel,edge_sha)
272 if edge_rc!=0{return edge_rc}
273 }
274 // Every verb checks the registered content, including legacy publish without a caller hash.
275 if (release_prefix as i64) != 0 {
276 let release_hash: *u8 = sys_mmap(PB_SHA_HEX+1)
277 pb_hash(body,n,release_hash)
278 let registered: i64 = pr_check(release_prefix,rel,release_hash)
279 sys_munmap(release_hash,PB_SHA_HEX+1)
280 if registered != PR_OK {
281 fsx_puts("NX-PUBLISH REFUSED release-registration code=" as *u8); fsx_putn(registered)
282 fsx_puts(" path=" as *u8); fsx_puts(rel)
283 fsx_puts("; verify catalogue integrity and registered digest before retrying\n" as *u8)
284 if registered == PR_IO { return FSX_RC_IO }
285 return PB_RC_CONFLICT
286 }
287 }
288 // build dst = sites/<site>/<relpath>
289 let dst: *u8 = sys_mmap(PB_PATH)
290 let op: *i64 = sys_mmap(8) as *i64
291 op[0] = 0
292 pb_app(dst, op, "sites/" as *u8)
293 pb_app(dst, op, site)
294 pb_app(dst, op, "/" as *u8)
295 pb_app(dst, op, rel)
296 // preserve the prior version (one-deep; rename-not-delete doctrine)
297 // The predecessor has its own extent: a smaller incoming artifact must still retain it whole.
298 var priorcap: i64 = readcap
299 if wanted > 0 {
300 let priorfd: i64 = sys_openat_rd(dst)
301 if priorfd >= 0 {
302 let extent: i64 = sys_lseek(priorfd,0,FSX_SEEK_END)
303 let closed: i64 = sys_close(priorfd)
304 if extent < 0 || extent > PB_I64_MAX/2-1 || closed != 0 {
305 fsx_puts("NX-PUBLISH REFUSED prior-extent-unavailable-or-overflow\n" as *u8)
306 return FSX_RC_IO
307 }
308 if extent >= priorcap { priorcap=extent+1 }
309 } else {
310 if priorfd != (0-2) { fsx_puts("NX-PUBLISH REFUSED prior-open-error\n" as *u8);return FSX_RC_IO }
311 }
312 }
313 let old: *u8 = sys_mmap(priorcap + 1)
314 if (old as i64) <= 0 { fsx_puts("NX-PUBLISH REFUSED prior-allocation-failed\n" as *u8);return FSX_RC_IO }
315 let on: i64 = pb_read_bounded(dst, old, priorcap)
316 // A retry must retain the last DIFFERENT version, not replace it with the current one.
317 if fsx_write_denied(dst) == 1 {
318 fsx_puts("NX-PUBLISH DENIED: destination rejected before retry comparison\n" as *u8)
319 return FSX_RC_DENIED
320 }
321 if on >= priorcap { fsx_puts("NX-PUBLISH REFUSED prior-at-read-cap\n" as *u8);return FSX_RC_IO }
322 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)
323 if (fsx_seq(argv[PB_ARG_VERB] as *u8,"publish-guarded" as *u8) == 1) || (staged == 1) {
324 let incoming: *u8 = sys_mmap(PB_SHA_HEX+1)
325 pb_hash(body,n,incoming)
326 if fsx_seq(incoming,argv[5] as *u8) == 0 {
327 fsx_puts("NX-PUBLISH CONFLICT artifact-sha256-mismatch\n" as *u8)
328 return PB_RC_CONFLICT
329 }
330 // A retry with the same result succeeds after its predecessor has changed.
331 if (staged == 0) && (pb_same_bytes(body,n,old,on) == 0) {
332 let current: *u8 = sys_mmap(PB_SHA_HEX+1)
333 if on < 0 {
334 let stat: *u8 = sys_mmap(160)
335 if sys_fstatat(dst,stat) != (0-2) { return FSX_RC_IO }
336 if fsx_seq(argv[6] as *u8,"absent" as *u8) == 0 { return PB_RC_CONFLICT }
337 } else {
338 pb_hash(old,on,current)
339 if fsx_seq(current,argv[6] as *u8) == 0 {
340 fsx_puts("NX-PUBLISH CONFLICT live-sha256-mismatch\n" as *u8)
341 return PB_RC_CONFLICT
342 }
343 }
344 }
345 }
346 if (pb_release_path(rel) == 1) && (pb_same_bytes(body,n,old,on) == 0) {
347 // A failed read is not absence. Only kernel ENOENT permits a new release path.
348 let stat: *u8 = sys_mmap(160)
349 let observed: i64 = sys_fstatat(dst,stat)
350 if observed != (0-2) {
351 if observed == 0 {
352 fsx_puts("NX-PUBLISH CONFLICT immutable-release-path; retain tested bytes and stage a new candidate path\n" as *u8)
353 return PB_RC_CONFLICT
354 }
355 fsx_puts("NX-PUBLISH IO release-target-stat errno=" as *u8);fsx_putn(observed)
356 fsx_puts(" target=" as *u8);fsx_puts(dst);fsx_puts("; resolve target access before retry\n" as *u8)
357 return FSX_RC_IO
358 }
359 }
360 if pb_same_bytes(body, n, old, on) == 1 {
361 fsx_puts("NX-PUBLISH UNCHANGED bytes=" as *u8); fsx_putn(n)
362 fsx_puts(" -> " as *u8); fsx_puts(dst)
363 fsx_puts(" prior_version=retained writes=0\n" as *u8)
364 return 0
365 }
366 if on > 0 {
367 let prev: *u8 = sys_mmap(PB_PATH)
368 let pp: *i64 = sys_mmap(8) as *i64
369 pp[0] = 0
370 pb_app(prev, pp, dst)
371 pb_app(prev, pp, ".prev" as *u8)
372 let pw: i64 = fsx_write(prev, old, on)
373 if pw < 0 { fsx_puts("NX-PUBLISH IO: could not preserve .prev; refusing to overwrite\n" as *u8); return FSX_RC_IO }
374 }
375 // atomic placement through the IO layer (write-deny classes included by construction)
376 let w: i64 = fsx_write(dst, body, n)
377 if w < 0 {
378 // ⚠THE SILENT EXIT, FIXED (debt seq910). Every other failure path in sp_main prints
379 // a diagnostic; this one returned an error code and NOT ONE WORD. Measured 2026-07-25:
380 // `publish <src> nishifamily research/rt004.html` produced EMPTY output and published
381 // nothing, which reads exactly like success to a caller that only sees stdout.
382 // ROOT CAUSE (read, not guessed): fsx_write does NOT create parent directories --
383 // sys_openat_wr fails and it returns -3 -- so any relpath containing a subdirectory
384 // fails here when sites/<site>/<subdir>/ is absent. Auto-creating docroot directories
385 // is a deliberate NON-goal (deny-by-construction), so the fix is to FAIL LOUD and name
386 // the cause rather than to start creating directories.
387 if w == 0 - (2 as i64) {
388 fsx_puts("NX-PUBLISH DENIED: the IO layer refused this destination -> " as *u8)
389 fsx_puts(dst); fsx_puts("\n" as *u8)
390 return FSX_RC_DENIED
391 }
392 fsx_puts("NX-PUBLISH IO-FAIL: NOTHING WAS PUBLISHED -> " as *u8); fsx_puts(dst); fsx_puts("\n" as *u8)
393 fsx_puts(" likely cause: the parent directory does not exist (this organ never creates docroot dirs).\n" as *u8)
394 fsx_puts(" if <relpath> contains a subdirectory, create it first or publish to a flat relpath.\n" as *u8)
395 return FSX_RC_IO
396 }
397 fsx_puts("NX-PUBLISH OK bytes=" as *u8); fsx_putn(w)
398 fsx_puts(" -> " as *u8); fsx_puts(dst)
399 fsx_puts("\nVERIFY (browser-grade, WAN vantage): nx_page_verify https://<site-domain>/" as *u8); fsx_puts(rel)
400 fsx_puts(" (site->domain map: surfaces.reg)\n" as *u8)
401 return 0
402}
403
404const PE_ARGC: i64=10
405// This lock coordinates only publishers honoring the immutable registered release contract.
406func pe_main(argc: i64,argv: *i64) -> i64 {
407 if argc!=PE_ARGC{return PB_RC_USAGE}
408 // Refuse a missing/nonwritable caller output before open can reuse descriptor1 for a lock.
409 if sys_write(1,"",0)!=0{return FSX_RC_IO}
410 let site: *u8=argv[3] as *u8;let rel: *u8=argv[8] as *u8
411 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}
412 var i: i64=0;while site[i]!=0{if site[i]==47{return FSX_RC_DENIED};i=i+1}
413 if fsx_seq(rel,argv[4] as *u8)==1{return FSX_RC_DENIED}
414 if vw_slen(site)+vw_slen(rel)+vw_slen("sites//.publish-lock")+1>=PB_PATH{return FSX_RC_DENIED}
415 let lock: *u8=sys_mmap_shared(PB_PATH);let pos: *i64=sys_mmap_shared(8) as *i64
416 let normalized: *i64=sys_mmap_shared((PB_GUARDED_SIZED_ARGC+1)*8) as *i64
417 var rc: i64=FSX_RC_IO
418 if (lock as i64)>0&&(pos as i64)>0&&(normalized as i64)>0 {
419 pos[0]=0;pb_app(lock,pos,"sites/");pb_app(lock,pos,site);pb_app(lock,pos,"/");pb_app(lock,pos,rel)
420 if fsx_write_denied(lock)==0 {
421 pb_app(lock,pos,".publish-lock");let fd: i64=sys_openat_wr(lock,FSX_MODE_RW)
422 if fd>=0 {
423 if sys_flock(fd,PB_LOCK_EX_NB)==0 {
424 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
425 rc=pb_main_edge(PB_GUARDED_SIZED_ARGC,normalized,rel,argv[9] as *u8)
426 } else {rc=PB_RC_BUSY;fsx_puts("NX-PUBLISH BUSY selected-dependency-owned\n")}
427 if sys_close(fd)!=0{rc=FSX_RC_IO;fsx_puts("NX-PUBLISH IO dependency-lock-close; inspect placement outcome\n")}
428 }
429 } else {rc=FSX_RC_DENIED}
430 }
431 if (normalized as i64)>0{if sys_munmap(normalized as *u8,(PB_GUARDED_SIZED_ARGC+1)*8)!=0{rc=FSX_RC_IO}}
432 if (pos as i64)>0{if sys_munmap(pos as *u8,8)!=0{rc=FSX_RC_IO}}
433 if (lock as i64)>0{if sys_munmap(lock,PB_PATH)!=0{rc=FSX_RC_IO}}
434 return rc
435}
436func sp_main(argc: i64,argv: *i64) -> i64 {
437 if argc>1{if fsx_seq(argv[1] as *u8,"publish-guarded-edge")==1{return pe_main(argc,argv)}}
438 return pb_main_edge(argc,argv,0 as *u8,0 as *u8)
439}
440func pe_check(body: *u8,n: i64,argv: *i64,rel: *u8,sha: *u8) -> i64 {
441 let conf: *u8=sys_mmap_shared(PB_CONFCAP);let spans: *i64=sys_mmap_shared(PB_SPANCAP) as *i64
442 let prefix: *u8=sys_mmap_shared(PB_PATH);let path: *u8=sys_mmap_shared(PB_PATH);let token: *u8=sys_mmap_shared(PB_PATH)
443 let av: *i64=sys_mmap_shared(8*8) as *i64;let pos: *i64=sys_mmap_shared(8) as *i64
444 var rc: i64=FSX_RC_IO
445 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 {
446 if pl_site_lookup("knowledge/pub_sites.conf",argv[3] as *u8,conf,spans)==1 {
447 if spans[3]-spans[2]<PB_PATH {
448 pl_span_cstr(conf,spans[2],spans[3],prefix)
449 let registered: i64=pr_check(prefix,rel,sha)
450 if registered==PR_OK {
451 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)
452 pos[0]=0;pb_app(token,pos,"/");pb_app(token,pos,rel)
453 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
454 let observed: i64=cd_buffer_command(7,av,body,n)
455 if observed==0{rc=0}else{if observed==1{rc=PB_RC_CONFLICT}else{rc=FSX_RC_IO}}
456 } else {if registered==PR_IO{rc=FSX_RC_IO}else{rc=PB_RC_CONFLICT};fsx_puts("NX-PUBLISH REFUSED selected-dependency-registration\n")}
457 }
458 } else {rc=FSX_RC_DENIED;fsx_puts("NX-PUBLISH DENIED selected-edge requires registered site\n")}
459 }
460 if (pos as i64)>0{if sys_munmap(pos as *u8,8)!=0{rc=FSX_RC_IO}}
461 if (av as i64)>0{if sys_munmap(av as *u8,64)!=0{rc=FSX_RC_IO}}
462 if (token as i64)>0{if sys_munmap(token,PB_PATH)!=0{rc=FSX_RC_IO}}
463 if (path as i64)>0{if sys_munmap(path,PB_PATH)!=0{rc=FSX_RC_IO}}
464 if (prefix as i64)>0{if sys_munmap(prefix,PB_PATH)!=0{rc=FSX_RC_IO}}
465 if (spans as i64)>0{if sys_munmap(spans as *u8,PB_SPANCAP)!=0{rc=FSX_RC_IO}}
466 if (conf as i64)>0{if sys_munmap(conf,PB_CONFCAP)!=0{rc=FSX_RC_IO}}
467 return rc
468}
469
470// Compatibility entry: callers retain the original two-argument API.
471func pb_publish_locked(argc: i64,argv: *i64) -> i64 {return pb_publish_locked_edge(argc,argv,0 as *u8,0 as *u8)}