code wiki / (root) / nx_site_publish_alpha_candidate_t343.nx

nx_site_publish_alpha_candidate_t343.nx source

↩ module page · 989 lines · 58495 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" 29import "json_emit.nx" 30 31const PB_ARG_VERB: i64 = 1 // argv: verb ("publish") 32const PB_RC_USAGE: i64 = 2 // exit: usage 33const PB_ARG_SRC: i64 = 2 // argv: source file (NAS-local) 34const PB_ARG_SITE: i64 = 3 // argv: site dir under sites/ 35const PB_ARG_REL: i64 = 4 // argv: page path under the site 36const PB_ARGC: i64 = 5 // publish src site relpath 37const PB_STAGE_ARGC: i64 = 6 // stage src site releases/<path> sha256 38const PB_GUARDED_ARGC: i64 = PB_STAGE_ARGC + 1 // adds expected live digest 39const PB_GUARDED_SIZED_ARGC: i64 = PB_GUARDED_ARGC + 1 // optional exact artifact extent 40const PB_PATH: i64 = 1024 // destination path buffer 41const PB_CONFCAP: i64 = 65536 // site-table read buffer (the gateway) 42const PB_SPANCAP: i64 = 128 // site-table span vector 43const PB_DOT: i64 = 46 // '.' 44const PB_SLASH: i64 = 47 // '/' 45 46// any ".." path segment, or a leading '/', or empty -> traversal refusal (1 = refuse) 47func pb_traversal(s: *u8) -> i64 { 48 if s[0] == (0 as u8) { return 1 } 49 if s[0] == (PB_SLASH as u8) { return 1 } 50 var i: i64 = 0 51 while s[i] != (0 as u8) { 52 if s[i] == (PB_DOT as u8) { if s[i+1] == (PB_DOT as u8) { return 1 } } 53 // Reject aliases before deriving the target lock: ./ and // could name the same file 54 // using different lock paths or conceal the reserved release namespace. 55 if s[i] == (PB_SLASH as u8) { 56 if s[i+1] == (PB_SLASH as u8) { return 1 } 57 if s[i+1] == (0 as u8) { return 1 } 58 } 59 var segment_start: i64 = 0 60 if i == 0 { segment_start = 1 } else { 61 if s[i-1] == (PB_SLASH as u8) { segment_start = 1 } 62 } 63 if segment_start == 1 { 64 if s[i] == (PB_DOT as u8) { 65 if (s[i+1] == (PB_SLASH as u8)) || (s[i+1] == (0 as u8)) { return 1 } 66 } 67 } 68 i = i + 1 69 } 70 return 0 71} 72// append NUL-terminated src to out at *op 73func pb_app(out: *u8, op: *i64, s: *u8) -> i64 { 74 var i: i64 = 0 75 var o: i64 = op[0] 76 while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 } 77 out[o] = 0 as u8 78 op[0] = o 79 return 0 80} 81 82// Artifact equality is byte-counted: WASM and other binary assets contain NUL bytes. 83func pb_same_bytes(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 84 if an != bn { return 0 } 85 if an < 0 { return 0 } 86 var i: i64 = 0 87 while i < an { if a[i] != b[i] { return 0 }; i = i + 1 } 88 return 1 89} 90 91const PB_LOCK_EX_NB: i64 = 6 92const PB_RC_CONFLICT: i64 = 8 93const PB_RC_BUSY: i64 = 9 94const PB_SHA_BYTES: i64 = 32 95const PB_SHA_HEX: i64 = 64 96func pb_hash(body: *u8, n: i64, hex: *u8) -> i64 { 97 let raw: *u8 = sys_mmap(PB_SHA_BYTES) 98 sha256_digest(body,n,raw) 99 let digits: *u8 = "0123456789abcdef" as *u8 100 var i: i64 = 0 101 while i < PB_SHA_BYTES { 102 let c: i64 = raw[i] as i64 103 hex[i*2] = digits[c/16]; hex[i*2+1] = digits[c%16] 104 i = i+1 105 } 106 hex[PB_SHA_HEX] = 0 as u8 107 sys_munmap(raw,PB_SHA_BYTES) 108 return 0 109} 110 111const PB_SIZED_ARGC: i64 = 7 112const PB_I64_MAX: i64 = 9223372036854775807 113func pb_sized_bytes(s: *u8) -> i64 { 114 var value: i64=0 115 var i: i64=0 116 while s[i] != (0 as u8) { 117 let c: i64=s[i] as i64 118 if c < 48 || c > 57 { return 0-1 } 119 if value > (PB_I64_MAX/2-1-(c-48))/10 { return 0-1 } 120 value=value*10+c-48; i=i+1 121 } 122 if value <= 0 { return 0-1 } 123 return value 124} 125func pb_read_bounded(path: *u8,b: *u8,cap: i64) -> i64 { 126 let fd: i64=sys_openat_rd(path) 127 if fd < 0 { return fd } 128 var n: i64=0 129 var go: i64=1 130 while n < cap && go == 1 { 131 let got: i64=sys_read(fd,((b as i64)+n) as *u8,cap-n) 132 if got < 0 { sys_close(fd); return got } 133 if got == 0 { go=0 } else { n=n+got } 134 } 135 sys_close(fd) 136 return n 137} 138 139func pb_main_edge(argc: i64, argv: *i64, edge_rel: *u8, edge_sha: *u8) -> i64 { 140 if argc < PB_ARGC { return pb_publish_locked_edge(argc,argv,edge_rel,edge_sha) } 141 let verb: *u8 = argv[PB_ARG_VERB] as *u8 142 if fsx_seq(verb,"publish" as *u8) == 0 { 143 if fsx_seq(verb,"stage" as *u8) == 1 { 144 if argc != PB_STAGE_ARGC { return PB_RC_USAGE } 145 } else { 146 if fsx_seq(verb,"publish-guarded" as *u8) == 0 { if fsx_seq(verb,"stage-sized" as *u8) == 0 { return PB_RC_USAGE } } 147 if argc != PB_GUARDED_ARGC { 148 if fsx_seq(verb,"publish-guarded" as *u8) == 0 || argc != PB_GUARDED_SIZED_ARGC { return PB_RC_USAGE } 149 } 150 } 151 } 152 let site: *u8 = argv[PB_ARG_SITE] as *u8 153 let rel: *u8 = argv[PB_ARG_REL] as *u8 154 var si: i64 = 0 155 while site[si] != (0 as u8) { 156 if site[si] == (PB_SLASH as u8) { return FSX_RC_DENIED } 157 si = si + 1 158 } 159 if fsx_seq(verb,"stage" as *u8) == 1 || fsx_seq(verb,"stage-sized" as *u8) == 1 { 160 if pb_release_path(rel) == 0 { 161 fsx_puts("NX-PUBLISH DENIED stage requires releases/<candidate-path>; live URL is not a staging target\n" as *u8) 162 return FSX_RC_DENIED 163 } 164 } 165 if pb_traversal(site) == 1 { return FSX_RC_DENIED } 166 if pb_traversal(rel) == 1 { return FSX_RC_DENIED } 167 if vw_slen(site)+vw_slen(rel)+vw_slen("sites//.publish-lock" as *u8)+1 >= PB_PATH { return FSX_RC_DENIED } 168 let path: *u8 = sys_mmap(PB_PATH) 169 let pos: *i64 = sys_mmap(8) as *i64 170 pos[0] = 0 171 pb_app(path,pos,"sites/" as *u8);pb_app(path,pos,site) 172 pb_app(path,pos,"/" as *u8);pb_app(path,pos,rel) 173 if fsx_write_denied(path) == 1 { return FSX_RC_DENIED } 174 // Keep one stable inode per target; unlinking a lock lets contenders lock different inodes. 175 pb_app(path,pos,".publish-lock" as *u8) 176 let fd: i64 = sys_openat_wr(path,FSX_MODE_RW) 177 if fd < 0 { fsx_puts("NX-PUBLISH IO lock-parent-unavailable\n" as *u8);return FSX_RC_IO } 178 if sys_flock(fd,PB_LOCK_EX_NB) != 0 { 179 sys_close(fd) 180 fsx_puts("NX-PUBLISH BUSY target-owned retry-after-owner-completes\n" as *u8) 181 return PB_RC_BUSY 182 } 183 let rc: i64 = pb_publish_locked_edge(argc,argv,edge_rel,edge_sha) 184 sys_close(fd) 185 return rc 186} 187// Every publisher verb enforces this namespace, so a legacy publish cannot replace a tested candidate. 188// The site registry still authorizes each release path; staging does not bypass publication policy. 189func pb_release_path(rel: *u8) -> i64 { 190 let prefix: *u8 = "releases/" as *u8 191 var i: i64 = 0 192 while prefix[i] != (0 as u8) { 193 if rel[i] != prefix[i] { return 0 } 194 i = i + 1 195 } 196 return (rel[i] != (0 as u8)) as i64 197} 198func pb_publish_locked_edge(argc: i64, argv: *i64, edge_rel: *u8, edge_sha: *u8) -> i64 { 199 var okverb: i64 = 0 200 if argc >= PB_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8, "publish" as *u8) == 1 { okverb = 1 } } 201 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 } } 202 if argc == PB_STAGE_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage" as *u8) == 1 { okverb = 1 } } 203 if argc == PB_SIZED_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage-sized" as *u8) == 1 { okverb = 1 } } 204 if okverb == 0 { 205 fsx_puts("usage: nx_site_publish publish <src-file> <site> <relpath> (-> sites/<site>/<relpath>; then verify: nx_page_verify <url>)\n" as *u8) 206 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) 207 return PB_RC_USAGE 208 } 209 let site: *u8 = argv[PB_ARG_SITE] as *u8 210 let rel: *u8 = argv[PB_ARG_REL] as *u8 211 if pb_traversal(site) == 1 { 212 fsx_puts("NX-PUBLISH DENIED: site escapes the docroot ('..'/absolute/empty)\n" as *u8) 213 return FSX_RC_DENIED 214 } 215 if pb_traversal(rel) == 1 { 216 fsx_puts("NX-PUBLISH DENIED: relpath escapes the docroot ('..'/absolute/empty)\n" as *u8) 217 return FSX_RC_DENIED 218 } 219 // ---- THE PUBLISH GATEWAY: the registry decides what may exist at this URL, not the caller ---- 220 let gw_conf: *u8 = sys_mmap(PB_CONFCAP) 221 let gw_spans: *i64 = sys_mmap(PB_SPANCAP) as *i64 222 var release_prefix: *u8 = 0 as *u8 223 var registry_checked: i64 = 0 224 if pl_site_lookup("knowledge/pub_sites.conf" as *u8, site, gw_conf, gw_spans) == 1 { 225 registry_checked = 1 226 let gw_pfx: *u8 = sys_mmap(PB_PATH) 227 pl_span_cstr(gw_conf, gw_spans[2], gw_spans[3], gw_pfx) 228 var row_status: i64 = 0 229 if pb_release_path(rel) == 1 { release_prefix = gw_pfx } else { row_status = pl_check(gw_pfx,rel) } 230 if row_status != 0 { 231 fsx_puts("NX-PUBLISH REFUSED: " as *u8); fsx_puts(rel) 232 fsx_puts(" is not a publishable row in this site's registry.\n" as *u8) 233 fsx_puts(" The registry -- not the caller -- decides what may exist at a URL. Either the path is\n" as *u8) 234 fsx_puts(" unregistered, or its status is withdrawn/redirect/debris.\n" as *u8) 235 fsx_puts(" Register it (id/title/owner/status/section/path/note, status=live|asset|draft) in " as *u8) 236 fsx_puts(gw_pfx); fsx_puts(" ,\n" as *u8) 237 fsx_puts(" or run: nx_pub_plane adopt " as *u8); fsx_puts(site) 238 fsx_puts(" to bring existing docroot entries in. Then re-run this publish.\n" as *u8) 239 return FSX_RC_DENIED 240 } 241 } else { 242 fsx_puts("NX-PUBLISH WARN: site '" as *u8); fsx_puts(site) 243 fsx_puts("' is not in knowledge/pub_sites.conf -- publishing UNGATED (add a row to gate it).\n" as *u8) 244 } 245 // stage-sized declares its exact artifact size; the read budget is that size plus one 246 // to distinguish complete bytes from truncation. Legacy verbs retain their existing cap. 247 var readcap: i64 = FSX_READ_CAP 248 var wanted: i64 = 0 249 var size_arg: i64 = 0 250 if fsx_seq(argv[PB_ARG_VERB] as *u8,"stage-sized" as *u8) == 1 { 251 size_arg=6 252 if (release_prefix as i64) == 0 { return FSX_RC_DENIED } 253 } 254 if argc == PB_GUARDED_SIZED_ARGC && fsx_seq(argv[PB_ARG_VERB] as *u8,"publish-guarded" as *u8) == 1 { 255 size_arg=7 256 if registry_checked != 1 { return FSX_RC_DENIED } 257 } 258 if size_arg > 0 { 259 wanted=pb_sized_bytes(argv[size_arg] as *u8) 260 if wanted < 0 { return PB_RC_USAGE } 261 readcap=wanted+1 262 } 263 let body: *u8 = sys_mmap(readcap + 1) 264 let n: i64 = pb_read_bounded(argv[PB_ARG_SRC] as *u8, body, readcap) 265 if wanted > 0 { if n != wanted { fsx_puts("NX-PUBLISH REFUSED source-size-mismatch\n" as *u8); return PB_RC_CONFLICT } } 266 if n <= 0 { 267 fsx_puts("NX-PUBLISH ABSENT: cannot read source " as *u8); fsx_puts(argv[PB_ARG_SRC] as *u8); fsx_puts("\n" as *u8) 268 return FSX_RC_ABSENT 269 } 270 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 } 271 // The owned body is checked once here and later passed unchanged to fsx_write. 272 if (edge_rel as i64)>0 { 273 let edge_rc: i64=pe_check(body,n,argv,edge_rel,edge_sha) 274 if edge_rc!=0{return edge_rc} 275 } 276 // Every verb checks the registered content, including legacy publish without a caller hash. 277 if (release_prefix as i64) != 0 { 278 let release_hash: *u8 = sys_mmap(PB_SHA_HEX+1) 279 pb_hash(body,n,release_hash) 280 let registered: i64 = pr_check(release_prefix,rel,release_hash) 281 sys_munmap(release_hash,PB_SHA_HEX+1) 282 if registered != PR_OK { 283 fsx_puts("NX-PUBLISH REFUSED release-registration code=" as *u8); fsx_putn(registered) 284 fsx_puts(" path=" as *u8); fsx_puts(rel) 285 fsx_puts("; verify catalogue integrity and registered digest before retrying\n" as *u8) 286 if registered == PR_IO { return FSX_RC_IO } 287 return PB_RC_CONFLICT 288 } 289 } 290 // build dst = sites/<site>/<relpath> 291 let dst: *u8 = sys_mmap(PB_PATH) 292 let op: *i64 = sys_mmap(8) as *i64 293 op[0] = 0 294 pb_app(dst, op, "sites/" as *u8) 295 pb_app(dst, op, site) 296 pb_app(dst, op, "/" as *u8) 297 pb_app(dst, op, rel) 298 // preserve the prior version (one-deep; rename-not-delete doctrine) 299 // The predecessor has its own extent: a smaller incoming artifact must still retain it whole. 300 var priorcap: i64 = readcap 301 if wanted > 0 { 302 let priorfd: i64 = sys_openat_rd(dst) 303 if priorfd >= 0 { 304 let extent: i64 = sys_lseek(priorfd,0,FSX_SEEK_END) 305 let closed: i64 = sys_close(priorfd) 306 if extent < 0 || extent > PB_I64_MAX/2-1 || closed != 0 { 307 fsx_puts("NX-PUBLISH REFUSED prior-extent-unavailable-or-overflow\n" as *u8) 308 return FSX_RC_IO 309 } 310 if extent >= priorcap { priorcap=extent+1 } 311 } else { 312 if priorfd != (0-2) { fsx_puts("NX-PUBLISH REFUSED prior-open-error\n" as *u8);return FSX_RC_IO } 313 } 314 } 315 let old: *u8 = sys_mmap(priorcap + 1) 316 if (old as i64) <= 0 { fsx_puts("NX-PUBLISH REFUSED prior-allocation-failed\n" as *u8);return FSX_RC_IO } 317 let on: i64 = pb_read_bounded(dst, old, priorcap) 318 // A retry must retain the last DIFFERENT version, not replace it with the current one. 319 if fsx_write_denied(dst) == 1 { 320 fsx_puts("NX-PUBLISH DENIED: destination rejected before retry comparison\n" as *u8) 321 return FSX_RC_DENIED 322 } 323 if on >= priorcap { fsx_puts("NX-PUBLISH REFUSED prior-at-read-cap\n" as *u8);return FSX_RC_IO } 324 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) 325 if (fsx_seq(argv[PB_ARG_VERB] as *u8,"publish-guarded" as *u8) == 1) || (staged == 1) { 326 let incoming: *u8 = sys_mmap(PB_SHA_HEX+1) 327 pb_hash(body,n,incoming) 328 if fsx_seq(incoming,argv[5] as *u8) == 0 { 329 fsx_puts("NX-PUBLISH CONFLICT artifact-sha256-mismatch\n" as *u8) 330 return PB_RC_CONFLICT 331 } 332 // A retry with the same result succeeds after its predecessor has changed. 333 if (staged == 0) && (pb_same_bytes(body,n,old,on) == 0) { 334 let current: *u8 = sys_mmap(PB_SHA_HEX+1) 335 if on < 0 { 336 let stat: *u8 = sys_mmap(160) 337 if sys_fstatat(dst,stat) != (0-2) { return FSX_RC_IO } 338 if fsx_seq(argv[6] as *u8,"absent" as *u8) == 0 { return PB_RC_CONFLICT } 339 } else { 340 pb_hash(old,on,current) 341 if fsx_seq(current,argv[6] as *u8) == 0 { 342 fsx_puts("NX-PUBLISH CONFLICT live-sha256-mismatch\n" as *u8) 343 return PB_RC_CONFLICT 344 } 345 } 346 } 347 } 348 if (pb_release_path(rel) == 1) && (pb_same_bytes(body,n,old,on) == 0) { 349 // A failed read is not absence. Only kernel ENOENT permits a new release path. 350 let stat: *u8 = sys_mmap(160) 351 let observed: i64 = sys_fstatat(dst,stat) 352 if observed != (0-2) { 353 if observed == 0 { 354 fsx_puts("NX-PUBLISH CONFLICT immutable-release-path; retain tested bytes and stage a new candidate path\n" as *u8) 355 return PB_RC_CONFLICT 356 } 357 fsx_puts("NX-PUBLISH IO release-target-stat errno=" as *u8);fsx_putn(observed) 358 fsx_puts(" target=" as *u8);fsx_puts(dst);fsx_puts("; resolve target access before retry\n" as *u8) 359 return FSX_RC_IO 360 } 361 } 362 if pb_same_bytes(body, n, old, on) == 1 { 363 fsx_puts("NX-PUBLISH UNCHANGED bytes=" as *u8); fsx_putn(n) 364 fsx_puts(" -> " as *u8); fsx_puts(dst) 365 fsx_puts(" prior_version=retained writes=0\n" as *u8) 366 return 0 367 } 368 if on > 0 { 369 let prev: *u8 = sys_mmap(PB_PATH) 370 let pp: *i64 = sys_mmap(8) as *i64 371 pp[0] = 0 372 pb_app(prev, pp, dst) 373 pb_app(prev, pp, ".prev" as *u8) 374 let pw: i64 = fsx_write(prev, old, on) 375 if pw < 0 { fsx_puts("NX-PUBLISH IO: could not preserve .prev; refusing to overwrite\n" as *u8); return FSX_RC_IO } 376 } 377 // atomic placement through the IO layer (write-deny classes included by construction) 378 let w: i64 = fsx_write(dst, body, n) 379 if w < 0 { 380 // ⚠THE SILENT EXIT, FIXED (debt seq910). Every other failure path in sp_main prints 381 // a diagnostic; this one returned an error code and NOT ONE WORD. Measured 2026-07-25: 382 // `publish <src> nishifamily research/rt004.html` produced EMPTY output and published 383 // nothing, which reads exactly like success to a caller that only sees stdout. 384 // ROOT CAUSE (read, not guessed): fsx_write does NOT create parent directories -- 385 // sys_openat_wr fails and it returns -3 -- so any relpath containing a subdirectory 386 // fails here when sites/<site>/<subdir>/ is absent. Auto-creating docroot directories 387 // is a deliberate NON-goal (deny-by-construction), so the fix is to FAIL LOUD and name 388 // the cause rather than to start creating directories. 389 if w == 0 - (2 as i64) { 390 fsx_puts("NX-PUBLISH DENIED: the IO layer refused this destination -> " as *u8) 391 fsx_puts(dst); fsx_puts("\n" as *u8) 392 return FSX_RC_DENIED 393 } 394 fsx_puts("NX-PUBLISH IO-FAIL: NOTHING WAS PUBLISHED -> " as *u8); fsx_puts(dst); fsx_puts("\n" as *u8) 395 fsx_puts(" likely cause: the parent directory does not exist (this organ never creates docroot dirs).\n" as *u8) 396 fsx_puts(" if <relpath> contains a subdirectory, create it first or publish to a flat relpath.\n" as *u8) 397 return FSX_RC_IO 398 } 399 fsx_puts("NX-PUBLISH OK bytes=" as *u8); fsx_putn(w) 400 fsx_puts(" -> " as *u8); fsx_puts(dst) 401 fsx_puts("\nVERIFY (browser-grade, WAN vantage): nx_page_verify https://<site-domain>/" as *u8); fsx_puts(rel) 402 fsx_puts(" (site->domain map: surfaces.reg)\n" as *u8) 403 return 0 404} 405 406const PE_ARGC: i64=10 407// This lock coordinates only publishers honoring the immutable registered release contract. 408func pe_main(argc: i64,argv: *i64) -> i64 { 409 if argc!=PE_ARGC{return PB_RC_USAGE} 410 // Refuse a missing/nonwritable caller output before open can reuse descriptor1 for a lock. 411 if sys_write(1,"",0)!=0{return FSX_RC_IO} 412 let site: *u8=argv[3] as *u8;let rel: *u8=argv[8] as *u8 413 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} 414 var i: i64=0;while site[i]!=0{if site[i]==47{return FSX_RC_DENIED};i=i+1} 415 if fsx_seq(rel,argv[4] as *u8)==1{return FSX_RC_DENIED} 416 if vw_slen(site)+vw_slen(rel)+vw_slen("sites//.publish-lock")+1>=PB_PATH{return FSX_RC_DENIED} 417 let lock: *u8=sys_mmap_shared(PB_PATH);let pos: *i64=sys_mmap_shared(8) as *i64 418 let normalized: *i64=sys_mmap_shared((PB_GUARDED_SIZED_ARGC+1)*8) as *i64 419 var rc: i64=FSX_RC_IO 420 if (lock as i64)>0&&(pos as i64)>0&&(normalized as i64)>0 { 421 pos[0]=0;pb_app(lock,pos,"sites/");pb_app(lock,pos,site);pb_app(lock,pos,"/");pb_app(lock,pos,rel) 422 if fsx_write_denied(lock)==0 { 423 pb_app(lock,pos,".publish-lock");let fd: i64=sys_openat_wr(lock,FSX_MODE_RW) 424 if fd>=0 { 425 if sys_flock(fd,PB_LOCK_EX_NB)==0 { 426 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 427 rc=pb_main_edge(PB_GUARDED_SIZED_ARGC,normalized,rel,argv[9] as *u8) 428 } else {rc=PB_RC_BUSY;fsx_puts("NX-PUBLISH BUSY selected-dependency-owned\n")} 429 if sys_close(fd)!=0{rc=FSX_RC_IO;fsx_puts("NX-PUBLISH IO dependency-lock-close; inspect placement outcome\n")} 430 } 431 } else {rc=FSX_RC_DENIED} 432 } 433 if (normalized as i64)>0{if sys_munmap(normalized as *u8,(PB_GUARDED_SIZED_ARGC+1)*8)!=0{rc=FSX_RC_IO}} 434 if (pos as i64)>0{if sys_munmap(pos as *u8,8)!=0{rc=FSX_RC_IO}} 435 if (lock as i64)>0{if sys_munmap(lock,PB_PATH)!=0{rc=FSX_RC_IO}} 436 return rc 437} 438func sp_main(argc: i64,argv: *i64) -> i64 { 439 if argc>1{if fsx_seq(argv[1] as *u8,"prepare-alpha-release")==1||fsx_seq(argv[1] as *u8,"release-alpha-manifest")==1{return rm2_main(argc,argv)}} 440 if argc>1{if fsx_seq(argv[1] as *u8,"prepare-path")==1{ 441 if argc!=4{return PB_RC_USAGE} 442 let rc:i64=pb_prepare_path(argv[2] as *u8,argv[3] as *u8) 443 fsx_puts("NX-PUBLISH PREPARE rc=");fsx_putn(rc);fsx_puts(" registered-path-only=1 published=0\n") 444 return rc 445 }} 446 if argc>1{if fsx_seq(argv[1] as *u8,"prepare-release")==1{return rm2_main(argc,argv)}} 447 if argc>1{if fsx_seq(argv[1] as *u8,"release-manifest")==1{return rm2_main(argc,argv)}} 448 if argc>1{if fsx_seq(argv[1] as *u8,"publish-guarded-edge")==1{return pe_main(argc,argv)}} 449 return pb_main_edge(argc,argv,0 as *u8,0 as *u8) 450} 451func pe_check(body: *u8,n: i64,argv: *i64,rel: *u8,sha: *u8) -> i64 { 452 let conf: *u8=sys_mmap_shared(PB_CONFCAP);let spans: *i64=sys_mmap_shared(PB_SPANCAP) as *i64 453 let prefix: *u8=sys_mmap_shared(PB_PATH);let path: *u8=sys_mmap_shared(PB_PATH);let token: *u8=sys_mmap_shared(PB_PATH) 454 let av: *i64=sys_mmap_shared(8*8) as *i64;let pos: *i64=sys_mmap_shared(8) as *i64 455 var rc: i64=FSX_RC_IO 456 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 { 457 if pl_site_lookup("knowledge/pub_sites.conf",argv[3] as *u8,conf,spans)==1 { 458 if spans[3]-spans[2]<PB_PATH { 459 pl_span_cstr(conf,spans[2],spans[3],prefix) 460 let registered: i64=pr_check(prefix,rel,sha) 461 if registered==PR_OK { 462 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) 463 pos[0]=0;pb_app(token,pos,"/");pb_app(token,pos,rel) 464 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 465 let observed: i64=cd_buffer_command(7,av,body,n) 466 if observed==0{rc=0}else{if observed==1{rc=PB_RC_CONFLICT}else{rc=FSX_RC_IO}} 467 } else {if registered==PR_IO{rc=FSX_RC_IO}else{rc=PB_RC_CONFLICT};fsx_puts("NX-PUBLISH REFUSED selected-dependency-registration\n")} 468 } 469 } else {rc=FSX_RC_DENIED;fsx_puts("NX-PUBLISH DENIED selected-edge requires registered site\n")} 470 } 471 if (pos as i64)>0{if sys_munmap(pos as *u8,8)!=0{rc=FSX_RC_IO}} 472 if (av as i64)>0{if sys_munmap(av as *u8,64)!=0{rc=FSX_RC_IO}} 473 if (token as i64)>0{if sys_munmap(token,PB_PATH)!=0{rc=FSX_RC_IO}} 474 if (path as i64)>0{if sys_munmap(path,PB_PATH)!=0{rc=FSX_RC_IO}} 475 if (prefix as i64)>0{if sys_munmap(prefix,PB_PATH)!=0{rc=FSX_RC_IO}} 476 if (spans as i64)>0{if sys_munmap(spans as *u8,PB_SPANCAP)!=0{rc=FSX_RC_IO}} 477 if (conf as i64)>0{if sys_munmap(conf,PB_CONFCAP)!=0{rc=FSX_RC_IO}} 478 return rc 479} 480 481// Compatibility entry: callers retain the original two-argument API. 482func pb_publish_locked(argc: i64,argv: *i64) -> i64 {return pb_publish_locked_edge(argc,argv,0 as *u8,0 as *u8)} 483 484// One selected immutable dependency, matching the existing guarded-edge contract. 485// Registration is opt-in on this verb only; legacy publication authority is unchanged. 486const RM_FIELDS: i64=12 487const RM_VALUES_BYTES: i64=RM_FIELDS*8 488const RM_SCHEMA: *u8="site-release/v1" 489const RM_RECEIPT_ROOT: *u8="knowledge/gates/" 490func rm_key(k: *u8) -> i64 { 491 if fsx_seq(k,"schema")==1{return 0};if fsx_seq(k,"site")==1{return 1} 492 if fsx_seq(k,"consumer_source")==1{return 2};if fsx_seq(k,"consumer_path")==1{return 3} 493 if fsx_seq(k,"consumer_sha256")==1{return 4};if fsx_seq(k,"consumer_bytes")==1{return 5} 494 if fsx_seq(k,"expected_live")==1{return 6};if fsx_seq(k,"dependency_source")==1{return 7} 495 if fsx_seq(k,"dependency_path")==1{return 8};if fsx_seq(k,"dependency_sha256")==1{return 9} 496 if fsx_seq(k,"dependency_bytes")==1{return 10};if fsx_seq(k,"registration")==1{return 11} 497 return 0-1 498} 499// This path-oriented schema deliberately refuses escapes rather than interpreting them differently. 500func rm_string(t: *NxJsonTok) -> *u8 { 501 if nx_json_next(t)!=NX_JSON_STRING{return 0 as *u8} 502 let n: i64=t.tok_len-2 503 if n<=0||n>=PB_PATH{return 0 as *u8} 504 let v: *u8=sys_mmap(n+1);if (v as i64)<=0{return 0 as *u8} 505 var i: i64=0 506 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} 507 v[n]=0;return v 508} 509func rm_parse_tokens(t: *NxJsonTok,v: *i64) -> i64 { 510 if nx_json_next(t)!=NX_JSON_LBRACE{return PB_RC_USAGE} 511 var count: i64=0 512 while count<RM_FIELDS { 513 let k: *u8=rm_string(t);if (k as i64)<=0{return PB_RC_USAGE} 514 let idx: i64=rm_key(k);sys_munmap(k,vw_slen(k)+1) 515 if idx<0{return PB_RC_USAGE};if v[idx]!=0{return PB_RC_USAGE} 516 if nx_json_next(t)!=NX_JSON_COLON{return PB_RC_USAGE} 517 let value: *u8=rm_string(t);if (value as i64)<=0{return PB_RC_USAGE} 518 v[idx]=value as i64;count=count+1 519 let separator: i64=nx_json_next(t) 520 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} 521 if separator!=NX_JSON_COMMA{return PB_RC_USAGE} 522 } 523 return PB_RC_USAGE 524} 525func rm_parse(body: *u8,n: i64,v: *i64) -> i64 { 526 let t: *NxJsonTok=nx_json_new(body,n);if (t as i64)<=0{return FSX_RC_IO} 527 let rc: i64=rm_parse_tokens(t,v);sys_munmap(t as *u8,NX_JSONTOK_BYTES);return rc 528} 529 530func rm_num(out: *u8,pos: *i64,value: i64) -> i64 { 531 // Internal epoch/PID/exit values are bounded; digits use the caller-owned receipt buffer. 532 var n: i64=value 533 if n<0{pb_app(out,pos,"-");n=0-n} 534 let start: i64=pos[0] 535 if n==0{pb_app(out,pos,"0")}else{ 536 while n>0{out[pos[0]]=(48+n%10) as u8;pos[0]=pos[0]+1;n=n/10} 537 var left: i64=start;var right: i64=pos[0]-1 538 while left<right{let ch: u8=out[left];out[left]=out[right];out[right]=ch;left=left+1;right=right-1} 539 out[pos[0]]=0 540 } 541 return 0 542} 543 544func rm_receipt(path: *u8,digest: *u8,phase: *u8,state: *u8,rc: i64) -> i64 { 545 let b: *u8=sys_mmap(PB_PATH);let p: *i64=sys_mmap(8) as *i64 546 if (b as i64)<=0||(p as i64)<=0{return FSX_RC_IO} 547 p[0]=0;pb_app(b,p,"{\"schema\":\"site-release-phase/v1\",\"manifest_sha256\":\"");pb_app(b,p,digest) 548 pb_app(b,p,"\",\"epoch\":");rm_num(b,p,sys_now_realtime_sec()) 549 pb_app(b,p,",\"attempt_pid\":");rm_num(b,p,osp_selfpid()) 550 pb_app(b,p,",\"phase\":\"");pb_app(b,p,phase);pb_app(b,p,"\",\"state\":\"");pb_app(b,p,state) 551 pb_app(b,p,"\",\"rc\":");rm_num(b,p,rc);pb_app(b,p,"}\n") 552 fsx_puts(b) 553 let written: i64=fsx_append(path,b,p[0]);var result: i64=FSX_RC_IO 554 if written>=p[0]{result=rm_sync(path)} 555 sys_munmap(b,PB_PATH);sys_munmap(p as *u8,8) 556 if result!=0{fsx_puts("NX-RELEASE receipt-io; inspect last recorded phase and actual artifact identity; no further phase executed\n")} 557 return result 558} 559func rm_finish(receipt: *u8,digest: *u8,phase: *u8,rc: i64) -> i64 { 560 var state: *u8="complete" 561 if rc!=0{state="refused";if rc==PB_RC_BUSY{state="waiting-owner-retry-required"}} 562 if rm_receipt(receipt,digest,phase,state,rc)!=0{return FSX_RC_IO} 563 return rc 564} 565func rm_artifact(src: *u8,size: *u8,hash: *u8,out: *i64) -> i64 { 566 let n: i64=pb_sized_bytes(size);if n<0||pr_hash_ok(hash)!=1{return PB_RC_USAGE} 567 let b: *u8=sys_mmap(n+1);if (b as i64)<=0{return FSX_RC_IO} 568 let got: i64=pb_read_bounded(src,b,n+1) 569 if got!=n{sys_munmap(b,n+1);return PB_RC_CONFLICT} 570 let h: *u8=sys_mmap(PB_SHA_HEX+1);if (h as i64)<=0{sys_munmap(b,n+1);return FSX_RC_IO} 571 pb_hash(b,n,h);let same: i64=fsx_seq(h,hash);sys_munmap(h,PB_SHA_HEX+1) 572 if same!=1{sys_munmap(b,n+1);return PB_RC_CONFLICT} 573 out[0]=b as i64;out[1]=n;return 0 574} 575// CLI/job scope: early preflight refusals and incumbent publisher allocations are reclaimed at process exit. 576// This entry is not a memory-stable long-lived daemon API. 577func rm_preflight(v: *i64,prefix: *u8) -> i64 { 578 if fsx_seq(v[0] as *u8,RM_SCHEMA)!=1{return PB_RC_USAGE} 579 if fsx_seq(v[11] as *u8,"allow")!=1&&fsx_seq(v[11] as *u8,"existing-only")!=1{return PB_RC_USAGE} 580 if pr_hash_ok(v[6] as *u8)!=1&&fsx_seq(v[6] as *u8,"absent")!=1{return PB_RC_USAGE} 581 let site: *u8=v[1] as *u8;let target: *u8=v[3] as *u8;let dep: *u8=v[8] as *u8 582 if pb_traversal(site)!=0||pb_traversal(target)!=0||pr_path_ok(dep)!=1||pb_release_path(target)==1{return FSX_RC_DENIED} 583 var i: i64=0;while site[i]!=0{if site[i]==47{return FSX_RC_DENIED};i=i+1} 584 if vw_slen(site)+vw_slen(target)+vw_slen("sites//.publish-lock")+1>=PB_PATH{return FSX_RC_DENIED} 585 if vw_slen(site)+vw_slen(dep)+vw_slen("sites//.publish-lock")+1>=PB_PATH{return FSX_RC_DENIED} 586 let conf: *u8=sys_mmap(PB_CONFCAP);let spans: *i64=sys_mmap(PB_SPANCAP) as *i64 587 if (conf as i64)<=0||(spans as i64)<=0{return FSX_RC_IO} 588 if pl_site_lookup("knowledge/pub_sites.conf",site,conf,spans)!=1{return FSX_RC_DENIED} 589 if spans[3]-spans[2]>=PB_PATH{return FSX_RC_DENIED} 590 pl_span_cstr(conf,spans[2],spans[3],prefix) 591 if pl_check(prefix,target)!=0{return FSX_RC_DENIED} 592 sys_munmap(conf,PB_CONFCAP);sys_munmap(spans as *u8,PB_SPANCAP) 593 let artifact: *i64=sys_mmap(16) as *i64;let asset: *i64=sys_mmap(16) as *i64 594 if (artifact as i64)<=0||(asset as i64)<=0{return FSX_RC_IO} 595 let cr: i64=rm_artifact(v[2] as *u8,v[5] as *u8,v[4] as *u8,artifact);if cr!=0{return cr} 596 let dr: i64=rm_artifact(v[7] as *u8,v[10] as *u8,v[9] as *u8,asset);if dr!=0{return dr} 597 let args: *i64=sys_mmap(8*8) as *i64;let token: *u8=sys_mmap(PB_PATH);let pos: *i64=sys_mmap(8) as *i64 598 if (args as i64)<=0||(token as i64)<=0||(pos as i64)<=0{return FSX_RC_IO} 599 pos[0]=0;pb_app(token,pos,"/");pb_app(token,pos,dep) 600 args[0]="nx_comparestale" as *u8 as i64;args[1]="dependency-check" as *u8 as i64;args[2]=v[2];args[3]=v[4] 601 args[4]=v[7];args[5]=v[9];args[6]=token as i64;args[7]=0 602 let checked: i64=cd_buffer_command(7,args,artifact[0] as *u8,artifact[1]) 603 sys_munmap(artifact[0] as *u8,artifact[1]+1);sys_munmap(asset[0] as *u8,asset[1]+1) 604 sys_munmap(args as *u8,64);sys_munmap(token,PB_PATH);sys_munmap(pos as *u8,8) 605 sys_munmap(artifact as *u8,16);sys_munmap(asset as *u8,16) 606 if checked!=0{return PB_RC_CONFLICT};return 0 607} 608func rm_execute(v: *i64,prefix: *u8,receipt: *u8,digest: *u8) -> i64 { 609 if rm_receipt(receipt,digest,"preflight","started",0)!=0{return FSX_RC_IO} 610 var rc: i64=rm_preflight(v,prefix);rc=rm_finish(receipt,digest,"preflight",rc);if rc!=0{return rc} 611 if rm_receipt(receipt,digest,"registration","started",0)!=0{return FSX_RC_IO} 612 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)} 613 rc=rm_finish(receipt,digest,"registration",rc);if rc!=0{return rc} 614 let args: *i64=sys_mmap((PE_ARGC+1)*8) as *i64;if (args as i64)<=0{return FSX_RC_IO} 615 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] 616 args[4]=v[8];args[5]=v[9];args[6]=v[10];args[7]=0 617 if rm_receipt(receipt,digest,"stage","started",0)!=0{return FSX_RC_IO} 618 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} 619 // The staging lock has been released. pe_main owns dependency then consumer locks itself. 620 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] 621 args[6]=v[6];args[7]=v[5];args[8]=v[8];args[9]=v[9];args[10]=0 622 if rm_receipt(receipt,digest,"publish","started",0)!=0{return FSX_RC_IO} 623 rc=pe_main(PE_ARGC,args);rc=rm_finish(receipt,digest,"publish",rc);if rc!=0{return rc} 624 sys_munmap(args as *u8,(PE_ARGC+1)*8) 625 return rm_receipt(receipt,digest,"delivery","published-served-review-pending",0) 626} 627func rm_main(argc: i64,argv: *i64) -> i64 { 628 if argc!=4{return PB_RC_USAGE};if pr_hash_ok(argv[3] as *u8)!=1{return PB_RC_USAGE} 629 let body: *u8=sys_mmap(PB_CONFCAP+1);let digest: *u8=sys_mmap(PB_SHA_HEX+1) 630 let v: *i64=sys_mmap(RM_VALUES_BYTES) as *i64;let receipt: *u8=sys_mmap(PB_PATH) 631 let pos: *i64=sys_mmap(8) as *i64;let prefix: *u8=sys_mmap(PB_PATH) 632 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} 633 let n: i64=pb_read_bounded(argv[2] as *u8,body,PB_CONFCAP+1) 634 if n<=0||n>PB_CONFCAP{rm_cleanup(body,digest,v,receipt,pos,prefix);return PB_RC_USAGE} 635 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} 636 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") 637 var rc: i64=rm_parse(body,n,v) 638 if rc!=0{rc=rm_finish(receipt,digest,"manifest",rc);rm_cleanup(body,digest,v,receipt,pos,prefix);return rc} 639 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") 640 rc=rm_bank(body,n,digest) 641 if rc!=0{rc=rm_finish(receipt,digest,"manifest-bank",rc);rm_cleanup(body,digest,v,receipt,pos,prefix);return rc} 642 rc=rm_execute(v,prefix,receipt,digest) 643 rm_cleanup(body,digest,v,receipt,pos,prefix) 644 return rc 645} 646 647func rm_sync(path: *u8) -> i64 { 648 let fd: i64=sys_openat_rd(path);if fd<0{return FSX_RC_IO} 649 let sync: i64=sys_fsync(fd);let closed: i64=sys_close(fd) 650 if sync!=0||closed!=0{return FSX_RC_IO} 651 let dir: i64=sys_openat_rd(RM_RECEIPT_ROOT);if dir<0{return FSX_RC_IO} 652 let ds: i64=sys_fsync(dir);let dc: i64=sys_close(dir) 653 if ds!=0||dc!=0{return FSX_RC_IO};return 0 654} 655// The caller's authorization and original precondition remain available after temporary input expires. 656func rm_bank(body: *u8,n: i64,digest: *u8) -> i64 { 657 let path: *u8=sys_mmap(PB_PATH);let lock: *u8=sys_mmap(PB_PATH);let p: *i64=sys_mmap(8) as *i64 658 if (path as i64)<=0||(lock as i64)<=0||(p as i64)<=0{return FSX_RC_IO} 659 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") 660 p[0]=0;pb_app(lock,p,path);pb_app(lock,p,".lock") 661 if fsx_write_denied(path)==1||fsx_write_denied(lock)==1{return FSX_RC_DENIED} 662 let fd: i64=sys_openat_wr(lock,FSX_MODE_RW);if fd<0{return FSX_RC_IO} 663 if sys_flock(fd,PB_LOCK_EX_NB)!=0{sys_close(fd);return PB_RC_BUSY} 664 let old: *u8=sys_mmap(n+1);var rc: i64=FSX_RC_IO 665 if (old as i64)>0 { 666 let got: i64=pb_read_bounded(path,old,n+1) 667 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}}} 668 sys_munmap(old,n+1) 669 } 670 if sys_close(fd)!=0{rc=FSX_RC_IO} 671 sys_munmap(path,PB_PATH);sys_munmap(lock,PB_PATH);sys_munmap(p as *u8,8) 672 return rc 673} 674func rm_cleanup(body: *u8,digest: *u8,v: *i64,receipt: *u8,pos: *i64,prefix: *u8) -> i64 { 675 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)} 676 if (body as i64)>0{sys_munmap(body,PB_CONFCAP+1)};if (digest as i64)>0{sys_munmap(digest,PB_SHA_HEX+1)} 677 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)} 678 return 0 679} 680 681 // Prepare only registered paths. Each directory is opened relative to a retained 682 // parent descriptor without following symlinks; file publication remains separate. 683func pb_prepare_path(site:*u8,rel:*u8)->i64 { 684 if pb_traversal(site)!=0||pb_traversal(rel)!=0{return FSX_RC_DENIED} 685 var i:i64=0 686 while site[i]!=0 {if site[i]==47{return FSX_RC_DENIED};i=i+1} 687 if i==0||vw_slen(rel)==0||vw_slen(rel)>=PB_PATH{return PB_RC_USAGE} 688 let conf:*u8=sys_mmap(PB_CONFCAP);let spans:*i64=sys_mmap(PB_SPANCAP) as *i64 689 let prefix:*u8=sys_mmap(PB_PATH);let component:*u8=sys_mmap(PB_PATH) 690 if (conf as i64)<=0||(spans as i64)<=0||(prefix as i64)<=0||(component as i64)<=0{return FSX_RC_IO} 691 var rc:i64=FSX_RC_DENIED 692 if pl_site_lookup("knowledge/pub_sites.conf",site,conf,spans)==1 { 693 if spans[3]-spans[2]<PB_PATH{ 694 pl_span_cstr(conf,spans[2],spans[3],prefix) 695 if pl_check(prefix,rel)==PL_OK{rc=0} 696 } 697 } 698 sys_munmap(conf,PB_CONFCAP);sys_munmap(spans as *u8,PB_SPANCAP);sys_munmap(prefix,PB_PATH) 699 if rc!=0{sys_munmap(component,PB_PATH);return rc} 700 // Validate every component before creating any directories. 701 i=0;var start:i64=0 702 while 1 { 703 if rel[i]==47||rel[i]==0{ 704 let n:i64=i-start 705 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} 706 if rel[i]==0{break};start=i+1 707 };i=i+1 708 } 709 var parent:i64=__syscall(SYS_OPENAT,AT_FDCWD,"sites" as *u8,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0) 710 if parent<0{sys_munmap(component,PB_PATH);return FSX_RC_IO} 711 let sitefd:i64=__syscall(SYS_OPENAT,parent,site,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0) 712 sys_close(parent) 713 if sitefd<0{sys_munmap(component,PB_PATH);return FSX_RC_IO} 714 parent=sitefd;i=0;start=0 715 while rel[i]!=0{ 716 if rel[i]==47{ 717 var j:i64=0;while start+j<i{component[j]=rel[start+j];j=j+1};component[j]=0 as u8 718 var child:i64=__syscall(SYS_OPENAT,parent,component,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0) 719 if child==(0-2){ 720 // Same Nishi mkdirat ABI as sys_mkdir; descriptor-relative variant. 721 let made:i64=__syscall(34,parent,component,0x1ed,0,0,0) 722 if made!=0&&made!=(0-17){rc=FSX_RC_IO;break} 723 if sys_fsync(parent)!=0{rc=FSX_RC_IO;break} 724 child=__syscall(SYS_OPENAT,parent,component,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC,0,0,0) 725 } 726 if child<0{rc=FSX_RC_IO;break} 727 sys_close(parent);parent=child;start=i+1 728 };i=i+1 729 } 730 if sys_close(parent)!=0{rc=FSX_RC_IO} 731 sys_munmap(component,PB_PATH) 732 return rc 733} 734 735 736// je_emit_string_raw owns RFC8259 escaping; retain this writer only for one output string. 737const RM_JSON_WRITER_BYTES:i64=__size_of(JsonWriter) 738func rm_put_escaped(s:*u8)->i64{ 739 let n:i64=vw_slen(s) 740 // A byte may expand to six ASCII bytes (backslash-u00XX), plus two quotes. 741 if n>(PB_I64_MAX-2)/6{return FSX_RC_IO} 742 let cap:i64=n*6+2 743 let b:*u8=sys_mmap_try(cap);let w:*JsonWriter=sys_mmap_try(RM_JSON_WRITER_BYTES) as *JsonWriter 744 var rc:i64=FSX_RC_IO 745 if (b as i64)>0&&(w as i64)>0{ 746 w.buf=b;w.pos=0;w.cap=cap;w.depth=0;w.prior=0 as *u8 747 if je_emit_string_raw(w,s,n)==0{ 748 let count:i64=w.pos-2 749 if sys_write(1,((b as i64)+1) as *u8,count)==count{rc=0} 750 } 751 } 752 if (w as i64)>0{if sys_munmap(w as *u8,RM_JSON_WRITER_BYTES)!=0{rc=FSX_RC_IO}} 753 if (b as i64)>0{if sys_munmap(b,cap)!=0{rc=FSX_RC_IO}} 754 return rc 755} 756func rm_prepare_emit(v:*i64,digest:*u8,observed:i64)->i64 { 757 var state:*u8="registration-conflict";var action:*u8="choose-new-immutable-dependency-path" 758 var planned:*u8="none";var rc:i64=PB_RC_CONFLICT 759 if observed==PR_OK{state="ready";action="execute-unchanged-manifest";planned="existing-only";rc=0} 760 if observed==PR_MISSING{ 761 state="registration-missing";action="explicit-registration-authorization-required";planned="allow" 762 if fsx_seq(v[11] as *u8,"allow")==1{state="ready-registration-required";action="execute-authorized-registration";rc=0} 763 } 764 if observed==PR_IO{state="registry-unavailable";action="repair-existing-registry";rc=FSX_RC_IO} 765 if observed==PR_INVALID{state="invalid-registration-identity";action="correct-manifest";rc=PB_RC_USAGE} 766 fsx_puts("{\"schema\":\"site-release-preparation/v1\",\"state\":\"");if rm_put_escaped(state)!=0{return FSX_RC_IO} 767 fsx_puts("\",\"next\":\"");if rm_put_escaped(action)!=0{return FSX_RC_IO};fsx_puts("\",\"registration_observed_code\":");fsx_putn(observed) 768 fsx_puts(",\"registration_requested\":\"");if rm_put_escaped(v[11] as *u8)!=0{return FSX_RC_IO} 769 fsx_puts("\",\"registration_needed\":\"");if rm_put_escaped(planned)!=0{return FSX_RC_IO} 770 fsx_puts("\",\"manifest_sha256\":\"");if rm_put_escaped(digest)!=0{return FSX_RC_IO} 771 fsx_puts("\",\"site\":\"");if rm_put_escaped(v[1] as *u8)!=0{return FSX_RC_IO} 772 fsx_puts("\",\"consumer_source\":\"");if rm_put_escaped(v[2] as *u8)!=0{return FSX_RC_IO} 773 fsx_puts("\",\"consumer_path\":\"");if rm_put_escaped(v[3] as *u8)!=0{return FSX_RC_IO} 774 fsx_puts("\",\"consumer_sha256\":\"");if rm_put_escaped(v[4] as *u8)!=0{return FSX_RC_IO} 775 fsx_puts("\",\"consumer_bytes\":");fsx_putn(pb_sized_bytes(v[5] as *u8)) 776 fsx_puts(",\"dependency_path\":\"");if rm_put_escaped(v[8] as *u8)!=0{return FSX_RC_IO} 777 fsx_puts("\",\"dependency_sha256\":\"");if rm_put_escaped(v[9] as *u8)!=0{return FSX_RC_IO} 778 fsx_puts("\",\"dependency_bytes\":");fsx_putn(pb_sized_bytes(v[10] as *u8)) 779 fsx_puts(",\"expected_live\":\"");if rm_put_escaped(v[6] as *u8)!=0{return FSX_RC_IO} 780 fsx_puts("\",\"source_identity\":\"verified-exact-bytes-and-sha256\",\"dependency_binding\":\"identity-and-literal-reference-only\",\"build_qualification\":null,\"served_identity\":null,\"live_precondition\":\"rechecked-by-locked-publish\",\"registry_recheck\":\"required-at-execution\",\"published\":false,\"registration_writes\":0,\"publication_writes\":0,\"rc\":");fsx_putn(rc);fsx_puts("}\n") 781 return rc 782} 783func rm_prepare_main(argc:i64,argv:*i64)->i64 { 784 if argc!=4{return PB_RC_USAGE};if pr_hash_ok(argv[3] as *u8)!=1{return PB_RC_USAGE} 785 let body:*u8=sys_mmap_try(PB_CONFCAP+1);let digest:*u8=sys_mmap_try(PB_SHA_HEX+1) 786 let v:*i64=sys_mmap_try(RM_VALUES_BYTES) as *i64;let prefix:*u8=sys_mmap_try(PB_PATH) 787 if (v as i64)>0{var i:i64=0;while i<RM_FIELDS{v[i]=0;i=i+1}} 788 var rc:i64=FSX_RC_IO 789 if (body as i64)>0&&(digest as i64)>0&&(v as i64)>0&&(prefix as i64)>0{ 790 let n:i64=pb_read_bounded(argv[2] as *u8,body,PB_CONFCAP+1) 791 if n>0&&n<=PB_CONFCAP{ 792 pb_hash(body,n,digest) 793 if fsx_seq(digest,argv[3] as *u8)==1{ 794 rc=rm_parse(body,n,v) 795 if rc==0{ 796 rc=rm_preflight(v,prefix) 797 if rc==0{rc=rm_prepare_emit(v,digest,pr_observe(prefix,v[8] as *u8,v[9] as *u8))} 798 else{fsx_puts("{\"schema\":\"site-release-preparation/v1\",\"state\":\"preflight-refused\",\"source_identity\":null,\"published\":false,\"rc\":");fsx_putn(rc);fsx_puts("}\n")} 799 } 800 }else{rc=PB_RC_CONFLICT;fsx_puts("{\"schema\":\"site-release-preparation/v1\",\"state\":\"manifest-hash-conflict\",\"published\":false}\n")} 801 }else{rc=PB_RC_USAGE} 802 } 803 rm_cleanup(body,digest,v,0 as *u8,0 as *i64,prefix) 804 return rc 805} 806 807// Additive site-release/v2 collection; legacy v1 functions remain unchanged. 808const RM2_COMMON:i64=12 809const RM2_DEP_WORDS:i64=5 810const RM2_STATE_WORDS:i64=3 811const RM2_SCHEMA:*u8="site-release/v2" 812func rm2_dep_key(k:*u8)->i64{ 813 if fsx_seq(k,"source")==1{return 0};if fsx_seq(k,"path")==1{return 1} 814 if fsx_seq(k,"sha256")==1{return 2};if fsx_seq(k,"bytes")==1{return 3};return -1 815} 816func rm2_dep(t:*NxJsonTok,d:*i64)->i64{ 817 var count:i64=0;while count<4{ 818 let k:*u8=rm_string(t);if (k as i64)<=0{return PB_RC_USAGE};let idx:i64=rm2_dep_key(k);sys_munmap(k,vw_slen(k)+1) 819 if idx<0||d[idx]!=0{return PB_RC_USAGE};if nx_json_next(t)!=NX_JSON_COLON{return PB_RC_USAGE} 820 let val:*u8=rm_string(t);if (val as i64)<=0{return PB_RC_USAGE};d[idx]=val as i64;count=count+1 821 let sep:i64=nx_json_next(t);if sep==NX_JSON_RBRACE{if count==4{return 0};return PB_RC_USAGE};if sep!=NX_JSON_COMMA{return PB_RC_USAGE} 822 };return PB_RC_USAGE 823} 824func rm2_parse(body:*u8,n:i64,v:*i64,state:*i64)->i64{ 825 let t:*NxJsonTok=nx_json_new(body,n);if (t as i64)<=0{return FSX_RC_IO} 826 if nx_json_next(t)!=NX_JSON_LBRACE{return PB_RC_USAGE};var count:i64=0;var deps:i64=0;var tail:*i64=0 as *i64 827 while count<9{ 828 let k:*u8=rm_string(t);if (k as i64)<=0{return PB_RC_USAGE};let depkey:i64=fsx_seq(k,"dependencies");let idx:i64=rm_key(k);sys_munmap(k,vw_slen(k)+1) 829 if nx_json_next(t)!=NX_JSON_COLON{return PB_RC_USAGE} 830 if depkey==1{ 831 if deps!=0{return PB_RC_USAGE};deps=1;if nx_json_next(t)!=NX_JSON_LBRACKET{return PB_RC_USAGE} 832 var more:i64=1;while more==1{ 833 if nx_json_next(t)!=NX_JSON_LBRACE{return PB_RC_USAGE} 834 let d:*i64=sys_mmap_try(RM2_DEP_WORDS*8) as *i64;if (d as i64)<=0{return FSX_RC_IO};var j:i64=0;while j<RM2_DEP_WORDS{d[j]=0;j=j+1} 835 let rc:i64=rm2_dep(t,d);if rc!=0{return rc} 836 if (tail as i64)==0{state[0]=d as i64}else{tail[4]=d as i64};tail=d;if state[1]>=PB_I64_MAX/8{return PB_RC_USAGE};state[1]=state[1]+1 837 let sep:i64=nx_json_next(t);if sep==NX_JSON_RBRACKET{more=0}else{if sep!=NX_JSON_COMMA{return PB_RC_USAGE}} 838 } 839 }else{ 840 if idx<0||(idx>=7&&idx<=10)||v[idx]!=0{return PB_RC_USAGE} 841 let val:*u8=rm_string(t);if (val as i64)<=0{return PB_RC_USAGE};v[idx]=val as i64 842 } 843 count=count+1;let sep:i64=nx_json_next(t) 844 if sep==NX_JSON_RBRACE{if count!=9||deps!=1{return PB_RC_USAGE};if nx_json_next(t)!=NX_JSON_EOF{return PB_RC_USAGE};if fsx_seq(v[0] as *u8,RM2_SCHEMA)!=1{return PB_RC_USAGE};return 0} 845 if sep!=NX_JSON_COMMA{return PB_RC_USAGE} 846 };return PB_RC_USAGE 847} 848func rm2_view(v:*i64,d:*i64)->*i64{ 849 let one:*i64=sys_mmap_try(RM_VALUES_BYTES) as *i64;if (one as i64)<=0{return 0 as *i64} 850 var i:i64=0;while i<RM_FIELDS{one[i]=v[i];i=i+1};one[0]=RM_SCHEMA as i64 851 i=0;while i<4{one[7+i]=d[i];i=i+1};return one 852} 853func rm2_path(site:*u8,rel:*u8)->*u8{ 854 let b:*u8=sys_mmap_try(PB_PATH);let p:*i64=sys_mmap_try(8) as *i64;if (b as i64)<=0||(p as i64)<=0{return 0 as *u8} 855 p[0]=0;pb_app(b,p,"sites/");pb_app(b,p,site);pb_app(b,p,"/");pb_app(b,p,rel);sys_munmap(p as *u8,8);return b 856} 857// Observe exact existing bytes without treating an IO failure as absence. 858func rm2_existing(path:*u8,size:*u8,sha:*u8)->i64{ 859 let fd:i64=sys_openat_rd(path);if fd==-2{return PR_MISSING};if fd<0{return FSX_RC_IO} 860 if sys_close(fd)!=0{return FSX_RC_IO};let out:*i64=sys_mmap_try(16) as *i64;if (out as i64)<=0{return FSX_RC_IO} 861 let rc:i64=rm_artifact(path,size,sha,out);if rc==0{sys_munmap(out[0] as *u8,out[1]+1)};sys_munmap(out as *u8,16);return rc 862} 863func rm2_live(v:*i64)->i64{ 864 let path:*u8=rm2_path(v[1] as *u8,v[3] as *u8);if (path as i64)<=0{return FSX_RC_IO} 865 let same:i64=rm2_existing(path,v[5] as *u8,v[4] as *u8);if same==0{return 0} 866 if same==PR_MISSING{if fsx_seq(v[6] as *u8,"absent")==1{return 0};return PB_RC_CONFLICT} 867 let fd:i64=sys_openat_rd(path);if fd<0{return FSX_RC_IO};let extent:i64=sys_lseek(fd,0,FSX_SEEK_END);let closed:i64=sys_close(fd) 868 if extent<0||extent>=PB_I64_MAX/2||closed!=0{return FSX_RC_IO} 869 let b:*u8=sys_mmap_try(extent+1);let h:*u8=sys_mmap_try(PB_SHA_HEX+1);if (b as i64)<=0||(h as i64)<=0{return FSX_RC_IO} 870 let got:i64=pb_read_bounded(path,b,extent+1);if got!=extent{return FSX_RC_IO};pb_hash(b,got,h) 871 let matched:i64=fsx_seq(h,v[6] as *u8);sys_munmap(b,extent+1);sys_munmap(h,PB_SHA_HEX+1);sys_munmap(path,PB_PATH) 872 if matched==1{return 0};return PB_RC_CONFLICT 873} 874func rm2_preflight(v:*i64,state:*i64,prefix:*u8)->i64{ 875 if state[1]<1{return PB_RC_USAGE};var d:*i64=state[0] as *i64 876 while (d as i64)!=0{ 877 var prior:*i64=state[0] as *i64;while (prior as i64)!=(d as i64){if fsx_seq(prior[1] as *u8,d[1] as *u8)==1{return PB_RC_CONFLICT};prior=prior[4] as *i64} 878 let one:*i64=rm2_view(v,d);if (one as i64)<=0{return FSX_RC_IO};let rc:i64=rm_preflight(one,prefix);sys_munmap(one as *u8,RM_VALUES_BYTES);if rc!=0{return rc} 879 let observed:i64=pr_observe(prefix,d[1] as *u8,d[2] as *u8) 880 if observed!=PR_OK{if observed!=PR_MISSING||fsx_seq(v[11] as *u8,"allow")!=1{if observed==PR_IO{return FSX_RC_IO};return PB_RC_CONFLICT}} 881 let dst:*u8=rm2_path(v[1] as *u8,d[1] as *u8);if (dst as i64)<=0{return FSX_RC_IO};let existing:i64=rm2_existing(dst,d[3] as *u8,d[2] as *u8);sys_munmap(dst,PB_PATH) 882 if existing!=0&&existing!=PR_MISSING{return existing};d=d[4] as *i64 883 };return rm2_live(v) 884} 885func rm2_phase(receipt:*u8,digest:*u8,kind:*u8,index:i64,rc:i64)->i64{ 886 let phase:*u8=sys_mmap_try(PB_PATH);let pos:*i64=sys_mmap_try(8) as *i64;if (phase as i64)<=0||(pos as i64)<=0{return FSX_RC_IO} 887 pos[0]=0;pb_app(phase,pos,kind);pb_app(phase,pos,"-");rm_num(phase,pos,index);let result:i64=rm_finish(receipt,digest,phase,rc) 888 sys_munmap(phase,PB_PATH);sys_munmap(pos as *u8,8);return result 889} 890// Locks remain held for every dependency through the one consumer publish. 891// Nonblocking locks avoid deadlock between overlapping manifest collections. 892func rm2_publish(v:*i64,state:*i64,prefix:*u8)->i64{ 893 if state[1]<1||state[1]>PB_I64_MAX/8{return PB_RC_USAGE} 894 let fds:*i64=sys_mmap_try(state[1]*8) as *i64;if (fds as i64)<=0{return FSX_RC_IO} 895 var held:i64=0;var rc:i64=0;var d:*i64=state[0] as *i64 896 while (d as i64)!=0&&rc==0{ 897 let path:*u8=rm2_path(v[1] as *u8,d[1] as *u8);let pos:*i64=sys_mmap_try(8) as *i64;if (path as i64)<=0||(pos as i64)<=0{rc=FSX_RC_IO;break} 898 if fsx_write_denied(path)!=0{rc=FSX_RC_DENIED;break};pos[0]=vw_slen(path);pb_app(path,pos,".publish-lock") 899 let fd:i64=sys_openat_wr(path,FSX_MODE_RW);sys_munmap(path,PB_PATH);sys_munmap(pos as *u8,8) 900 if fd<0{rc=FSX_RC_IO;break};if sys_flock(fd,PB_LOCK_EX_NB)!=0{sys_close(fd);rc=PB_RC_BUSY;break};fds[held]=fd;held=held+1;d=d[4] as *i64 901 } 902 d=state[0] as *i64;while (d as i64)!=0&&rc==0{ 903 if pr_check(prefix,d[1] as *u8,d[2] as *u8)!=PR_OK{rc=PB_RC_CONFLICT;break} 904 let path:*u8=rm2_path(v[1] as *u8,d[1] as *u8);if (path as i64)<=0{rc=FSX_RC_IO;break};rc=rm2_existing(path,d[3] as *u8,d[2] as *u8);sys_munmap(path,PB_PATH);if rc==PR_MISSING{rc=PB_RC_CONFLICT};d=d[4] as *i64 905 } 906 if rc==0{ 907 let args:*i64=sys_mmap_try((PB_GUARDED_SIZED_ARGC+1)*8) as *i64;if (args as i64)<=0{rc=FSX_RC_IO}else{ 908 args[0]="nx_site_publish" as *u8 as i64;args[1]="publish-guarded" as *u8 as i64;args[2]=v[2];args[3]=v[1];args[4]=v[3];args[5]=v[4];args[6]=v[6];args[7]=v[5];args[8]=0 909 rc=pb_main_edge(PB_GUARDED_SIZED_ARGC,args,0 as *u8,0 as *u8);sys_munmap(args as *u8,(PB_GUARDED_SIZED_ARGC+1)*8) 910 } 911 } 912 var i:i64=0;while i<held{if sys_close(fds[i])!=0{rc=FSX_RC_IO};i=i+1};sys_munmap(fds as *u8,state[1]*8);return rc 913} 914func rm2_execute(v:*i64,state:*i64,prefix:*u8,receipt:*u8,digest:*u8)->i64{ 915 var rc:i64=rm2_preflight(v,state,prefix);rc=rm_finish(receipt,digest,"preflight-all",rc);if rc!=0{return rc} 916 var d:*i64=state[0] as *i64;var index:i64=0 917 while (d as i64)!=0{ 918 if fsx_seq(v[11] as *u8,"allow")==1{rc=pr_register(prefix,d[1] as *u8,d[2] as *u8);if rc==PR_CREATED{rc=0}}else{rc=pr_check(prefix,d[1] as *u8,d[2] as *u8)} 919 rc=rm2_phase(receipt,digest,"registration",index,rc);if rc!=0{return rc};d=d[4] as *i64;index=index+1 920 } 921 d=state[0] as *i64;index=0;while (d as i64)!=0{ 922 let args:*i64=sys_mmap_try((PB_SIZED_ARGC+1)*8) as *i64;if (args as i64)<=0{return FSX_RC_IO} 923 args[0]="nx_site_publish" as *u8 as i64;args[1]="stage-sized" as *u8 as i64;args[2]=d[0];args[3]=v[1];args[4]=d[1];args[5]=d[2];args[6]=d[3];args[7]=0 924 rc=pb_main_edge(PB_SIZED_ARGC,args,0 as *u8,0 as *u8);sys_munmap(args as *u8,(PB_SIZED_ARGC+1)*8) 925 rc=rm2_phase(receipt,digest,"stage",index,rc);if rc!=0{return rc};d=d[4] as *i64;index=index+1 926 } 927 rc=rm2_publish(v,state,prefix);rc=rm_finish(receipt,digest,"verify-all-publish-once",rc);if rc!=0{return rc} 928 return rm_receipt(receipt,digest,"delivery","published-served-review-pending",0) 929} 930// Size comes from the actual regular file; exact read and caller digest detect extent/content races. 931func rm2_load(path:*u8,out:*i64)->i64{ 932 out[0]=0;out[1]=0;let fd:i64=sys_openat_rd(path);if fd<0{return FSX_RC_IO} 933 let n:i64=sys_lseek(fd,0,FSX_SEEK_END);let closed:i64=sys_close(fd) 934 if n<=0||n>=PB_I64_MAX||closed!=0{return FSX_RC_IO} 935 let b:*u8=sys_mmap_try(n+1);if (b as i64)<=0{return FSX_RC_IO} 936 let got:i64=pb_read_bounded(path,b,n+1);if got!=n{sys_munmap(b,n+1);return PB_RC_CONFLICT} 937 out[0]=b as i64;out[1]=n;return 0 938} 939// Boundary diagnostics preserve the original native return code and never echo manifest contents. 940func rm2_boundary_error(operation:*u8,path:*u8,stage:*u8,reason:*u8,rc:i64)->i64{ 941 fsx_puts("{\"schema\":\"site-release-error/v1\",\"operation\":\"");rm_put_escaped(operation) 942 fsx_puts("\",\"path\":\"");rm_put_escaped(path) 943 fsx_puts("\",\"stage\":\"");rm_put_escaped(stage) 944 fsx_puts("\",\"reason\":\"");rm_put_escaped(reason) 945 fsx_puts("\",\"rc\":");fsx_putn(rc) 946 fsx_puts(",\"publication_attempted\":false,\"cwd\":") 947 let cwd:*u8=sys_mmap_try(PB_PATH) 948 var got:i64=0 949 if (cwd as i64)>0{got=sys_getcwd(cwd,PB_PATH)} 950 if got>0{fsx_puts("\"");rm_put_escaped(cwd);fsx_puts("\"")}else{fsx_puts("null")} 951 if (cwd as i64)>0{sys_munmap_direct(cwd,PB_PATH)} 952 fsx_puts(",\"next\":\"Check the operation, manifest path and SHA against this process cwd; relative manifest and manifest source paths resolve there. A direct MCP call may use the serving root while a build qualification uses buildroot. Supply paths for the observed cwd; do not automatically prefix or retry publication.\"}\n") 953 return rc 954} 955// Alpha is an experimental delivery boundary, not quality or proprietary-source approval. 956func rm2_alpha_path(path:*u8)->i64{ 957 if pb_traversal(path)!=0{return 0} 958 let prefix:*u8="alpha/";let n:i64=vw_slen(prefix) 959 if vw_slen(path)<=n{return 0};var i:i64=0 960 while i<n{if path[i]!=prefix[i]{return 0};i=i+1} 961 // Reuse release path grammar so browser decoding cannot alias an Alpha destination. 962 let tail:*u8=((path as i64)+n) as *u8;let bytes:i64=vw_slen("releases/")+vw_slen(tail)+1 963 let checked:*u8=sys_mmap_try(bytes);if (checked as i64)<=0{return 0} 964 let offset:i64=ss_cat(checked,0,"releases/");checked[ss_cat(checked,offset,tail)]=0 965 let valid:i64=pr_path_ok(checked);if sys_munmap_direct(checked,bytes)!=0{return 0};return valid 966} 967func rm2_main(argc:i64,argv:*i64)->i64{ 968 var operation:*u8="<missing>";var path:*u8="<missing>" 969 if argc>1{operation=argv[1] as *u8};if argc>2{path=argv[2] as *u8} 970 if argc!=4{return rm2_boundary_error(operation,path,"arguments","expected-operation-manifest-sha256",PB_RC_USAGE)} 971 if pr_hash_ok(argv[3] as *u8)!=1{return rm2_boundary_error(operation,path,"manifest-hash","invalid-sha256-format",PB_RC_USAGE)} 972 let loaded:*i64=sys_mmap_try(16) as *i64;if (loaded as i64)<=0{return rm2_boundary_error(operation,path,"allocation","manifest-loader-allocation-failed",FSX_RC_IO)};let readrc:i64=rm2_load(argv[2] as *u8,loaded);if readrc!=0{return rm2_boundary_error(operation,path,"manifest-read","manifest-load-failed",readrc)};let body:*u8=loaded[0] as *u8;let n:i64=loaded[1];let digest:*u8=sys_mmap_try(PB_SHA_HEX+1);let v:*i64=sys_mmap_try(RM_VALUES_BYTES) as *i64;let state:*i64=sys_mmap_try(RM2_STATE_WORDS*8) as *i64;let prefix:*u8=sys_mmap_try(PB_PATH) 973 if (body as i64)<=0||(digest as i64)<=0||(v as i64)<=0||(state as i64)<=0||(prefix as i64)<=0{return rm2_boundary_error(operation,path,"allocation","manifest-state-allocation-failed",FSX_RC_IO)} 974 var i:i64=0;while i<RM_FIELDS{v[i]=0;i=i+1};i=0;while i<RM2_STATE_WORDS{state[i]=0;i=i+1} 975 pb_hash(body,n,digest);if fsx_seq(digest,argv[3] as *u8)!=1{return rm2_boundary_error(operation,path,"manifest-hash","manifest-sha256-mismatch",PB_RC_CONFLICT)} 976 // Preserve the v1 parser and executor unchanged. 977 if rm_parse(body,n,v)==0{if fsx_seq(v[0] as *u8,RM_SCHEMA)==1{if fsx_seq(argv[1] as *u8,"prepare-release")==1{return rm_prepare_main(argc,argv)};if fsx_seq(argv[1] as *u8,"release-manifest")==1{return rm_main(argc,argv)};return PB_RC_USAGE}} 978 i=0;while i<RM_FIELDS{v[i]=0;i=i+1};let parsed:i64=rm2_parse(body,n,v,state);if parsed!=0{return rm2_boundary_error(operation,path,"manifest-parse","invalid-release-manifest",parsed)} 979 let alpha:i64=(fsx_seq(operation,"prepare-alpha-release")==1||fsx_seq(operation,"release-alpha-manifest")==1) as i64 980 if alpha==1{if rm2_alpha_path(v[3] as *u8)!=1{return rm2_boundary_error(operation,path,"scope","experimental-alpha-consumer-required",FSX_RC_DENIED)}} 981 if fsx_seq(operation,"prepare-release")==1||fsx_seq(operation,"prepare-alpha-release")==1{ 982 let rc:i64=rm2_preflight(v,state,prefix);fsx_puts("NX-RELEASE-V2 PREPARE dependencies=");fsx_putn(state[1]);fsx_puts(" rc=");fsx_putn(rc);fsx_puts(" registration_writes=0 publication_writes=0\n");if rc!=0{return rm2_boundary_error(operation,path,"preflight","manifest-dependencies-or-live-identity-refused",rc)};return rc 983 } 984 if fsx_seq(operation,"release-manifest")!=1&&fsx_seq(operation,"release-alpha-manifest")!=1{return PB_RC_USAGE} 985 let receipt:*u8=sys_mmap_try(PB_PATH);let pos:*i64=sys_mmap_try(8) as *i64;if (receipt as i64)<=0||(pos as i64)<=0{return FSX_RC_IO} 986 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") 987 fsx_puts("NX-RELEASE-V2 receipt=");fsx_puts(receipt);fsx_puts(" dependencies=");fsx_putn(state[1]);fsx_puts(" consumer_publishes_max=1\n") 988 let bank:i64=rm_bank(body,n,digest);if bank!=0{return bank};return rm2_execute(v,state,prefix,receipt,digest) 989}