nx_artifact_store.nx source
↩ module page · 434 lines · 18401 B
1// nx_artifact_store.nx -- pipeline-aware artifact metadata store.
2//
3// Extension of NxWikiDocStore (V1 SHIPPED at 08e67598) with pipeline
4// stage + V5 living-docs taxonomy fields (per V2_0_PIPELINE_BOOTSTRAP_ARC_PLAN §2.0).
5//
6// COMPOSES (zero new tokenization / parse / hash code):
7// wiki/nx_wiki_index_builder (NxWikiDocStore base)
8// nx_search_inverted (nx_inv_hash_bytes_lower for path-hash)
9//
10// COMPOSED BY:
11// wiki/nx_pipeline_walker.nx (P-1; populates this store)
12// wiki/nx_pipeline_route.nx (P-3; renders this store as /wiki/pipeline)
13//
14// Per operator cardinal 2026-05-27 "from zero everything nishi": pure
15// NishiLang; composes existing audit primitives (nx_doc_xref D22 +
16// nx_doc_census D1); zero bash + sed.
17//
18// V2.0 SCOPE:
19// - Parallel-pool storage per artifact (path / kind / pipeline_stage /
20// quadrant / topic_type / status / trust_state / competitive_state /
21// last_commit_hash / line_count)
22// - Insert + lookup by path-hash (FNV-1a 64 via existing primitive)
23// - Iterate by stage + by kind (filtered scans)
24//
25// Status: V2.0. 2026-05-27.
26
27import "nx_syscalls.nx"
28import "nx_search_inverted.nx"
29
30// ===== Sealed verdict surface (codes 2900-2919; V2.0 reserved) =================================================
31const NX_ART_OK: i64 = 0
32const NX_ART_BAD_INPUT: i64 = 2900
33const NX_ART_STORE_FULL: i64 = 2901
34const NX_ART_POOL_OVERFLOW: i64 = 2902
35const NX_ART_NOT_FOUND: i64 = 2903
36const NX_ART_DUPLICATE_PATH: i64 = 2904
37const NX_ART_BAD_STAGE: i64 = 2905
38
39// ===== Sealed pipeline stage enum (per NISHI_INTELLIGENT_WIKI_CHARTER §1) =================================================
40const NX_ART_STAGE_UNSTRUCTURED: i64 = 0
41const NX_ART_STAGE_STRUCTURED: i64 = 1
42const NX_ART_STAGE_MEANINGFUL: i64 = 2
43const NX_ART_STAGE_ACTIONABLE: i64 = 3
44const NX_ART_STAGE_REPEATABLE: i64 = 4
45const NX_ART_STAGE_N: i64 = 5
46
47// ===== Sealed kind enum (per V2.0 PLAN §1 inventory) =================================================
48const NX_ART_KIND_UNKNOWN: i64 = 0
49const NX_ART_KIND_RUNTIME_NX: i64 = 1 // nxc2/runtime/*.nx
50const NX_ART_KIND_CHARTER_MD: i64 = 2 // nishi-silicon/*.md
51const NX_ART_KIND_BENCH_SH: i64 = 3 // nxc2/bench/*.sh
52const NX_ART_KIND_RFC_MD: i64 = 4 // nishi-silicon/RFC*.md or *_DOCTRINE_*.md
53const NX_ART_KIND_MEMORY_MD: i64 = 5 // memory/*.md (operator-private)
54const NX_ART_KIND_DASHBOARD_MD: i64 = 6 // nxc2/docs/dashboards/*.md
55const NX_ART_KIND_SILICON_ARC: i64 = 7 // nishi-silicon/hdl/*.md or *.nx
56const NX_ART_KIND_TEST_NX: i64 = 8 // *_test.nx
57const NX_ART_KIND_OTHER: i64 = 9
58const NX_ART_KIND_N: i64 = 10
59
60// ===== Sealed Diátaxis quadrant (per nxc2 V5 living-docs taxonomy) =================================================
61const NX_ART_QUAD_NONE: i64 = 0 // missing front-matter
62const NX_ART_QUAD_TUTORIAL: i64 = 1
63const NX_ART_QUAD_HOWTO: i64 = 2
64const NX_ART_QUAD_REFERENCE: i64 = 3
65const NX_ART_QUAD_EXPLANATION: i64 = 4
66const NX_ART_QUAD_N: i64 = 5
67
68// ===== Sealed DITA topic type =================================================
69const NX_ART_DITA_NONE: i64 = 0
70const NX_ART_DITA_TASK: i64 = 1
71const NX_ART_DITA_CONCEPT: i64 = 2
72const NX_ART_DITA_REFERENCE: i64 = 3
73const NX_ART_DITA_GLOSSARY: i64 = 4
74const NX_ART_DITA_N: i64 = 5
75
76// ===== Sealed status (substrate-defined) =================================================
77const NX_ART_STATUS_NONE: i64 = 0
78const NX_ART_STATUS_CANONICAL: i64 = 1
79const NX_ART_STATUS_WIP: i64 = 2
80const NX_ART_STATUS_DEPRECATED: i64 = 3
81const NX_ART_STATUS_N: i64 = 4
82
83// ===== Sealed Trust State (substrate-defined V5 provenance tier) =================================================
84const NX_ART_TRUST_NONE: i64 = 0
85const NX_ART_TRUST_WRITTEN_UNTESTED: i64 = 1
86const NX_ART_TRUST_WEBSEARCH_VERIFIED: i64 = 2
87const NX_ART_TRUST_EXPERT_VERIFIED: i64 = 3
88const NX_ART_TRUST_PERSONALLY_MEASURED: i64 = 4
89const NX_ART_TRUST_N: i64 = 5
90
91// ===== Sealed Competitive State (substrate-defined V5 benchmark spec) =================================================
92const NX_ART_COMP_NONE: i64 = 0
93const NX_ART_COMP_UNCLASSIFIED: i64 = 1
94const NX_ART_COMP_UNDER: i64 = 2
95const NX_ART_COMP_MATCHES: i64 = 3
96const NX_ART_COMP_BEATS: i64 = 4
97const NX_ART_COMP_N: i64 = 5
98
99// ===== Named sizing constants (M7) =================================================
100const NX_ART_DEFAULT_CAP: i64 = 1000 // V2.0 bounded; covers ~230 known artifacts + headroom
101const NX_ART_HARD_CAP: i64 = 10000
102const NX_ART_PATH_POOL_CAP: i64 = 524288 // 512 KB packed paths
103const NX_ART_HASH_POOL_CAP: i64 = 524288 // 512 KB packed commit hashes
104const NX_ART_MAX_PATH_LEN: i64 = 512
105const NX_ART_LOOP_BUDGET_CAP: i64 = 10000000
106
107// ===== NxArtifactStore: parallel pools =================================================
108
109struct NxArtifactStore {
110 // Path identification
111 paths_pool: *u8 // packed UTF-8 paths
112 paths_pool_cap: i64
113 paths_pool_used: i64
114 paths_offs: *i64 // per-artifact offset into paths_pool
115 paths_lens: *i64 // per-artifact path length
116 path_hashes: *i64 // FNV-1a 64 of path (for O(1) lookup)
117
118 // Last-commit metadata
119 hashes_pool: *u8 // packed 40-char SHA-1 hashes
120 hashes_pool_cap: i64
121 hashes_pool_used: i64
122 hashes_offs: *i64
123 hashes_lens: *i64
124
125 // Per-artifact classification (parallel arrays; rowid-indexed)
126 kind: *i64 // NX_ART_KIND_*
127 pipeline_stage: *i64 // NX_ART_STAGE_*
128 quadrant: *i64 // NX_ART_QUAD_*
129 topic_type: *i64 // NX_ART_DITA_*
130 status: *i64 // NX_ART_STATUS_*
131 trust_state: *i64 // NX_ART_TRUST_*
132 competitive_state: *i64 // NX_ART_COMP_*
133
134 // Per-artifact stats
135 line_count: *i64
136 import_count: *i64 // for .nx kinds
137 export_count: *i64 // # of public funcs for .nx kinds
138 composes_count: *i64 // # of [[X]] refs in Composes section
139
140 // Bookkeeping
141 artifact_count: i64
142 artifact_cap: i64
143 valid: i64
144}
145
146// ===== Sealed init =================================================
147
148func nx_artifact_store_init(s: *NxArtifactStore, cap: i64) -> i64 {
149 if (s as i64) == 0 { return 0 - NX_ART_BAD_INPUT }
150 if cap < 1 { return 0 - NX_ART_BAD_INPUT }
151 if cap > NX_ART_HARD_CAP { return 0 - NX_ART_STORE_FULL }
152
153 // Pools
154 s.paths_pool = sys_mmap(NX_ART_PATH_POOL_CAP)
155 s.paths_pool_cap = NX_ART_PATH_POOL_CAP
156 s.paths_pool_used = 0
157 s.paths_offs = (sys_mmap(cap * 8)) as *i64
158 s.paths_lens = (sys_mmap(cap * 8)) as *i64
159 s.path_hashes = (sys_mmap(cap * 8)) as *i64
160
161 s.hashes_pool = sys_mmap(NX_ART_HASH_POOL_CAP)
162 s.hashes_pool_cap = NX_ART_HASH_POOL_CAP
163 s.hashes_pool_used = 0
164 s.hashes_offs = (sys_mmap(cap * 8)) as *i64
165 s.hashes_lens = (sys_mmap(cap * 8)) as *i64
166
167 s.kind = (sys_mmap(cap * 8)) as *i64
168 s.pipeline_stage = (sys_mmap(cap * 8)) as *i64
169 s.quadrant = (sys_mmap(cap * 8)) as *i64
170 s.topic_type = (sys_mmap(cap * 8)) as *i64
171 s.status = (sys_mmap(cap * 8)) as *i64
172 s.trust_state = (sys_mmap(cap * 8)) as *i64
173 s.competitive_state = (sys_mmap(cap * 8)) as *i64
174
175 s.line_count = (sys_mmap(cap * 8)) as *i64
176 s.import_count = (sys_mmap(cap * 8)) as *i64
177 s.export_count = (sys_mmap(cap * 8)) as *i64
178 s.composes_count = (sys_mmap(cap * 8)) as *i64
179
180 s.artifact_count = 0
181 s.artifact_cap = cap
182 s.valid = 1
183 return NX_ART_OK
184}
185
186// ===== Add an artifact =================================================
187
188func nx_artifact_store_add(s: *NxArtifactStore,
189 path: *u8, path_n: i64,
190 kind: i64,
191 last_commit_hash: *u8, hash_n: i64,
192 line_count: i64) -> i64 {
193 if s.valid != 1 { return 0 - NX_ART_BAD_INPUT }
194 if s.artifact_count >= s.artifact_cap { return 0 - NX_ART_STORE_FULL }
195 if path_n < 1 { return 0 - NX_ART_BAD_INPUT }
196 if path_n > NX_ART_MAX_PATH_LEN { return 0 - NX_ART_BAD_INPUT }
197 if kind < 0 { return 0 - NX_ART_BAD_INPUT }
198 if kind >= NX_ART_KIND_N { return 0 - NX_ART_BAD_INPUT }
199 if hash_n < 0 { return 0 - NX_ART_BAD_INPUT }
200 if hash_n > 40 { return 0 - NX_ART_BAD_INPUT }
201 if line_count < 0 { return 0 - NX_ART_BAD_INPUT }
202
203 let rowid: i64 = s.artifact_count
204
205 // Path
206 if s.paths_pool_used + path_n > s.paths_pool_cap { return 0 - NX_ART_POOL_OVERFLOW }
207 let p_off: i64 = s.paths_pool_used
208 var i: i64 = 0
209 while i < path_n { s.paths_pool[p_off + i] = path[i]; i = i + 1 }
210 s.paths_pool_used = s.paths_pool_used + path_n
211 s.paths_offs[rowid] = p_off
212 s.paths_lens[rowid] = path_n
213 s.path_hashes[rowid] = nx_inv_hash_bytes_lower(path, path_n)
214
215 // Last-commit hash
216 if hash_n > 0 {
217 if s.hashes_pool_used + hash_n > s.hashes_pool_cap { return 0 - NX_ART_POOL_OVERFLOW }
218 let h_off: i64 = s.hashes_pool_used
219 i = 0
220 while i < hash_n { s.hashes_pool[h_off + i] = last_commit_hash[i]; i = i + 1 }
221 s.hashes_pool_used = s.hashes_pool_used + hash_n
222 s.hashes_offs[rowid] = h_off
223 s.hashes_lens[rowid] = hash_n
224 }
225 if hash_n == 0 {
226 s.hashes_offs[rowid] = 0
227 s.hashes_lens[rowid] = 0
228 }
229
230 // Classification: default to UNSTRUCTURED + UNCLASSIFIED; walker
231 // fills these in via nx_artifact_store_set_taxonomy after parse.
232 s.kind[rowid] = kind
233 s.pipeline_stage[rowid] = NX_ART_STAGE_UNSTRUCTURED
234 s.quadrant[rowid] = NX_ART_QUAD_NONE
235 s.topic_type[rowid] = NX_ART_DITA_NONE
236 s.status[rowid] = NX_ART_STATUS_NONE
237 s.trust_state[rowid] = NX_ART_TRUST_NONE
238 s.competitive_state[rowid] = NX_ART_COMP_NONE
239
240 s.line_count[rowid] = line_count
241 s.import_count[rowid] = 0
242 s.export_count[rowid] = 0
243 s.composes_count[rowid] = 0
244
245 s.artifact_count = s.artifact_count + 1
246 return rowid
247}
248
249// ===== Set taxonomy fields (called by walker after front-matter parse) =================================================
250
251func nx_artifact_store_set_taxonomy(s: *NxArtifactStore, rowid: i64,
252 quadrant: i64, topic_type: i64,
253 status: i64,
254 trust_state: i64,
255 competitive_state: i64) -> i64 {
256 if s.valid != 1 { return 0 - NX_ART_BAD_INPUT }
257 if rowid < 0 { return 0 - NX_ART_BAD_INPUT }
258 if rowid >= s.artifact_count { return 0 - NX_ART_NOT_FOUND }
259 if quadrant < 0 { return 0 - NX_ART_BAD_INPUT }
260 if quadrant >= NX_ART_QUAD_N { return 0 - NX_ART_BAD_INPUT }
261 if topic_type < 0 { return 0 - NX_ART_BAD_INPUT }
262 if topic_type >= NX_ART_DITA_N { return 0 - NX_ART_BAD_INPUT }
263 if status < 0 { return 0 - NX_ART_BAD_INPUT }
264 if status >= NX_ART_STATUS_N { return 0 - NX_ART_BAD_INPUT }
265 if trust_state < 0 { return 0 - NX_ART_BAD_INPUT }
266 if trust_state >= NX_ART_TRUST_N { return 0 - NX_ART_BAD_INPUT }
267 if competitive_state < 0 { return 0 - NX_ART_BAD_INPUT }
268 if competitive_state >= NX_ART_COMP_N { return 0 - NX_ART_BAD_INPUT }
269
270 s.quadrant[rowid] = quadrant
271 s.topic_type[rowid] = topic_type
272 s.status[rowid] = status
273 s.trust_state[rowid] = trust_state
274 s.competitive_state[rowid] = competitive_state
275 return NX_ART_OK
276}
277
278// ===== Set stats (called by walker after content analysis) =================================================
279
280func nx_artifact_store_set_stats(s: *NxArtifactStore, rowid: i64,
281 import_count: i64, export_count: i64,
282 composes_count: i64) -> i64 {
283 if s.valid != 1 { return 0 - NX_ART_BAD_INPUT }
284 if rowid < 0 { return 0 - NX_ART_BAD_INPUT }
285 if rowid >= s.artifact_count { return 0 - NX_ART_NOT_FOUND }
286 if import_count < 0 { return 0 - NX_ART_BAD_INPUT }
287 if export_count < 0 { return 0 - NX_ART_BAD_INPUT }
288 if composes_count < 0 { return 0 - NX_ART_BAD_INPUT }
289
290 s.import_count[rowid] = import_count
291 s.export_count[rowid] = export_count
292 s.composes_count[rowid] = composes_count
293 return NX_ART_OK
294}
295
296// ===== Derive pipeline stage from taxonomy + stats (per V2.0 PLAN §2.0 mapping) =================================================
297//
298// Sealed rules (operator-existing taxonomy → derived stage):
299// STAGE_REPEATABLE iff trust=PERSONALLY_MEASURED + comp∈{MATCHES,BEATS}
300// STAGE_ACTIONABLE iff trust>WRITTEN_UNTESTED + comp!=NONE
301// STAGE_MEANINGFUL iff status!=NONE + composes_count>0
302// STAGE_STRUCTURED iff quadrant!=NONE + topic_type!=NONE + status!=NONE
303// STAGE_UNSTRUCTURED otherwise
304
305func nx_artifact_store_derive_stage(s: *NxArtifactStore, rowid: i64) -> i64 {
306 if s.valid != 1 { return 0 - NX_ART_BAD_INPUT }
307 if rowid < 0 { return 0 - NX_ART_BAD_INPUT }
308 if rowid >= s.artifact_count { return 0 - NX_ART_NOT_FOUND }
309
310 let q: i64 = s.quadrant[rowid]
311 let tt: i64 = s.topic_type[rowid]
312 let st: i64 = s.status[rowid]
313 let tr: i64 = s.trust_state[rowid]
314 let co: i64 = s.competitive_state[rowid]
315 let nc: i64 = s.composes_count[rowid]
316
317 var stage: i64 = NX_ART_STAGE_UNSTRUCTURED
318
319 // STRUCTURED: all 3 of (quadrant, topic_type, status) present
320 if q != NX_ART_QUAD_NONE {
321 if tt != NX_ART_DITA_NONE {
322 if st != NX_ART_STATUS_NONE { stage = NX_ART_STAGE_STRUCTURED }
323 }
324 }
325
326 // MEANINGFUL: + composes count > 0 (composes nx_doc_xref signal)
327 if stage == NX_ART_STAGE_STRUCTURED {
328 if nc > 0 { stage = NX_ART_STAGE_MEANINGFUL }
329 }
330
331 // ACTIONABLE: + trust state set (NOT WRITTEN_UNTESTED) + comp set
332 if stage == NX_ART_STAGE_MEANINGFUL {
333 if tr > NX_ART_TRUST_WRITTEN_UNTESTED {
334 if co != NX_ART_COMP_NONE { stage = NX_ART_STAGE_ACTIONABLE }
335 }
336 }
337
338 // REPEATABLE: + trust=PERSONALLY_MEASURED + comp∈{MATCHES, BEATS}
339 if stage == NX_ART_STAGE_ACTIONABLE {
340 if tr == NX_ART_TRUST_PERSONALLY_MEASURED {
341 if co >= NX_ART_COMP_MATCHES { stage = NX_ART_STAGE_REPEATABLE }
342 }
343 }
344
345 // Status==DEPRECATED freezes stage (no auto-advance; operator action only)
346 if st == NX_ART_STATUS_DEPRECATED { stage = s.pipeline_stage[rowid] }
347
348 s.pipeline_stage[rowid] = stage
349 return stage
350}
351
352// ===== Accessors =================================================
353
354func nx_artifact_store_count(s: *NxArtifactStore) -> i64 {
355 if s.valid != 1 { return 0 }
356 return s.artifact_count
357}
358
359func nx_artifact_store_count_by_stage(s: *NxArtifactStore, stage: i64) -> i64 {
360 if s.valid != 1 { return 0 }
361 if stage < 0 { return 0 }
362 if stage >= NX_ART_STAGE_N { return 0 }
363 var i: i64 = 0
364 var n: i64 = 0
365 while i < s.artifact_count {
366 if s.pipeline_stage[i] == stage { n = n + 1 }
367 i = i + 1
368 }
369 return n
370}
371
372func nx_artifact_store_path_at(s: *NxArtifactStore, rowid: i64,
373 out_ptr: *i64, out_n: *i64) -> i64 {
374 if s.valid != 1 { return 0 - NX_ART_BAD_INPUT }
375 if rowid < 0 { return 0 - NX_ART_BAD_INPUT }
376 if rowid >= s.artifact_count { return 0 - NX_ART_NOT_FOUND }
377 if (out_ptr as i64) == 0 { return 0 - NX_ART_BAD_INPUT }
378 if (out_n as i64) == 0 { return 0 - NX_ART_BAD_INPUT }
379 out_ptr[0] = (s.paths_pool as i64) + s.paths_offs[rowid]
380 out_n[0] = s.paths_lens[rowid]
381 return NX_ART_OK
382}
383
384func nx_artifact_store_stage_at(s: *NxArtifactStore, rowid: i64) -> i64 {
385 if s.valid != 1 { return 0 - NX_ART_BAD_INPUT }
386 if rowid < 0 { return 0 - NX_ART_BAD_INPUT }
387 if rowid >= s.artifact_count { return 0 - NX_ART_NOT_FOUND }
388 return s.pipeline_stage[rowid]
389}
390
391func nx_artifact_store_kind_at(s: *NxArtifactStore, rowid: i64) -> i64 {
392 if s.valid != 1 { return 0 - NX_ART_BAD_INPUT }
393 if rowid < 0 { return 0 - NX_ART_BAD_INPUT }
394 if rowid >= s.artifact_count { return 0 - NX_ART_NOT_FOUND }
395 return s.kind[rowid]
396}
397
398// Look up by path-hash (O(N) linear scan; V2.0 acceptable for ~1000
399// artifacts; V3 swaps for hash-keyed lookup).
400func nx_artifact_store_find_by_path(s: *NxArtifactStore,
401 path: *u8, path_n: i64) -> i64 {
402 if s.valid != 1 { return 0 - NX_ART_NOT_FOUND }
403 if path_n < 1 { return 0 - NX_ART_BAD_INPUT }
404 let h: i64 = nx_inv_hash_bytes_lower(path, path_n)
405 var i: i64 = 0
406 while i < s.artifact_count {
407 if s.path_hashes[i] == h { return i }
408 i = i + 1
409 }
410 return 0 - NX_ART_NOT_FOUND
411}
412
413// Sealed-name accessors for stage/kind/quadrant/etc (for renderer)
414func nx_art_stage_name(s: i64) -> *u8 {
415 if s == NX_ART_STAGE_UNSTRUCTURED { return "UNSTRUCTURED" as *u8 }
416 if s == NX_ART_STAGE_STRUCTURED { return "STRUCTURED" as *u8 }
417 if s == NX_ART_STAGE_MEANINGFUL { return "MEANINGFUL" as *u8 }
418 if s == NX_ART_STAGE_ACTIONABLE { return "ACTIONABLE" as *u8 }
419 if s == NX_ART_STAGE_REPEATABLE { return "REPEATABLE" as *u8 }
420 return "UNKNOWN" as *u8
421}
422
423func nx_art_kind_name(k: i64) -> *u8 {
424 if k == NX_ART_KIND_RUNTIME_NX { return "RUNTIME_NX" as *u8 }
425 if k == NX_ART_KIND_CHARTER_MD { return "CHARTER_MD" as *u8 }
426 if k == NX_ART_KIND_BENCH_SH { return "BENCH_SH" as *u8 }
427 if k == NX_ART_KIND_RFC_MD { return "RFC_MD" as *u8 }
428 if k == NX_ART_KIND_MEMORY_MD { return "MEMORY_MD" as *u8 }
429 if k == NX_ART_KIND_DASHBOARD_MD{ return "DASHBOARD_MD"as *u8 }
430 if k == NX_ART_KIND_SILICON_ARC { return "SILICON_ARC" as *u8 }
431 if k == NX_ART_KIND_TEST_NX { return "TEST_NX" as *u8 }
432 if k == NX_ART_KIND_OTHER { return "OTHER" as *u8 }
433 return "UNKNOWN" as *u8
434}