nx_mvault.nx source
↩ module page · 670 lines · 26881 B
1// nx_mvault.nx -- the vault CLI + MCP tool. Exposes the gate-proven vault
2// bricks as a callable capability (classify / tag / measure) so it registers
3// as an MCP tool + /api endpoint + is drivable by agents.
4//
5// nx_mvault classify <path> <gen|real> <source> <ext>
6// -> {"cid","class","type","mls","dsm_path"} (JSON)
7// nx_mvault tag <namespace> <value> -> ns:value
8// nx_mvault measure <axis> <value> <leg> <conf> -> axis=value@leg#conf
9//
10// Composes nx_mvault_{ingest,context,layout,tag,measure} + nx_canon_cid +
11// nx_media_sniff. license_tier: ORIGINAL
12
13import "nx_syscalls.nx"
14import "nx_canon_cid.nx"
15import "nx_media_pool.nx"
16import "nx_media_sniff.nx"
17import "nx_mvault_ingest.nx"
18import "nx_mvault_context.nx"
19import "nx_mvault_layout.nx"
20import "nx_mvault_tag.nx"
21import "nx_mvault_measure.nx"
22import "nx_mvault_record.nx"
23import "nx_mvault_coll.nx"
24import "nx_mvault_fetch.nx"
25import "nx_https_fetch_lib.nx"
26import "nx_registry.nx"
27const K_MAGIC_65536: i64 = 65536
28const K_MAGIC_1024: i64 = 1024
29const K_MAGIC_2048: i64 = 2048
30const K_MAGIC_4096: i64 = 4096
31const K_MAGIC_4194304: i64 = 4194304
32
33const MV_HDR: i64 = 8192
34const MV_SEEK_END: i64 = 2
35
36func argp(argv: *i64, i: i64) -> *u8 { return (argv[i]) as *u8 }
37
38// Byte length of a file without reading it; -1 when unopenable.
39func mv_file_size(path: *u8) -> i64 {
40 let fd: i64 = sys_openat_rd(path)
41 if fd < 0 { return 0 - 1 }
42 let n: i64 = sys_lseek(fd, 0, MV_SEEK_END)
43 sys_close(fd)
44 return n
45}
46
47// Read up to `cap` HEADER bytes into buf; returns bytes read or -1.
48// Format identity lives in the first few hundred bytes, so a sniffer never
49// needs more -- this is what replaces the old whole-file read.
50func mv_read_head(path: *u8, buf: *u8, cap: i64) -> i64 {
51 let fd: i64 = sys_openat_rd(path)
52 if fd < 0 { return 0 - 1 }
53 var got: i64 = 0
54 var go: i64 = 1
55 while go == 1 {
56 if got >= cap { go = 0 }
57 if go == 1 {
58 let dst: *u8 = ((buf as i64) + got) as *u8
59 let r: i64 = sys_read(fd, dst, cap - got)
60 if r <= 0 { go = 0 }
61 if r > 0 { got = got + r }
62 }
63 }
64 sys_close(fd)
65 return got
66}
67
68func mv_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
69
70func seq(a: *u8, b: *u8) -> i64 {
71 var i: i64 = 0
72 while 1 == 1 {
73 if a[i] != b[i] { return 0 }
74 if a[i] == (0 as u8) { return 1 }
75 i = i + 1
76 }
77 return 0
78}
79
80func p_ch(dst: *u8, off: i64, c: i64) -> i64 { dst[off] = c as u8; return off + 1 }
81func p_cat(dst: *u8, off: i64, s: *u8) -> i64 {
82 var i: i64 = 0
83 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
84 return off + i
85}
86// emit "key":"val" (comma-prefixed unless first)
87func p_kv(dst: *u8, off: i64, key: *u8, val: *u8, first: i64) -> i64 {
88 var o: i64 = off
89 if first == 0 { o = p_ch(dst, o, 44) } // ,
90 o = p_ch(dst, o, 34); o = p_cat(dst, o, key); o = p_ch(dst, o, 34)
91 o = p_ch(dst, o, 58) // :
92 o = p_ch(dst, o, 34); o = p_cat(dst, o, val); o = p_ch(dst, o, 34)
93 return o
94}
95
96func mv_leg_parse(s: *u8) -> i64 {
97 let m: *u8 = "math" as *u8
98 let v: *u8 = "vlm" as *u8
99 let h: *u8 = "human" as *u8
100 let f: *u8 = "fused" as *u8
101 if seq(s, m) == 1 { return MV_LEG_MATH }
102 if seq(s, v) == 1 { return MV_LEG_VLM }
103 if seq(s, h) == 1 { return MV_LEG_HUMAN }
104 if seq(s, f) == 1 { return MV_LEG_FUSED }
105 return MV_LEG_VLM
106}
107
108func mv_atoi(s: *u8) -> i64 {
109 var n: i64 = 0
110 var i: i64 = 0
111 while s[i] != (0 as u8) {
112 let c: i64 = s[i]
113 if c >= 48 {
114 if c <= 57 { n = n * 10 + (c - 48) }
115 }
116 i = i + 1
117 }
118 return n
119}
120
121// contextual ctx from container magic + extension (pdf/epub -> book, cbz/cbr -> manga)
122func mv_ctx_from(b: *u8, n: i64, ext: *u8) -> i64 {
123 if mv_is_pdf(b, n) == 1 { return MV_CTX_PDF }
124 let e_epub: *u8 = "epub" as *u8
125 let e_cbz: *u8 = "cbz" as *u8
126 let e_cbr: *u8 = "cbr" as *u8
127 if mv_is_zip(b, n) == 1 {
128 if seq(ext, e_epub) == 1 { return MV_CTX_EPUB }
129 if seq(ext, e_cbz) == 1 { return MV_CTX_CBZ }
130 }
131 if mv_is_rar(b, n) == 1 {
132 if seq(ext, e_cbr) == 1 { return MV_CTX_CBR }
133 }
134 return MV_CTX_NONE
135}
136
137// Classify from a BOUNDED header window. Returns the vault type code, or -1
138// when the file cannot be read. out_size receives the true file size (the
139// binary-STL detector identifies purely by the size equation, so the sniffer
140// needs it) -- the single place where "what type is this item" is decided.
141func mv_type_of_path(path: *u8, ext: *u8, out_size: *i64) -> i64 {
142 let hdr: *u8 = sys_mmap(MV_HDR)
143 let hn: i64 = mv_read_head(path, hdr, MV_HDR)
144 if hn < 0 { return 0 - 1 }
145 let fsz: i64 = mv_file_size(path)
146 out_size[0] = fsz
147 let empty: *u8 = sys_mmap(1)
148 let mtype: i64 = nx_sniff_full(hdr, hn, fsz, empty, 0, path, mv_strlen(path))
149 return mv_context_type(mtype, mv_ctx_from(hdr, hn, ext))
150}
151
152// sniff = TYPE ONLY, no hashing. Reads a bounded header window and nothing
153// else, so triaging a 100k-item corpus costs one short read per file instead
154// of a full-content sha256.
155func do_sniff(argv: *i64) -> i64 {
156 let path: *u8 = argp(argv, 2)
157 var ext: *u8 = "" as *u8
158 if (argv[3]) != 0 { ext = argp(argv, 3) }
159 let szbox: *i64 = sys_mmap(8) as *i64
160 szbox[0] = 0
161 let vtype: i64 = mv_type_of_path(path, ext, szbox)
162 if vtype < 0 {
163 sys_write(2, "nx_mvault: cannot read file\n" as *u8, 28)
164 return 3
165 }
166 let k_path: *u8 = "path" as *u8
167 let k_typ: *u8 = "type" as *u8
168 let k_byt: *u8 = "bytes" as *u8
169 let buf: *u8 = sys_mmap(K_MAGIC_2048)
170 var o: i64 = 0
171 o = p_ch(buf, o, 123)
172 o = p_kv(buf, o, k_path, path, 1)
173 o = p_kv(buf, o, k_typ, mv_type_str(vtype), 0)
174 o = p_ch(buf, o, 44)
175 o = p_ch(buf, o, 34); o = p_cat(buf, o, k_byt); o = p_ch(buf, o, 34)
176 o = p_ch(buf, o, 58)
177 o = mv_u_dec(buf, o, szbox[0])
178 o = p_ch(buf, o, 125)
179 o = p_ch(buf, o, 10)
180 sys_write(1, buf, o)
181 return 0
182}
183
184func do_classify(argv: *i64) -> i64 {
185 let path: *u8 = argp(argv, 2)
186 let prov_s: *u8 = argp(argv, 3)
187 let source: *u8 = argp(argv, 4)
188 let ext: *u8 = argp(argv, 5)
189 let szbox: *i64 = sys_mmap(8) as *i64
190 szbox[0] = 0
191 let vtype: i64 = mv_type_of_path(path, ext, szbox)
192 if vtype < 0 {
193 sys_write(2, "nx_mvault: cannot read file\n" as *u8, 28)
194 return 3
195 }
196 let g: *u8 = "gen" as *u8
197 var prov: i64 = MV_PROV_REAL
198 if seq(prov_s, g) == 1 { prov = MV_PROV_GEN }
199 let cls: i64 = mv_class(prov)
200 // Streaming CID (nx_canon_cid): byte-identical to the whole-file hash at
201 // O(1) memory, so a multi-GB recording never loads whole.
202 let cid: *u8 = sys_mmap(80)
203 if cid_of_file_chunk(path, CID_FILE_CHUNK, szbox, cid) < 0 {
204 sys_write(2, "nx_mvault: cannot read file\n" as *u8, 28)
205 return 3
206 }
207 let root: *u8 = "/volume1/vault" as *u8
208 let dpath: *u8 = sys_mmap(K_MAGIC_1024)
209 mv_disk_path(dpath, root, cls, vtype, cid, source, ext)
210
211 let k_cid: *u8 = "cid" as *u8
212 let k_cls: *u8 = "class" as *u8
213 let k_typ: *u8 = "type" as *u8
214 let k_mls: *u8 = "mls" as *u8
215 let k_path: *u8 = "dsm_path" as *u8
216 let v_ts: *u8 = "TS" as *u8
217 let buf: *u8 = sys_mmap(K_MAGIC_2048)
218 var o: i64 = 0
219 o = p_ch(buf, o, 123) // {
220 o = p_kv(buf, o, k_cid, cid, 1)
221 o = p_kv(buf, o, k_cls, mv_class_str(cls), 0)
222 o = p_kv(buf, o, k_typ, mv_type_str(vtype), 0)
223 o = p_kv(buf, o, k_mls, v_ts, 0)
224 o = p_kv(buf, o, k_path, dpath, 0)
225 o = p_ch(buf, o, 125) // }
226 o = p_ch(buf, o, 10) // \n
227 sys_write(1, buf, o)
228 return 0
229}
230
231// ===== retype backfill =====================================================
232// Fixing the classifier does NOT fix records already written. The blobs were
233// never moved (record-only migration), so the original bytes are unreachable
234// from a record -- but the ref path carries the file EXTENSION, which is a
235// real sniffer tier and the one that resolves `.ts` -> video. Re-derive and
236// rewrite ONLY records whose type actually changed. DRY-RUN BY DEFAULT.
237
238const MV_FLDCAP: i64 = 1024
239const MV_BATCH: i64 = 16777216 // 16 MiB per commit batch; flushed, never truncated
240
241func mv_fld_eq(p: *u8, n: i64, s: *u8) -> i64 {
242 var i: i64 = 0
243 while i < n {
244 if s[i] == (0 as u8) { return 0 }
245 if p[i] != s[i] { return 0 }
246 i = i + 1
247 }
248 if s[n] != (0 as u8) { return 0 }
249 return 1
250}
251
252func mv_fld_cpy(dst: *u8, p: *u8, n: i64, cap: i64) -> i64 {
253 var m: i64 = n
254 if m > cap - 1 { m = cap - 1 }
255 var i: i64 = 0
256 while i < m { dst[i] = p[i]; i = i + 1 }
257 dst[m] = 0 as u8
258 return m
259}
260
261func do_retype(argv: *i64) -> i64 {
262 var commit: i64 = 0
263 var off0: i64 = 0
264 var lim: i64 = 0
265 if (argv[2]) != 0 {
266 let c: *u8 = "commit" as *u8
267 if seq(argp(argv, 2), c) == 1 { commit = 1 }
268 }
269 // BOUNDED WINDOW, declared not silent: reg_get sys_mmap's per call and
270 // never frees, so 37k lookups in ONE process exhausts it (measured: rc=1,
271 // zero output). Chunking bounds the mappings to one process-lifetime.
272 if (argv[3]) != 0 { off0 = mv_atoi(argp(argv, 3)) }
273 if (argv[4]) != 0 { lim = mv_atoi(argp(argv, 4)) }
274 let store: *u8 = "knowledge/store/mvault-" as *u8
275 // SEQUENTIAL SCAN, not point lookups: ss_get costs O(whole-store bytes)
276 // per call (it ss_readall's every live segment and returns a pointer INTO
277 // that mapping), so the point-lookup build died at 10k records. ss_cur_*
278 // maps each segment ONCE.
279 let st: *i64 = ss_cur_open(store)
280 let kout: *i64 = sys_mmap(16) as *i64
281 let klout: *i64 = sys_mmap(16) as *i64
282 let vout: *i64 = sys_mmap(16) as *i64
283 let vlout: *i64 = sys_mmap(16) as *i64
284 let fp: *i64 = sys_mmap(16) as *i64
285 let fl: *i64 = sys_mmap(16) as *i64
286 let cidb: *u8 = sys_mmap(128)
287 let refbuf: *u8 = sys_mmap(MV_FLDCAP)
288 let srcbuf: *u8 = sys_mmap(MV_FLDCAP)
289 let newref: *u8 = sys_mmap(MV_FLDCAP)
290 let rec2: *u8 = sys_mmap(K_MAGIC_4096)
291 let extb: *u8 = sys_mmap(64)
292 let root: *u8 = "/volume1/vault" as *u8
293 let k_ref: *u8 = "ref" as *u8
294 let k_typ: *u8 = "type" as *u8
295 let k_cls: *u8 = "class" as *u8
296 let k_src: *u8 = "source" as *u8
297 let s_gen: *u8 = "gen" as *u8
298 // BATCHED WRITE, not reg_put per record: reg_put mints a NEW SEGMENT and
299 // rewrites the whole id-index every call, so 17k corrections would be 17k
300 // segments. One ss_begin -> many ss_add2 -> one ss_commit instead. The
301 // id-index is deliberately untouched: retype only UPDATES existing keys,
302 // never introduces one, so enumeration is unchanged.
303 var w: *i64 = 0 as *i64
304 if commit == 1 { w = ss_begin_cap(MV_BATCH) }
305 var seen: i64 = 0
306 var changed: i64 = 0
307 var processed: i64 = 0
308 var more: i64 = 1
309 while more == 1 {
310 if ss_cur_next(store, st, kout, klout, vout, vlout) == 0 { more = 0 }
311 var isrec: i64 = 0
312 if more == 1 {
313 if st[6] == 1 {
314 let kb0: *u8 = (kout[0]) as *u8
315 let kl0: i64 = klout[0]
316 if kl0 > 3 {
317 if kb0[0] == (109 as u8) {
318 if kb0[1] == (118 as u8) {
319 if kb0[2] == (58 as u8) { isrec = 1 }
320 }
321 }
322 }
323 if kl0 == 6 { isrec = 0 }
324 }
325 }
326 if isrec == 1 { seen = seen + 1 }
327 var inwin: i64 = 0
328 if isrec == 1 {
329 if seen > off0 {
330 inwin = 1
331 if lim > 0 { if processed >= lim { inwin = 0 } }
332 }
333 }
334 if inwin == 1 {
335 processed = processed + 1
336 let kb: *u8 = (kout[0]) as *u8
337 let kl: i64 = klout[0]
338 let rec: *u8 = (vout[0]) as *u8
339 let rl: i64 = vlout[0]
340 if mv_rec_field(rec, rl, k_ref, 3, fp, fl) == 1 {
341 let refl: i64 = mv_fld_cpy(refbuf, (fp[0]) as *u8, fl[0], MV_FLDCAP)
342 let mt: i64 = nx_sniff_extension(refbuf, refl)
343 let nt: i64 = mv_type(MV_HINT_NONE, mt)
344 let nts: *u8 = mv_type_str(nt)
345 var differs: i64 = 0
346 if mv_rec_field(rec, rl, k_typ, 4, fp, fl) == 1 {
347 if mv_fld_eq((fp[0]) as *u8, fl[0], nts) == 0 { differs = 1 }
348 }
349 if nt == MV_TYPE_UNKNOWN { differs = 0 }
350 if differs == 1 {
351 changed = changed + 1
352 if commit == 1 {
353 mv_fld_cpy(cidb, ((kb as i64) + 3) as *u8, kl - 3, 128)
354 var cls: i64 = MV_CLASS_REAL
355 if mv_rec_field(rec, rl, k_cls, 5, fp, fl) == 1 {
356 if mv_fld_eq((fp[0]) as *u8, fl[0], s_gen) == 1 { cls = MV_CLASS_GEN }
357 }
358 srcbuf[0] = 0 as u8
359 if mv_rec_field(rec, rl, k_src, 6, fp, fl) == 1 {
360 mv_fld_cpy(srcbuf, (fp[0]) as *u8, fl[0], MV_FLDCAP)
361 }
362 let eoff: i64 = nx_sniff_ext_off(refbuf, refl)
363 extb[0] = 0 as u8
364 if eoff >= 0 { mv_fld_cpy(extb, ((refbuf as i64) + eoff) as *u8, refl - eoff, 64) }
365 mv_disk_path(newref, root, cls, nt, cidb, srcbuf, extb)
366 let rl2: i64 = mv_record_build(rec2, cls, nt, MV_LVL_TS, srcbuf, newref)
367 if ss_add2(w, 1, kb, kl, rec2, rl2) < 0 {
368 ss_commit(store, w, ss_next_segid(store))
369 w = ss_begin_cap(MV_BATCH)
370 ss_add2(w, 1, kb, kl, rec2, rl2)
371 }
372 }
373 }
374 }
375 }
376 }
377 if commit == 1 {
378 if w[1] > 0 { ss_commit(store, w, ss_next_segid(store)) }
379 }
380 let k_seen: *u8 = "seen" as *u8
381 let k_chg: *u8 = "retyped" as *u8
382 let k_mode: *u8 = "mode" as *u8
383 let k_proc: *u8 = "processed" as *u8
384 let k_next: *u8 = "next" as *u8
385 let v_dry: *u8 = "dry" as *u8
386 let v_com: *u8 = "commit" as *u8
387 let out: *u8 = sys_mmap(512)
388 var o: i64 = 0
389 o = p_ch(out, o, 123)
390 o = p_ch(out, o, 34); o = p_cat(out, o, k_seen); o = p_ch(out, o, 34); o = p_ch(out, o, 58)
391 o = mv_u_dec(out, o, seen)
392 o = p_ch(out, o, 44)
393 o = p_ch(out, o, 34); o = p_cat(out, o, k_chg); o = p_ch(out, o, 34); o = p_ch(out, o, 58)
394 o = mv_u_dec(out, o, changed)
395 o = p_ch(out, o, 44)
396 o = p_ch(out, o, 34); o = p_cat(out, o, k_proc); o = p_ch(out, o, 34); o = p_ch(out, o, 58)
397 o = mv_u_dec(out, o, processed)
398 o = p_ch(out, o, 44)
399 o = p_ch(out, o, 34); o = p_cat(out, o, k_next); o = p_ch(out, o, 34); o = p_ch(out, o, 58)
400 o = mv_u_dec(out, o, off0 + processed)
401 if commit == 1 { o = p_kv(out, o, k_mode, v_com, 0) } else { o = p_kv(out, o, k_mode, v_dry, 0) }
402 o = p_ch(out, o, 125)
403 o = p_ch(out, o, 10)
404 sys_write(1, out, o)
405 return 0
406}
407
408// record = classify + write the NXR1 record to the sovereign seg-store (additive).
409func do_record(argv: *i64) -> i64 {
410 let path: *u8 = argp(argv, 2)
411 let prov_s: *u8 = argp(argv, 3)
412 let source: *u8 = argp(argv, 4)
413 let ext: *u8 = argp(argv, 5)
414 let szbox: *i64 = sys_mmap(8) as *i64
415 szbox[0] = 0
416 let vtype: i64 = mv_type_of_path(path, ext, szbox)
417 if vtype < 0 { sys_write(2, "nx_mvault: cannot read file\n" as *u8, 28); return 3 }
418 let g: *u8 = "gen" as *u8
419 var prov: i64 = MV_PROV_REAL
420 if seq(prov_s, g) == 1 { prov = MV_PROV_GEN }
421 let cls: i64 = mv_class(prov)
422 let cid: *u8 = sys_mmap(80)
423 if cid_of_file_chunk(path, CID_FILE_CHUNK, szbox, cid) < 0 { sys_write(2, "nx_mvault: cannot read file\n" as *u8, 28); return 3 }
424 let root: *u8 = "/volume1/vault" as *u8
425 let dpath: *u8 = sys_mmap(K_MAGIC_1024)
426 mv_disk_path(dpath, root, cls, vtype, cid, source, ext)
427 let store: *u8 = "knowledge/store/mvault-" as *u8
428 let rec: *u8 = sys_mmap(K_MAGIC_4096)
429 let rl: i64 = mv_record_build(rec, cls, vtype, MV_LVL_TS, source, dpath)
430 let rc: i64 = mv_store_put(store, cid, rec, rl)
431 let k_cid: *u8 = "cid" as *u8
432 let k_typ: *u8 = "type" as *u8
433 let k_rec: *u8 = "recorded" as *u8
434 let v_ok: *u8 = "1" as *u8
435 let buf: *u8 = sys_mmap(K_MAGIC_2048)
436 var o: i64 = 0
437 o = p_ch(buf, o, 123)
438 o = p_kv(buf, o, k_cid, cid, 1)
439 o = p_kv(buf, o, k_typ, mv_type_str(vtype), 0)
440 o = p_kv(buf, o, k_rec, v_ok, 0)
441 o = p_ch(buf, o, 125)
442 o = p_ch(buf, o, 10)
443 sys_write(1, buf, o)
444 if rc < 0 { return 4 }
445 return 0
446}
447
448// ---- ALBUM INGEST ---------------------------------------------------------
449// Exposes the album downloader so it is actually RUNNABLE (and therefore
450// callable over MCP). A capability with no verb is a capability nobody can use.
451func do_fetch(argc: i64, argv: *i64) -> i64 {
452 if argc < 3 {
453 sys_write(2, "usage: nx_mvault fetch <adapter|album|adapters>\n" as *u8, 47)
454 return 2
455 }
456 let sub: *u8 = argp(argv, 2)
457
458 if seq(sub, "adapter" as *u8) == 1 {
459 if argc < 9 { sys_write(2, "usage: nx_mvault fetch adapter <host> <link_tag> <link_attr> <link_pfx> <item_tag> <item_attr> [ua]\n" as *u8, 98); return 2 }
460 var ua: *u8 = "nishi" as *u8
461 if argc > 9 { ua = argp(argv, 9) }
462 // an empty item_tag means the album links ARE the media (one hop)
463 var itag: *u8 = argp(argv, 7)
464 if seq(itag, "-" as *u8) == 1 { itag = "" as *u8 }
465 let rc: i64 = mvf_adapter_put(argp(argv,3), argp(argv,4), argp(argv,5), argp(argv,6), itag, argp(argv,8), ua)
466 if rc < 0 { sys_write(2, "adapter put failed\n" as *u8, 19); return 4 }
467 sys_write(1, "{\"adapter\":\"" as *u8, 12)
468 sys_write(1, argp(argv,3), mv_strlen(argp(argv,3)))
469 sys_write(1, "\"}\n" as *u8, 3)
470 return 0
471 }
472 if seq(sub, "adapters" as *u8) == 1 {
473 let out: *u8 = sys_mmap(K_MAGIC_4194304)
474 let n: i64 = mvf_adapter_list(out, K_MAGIC_4194304)
475 if n < 0 { sys_write(2, "REFUSED: adapter index does not fit\n" as *u8, 36); return 4 }
476 sys_write(1, out, n)
477 return 0
478 }
479 if seq(sub, "album" as *u8) == 1 {
480 if argc < 5 { sys_write(2, "usage: nx_mvault fetch album <album-url> <dest-dir>\n" as *u8, 51); return 2 }
481 let store: i64 = hf_store_load()
482 if store <= 0 { sys_write(2, "ERR: trust store load failed (data/mozilla_certdata.txt on CWD?)\n" as *u8, 64); return 3 }
483 let rep: *u8 = sys_mmap(K_MAGIC_65536)
484 let got: i64 = mvf_fetch_album(store, argp(argv,3), argp(argv,4), rep, K_MAGIC_65536)
485 if got == (0-1) { sys_write(2, "REFUSED: no adapter for that host -- declare one first\n" as *u8, 54); return 5 }
486 if got == (0-2) { sys_write(2, "ERR: album page fetch failed\n" as *u8, 29); return 6 }
487 if got == (0-3) { sys_write(2, "ERR: no item links matched the adapter rule\n" as *u8, 44); return 7 }
488 sys_write(1, rep, mv_strlen(rep))
489 return 0
490 }
491 sys_write(2, "nx_mvault fetch: unknown subcommand\n" as *u8, 36)
492 return 2
493}
494
495// ---- COLLECTION AXIS (site / album / source grouping) ----------------------
496// Makes nx_mvault_coll operable from the CLI and therefore from MCP. A plane
497// with no verb is a plane nobody can use: the 07-30 tag plane shaped keys that
498// nothing could ever write or read, which is exactly how it stayed dark.
499func do_coll(argc: i64, argv: *i64) -> i64 {
500 if argc < 3 {
501 sys_write(2, "usage: nx_mvault coll <declare|add|get|items|of|list|count>\n" as *u8, 60)
502 return 2
503 }
504 let sub: *u8 = argp(argv, 2)
505 let out: *u8 = sys_mmap(K_MAGIC_4194304)
506
507 if seq(sub, "declare" as *u8) == 1 {
508 if argc < 7 { sys_write(2, "usage: nx_mvault coll declare <kind> <site> <key> <name> [url] [parent]\n" as *u8, 71); return 2 }
509 let cid: *u8 = sys_mmap(256)
510 var url: *u8 = "-" as *u8
511 var par: *u8 = "-" as *u8
512 if argc > 7 { url = argp(argv, 7) }
513 if argc > 8 { par = argp(argv, 8) }
514 mvc_declare(argp(argv,3), argp(argv,4), argp(argv,5), argp(argv,6), url, par, cid)
515 sys_write(1, cid, mv_strlen(cid))
516 sys_write(1, "\n" as *u8, 1)
517 return 0
518 }
519 if seq(sub, "add" as *u8) == 1 {
520 if argc < 5 { sys_write(2, "usage: nx_mvault coll add <colid> <item-cid>\n" as *u8, 44); return 2 }
521 let item: *u8 = argp(argv, 4)
522 let buf: *u8 = sys_mmap(K_MAGIC_1024)
523 var o: i64 = 0
524 var i: i64 = 0
525 while item[i] != (0 as u8) { buf[o] = item[i]; o = o + 1; i = i + 1 }
526 buf[o] = 10 as u8; o = o + 1
527 let n: i64 = mvc_batch_add(argp(argv,3), buf, o)
528 sys_write(1, "{\"linked\":" as *u8, 10)
529 let d: *u8 = sys_mmap(32)
530 let dl: i64 = mv_u_dec(d, 0, n)
531 sys_write(1, d, dl)
532 sys_write(1, "}\n" as *u8, 2)
533 return 0
534 }
535 if seq(sub, "items" as *u8) == 1 {
536 if argc < 4 { sys_write(2, "usage: nx_mvault coll items <colid>\n" as *u8, 36); return 2 }
537 let n: i64 = mvc_items(argp(argv,3), out, K_MAGIC_4194304)
538 if n < 0 { sys_write(2, "REFUSED: collection does not fit the buffer\n" as *u8, 43); return 4 }
539 sys_write(1, out, n)
540 return 0
541 }
542 if seq(sub, "of" as *u8) == 1 {
543 if argc < 4 { sys_write(2, "usage: nx_mvault coll of <item-cid>\n" as *u8, 36); return 2 }
544 let n: i64 = mvc_of_item(argp(argv,3), out, K_MAGIC_4194304)
545 if n < 0 { sys_write(2, "REFUSED: does not fit the buffer\n" as *u8, 33); return 4 }
546 sys_write(1, out, n)
547 return 0
548 }
549 if seq(sub, "count" as *u8) == 1 {
550 if argc < 4 { sys_write(2, "usage: nx_mvault coll count <colid>\n" as *u8, 36); return 2 }
551 let c: i64 = mvc_count(argp(argv,3))
552 let d: *u8 = sys_mmap(32)
553 let dl: i64 = mv_u_dec(d, 0, c)
554 sys_write(1, "{\"items\":" as *u8, 9)
555 sys_write(1, d, dl)
556 sys_write(1, "}\n" as *u8, 2)
557 return 0
558 }
559 if seq(sub, "get" as *u8) == 1 {
560 if argc < 4 { sys_write(2, "usage: nx_mvault coll get <colid>\n" as *u8, 34); return 2 }
561 let pp: *i64 = sys_mmap(16) as *i64
562 let ll: *i64 = sys_mmap(16) as *i64
563 mvc_get(argp(argv,3), pp, ll)
564 if ll[0] <= 0 { sys_write(2, "REFUSED: collection not found\n" as *u8, 30); return 4 }
565 sys_write(1, pp[0] as *u8, ll[0])
566 sys_write(1, "\n" as *u8, 1)
567 return 0
568 }
569 if seq(sub, "list" as *u8) == 1 {
570 let n: i64 = mvc_list(out, K_MAGIC_4194304)
571 if n < 0 { sys_write(2, "REFUSED: index does not fit the buffer\n" as *u8, 38); return 4 }
572 sys_write(1, out, n)
573 return 0
574 }
575 sys_write(2, "nx_mvault coll: unknown subcommand\n" as *u8, 35)
576 return 2
577}
578
579// stats = count of items on the vault seg-store (the id index).
580func do_stats(argv: *i64) -> i64 {
581 let store: *u8 = "knowledge/store/mvault-" as *u8
582 let idx: *u8 = "mv:ids" as *u8
583 // ZERO-COPY (2026-07-31): this used to copy the whole id index into a fixed
584 // 4 MiB mapping. The live index passed 10 MiB, so the copy ran ~7 MiB off
585 // the end and this ruler died SILENTLY -- `nx_mvault stats` printed nothing
586 // at all, which reads as "no answer" rather than "I crashed". Counting the
587 // newlines directly in the store's own mapping needs no buffer, so the
588 // failure mode is gone by construction rather than pushed to a bigger cap.
589 let ipo: *i64 = sys_mmap(16) as *i64
590 let ilo: *i64 = sys_mmap(16) as *i64
591 var count: i64 = 0
592 if reg_index_open(store, idx, ipo, ilo) == 1 {
593 let buf: *u8 = ipo[0] as *u8
594 let ln: i64 = ilo[0]
595 var i: i64 = 0
596 while i < ln {
597 if buf[i] == (10 as u8) { count = count + 1 }
598 i = i + 1
599 }
600 }
601 let k_items: *u8 = "items" as *u8
602 let out: *u8 = sys_mmap(256)
603 var o: i64 = 0
604 o = p_ch(out, o, 123)
605 o = p_ch(out, o, 34); o = p_cat(out, o, k_items); o = p_ch(out, o, 34); o = p_ch(out, o, 58)
606 o = mv_u_dec(out, o, count)
607 o = p_ch(out, o, 125)
608 o = p_ch(out, o, 10)
609 sys_write(1, out, o)
610 return 0
611}
612
613func main(argc: i64, argv: *i64) -> i64 {
614 if argc < 2 {
615 sys_write(2, "usage: nx_mvault <sniff|classify|record|stats|tag|measure>\n" as *u8, 59)
616 return 2
617 }
618 let cmd: *u8 = argp(argv, 1)
619 let c_classify: *u8 = "classify" as *u8
620 let c_tag: *u8 = "tag" as *u8
621 let c_measure: *u8 = "measure" as *u8
622
623 if seq(cmd, c_classify) == 1 {
624 if argc < 6 {
625 sys_write(2, "usage: nx_mvault classify <path> <gen|real> <source> <ext>\n" as *u8, 59)
626 return 2
627 }
628 return do_classify(argv)
629 }
630 if seq(cmd, c_tag) == 1 {
631 if argc < 4 { sys_write(2, "usage: nx_mvault tag <ns> <value>\n" as *u8, 34); return 2 }
632 let out: *u8 = sys_mmap(512)
633 mv_tag_make(out, argp(argv, 2), argp(argv, 3))
634 let ln: i64 = mv_strlen(out)
635 sys_write(1, out, ln)
636 sys_write(1, "\n" as *u8, 1)
637 return 0
638 }
639 if seq(cmd, c_measure) == 1 {
640 if argc < 6 { sys_write(2, "usage: nx_mvault measure <axis> <value> <leg> <conf>\n" as *u8, 53); return 2 }
641 let out: *u8 = sys_mmap(512)
642 let leg: i64 = mv_leg_parse(argp(argv, 4))
643 let conf: i64 = mv_atoi(argp(argv, 5))
644 mv_meas_make(out, argp(argv, 2), argp(argv, 3), leg, conf)
645 let ln: i64 = mv_strlen(out)
646 sys_write(1, out, ln)
647 sys_write(1, "\n" as *u8, 1)
648 return 0
649 }
650 let c_record: *u8 = "record" as *u8
651 if seq(cmd, c_record) == 1 {
652 if argc < 6 { sys_write(2, "usage: nx_mvault record <path> <gen|real> <source> <ext>\n" as *u8, 57); return 2 }
653 return do_record(argv)
654 }
655 let c_sniff: *u8 = "sniff" as *u8
656 if seq(cmd, c_sniff) == 1 {
657 if argc < 3 { sys_write(2, "usage: nx_mvault sniff <path> [ext]\n" as *u8, 36); return 2 }
658 return do_sniff(argv)
659 }
660 let c_coll: *u8 = "coll" as *u8
661 if seq(cmd, c_coll) == 1 { return do_coll(argc, argv) }
662 let c_fetch: *u8 = "fetch" as *u8
663 if seq(cmd, c_fetch) == 1 { return do_fetch(argc, argv) }
664 let c_retype: *u8 = "retype" as *u8
665 if seq(cmd, c_retype) == 1 { return do_retype(argv) }
666 let c_stats: *u8 = "stats" as *u8
667 if seq(cmd, c_stats) == 1 { return do_stats(argv) }
668 sys_write(2, "nx_mvault: unknown command\n" as *u8, 27)
669 return 2
670}