code wiki / _hdl_build / nx_docportal_lib.nx
nx_docportal_lib.nx source
↩ module page · 186 lines · 11724 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"
21
22const DP_VIS_PUBLIC: i64 = 0
23const DP_VIS_PRIVATE: i64 = 1
24
25// ---- owner USAGE-POLICY flags (operator: owners flag how their uploads are managed; extensible -- "...or whatever") ----
26const DP_USE_PUB_SEARCH: i64 = 1 // index this doc for the public onsite search
27const DP_USE_AI_BLOG: i64 = 2 // an AI writer MAY publish blog posts derived from this content
28const DP_USE_AI_SUMMARY: i64 = 4 // AI may summarize/derive internally (non-publishing)
29const DP_USE_SHARE_EXT: i64 = 8 // may be shared outside the firm
30
31func dp_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
32func 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 }
33func dp_catn(out: *u8, o: i64, v: i64) -> i64 {
34 if v == 0 { out[o] = 48 as u8; return o + 1 }
35 var m: i64 = v
36 if m < 0 { out[o] = 45 as u8; o = o + 1; m = 0 - m }
37 let t: *u8 = sys_mmap(24); var k: i64 = 0
38 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
39 var i: i64 = 0; while i < k { out[o] = t[k - 1 - i]; o = o + 1; i = i + 1 }
40 return o
41}
42
43// deterministic content id (FNV-1a over the doc bytes, folded positive). Same bytes -> same CID.
44func dp_cid(text: *u8, n: i64) -> i64 {
45 var h: i64 = fnv1a_init()
46 h = fnv1a_update(h, text, n)
47 return h & 0x7fffffffffffffff
48}
49
50// store prefix for (domain, visibility): knowledge/store/dp-<domain>-pub- / knowledge/store/dp-<domain>-prv-
51func dp_prefix(domain: *u8, vis: i64, out: *u8) -> i64 {
52 var o: i64 = dp_cat(out, 0, "knowledge/store/dp-" as *u8)
53 o = dp_cat(out, o, domain)
54 if vis == DP_VIS_PRIVATE { o = dp_cat(out, o, "-prv-" as *u8) } else { o = dp_cat(out, o, "-pub-" as *u8) }
55 out[o] = 0 as u8
56 return o
57}
58
59// content-addressed doc key: doc:<cid>
60func dp_key(cid: i64, out: *u8) -> i64 {
61 var o: i64 = dp_cat(out, 0, "doc:" as *u8)
62 o = dp_catn(out, o, cid)
63 out[o] = 0 as u8
64 return o
65}
66
67// RETIRED (operator 2026-07-02, "no tsv -- nishi ecosystem hardware rung up"): dp_pubsrc/dp_append_pubsrc,
68// the derived knowledge/index/<domain>_src.tsv search-source emitter, are DELETED. The sovereign store IS the
69// search index (ss_write_seg builds .terms for every segment); nx_docportal_search_seg queries it directly and
70// enforces DP_USE_PUB_SEARCH consent LIVE per hit (pol:<cid>), so the emission-time filter has no job left.
71
72// INGEST one document into the domain's shard by visibility. Writes the raw bytes content-addressed into the
73// sovereign seg_store (immutable; segid=CID so distinct content never overwrites a prior doc, identical re-ingest
74// is idempotent). PUBLIC docs are ALSO emitted to the derived BM25 search source. A PRIVATE doc touches ONLY the
75// private store -- no public store write, no search-source line. Returns 0 ok; cidout[0] = content id.
76// INGEST with the DEFAULT owner policy (public: searchable, AI-blog opt-in OFF; private: nothing). Backward-
77// compatible wrapper -- existing callers get sensible, consent-respecting defaults; owners set explicit flags via
78// dp_ingest_policy (the admin handler passes the owner's choices). Returns 0 ok; cidout[0]=cid.
79func dp_ingest(domain: *u8, vis: i64, text: *u8, n: i64, cidout: *i64) -> i64 {
80 return dp_ingest_policy(domain, vis, dp_default_policy(vis), text, n, cidout)
81}
82
83// READ a doc back from the (domain, visibility) shard by cid, byte-exact. 1 = found (ptrout[0]/lenout[0] set).
84// Uses the PROVEN ss_open+ss_hget path (nx_raci_sov): an empty/absent shard -> ss_open returns 0 -> not found,
85// which is exactly the cross-shard isolation guarantee (a public/other-domain prefix with no segments answers 0).
86func dp_read(domain: *u8, vis: i64, cid: i64, ptrout: *i64, lenout: *i64) -> i64 {
87 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix)
88 let key: *u8 = sys_mmap(64); dp_key(cid, key)
89 // ss_open_cached (seq905/962 class fix, seq1347 migration, 2026-07-30). ss_open reads every live
90 // segment into anon RAM and nothing frees it, so a per-request reader leaks the WHOLE store per
91 // request -- the fingerprint behind ~23.6 GiB across nine organs. SAFE HERE: open -> one ss_hget ->
92 // return, so the handle's shared query-scratch arena is never re-entered while held. Manifest
93 // (st_size, st_mtime) invalidation means live edits are still picked up on the next call.
94 let h: *i64 = ss_open_cached(prefix)
95 if (h as i64) == 0 { return 0 }
96 if ss_hget(h, key, ptrout, lenout) == 1 { return 1 } // normalize: ss_hget returns -1 (not 0) for not-found
97 return 0
98}
99
100// ================= OWNER USAGE POLICY (operator: lawyers/users flag how their uploads are managed) =================
101// Per-doc flags, set by the owner at upload + amendable, stored in the doc's shard (pol:<cid>) ATOMICALLY with the
102// doc, and ENFORCED BY CONSTRUCTION: a consumer (the public indexer, the AI-blog writer) is only ever fed a doc whose
103// flag permits that use -- a non-permitted doc never enters the consumer's corpus, and consumers re-check dp_may LIVE
104// so a consent amendment takes effect at once. Conservative + defensible (the privilege research): private content is
105// never auto-published. Extensible bitset ("...or whatever etc").
106
107func dp_default_policy(vis: i64) -> i64 {
108 if vis == DP_VIS_PRIVATE { return 0 } // private: nothing permitted by default (most restrictive)
109 return DP_USE_PUB_SEARCH // public: searchable; AI-blog is OPT-IN (owner must flag it)
110}
111func 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 }
112
113// AI-blog-permitted corpus (DERIVED, policy-gated): knowledge/index/<domain>_blogsrc.tsv (cid<TAB>text). A doc enters
114// it ONLY if PUBLIC visibility AND carries DP_USE_AI_BLOG -- a private/unflagged doc is never here, so it can never
115// reach a published blog (private content is never auto-published).
116func dp_blogsrc(domain: *u8, out: *u8) -> i64 {
117 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
118}
119func dp_append_blogsrc(domain: *u8, cid: i64, text: *u8, n: i64) -> i64 {
120 let path: *u8 = sys_mmap(512); dp_blogsrc(domain, path)
121 let fd: i64 = sys_openat_append(path, 0x1a4); if fd < 0 { return 0 - 1 }
122 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)
123 let san: *u8 = sys_mmap(n + 8); var i: i64 = 0
124 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 }
125 sys_write(fd, san, n); sys_write(fd, "\n" as *u8, 1); sys_close(fd); return 0
126}
127
128// INGEST with an explicit owner policy. Stores doc + policy ATOMICALLY (one commit, segid=CID); emits to the public
129// search source iff (PUBLIC & DP_USE_PUB_SEARCH); emits to the AI-blog corpus iff (PUBLIC & DP_USE_AI_BLOG).
130func dp_ingest_policy(domain: *u8, vis: i64, flags: i64, text: *u8, n: i64, cidout: *i64) -> i64 {
131 let cid: i64 = dp_cid(text, n); cidout[0] = cid
132 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix)
133 let dkey: *u8 = sys_mmap(64); dp_key(cid, dkey)
134 let pkey: *u8 = sys_mmap(64); dp_polkey(cid, pkey)
135 let polbuf: *u8 = sys_mmap(32); let pl: i64 = dp_catn(polbuf, 0, flags)
136 let w: *i64 = ss_begin()
137 if ss_add(w, 1, dkey, text, n) < 0 { return 0 - 2 }
138 if ss_add(w, 1, pkey, polbuf, pl) < 0 { return 0 - 2 }
139 ss_commit(prefix, w, cid)
140 // NO derived search emission: the committed segment's .terms postings make the doc searchable at once,
141 // and the stored pol:<cid> row is what the search consumer (dss_search) checks LIVE for DP_USE_PUB_SEARCH.
142 if vis == DP_VIS_PUBLIC {
143 if (flags & DP_USE_AI_BLOG) != 0 { dp_append_blogsrc(domain, cid, text, n) }
144 }
145 return 0
146}
147
148// read a doc's stored policy flags (0 if absent)
149func dp_get_policy(domain: *u8, vis: i64, cid: i64) -> i64 {
150 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix)
151 let pkey: *u8 = sys_mmap(64); dp_polkey(cid, pkey)
152 let h: *i64 = ss_open_cached(prefix); if (h as i64) == 0 { return 0 } // seq1347: same class fix as dp_read
153 let pq: *i64 = sys_mmap(16) as *i64; let lq: *i64 = sys_mmap(16) as *i64
154 if ss_hget(h, pkey, pq, lq) != 1 { return 0 }
155 let p: *u8 = pq[0] as *u8; let l: i64 = lq[0]
156 var v: i64 = 0; var i: i64 = 0
157 while i < l { let c: i64 = p[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 }
158 return v
159}
160
161// does the owner's policy permit `use_flag` for this doc? 1/0. The LIVE authoritative check every consumer makes.
162func dp_may(domain: *u8, vis: i64, cid: i64, use_flag: i64) -> i64 {
163 let f: i64 = dp_get_policy(domain, vis, cid)
164 if (f & use_flag) != 0 { return 1 }
165 return 0
166}
167
168// AMEND a doc's policy (owner changes consent). Re-commits the doc bytes (copied, unchanged) + the new flags with
169// segid=CID -- idempotent on the doc (bytes preserved, never-delete), policy updated. The LIVE policy is authoritative
170// immediately (consumers re-check dp_may), so withdrawing AI-blog consent excludes the doc at once. 0 ok, <0 absent.
171func dp_set_policy(domain: *u8, vis: i64, cid: i64, flags: i64) -> i64 {
172 let pq: *i64 = sys_mmap(16) as *i64; let lq: *i64 = sys_mmap(16) as *i64
173 if dp_read(domain, vis, cid, pq, lq) == 0 { return 0 - 1 }
174 let srclen: i64 = lq[0]
175 let copy: *u8 = sys_mmap(srclen + 8); let src: *u8 = pq[0] as *u8
176 var i: i64 = 0; while i < srclen { copy[i] = src[i]; i = i + 1 }
177 let prefix: *u8 = sys_mmap(512); dp_prefix(domain, vis, prefix)
178 let dkey: *u8 = sys_mmap(64); dp_key(cid, dkey)
179 let pkey: *u8 = sys_mmap(64); dp_polkey(cid, pkey)
180 let polbuf: *u8 = sys_mmap(32); let pl: i64 = dp_catn(polbuf, 0, flags)
181 let w: *i64 = ss_begin()
182 ss_add(w, 1, dkey, copy, srclen)
183 ss_add(w, 1, pkey, polbuf, pl)
184 ss_commit(prefix, w, cid)
185 return 0
186}