nx_doc_census.nx source
↩ module page · 607 lines · 23157 B
1// nx_doc_census.nx -- D1 stone of NISHI_DOCS_DOCTRINE_ROADMAP V2.
2//
3// Bits-up classifier for substrate documentation files. Operation-
4// alizes the V2 header schema introduced in docs doctrine V2:
5// **Quadrant:** (Diátaxis)
6// **Topic Type:** (DITA)
7// **Status:** (lifecycle)
8// **Trust State:** (composes nx_trust_state cardinal)
9// **Competitive State:** (composes nx_competitive_state cardinal)
10//
11// Per [[feedback-docs-above-book-hyper-efficient-structured-delivery]]
12// + [[feedback-audits-are-ecosystem-citizens-not-islands]] cardinals:
13// this audit measures the substrate's own docs against the V2 KPI
14// surface + emits NxEtgEntry into the substrate's attestation chain.
15//
16// Composes:
17// nx_grep (S1; substring pattern detection in doc bytes)
18// nx_string_ops (helpers)
19// nx_etg (NxEtgEntry attestation)
20// nx_trust_state (composed: docs themselves have trust state)
21// nx_competitive_state (composed: docs decay too)
22//
23// **Quadrant:** REFERENCE + EXPLANATION (Diátaxis)
24// **Topic Type:** REFERENCE (DITA)
25// **Status:** DRAFT (D1 first stone)
26// **Trust State:** WRITTEN_UNTESTED (this file is brand-new; not yet
27// KAT-passed; per the never-trust-just-written cardinal)
28// **Competitive State:** UNCLASSIFIED (no benchmark vs Diátaxis-
29// linter / Vale.sh / textlint incumbents yet)
30
31// nx_safety_envelope:
32// intended_use: "audit substrate docs for V2 header schema
33// compliance; classify; aggregate; attest"
34// sil_target: SIL2
35// evidence: [kat_quadrant_classifier,
36// kat_topic_type_classifier,
37// kat_status_classifier,
38// kat_trust_competitive_presence,
39// kat_aggregator_arithmetic,
40// kat_etg_attestation_emitted]
41// hazard_register: [bug-tape-marker-substring-false-positive,
42// bug-tape-multiple-markers-classification,
43// bug-tape-pct-divbyzero]
44// verdict: NOT_YET_EVALUATED
45
46import "nx_syscalls.nx"
47import "nx_string_ops.nx"
48import "nx_grep.nx"
49import "nx_grep_rt.nx" // nx_grep_any lives here, not in nx_grep.nx
50import "nx_etg.nx"
51import "nx_dir.nx"
52const NX_MAGIC_65536: i64 = 65536
53const NX_MAGIC_1024: i64 = 1024
54
55// ===== Diátaxis quadrant sealed enum ==============================
56
57const NX_DOC_Q_NONE: i64 = 0
58const NX_DOC_Q_TUTORIAL: i64 = 1
59const NX_DOC_Q_HOW_TO: i64 = 2
60const NX_DOC_Q_REFERENCE: i64 = 3
61const NX_DOC_Q_EXPLANATION: i64 = 4
62const NX_DOC_Q_MIXED: i64 = 5
63const NX_DOC_Q_N: i64 = 6
64
65func nx_doc_q_is_valid(q: i64) -> i64 {
66 if q < 0 { return 0 }
67 if q >= NX_DOC_Q_N { return 0 }
68 return 1
69}
70
71func nx_doc_q_name(q: i64) -> *u8 {
72 if q == NX_DOC_Q_NONE { return "NONE" }
73 if q == NX_DOC_Q_TUTORIAL { return "TUTORIAL" }
74 if q == NX_DOC_Q_HOW_TO { return "HOW_TO" }
75 if q == NX_DOC_Q_REFERENCE { return "REFERENCE" }
76 if q == NX_DOC_Q_EXPLANATION { return "EXPLANATION" }
77 if q == NX_DOC_Q_MIXED { return "MIXED" }
78 return "UNKNOWN"
79}
80
81// ===== DITA topic type sealed enum ================================
82
83const NX_DOC_T_NONE: i64 = 0
84const NX_DOC_T_CONCEPT: i64 = 1
85const NX_DOC_T_TASK: i64 = 2
86const NX_DOC_T_REFERENCE: i64 = 3
87const NX_DOC_T_PATTERN: i64 = 4
88const NX_DOC_T_EXPLANATION: i64 = 5
89const NX_DOC_T_N: i64 = 6
90
91func nx_doc_t_is_valid(t: i64) -> i64 {
92 if t < 0 { return 0 }
93 if t >= NX_DOC_T_N { return 0 }
94 return 1
95}
96
97func nx_doc_t_name(t: i64) -> *u8 {
98 if t == NX_DOC_T_NONE { return "NONE" }
99 if t == NX_DOC_T_CONCEPT { return "CONCEPT" }
100 if t == NX_DOC_T_TASK { return "TASK" }
101 if t == NX_DOC_T_REFERENCE { return "REFERENCE" }
102 if t == NX_DOC_T_PATTERN { return "PATTERN" }
103 if t == NX_DOC_T_EXPLANATION { return "EXPLANATION" }
104 return "UNKNOWN"
105}
106
107// ===== Status sealed enum =========================================
108
109const NX_DOC_S_NONE: i64 = 0
110const NX_DOC_S_DRAFT: i64 = 1
111const NX_DOC_S_CANONICAL: i64 = 2
112const NX_DOC_S_SUPERSEDED: i64 = 3
113const NX_DOC_S_DEPRECATED: i64 = 4
114const NX_DOC_S_N: i64 = 5
115
116func nx_doc_s_is_valid(s: i64) -> i64 {
117 if s < 0 { return 0 }
118 if s >= NX_DOC_S_N { return 0 }
119 return 1
120}
121
122func nx_doc_s_name(s: i64) -> *u8 {
123 if s == NX_DOC_S_NONE { return "NONE" }
124 if s == NX_DOC_S_DRAFT { return "DRAFT" }
125 if s == NX_DOC_S_CANONICAL { return "CANONICAL" }
126 if s == NX_DOC_S_SUPERSEDED { return "SUPERSEDED" }
127 if s == NX_DOC_S_DEPRECATED { return "DEPRECATED" }
128 return "UNKNOWN"
129}
130
131// ===== Marker detectors via nx_grep ===============================
132//
133// V2 header schema uses Markdown-bold markers like '**Quadrant:**
134// TUTORIAL'. We scan for the LONGEST distinctive marker first so
135// EXPLANATION isn't shadowed by REFERENCE (substring containment).
136
137func nx_doc_classify_quadrant(content: *u8, n: i64) -> i64 {
138 if nx_grep_any(content, n, "**Quadrant:** TUTORIAL", 22) == 1 { return NX_DOC_Q_TUTORIAL }
139 if nx_grep_any(content, n, "**Quadrant:** HOW_TO", 20) == 1 { return NX_DOC_Q_HOW_TO }
140 if nx_grep_any(content, n, "**Quadrant:** REFERENCE + EXPLANATION", 37) == 1 { return NX_DOC_Q_MIXED }
141 if nx_grep_any(content, n, "**Quadrant:** MIXED", 19) == 1 { return NX_DOC_Q_MIXED }
142 if nx_grep_any(content, n, "**Quadrant:** EXPLANATION", 25) == 1 { return NX_DOC_Q_EXPLANATION }
143 if nx_grep_any(content, n, "**Quadrant:** REFERENCE", 23) == 1 { return NX_DOC_Q_REFERENCE }
144 return NX_DOC_Q_NONE
145}
146
147func nx_doc_classify_topic_type(content: *u8, n: i64) -> i64 {
148 if nx_grep_any(content, n, "**Topic Type:** CONCEPT", 23) == 1 { return NX_DOC_T_CONCEPT }
149 if nx_grep_any(content, n, "**Topic Type:** TASK", 20) == 1 { return NX_DOC_T_TASK }
150 if nx_grep_any(content, n, "**Topic Type:** REFERENCE", 25) == 1 { return NX_DOC_T_REFERENCE }
151 if nx_grep_any(content, n, "**Topic Type:** PATTERN", 23) == 1 { return NX_DOC_T_PATTERN }
152 if nx_grep_any(content, n, "**Topic Type:** EXPLANATION", 27) == 1 { return NX_DOC_T_EXPLANATION }
153 return NX_DOC_T_NONE
154}
155
156func nx_doc_classify_status(content: *u8, n: i64) -> i64 {
157 if nx_grep_any(content, n, "**Status:** CANONICAL", 21) == 1 { return NX_DOC_S_CANONICAL }
158 if nx_grep_any(content, n, "**Status:** SUPERSEDED", 22) == 1 { return NX_DOC_S_SUPERSEDED }
159 if nx_grep_any(content, n, "**Status:** DEPRECATED", 22) == 1 { return NX_DOC_S_DEPRECATED }
160 if nx_grep_any(content, n, "**Status:** DRAFT", 17) == 1 { return NX_DOC_S_DRAFT }
161 return NX_DOC_S_NONE
162}
163
164// ===== Presence detectors for Trust + Competitive markers =========
165
166func nx_doc_has_trust_state(content: *u8, n: i64) -> i64 {
167 if nx_grep_any(content, n, "**Trust State:**", 16) == 1 { return 1 }
168 return 0
169}
170
171func nx_doc_has_competitive_state(content: *u8, n: i64) -> i64 {
172 if nx_grep_any(content, n, "**Competitive State:**", 22) == 1 { return 1 }
173 return 0
174}
175
176// V2 doctrine: research-honest docs cite WebSearch-verified sources.
177// Returns 1 if the doc has any of the WebSearch-verified provenance
178// markers.
179func nx_doc_has_websearch_provenance(content: *u8, n: i64) -> i64 {
180 if nx_grep_any(content, n, "WebSearch-verified", 18) == 1 { return 1 }
181 if nx_grep_any(content, n, "WebSearch verified", 18) == 1 { return 1 }
182 if nx_grep_any(content, n, "Sources:", 8) == 1 { return 1 }
183 return 0
184}
185
186// V2 doctrine: bidirectional links use [[name]] Zettelkasten style.
187// Returns 1 if doc has any bidirectional link.
188func nx_doc_has_bidirectional_links(content: *u8, n: i64) -> i64 {
189 if nx_grep_any(content, n, "[[", 2) == 1 { return 1 }
190 return 0
191}
192
193// ===== Per-doc V2 compliance record ==============================
194
195struct NxDocV2Record {
196 quadrant: i64, // NX_DOC_Q_*
197 topic_type: i64, // NX_DOC_T_*
198 status: i64, // NX_DOC_S_*
199 has_trust_state: i64, // 0/1
200 has_competitive_state: i64, // 0/1
201 has_websearch_provenance: i64, // 0/1
202 has_bidirectional_links: i64, // 0/1
203 compliance_score: i64, // 0..7 (sum of axes met)
204}
205
206func nx_doc_classify(
207 content: *u8, n: i64,
208 r: *NxDocV2Record
209) -> i64 {
210 r.quadrant = nx_doc_classify_quadrant(content, n)
211 r.topic_type = nx_doc_classify_topic_type(content, n)
212 r.status = nx_doc_classify_status(content, n)
213 r.has_trust_state = nx_doc_has_trust_state(content, n)
214 r.has_competitive_state = nx_doc_has_competitive_state(content, n)
215 r.has_websearch_provenance = nx_doc_has_websearch_provenance(content, n)
216 r.has_bidirectional_links = nx_doc_has_bidirectional_links(content, n)
217
218 var score: i64 = 0
219 if r.quadrant != NX_DOC_Q_NONE { score = score + 1 }
220 if r.topic_type != NX_DOC_T_NONE { score = score + 1 }
221 if r.status != NX_DOC_S_NONE { score = score + 1 }
222 if r.has_trust_state == 1 { score = score + 1 }
223 if r.has_competitive_state == 1 { score = score + 1 }
224 if r.has_websearch_provenance == 1 { score = score + 1 }
225 if r.has_bidirectional_links == 1 { score = score + 1 }
226 r.compliance_score = score
227 return 0
228}
229
230// ===== Aggregator counts ==========================================
231
232struct NxDocCensusCounts {
233 n_total: i64,
234 n_quadrant_declared: i64,
235 n_topic_type_declared: i64,
236 n_status_declared: i64,
237 n_trust_state_declared: i64,
238 n_competitive_state_declared: i64,
239 n_websearch_provenance: i64,
240 n_bidirectional_links: i64,
241 n_full_compliance: i64, // score == 7
242 n_zero_compliance: i64, // score == 0
243}
244
245func nx_doc_census_init(c: *NxDocCensusCounts) {
246 c.n_total = 0
247 c.n_quadrant_declared = 0
248 c.n_topic_type_declared = 0
249 c.n_status_declared = 0
250 c.n_trust_state_declared = 0
251 c.n_competitive_state_declared = 0
252 c.n_websearch_provenance = 0
253 c.n_bidirectional_links = 0
254 c.n_full_compliance = 0
255 c.n_zero_compliance = 0
256}
257
258func nx_doc_census_accumulate(c: *NxDocCensusCounts, r: *NxDocV2Record) -> i64 {
259 c.n_total = c.n_total + 1
260 if r.quadrant != NX_DOC_Q_NONE { c.n_quadrant_declared = c.n_quadrant_declared + 1 }
261 if r.topic_type != NX_DOC_T_NONE { c.n_topic_type_declared = c.n_topic_type_declared + 1 }
262 if r.status != NX_DOC_S_NONE { c.n_status_declared = c.n_status_declared + 1 }
263 if r.has_trust_state == 1 { c.n_trust_state_declared = c.n_trust_state_declared + 1 }
264 if r.has_competitive_state == 1 { c.n_competitive_state_declared = c.n_competitive_state_declared + 1 }
265 if r.has_websearch_provenance == 1 { c.n_websearch_provenance = c.n_websearch_provenance + 1 }
266 if r.has_bidirectional_links == 1 { c.n_bidirectional_links = c.n_bidirectional_links + 1 }
267 if r.compliance_score == 7 { c.n_full_compliance = c.n_full_compliance + 1 }
268 if r.compliance_score == 0 { c.n_zero_compliance = c.n_zero_compliance + 1 }
269 return 0
270}
271
272// Compliance percentage (compliance_score sum / max possible) * 100.
273// Returns 0 if no docs scanned (no div-by-zero; INCONCLUSIVE shape).
274func nx_doc_census_pct_compliance(c: *NxDocCensusCounts) -> i64 {
275 if c.n_total <= 0 { return 0 }
276 let max_possible: i64 = c.n_total * 7
277 let achieved: i64 = c.n_quadrant_declared + c.n_topic_type_declared +
278 c.n_status_declared + c.n_trust_state_declared +
279 c.n_competitive_state_declared + c.n_websearch_provenance +
280 c.n_bidirectional_links
281 return (achieved * 100) / max_possible
282}
283
284// ===== ETG attestation ============================================
285//
286// Maps compliance % to outcome:
287// >= 75% -> CONFIRMED (docs at substrate-honest health)
288// >= 50% -> INCONCLUSIVE (mid-band; needs work)
289// < 50% -> FALSIFIED (docs proliferation pathology)
290
291func nx_doc_census_to_etg_outcome(c: *NxDocCensusCounts) -> i64 {
292 if c.n_total <= 0 { return NX_ETG_OUTCOME_INCONCLUSIVE }
293 let pct: i64 = nx_doc_census_pct_compliance(c)
294 if pct >= 75 { return NX_ETG_OUTCOME_CONFIRMED }
295 if pct >= 50 { return NX_ETG_OUTCOME_INCONCLUSIVE }
296 return NX_ETG_OUTCOME_FALSIFIED
297}
298
299func nx_doc_census_attest(
300 c: *NxDocCensusCounts,
301 entry: *NxEtgEntry,
302 silicon_serial: i64,
303 selector_version: i64,
304 timestamp_q14: i64
305) -> i64 {
306 let outcome: i64 = nx_doc_census_to_etg_outcome(c)
307 let pct: i64 = nx_doc_census_pct_compliance(c)
308 return nx_etg_entry_init(
309 entry,
310 silicon_serial,
311 NX_ETG_PROBE_AUDIT_GENEALOGY_MAP, // reusing audit probe family
312 NX_ETG_CLAIM_PRIOR_CALIBRATION,
313 c.n_total, // claim: docs scanned
314 pct, // measurement: compliance %
315 outcome,
316 selector_version,
317 timestamp_q14
318 )
319}
320
321// ===== Path join helper ===========================================
322//
323// Build "<dir>/<name>" + NUL into out buffer. Substrate-honest no
324// silent overrun. Returns total bytes (excluding NUL) or -1.
325// (Local copy because nx_tool_census's _census_path_join is file-
326// scoped; future consolidation queued -- per the no-tool-
327// proliferation bit-level cardinal, this duplicate should fold
328// into a shared nx_path_join primitive.)
329
330func _doccensus_path_join(
331 dir: *u8, n_dir: i64,
332 name: *u8, n_name: i64,
333 out: *u8, out_cap: i64
334) -> i64 {
335 if n_dir + 1 + n_name + 1 > out_cap { return -1 }
336 var i: i64 = 0
337 while i < n_dir {
338 out[i] = dir[i]
339 i = i + 1
340 }
341 out[n_dir] = 47 // '/'
342 var j: i64 = 0
343 while j < n_name {
344 out[n_dir + 1 + j] = name[j]
345 j = j + 1
346 }
347 out[n_dir + 1 + n_name] = 0 // NUL
348 return n_dir + 1 + n_name
349}
350
351// ===== Walk + classify + accumulate ==============================
352//
353// Opens dir_path; lists entries via nx_dir_list; for each regular-
354// file with .md suffix opens + reads up to 4 KiB header + classifies
355// via nx_doc_classify + accumulates into NxDocCensusCounts.
356//
357// Composes nx_dir_list (LIST) + sys_openat_rd/sys_read/sys_close
358// (READ) + nx_doc_classify (CLASSIFY) -- all bits-up NishiLang.
359//
360// Returns 0 on success; negative on dir-list failure.
361
362func nx_doc_census_walk_dir(
363 dir_path: *u8, n_dir_path: i64,
364 counts: *NxDocCensusCounts
365) -> i64 {
366 // 512 rows × 32B + 64KB name arena = 80KB total. Handles
367 // substrate's ~278 .md docs in one shot. Substrate-honest
368 // lift of the prior 64-row cap. If a dir genuinely has
369 // >512 entries, result.verdict will be NX_DIR_TRUNCATED;
370 // we accept TRUNCATED but report what we got.
371 let rows_buf: *u8 = sys_mmap(512 * NX_DIR_ROW_BYTES + 64)
372 let rows: *NxDirRow = rows_buf as *NxDirRow
373 let name_arena: *u8 = sys_mmap(NX_MAGIC_65536)
374 let result_buf: *u8 = sys_mmap(NX_DIR_RESULT_BYTES + 16)
375 let result: *NxDirResult = result_buf as *NxDirResult
376
377 nx_dir_list(dir_path, rows, 512, name_arena, NX_MAGIC_65536, 0, result)
378 if result.verdict != NX_DIR_OK {
379 if result.verdict != NX_DIR_TRUNCATED { return -10 }
380 }
381
382 // 64 KiB buffer per doc + read-until-EOF loop. Bumped from
383 // single 16 KiB sys_read because Sources blocks at the end of
384 // 30-55 KB roadmaps were beyond the window. 64 KiB covers
385 // ~95% of substrate roadmaps; the few >64 KB docs (TLS 1.3 /
386 // multi-roadmap merges) get TRUNCATED -- substrate-honest
387 // disclosure via the verdict.
388 let hdr_buf: *u8 = sys_mmap(NX_MAGIC_65536)
389 let path_buf: *u8 = sys_mmap(NX_MAGIC_1024)
390 let r_buf: *u8 = sys_mmap(128)
391 let r: *NxDocV2Record = r_buf as *NxDocV2Record
392 let md_suffix: *u8 = ".md"
393
394 var i: i64 = 0
395 while i < result.n_filled {
396 let row: *NxDirRow = nx_dir_row_at(rows, i)
397 if row.is_dotlike == 1 {
398 i = i + 1
399 continue
400 }
401 if row.dtype != NX_DT_REG {
402 i = i + 1
403 continue
404 }
405 // Filter to .md files only.
406 if nx_str_ends_with(row.name_ptr, row.name_len, md_suffix, 3) != 1 {
407 i = i + 1
408 continue
409 }
410
411 let p_len: i64 = _doccensus_path_join(
412 dir_path, n_dir_path,
413 row.name_ptr, row.name_len,
414 path_buf, NX_MAGIC_1024
415 )
416 if p_len < 0 {
417 i = i + 1
418 continue
419 }
420 let fd: i64 = sys_openat_rd(path_buf)
421 if fd < 0 {
422 i = i + 1
423 continue
424 }
425 // Read-until-EOF loop into 64 KiB buffer. TRUNCATED at 64 KiB
426 // (substrate-honest cap; future: streaming-scan to avoid the
427 // cap entirely; queued).
428 var content_len: i64 = 0
429 let cap_bytes: i64 = NX_MAGIC_65536
430 var keep_reading: i64 = 1
431 while keep_reading == 1 {
432 let remaining: i64 = cap_bytes - content_len
433 if remaining <= 0 {
434 keep_reading = 0
435 continue
436 }
437 let dst: *u8 = (hdr_buf as *u8) + content_len
438 let n_chunk: i64 = sys_read(fd, dst, remaining)
439 if n_chunk <= 0 {
440 keep_reading = 0
441 continue
442 }
443 content_len = content_len + n_chunk
444 }
445 sys_close(fd)
446
447 nx_doc_classify(hdr_buf, content_len, r)
448 nx_doc_census_accumulate(counts, r)
449 i = i + 1
450 }
451 return 0
452}
453
454// ===== Verdict reporting via sys_write ===========================
455//
456// Substrate-honest visibility: print counts to stdout so humans
457// can see the verdict without parsing attestation hashes. Per
458// the cardinal "document what is written" -- this primitive
459// documents what the audit found.
460
461// Helper: write a string literal + integer + newline to fd 1.
462// Allocates scratch each call (simple; caller doesn't manage).
463func _docc_write_kv(label: *u8, n_label: i64, value: i64) {
464 sys_write(1, label, n_label)
465 let buf: *u8 = sys_mmap(32)
466 let n_int: i64 = nx_str_format_int(value, buf, 32)
467 sys_write(1, buf, n_int)
468 sys_write(1, "\n", 1)
469}
470
471// Write the outcome name (CONFIRMED / INCONCLUSIVE / FALSIFIED) +
472// newline to fd 1.
473func _docc_write_outcome(outcome: i64) {
474 let prefix: *u8 = " outcome: "
475 sys_write(1, prefix, 19)
476 if outcome == NX_ETG_OUTCOME_CONFIRMED {
477 sys_write(1, "CONFIRMED", 9)
478 }
479 if outcome == NX_ETG_OUTCOME_INCONCLUSIVE {
480 sys_write(1, "INCONCLUSIVE", 12)
481 }
482 if outcome == NX_ETG_OUTCOME_FALSIFIED {
483 sys_write(1, "FALSIFIED", 9)
484 }
485 sys_write(1, "\n", 1)
486}
487
488func nx_doc_census_report_stdout(c: *NxDocCensusCounts) {
489 sys_write(1, "nx_doc_census verdict:\n", 23)
490 _docc_write_kv(" total: ", 19, c.n_total)
491 _docc_write_kv(" quadrant: ", 19, c.n_quadrant_declared)
492 _docc_write_kv(" topic_type: ", 19, c.n_topic_type_declared)
493 _docc_write_kv(" status: ", 19, c.n_status_declared)
494 _docc_write_kv(" trust_state: ", 19, c.n_trust_state_declared)
495 _docc_write_kv(" competitive: ", 19, c.n_competitive_state_declared)
496 _docc_write_kv(" websearch: ", 19, c.n_websearch_provenance)
497 _docc_write_kv(" bidir_links: ", 19, c.n_bidirectional_links)
498 _docc_write_kv(" full_compliance: ", 19, c.n_full_compliance)
499 _docc_write_kv(" zero_compliance: ", 19, c.n_zero_compliance)
500 _docc_write_kv(" pct_compliance: ", 19, nx_doc_census_pct_compliance(c))
501 _docc_write_outcome(nx_doc_census_to_etg_outcome(c))
502}
503
504// ===== Per-doc verdict listing ===================================
505//
506// Substrate names which specific docs are at which compliance score.
507// Lets V2 backfill prioritize specific docs rather than guessing.
508//
509// Output format per doc:
510// score=N/7 <name>\n
511// followed by the standard aggregate verdict.
512
513func _docc_write_doc_line(name: *u8, n_name: i64, score: i64) {
514 let prefix: *u8 = " score="
515 sys_write(1, prefix, 8)
516 let buf: *u8 = sys_mmap(8)
517 let n_int: i64 = nx_str_format_int(score, buf, 8)
518 sys_write(1, buf, n_int)
519 sys_write(1, "/7 ", 4)
520 sys_write(1, name, n_name)
521 sys_write(1, "\n", 1)
522}
523
524// Walk + classify + accumulate + EMIT PER-DOC LINE to fd 1.
525// Mirrors nx_doc_census_walk_dir but writes a `score=N/7 <name>`
526// line per doc to stdout. Same buffer/cap discipline.
527
528func nx_doc_census_walk_dir_listing(
529 dir_path: *u8, n_dir_path: i64,
530 counts: *NxDocCensusCounts
531) -> i64 {
532 let rows_buf: *u8 = sys_mmap(512 * NX_DIR_ROW_BYTES + 64)
533 let rows: *NxDirRow = rows_buf as *NxDirRow
534 let name_arena: *u8 = sys_mmap(NX_MAGIC_65536)
535 let result_buf: *u8 = sys_mmap(NX_DIR_RESULT_BYTES + 16)
536 let result: *NxDirResult = result_buf as *NxDirResult
537
538 nx_dir_list(dir_path, rows, 512, name_arena, NX_MAGIC_65536, 0, result)
539 if result.verdict != NX_DIR_OK {
540 if result.verdict != NX_DIR_TRUNCATED { return -10 }
541 }
542
543 let hdr_buf: *u8 = sys_mmap(NX_MAGIC_65536)
544 let path_buf: *u8 = sys_mmap(NX_MAGIC_1024)
545 let r_buf: *u8 = sys_mmap(128)
546 let r: *NxDocV2Record = r_buf as *NxDocV2Record
547 let md_suffix: *u8 = ".md"
548
549 sys_write(1, "nx_doc_census per-doc listing:\n", 31)
550
551 var i: i64 = 0
552 while i < result.n_filled {
553 let row: *NxDirRow = nx_dir_row_at(rows, i)
554 if row.is_dotlike == 1 {
555 i = i + 1
556 continue
557 }
558 if row.dtype != NX_DT_REG {
559 i = i + 1
560 continue
561 }
562 if nx_str_ends_with(row.name_ptr, row.name_len, md_suffix, 3) != 1 {
563 i = i + 1
564 continue
565 }
566
567 let p_len: i64 = _doccensus_path_join(
568 dir_path, n_dir_path,
569 row.name_ptr, row.name_len,
570 path_buf, NX_MAGIC_1024
571 )
572 if p_len < 0 {
573 i = i + 1
574 continue
575 }
576 let fd: i64 = sys_openat_rd(path_buf)
577 if fd < 0 {
578 i = i + 1
579 continue
580 }
581
582 var content_len: i64 = 0
583 let cap_bytes: i64 = NX_MAGIC_65536
584 var keep_reading: i64 = 1
585 while keep_reading == 1 {
586 let remaining: i64 = cap_bytes - content_len
587 if remaining <= 0 {
588 keep_reading = 0
589 continue
590 }
591 let dst: *u8 = (hdr_buf as *u8) + content_len
592 let n_chunk: i64 = sys_read(fd, dst, remaining)
593 if n_chunk <= 0 {
594 keep_reading = 0
595 continue
596 }
597 content_len = content_len + n_chunk
598 }
599 sys_close(fd)
600
601 nx_doc_classify(hdr_buf, content_len, r)
602 _docc_write_doc_line(row.name_ptr, row.name_len, r.compliance_score)
603 nx_doc_census_accumulate(counts, r)
604 i = i + 1
605 }
606 return 0
607}