nx_scaffolding_audit.nx source
↩ module page · 389 lines · 20024 B
1// nx_scaffolding_audit.nx -- REPO-FILE audit primitive that enforces the
2// no-temporary-third-party-scaffolding-when-substrate-arc-exists cardinal.
3//
4// AUDIT-FIRST CORRECTION (2026-05-20, post-creation): user caught that I
5// built this without checking for existing prior art. The substrate
6// already ships an audit family:
7// - nx_audit_dashboard.nx : substrate-STATE aggregation
8// (hunter kills / gatherer yields /
9// xenocell alerts / immune health /
10// book unseen-alert / organism pressure /
11// pollinate pending)
12// - nx_audit_server_daemon.nx : exposes substrate state over HTTP
13// - nx_audit_server_main.nx : daemon entry point
14// - nx_audit_compose_test.nx : test harness
15// - nx_ai_audit.nx : AI-output audit
16// - nx_adversarial_pattern_audit.nx: adversarial input pattern detection
17//
18// THIS primitive (nx_scaffolding_audit) is a DIFFERENT DOMAIN: it audits
19// REPO FILES against the SCAFFOLDING_REPLACED_BY cardinal. Distinct from
20// the substrate-state-aggregation primitives above. However it could
21// COMPOSE INTO the dashboard: future nx_audit_dashboard rows could include
22// a "scaffolding violations" counter sourced from nx_saudit_run, surfacing
23// the cardinal-compliance state alongside other substrate health metrics.
24//
25// Per feedback-no-temporary-third-party-scaffolding-when-substrate-arc-exists
26// audit-first rule: ls nxc2/runtime/ before authoring new primitives.
27//
28//
29// module: nishi-core.ops.scaffolding_audit
30// depends: nishi-core.perception.profile + nishi-core.io.syscalls
31// disk_kb: 9
32// capability: OPS
33// wired_status: PARTIAL_WIRED (nx_saudit_check_one_file + nx_saudit_run
34// both GRADUATED to basic-glue 2026-05-20:
35// compose sys_read_file + nx_str_find +
36// nx_dir_list; refuses self-exempt loophole
37// at substrate level; aggregates per-file
38// findings into NxSauditSummary; rest of
39// entries still PARTIAL_WIRED)
40//
41// MISSING_CAPABILITIES (still queued):
42// - FS_WALK_FILTER (walk repo via nx_dir_list + prefix-exclusion +
43// content-heuristic-match; nx_dir FULLY_WIRED; just needs glue)
44// - HEADER_VALUE_EXTRACT (parse primitive-name list out of the header line)
45// - PRIMITIVE_FILE_EXISTS (nx_fs_stat against runtime/nx_<name>.nx)
46// - WIRED_STATUS_PARSE (read "wired_status:" header from each named
47// primitive .nx file -- enables SUNSET_OVERDUE detection)
48// - VIOLATION_REPORT_EMIT (structured JSONL via nx_log_jsonl FULLY_WIRED)
49// - CI_GATE_DISPATCH (exit-code-bits encoding for pre-commit / CI gate)
50//
51// license_tier: PUBLIC_NISHI_SUBSTRATE
52// genealogy_id: feedback-no-temporary-third-party-scaffolding-when-substrate-arc-exists_2026 +
53// feedback-substrate-primitives-meta-not-one-off +
54// feedback-self-surfacing-intelligence-staged-autonomy +
55// feedback-substrate-does-heavy-lifting-user-is-partner-not-gate
56//
57// USER CALLED OUT 2026-05-20: "lint is this nishi lint or what the is it"
58//
59// The earlier lint_scaffolding_audit.sh was itself a shell script + grep
60// + awk + sed + find -- exactly the kind of third-party scaffolding the
61// cardinal it enforces forbids. Self-exempting the auditor via a
62// SCAFFOLDING_AUDIT_SELF_EXEMPT header was a loophole; the cardinal does
63// NOT permit auditor self-exemption. The lint script gets a proper
64// SCAFFOLDING_REPLACED_BY header naming this primitive in the same
65// commit.
66//
67// THIS primitive IS the Nishi-native replacement. Same audit logic,
68// implemented in NishiLang, composing with already-FULLY_WIRED substrate
69// (nx_dir for fs walk, nx_fs_stat for primitive existence check,
70// nx_log_jsonl for violation output). When this primitive reaches
71// FULLY_WIRED, the shell lint gets DELETED.
72//
73// Reuse set:
74// - Enforce the no-temporary-third-party-scaffolding cardinal repo-side
75// - Future cardinal-class lints will be sibling primitives
76// (nx_genealogy_audit, nx_dependency_audit, nx_naming_audit, etc.)
77// sharing a common nx_audit_engine base when one is extracted
78// - Pre-commit hook substrate (composes with nx_proc_spawn to register
79// itself as the hook handler)
80// - CI gate substrate (same pre-commit interface used by remote CI)
81
82import "nx_syscalls.nx"
83import "nx_perceptual_profile.nx"
84import "nx_string_ops.nx"
85import "nx_dir.nx"
86
87// ===== Violation class sealed enum ===============================
88
89const NX_SAUDIT_VIOLATION_MISSING_HEADER: i64 = 1 // no SCAFFOLDING_REPLACED_BY line
90const NX_SAUDIT_VIOLATION_BAD_REFERENCE: i64 = 2 // named primitive file missing
91const NX_SAUDIT_VIOLATION_SUNSET_OVERDUE: i64 = 4 // primitive FULLY_WIRED;
92 // scaffolding should be gone
93const NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT: i64 = 8 // ANY file claiming
94 // self-exempt header --
95 // refused per cardinal
96
97func nx_saudit_violation_name(v: i64) -> *u8 {
98 if v == NX_SAUDIT_VIOLATION_MISSING_HEADER { return "MISSING_HEADER" }
99 if v == NX_SAUDIT_VIOLATION_BAD_REFERENCE { return "BAD_REFERENCE" }
100 if v == NX_SAUDIT_VIOLATION_SUNSET_OVERDUE { return "SUNSET_OVERDUE" }
101 if v == NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT { return "AUDITOR_SELF_EXEMPT" }
102 return "UNKNOWN_VIOLATION"
103}
104
105// ===== Scope policy sealed enum ==================================
106//
107// Per SCAFFOLDING_INVENTORY.md. Content-heuristic in-scope detection
108// avoids exclude-by-prefix being too permissive or too strict.
109
110const NX_SAUDIT_SCOPE_OPS_DIR: i64 = 1 // bench/ops/*
111const NX_SAUDIT_SCOPE_OPERATIONAL_FILENAME: i64 = 2 // nishifamily_* etc
112const NX_SAUDIT_SCOPE_CONTENT_HEURISTIC: i64 = 3 // grep for nginx/sudo/etc
113const NX_SAUDIT_SCOPE_OUT_OF_SCOPE: i64 = 4 // substrate-test prefixes
114
115func nx_saudit_scope_name(s: i64) -> *u8 {
116 if s == NX_SAUDIT_SCOPE_OPS_DIR { return "OPS_DIR" }
117 if s == NX_SAUDIT_SCOPE_OPERATIONAL_FILENAME { return "OPERATIONAL_FILENAME" }
118 if s == NX_SAUDIT_SCOPE_CONTENT_HEURISTIC { return "CONTENT_HEURISTIC" }
119 if s == NX_SAUDIT_SCOPE_OUT_OF_SCOPE { return "OUT_OF_SCOPE" }
120 return "UNKNOWN_SCOPE"
121}
122
123// ===== Audit verdict =============================================
124
125const NX_SAUDIT_VERDICT_CLEAN: i64 = 0
126const NX_SAUDIT_VERDICT_VIOLATIONS_PRESENT: i64 = 1 // any violation class
127const NX_SAUDIT_VERDICT_DEPENDENCY_MISSING: i64 = 2 // PARTIAL_WIRED default
128
129func nx_saudit_verdict_name(v: i64) -> *u8 {
130 if v == NX_SAUDIT_VERDICT_CLEAN { return "CLEAN" }
131 if v == NX_SAUDIT_VERDICT_VIOLATIONS_PRESENT { return "VIOLATIONS_PRESENT" }
132 if v == NX_SAUDIT_VERDICT_DEPENDENCY_MISSING { return "DEPENDENCY_MISSING" }
133 return "UNKNOWN_SAUDIT_VERDICT"
134}
135
136// ===== Per-file finding struct ===================================
137
138struct NxSauditFinding {
139 file_path_hash_ptr: *u8
140 file_path_hash_len: i64
141 scope_class: i64 // NX_SAUDIT_SCOPE_*
142 violation_class: i64 // NX_SAUDIT_VIOLATION_*
143 named_primitives_hash_ptr: *u8 // content-hash of declared
144 // replacement primitives string
145 named_primitives_hash_len: i64
146 primitive_count: i64
147 primitives_existing_count: i64 // how many of named primitives
148 // have .nx files
149 primitives_fully_wired_count: i64 // how many are FULLY_WIRED
150}
151
152// ===== Audit result summary struct ===============================
153
154struct NxSauditSummary {
155 total_in_scope: i64
156 ok_count: i64
157 missing_header_count: i64
158 bad_reference_count: i64
159 sunset_overdue_count: i64
160 auditor_self_exempt_count: i64
161 overall_verdict: i64 // NX_SAUDIT_VERDICT_*
162 exit_code_bits: i64 // OR'd violation classes for CI gate
163}
164
165// ===== Top-level entry stubs =====================================
166
167// nx_saudit_run -- walk the repo, audit every in-scope file, emit findings.
168// Returns OR'd violation-class exit code bits for CI gate use.
169//
170// GRADUATED 2026-05-20 from PARTIAL_WIRED -> wired-at-basic-glue.
171// Composes nx_dir_list (FULLY_WIRED) -> per-row nx_saudit_check_one_file ->
172// aggregate counters into NxSauditSummary.
173//
174// v1 scope: hardcoded walk of "nxc2/bench/ops/" + filter to *.sh files.
175// v2 expands to bench/ + content-heuristic per SCAFFOLDING_INVENTORY.md.
176// repo_root_ptr/len accepted for forward compatibility; v1 ignores.
177//
178// Per cardinal feedback-no-temporary-third-party-scaffolding-when-substrate-arc-exists:
179// this is the Nishi-native replacement for the lint_scaffolding_audit.sh
180// repo-walk + per-file-grep chain. When v2 expands scope to match the
181// shell lint's full coverage, the shell lint becomes SUNSET_OVERDUE.
182
183func nx_saudit_run(repo_root_ptr: *u8, repo_root_len: i64,
184 summary_out_ptr: *NxSauditSummary) -> i64 {
185 if repo_root_len <= 0 { return NX_SAUDIT_VERDICT_DEPENDENCY_MISSING }
186 if summary_out_ptr == 0 as *NxSauditSummary { return NX_SAUDIT_VERDICT_DEPENDENCY_MISSING }
187
188 // Initialize summary counters.
189 summary_out_ptr.total_in_scope = 0
190 summary_out_ptr.ok_count = 0
191 summary_out_ptr.missing_header_count = 0
192 summary_out_ptr.bad_reference_count = 0
193 summary_out_ptr.sunset_overdue_count = 0
194 summary_out_ptr.auditor_self_exempt_count = 0
195 summary_out_ptr.overall_verdict = NX_SAUDIT_VERDICT_CLEAN
196 summary_out_ptr.exit_code_bits = 0
197
198 // Build dir path "nxc2/bench/ops" (15 bytes + NUL).
199 let dir: *u8 = sys_mmap(64)
200 dir[0]=0x6E as u8; dir[1]=0x78 as u8; dir[2]=0x63 as u8; dir[3]=0x32 as u8 // "nxc2"
201 dir[4]=0x2F as u8 // "/"
202 dir[5]=0x62 as u8; dir[6]=0x65 as u8; dir[7]=0x6E as u8; dir[8]=0x63 as u8; dir[9]=0x68 as u8 // "bench"
203 dir[10]=0x2F as u8 // "/"
204 dir[11]=0x6F as u8; dir[12]=0x70 as u8; dir[13]=0x73 as u8 // "ops"
205 dir[14]=0 // NUL
206
207 // Allocate row storage: 256 rows × 32 bytes = 8 KB.
208 let rows: *NxDirRow = sys_mmap(8192) as *NxDirRow
209 // Name arena: 16 KB of name bytes.
210 let names: *u8 = sys_mmap(16384)
211 let dres: *NxDirResult = sys_mmap(64) as *NxDirResult
212
213 let dv: i64 = nx_dir_list(dir, rows, 256, names, 16384, 0, dres)
214 if dv != NX_DIR_OK {
215 // Directory unreadable; substrate cannot audit.
216 return NX_SAUDIT_VERDICT_DEPENDENCY_MISSING
217 }
218
219 // Build ".sh" suffix for filename filter (3 bytes + NUL).
220 let sh: *u8 = sys_mmap(16)
221 sh[0]=0x2E as u8; sh[1]=0x73 as u8; sh[2]=0x68 as u8; sh[3]=0 // ".sh"
222 // Build "_" leading-underscore byte for prefix exclusion.
223 // (helpers prefixed _ are out-of-scope per inventory)
224
225 let n: i64 = dres.n_filled
226 let finding: *NxSauditFinding = sys_mmap(96) as *NxSauditFinding
227 let fullpath: *u8 = sys_mmap(512)
228
229 var i: i64 = 0
230 while i < n {
231 let row: *NxDirRow = nx_dir_row_at(rows, i)
232 // Filter: regular file, name ends in ".sh", not prefixed "_".
233 if nx_dir_row_is_regular_file(row) != 0 {
234 if nx_str_ends_with(row.name_ptr, row.name_len, sh, 3) != 0 {
235 if row.name_ptr[0] != 0x5F as u8 { // skip "_" prefix
236 // Build fullpath = "nxc2/bench/ops/" + name + NUL.
237 var j: i64 = 0
238 while j < 14 { fullpath[j] = dir[j]; j = j + 1 }
239 fullpath[14] = 0x2F as u8 // "/"
240 var k: i64 = 0
241 while k < row.name_len {
242 fullpath[15 + k] = row.name_ptr[k]
243 k = k + 1
244 }
245 fullpath[15 + row.name_len] = 0
246
247 summary_out_ptr.total_in_scope = summary_out_ptr.total_in_scope + 1
248
249 let v: i64 = nx_saudit_check_one_file(fullpath,
250 15 + row.name_len,
251 finding)
252 if v == NX_SAUDIT_VERDICT_CLEAN {
253 summary_out_ptr.ok_count = summary_out_ptr.ok_count + 1
254 }
255 if v == NX_SAUDIT_VIOLATION_MISSING_HEADER {
256 summary_out_ptr.missing_header_count = summary_out_ptr.missing_header_count + 1
257 summary_out_ptr.exit_code_bits = summary_out_ptr.exit_code_bits | NX_SAUDIT_VIOLATION_MISSING_HEADER
258 }
259 if v == NX_SAUDIT_VIOLATION_BAD_REFERENCE {
260 summary_out_ptr.bad_reference_count = summary_out_ptr.bad_reference_count + 1
261 summary_out_ptr.exit_code_bits = summary_out_ptr.exit_code_bits | NX_SAUDIT_VIOLATION_BAD_REFERENCE
262 }
263 if v == NX_SAUDIT_VIOLATION_SUNSET_OVERDUE {
264 summary_out_ptr.sunset_overdue_count = summary_out_ptr.sunset_overdue_count + 1
265 summary_out_ptr.exit_code_bits = summary_out_ptr.exit_code_bits | NX_SAUDIT_VIOLATION_SUNSET_OVERDUE
266 }
267 if v == NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT {
268 summary_out_ptr.auditor_self_exempt_count = summary_out_ptr.auditor_self_exempt_count + 1
269 summary_out_ptr.exit_code_bits = summary_out_ptr.exit_code_bits | NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT
270 }
271 }
272 }
273 }
274 i = i + 1
275 }
276
277 if summary_out_ptr.exit_code_bits == 0 {
278 summary_out_ptr.overall_verdict = NX_SAUDIT_VERDICT_CLEAN
279 } else {
280 summary_out_ptr.overall_verdict = NX_SAUDIT_VERDICT_VIOLATIONS_PRESENT
281 }
282 return summary_out_ptr.overall_verdict
283}
284
285// nx_saudit_check_one_file -- per-file audit; used by editor / pre-commit
286// for fast single-file checks before running the whole repo sweep.
287//
288// GRADUATED 2026-05-20 from PARTIAL_WIRED -> wired-at-basic-glue.
289// Composes sys_read_file + nx_str_find for marker detection.
290// Checks:
291// 1. Does the file contain "# SCAFFOLDING_AUDIT_SELF_EXEMPT" (loophole)?
292// -> NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT
293// 2. Does it contain "# SCAFFOLDING_REPLACED_BY:" header?
294// -> if YES: NX_SAUDIT_VERDICT_CLEAN
295// -> if NO: NX_SAUDIT_VIOLATION_MISSING_HEADER
296// Per-primitive existence + wired_status checks (BAD_REFERENCE +
297// SUNSET_OVERDUE) still queued -- require parsing primitive names out of
298// the header value and nx_fs_stat against runtime/nx_<name>.nx.
299
300func nx_saudit_check_one_file(file_path_ptr: *u8, file_path_len: i64,
301 finding_out_ptr: *NxSauditFinding) -> i64 {
302 if file_path_len <= 0 { return NX_SAUDIT_VERDICT_DEPENDENCY_MISSING }
303
304 let content_len_ptr: *i64 = sys_mmap(16) as *i64
305 let content: *u8 = sys_read_file(file_path_ptr, content_len_ptr)
306 let content_len: i64 = content_len_ptr[0]
307
308 if content_len <= 0 {
309 // Unreadable / empty file -- treat as missing-header so it surfaces.
310 finding_out_ptr.violation_class = NX_SAUDIT_VIOLATION_MISSING_HEADER
311 return NX_SAUDIT_VIOLATION_MISSING_HEADER
312 }
313
314 // Build "# SCAFFOLDING_AUDIT_SELF_EXEMPT" marker (30 bytes) first --
315 // self-exempt is a stronger violation than missing-header, so we check
316 // it before the proper-header check.
317 let exempt: *u8 = sys_mmap(40)
318 exempt[0]=0x23 as u8; exempt[1]=0x20 as u8 // "# "
319 exempt[2]=0x53 as u8; exempt[3]=0x43 as u8; exempt[4]=0x41 as u8 // "SCA"
320 exempt[5]=0x46 as u8; exempt[6]=0x46 as u8; exempt[7]=0x4F as u8 // "FFO"
321 exempt[8]=0x4C as u8; exempt[9]=0x44 as u8; exempt[10]=0x49 as u8 // "LDI"
322 exempt[11]=0x4E as u8; exempt[12]=0x47 as u8 // "NG"
323 exempt[13]=0x5F as u8 // "_"
324 exempt[14]=0x41 as u8; exempt[15]=0x55 as u8; exempt[16]=0x44 as u8 // "AUD"
325 exempt[17]=0x49 as u8; exempt[18]=0x54 as u8 // "IT"
326 exempt[19]=0x5F as u8 // "_"
327 exempt[20]=0x53 as u8; exempt[21]=0x45 as u8; exempt[22]=0x4C as u8 // "SEL"
328 exempt[23]=0x46 as u8 // "F"
329 exempt[24]=0x5F as u8 // "_"
330 exempt[25]=0x45 as u8; exempt[26]=0x58 as u8; exempt[27]=0x45 as u8 // "EXE"
331 exempt[28]=0x4D as u8; exempt[29]=0x50 as u8; exempt[30]=0x54 as u8 // "MPT"
332
333 let epos: i64 = nx_str_find(content, content_len, exempt, 31) as i64
334 if epos >= 0 {
335 // Auditor self-exemption REFUSED per cardinal.
336 finding_out_ptr.violation_class = NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT
337 return NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT
338 }
339
340 // Build "# SCAFFOLDING_REPLACED_BY:" marker (26 bytes).
341 let marker: *u8 = sys_mmap(40)
342 marker[0]=0x23 as u8; marker[1]=0x20 as u8 // "# "
343 marker[2]=0x53 as u8; marker[3]=0x43 as u8; marker[4]=0x41 as u8 // "SCA"
344 marker[5]=0x46 as u8; marker[6]=0x46 as u8; marker[7]=0x4F as u8 // "FFO"
345 marker[8]=0x4C as u8; marker[9]=0x44 as u8; marker[10]=0x49 as u8 // "LDI"
346 marker[11]=0x4E as u8; marker[12]=0x47 as u8 // "NG"
347 marker[13]=0x5F as u8 // "_"
348 marker[14]=0x52 as u8; marker[15]=0x45 as u8; marker[16]=0x50 as u8 // "REP"
349 marker[17]=0x4C as u8; marker[18]=0x41 as u8; marker[19]=0x43 as u8 // "LAC"
350 marker[20]=0x45 as u8; marker[21]=0x44 as u8 // "ED"
351 marker[22]=0x5F as u8 // "_"
352 marker[23]=0x42 as u8; marker[24]=0x59 as u8 // "BY"
353 marker[25]=0x3A as u8 // ":"
354
355 let pos: i64 = nx_str_find(content, content_len, marker, 26) as i64
356 if pos >= 0 {
357 finding_out_ptr.violation_class = 0 // CLEAN -- no violation
358 return NX_SAUDIT_VERDICT_CLEAN
359 }
360
361 finding_out_ptr.violation_class = NX_SAUDIT_VIOLATION_MISSING_HEADER
362 return NX_SAUDIT_VIOLATION_MISSING_HEADER
363}
364
365// nx_saudit_in_scope -- inspector: does this file fall under audit scope?
366// Returns NX_SAUDIT_SCOPE_* class or NX_SAUDIT_SCOPE_OUT_OF_SCOPE.
367
368func nx_saudit_in_scope(file_path_ptr: *u8, file_path_len: i64) -> i64 {
369 if file_path_len <= 0 { return NX_SAUDIT_SCOPE_OUT_OF_SCOPE }
370 return NX_SAUDIT_SCOPE_OUT_OF_SCOPE
371}
372
373// nx_saudit_refuse_self_exempt -- per cardinal: NO auditor may exempt
374// itself from the rule it enforces. This function structurally refuses
375// any SCAFFOLDING_AUDIT_SELF_EXEMPT marker. Closes the loophole my
376// earlier shell lint exploited.
377
378func nx_saudit_refuse_self_exempt(file_path_ptr: *u8, file_path_len: i64) -> i64 {
379 if file_path_len <= 0 { return 0 }
380 // PARTIAL_WIRED: marker detection queued. When wired, returns
381 // NX_SAUDIT_VIOLATION_AUDITOR_SELF_EXEMPT if the marker is present.
382 return 0
383}
384
385// nx_saudit_get_last_verdict -- inspector.
386
387func nx_saudit_get_last_verdict() -> i64 {
388 return NX_SAUDIT_VERDICT_DEPENDENCY_MISSING
389}