nx_pipeline_graph_builder.nx source
↩ module page · 185 lines · 8502 B
1// nx_pipeline_graph_builder.nx -- V2.0 P-8.5: populate NxDepGraph from
2// NxArtifactStore via doc_extractor_nx imports + suffix-match resolution.
3//
4// Closes the dep-graph data path: walker -> store -> graph -> Tarjan SCC ->
5// per-artifact cycle membership exposed at /wiki/pipeline + /wiki/<doc>.
6//
7// COMPOSES:
8// wiki/nx_artifact_store (artifact paths to resolve against)
9// hub/nx_doc_extractor_nx (nx_doc_extract_nx_imports +
10// nx_doc_imports_iter_next)
11// hub/nx_dep_graph (graph storage + Tarjan)
12// nx_syscalls.sys_read_file (re-read .nx bodies for imports)
13// nx_html_extract.nx_he_match_ci (path suffix match for resolution)
14//
15// COMPOSED BY:
16// wiki/nx_wiki_main (startup; called after artifact_store populated)
17//
18// V2.0 P-8.5 SCOPE:
19// - For each RUNTIME_NX / TEST_NX / SILICON_ARC artifact: re-read body;
20// extract import paths; suffix-match each against artifact paths;
21// add resolved edges to graph
22// - Bounded by NX_ARTIFACT_STORE artifact_cap + edges_cap
23// - Per Cardinal 14 graceful degradation: per-file read failures skip
24// the artifact; unresolved imports skipped silently
25//
26// V3+ SCOPE (TODO):
27// - Body cache in NxArtifactStore (avoid re-read)
28// - Hash-keyed import resolution (avoid O(N) suffix match)
29// - Per-import resolution-confidence score (exact vs ambiguous match)
30//
31// Status: V2.0 P-8.5. 2026-05-27.
32
33import "nx_syscalls.nx"
34import "nx_html_extract.nx"
35import "wiki/nx_artifact_store.nx"
36import "hub/nx_doc_extractor_nx.nx"
37import "hub/nx_dep_graph.nx"
38
39// ===== Sealed verdict surface (codes 3060-3064) =================================================
40const NX_PGB_OK: i64 = 0
41const NX_PGB_BAD_INPUT: i64 = 3060
42const NX_PGB_READ_FAILED: i64 = 3061
43const NX_PGB_GRAPH_FULL: i64 = 3062
44const NX_PGB_LOOP_BUDGET: i64 = 3063
45
46// ===== Named constants (M7) =================================================
47const NX_PGB_IMPORTS_BUF_CAP: i64 = 16384 // 16 KB per-artifact packed imports
48const NX_PGB_LOOP_BUDGET_CAP: i64 = 10000000
49
50// ===== Suffix-match resolution: find artifact rowid whose path ENDS WITH suffix =================================================
51//
52// Substrate convention: `import "wiki/nx_artifact_store.nx"` should match
53// any artifact path ending in `/wiki/nx_artifact_store.nx` (or
54// `\wiki\nx_artifact_store.nx` on hostile filesystems). Linear scan
55// over artifact_count; V2.0 acceptable for ~500 artifacts; V3+ swaps
56// for hash-keyed lookup.
57//
58// Returns rowid (>= 0) on unique match; -1 on no match; -2 on ambiguous (>1 match).
59
60func nx_pgb_resolve_import(store: *NxArtifactStore,
61 import_path: *u8, import_n: i64) -> i64 {
62 if store.valid != 1 { return 0 - 1 }
63 if import_n < 1 { return 0 - 1 }
64
65 var found: i64 = 0 - 1
66 var n_matches: i64 = 0
67 var i: i64 = 0
68 while i < store.artifact_count {
69 let path_ptr: *u8 = ((store.paths_pool as i64) + store.paths_offs[i]) as *u8
70 let path_n: i64 = store.paths_lens[i]
71 if path_n >= import_n {
72 // Suffix match: compare path[path_n - import_n .. path_n] vs import
73 let suffix_off: i64 = path_n - import_n
74 // Must be preceded by '/' (or be the entire path)
75 var suffix_ok: i64 = 1
76 if suffix_off > 0 {
77 if path_ptr[suffix_off - 1] != (0x2F as u8) { suffix_ok = 0 }
78 }
79 if suffix_ok == 1 {
80 if nx_he_match_ci(path_ptr, suffix_off, path_n,
81 import_path, import_n) == 1 {
82 found = i
83 n_matches = n_matches + 1
84 }
85 }
86 }
87 i = i + 1
88 }
89 if n_matches > 1 { return 0 - 2 }
90 return found
91}
92
93// ===== Build dep graph: walk store; per .nx artifact re-read body + extract imports + add edges =================================================
94
95func nx_pipeline_build_dep_graph(store: *NxArtifactStore,
96 graph: *NxDepGraph,
97 out_counters: *i64) -> i64 {
98 if store.valid != 1 { return 0 - NX_PGB_BAD_INPUT }
99 if graph.valid != 1 { return 0 - NX_PGB_BAD_INPUT }
100 if (out_counters as i64) == 0 { return 0 - NX_PGB_BAD_INPUT }
101
102 // counters[0] = edges added; [1] = imports unresolved; [2] = imports ambiguous;
103 // [3] = artifacts re-read; [4] = read failures
104 out_counters[0] = 0
105 out_counters[1] = 0
106 out_counters[2] = 0
107 out_counters[3] = 0
108 out_counters[4] = 0
109
110 let imports_buf: *u8 = sys_mmap(NX_PGB_IMPORTS_BUF_CAP)
111 let count_p: *i64 = (sys_mmap(8)) as *i64
112 let used_p: *i64 = (sys_mmap(8)) as *i64
113 let ptr_p: *i64 = (sys_mmap(8)) as *i64
114 let n_p: *i64 = (sys_mmap(8)) as *i64
115 let lenbox: *u8 = sys_mmap(8)
116 let lp: *i64 = lenbox as *i64
117
118 var rowid: i64 = 0
119 var iter: i64 = 0
120 while rowid < store.artifact_count {
121 if iter >= NX_PGB_LOOP_BUDGET_CAP { return 0 - NX_PGB_LOOP_BUDGET }
122 iter = iter + 1
123
124 let kind: i64 = nx_artifact_store_kind_at(store, rowid)
125 var is_nx: i64 = 0
126 if kind == NX_ART_KIND_RUNTIME_NX { is_nx = 1 }
127 if kind == NX_ART_KIND_TEST_NX { is_nx = 1 }
128 if kind == NX_ART_KIND_SILICON_ARC{ is_nx = 1 }
129
130 if is_nx == 1 {
131 // Re-read body (V3+ caches in store)
132 let path_ptr_out: *i64 = (sys_mmap(8)) as *i64
133 let path_n_out: *i64 = (sys_mmap(8)) as *i64
134 path_ptr_out[0] = 0; path_n_out[0] = 0
135 let rc_p: i64 = nx_artifact_store_path_at(store, rowid, path_ptr_out, path_n_out)
136 if rc_p == NX_ART_OK {
137 lp[0] = 0
138 let body: *u8 = sys_read_file(path_ptr_out[0] as *u8, lp)
139 if (body as i64) != 0 {
140 if lp[0] > 0 {
141 out_counters[3] = out_counters[3] + 1
142 // Extract import paths
143 count_p[0] = 0; used_p[0] = 0
144 let rc_x: i64 = nx_doc_extract_nx_imports(body, lp[0],
145 imports_buf, NX_PGB_IMPORTS_BUF_CAP,
146 count_p, used_p)
147 if rc_x == NX_DXNX_OK {
148 // Iterate packed imports; resolve + add edges
149 var off: i64 = 0
150 var imp_iter: i64 = 0
151 while off >= 0 {
152 if imp_iter >= 1000 { off = 0 - 1 } // per-artifact cap
153 imp_iter = imp_iter + 1
154 ptr_p[0] = 0; n_p[0] = 0
155 let next_off: i64 = nx_doc_imports_iter_next(imports_buf, used_p[0],
156 off, ptr_p, n_p)
157 if next_off < 0 { off = 0 - 1 }
158 if next_off >= 0 {
159 let resolved: i64 = nx_pgb_resolve_import(store,
160 ptr_p[0] as *u8,
161 n_p[0])
162 if resolved >= 0 {
163 let rc_e: i64 = nx_dep_graph_add_edge(graph, rowid, resolved)
164 if rc_e == NX_DEPG_OK { out_counters[0] = out_counters[0] + 1 }
165 if rc_e == 0 - NX_DEPG_EDGE_FULL { return 0 - NX_PGB_GRAPH_FULL }
166 }
167 if resolved == 0 - 1 { out_counters[1] = out_counters[1] + 1 }
168 if resolved == 0 - 2 { out_counters[2] = out_counters[2] + 1 }
169 off = next_off
170 }
171 }
172 }
173 }
174 }
175 if (body as i64) == 0 { out_counters[4] = out_counters[4] + 1 }
176 }
177 }
178 rowid = rowid + 1
179 }
180
181 // Run Tarjan
182 let rc_tarjan: i64 = nx_dep_graph_tarjan_scc(graph)
183 if rc_tarjan != NX_DEPG_OK { return 0 - NX_PGB_BAD_INPUT }
184 return NX_PGB_OK
185}