code wiki / (root) / nx_quality_grade.nx

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}