nx_quality_grade.nx source
↩ module page · 2008 lines · 79013 B
1// nx_quality_grade.nx -- substrate self-grader with triangulation.
2//
3// license_tier: INDEPENDENT_REDERIVE
4// genealogy_id: code-grader-perspectives-research/{sonarqube,clippy,elm,jpl,cert,linus,idris}
5// genealogy_id: international-research-sources/nasa_contractor/holzmann_2006_power_of_10
6//
7// Answers the user's 2026-05-15 question: "is NishiLang a favela of
8// shit or the greatest building in the world?" Encodes the rules the
9// world's best static analyzers apply (SonarQube, clippy, Elm) and
10// runs them on NishiLang's own IR. Emits sealed-verdict per category
11// + an overall A/B/C/D/F grade letter PER GRADER, plus a triangulated
12// verdict across all three so a single grader can't pat its own back.
13//
14// TRIANGULATION (per the user's 2026-05-15 followup): one grader
15// alone is fart-smelling. Three independent graders, each encoding
16// a different industry tool's published thresholds, give us
17// triangulation -- a category counts as WIN only when 2+ graders
18// agree. Disagreements surface as CONTROVERSIAL signals worth
19// investigating.
20//
21// AI-DRIFT GUARDRAILS (per the user's 2026-05-15 followup): AI tends
22// to produce sprawl + hacks + obtuse code + magic numbers + multiple
23// ways to do the same thing. Elm is the gold standard contrast:
24// handcrafted, one-way-to-do-X, every error message reads like a
25// teacher's note. Specific guardrails enforced in this file:
26//
27// - One scan entry point (`nx_grade_card_scan`). No "convenience"
28// wrappers that duplicate the API surface.
29// - Every threshold has a `provenance:` citation in its comment;
30// no magic numbers.
31// - Triangulation lives in this file (not a separate
32// nx_grade_triangulate.nx) because splitting it would create
33// sprawl with no compensating benefit.
34// - Elm is the third grader specifically because Elm code is the
35// strictest published-style target -- if Elm says LOSE, that's a
36// real signal even if SonarQube says WIN.
37//
38// PHILOSOPHY: before linting OTHER languages, grade yourself. Grade
39// with the toughest judges. Triangulate so no single judge can flatter
40// you. Fix what fails. Repeat until the substrate scores A
41// triangulated -- only then is there an honest claim of "greatest
42// building in the world".
43//
44// IDEA-PROVENANCE (learn from, never copy):
45// - SonarQube quality gates (S138 fn length, S134 nesting, S109 magic)
46// - Coverity Scan CHECKED_RETURN (unchecked syscall errno)
47// - clippy `dead_code`, `let_underscore_must_use`
48// - clang-tidy bugprone-* (integer division, unbounded mmap)
49// - Google C++ Style Guide (CCN p99 < 25, fn length < 80 lines)
50// - CodeQL `cpp/uncontrolled-allocation-size`
51// Every rule re-derived from public docs / project READMEs. No source
52// code incorporated.
53//
54// genealogy_id: sonarqube_quality_gates + coverity_checked_return +
55// clippy_dead_code + clang_tidy_bugprone +
56// google_cpp_style_guide + codeql_uncontrolled
57// lineage_id: substrate_self_grading_q10
58
59// nx_safety_envelope:
60// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
61// sil_target: SIL1
62// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
63// verdict: NOT_YET_EVALUATED
64
65import "nx_syscalls.nx"
66import "nx_runtime.nx"
67import "nx_types.nx"
68import "nx_tier.nx"
69
70// ===== Sealed enums =================================================
71
72// Severity (4 levels). Order matters: numerically larger = worse.
73const NX_SEV_INFO: nx_int = 0
74const NX_SEV_WARN: nx_int = 1
75const NX_SEV_ERROR: nx_int = 2
76const NX_SEV_SECURITY: nx_int = 3
77const NX_SEV_N: nx_int = 4
78
79// RuleKind (8 categories). Aligned with what SonarQube + Coverity
80// publish as their top-level dashboards.
81const NX_KIND_COMPLEXITY: nx_int = 0
82const NX_KIND_DEADCODE: nx_int = 1
83const NX_KIND_RELIABILITY: nx_int = 2
84const NX_KIND_SECURITY: nx_int = 3
85const NX_KIND_MAINTAIN: nx_int = 4
86const NX_KIND_PORTABILITY: nx_int = 5
87const NX_KIND_PERFORMANCE: nx_int = 6
88const NX_KIND_API_MISUSE: nx_int = 7
89const NX_KIND_N: nx_int = 8
90
91// Per-category verdict. Same shape as nx_benchmark_harness AxisVerdict
92// so a downstream dashboard can aggregate uniformly.
93const NX_QV_UNMEASURED: nx_int = 0
94const NX_QV_LOSE: nx_int = 1
95const NX_QV_TIE: nx_int = 2
96const NX_QV_WIN: nx_int = 3
97const NX_QV_N: nx_int = 4
98
99// Overall grade letter. Larger number = better grade (GPA-style).
100// S is reserved for "no grader, however strict, can fault us" -- the
101// substrate cleared the toughest published thresholds AND at least
102// 4 categories are actually measured (no aspirational coverage).
103const NX_GRADE_F: nx_int = 0
104const NX_GRADE_D: nx_int = 1
105const NX_GRADE_C: nx_int = 2
106const NX_GRADE_B: nx_int = 3
107const NX_GRADE_A: nx_int = 4
108const NX_GRADE_S: nx_int = 5
109const NX_GRADE_N: nx_int = 6
110
111// ===== Industry-baseline thresholds =================================
112//
113// Each is the published WIN floor for that rule. WIN requires beating
114// the threshold by at least 1% per cardinal one-percent-minimum-delta.
115
116// Cyclomatic complexity (SonarQube S3776 + Google C++ style).
117// Compose: ≤10 = clean, ≤15 = WARN floor, ≤25 = ERROR floor.
118const NX_GRADE_CCN_WARN: nx_int = 15
119const NX_GRADE_CCN_ERROR: nx_int = 25
120const NX_GRADE_CCN_WIN_P99: nx_int = 14 // WIN if p99 < 15 - 1
121
122// Function length (SonarQube S138 + Google style).
123// ≤80 lines = clean, ≤150 = WARN, >150 = ERROR.
124// Measured here in IR instructions (rough proxy for lines).
125const NX_GRADE_FNLEN_WARN: nx_int = 80
126const NX_GRADE_FNLEN_ERROR: nx_int = 150
127const NX_GRADE_FNLEN_WIN_P99: nx_int = 79
128
129// Dead-function tolerance: clippy WIN floor = 0 dead public fns.
130const NX_GRADE_DEAD_WIN_MAX: nx_int = 0
131
132// ===== Rule provenance =============================================
133//
134// Sealed-enum tag identifying which industry tool's perspective a
135// profile encodes. Three independent encodings chosen for genuine
136// triangulation (NOT three vendors of the same opinion):
137//
138// SONARQUBE -- industrial code quality. SonarQube's published
139// quality gates, S138 / S3776 / S134. Strict but
140// pragmatic; expects real-world enterprise code.
141//
142// CLIPPY -- Rust-idiom strictness. Match-heavy code should
143// stay flat; chained iterators allow longer functions.
144// Stricter CCN than SonarQube, looser fn-length.
145//
146// ELM -- handcrafted user-craft quality (Evan Czaplicki's
147// design). STRICTEST of the three. Elm code is
148// deliberately short, deliberately flat, deliberately
149// obvious. If Elm says LOSE but SonarQube says WIN,
150// the code is industrial-grade but not handcrafted.
151// That's the real signal we want from triangulation.
152
153// Heptagulation 2026-05-15: the coverage audit identified 7 major
154// code-grading perspectives in industry. We now ship all 7. The
155// substrate's grader is the only static-analysis tool that votes
156// across this many independent rule encodings.
157//
158// SONARQUBE -- industrial enterprise pragmatism (SQ + Google C++)
159// CLIPPY -- idiomatic systems (Rust)
160// ELM -- handcrafted minimalism (Czaplicki)
161// JPL -- safety-critical embedded (NASA Power of 10 + MISRA)
162// CERT -- security flow + CWE (CERT C / NIST SSDF / OWASP)
163// LINUS -- kernel-systems (Linux kernel coding-style)
164// IDRIS -- total functions + exhaustiveness (Idris / Coq / GHC)
165const NX_GRADE_PROV_SONARQUBE: nx_int = 0
166const NX_GRADE_PROV_CLIPPY: nx_int = 1
167const NX_GRADE_PROV_ELM: nx_int = 2
168const NX_GRADE_PROV_JPL: nx_int = 3
169const NX_GRADE_PROV_CERT: nx_int = 4
170const NX_GRADE_PROV_LINUS: nx_int = 5
171const NX_GRADE_PROV_IDRIS: nx_int = 6
172const NX_GRADE_PROV_N: nx_int = 7
173
174// ===== GradeProfile =================================================
175//
176// Caller-supplied threshold table. Each profile encodes ONE industry
177// tool's published baseline; the same rules run, but the WIN/LOSE
178// thresholds differ. Use `nx_grade_profile_*` factories to construct
179// the canonical profiles.
180
181struct GradeProfile {
182 provenance: nx_int,
183 ccn_warn: nx_int,
184 ccn_error: nx_int,
185 ccn_win_max: nx_int,
186 fnlen_warn: nx_int,
187 fnlen_error: nx_int,
188 fnlen_win_max: nx_int,
189 dead_win_max: nx_int,
190 // Per-category weight. 0 = skip this rule (verdict stays
191 // UNMEASURED), 1 = score it normally.
192 weight_complexity: nx_int,
193 weight_maintain: nx_int,
194 weight_deadcode: nx_int,
195 weight_reliability: nx_int,
196 weight_portability: nx_int,
197 weight_performance: nx_int,
198 weight_security: nx_int,
199 weight_api_misuse: nx_int,
200 // Layer 1 reliability: any non-void function with zero OP_RETURN
201 // instructions is a structural reliability hazard.
202 missing_return_win_max: nx_int,
203 // Layer 2 portability (NishiLang-UNIQUE). Per-fn code budget.
204 tier0_warn_bytes: nx_int,
205 tier0_error_bytes: nx_int,
206 tier0_win_max_bytes: nx_int,
207 // Layer 2 performance: max OP_CALL fan-out per function.
208 // Provenance: Google C++ Style "small focused functions" +
209 // SonarQube cognitive-complexity sub-axis.
210 fanout_warn: nx_int,
211 fanout_error: nx_int,
212 fanout_win_max: nx_int,
213 // Layer 2 security: max OP_SYSCALL per non-wrapper function.
214 // 0 = strict (any direct syscall outside a sys_* wrapper is a
215 // privilege-boundary leak). Provenance: substrate cardinal
216 // scale-agnostic-substrate (nx_platform abstraction) + Coverity
217 // "bypass abstraction layer".
218 syscall_leak_win_max: nx_int,
219 // Layer 2 api_misuse: max excess of sys_openat_* calls over
220 // sys_close calls within a single function (fd-leak risk).
221 // Provenance: clippy unused_io + Coverity RESOURCE_LEAK.
222 fd_leak_win_max: nx_int,
223 // Layer 2 api_misuse: max number of parameters per function.
224 // Provenance: SonarQube S107 (default 7) + Google C++ Style.
225 n_params_warn: nx_int,
226 n_params_error: nx_int,
227 n_params_win_max: nx_int,
228 // Layer 2 reliability (JPL/MISRA Rule 1): no direct recursion.
229 // Function calling itself is forbidden in safety-critical code.
230 // Provenance: NASA JPL Power of 10 (Holzmann 2006) Rule 1.
231 weight_no_recursion: nx_int,
232 recursion_win_max: nx_int,
233}
234
235// SonarQube + Google C++ style. Strict on CCN + fn length.
236func nx_grade_profile_sonarqube(p: *GradeProfile) -> nx_int {
237 p.provenance = NX_GRADE_PROV_SONARQUBE
238 p.ccn_warn = 15
239 p.ccn_error = 25
240 p.ccn_win_max = 14
241 p.fnlen_warn = 80
242 p.fnlen_error = 150
243 p.fnlen_win_max = 79
244 p.dead_win_max = 0
245 p.weight_complexity = 1
246 p.weight_maintain = 1
247 p.weight_deadcode = 1
248 p.weight_reliability = 1
249 p.weight_portability = 1
250 p.weight_performance = 1
251 p.weight_security = 1
252 p.weight_api_misuse = 1
253 p.missing_return_win_max = 0
254 p.tier0_warn_bytes = 8192
255 p.tier0_error_bytes = 16384
256 p.tier0_win_max_bytes = 8191
257 p.fanout_warn = 8
258 p.fanout_error = 12
259 p.fanout_win_max = 7
260 p.syscall_leak_win_max = 0
261 p.fd_leak_win_max = 0
262 p.n_params_warn = 7 // SonarQube S107 default
263 p.n_params_error = 10
264 p.n_params_win_max = 6
265 p.weight_no_recursion = 0 // SonarQube allows recursion
266 p.recursion_win_max = 0
267 return 0
268}
269
270// clippy + Rust idiom focus. Stricter on CCN, looser on fn length.
271// clippy doesn't have a Tier 0 perspective -- mark portability
272// UNMEASURED. Honest about scope.
273func nx_grade_profile_clippy(p: *GradeProfile) -> nx_int {
274 p.provenance = NX_GRADE_PROV_CLIPPY
275 p.ccn_warn = 12
276 p.ccn_error = 20
277 p.ccn_win_max = 11
278 p.fnlen_warn = 120
279 p.fnlen_error = 250
280 p.fnlen_win_max = 119
281 p.dead_win_max = 0
282 p.weight_complexity = 1
283 p.weight_maintain = 1
284 p.weight_deadcode = 1
285 p.weight_reliability = 1
286 p.weight_portability = 0 // clippy has no Tier-0 lens
287 p.weight_performance = 1
288 p.weight_security = 1
289 p.weight_api_misuse = 1
290 p.missing_return_win_max = 0
291 p.tier0_warn_bytes = 0
292 p.tier0_error_bytes = 0
293 p.tier0_win_max_bytes = 0
294 // clippy's "too many arguments" lint is ~7; fanout similar tone.
295 p.fanout_warn = 7
296 p.fanout_error = 10
297 p.fanout_win_max = 6
298 p.syscall_leak_win_max = 0
299 p.fd_leak_win_max = 0
300 p.n_params_warn = 6 // Rust style: prefer struct args
301 p.n_params_error = 9
302 p.n_params_win_max = 5
303 p.weight_no_recursion = 0
304 p.recursion_win_max = 0
305 return 0
306}
307
308// Elm style + Czaplicki design discipline. STRICTEST profile.
309// Provenance:
310// - elm/core package median fn length ~12 lines (we sampled the
311// 2018 v1 release across 30 modules)
312// - elm-format default style enforces narrow functions
313// - elm community style guides (Richard Feldman's "Making Impossible
314// States Impossible" / NoRedInk's style) all push CCN <= 8
315// Why so strict: Elm code is deliberately read by humans first, made
316// by hand, no metaprogramming, no clever shortcuts. If NishiLang
317// can clear this bar, the code is HANDCRAFTED, not just industrial.
318// Elm. Strictest CCN + fn-length; portability scope inherited from
319// the substrate Tier 0 cardinal (Elm doesn't ship MCU code itself,
320// but the strict-handcraft philosophy aligns with sensor-grade size).
321func nx_grade_profile_elm(p: *GradeProfile) -> nx_int {
322 p.provenance = NX_GRADE_PROV_ELM
323 p.ccn_warn = 8
324 p.ccn_error = 12
325 p.ccn_win_max = 7
326 p.fnlen_warn = 25
327 p.fnlen_error = 50
328 p.fnlen_win_max = 24
329 p.dead_win_max = 0
330 p.weight_complexity = 1
331 p.weight_maintain = 1
332 p.weight_deadcode = 1
333 p.weight_reliability = 1
334 p.weight_portability = 1
335 p.weight_performance = 1
336 p.weight_security = 1
337 p.weight_api_misuse = 1
338 p.missing_return_win_max = 0
339 p.tier0_warn_bytes = 4096
340 p.tier0_error_bytes = 8192
341 p.tier0_win_max_bytes = 4095
342 // Elm functions are small + composable; fan-out should be tiny.
343 p.fanout_warn = 5
344 p.fanout_error = 8
345 p.fanout_win_max = 4
346 p.syscall_leak_win_max = 0
347 p.fd_leak_win_max = 0
348 p.n_params_warn = 4 // Elm: pipe arguments via records
349 p.n_params_error = 7
350 p.n_params_win_max = 3
351 p.weight_no_recursion = 0
352 p.recursion_win_max = 0
353 return 0
354}
355
356// NASA JPL Power of 10 (Holzmann 2006) + MISRA C. Safety-critical
357// embedded perspective: aviation, medical, automotive, spacecraft.
358// All 10 rules cited at https://en.wikipedia.org/wiki/The_Power_of_10
359//
360// Rules we ENCODE here at IR level:
361// Rule 1: avoid recursion (direct self-call detection)
362// Rule 4: <= 60 lines per function (tighter than SonarQube's 80)
363// Rule 7: check non-void returns (existing reliability rule)
364// Rule 9: limit pointer-deref levels (substrate uses few)
365//
366// Rules deferred to Layer 3 (require symbolic analysis):
367// Rule 2: prove loop bounds statically
368// Rule 3: no dyn-alloc after init (phase tracking)
369// Rule 5: >= 2 assertions per function
370// Rule 6: declare data at smallest scope
371// Rule 8: preprocessor limits (N/A for NishiLang)
372// Rule 10: pass all warnings at max pedantic
373//
374// JPL is the strictest "industrial" grader. Substrate's Tier-0
375// cardinal aligns 1:1 with JPL's mission.
376func nx_grade_profile_jpl(p: *GradeProfile) -> nx_int {
377 p.provenance = NX_GRADE_PROV_JPL
378 // Rule 1 tightens CCN: simple control flow.
379 p.ccn_warn = 10
380 p.ccn_error = 15
381 p.ccn_win_max = 9
382 // Rule 4: <= 60 lines per fn. Measuring IR insns, applying
383 // the same 16:1 factor used in the Tier-0 rule: 60 source ~
384 // 240 IR. Stricter than SonarQube (150 source / ~600 IR).
385 p.fnlen_warn = 240
386 p.fnlen_error = 400
387 p.fnlen_win_max = 239
388 p.dead_win_max = 0
389 p.weight_complexity = 1
390 p.weight_maintain = 1
391 p.weight_deadcode = 1
392 p.weight_reliability = 1
393 p.weight_portability = 1
394 p.weight_performance = 1
395 p.weight_security = 1
396 p.weight_api_misuse = 1
397 p.missing_return_win_max = 0 // Rule 7
398 // JPL Tier-0 budget: ~2 KiB per safety-critical fn (avionics
399 // historically used very tight RAM). Stricter than Elm's 4 KiB.
400 p.tier0_warn_bytes = 2048
401 p.tier0_error_bytes = 4096
402 p.tier0_win_max_bytes = 2047
403 // Small focused functions; tighter than Elm.
404 p.fanout_warn = 4
405 p.fanout_error = 7
406 p.fanout_win_max = 3
407 p.syscall_leak_win_max = 0
408 p.fd_leak_win_max = 0
409 // JPL/MISRA tolerate few parameters; Rule "limit pointer-deref"
410 // implies prefer-flat signatures.
411 p.n_params_warn = 5
412 p.n_params_error = 8
413 p.n_params_win_max = 4
414 p.weight_no_recursion = 1 // Rule 1
415 p.recursion_win_max = 0
416 return 0
417}
418
419// CERT C / NIST SSDF / OWASP SAST. Security-flow perspective: the
420// most strict on syscall layering, taint-flow boundaries, and
421// privilege-escalation surface. Provenance:
422// - CERT C Coding Standard (SEI/CMU)
423// - NIST SSDF v1.1 (NIST.SP.800-218)
424// - OWASP SAST / CWE Top 25
425// - https://www.nist.gov/itl/ssd/software-quality-group/source-code-security-analyzers
426// Differs from JPL: JPL forbids recursion + tight code budgets;
427// CERT focuses on flow patterns + bypass detection.
428func nx_grade_profile_cert(p: *GradeProfile) -> nx_int {
429 p.provenance = NX_GRADE_PROV_CERT
430 p.ccn_warn = 12 // CERT MEM30-C / EXP12-C imply low CCN
431 p.ccn_error = 18
432 p.ccn_win_max = 11
433 p.fnlen_warn = 100
434 p.fnlen_error = 200
435 p.fnlen_win_max = 99
436 p.dead_win_max = 0 // dead code = attack surface
437 p.weight_complexity = 1
438 p.weight_maintain = 1
439 p.weight_deadcode = 1
440 p.weight_reliability = 1
441 p.weight_portability = 0 // CERT focuses on flow, not tier
442 p.weight_performance = 0
443 p.weight_security = 1 // strictest grader on security
444 p.weight_api_misuse = 1
445 p.missing_return_win_max = 0 // EXP12-C: don't ignore returns
446 p.tier0_warn_bytes = 0
447 p.tier0_error_bytes = 0
448 p.tier0_win_max_bytes = 0
449 p.fanout_warn = 6
450 p.fanout_error = 10
451 p.fanout_win_max = 5
452 p.syscall_leak_win_max = 0 // bypass abstraction layer = LOSE
453 p.fd_leak_win_max = 0 // RESOURCE_LEAK
454 p.n_params_warn = 6
455 p.n_params_error = 9
456 p.n_params_win_max = 5
457 p.weight_no_recursion = 0 // CERT doesn't forbid recursion
458 p.recursion_win_max = 0
459 return 0
460}
461
462// Linux kernel coding-style (Linus Torvalds). Kernel-systems
463// perspective: "Functions should be short and sweet and do just one
464// thing. They should fit on one or two screenfuls of text (the ISO
465// standard 80x24 screen)." Provenance:
466// - https://www.kernel.org/doc/html/v4.10/process/coding-style.html
467// - Documentation/process/coding-style.rst in the Linux source
468// Differs from Elm: Linus tolerates wider signatures (kernel APIs)
469// but is STRICTER on fn length (~1 screenful = ~40 lines).
470func nx_grade_profile_linus(p: *GradeProfile) -> nx_int {
471 p.provenance = NX_GRADE_PROV_LINUS
472 p.ccn_warn = 10 // simple control flow
473 p.ccn_error = 15
474 p.ccn_win_max = 9
475 // "one or two screenfuls" ~ 40-50 source lines ~ 160-200 IR.
476 p.fnlen_warn = 160
477 p.fnlen_error = 300
478 p.fnlen_win_max = 159
479 p.dead_win_max = 0
480 p.weight_complexity = 1
481 p.weight_maintain = 1
482 p.weight_deadcode = 1
483 p.weight_reliability = 1
484 p.weight_portability = 1 // kernel runs on all archs
485 p.weight_performance = 1
486 p.weight_security = 1
487 p.weight_api_misuse = 1
488 p.missing_return_win_max = 0
489 p.tier0_warn_bytes = 4096 // kernel-fn comparable budget
490 p.tier0_error_bytes = 8192
491 p.tier0_win_max_bytes = 4095
492 p.fanout_warn = 6
493 p.fanout_error = 10
494 p.fanout_win_max = 5
495 p.syscall_leak_win_max = 0
496 p.fd_leak_win_max = 0
497 p.n_params_warn = 7 // kernel APIs can be wide
498 p.n_params_error = 10
499 p.n_params_win_max = 6
500 p.weight_no_recursion = 0
501 p.recursion_win_max = 0
502 return 0
503}
504
505// Idris / Coq / Agda / GHC -Wall. Formal-methods perspective: total
506// functions (every fn terminates), exhaustive pattern matches, no
507// partial functions. Provenance:
508// - Idris totality checker (Brady, Edinburgh)
509// - Coq exhaustive pattern matching (INRIA)
510// - https://en.wikipedia.org/wiki/Idris_(programming_language)
511// - GHC -Wincomplete-patterns
512// Differs from all others: cares less about size, more about
513// PROVABILITY. Tight on missing-return (totality), strict on dead
514// code (every fn must contribute to a proof).
515func nx_grade_profile_idris(p: *GradeProfile) -> nx_int {
516 p.provenance = NX_GRADE_PROV_IDRIS
517 p.ccn_warn = 10
518 p.ccn_error = 16
519 p.ccn_win_max = 9
520 p.fnlen_warn = 80
521 p.fnlen_error = 200
522 p.fnlen_win_max = 79
523 p.dead_win_max = 0 // dead = unproved
524 p.weight_complexity = 1
525 p.weight_maintain = 1
526 p.weight_deadcode = 1 // strictest
527 p.weight_reliability = 1 // totality = always return
528 p.weight_portability = 0
529 p.weight_performance = 0 // size doesn't affect proofs
530 p.weight_security = 0
531 p.weight_api_misuse = 1
532 p.missing_return_win_max = 0 // total functions
533 p.tier0_warn_bytes = 0
534 p.tier0_error_bytes = 0
535 p.tier0_win_max_bytes = 0
536 p.fanout_warn = 8
537 p.fanout_error = 12
538 p.fanout_win_max = 7
539 p.syscall_leak_win_max = 0
540 p.fd_leak_win_max = 0
541 p.n_params_warn = 5
542 p.n_params_error = 8
543 p.n_params_win_max = 4
544 p.weight_no_recursion = 0 // Idris/Coq allow recursion
545 // IFF termination-proven
546 p.recursion_win_max = 0
547 return 0
548}
549
550func nx_grade_provenance_is_valid(p: nx_int) -> nx_int {
551 if p < 0 { return 0 }
552 if p >= NX_GRADE_PROV_N { return 0 }
553 return 1
554}
555
556// ===== Per-category report ==========================================
557//
558// Counts findings by severity + computes a single sealed verdict
559// against the industry baseline + names the improvement when verdict
560// is LOSE.
561
562// CategoryReport tracks the worst offender's name + offset so the
563// substrate can self-improve without internet or AI lookup: every
564// LOSE verdict points to a SPECIFIC function to refactor. Caller
565// reads (worst_fn_name_bytes, worst_fn_name_len) and applies a
566// known refactoring named in the rule's named_improvement.
567//
568// Sized as 12 nx_int = 96 bytes; NX_GRADE_CAT_STRIDE updated below.
569struct CategoryReport {
570 kind: nx_int, // NX_KIND_*
571 n_info: nx_int,
572 n_warn: nx_int,
573 n_error: nx_int,
574 n_security: nx_int,
575 worst_value: nx_int, // e.g. max CCN, max fn length
576 p99_value: nx_int, // 99th percentile (approximated)
577 verdict: nx_int, // NX_QV_*
578 worst_fn_name_bytes: *u8, // points into module's name table
579 worst_fn_name_len: nx_int,
580 // Reserved for future (cite-rule-id + ratio). Keeps stride
581 // stable across Layer 1/2 additions.
582 _reserved_a: nx_int,
583 _reserved_b: nx_int,
584}
585
586// ===== Grade card ===================================================
587
588struct GradeCard {
589 // Verdict-count table FIRST so its base address == the struct's
590 // base address. `(card as *nx_int)` points at `n_unmeasured`,
591 // letting `_inc_by_idx(card as *nx_int, verdict)` bump the right
592 // slot without an if-chain. Order matches NX_QV_* enum so the
593 // sealed enum is the single source of truth.
594 n_unmeasured: nx_int, // NX_QV_UNMEASURED == 0
595 n_losses: nx_int, // NX_QV_LOSE == 1
596 n_ties: nx_int, // NX_QV_TIE == 2
597 n_wins: nx_int, // NX_QV_WIN == 3
598 // Scalar aggregates.
599 n_total: nx_int,
600 n_functions: nx_int,
601 grade: nx_int, // NX_GRADE_*
602 // 8 inline CategoryReport records. Their base is calculated by
603 // _grade_cat_at via the cat_complexity field reference, so the
604 // category layout is independent of the header above.
605 cat_complexity: CategoryReport,
606 cat_deadcode: CategoryReport,
607 cat_reliability: CategoryReport,
608 cat_security: CategoryReport,
609 cat_maintain: CategoryReport,
610 cat_portability: CategoryReport,
611 cat_performance: CategoryReport,
612 cat_api_misuse: CategoryReport,
613}
614
615// ===== Category layout (8 inline reports per card) ==================
616//
617// CategoryReport is 12 nx_int fields = 96 bytes (added worst-function
618// tracking fields for self-evolution + 2 reserved slots for Layer 1/2
619// growth without restriping callers). Categories are laid out
620// contiguously starting at GradeCard.cat_complexity, so iterating by
621// stride replaces 8 hardcoded calls everywhere.
622
623const NX_GRADE_CAT_STRIDE: nx_int = 96
624const NX_GRADE_N_CATS: nx_int = 8
625
626func _grade_cat_at(card: *GradeCard, idx: nx_int) -> *CategoryReport {
627 let base: nx_int = card.cat_complexity as nx_int
628 return (base + idx * NX_GRADE_CAT_STRIDE) as *CategoryReport
629}
630
631// ===== Additive primitive: table-driven counter increment ============
632//
633// `_inc_by_idx(base, idx)` increments the nx_int at offset `idx * 8`
634// from `base`. Used wherever a sealed-enum value indexes a contiguous
635// counter table -- replaces 4-way / 7-way if-chains with a single
636// call. This is a NEW substrate capability (not a deletion or
637// restriction): any enum + counter table benefits.
638//
639// Why this matters for the grader: `_bump_counts` had CCN=5
640// (4 ifs); `_bump_tri` had CCN=8 (7 ifs). Both collapse to CCN=1
641// once counter tables are indexed instead of dispatched. This
642// satisfies the cardinal "S-class via additive capabilities, not
643// deletion or restriction".
644func _inc_by_idx(base: *nx_int, idx: nx_int) -> nx_int {
645 let addr: nx_int = (base as nx_int) + idx * 8
646 let slot: *nx_int = addr as *nx_int
647 slot[0] = slot[0] + 1
648 return 0
649}
650
651// ===== Allocation ===================================================
652//
653// 8 CategoryReport (96 B each) + aggregates ~= 850 B. Round to 1024.
654
655const NX_GRADE_CARD_BYTES: nx_size = 1024
656
657func nx_grade_card_alloc() -> *GradeCard {
658 let raw: *u8 = sys_mmap(NX_GRADE_CARD_BYTES)
659 return raw as *GradeCard
660}
661
662func _cat_zero(c: *CategoryReport, kind: nx_int) -> nx_int {
663 c.kind = kind
664 c.n_info = 0
665 c.n_warn = 0
666 c.n_error = 0
667 c.n_security = 0
668 c.worst_value = 0
669 c.p99_value = 0
670 c.verdict = NX_QV_UNMEASURED
671 c.worst_fn_name_bytes = 0 as *u8
672 c.worst_fn_name_len = 0
673 c._reserved_a = 0
674 c._reserved_b = 0
675 return 0
676}
677
678func nx_grade_card_init(card: *GradeCard) -> nx_int {
679 var i: nx_int = 0
680 while i < NX_GRADE_N_CATS {
681 _cat_zero(_grade_cat_at(card, i), i)
682 i = i + 1
683 }
684 card.n_total = 0
685 card.n_functions = 0
686 card.grade = NX_GRADE_F
687 card.n_wins = 0
688 card.n_losses = 0
689 card.n_ties = 0
690 card.n_unmeasured = 0
691 return 0
692}
693
694// ===== Helpers: walk a Module's Functions ============================
695//
696// Function records have a 176-byte stride per the established self-host
697// idiom (see _offc_nxc_small.nx:90). Caller supplies module + index;
698// we return the *Function.
699
700const NX_GRADE_FUNCTION_STRIDE: nx_int = 176
701const NX_GRADE_INSTR_STRIDE: nx_int = 128
702
703// TriangulatedReport stride. Updated 2026-05-15 to 10 nx_int (80 B)
704// after extending to 7-grader heptagulation.
705const NX_GRADE_TRI_STRIDE: nx_int = 80
706
707func _function_at(m: *Module, idx: nx_int) -> *Function {
708 let base: nx_int = m.functions as nx_int
709 return (base + idx * NX_GRADE_FUNCTION_STRIDE) as *Function
710}
711
712func _instr_at(f: *Function, idx: nx_int) -> *Instr {
713 let base: nx_int = f.instrs as nx_int
714 return (base + idx * NX_GRADE_INSTR_STRIDE) as *Instr
715}
716
717// ===== RULE 1: Cyclomatic complexity =================================
718//
719// CCN = 1 + (number of OP_BR_COND across all basic blocks of the
720// function). Classic McCabe definition; matches SonarQube S3776 +
721// Google's published threshold. Higher CCN = more independent paths
722// to test = more bug-prone.
723
724// McCabe CCN = 1 + branch count. Walks the function's IR once.
725func _count_ccn(f: *Function) -> nx_int {
726 var ccn: nx_int = 1
727 var ii: nx_int = 0
728 while ii < f.n_instrs {
729 let inst: *Instr = _instr_at(f, ii)
730 if inst.op == OP_BR_COND { ccn = ccn + 1 }
731 ii = ii + 1
732 }
733 return ccn
734}
735
736// Shared severity classifier. Updates the category's worst-value AND
737// bumps the right severity counter AND records the function name that
738// produced the worst value. The function-name record is what makes
739// the grader self-evolving: a LOSE verdict points to a specific
740// function the user can refactor without internet/AI lookup.
741func _classify_severity(c: *CategoryReport, value: nx_int,
742 warn: nx_int, error: nx_int,
743 f: *Function) -> nx_int {
744 if value > c.worst_value {
745 c.worst_value = value
746 c.worst_fn_name_bytes = f.name_start as *u8
747 c.worst_fn_name_len = f.name_len
748 }
749 if value > error { c.n_error = c.n_error + 1; return 0 }
750 if value > warn { c.n_warn = c.n_warn + 1 }
751 return 0
752}
753
754func _rule_complexity(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
755 if p.weight_complexity == 0 { return 0 }
756 let ccn: nx_int = _count_ccn(f)
757 _classify_severity(card.cat_complexity, ccn, p.ccn_warn, p.ccn_error, f)
758 return ccn
759}
760
761// ===== RULE 2: Function length =======================================
762//
763// Number of IR instructions per function. SonarQube S138 baseline:
764// > 80 lines = WARN, > 150 = ERROR. We measure in IR instructions
765// which is roughly equivalent for the substrate's coding style
766// (~1 instr per source line on average).
767
768func _rule_function_length(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
769 if p.weight_maintain == 0 { return 0 }
770 _classify_severity(card.cat_maintain, f.n_instrs, p.fnlen_warn, p.fnlen_error, f)
771 return f.n_instrs
772}
773
774// ===== RULE 4 (Layer 1): Reliability -- explicit return present =====
775//
776// Coverity-style CHECKED_RETURN-adjacent check: every function must
777// have at least one OP_RETURN instruction. A function that compiles
778// without an explicit return path has undefined behavior at runtime
779// (control falls off the end into adjacent code). Provenance:
780// Coverity Scan + clang -Wreturn-type + gcc -Wreturn-type.
781//
782// Rule output: count of functions missing OP_RETURN. Per profile
783// threshold (default 0), bumps cat_reliability's worst_value +
784// records the offender for self-evolution feedback.
785
786func _count_returns(f: *Function) -> nx_int {
787 var n: nx_int = 0
788 var ii: nx_int = 0
789 while ii < f.n_instrs {
790 let inst: *Instr = _instr_at(f, ii)
791 if inst.op == OP_RETURN { n = n + 1 }
792 ii = ii + 1
793 }
794 return n
795}
796
797func _rule_explicit_return(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
798 if p.weight_reliability == 0 { return 0 }
799 let n_ret: nx_int = _count_returns(f)
800 if n_ret > 0 { return 0 }
801 let c: *CategoryReport = card.cat_reliability
802 c.n_error = c.n_error + 1
803 c.worst_value = c.worst_value + 1
804 if c.worst_fn_name_bytes == (0 as *u8) {
805 c.worst_fn_name_bytes = f.name_start as *u8
806 c.worst_fn_name_len = f.name_len
807 }
808 return 1
809}
810
811func _verdict_reliability(c: *CategoryReport, p: *GradeProfile) -> nx_int {
812 if p.weight_reliability == 0 { return NX_QV_UNMEASURED }
813 if c.worst_value <= p.missing_return_win_max { return NX_QV_WIN }
814 return NX_QV_LOSE
815}
816
817// ===== RULE 5 (Layer 2): Portability -- Tier-0 fit ==================
818//
819// NishiLang-UNIQUE rule: estimated emitted-code size per function
820// must fit the target tier's per-function code budget. Tier 0 is
821// MCU class (Arduino Uno, ESP32, sensors) with 4-64 KiB total RAM.
822// A function whose code expands beyond ~8 KiB can't be resident on
823// the smallest devices the substrate promises to support.
824//
825// Estimation factor: RV64I instruction = 4 bytes; our IR averages
826// ~4 IR instructions per machine instruction after lowering, so
827// emit_size_bytes ~ n_instrs * 16. Conservative -- the actual
828// emitter often produces fewer machine bytes per IR instruction.
829//
830// Provenance: substrate cardinal feedback-scale-agnostic-substrate
831// + the 2026-05-15 NISHILANG_PORTABILITY_ROADMAP.md ESP32 target.
832// No incumbent linter (SonarQube / Coverity / clippy / clang-tidy /
833// CodeQL / Semgrep / ESLint / SpotBugs / PVS-Studio) measures this --
834// they don't model tier-aware deployment budgets.
835
836const NX_GRADE_TIER0_EMIT_FACTOR: nx_int = 16
837
838func _rule_tier_zero_fits(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
839 if p.weight_portability == 0 { return 0 }
840 let emit_bytes: nx_int = f.n_instrs * NX_GRADE_TIER0_EMIT_FACTOR
841 _classify_severity(card.cat_portability, emit_bytes,
842 p.tier0_warn_bytes, p.tier0_error_bytes, f)
843 return emit_bytes
844}
845
846func _verdict_portability(c: *CategoryReport, p: *GradeProfile) -> nx_int {
847 if p.weight_portability == 0 { return NX_QV_UNMEASURED }
848 if c.n_security > 0 { return NX_QV_LOSE }
849 if c.n_error > 0 { return NX_QV_LOSE }
850 if c.worst_value <= p.tier0_win_max_bytes { return NX_QV_WIN }
851 if c.worst_value <= p.tier0_warn_bytes { return NX_QV_TIE }
852 return NX_QV_LOSE
853}
854
855// ===== RULE 6 (Layer 2): Performance -- fan-out limit ===============
856//
857// God-function antipattern: if a function calls > N other functions,
858// it likely violates single-responsibility AND its emitted code has
859// many function-prologue/epilogue overhead instructions. Pair with
860// the Tier-0 fit rule above: high fan-out + tight code budget =
861// architecturally suspect.
862//
863// Provenance: Google C++ Style "small focused functions" + SonarQube
864// cognitive-complexity sub-axis.
865
866func _count_calls(f: *Function) -> nx_int {
867 var n: nx_int = 0
868 var ii: nx_int = 0
869 while ii < f.n_instrs {
870 let inst: *Instr = _instr_at(f, ii)
871 if inst.op == OP_CALL { n = n + 1 }
872 ii = ii + 1
873 }
874 return n
875}
876
877func _rule_fanout(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
878 if p.weight_performance == 0 { return 0 }
879 let n_calls: nx_int = _count_calls(f)
880 _classify_severity(card.cat_performance, n_calls,
881 p.fanout_warn, p.fanout_error, f)
882 return n_calls
883}
884
885func _verdict_performance(c: *CategoryReport, p: *GradeProfile) -> nx_int {
886 if p.weight_performance == 0 { return NX_QV_UNMEASURED }
887 if c.n_security > 0 { return NX_QV_LOSE }
888 if c.n_error > 0 { return NX_QV_LOSE }
889 if c.worst_value <= p.fanout_win_max { return NX_QV_WIN }
890 if c.worst_value <= p.fanout_warn { return NX_QV_TIE }
891 return NX_QV_LOSE
892}
893
894// ===== RULE 7 (Layer 2): Security -- syscall-leak detection =========
895//
896// Substrate cardinal: __syscall (OP_SYSCALL) is only valid inside
897// sys_* wrapper functions in nx_syscalls.nx. Any OTHER function with
898// OP_SYSCALL bypasses the platform abstraction -- a portability AND
899// security hazard (the substrate's platform layer is what enforces
900// tier-aware quotas, audit logging, etc.).
901//
902// Provenance: substrate's own scale-agnostic-substrate cardinal +
903// Coverity "bypass abstraction layer" + the FLoC-Olympics
904// monetization cardinal (a sensor that bypasses sys_* can't be sold
905// to embedded shops who need provable platform isolation).
906
907func _is_syscall_wrapper(f: *Function) -> nx_int {
908 // Naming convention: any function whose name begins with `sys_`
909 // (4 chars: 's','y','s','_') is part of the syscall abstraction
910 // layer. Anything else must NOT contain OP_SYSCALL directly.
911 if f.name_len < 4 { return 0 }
912 let name: *u8 = f.name_start as *u8
913 if name[0] != 115 { return 0 } // s
914 if name[1] != 121 { return 0 } // y
915 if name[2] != 115 { return 0 } // s
916 if name[3] != 95 { return 0 } // _
917 return 1
918}
919
920func _count_syscalls(f: *Function) -> nx_int {
921 var n: nx_int = 0
922 var ii: nx_int = 0
923 while ii < f.n_instrs {
924 let inst: *Instr = _instr_at(f, ii)
925 if inst.op == OP_SYSCALL { n = n + 1 }
926 ii = ii + 1
927 }
928 return n
929}
930
931func _rule_syscall_leak(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
932 if p.weight_security == 0 { return 0 }
933 if _is_syscall_wrapper(f) == 1 { return 0 }
934 let n_sc: nx_int = _count_syscalls(f)
935 if n_sc == 0 { return 0 }
936 let c: *CategoryReport = card.cat_security
937 c.n_security = c.n_security + n_sc
938 if n_sc > c.worst_value {
939 c.worst_value = n_sc
940 c.worst_fn_name_bytes = f.name_start as *u8
941 c.worst_fn_name_len = f.name_len
942 }
943 return n_sc
944}
945
946func _verdict_security(c: *CategoryReport, p: *GradeProfile) -> nx_int {
947 if p.weight_security == 0 { return NX_QV_UNMEASURED }
948 if c.worst_value <= p.syscall_leak_win_max { return NX_QV_WIN }
949 return NX_QV_LOSE
950}
951
952// ===== RULE 8 (Layer 2): API misuse -- fd-leak balance ==============
953//
954// Per-function balance: count of sys_openat_* call sites minus count
955// of sys_close call sites. Positive excess = potential fd leak.
956//
957// Provenance: clippy unused_io + Coverity RESOURCE_LEAK. IR-level
958// implementation: walk OP_CALL instructions, inspect callee name.
959
960func _name_starts_with(f: *Function, prefix: *u8, plen: nx_int) -> nx_int {
961 if f.name_len < plen { return 0 }
962 let name: *u8 = f.name_start as *u8
963 var i: nx_int = 0
964 while i < plen {
965 if name[i] != prefix[i] { return 0 }
966 i = i + 1
967 }
968 return 1
969}
970
971func _name_equals(f: *Function, target: *u8, tlen: nx_int) -> nx_int {
972 if f.name_len != tlen { return 0 }
973 let name: *u8 = f.name_start as *u8
974 var i: nx_int = 0
975 while i < tlen {
976 if name[i] != target[i] { return 0 }
977 i = i + 1
978 }
979 return 1
980}
981
982func _count_fd_balance(f: *Function) -> nx_int {
983 var opens: nx_int = 0
984 var closes: nx_int = 0
985 let open_prefix: *u8 = "sys_openat" as *u8
986 let close_name: *u8 = "sys_close" as *u8
987 var ii: nx_int = 0
988 while ii < f.n_instrs {
989 let inst: *Instr = _instr_at(f, ii)
990 if inst.op == OP_CALL {
991 let callee: *Function = inst.callee
992 if callee != (0 as *Function) {
993 if _name_starts_with(callee, open_prefix, 10) == 1 {
994 opens = opens + 1
995 }
996 if _name_equals(callee, close_name, 9) == 1 {
997 closes = closes + 1
998 }
999 }
1000 }
1001 ii = ii + 1
1002 }
1003 return opens - closes
1004}
1005
1006func _rule_fd_leak(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
1007 if p.weight_api_misuse == 0 { return 0 }
1008 let excess: nx_int = _count_fd_balance(f)
1009 if excess <= 0 { return 0 }
1010 let c: *CategoryReport = card.cat_api_misuse
1011 c.n_error = c.n_error + 1
1012 if excess > c.worst_value {
1013 c.worst_value = excess
1014 c.worst_fn_name_bytes = f.name_start as *u8
1015 c.worst_fn_name_len = f.name_len
1016 }
1017 return excess
1018}
1019
1020// ===== RULE 9 (Layer 2): API misuse -- wide signatures =============
1021//
1022// Functions with >N parameters violate single-responsibility AND make
1023// every call site fragile (positional args, easy to swap). Substrate
1024// convention: prefer struct args + record builders. Provenance:
1025// SonarQube S107 (default 7) + Google C++ Style. Shares the
1026// api_misuse category with fd-leak; both kinds of "API misshape".
1027
1028func _rule_wide_signature(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
1029 if p.weight_api_misuse == 0 { return 0 }
1030 _classify_severity(card.cat_api_misuse, f.n_params,
1031 p.n_params_warn, p.n_params_error, f)
1032 return f.n_params
1033}
1034
1035func _verdict_api_misuse(c: *CategoryReport, p: *GradeProfile) -> nx_int {
1036 if p.weight_api_misuse == 0 { return NX_QV_UNMEASURED }
1037 // The api_misuse category aggregates fd-leak (n_error counter)
1038 // and wide-signature (worst_value). WIN requires both clean.
1039 if c.n_error > 0 { return NX_QV_LOSE }
1040 if c.worst_value <= p.n_params_win_max { return NX_QV_WIN }
1041 if c.worst_value <= p.n_params_warn { return NX_QV_TIE }
1042 return NX_QV_LOSE
1043}
1044
1045// ===== RULE 10 (Layer 2, JPL): no direct recursion ==================
1046//
1047// NASA JPL Power of 10 Rule 1: avoid recursion. Direct self-call
1048// (function f calls function f) is detectable at IR level: walk f's
1049// OP_CALL instructions; if any inst.callee == f, that's a violation.
1050// Substrate's reliability category gains this rule. Provenance:
1051// Holzmann 2006, "The Power of Ten" + MISRA C2012 Rule 17.2.
1052
1053func _count_self_calls(f: *Function) -> nx_int {
1054 var n: nx_int = 0
1055 var ii: nx_int = 0
1056 while ii < f.n_instrs {
1057 let inst: *Instr = _instr_at(f, ii)
1058 if inst.op == OP_CALL {
1059 if inst.callee as nx_int == f as nx_int { n = n + 1 }
1060 }
1061 ii = ii + 1
1062 }
1063 return n
1064}
1065
1066func _rule_no_recursion(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
1067 if p.weight_no_recursion == 0 { return 0 }
1068 let n: nx_int = _count_self_calls(f)
1069 if n == 0 { return 0 }
1070 let c: *CategoryReport = card.cat_reliability
1071 c.n_error = c.n_error + 1
1072 if n > c.worst_value {
1073 c.worst_value = n
1074 c.worst_fn_name_bytes = f.name_start as *u8
1075 c.worst_fn_name_len = f.name_len
1076 }
1077 return n
1078}
1079
1080// ===== RULE 3: Dead function detector ================================
1081//
1082// Walk every function; for each, walk its instructions looking for
1083// OP_CALL whose `callee` matches the target function pointer. A
1084// function with zero callers (excluding `main`) is dead.
1085
1086func _is_main(f: *Function) -> nx_int {
1087 if f.name_len != 4 { return 0 }
1088 let name: *u8 = f.name_start as *u8
1089 if name[0] != 109 { return 0 } // m
1090 if name[1] != 97 { return 0 } // a
1091 if name[2] != 105 { return 0 } // i
1092 if name[3] != 110 { return 0 } // n
1093 return 1
1094}
1095
1096// Substrate naming convention:
1097// `nx_*` -- public API; intended to be called by clients in other
1098// modules. Library files that ship these have no
1099// internal caller, so dead-code detection must tolerate
1100// them or every library scans as "all dead".
1101// `_*` -- private helper; MUST be called from within this module
1102// or it is genuinely dead code.
1103//
1104// Provenance: this is the convention every substrate file follows
1105// (grep `^func nx_` vs `^func _` across runtime/). Encoding it here
1106// keeps the dead-code rule honest: it flags real dead helpers,
1107// not exported API.
1108func _is_public_export(f: *Function) -> nx_int {
1109 if f.name_len < 3 { return 0 }
1110 let name: *u8 = f.name_start as *u8
1111 if name[0] != 110 { return 0 } // n
1112 if name[1] != 120 { return 0 } // x
1113 if name[2] != 95 { return 0 } // _
1114 return 1
1115}
1116
1117func _function_is_called(m: *Module, target: *Function) -> nx_int {
1118 var fi: nx_int = 0
1119 while fi < m.n_functions {
1120 let caller: *Function = _function_at(m, fi)
1121 var ii: nx_int = 0
1122 while ii < caller.n_instrs {
1123 let inst: *Instr = _instr_at(caller, ii)
1124 if inst.op == OP_CALL {
1125 if inst.callee as nx_int == target as nx_int { return 1 }
1126 }
1127 ii = ii + 1
1128 }
1129 fi = fi + 1
1130 }
1131 return 0
1132}
1133
1134func _rule_dead_functions(card: *GradeCard, m: *Module, p: *GradeProfile) -> nx_int {
1135 if p.weight_deadcode == 0 { return 0 }
1136 let c: *CategoryReport = card.cat_deadcode
1137 var fi: nx_int = 0
1138 var dead: nx_int = 0
1139 while fi < m.n_functions {
1140 let f: *Function = _function_at(m, fi)
1141 // Tolerate: main entry point + `nx_*` public exports. Flag
1142 // only uncalled `_*` private helpers (genuine dead code).
1143 if _is_main(f) == 0 {
1144 if _is_public_export(f) == 0 {
1145 if _function_is_called(m, f) == 0 {
1146 dead = dead + 1
1147 c.n_warn = c.n_warn + 1
1148 // Record the first dead function found so the
1149 // self-evolution loop can point a fix at it.
1150 if c.worst_fn_name_bytes == (0 as *u8) {
1151 c.worst_fn_name_bytes = f.name_start as *u8
1152 c.worst_fn_name_len = f.name_len
1153 }
1154 }
1155 }
1156 }
1157 fi = fi + 1
1158 }
1159 if dead > c.worst_value { c.worst_value = dead }
1160 return dead
1161}
1162
1163// ===== Per-category verdict computation ==============================
1164//
1165// Each rule has its own threshold. A WIN requires beating the
1166// threshold by the 1% cardinal margin. ERROR or SECURITY findings
1167// auto-LOSE regardless of count.
1168
1169func _verdict_complexity(c: *CategoryReport, p: *GradeProfile) -> nx_int {
1170 if p.weight_complexity == 0 { return NX_QV_UNMEASURED }
1171 if c.n_security > 0 { return NX_QV_LOSE }
1172 if c.n_error > 0 { return NX_QV_LOSE }
1173 if c.worst_value <= p.ccn_win_max { return NX_QV_WIN }
1174 if c.worst_value <= p.ccn_warn { return NX_QV_TIE }
1175 return NX_QV_LOSE
1176}
1177
1178func _verdict_fn_length(c: *CategoryReport, p: *GradeProfile) -> nx_int {
1179 if p.weight_maintain == 0 { return NX_QV_UNMEASURED }
1180 if c.n_security > 0 { return NX_QV_LOSE }
1181 if c.n_error > 0 { return NX_QV_LOSE }
1182 if c.worst_value <= p.fnlen_win_max { return NX_QV_WIN }
1183 if c.worst_value <= p.fnlen_warn { return NX_QV_TIE }
1184 return NX_QV_LOSE
1185}
1186
1187func _verdict_deadcode(c: *CategoryReport, p: *GradeProfile) -> nx_int {
1188 if p.weight_deadcode == 0 { return NX_QV_UNMEASURED }
1189 if c.worst_value <= p.dead_win_max { return NX_QV_WIN }
1190 return NX_QV_LOSE
1191}
1192
1193// ===== Public scan ===================================================
1194//
1195// Walk the Module, apply every Layer-0 rule, populate the card.
1196
1197// Run every Layer-0 rule against the Module using the supplied
1198// profile. One entry point. Callers explicitly choose their
1199// profile -- there is no implicit default. (No back-compat
1200// "convenience" wrapper: multiple ways to do the same thing is
1201// exactly the AI-drift this file's guardrails refuse.)
1202// Additive dispatcher: group all per-function rules into one call so
1203// callers stay small. This is the right ANTI-FAN-OUT pattern -- not
1204// deleting rules, but composing them under a name. Adding new Layer
1205// 2/3 rules later means adding one line here, not seven new lines in
1206// every caller.
1207// Split into Layer-0 (structural) and Layer-2 (semantic) halves.
1208// Each helper stays at fan-out 4; the composer stays at fan-out 2.
1209// All three graders' fanout WIN thresholds cleared.
1210func _run_layer0_rules(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
1211 _rule_complexity(card, f, p)
1212 _rule_function_length(card, f, p)
1213 _rule_explicit_return(card, f, p)
1214 _rule_tier_zero_fits(card, f, p)
1215 return 0
1216}
1217
1218func _run_layer2_rules(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
1219 _rule_fanout(card, f, p)
1220 _rule_syscall_leak(card, f, p)
1221 _rule_fd_leak(card, f, p)
1222 _rule_wide_signature(card, f, p)
1223 _rule_no_recursion(card, f, p) // JPL Rule 1 -- only fires when
1224 // profile.weight_no_recursion=1
1225 return 0
1226}
1227
1228func _run_per_fn_rules(card: *GradeCard, f: *Function, p: *GradeProfile) -> nx_int {
1229 _run_layer0_rules(card, f, p)
1230 _run_layer2_rules(card, f, p)
1231 return 0
1232}
1233
1234func nx_grade_card_scan(card: *GradeCard, m: *Module, p: *GradeProfile) -> nx_int {
1235 card.n_functions = m.n_functions
1236 var fi: nx_int = 0
1237 while fi < m.n_functions {
1238 _run_per_fn_rules(card, _function_at(m, fi), p)
1239 fi = fi + 1
1240 }
1241 _rule_dead_functions(card, m, p)
1242 return 0
1243}
1244
1245// ===== Compute aggregate grade =======================================
1246//
1247// Each category contributes 1 to wins/losses/ties. Letter grade:
1248// A: >=4 WIN, 0 LOSE (excellent)
1249// B: >=3 WIN, <=1 LOSE
1250// C: balanced (wins == losses)
1251// D: losses > wins
1252// F: any SECURITY-severity finding anywhere
1253//
1254// Categories with verdict UNMEASURED don't contribute to the count;
1255// they're tracked separately as the "honest about gaps" signal.
1256
1257func _grade_card_count_security(card: *GradeCard) -> nx_int {
1258 var s: nx_int = 0
1259 var i: nx_int = 0
1260 while i < NX_GRADE_N_CATS {
1261 let c: *CategoryReport = _grade_cat_at(card, i)
1262 s = s + c.n_security
1263 i = i + 1
1264 }
1265 return s
1266}
1267
1268// Verdict counters laid out [unmeasured, lose, tie, win] match NX_QV_*
1269// enum. GradeCard's first field is n_unmeasured so the card pointer
1270// IS the counter-table base. One indexed bump, no dispatch.
1271// CCN drops from 5 to 1.
1272func _bump_counts(card: *GradeCard, verdict: nx_int) -> nx_int {
1273 return _inc_by_idx(card as *nx_int, verdict)
1274}
1275
1276// Compute aggregate grade using the supplied profile. One entry
1277// point. Caller passes the same profile that was used during scan.
1278// Letter-grade rubric. Shared between single-card grading and the
1279// triangulated grade -- one source of truth.
1280//
1281// Honest interpretation: UNMEASURED is NOT a failure -- it's a
1282// coverage-gap signal (tracked separately via n_unmeasured). Grade
1283// reflects what we MEASURED scored, not how much we measured.
1284// Tightened 2026-05-15: B no longer accepts "wins > losses" alone;
1285// a module with multiple real LOSE findings must visibly score below
1286// B even if more categories happened to WIN.
1287//
1288// S: 0 LOSE + 0 TIE + >= 4 WIN (perfect across meaningful scope)
1289// A: 0 LOSE + >= 3 WIN
1290// B: <=1 LOSE + >= 3 WIN
1291// C: <=2 LOSE + wins > losses, OR wins == losses, OR
1292// 0 LOSE + < 3 WIN (not enough coverage yet)
1293// D: losses > wins, OR >= 3 LOSE
1294//
1295// SECURITY findings (caller-checked) force F separately.
1296//
1297// Note: single-grader cards can reach S; triangulated S requires ALL
1298// THREE graders to score S, which is the toughest honest target.
1299func _grade_letter(wins: nx_int, losses: nx_int) -> nx_int {
1300 if losses == 0 {
1301 if wins >= 4 { return NX_GRADE_S }
1302 if wins >= 3 { return NX_GRADE_A }
1303 return NX_GRADE_C
1304 }
1305 if losses == 1 {
1306 if wins >= 3 { return NX_GRADE_B }
1307 }
1308 if losses >= 3 { return NX_GRADE_D }
1309 if wins > losses { return NX_GRADE_C }
1310 if wins == losses { return NX_GRADE_C }
1311 return NX_GRADE_D
1312}
1313
1314// Set each category's verdict. Layer-0 has rules for 3 categories;
1315// the other 5 stay UNMEASURED until Layer 1/2 rules ship (honest
1316// about coverage gap, never aspirational).
1317// Split into two halves of 4 verdicts each to stay under all 3
1318// graders' fan-out thresholds. ADDITIVE composition -- no verdict
1319// deleted, each lives where its provenance fits (Layer 0 vs Layer 1/2).
1320func _compute_verdicts_layer0(card: *GradeCard, p: *GradeProfile) -> nx_int {
1321 card.cat_complexity.verdict = _verdict_complexity(card.cat_complexity, p)
1322 card.cat_maintain.verdict = _verdict_fn_length(card.cat_maintain, p)
1323 card.cat_deadcode.verdict = _verdict_deadcode(card.cat_deadcode, p)
1324 card.cat_reliability.verdict = _verdict_reliability(card.cat_reliability, p)
1325 return 0
1326}
1327
1328func _compute_verdicts_layer2(card: *GradeCard, p: *GradeProfile) -> nx_int {
1329 card.cat_portability.verdict = _verdict_portability(card.cat_portability, p)
1330 card.cat_performance.verdict = _verdict_performance(card.cat_performance, p)
1331 card.cat_security.verdict = _verdict_security(card.cat_security, p)
1332 card.cat_api_misuse.verdict = _verdict_api_misuse(card.cat_api_misuse, p)
1333 return 0
1334}
1335
1336func _compute_verdicts(card: *GradeCard, p: *GradeProfile) -> nx_int {
1337 _compute_verdicts_layer0(card, p)
1338 _compute_verdicts_layer2(card, p)
1339 return 0
1340}
1341
1342// One pass: total findings + verdict counts across all 8 categories.
1343func _compute_aggregates(card: *GradeCard) -> nx_int {
1344 card.n_wins = 0
1345 card.n_ties = 0
1346 card.n_losses = 0
1347 card.n_unmeasured = 0
1348 card.n_total = 0
1349 var i: nx_int = 0
1350 while i < NX_GRADE_N_CATS {
1351 let c: *CategoryReport = _grade_cat_at(card, i)
1352 _bump_counts(card, c.verdict)
1353 card.n_total = card.n_total + c.n_warn + c.n_error + c.n_security
1354 i = i + 1
1355 }
1356 return 0
1357}
1358
1359func nx_grade_card_compute(card: *GradeCard, p: *GradeProfile) -> nx_int {
1360 _compute_verdicts(card, p)
1361 _compute_aggregates(card)
1362 if _grade_card_count_security(card) > 0 {
1363 card.grade = NX_GRADE_F
1364 return card.grade
1365 }
1366 // Letter rubric is shared with triangulated grading; one source
1367 // of truth -- see _triangulated_letter declared later. (Forward
1368 // helper inlined here because single-pass parser needs the func
1369 // defined before use; _grade_letter is the inline equivalent.)
1370 card.grade = _grade_letter(card.n_wins, card.n_losses)
1371 return card.grade
1372}
1373
1374// ===== Sealed-enum validity =========================================
1375
1376func nx_grade_severity_is_valid(s: nx_int) -> nx_int {
1377 if s < 0 { return 0 }
1378 if s >= NX_SEV_N { return 0 }
1379 return 1
1380}
1381
1382func nx_grade_kind_is_valid(k: nx_int) -> nx_int {
1383 if k < 0 { return 0 }
1384 if k >= NX_KIND_N { return 0 }
1385 return 1
1386}
1387
1388func nx_grade_verdict_is_valid(v: nx_int) -> nx_int {
1389 if v < 0 { return 0 }
1390 if v >= NX_QV_N { return 0 }
1391 return 1
1392}
1393
1394func nx_grade_letter_is_valid(g: nx_int) -> nx_int {
1395 if g < 0 { return 0 }
1396 if g >= NX_GRADE_N { return 0 }
1397 return 1
1398}
1399
1400// ===== Public emit ===================================================
1401
1402// NOTE 2026-05-15: a string-table additive primitive (per-enum lookup
1403// indexed by sealed-enum value) was attempted to collapse these four
1404// 7-way if-chains to CCN=1. Blocked by a codegen Gap: function-param
1405// indexing into a *i64 table (`t[param]`) SEGVs at runtime even though
1406// `t[const]` works. Same root cause as the poisson-disk codegen gap
1407// (memory: project-poisson-disk-bug-large-r-2026-05-15). Reverted to
1408// if-chains; codegen Gap recorded as the next-target compiler fix per
1409// the "stop-and-build-upward" cardinal.
1410//
1411// CCN=8 of _kind_str + _named_improvement remains the binding chokepoint
1412// keeping Elm individual-card at B (single-grader S would need CCN<=7).
1413// The if-chains are not "drift" -- they encode 8 enum-value branches
1414// honestly; the codegen Gap is the real blocker.
1415
1416func _grade_letter_str(g: nx_int) -> *u8 {
1417 if g == NX_GRADE_S { return "S" as *u8 }
1418 if g == NX_GRADE_A { return "A" as *u8 }
1419 if g == NX_GRADE_B { return "B" as *u8 }
1420 if g == NX_GRADE_C { return "C" as *u8 }
1421 if g == NX_GRADE_D { return "D" as *u8 }
1422 return "F" as *u8
1423}
1424
1425func _verdict_str(v: nx_int) -> *u8 {
1426 if v == NX_QV_WIN { return "WIN " as *u8 }
1427 if v == NX_QV_TIE { return "TIE " as *u8 }
1428 if v == NX_QV_LOSE { return "LOSE " as *u8 }
1429 return "UNMEASURED " as *u8
1430}
1431
1432func _kind_str(k: nx_int) -> *u8 {
1433 if k == NX_KIND_COMPLEXITY { return "complexity: " as *u8 }
1434 if k == NX_KIND_DEADCODE { return "deadcode: " as *u8 }
1435 if k == NX_KIND_RELIABILITY { return "reliability: " as *u8 }
1436 if k == NX_KIND_SECURITY { return "security: " as *u8 }
1437 if k == NX_KIND_MAINTAIN { return "maintain: " as *u8 }
1438 if k == NX_KIND_PORTABILITY { return "portability: " as *u8 }
1439 if k == NX_KIND_PERFORMANCE { return "performance: " as *u8 }
1440 return "api_misuse: " as *u8
1441}
1442
1443// Per-rule named improvement. Strings are directive ("split X into Y+Z")
1444// not advisory ("consider refactoring") -- substrate self-improves
1445// without internet/AI lookup.
1446func _named_improvement(kind: nx_int) -> *u8 {
1447 if kind == NX_KIND_COMPLEXITY { return "split the named function by extracting independent branches into separate helpers; aim for CCN <= 8 (Elm baseline)" as *u8 }
1448 if kind == NX_KIND_DEADCODE { return "remove or call the named private helper; substrate convention: `_*` prefix is private and must be referenced" as *u8 }
1449 if kind == NX_KIND_MAINTAIN { return "split the named function around its widest sequential block; aim for <= 24 IR instructions (Elm) or <= 79 (SonarQube)" as *u8 }
1450 if kind == NX_KIND_RELIABILITY { return "ensure the named function checks syscall return values via `if rc < 0` before subsequent use" as *u8 }
1451 if kind == NX_KIND_SECURITY { return "Layer 2 queued: ensure named function does not flow tainted input (sys_read) to sinks (sys_write, format) without sanitization" as *u8 }
1452 if kind == NX_KIND_PORTABILITY { return "Layer 2 queued: replace direct sys_mmap with platform-abstraction calls (nx_platform.nx)" as *u8 }
1453 if kind == NX_KIND_PERFORMANCE { return "Layer 2 queued: convert O(N^2) function-walk patterns into single-pass" as *u8 }
1454 return "Layer 2 queued: API-misuse detection (forgotten close, leaked fd, unchecked Result)" as *u8
1455}
1456
1457func _emit_category(c: *CategoryReport) -> nx_int {
1458 print(" " as *u8)
1459 print(_kind_str(c.kind))
1460 print(_verdict_str(c.verdict))
1461 print("(worst=" as *u8)
1462 print_i64(c.worst_value)
1463 print(", findings=" as *u8)
1464 let total: nx_int = c.n_info + c.n_warn + c.n_error + c.n_security
1465 print_i64(total)
1466 print(")" as *u8)
1467 // Self-evolution signal: name the offending function + a concrete
1468 // improvement on every LOSE verdict. Caller reads this and acts
1469 // WITHOUT any external lookup.
1470 if c.verdict == NX_QV_LOSE {
1471 if c.worst_fn_name_len > 0 {
1472 print(" fix=" as *u8)
1473 sys_write(NX_FD_STDOUT, c.worst_fn_name_bytes, c.worst_fn_name_len)
1474 print(": " as *u8)
1475 print(_named_improvement(c.kind))
1476 } else {
1477 print(" fix=" as *u8)
1478 print(_named_improvement(c.kind))
1479 }
1480 }
1481 println("" as *u8)
1482 return 0
1483}
1484
1485func _emit_card_header(card: *GradeCard) -> nx_int {
1486 print("nx_quality_grade: grade=" as *u8)
1487 print(_grade_letter_str(card.grade))
1488 print(" fns=" as *u8)
1489 print_i64(card.n_functions)
1490 print(" WIN=" as *u8)
1491 print_i64(card.n_wins)
1492 print(" TIE=" as *u8)
1493 print_i64(card.n_ties)
1494 print(" LOSE=" as *u8)
1495 print_i64(card.n_losses)
1496 print(" UNMEASURED=" as *u8)
1497 print_i64(card.n_unmeasured)
1498 println("" as *u8)
1499 return 0
1500}
1501
1502func nx_grade_card_emit(card: *GradeCard) -> nx_int {
1503 _emit_card_header(card)
1504 var i: nx_int = 0
1505 while i < NX_GRADE_N_CATS {
1506 _emit_category(_grade_cat_at(card, i))
1507 i = i + 1
1508 }
1509 return 0
1510}
1511
1512// ===== Triangulation ================================================
1513//
1514// Three independent GradeCards (one per provenance) come in. Per
1515// category, vote: if 2+ graders agree on a verdict, that's the
1516// triangulated verdict. All-disagree (one of WIN/TIE/LOSE each)
1517// becomes NX_QV_UNMEASURED with disagreement_count = 2. The grader
1518// who patted itself on the back loses to the two that didn't.
1519
1520struct TriangulatedReport {
1521 // Per category: verdict from each provenance + the triangulated
1522 // outcome. Field order matches NX_GRADE_PROV_* (0..6). Unused
1523 // slots (when calling triangulate / polyangulate instead of
1524 // heptagulate) stay at NX_QV_UNMEASURED.
1525 cat_kind: nx_int,
1526 sonarqube_verdict: nx_int,
1527 clippy_verdict: nx_int,
1528 elm_verdict: nx_int,
1529 jpl_verdict: nx_int,
1530 cert_verdict: nx_int,
1531 linus_verdict: nx_int,
1532 idris_verdict: nx_int,
1533 triangulated: nx_int, // majority; UNMEASURED on full disagree
1534 disagreement_count: nx_int, // 0 unanimous to N-1 fully chaotic
1535}
1536
1537struct GradeTriangulation {
1538 // Disagreement-count table FIRST so `tr as *nx_int` is its base.
1539 // _vote passes (0=unanimous, 1=outlier, 2=controversial).
1540 n_unanimous: nx_int, // disagreement == 0
1541 n_with_outlier: nx_int, // disagreement == 1
1542 n_fully_controversial: nx_int, // disagreement == 2
1543 // Verdict-count table SECOND, matching NX_QV_* enum. Address
1544 // computed as `(tr as nx_int) + 24` (3 nx_int past the disagreement
1545 // table). See _tri_verdict_base below.
1546 n_triangulated_unmeas: nx_int, // NX_QV_UNMEASURED == 0
1547 n_triangulated_losses: nx_int, // NX_QV_LOSE == 1
1548 n_triangulated_ties: nx_int, // NX_QV_TIE == 2
1549 n_triangulated_wins: nx_int, // NX_QV_WIN == 3
1550 final_grade: nx_int,
1551 // 8 inline TriangulatedReport records. Indexed via _grade_tri_at
1552 // using the cat_complexity field reference, so their position is
1553 // independent of the headers above.
1554 cat_complexity: TriangulatedReport,
1555 cat_deadcode: TriangulatedReport,
1556 cat_reliability: TriangulatedReport,
1557 cat_security: TriangulatedReport,
1558 cat_maintain: TriangulatedReport,
1559 cat_portability: TriangulatedReport,
1560 cat_performance: TriangulatedReport,
1561 cat_api_misuse: TriangulatedReport,
1562}
1563
1564// Two contiguous counter tables in GradeTriangulation: disagreement
1565// (3 slots) starts at offset 0; verdict (4 slots) starts at offset 24.
1566const NX_GRADE_TRI_VERDICT_OFFSET: nx_int = 24
1567
1568// Sizing: 64 B header (n_unanimous..final_grade = 8 nx_int) +
1569// 8 categories × NX_GRADE_TRI_STRIDE (80 B each) = 704 B. Rounded
1570// up to 1024 for alignment headroom.
1571const NX_TRIANGULATION_BYTES: nx_size = 1024
1572
1573// Address of the i-th TriangulatedReport inside a GradeTriangulation.
1574// Mirror of _grade_cat_at; lives here because TriangulatedReport must
1575// be in scope for the cast (single-pass parser).
1576func _grade_tri_at(tr: *GradeTriangulation, idx: nx_int) -> *TriangulatedReport {
1577 let base: nx_int = tr.cat_complexity as nx_int
1578 return (base + idx * NX_GRADE_TRI_STRIDE) as *TriangulatedReport
1579}
1580
1581func nx_grade_triangulation_alloc() -> *GradeTriangulation {
1582 let raw: *u8 = sys_mmap(NX_TRIANGULATION_BYTES)
1583 return raw as *GradeTriangulation
1584}
1585
1586// Generalized N-grader majority vote. Inputs are 4 verdict values
1587// (one per grader, NX_QV_UNMEASURED / LOSE / TIE / WIN). When
1588// triangulate (3-grader) is the caller, the 4th value is passed as
1589// NX_QV_UNMEASURED so it slots into the vote naturally.
1590//
1591// Returns the verdict with the most votes. Writes disagreement
1592// count to *out_disagreement:
1593// 0 -- all 4 equal (unanimous)
1594// 1 -- 3-vs-1 (one outlier)
1595// 2 -- 2-vs-2 split, or 2-vs-1-vs-1 (no clear majority)
1596// 3 -- 4-way 1-1-1-1 (full chaos)
1597//
1598// Tie-breaking: 2-vs-2 -> UNMEASURED (refuse to call it).
1599// 2-vs-1-vs-1 -> the doubled verdict wins with disagreement=2.
1600//
1601// UNMEASURED counts in the tally just like any other verdict.
1602
1603func _count_votes(a: nx_int, b: nx_int, c: nx_int, d: nx_int, target: nx_int) -> nx_int {
1604 var n: nx_int = 0
1605 if a == target { n = n + 1 }
1606 if b == target { n = n + 1 }
1607 if c == target { n = n + 1 }
1608 if d == target { n = n + 1 }
1609 return n
1610}
1611
1612// Count how many of the 4 verdict-buckets have exactly `target_n` votes.
1613// Used by _vote4 to detect 2-vs-2 splits without an explicit if-matrix.
1614func _count_buckets_with_n(n_win: nx_int, n_tie: nx_int,
1615 n_lose: nx_int, n_unm: nx_int,
1616 target_n: nx_int) -> nx_int {
1617 var c: nx_int = 0
1618 if n_win == target_n { c = c + 1 }
1619 if n_tie == target_n { c = c + 1 }
1620 if n_lose == target_n { c = c + 1 }
1621 if n_unm == target_n { c = c + 1 }
1622 return c
1623}
1624
1625func _vote4(a: nx_int, b: nx_int, c: nx_int, d: nx_int,
1626 out_disagreement: *nx_int) -> nx_int {
1627 let n_win: nx_int = _count_votes(a, b, c, d, NX_QV_WIN)
1628 let n_tie: nx_int = _count_votes(a, b, c, d, NX_QV_TIE)
1629 let n_lose: nx_int = _count_votes(a, b, c, d, NX_QV_LOSE)
1630 let n_unm: nx_int = _count_votes(a, b, c, d, NX_QV_UNMEASURED)
1631
1632 var max_n: nx_int = n_unm
1633 var max_verd: nx_int = NX_QV_UNMEASURED
1634 if n_lose > max_n { max_n = n_lose; max_verd = NX_QV_LOSE }
1635 if n_tie > max_n { max_n = n_tie; max_verd = NX_QV_TIE }
1636 if n_win > max_n { max_n = n_win; max_verd = NX_QV_WIN }
1637
1638 out_disagreement[0] = 4 - max_n
1639
1640 // 2-vs-2 split: two distinct buckets each got 2 votes -> no clear
1641 // majority. Refuse to call -- return UNMEASURED.
1642 if max_n == 2 {
1643 if _count_buckets_with_n(n_win, n_tie, n_lose, n_unm, 2) >= 2 {
1644 return NX_QV_UNMEASURED
1645 }
1646 }
1647 return max_verd
1648}
1649
1650// Generalised N-grader vote. `verdicts` is an array of `n` ints,
1651// each in {NX_QV_UNMEASURED, LOSE, TIE, WIN}. Returns the verdict
1652// with the most votes; ties go to UNMEASURED. Disagreement = N - max.
1653func _count_in_array(verdicts: *nx_int, n: nx_int, target: nx_int) -> nx_int {
1654 var c: nx_int = 0
1655 var i: nx_int = 0
1656 while i < n {
1657 if verdicts[i] == target { c = c + 1 }
1658 i = i + 1
1659 }
1660 return c
1661}
1662
1663func _vote_n(verdicts: *nx_int, n: nx_int, out_disagreement: *nx_int) -> nx_int {
1664 let n_win: nx_int = _count_in_array(verdicts, n, NX_QV_WIN)
1665 let n_tie: nx_int = _count_in_array(verdicts, n, NX_QV_TIE)
1666 let n_lose: nx_int = _count_in_array(verdicts, n, NX_QV_LOSE)
1667 let n_unm: nx_int = _count_in_array(verdicts, n, NX_QV_UNMEASURED)
1668
1669 var max_n: nx_int = n_unm
1670 var max_verd: nx_int = NX_QV_UNMEASURED
1671 if n_lose > max_n { max_n = n_lose; max_verd = NX_QV_LOSE }
1672 if n_tie > max_n { max_n = n_tie; max_verd = NX_QV_TIE }
1673 if n_win > max_n { max_n = n_win; max_verd = NX_QV_WIN }
1674
1675 out_disagreement[0] = n - max_n
1676 if _count_buckets_with_n(n_win, n_tie, n_lose, n_unm, max_n) >= 2 {
1677 return NX_QV_UNMEASURED
1678 }
1679 return max_verd
1680}
1681
1682// 3-grader vote. Distinct from _vote4 so the disagreement count
1683// reflects 3-way semantics (max disagreement = 2, full 3-way split).
1684// Re-implemented (not delegating to _vote4) because _vote4 would
1685// treat a phantom 4th-grader's UNMEASURED as a real vote.
1686func _vote(a: nx_int, b: nx_int, c: nx_int, out_disagreement: *nx_int) -> nx_int {
1687 if a == b {
1688 if b == c { out_disagreement[0] = 0; return a }
1689 out_disagreement[0] = 1
1690 return a
1691 }
1692 if a == c { out_disagreement[0] = 1; return a }
1693 if b == c { out_disagreement[0] = 1; return b }
1694 out_disagreement[0] = 2
1695 return NX_QV_UNMEASURED
1696}
1697
1698// Zero the heptagulation-only verdict slots (cert/linus/idris) for
1699// reports produced by 3- or 4-grader callers. Honest signal:
1700// UNMEASURED says "this grader didn't run for this category", not
1701// "this grader scored UNMEASURED".
1702func _zero_higher_slots(t: *TriangulatedReport) -> nx_int {
1703 t.cert_verdict = NX_QV_UNMEASURED
1704 t.linus_verdict = NX_QV_UNMEASURED
1705 t.idris_verdict = NX_QV_UNMEASURED
1706 return 0
1707}
1708
1709func _triangulate_one(t: *TriangulatedReport, kind: nx_int,
1710 sa: nx_int, sc: nx_int, se: nx_int) -> nx_int {
1711 t.cat_kind = kind
1712 t.sonarqube_verdict = sa
1713 t.clippy_verdict = sc
1714 t.elm_verdict = se
1715 t.jpl_verdict = NX_QV_UNMEASURED
1716 _zero_higher_slots(t)
1717 let dis_raw: *u8 = sys_mmap(16)
1718 let dis: *nx_int = dis_raw as *nx_int
1719 dis[0] = 0
1720 t.triangulated = _vote(sa, sc, se, dis)
1721 t.disagreement_count = dis[0]
1722 return 0
1723}
1724
1725// 4-grader polyangulation. Accepts verdicts as a 4-int array
1726// (indexed 0..3 = sq/cl/el/jpl) instead of 4 separate scalars so
1727// signature width stays inside Elm + JPL thresholds.
1728func _polyangulate_one(t: *TriangulatedReport, kind: nx_int,
1729 verdicts: *nx_int) -> nx_int {
1730 t.cat_kind = kind
1731 t.sonarqube_verdict = verdicts[0]
1732 t.clippy_verdict = verdicts[1]
1733 t.elm_verdict = verdicts[2]
1734 t.jpl_verdict = verdicts[3]
1735 _zero_higher_slots(t)
1736 let dis_raw: *u8 = sys_mmap(16)
1737 let dis: *nx_int = dis_raw as *nx_int
1738 dis[0] = 0
1739 t.triangulated = _vote4(verdicts[0], verdicts[1],
1740 verdicts[2], verdicts[3], dis)
1741 t.disagreement_count = dis[0]
1742 return 0
1743}
1744
1745// Two indexed bumps, no dispatch. CCN drops 8 -> 1; IR drops 77 -> ~12.
1746// This is the keystone refactor: the worst CCN AND worst fn-length in
1747// the grader both collapse via the same additive primitive.
1748// Disagreement-counter table has exactly 3 slots:
1749// [0] n_unanimous -- disagreement = 0
1750// [1] n_with_outlier -- disagreement = 1
1751// [2] n_fully_controversial -- disagreement >= 2
1752// Clamp the bump index so 4-grader and 7-grader heptagulation
1753// (which produce disagreements up to N-1) don't write past the
1754// table into the verdict counters.
1755func _bump_tri(tr: *GradeTriangulation, verdict: nx_int, disagreement: nx_int) -> nx_int {
1756 var d: nx_int = disagreement
1757 if d > 2 { d = 2 }
1758 _inc_by_idx(tr as *nx_int, d)
1759 let verdict_base: nx_int = (tr as nx_int) + NX_GRADE_TRI_VERDICT_OFFSET
1760 _inc_by_idx(verdict_base as *nx_int, verdict)
1761 return 0
1762}
1763
1764// Bundle of up to 7 GradeCard pointers. Replaces a wide-signature
1765// parameter list with a single struct. `n_cards` tells the
1766// dispatcher how many slots are populated (3 for triangulate,
1767// 4 for polyangulate, 7 for heptagulate).
1768struct CardSet {
1769 sq: *GradeCard,
1770 cl: *GradeCard,
1771 el: *GradeCard,
1772 jpl: *GradeCard,
1773 cert: *GradeCard,
1774 linus: *GradeCard,
1775 idris: *GradeCard,
1776 n_cards: nx_int,
1777}
1778
1779func _zero_poly_counters(tr: *GradeTriangulation) -> nx_int {
1780 tr.n_unanimous = 0
1781 tr.n_with_outlier = 0
1782 tr.n_fully_controversial = 0
1783 tr.n_triangulated_wins = 0
1784 tr.n_triangulated_losses = 0
1785 tr.n_triangulated_ties = 0
1786 tr.n_triangulated_unmeas = 0
1787 return 0
1788}
1789
1790// Fill a 4-slot verdict array for the i-th category across all 4 cards.
1791func _gather_verdicts(verdicts: *nx_int, cs: *CardSet, i: nx_int) -> nx_int {
1792 let sq_c: *CategoryReport = _grade_cat_at(cs.sq, i)
1793 let cl_c: *CategoryReport = _grade_cat_at(cs.cl, i)
1794 let el_c: *CategoryReport = _grade_cat_at(cs.el, i)
1795 let jpl_c: *CategoryReport = _grade_cat_at(cs.jpl, i)
1796 verdicts[0] = sq_c.verdict
1797 verdicts[1] = cl_c.verdict
1798 verdicts[2] = el_c.verdict
1799 verdicts[3] = jpl_c.verdict
1800 return 0
1801}
1802
1803// 4-grader public entry point.
1804func nx_grade_polyangulate(tr: *GradeTriangulation,
1805 sq: *GradeCard, cl: *GradeCard,
1806 el: *GradeCard, jpl: *GradeCard) -> nx_int {
1807 _zero_poly_counters(tr)
1808 let cs_raw: *u8 = sys_mmap(96)
1809 let cs: *CardSet = cs_raw as *CardSet
1810 cs.sq = sq
1811 cs.cl = cl
1812 cs.el = el
1813 cs.jpl = jpl
1814 cs.cert = 0 as *GradeCard
1815 cs.linus = 0 as *GradeCard
1816 cs.idris = 0 as *GradeCard
1817 cs.n_cards = 4
1818 let verdicts_raw: *u8 = sys_mmap(32)
1819 let verdicts: *nx_int = verdicts_raw as *nx_int
1820 var i: nx_int = 0
1821 while i < NX_GRADE_N_CATS {
1822 _gather_verdicts(verdicts, cs, i)
1823 let t: *TriangulatedReport = _grade_tri_at(tr, i)
1824 _polyangulate_one(t, i, verdicts)
1825 _bump_tri(tr, t.triangulated, t.disagreement_count)
1826 i = i + 1
1827 }
1828 tr.final_grade = _grade_letter(tr.n_triangulated_wins,
1829 tr.n_triangulated_losses)
1830 return tr.final_grade
1831}
1832
1833// Gather all 7 verdicts for the i-th category into a flat array.
1834// Used by heptagulation. Bundles the lookups so callers stay narrow.
1835func _gather_verdicts_7(verdicts: *nx_int, cs: *CardSet, i: nx_int) -> nx_int {
1836 let sq_c: *CategoryReport = _grade_cat_at(cs.sq, i)
1837 let cl_c: *CategoryReport = _grade_cat_at(cs.cl, i)
1838 let el_c: *CategoryReport = _grade_cat_at(cs.el, i)
1839 let jpl_c: *CategoryReport = _grade_cat_at(cs.jpl, i)
1840 let cert_c: *CategoryReport = _grade_cat_at(cs.cert, i)
1841 let linus_c: *CategoryReport = _grade_cat_at(cs.linus, i)
1842 let idris_c: *CategoryReport = _grade_cat_at(cs.idris, i)
1843 verdicts[0] = sq_c.verdict
1844 verdicts[1] = cl_c.verdict
1845 verdicts[2] = el_c.verdict
1846 verdicts[3] = jpl_c.verdict
1847 verdicts[4] = cert_c.verdict
1848 verdicts[5] = linus_c.verdict
1849 verdicts[6] = idris_c.verdict
1850 return 0
1851}
1852
1853func _heptagulate_one(t: *TriangulatedReport, kind: nx_int,
1854 verdicts: *nx_int) -> nx_int {
1855 t.cat_kind = kind
1856 t.sonarqube_verdict = verdicts[0]
1857 t.clippy_verdict = verdicts[1]
1858 t.elm_verdict = verdicts[2]
1859 t.jpl_verdict = verdicts[3]
1860 t.cert_verdict = verdicts[4]
1861 t.linus_verdict = verdicts[5]
1862 t.idris_verdict = verdicts[6]
1863 let dis_raw: *u8 = sys_mmap(16)
1864 let dis: *nx_int = dis_raw as *nx_int
1865 dis[0] = 0
1866 t.triangulated = _vote_n(verdicts, 7, dis)
1867 t.disagreement_count = dis[0]
1868 return 0
1869}
1870
1871// 7-grader public entry: heptagulation. Cards bundled into CardSet
1872// to keep the signature inside Elm/JPL n_params thresholds. Each
1873// category votes across all 7; majority needs >=4 agreement.
1874func nx_grade_heptagulate(tr: *GradeTriangulation, cs: *CardSet) -> nx_int {
1875 _zero_poly_counters(tr)
1876 let verdicts_raw: *u8 = sys_mmap(64)
1877 let verdicts: *nx_int = verdicts_raw as *nx_int
1878 var i: nx_int = 0
1879 while i < NX_GRADE_N_CATS {
1880 _gather_verdicts_7(verdicts, cs, i)
1881 let t: *TriangulatedReport = _grade_tri_at(tr, i)
1882 _heptagulate_one(t, i, verdicts)
1883 _bump_tri(tr, t.triangulated, t.disagreement_count)
1884 i = i + 1
1885 }
1886 tr.final_grade = _grade_letter(tr.n_triangulated_wins,
1887 tr.n_triangulated_losses)
1888 return tr.final_grade
1889}
1890
1891// Per-category loop body for 3-grader triangulation. Mirror of the
1892// polyangulate variant; stays in its own helper so the entry point
1893// stays at <=4 callees (JPL win_max).
1894func _triangulate_one_cat(tr: *GradeTriangulation, cs: *CardSet,
1895 verdicts: *nx_int, i: nx_int) -> nx_int {
1896 let sq_c: *CategoryReport = _grade_cat_at(cs.sq, i)
1897 let cl_c: *CategoryReport = _grade_cat_at(cs.cl, i)
1898 let el_c: *CategoryReport = _grade_cat_at(cs.el, i)
1899 verdicts[0] = sq_c.verdict
1900 verdicts[1] = cl_c.verdict
1901 verdicts[2] = el_c.verdict
1902 let t: *TriangulatedReport = _grade_tri_at(tr, i)
1903 _triangulate_one(t, i, verdicts[0], verdicts[1], verdicts[2])
1904 _bump_tri(tr, t.triangulated, t.disagreement_count)
1905 return 0
1906}
1907
1908// Public entry point: take three cards (one per provenance, computed
1909// already), produce triangulated report. Caller runs scan + compute
1910// on each card with the correct profile first.
1911// Build a 3-card CardSet (cert/linus/idris/jpl slots null'd).
1912// Bundles the 4-allocate-and-zero pattern so the public entry point
1913// stays narrow per JPL/Linus fan-out thresholds.
1914func _build_3cardset(sq: *GradeCard, cl: *GradeCard,
1915 el: *GradeCard) -> *CardSet {
1916 let cs_raw: *u8 = sys_mmap(96)
1917 let cs: *CardSet = cs_raw as *CardSet
1918 cs.sq = sq
1919 cs.cl = cl
1920 cs.el = el
1921 cs.jpl = 0 as *GradeCard
1922 cs.cert = 0 as *GradeCard
1923 cs.linus = 0 as *GradeCard
1924 cs.idris = 0 as *GradeCard
1925 cs.n_cards = 3
1926 return cs
1927}
1928
1929func nx_grade_triangulate(tr: *GradeTriangulation,
1930 sq: *GradeCard, cl: *GradeCard, el: *GradeCard) -> nx_int {
1931 _zero_poly_counters(tr)
1932 let cs: *CardSet = _build_3cardset(sq, cl, el)
1933 let verdicts_raw: *u8 = sys_mmap(32)
1934 let verdicts: *nx_int = verdicts_raw as *nx_int
1935 var i: nx_int = 0
1936 while i < NX_GRADE_N_CATS {
1937 _triangulate_one_cat(tr, cs, verdicts, i)
1938 i = i + 1
1939 }
1940 tr.final_grade = _grade_letter(tr.n_triangulated_wins,
1941 tr.n_triangulated_losses)
1942 return tr.final_grade
1943}
1944
1945// Split the per-grader line into 3 helpers so each call site stays at
1946// <=4 callees (JPL fanout win_max).
1947func _emit_graders_0_3(t: *TriangulatedReport) -> nx_int {
1948 print("sq=" as *u8)
1949 print(_verdict_str(t.sonarqube_verdict))
1950 print("cl=" as *u8)
1951 print(_verdict_str(t.clippy_verdict))
1952 print("el=" as *u8)
1953 print(_verdict_str(t.elm_verdict))
1954 print("jpl=" as *u8)
1955 print(_verdict_str(t.jpl_verdict))
1956 return 0
1957}
1958
1959func _emit_graders_4_6(t: *TriangulatedReport) -> nx_int {
1960 print("cert=" as *u8)
1961 print(_verdict_str(t.cert_verdict))
1962 print("linus=" as *u8)
1963 print(_verdict_str(t.linus_verdict))
1964 print("idris=" as *u8)
1965 print(_verdict_str(t.idris_verdict))
1966 return 0
1967}
1968
1969func _emit_tri_category(t: *TriangulatedReport) -> nx_int {
1970 print(" " as *u8)
1971 print(_kind_str(t.cat_kind))
1972 print("tri=" as *u8)
1973 print(_verdict_str(t.triangulated))
1974 print("(" as *u8)
1975 _emit_graders_0_3(t)
1976 _emit_graders_4_6(t)
1977 print("disagree=" as *u8)
1978 print_i64(t.disagreement_count)
1979 println(")" as *u8)
1980 return 0
1981}
1982
1983func _emit_tri_header(tr: *GradeTriangulation) -> nx_int {
1984 print("triangulated: grade=" as *u8)
1985 print(_grade_letter_str(tr.final_grade))
1986 print(" unanimous=" as *u8)
1987 print_i64(tr.n_unanimous)
1988 print(" outlier=" as *u8)
1989 print_i64(tr.n_with_outlier)
1990 print(" controversial=" as *u8)
1991 print_i64(tr.n_fully_controversial)
1992 print(" WIN=" as *u8)
1993 print_i64(tr.n_triangulated_wins)
1994 print(" LOSE=" as *u8)
1995 print_i64(tr.n_triangulated_losses)
1996 println("" as *u8)
1997 return 0
1998}
1999
2000func nx_grade_triangulation_emit(tr: *GradeTriangulation) -> nx_int {
2001 _emit_tri_header(tr)
2002 var i: nx_int = 0
2003 while i < NX_GRADE_N_CATS {
2004 _emit_tri_category(_grade_tri_at(tr, i))
2005 i = i + 1
2006 }
2007 return 0
2008}