code wiki / (root) / nx_site_publish_lib.nx

nx_site_publish_lib.nx source

↩ module page · 152 lines · 8937 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 25 26const PB_ARG_VERB: i64 = 1 // argv: verb ("publish") 27const PB_RC_USAGE: i64 = 2 // exit: usage 28const PB_ARG_SRC: i64 = 2 // argv: source file (NAS-local) 29const PB_ARG_SITE: i64 = 3 // argv: site dir under sites/ 30const PB_ARG_REL: i64 = 4 // argv: page path under the site 31const PB_ARGC: i64 = 5 // publish src site relpath 32const PB_PATH: i64 = 1024 // destination path buffer 33const PB_CONFCAP: i64 = 65536 // site-table read buffer (the gateway) 34const PB_SPANCAP: i64 = 128 // site-table span vector 35const PB_DOT: i64 = 46 // '.' 36const PB_SLASH: i64 = 47 // '/' 37 38// any ".." path segment, or a leading '/', or empty -> traversal refusal (1 = refuse) 39func pb_traversal(s: *u8) -> i64 { 40 if s[0] == (0 as u8) { return 1 } 41 if s[0] == (PB_SLASH as u8) { return 1 } 42 var i: i64 = 0 43 while s[i] != (0 as u8) { 44 if s[i] == (PB_DOT as u8) { if s[i+1] == (PB_DOT as u8) { return 1 } } 45 i = i + 1 46 } 47 return 0 48} 49// append NUL-terminated src to out at *op 50func pb_app(out: *u8, op: *i64, s: *u8) -> i64 { 51 var i: i64 = 0 52 var o: i64 = op[0] 53 while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 } 54 out[o] = 0 as u8 55 op[0] = o 56 return 0 57} 58 59func sp_main(argc: i64, argv: *i64) -> i64 { 60 var okverb: i64 = 0 61 if argc >= PB_ARGC { if fsx_seq(argv[PB_ARG_VERB] as *u8, "publish" as *u8) == 1 { okverb = 1 } } 62 if okverb == 0 { 63 fsx_puts("usage: nx_site_publish publish <src-file> <site> <relpath> (-> sites/<site>/<relpath>; then verify: nx_page_verify <url>)\n" as *u8) 64 return PB_RC_USAGE 65 } 66 let site: *u8 = argv[PB_ARG_SITE] as *u8 67 let rel: *u8 = argv[PB_ARG_REL] as *u8 68 if pb_traversal(site) == 1 { 69 fsx_puts("NX-PUBLISH DENIED: site escapes the docroot ('..'/absolute/empty)\n" as *u8) 70 return FSX_RC_DENIED 71 } 72 if pb_traversal(rel) == 1 { 73 fsx_puts("NX-PUBLISH DENIED: relpath escapes the docroot ('..'/absolute/empty)\n" as *u8) 74 return FSX_RC_DENIED 75 } 76 // ---- THE PUBLISH GATEWAY: the registry decides what may exist at this URL, not the caller ---- 77 let gw_conf: *u8 = sys_mmap(PB_CONFCAP) 78 let gw_spans: *i64 = sys_mmap(PB_SPANCAP) as *i64 79 if pl_site_lookup("knowledge/pub_sites.conf" as *u8, site, gw_conf, gw_spans) == 1 { 80 let gw_pfx: *u8 = sys_mmap(PB_PATH) 81 pl_span_cstr(gw_conf, gw_spans[2], gw_spans[3], gw_pfx) 82 if pl_check(gw_pfx, rel) != 0 { 83 fsx_puts("NX-PUBLISH REFUSED: " as *u8); fsx_puts(rel) 84 fsx_puts(" is not a publishable row in this site's registry.\n" as *u8) 85 fsx_puts(" The registry -- not the caller -- decides what may exist at a URL. Either the path is\n" as *u8) 86 fsx_puts(" unregistered, or its status is withdrawn/redirect/debris.\n" as *u8) 87 fsx_puts(" Register it (id/title/owner/status/section/path/note, status=live|asset|draft) in " as *u8) 88 fsx_puts(gw_pfx); fsx_puts(" ,\n" as *u8) 89 fsx_puts(" or run: nx_pub_plane adopt " as *u8); fsx_puts(site) 90 fsx_puts(" to bring existing docroot entries in. Then re-run this publish.\n" as *u8) 91 return FSX_RC_DENIED 92 } 93 } else { 94 fsx_puts("NX-PUBLISH WARN: site '" as *u8); fsx_puts(site) 95 fsx_puts("' is not in knowledge/pub_sites.conf -- publishing UNGATED (add a row to gate it).\n" as *u8) 96 } 97 // read the source (bounded by the IO layer's named cap; bigger artifacts go via the mgmt upload lane) 98 let body: *u8 = sys_mmap(FSX_READ_CAP + 1) 99 let n: i64 = vw_read(argv[PB_ARG_SRC] as *u8, body, FSX_READ_CAP) 100 if n <= 0 { 101 fsx_puts("NX-PUBLISH ABSENT: cannot read source " as *u8); fsx_puts(argv[PB_ARG_SRC] as *u8); fsx_puts("\n" as *u8) 102 return FSX_RC_ABSENT 103 } 104 if n == FSX_READ_CAP { fsx_puts("NX-PUBLISH REFUSED: source at/over the 1MiB organ cap -- use the mgmt upload lane\n" as *u8); return FSX_RC_IO } 105 // build dst = sites/<site>/<relpath> 106 let dst: *u8 = sys_mmap(PB_PATH) 107 let op: *i64 = sys_mmap(8) as *i64 108 op[0] = 0 109 pb_app(dst, op, "sites/" as *u8) 110 pb_app(dst, op, site) 111 pb_app(dst, op, "/" as *u8) 112 pb_app(dst, op, rel) 113 // preserve the prior version (one-deep; rename-not-delete doctrine) 114 let old: *u8 = sys_mmap(FSX_READ_CAP + 1) 115 let on: i64 = vw_read(dst, old, FSX_READ_CAP) 116 if on > 0 { 117 let prev: *u8 = sys_mmap(PB_PATH) 118 let pp: *i64 = sys_mmap(8) as *i64 119 pp[0] = 0 120 pb_app(prev, pp, dst) 121 pb_app(prev, pp, ".prev" as *u8) 122 let pw: i64 = fsx_write(prev, old, on) 123 if pw < 0 { fsx_puts("NX-PUBLISH IO: could not preserve .prev; refusing to overwrite\n" as *u8); return FSX_RC_IO } 124 } 125 // atomic placement through the IO layer (write-deny classes included by construction) 126 let w: i64 = fsx_write(dst, body, n) 127 if w < 0 { 128 // ⚠THE SILENT EXIT, FIXED (debt seq910). Every other failure path in sp_main prints 129 // a diagnostic; this one returned an error code and NOT ONE WORD. Measured 2026-07-25: 130 // `publish <src> nishifamily research/rt004.html` produced EMPTY output and published 131 // nothing, which reads exactly like success to a caller that only sees stdout. 132 // ROOT CAUSE (read, not guessed): fsx_write does NOT create parent directories -- 133 // sys_openat_wr fails and it returns -3 -- so any relpath containing a subdirectory 134 // fails here when sites/<site>/<subdir>/ is absent. Auto-creating docroot directories 135 // is a deliberate NON-goal (deny-by-construction), so the fix is to FAIL LOUD and name 136 // the cause rather than to start creating directories. 137 if w == 0 - (2 as i64) { 138 fsx_puts("NX-PUBLISH DENIED: the IO layer refused this destination -> " as *u8) 139 fsx_puts(dst); fsx_puts("\n" as *u8) 140 return FSX_RC_DENIED 141 } 142 fsx_puts("NX-PUBLISH IO-FAIL: NOTHING WAS PUBLISHED -> " as *u8); fsx_puts(dst); fsx_puts("\n" as *u8) 143 fsx_puts(" likely cause: the parent directory does not exist (this organ never creates docroot dirs).\n" as *u8) 144 fsx_puts(" if <relpath> contains a subdirectory, create it first or publish to a flat relpath.\n" as *u8) 145 return FSX_RC_IO 146 } 147 fsx_puts("NX-PUBLISH OK bytes=" as *u8); fsx_putn(w) 148 fsx_puts(" -> " as *u8); fsx_puts(dst) 149 fsx_puts("\nVERIFY (browser-grade, WAN vantage): nx_page_verify https://<site-domain>/" as *u8); fsx_puts(rel) 150 fsx_puts(" (site->domain map: surfaces.reg)\n" as *u8) 151 return 0 152}