code wiki / _hdl_build / nx_docportal_lib.nx

nx_docportal_lib.nx source

↩ module page · 216 lines · 14099 B

1// nx_docportal_lib.nx -- shared core for the PER-DOMAIN DOCUMENT PORTAL (operator 2026-06-28: "a document portal 2// to upload documents for their onsite search ... all our sites sharded off per domain ... public and private"). 3// The s-class substrate is the SOVEREIGN seg_store (immutable append-only segments + content-addressed values + 4// never-delete) -- NOT a new TSV database (operator: get off TSV). ONE call ingests a document into the domain's 5// PUBLIC store (also emitted to the domain's derived BM25 search source so it is findable on <domain>/search) or 6// PRIVATE store (the client vault, NEVER emitted to the public search source). 7// 8// THE PUBLIC/PRIVATE SPLIT IS BY CONSTRUCTION: public and private are DIFFERENT store prefixes => DIFFERENT 9// physical segment files => a public search query cannot read the private shard (it never opens those files). 10// THE PER-DOMAIN SHARD is the domain: prefix carries <domain>, so a query scoped to domain A cannot read domain 11// B's segments the same way. This is the records-management "balance of confidentiality, data privacy, and public 12// access" (ldm_records_mgmt) enforced mechanically, not by an ACL check that can be misconfigured. 13// 14// CID = deterministic content id (FNV-1a, folded positive): same bytes -> same CID (exceed dimension #8); used as 15// the segid so distinct content never overwrites a prior doc's segment (never-delete) and identical re-ingest is 16// idempotent. The cryptographic seal (sha256 via nx_doc_seal) is a later rung. Composes nx_seg_store + nx_fnv. 17// license_tier: ORIGINAL 18import "nx_seg_store.nx" 19import "nx_fnv.nx" 20import "nx_syscalls.nx" 21import "nx_docprose.nx" // dpr_http_body / dpr_is_html: capture front matter, and IS this really html 22import "nx_block_density.nx" // bd_fit_text: the estate's shipping Kohlschuetter boilerplate remover 23 24const DP_VIS_PUBLIC: i64 = 0 25const DP_VIS_PRIVATE: i64 = 1 26 27// ---- owner USAGE-POLICY flags (operator: owners flag how their uploads are managed; extensible -- "...or whatever") ---- 28const DP_USE_PUB_SEARCH: i64 = 1 // index this doc for the public onsite search 29const DP_USE_AI_BLOG: i64 = 2 // an AI writer MAY publish blog posts derived from this content 30const DP_USE_AI_SUMMARY: i64 = 4 // AI may summarize/derive internally (non-publishing) 31const DP_USE_SHARE_EXT: i64 = 8 // may be shared outside the firm 32 33func dp_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 34func dp_cat(out: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 } return o } 35func dp_catn(out: *u8, o: i64, v: i64) -> i64 { 36 if v == 0 { out[o] = 48 as u8; return o + 1 } 37 var m: i64 = v 38 if m < 0 { out[o] = 45 as u8; o = o + 1; m = 0 - m } 39 let t: *u8 = sys_mmap(24); var k: i64 = 0 40 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 41 var i: i64 = 0; while i < k { out[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } 42 return o 43} 44 45// deterministic content id (FNV-1a over the doc bytes, folded positive). Same bytes -> same CID. 46func dp_cid(text: *u8, n: i64) -> i64 { 47 var h: i64 = fnv1a_init() 48 h = fnv1a_update(h, text, n) 49 return h & 0x7fffffffffffffff 50} 51 52// store prefix for (domain, visibility): knowledge/store/dp-<domain>-pub- / knowledge/store/dp-<domain>-prv- 53func dp_prefix(domain: *u8, vis: i64, out: *u8) -> i64 { 54 var o: i64 = dp_cat(out, 0, "knowledge/store/dp-" as *u8) 55 o = dp_cat(out, o, domain) 56 if vis == DP_VIS_PRIVATE { o = dp_cat(out, o, "-prv-" as *u8) } else { o = dp_cat(out, o, "-pub-" as *u8) } 57 out[o] = 0 as u8 58 return o 59} 60 61// content-addressed doc key: doc:<cid> 62func dp_key(cid: i64, out: *u8) -> i64 { 63 var o: i64 = dp_cat(out, 0, "doc:" as *u8) 64 o = dp_catn(out, o, cid) 65 out[o] = 0 as u8 66 return o 67} 68 69// RETIRED (operator 2026-07-02, "no tsv -- nishi ecosystem hardware rung up"): dp_pubsrc/dp_append_pubsrc, 70// the derived knowledge/index/<domain>_src.tsv search-source emitter, are DELETED. The sovereign store IS the 71// search index (ss_write_seg builds .terms for every segment); nx_docportal_search_seg queries it directly and 72// enforces DP_USE_PUB_SEARCH consent LIVE per hit (pol:<cid>), so the emission-time filter has no job left. 73 74// INGEST one document into the domain's shard by visibility. Writes the raw bytes content-addressed into the 75// sovereign seg_store (immutable; segid=CID so distinct content never overwrites a prior doc, identical re-ingest 76// is idempotent). PUBLIC docs are ALSO emitted to the derived BM25 search source. A PRIVATE doc touches ONLY the 77// private store -- no public store write, no search-source line. Returns 0 ok; cidout[0] = content id. 78// INGEST with the DEFAULT owner policy (public: searchable, AI-blog opt-in OFF; private: nothing). Backward- 79// compatible wrapper -- existing callers get sensible, consent-respecting defaults; owners set explicit flags via 80// dp_ingest_policy (the admin handler passes the owner's choices). Returns 0 ok; cidout[0]=cid. 81func dp_ingest(domain: *u8, vis: i64, text: *u8, n: i64, cidout: *i64) -> i64 { 82 return dp_ingest_policy(domain, vis, dp_default_policy(vis), text, n, cidout) 83} 84 85// READ a doc back from the (domain, visibility) shard by cid, byte-exact. 1 = found (ptrout[0]/lenout[0] set). 86// Uses the PROVEN ss_open+ss_hget path (nx_raci_sov): an empty/absent shard -> ss_open returns 0 -> not found, 87// which is exactly the cross-shard isolation guarantee (a public/other-domain prefix with no segments answers 0). 88func dp_read(domain: *u8, vis: i64, cid: i64, ptrout: *i64, lenout: *i64) -> i64 { 89 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix) 90 let key: *u8 = sys_mmap(64); dp_key(cid, key) 91 // ss_open_cached (seq905/962 class fix, seq1347 migration, 2026-07-30). ss_open reads every live 92 // segment into anon RAM and nothing frees it, so a per-request reader leaks the WHOLE store per 93 // request -- the fingerprint behind ~23.6 GiB across nine organs. SAFE HERE: open -> one ss_hget -> 94 // return, so the handle's shared query-scratch arena is never re-entered while held. Manifest 95 // (st_size, st_mtime) invalidation means live edits are still picked up on the next call. 96 let h: *i64 = ss_open_cached(prefix) 97 if (h as i64) == 0 { return 0 } 98 if ss_hget(h, key, ptrout, lenout) == 1 { return 1 } // normalize: ss_hget returns -1 (not 0) for not-found 99 return 0 100} 101 102// ================= OWNER USAGE POLICY (operator: lawyers/users flag how their uploads are managed) ================= 103// Per-doc flags, set by the owner at upload + amendable, stored in the doc's shard (pol:<cid>) ATOMICALLY with the 104// doc, and ENFORCED BY CONSTRUCTION: a consumer (the public indexer, the AI-blog writer) is only ever fed a doc whose 105// flag permits that use -- a non-permitted doc never enters the consumer's corpus, and consumers re-check dp_may LIVE 106// so a consent amendment takes effect at once. Conservative + defensible (the privilege research): private content is 107// never auto-published. Extensible bitset ("...or whatever etc"). 108 109func dp_default_policy(vis: i64) -> i64 { 110 if vis == DP_VIS_PRIVATE { return 0 } // private: nothing permitted by default (most restrictive) 111 return DP_USE_PUB_SEARCH // public: searchable; AI-blog is OPT-IN (owner must flag it) 112} 113func dp_polkey(cid: i64, out: *u8) -> i64 { var o: i64 = dp_cat(out, 0, "pol:" as *u8); o = dp_catn(out, o, cid); out[o] = 0 as u8; return o } 114 115// AI-blog-permitted corpus (DERIVED, policy-gated): knowledge/index/<domain>_blogsrc.tsv (cid<TAB>text). A doc enters 116// it ONLY if PUBLIC visibility AND carries DP_USE_AI_BLOG -- a private/unflagged doc is never here, so it can never 117// reach a published blog (private content is never auto-published). 118func dp_blogsrc(domain: *u8, out: *u8) -> i64 { 119 var o: i64 = dp_cat(out, 0, "knowledge/index/" as *u8); o = dp_cat(out, o, domain); o = dp_cat(out, o, "_blogsrc.tsv" as *u8); out[o] = 0 as u8; return o 120} 121func dp_append_blogsrc(domain: *u8, cid: i64, text: *u8, n: i64) -> i64 { 122 let path: *u8 = sys_mmap(512); dp_blogsrc(domain, path) 123 let fd: i64 = sys_openat_append(path, 0x1a4); if fd < 0 { return 0 - 1 } 124 let hdr: *u8 = sys_mmap(64); var ho: i64 = dp_catn(hdr, 0, cid); hdr[ho] = 9 as u8; ho = ho + 1; sys_write(fd, hdr, ho) 125 let san: *u8 = sys_mmap(n + 8); var i: i64 = 0 126 while i < n { var c: i64 = text[i] as i64; if c == 9 { c = 32 } if c == 10 { c = 32 } if c == 13 { c = 32 } san[i] = c as u8; i = i + 1 } 127 sys_write(fd, san, n); sys_write(fd, "\n" as *u8, 1); sys_close(fd); return 0 128} 129 130// INGEST with an explicit owner policy. Stores doc + policy ATOMICALLY (one commit, segid=CID); emits to the public 131// search source iff (PUBLIC & DP_USE_PUB_SEARCH); emits to the AI-blog corpus iff (PUBLIC & DP_USE_AI_BLOG). 132func dp_ingest_policy(domain: *u8, vis: i64, flags: i64, text_in: *u8, n_in: i64, cidout: *i64) -> i64 { 133 // ---- DERIVE CLEAN TEXT BEFORE THE CID IS TAKEN (2026-08-25) ------------------------------------ 134 // WAS: the raw uploaded bytes were hashed, stored AND tokenised, with no extraction step anywhere on 135 // this path. That is why nishifamily.com/search served titles like 136 // "HTTP/1.1 200 OK Date: Thu, 02 Jul 2026 17:07:07 GMT Content-Type: text/h" 137 // -- evidence-mirror captures reached the index with their wire bytes intact. 138 // It is ALSO why one page appears twice: dp_cid is FNV-1a over exactly these bytes, so two fetches of 139 // the same page differing only in a Date: header are two different documents with two different cids. 140 // Cleaning the text BEFORE dp_cid fixes BOTH: the duplicate collapses to one cid, and this function's 141 // existing identical-re-ingest idempotence then absorbs it. ORDER IS LOAD-BEARING: 142 // 1. strip the HTTP header block -- protocol-exact and anchored at byte 0. bd_fit_text does NOT do 143 // this: on unstructured input it self-degrades to nx_html_to_text, a tag stripper, which would 144 // leave the whole status line and every header sitting in the indexed text. 145 // 2. extract article text, but ONLY when the document really is html. dpr_is_html decides on a 146 // STRUCTURAL fact (a declared Content-Type, or a doctype/html/body marker), never on tag density, 147 // because this estate indexes its own source and a tag stripper would silently eat every `<u8>`. 148 let bstart: i64 = dpr_http_body(text_in, n_in) 149 let ishtml: i64 = dpr_is_html(text_in, n_in) 150 var text: *u8 = ((text_in as i64) + bstart) as *u8 151 var n: i64 = n_in - bstart 152 if ishtml == 1 { if n > 0 { 153 let fcap: i64 = n + 1 154 let fout: *u8 = sys_mmap(fcap) 155 let fm: i64 = bd_fit_text(text, n, fout, fcap) 156 // A fit that yields NOTHING is a fit that failed: keep the header-stripped original rather than 157 // index an empty document. Falling through silently here would turn a boilerplate-heavy page into 158 // a zero-byte doc that still gets a cid and still occupies a result slot. 159 if fm > 0 { text = fout; n = fm } 160 } } 161 let cid: i64 = dp_cid(text, n); cidout[0] = cid 162 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix) 163 let dkey: *u8 = sys_mmap(64); dp_key(cid, dkey) 164 let pkey: *u8 = sys_mmap(64); dp_polkey(cid, pkey) 165 let polbuf: *u8 = sys_mmap(32); let pl: i64 = dp_catn(polbuf, 0, flags) 166 let w: *i64 = ss_begin() 167 if ss_add(w, 1, dkey, text, n) < 0 { return 0 - 2 } 168 if ss_add(w, 1, pkey, polbuf, pl) < 0 { return 0 - 2 } 169 ss_commit(prefix, w, cid) 170 // NO derived search emission: the committed segment's .terms postings make the doc searchable at once, 171 // and the stored pol:<cid> row is what the search consumer (dss_search) checks LIVE for DP_USE_PUB_SEARCH. 172 if vis == DP_VIS_PUBLIC { 173 if (flags & DP_USE_AI_BLOG) != 0 { dp_append_blogsrc(domain, cid, text, n) } 174 } 175 return 0 176} 177 178// read a doc's stored policy flags (0 if absent) 179func dp_get_policy(domain: *u8, vis: i64, cid: i64) -> i64 { 180 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix) 181 let pkey: *u8 = sys_mmap(64); dp_polkey(cid, pkey) 182 let h: *i64 = ss_open_cached(prefix); if (h as i64) == 0 { return 0 } // seq1347: same class fix as dp_read 183 let pq: *i64 = sys_mmap(16) as *i64; let lq: *i64 = sys_mmap(16) as *i64 184 if ss_hget(h, pkey, pq, lq) != 1 { return 0 } 185 let p: *u8 = pq[0] as *u8; let l: i64 = lq[0] 186 var v: i64 = 0; var i: i64 = 0 187 while i < l { let c: i64 = p[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } 188 return v 189} 190 191// does the owner's policy permit `use_flag` for this doc? 1/0. The LIVE authoritative check every consumer makes. 192func dp_may(domain: *u8, vis: i64, cid: i64, use_flag: i64) -> i64 { 193 let f: i64 = dp_get_policy(domain, vis, cid) 194 if (f & use_flag) != 0 { return 1 } 195 return 0 196} 197 198// AMEND a doc's policy (owner changes consent). Re-commits the doc bytes (copied, unchanged) + the new flags with 199// segid=CID -- idempotent on the doc (bytes preserved, never-delete), policy updated. The LIVE policy is authoritative 200// immediately (consumers re-check dp_may), so withdrawing AI-blog consent excludes the doc at once. 0 ok, <0 absent. 201func dp_set_policy(domain: *u8, vis: i64, cid: i64, flags: i64) -> i64 { 202 let pq: *i64 = sys_mmap(16) as *i64; let lq: *i64 = sys_mmap(16) as *i64 203 if dp_read(domain, vis, cid, pq, lq) == 0 { return 0 - 1 } 204 let srclen: i64 = lq[0] 205 let copy: *u8 = sys_mmap(srclen + 8); let src: *u8 = pq[0] as *u8 206 var i: i64 = 0; while i < srclen { copy[i] = src[i]; i = i + 1 } 207 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix) 208 let dkey: *u8 = sys_mmap(64); dp_key(cid, dkey) 209 let pkey: *u8 = sys_mmap(64); dp_polkey(cid, pkey) 210 let polbuf: *u8 = sys_mmap(32); let pl: i64 = dp_catn(polbuf, 0, flags) 211 let w: *i64 = ss_begin() 212 ss_add(w, 1, dkey, copy, srclen) 213 ss_add(w, 1, pkey, polbuf, pl) 214 ss_commit(prefix, w, cid) 215 return 0 216}