diff --git a/.gitattributes b/.gitattributes index 579e0ba6cd..8302eb59e3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -23,3 +23,8 @@ # # scripts/check_append_only_ledgers.sh holds that distinction as a case table. docs/audits/*.jsonl merge=union +# Append-only ledgers one level down. `docs/audits/*.jsonl` does not cross a `/`, +# so these two resolved no merge driver (check_append_only_ledgers, after the +# main merge into #4431). Named file by file: the scope rule above still holds. +docs/audits/review-corpus/corpus-v1.jsonl merge=union +docs/audits/rex-001/rex-04-admission.jsonl merge=union diff --git a/.github/workflows/conleche-nightly.yml b/.github/workflows/conleche-nightly.yml new file mode 100644 index 0000000000..63285936a7 --- /dev/null +++ b/.github/workflows/conleche-nightly.yml @@ -0,0 +1,79 @@ +# conleche-nightly.yml -- PVL-F7 (#3142): an independent kernel re-checks the Lean tree once a day. ADVISORY. +# +# Every other verdict on crates/aprender-contracts-staging/lean comes from Lean's own C++ kernel (lake build, +# leanchecker via `pv discharge check`). con-leche is a second checker that shares no code with it or with pv, +# and it rejects every axiom beyond propext / Classical.choice / Quot.sound -- so it also re-proves that the +# escape allowlist is empty (#4347). The whole procedure, its pins and its controls live in conleche.sh; this +# file only provisions elan and runs it. +# +# ADVISORY. Nothing requires this job; a red run blocks no PR and no release. It is a finding to file, not a +# gate: exit 1 = RED (a rejected declaration or an escape axiom), exit 2 = NOT A VERDICT (unsupported feature, +# OOM, unpinned toolchain, or a control that failed). Both turn the run red, with an annotation naming which -- +# "not a verdict" is never shown green. +# +# Measured on lambda 2026-09-25 at 9018366cf (warm pins): controls ok, 402163 declarations accepted, 7:11 wall, +# 5.7 GB max RSS. Cold adds the con-leche build (395 s) and two toolchain downloads. +# +# RUNNER. [self-hosted, Linux, X64, clean-room] -- the sovereign-ci pool, never a hosted runner (operator rule, +# 2026-09-10). elan is not part of the pool image, so the job installs a PINNED, sha256-checked elan into its +# own directory and touches nothing else on the host. Toolchains, pins and the Mathlib cache persist there +# between runs; the concurrency group keeps two runs from sharing them at once. +name: conleche-nightly + +on: + schedule: + # 21:23 UTC -> lands ~02:23 UTC. GitHub dispatches schedules ~5h late on this account (#3292); the slot is + # free and clear of guards-nightly (21:47) and cuda-nightly (20:30). + - cron: '23 21 * * *' + workflow_dispatch: + +concurrency: + group: conleche-nightly + cancel-in-progress: false + +permissions: + contents: read + +jobs: + conleche: + runs-on: [self-hosted, Linux, X64, clean-room] + # 90: BSE-05 T = max(15, ceil(1.5*p99), p99+20) with p99 taken as a cold ~45 min (no history yet: con-leche + # build 6.6 min, toolchains + Mathlib cache, a from-scratch ProvableContracts build, export 4 min, check + # 2.6 min) -> T = max(15, 68, 65) = 68, rounded up for a shared host. The first three runs replace this basis. + timeout-minutes: 90 + env: + CI_CACHE: /mnt/nvme-raid0/ci-cache/pvl-conleche + ELAN_VERSION: v4.2.4 + ELAN_SHA256: 42b94d4244e8353142c456ec0e4ca6528fd898a6c604d4059f494e706e431f63 + LEAN_NUM_THREADS: '8' + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 1 + - name: Verdict case table (the classifier this job trusts) + run: bash crates/aprender-contracts-staging/lean/conleche.sh --self-test + - name: Pinned elan in the job's own directory (sha256-checked, never a host install) + run: | + set -euo pipefail + export ELAN_HOME="$CI_CACHE/elan" + mkdir -p "$CI_CACHE" + if [ ! -x "$ELAN_HOME/bin/elan" ] || ! "$ELAN_HOME/bin/elan" --version | grep -q "${ELAN_VERSION#v}"; then + tgz="$RUNNER_TEMP/elan.tgz" + curl -fsSL -o "$tgz" "https://github.com/leanprover/elan/releases/download/$ELAN_VERSION/elan-x86_64-unknown-linux-gnu.tar.gz" + echo "$ELAN_SHA256 $tgz" | sha256sum -c - + tar xzf "$tgz" -C "$RUNNER_TEMP" + "$RUNNER_TEMP/elan-init" -y --no-modify-path --default-toolchain none + fi + echo "ELAN_HOME=$ELAN_HOME" >> "$GITHUB_ENV" + echo "$ELAN_HOME/bin" >> "$GITHUB_PATH" + - name: con-leche re-check (advisory) + run: | + set -uo pipefail + rc=0 + PVL_CONLECHE_CACHE="$CI_CACHE/pins" bash crates/aprender-contracts-staging/lean/conleche.sh || rc=$? + case "$rc" in + 0) ;; + 1) echo "::error title=con-leche RED::a declaration was rejected or an escape axiom is present -- file it against #3142" ;; + *) echo "::warning title=con-leche NOT A VERDICT::exit $rc -- the tree was not checked (see the log above); not a pass" ;; + esac + exit "$rc" diff --git a/.gitignore b/.gitignore index fe9cf7f63e..036e18e684 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,7 @@ docs/roadmaps/*.lock # The VERDICT artifact (quorum-*.json) is committed; its .lanes/ working # directory is transient and a lane review correctly refused a PR carrying it. docs/audits/quorum-*.json.lanes/ + +# PVL-001 EV-8a (#4202): `pv discharge run`'s full log. The tracked summary is its sibling +# crates/aprender-contracts-staging/discharge-summary.json, outside the tree it hashes. +crates/aprender-contracts-staging/lean/discharge.json diff --git a/Cargo.lock b/Cargo.lock index 5e1d7bff28..ed6b69ff51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,8 +486,10 @@ name = "aprender-contracts" version = "0.69.3" dependencies = [ "aprender-contracts-macros", + "blake3", "criterion 0.5.1", "half", + "proc-macro2", "proptest", "regex", "serde", @@ -1307,6 +1309,16 @@ dependencies = [ "zstd", ] +[[package]] +name = "aprender-review-experiment" +version = "0.69.3" +dependencies = [ + "serde", + "serde_json", + "sha2 0.10.9", + "ureq 2.12.1", +] + [[package]] name = "aprender-serve" version = "0.69.3" diff --git a/Cargo.toml b/Cargo.toml index e8541d3fab..8a14186ab6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,8 @@ members = [ "crates/aprender-zram-adaptive", "crates/aprender-zram-cli", "crates/aprender-zram-generator", + # --- REX-001 (#4354): review-lane experiment harness + frozen analysis --- + "crates/aprender-review-experiment", # --- APR-MONO Phase 3b: presentar sub-crates (enabled) --- "crates/aprender-present-core", "crates/aprender-present-terminal", diff --git a/Makefile b/Makefile index f55faeb772..9b847c0ccb 100644 --- a/Makefile +++ b/Makefile @@ -577,6 +577,7 @@ readme-sync-check: ## Fail if README.md is not what the generator produces # merge-tree measurement READS A FILE ON DISK must turn the hand-edited rows # GREEN, which is what makes their RED load-bearing rather than incidental. # `--class complexity` and `--class satd` are stubs and exit 3, never 0. +.PHONY: oracle-owl oracle-owl-check .PHONY: roadmap-aggregate roadmap-aggregate-check roadmap-aggregate: ## Regenerate docs/roadmaps/roadmap.yaml from docs/roadmaps/entries/ (#3296) @python3 scripts/lib/roadmap_fragments.py aggregate --write @@ -594,6 +595,12 @@ ratchet-semantics-test: ## BSE-03: D2 ratchet polarity rows (--class readme) # enforces COV_FLOOR, so this is a name, not a new policy. coverage-check: coverage +# PVL-001 EV-6a (#4139): the ONLY writer of the Lean label ratchet. `pv discharge check` never writes +# unresolved-labels.json; this rewrites it DOWNWARD (a label that resolves now leaves; a new one is never added). +.PHONY: label-ratchet +label-ratchet: + @. scripts/pv_bin.sh && "$$PV" discharge label-ratchet crates/aprender-contracts-staging/lean --contracts contracts + # Ditto for `contracts`. The provable-contract tier is a HARD release gate per # CLAUDE.md, and the dogfood protocol looked for a target that did not exist, so # it WARNed instead of checking. `pv lint` runs validate + audit + score across @@ -612,7 +619,10 @@ contracts: @. scripts/pv_bin.sh && "$$PV" census contracts --format json > contracts/census.json @git diff --exit-code contracts/census.json || { echo "FAIL: the tracked census differs from a fresh one — commit the regenerated contracts/census.json"; exit 1; } @echo "== graph: tracked contracts/contracts.nt + shapes.ttl == a fresh extraction (ONT-001 ONT-4b, R-18) ==" - @. scripts/pv_bin.sh && "$$PV" extract contracts --check >/dev/null + @. scripts/pv_bin.sh && "$$PV" extract contracts --check >/dev/null || exit 1 + @echo "== consistency: pv-sat writes the witness, pv lint re-checks it (ONT-001 ONT-5, R-1); refines is Liskov (ONT-4e, R-20); bindings resolve (ONT-3a) ==" + @. scripts/pv_bin.sh && { [ -x "$$PV_SAT" ] || { echo "FAIL: no pv-sat beside $$PV -- a PV_BIN override must ship its pv-sat too"; exit 1; }; } && "$$PV_SAT" contracts && "$$PV" lint contracts/ --gate ont-consistency >/dev/null && "$$PV" lint contracts/ --gate refines >/dev/null && "$$PV" lint contracts/ --gate bindings >/dev/null || exit 1 + @test -z "$$(git status --porcelain -- contracts/witness)" || { git status --short -- contracts/witness; echo "FAIL: contracts/witness/ differs from what pv-sat writes -- commit it"; exit 1; } @echo "== README states the censused count ==" @bash scripts/readme_sync.sh --check @echo "== provenance marks, interim (ONT-001 R-10) ==" @@ -1398,6 +1408,16 @@ ont-ratchet: ont-ratchet-check: @bash scripts/check_ont_ratchet.sh --check +# PVL-001 EV-11 (PMAT-4166): the two `pv lint` ratchets (theorem-pairing, depends-on-present) move ONLY +# through this target, and only DOWN. The gates read contracts/lint-baseline.json and never write it. +# NEVER in CI: a CI job that could rewrite the baseline is a ratchet that turns both ways. +.PHONY: lint-ratchet lint-ratchet-self-test +lint-ratchet: + @bash scripts/lint_ratchet.sh + +lint-ratchet-self-test: + @bash scripts/lint_ratchet.sh --self-test + # ONT-001 §5 ONT-4b2 / R-13 — the out-of-gate SHACL differential oracle. # # NOT a PR check, by the rule that puts it here: `shacl` is 316 crates and pinned at ONE version (ONT-0's @@ -1417,3 +1437,22 @@ oracle: oracle-check: oracle @git diff --exit-code tests/oracle/differential.json \ || { echo "FAIL: tests/oracle/differential.json differs from a fresh run — commit it"; exit 1; } + +# ONT-001 §3.8 / ONT-2c — the OWL oracle (release gate only, R-13; never per PR). Three arms: +# horned-owl re-parses the fixture's written .ofn and must equal the HAND-WRITTEN axiom list; every live +# axiom must be a told-closure-admitted kind; ELK 0.4.3 (pinned by sha256, needs a JVM) must agree with +# contracts/tbox-report.json, with a planted positive control turning it RED every run. No JVM exits 2 with +# `decline: NOT MEASURED`, which is RED at the release gate and never a skip. The crate is detached from the +# workspace AND from tests/oracle's SHACL crate (feature unification breaks horned-owl there). +oracle-owl: + @echo "== OWL oracle: horned-owl round-trip + admitted kinds + ELK TBox differential (out of gate) ==" + @. scripts/pv_bin.sh && "$$PV" ontology export --owl tests/fixtures/ont/owl/ontology.yaml > "$${TMPDIR:-/tmp}/ont2c-fixture.ofn" + @cargo build --release --quiet --manifest-path tests/oracle/owl/Cargo.toml + @O="$$(cargo metadata --no-deps --format-version 1 --manifest-path tests/oracle/owl/Cargo.toml | sed -n 's/.*"target_directory":"\([^"]*\)".*/\1/p')/release/owl-oracle"; \ + "$$O" roundtrip "$${TMPDIR:-/tmp}/ont2c-fixture.ofn" tests/fixtures/ont/owl/axioms.txt && \ + "$$O" kinds contracts/ontology.ofn && \ + "$$O" elk . + +oracle-owl-check: oracle-owl + @git diff --exit-code tests/oracle/tbox-differential.json \ + || { echo "FAIL: tests/oracle/tbox-differential.json differs from a fresh run — commit it"; exit 1; } diff --git a/README.md b/README.md index df038597eb..2db49806c1 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ publishing — all backed by YAML provable contracts that fail CI on drift. | Metric | Count | Source of truth | |-------:|------:|---| | Workspace crates | **79** workspace crates | `cargo metadata --no-deps` (NOT `ls crates/` — 4 are `exclude`d, 1 has no Cargo.toml) | -| Provable contracts | **1837** provable contracts | `contracts/census.json` `.n_files` — the set `pv lint` walks (`pv census`, ONT-001 ONT-1; regenerated by `make contracts`, written by `make readme-sync`, guarded by `scripts/check_readme_claims.sh`) | +| Provable contracts | **1851** provable contracts | `contracts/census.json` `.n_files` — the set `pv lint` walks (`pv census`, ONT-001 ONT-1; regenerated by `make contracts`, written by `make readme-sync`, guarded by `scripts/check_readme_claims.sh`) | | CLI commands | **111** CLI commands | `contracts/apr-cli-commands-v1.yaml` §`commands` (parse the list; `apr --help` prints 112 because it lists `help` itself, and `grep -c '^ - name:'` gives 117 — other same-indent `name:` keys exist in the file) | | Book CLI chapters | **113** chapters | `ls book/src/cli/*.md` | | Book lib chapters | **72** chapters | `ls book/src/lib/*.md` (parity with `pub mod`) | @@ -321,7 +321,7 @@ falsification_tests: prediction: apr validate bad-model.apr exits non-zero ``` -The tree carries 1837 contracts across inference, training, quantization, attention, FFN, +The tree carries 1851 contracts across inference, training, quantization, attention, FFN, tokenization, model formats, CLI safety — and this README itself. ## Migration from old crates diff --git a/ci/explicit-test-commands.d/456-aprender-contracts-cli-ev11-lint-ratchets.cmd b/ci/explicit-test-commands.d/456-aprender-contracts-cli-ev11-lint-ratchets.cmd new file mode 100644 index 0000000000..7f427a94da --- /dev/null +++ b/ci/explicit-test-commands.d/456-aprender-contracts-cli-ev11-lint-ratchets.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ev11_lint_ratchets diff --git a/ci/explicit-test-commands.d/457-aprender-contracts-cli-ont8-evidence-gate.cmd b/ci/explicit-test-commands.d/457-aprender-contracts-cli-ont8-evidence-gate.cmd new file mode 100644 index 0000000000..61c839eb5b --- /dev/null +++ b/ci/explicit-test-commands.d/457-aprender-contracts-cli-ont8-evidence-gate.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont8_evidence_gate diff --git a/ci/explicit-test-commands.d/461-aprender-contracts-cli-pvl-discharge-check.cmd b/ci/explicit-test-commands.d/461-aprender-contracts-cli-pvl-discharge-check.cmd new file mode 100644 index 0000000000..f10fbfea13 --- /dev/null +++ b/ci/explicit-test-commands.d/461-aprender-contracts-cli-pvl-discharge-check.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test pvl_discharge_check diff --git a/ci/explicit-test-commands.d/462-aprender-contracts-cli-pvl-obligations-golden.cmd b/ci/explicit-test-commands.d/462-aprender-contracts-cli-pvl-obligations-golden.cmd new file mode 100644 index 0000000000..7f4042f19d --- /dev/null +++ b/ci/explicit-test-commands.d/462-aprender-contracts-cli-pvl-obligations-golden.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test pvl_obligations_golden diff --git a/ci/explicit-test-commands.d/463-aprender-contracts-cli-ont2c-owl-tbox.cmd b/ci/explicit-test-commands.d/463-aprender-contracts-cli-ont2c-owl-tbox.cmd new file mode 100644 index 0000000000..16f5bbe212 --- /dev/null +++ b/ci/explicit-test-commands.d/463-aprender-contracts-cli-ont2c-owl-tbox.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont2c_owl_tbox diff --git a/ci/explicit-test-commands.d/471-aprender-contracts-cli-pvl-discharge-leanchecker.cmd b/ci/explicit-test-commands.d/471-aprender-contracts-cli-pvl-discharge-leanchecker.cmd new file mode 100644 index 0000000000..663ef8f32a --- /dev/null +++ b/ci/explicit-test-commands.d/471-aprender-contracts-cli-pvl-discharge-leanchecker.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test pvl_discharge_leanchecker diff --git a/ci/explicit-test-commands.d/472-aprender-contracts-cli-ont4d-subsumption.cmd b/ci/explicit-test-commands.d/472-aprender-contracts-cli-ont4d-subsumption.cmd new file mode 100644 index 0000000000..84cc173d0a --- /dev/null +++ b/ci/explicit-test-commands.d/472-aprender-contracts-cli-ont4d-subsumption.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont4d_subsumption diff --git a/ci/explicit-test-commands.d/480-aprender-contracts-cli-pvl-challenge.cmd b/ci/explicit-test-commands.d/480-aprender-contracts-cli-pvl-challenge.cmd new file mode 100644 index 0000000000..1f5f60d80e --- /dev/null +++ b/ci/explicit-test-commands.d/480-aprender-contracts-cli-pvl-challenge.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test pvl_challenge diff --git a/ci/explicit-test-commands.d/481-aprender-contracts-cli-pvl-comparator.cmd b/ci/explicit-test-commands.d/481-aprender-contracts-cli-pvl-comparator.cmd new file mode 100644 index 0000000000..02c3c9635d --- /dev/null +++ b/ci/explicit-test-commands.d/481-aprender-contracts-cli-pvl-comparator.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test pvl_comparator diff --git a/ci/explicit-test-commands.d/490-aprender-contracts-cli-pvl-ghost-binding.cmd b/ci/explicit-test-commands.d/490-aprender-contracts-cli-pvl-ghost-binding.cmd new file mode 100644 index 0000000000..ddafe0fb95 --- /dev/null +++ b/ci/explicit-test-commands.d/490-aprender-contracts-cli-pvl-ghost-binding.cmd @@ -0,0 +1,3 @@ +# PVL-001 EV-2 (#4080): pv proof-status --binding resolves bindings; a ghost binding is a reject. +# Reads tests/fixtures/pvl/ and contracts/ from the workspace root (the resolver is CWD-sensitive). +cargo test -p aprender-contracts-cli --test pvl_ghost_binding diff --git a/ci/explicit-test-commands.d/510-aprender-contracts-cli-ont5-consistency-gate.cmd b/ci/explicit-test-commands.d/510-aprender-contracts-cli-ont5-consistency-gate.cmd new file mode 100644 index 0000000000..4245d9c682 --- /dev/null +++ b/ci/explicit-test-commands.d/510-aprender-contracts-cli-ont5-consistency-gate.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont5_consistency_gate diff --git a/ci/explicit-test-commands.d/520-aprender-contracts-cli-pv-sat-bin.cmd b/ci/explicit-test-commands.d/520-aprender-contracts-cli-pv-sat-bin.cmd new file mode 100644 index 0000000000..c489d7f13b --- /dev/null +++ b/ci/explicit-test-commands.d/520-aprender-contracts-cli-pv-sat-bin.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --bin pv-sat diff --git a/ci/explicit-test-commands.d/575-aprender-contracts-cli-ont9-self-contract.cmd b/ci/explicit-test-commands.d/575-aprender-contracts-cli-ont9-self-contract.cmd new file mode 100644 index 0000000000..de771831df --- /dev/null +++ b/ci/explicit-test-commands.d/575-aprender-contracts-cli-ont9-self-contract.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont9_self_contract diff --git a/ci/explicit-test-commands.d/580-aprender-contracts-cli-ont4f-github-entities.cmd b/ci/explicit-test-commands.d/580-aprender-contracts-cli-ont4f-github-entities.cmd new file mode 100644 index 0000000000..4b2e98664b --- /dev/null +++ b/ci/explicit-test-commands.d/580-aprender-contracts-cli-ont4f-github-entities.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont4f_github_entities diff --git a/ci/explicit-test-commands.d/600-aprender-contracts-cli-ont4e-refines-gate.cmd b/ci/explicit-test-commands.d/600-aprender-contracts-cli-ont4e-refines-gate.cmd new file mode 100644 index 0000000000..1d168b3861 --- /dev/null +++ b/ci/explicit-test-commands.d/600-aprender-contracts-cli-ont4e-refines-gate.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont4e_refines_gate diff --git a/contracts/alibi-slopes-v1.yaml b/contracts/alibi-slopes-v1.yaml index 688538bf36..5d299a6655 100644 --- a/contracts/alibi-slopes-v1.yaml +++ b/contracts/alibi-slopes-v1.yaml @@ -55,7 +55,7 @@ proof_obligations: mathlib_imports: - Mathlib.Analysis.SpecialFunctions.Pow.Real notes: 'Exponent -8(h+1)/n < 0 for h >= 0, n > 0; base 2 > 1 so 2^(neg) < 1. - Companion alibi_slope_pos proves 0 < m[h] (strictly positive geometric sequence).' + Companion alibi_slope_real_pos proves 0 < m[h] (strictly positive geometric sequence).' - id: AS-EQ-003 type: equivalence property: Matches ggml reference exponent @@ -89,7 +89,7 @@ verification_summary: theorem: Alibi.alibi_slope_lt_one file: ProvableContracts/Theorems/Alibi/Slopes.lean note: 'exponent -8(h+1)/n < 0 for h>=0, n>0, and base 2>1, so 2^(neg) < 1 - (Real.rpow_lt_one_of_one_lt_of_neg). alibi_slope_pos additionally proves 0 < m[h].' + (Real.rpow_lt_one_of_one_lt_of_neg). alibi_slope_real_pos additionally proves 0 < m[h].' - obligation: Matches ggml reference exponent theorem: Alibi.alibi_slope_ggml file: ProvableContracts/Theorems/Alibi/Slopes.lean @@ -97,7 +97,7 @@ verification_summary: matching llama.cpp soft_max_ext.' notes: 'All 3 obligations proved sorry-free in Lean 4 over the reals (Mathlib Real.rpow). Zero N/A — the contract is fully analytic. Slopes are a positive, - strictly-decreasing geometric sequence: alibi_slope_pos (m[h]>0) and + strictly-decreasing geometric sequence: alibi_slope_real_pos (m[h]>0) and alibi_slope_strict_anti (m[0]>m[1]>...>m[n-1]) additionally proved. Verified via lake env lean ProvableContracts/Theorems/Alibi/Slopes.lean (exit 0, 0 sorry).' kernel_structure: diff --git a/contracts/apr-cli-commands-v1.yaml b/contracts/apr-cli-commands-v1.yaml index 8e94cce4f5..25746cf70d 100644 --- a/contracts/apr-cli-commands-v1.yaml +++ b/contracts/apr-cli-commands-v1.yaml @@ -786,7 +786,7 @@ commands: description: "Provable-contracts CLI (also ships as the standalone pv binary)" requires_model: false side_effects: [filesystem] - subcommands: [audit, book, census, certify, check-parity, codegen, coq, coverage, diff, equations, explain, extract, extract-pytorch, flux, fuzz, generate, graph, infer, invariants, kaizen, kani, lean, lean-status, lint, migrate, mirai, pipeline, probar, proof-status, query, roofline, scaffold, score, status, tla, unlock, validate, verify-bindings, verify-pipeline, verify-structure] + subcommands: [audit, book, census, certify, challenge, check-parity, codegen, coq, coverage, diff, discharge, equations, explain, extract, extract-pytorch, flux, fuzz, generate, graph, infer, invariants, kaizen, kani, lean, lean-status, lint, migrate, mirai, obligations, ontology, pipeline, probar, proof-status, query, roofline, scaffold, score, status, tla, unlock, validate, verify-bindings, verify-pipeline, verify-structure] # ── Falsification Conditions ── diff --git a/contracts/apr-cli-coverage-v1.yaml b/contracts/apr-cli-coverage-v1.yaml index b09e842449..a0f20e02b6 100644 --- a/contracts/apr-cli-coverage-v1.yaml +++ b/contracts/apr-cli-coverage-v1.yaml @@ -44,7 +44,7 @@ falsification_tests: if_fails: "apr-cli coverage below 95%" verification_summary: - total_obligations: 1 + total_obligations: 0 proven: 0 tested: 0 status: pending diff --git a/contracts/apr-cli-dep-migration-v1.yaml b/contracts/apr-cli-dep-migration-v1.yaml index 0f62f46a46..b324069149 100644 --- a/contracts/apr-cli-dep-migration-v1.yaml +++ b/contracts/apr-cli-dep-migration-v1.yaml @@ -58,7 +58,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 2 + total_obligations: 1 proven: 0 tested: 0 status: pending diff --git a/contracts/apr-cli-publish-v1.yaml b/contracts/apr-cli-publish-v1.yaml index 286aeb97d4..d8b539b16f 100644 --- a/contracts/apr-cli-publish-v1.yaml +++ b/contracts/apr-cli-publish-v1.yaml @@ -88,7 +88,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 4 + total_obligations: 1 proven: 0 tested: 0 status: pending diff --git a/contracts/apr-cli-qa-v1.yaml b/contracts/apr-cli-qa-v1.yaml index 86805829f2..8fbec732e1 100644 --- a/contracts/apr-cli-qa-v1.yaml +++ b/contracts/apr-cli-qa-v1.yaml @@ -254,7 +254,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 11 + total_obligations: 5 proven: 0 tested: 11 status: tested diff --git a/contracts/apr-cli-safety-v1.yaml b/contracts/apr-cli-safety-v1.yaml index 7d2ad8b6fc..ddf18d4487 100644 --- a/contracts/apr-cli-safety-v1.yaml +++ b/contracts/apr-cli-safety-v1.yaml @@ -88,8 +88,8 @@ qa_gate: - falsification pass_criteria: All 5 falsification tests + 4 proof obligations pass verification_summary: - total_obligations: 4 - l2_property_tested: 4 + total_obligations: 1 + l2_property_tested: 1 l3_kani_proved: 0 l4_lean_proved: 0 l4_sorry_count: 0 diff --git a/contracts/apr-cpu-vs-gpu-output-parity-v1.yaml b/contracts/apr-cpu-vs-gpu-output-parity-v1.yaml index 7c1e0690ff..12a93756d7 100644 --- a/contracts/apr-cpu-vs-gpu-output-parity-v1.yaml +++ b/contracts/apr-cpu-vs-gpu-output-parity-v1.yaml @@ -607,7 +607,7 @@ proof_obligations: property: "on Blackwell (cc>=120) default apr run --gpu decode throughput for a 1.5B Q4_K_M model is >= 100 tok/s (on-GPU resident path, no silent CPU/wgpu fallback); ~10 tok/s falsifies it as an F2 false-fallback / stale binary (PMAT-885)" verification_summary: - total_obligations: 11 + total_obligations: 10 proven: 0 tested: 1 status: pending diff --git a/contracts/apr-docs-v1.yaml b/contracts/apr-docs-v1.yaml index b610f4082a..537a6da980 100644 --- a/contracts/apr-docs-v1.yaml +++ b/contracts/apr-docs-v1.yaml @@ -159,7 +159,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 9 + total_obligations: 3 proven: 0 tested: 9 status: tested diff --git a/contracts/apr-dogfood-coverage-v1.yaml b/contracts/apr-dogfood-coverage-v1.yaml index f4d1558b70..78407bb40a 100644 --- a/contracts/apr-dogfood-coverage-v1.yaml +++ b/contracts/apr-dogfood-coverage-v1.yaml @@ -62,6 +62,13 @@ metadata: # crates/apr-cli/src/commands_enum.rs. All 64 citations of that file in 55 rows # were re-derived: 42 at lines >= 139 shifted by +4, 22 at <= 138 unchanged, and # every cited line's text was checked identical between 79a3af79d and the batch. + # PARTIAL RE-AUDIT, PVL-001 EV-2 / #4095 (G2.1). Same rule: `measured_commit` does not + # move. #4095 rewrites one doc comment in crates/aprender-contracts-cli/src/cli.rs + # line-for-line (4 for 4, no shift), but re-deriving the 76 `pv`/`apr pv` rows that + # cite it found 54 ALREADY stale on main (+6 from `Coverage` on, +12 from `Lint` on -- + # variants inserted since 2026-08-22, never re-audited). Every row now cites the line of + # its own `Commands::` declaration, checked by reading that line back; 22 were + # already exact. `pv census` and `pv extract` ship with no row at all -- not added here. ledger: docs/audits/surface_audit.csv overall: @@ -842,7 +849,7 @@ qa_gate: pass_criteria: "F-DOGCOV-{001..016} all PASS; any one FAIL is NO-GO" verification_summary: - total_obligations: 16 + total_obligations: 9 proven: 0 tested: 16 status: proposed diff --git a/contracts/apr-pretrain-from-init-v1.yaml b/contracts/apr-pretrain-from-init-v1.yaml index 4a61a70ae4..0aa30b1e6c 100644 --- a/contracts/apr-pretrain-from-init-v1.yaml +++ b/contracts/apr-pretrain-from-init-v1.yaml @@ -313,7 +313,7 @@ kani_harnesses: bound: 8 # 4 failure modes × 2 mode values verification_summary: - total_obligations: 6 + total_obligations: 7 proven: 0 tested: 0 status: pending diff --git a/contracts/aprender/binding.yaml b/contracts/aprender/binding.yaml index 18d9f39a96..c0bf2e2a1d 100644 --- a/contracts/aprender/binding.yaml +++ b/contracts/aprender/binding.yaml @@ -30,15 +30,15 @@ bindings: status: implemented - contract: pagerank-kernel-v1.yaml equation: pagerank - module_path: aprender::graph::pagerank - function: pagerank + module_path: aprender::graph::centrality + function: Graph::pagerank signature: 'fn pagerank(adj: &Tensor, damping: f64, max_iter: usize, tol: f64) -> Tensor' status: implemented notes: Power iteration PageRank - contract: pagerank-kernel-v1.yaml equation: power_iteration - module_path: aprender::graph::pagerank - function: pagerank + module_path: aprender::graph::centrality + function: Graph::pagerank signature: 'fn pagerank(adj: &Tensor, damping: f64, max_iter: usize, tol: f64) -> Tensor' status: implemented notes: Iteration loop inside pagerank() @@ -186,7 +186,7 @@ bindings: notes: Weighted Gini impurity for binary split - contract: decision-tree-v1.yaml equation: mse_split - module_path: aprender::tree::regression_helpers + module_path: aprender::tree::helpers function: compute_mse signature: 'fn compute_mse(y_left: &[f32], y_right: &[f32]) -> f32' status: implemented @@ -253,43 +253,43 @@ bindings: notes: Header validation with model type check before loading - contract: model-format-conversion-v1.yaml equation: format_conversion_roundtrip - module_path: aprender::convert + module_path: apr_cli::commands::convert function: run status: implemented notes: Tensor count/names/shapes preserved across format conversion - contract: model-format-conversion-v1.yaml equation: quantization_bounds - module_path: aprender::quantize - function: quantize_tensor + module_path: aprender::format + function: quantize_data status: implemented notes: Error bounded by dtype-specific tolerance - contract: model-format-conversion-v1.yaml equation: import_integrity - module_path: aprender::import + module_path: apr_cli::commands::import function: run status: implemented notes: Content-based format detection, not extension-based - contract: model-format-conversion-v1.yaml equation: export_fidelity - module_path: aprender::export + module_path: apr_cli::commands::export function: run status: implemented notes: Atomic write via temp file + rename - contract: model-format-conversion-v1.yaml equation: apr_tokenizer_embedding - module_path: aprender::convert + module_path: aprender::format::converter function: save_model_tensors_with_gguf_config_and_tokenizer status: implemented notes: "P0 invariant \u2014 every APR file embeds tokenizer (PMAT-154 fix, Q4K passthrough/fallback paths)" - contract: apr-cli-operations-v1.yaml equation: side_effect_classification - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_core_command status: implemented notes: All 48 commands classified as ReadOnly/Mutating/LongRunning - contract: apr-cli-operations-v1.yaml equation: resource_cleanup - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_core_command status: implemented notes: GPU/tmp/thread cleanup on all exit paths via RAII @@ -297,36 +297,36 @@ bindings: equation: inference_determinism module_path: aprender::inference function: generate - status: implemented + status: not_implemented notes: temperature=0 greedy decoding is deterministic - contract: apr-cli-operations-v1.yaml equation: tokenizer_consistency - module_path: aprender::tokenizer - function: encode + module_path: aprender::text::tokenize::bpe_impl + function: BpeTokenizer::encode status: implemented notes: BPE encode/decode roundtrip for valid UTF-8 - contract: apr-cli-operations-v1.yaml equation: concurrent_model_access - module_path: aprender::serve + module_path: apr_cli::commands::serve function: run status: implemented notes: Per-request KV cache, immutable model weights - contract: apr-cli-v1.yaml equation: contract_gate_enforcement - module_path: apr_cli::dispatch + module_path: apr_cli function: execute_command status: implemented notes: "PMAT-237 contract gate \u2014 extract_model_paths + validate_model_contract before dispatch" - contract: apr-cli-v1.yaml equation: training_plan_apply_semantics module_path: apr_cli::commands::train - function: train::run + function: run_apply status: implemented notes: Plan is pure (no GPU/filesystem), Apply writes to --output directory only - contract: apr-cli-v1.yaml equation: tokenizer_training_correctness module_path: apr_cli::commands::tokenize - function: tokenize::run + function: run_apply status: implemented notes: "BPE plan/apply \u2014 vocab.json size equals requested vocab_size" - contract: apr-cli-v1.yaml @@ -392,20 +392,20 @@ bindings: - contract: apr-data-pipeline-v1.yaml equation: data_validation module_path: apr_cli::commands::data - function: run + function: run_audit status: implemented notes: JSONL/CSV/text validation with line-level error reporting - contract: apr-data-pipeline-v1.yaml equation: data_split_determinism module_path: apr_cli::commands::data - function: run + function: run_split status: implemented notes: Seed-deterministic train/val/test split - contract: apr-data-pipeline-v1.yaml equation: streaming_data_loader module_path: aprender::data::dataloader function: DataLoader::next_batch - status: implemented + status: not_implemented notes: Epoch-seeded shuffle, no sample drop on final partial batch - contract: apr-model-qa-v1.yaml equation: model_integrity_check @@ -440,26 +440,26 @@ bindings: - contract: apr-architecture-schema-v1.yaml equation: architecture_config_invariants module_path: aprender::format::gguf::api - function: GgufModelConfig::validate + function: GgufModelConfig::warn_out_of_bounds status: implemented notes: Validates hidden_size/num_heads divisibility, GQA ratio, MoE fields - contract: apr-architecture-schema-v1.yaml equation: rope_position_encoding - module_path: aprender::nn::rope - function: RotaryPositionEmbedding::validate_config + module_path: aprender::nn::transformer::attention_helpers + function: RotaryPositionEmbedding::with_base status: implemented notes: RoPE theta, type (NORM vs NEOX), frequency vector length - contract: apr-format-safety-v1.yaml equation: magic_byte_validation module_path: aprender::gguf::reader function: detect_format - status: implemented + status: not_implemented notes: GGUF/SafeTensors/APR magic byte prefix detection, panic-free on truncated input - contract: apr-format-safety-v1.yaml equation: header_integrity module_path: aprender::gguf::reader function: validate_header - status: implemented + status: not_implemented notes: Bounded allocation, no OOM from crafted tensor_count - contract: apr-chat-session-v1.yaml equation: session_state_machine @@ -469,14 +469,14 @@ bindings: notes: Init->WaitInput->Generating->WaitInput->Exit state machine - contract: apr-chat-session-v1.yaml equation: session_persistence - module_path: apr_cli::commands::chat_session + module_path: apr_cli::commands::chat function: run status: implemented notes: JSON serialization, KV-cache not persisted (rebuilt from history) # --- cli-dispatch-v1 bindings (PMAT-495) --- - contract: cli-dispatch-v1.yaml equation: dispatch_completeness - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_core_command signature: 'fn dispatch_core_command(cli: &Cli) -> Option>' status: implemented @@ -490,21 +490,21 @@ bindings: notes: Distinct error classes map to distinct non-zero exit codes (0-11) - contract: cli-dispatch-v1.yaml equation: output_format_fidelity - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_core_command signature: 'fn dispatch_core_command(cli: &Cli) -> Option>' status: implemented notes: --json/--yaml/--csv produce parseable output per respective RFC - contract: cli-dispatch-v1.yaml equation: feature_gated_dispatch - module_path: apr_cli::dispatch_analysis + module_path: apr_cli function: dispatch_extended_command signature: 'fn dispatch_extended_command(cli: &Cli) -> Result<(), CliError>' status: implemented notes: Code variant dispatches to batuta::agent::code::cmd_code (PMAT-182) - contract: cli-dispatch-v1.yaml equation: idempotent_inspection - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_inspection_commands signature: 'fn dispatch_inspection_commands(cli: &Cli) -> Option>' status: implemented @@ -666,7 +666,7 @@ bindings: module_path: aprender::inference function: generate signature: 'fn generate(&mut self, prompt_tokens: &[u32], max_tokens: usize, ...) -> Vec' - status: implemented + status: not_implemented notes: temperature=0 produces deterministic greedy output on GPU - contract: apr-gpu-backend-v1.yaml equation: gpu_cpu_parity @@ -685,21 +685,21 @@ bindings: # --- tokenizer-loading-v1 bindings (PMAT-495) --- - contract: tokenizer-loading-v1.yaml equation: identity - module_path: aprender::format::converter::tokenizer_loader + module_path: aprender::format::converter::import function: load_tokenizer_from_explicit_path signature: 'fn load_tokenizer_from_explicit_path(path: &Path) -> Result' status: implemented notes: Loads BPE tokenizer from tokenizer.json or vocab.json - contract: tokenizer-loading-v1.yaml equation: roundtrip_encoding - module_path: aprender::tokenizer - function: encode + module_path: aprender::text::tokenize::bpe_impl + function: BpeTokenizer::encode signature: 'fn encode(&self, text: &str) -> Vec' status: implemented notes: decode(encode(text)) == text for valid UTF-8 - contract: tokenizer-loading-v1.yaml equation: byte_encoder_coverage - module_path: apr_cli::commands::chat_load_tokenizers + module_path: apr_cli::commands::chat::realizar_chat function: load_tokenizers signature: 'fn load_tokenizers(...) -> Result' status: implemented @@ -721,22 +721,22 @@ bindings: notes: KV projection shape [hidden_size, kv_heads * head_dim] - contract: qwen2-weight-loading-v1.yaml equation: swiglu_expansion - module_path: aprender::models::qwen2::constructors - function: new + module_path: aprender::models::qwen2 + function: Qwen2Model::new signature: 'pub fn new(config: &Qwen2Config) -> Self' status: implemented notes: FFN intermediate = 2/3 * 4 * hidden_size (SwiGLU) - contract: qwen2-weight-loading-v1.yaml equation: total_parameters - module_path: aprender::models::qwen2::constructors - function: weight_names + module_path: aprender::models::qwen2 + function: Qwen2Model::weight_names signature: 'fn weight_names() -> Vec' status: implemented notes: Total params matches HuggingFace config.json num_parameters # --- apr-cli-mutating-v1 bindings (GH-689) --- - contract: apr-cli-mutating-v1.yaml equation: output_path_validation - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_model_commands signature: 'fn dispatch_model_commands(cli: &Cli) -> Option>' status: implemented @@ -757,7 +757,7 @@ bindings: notes: Temp file + rename pattern for atomic writes - contract: apr-cli-mutating-v1.yaml equation: rm_confirmation_gate - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_model_commands signature: 'fn dispatch_model_commands(cli: &Cli) -> Option>' status: implemented @@ -765,14 +765,14 @@ bindings: # --- apr-cli-readonly-v1 bindings (GH-688) --- - contract: apr-cli-readonly-v1.yaml equation: no_side_effects - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_inspection_commands signature: 'fn dispatch_inspection_commands(cli: &Cli) -> Option>' status: implemented notes: 28 readonly commands never modify filesystem or model state - contract: apr-cli-readonly-v1.yaml equation: idempotent_output - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_inspection_commands signature: 'fn dispatch_inspection_commands(cli: &Cli) -> Option>' status: implemented @@ -809,8 +809,8 @@ bindings: # --- encoder-forward-v1 bindings (GH-326, BERT inference) --- - contract: ../encoder-forward-v1.yaml equation: encoder_layer - module_path: aprender::models::bert - function: forward + module_path: aprender::models::bert::layer + function: BertLayer::forward signature: 'fn forward(&self, input: &Tensor) -> Tensor' status: implemented notes: "BERT encoder layer: LayerNorm(x + BiAttn(x)) → LayerNorm(h + FFN(h))" @@ -819,7 +819,7 @@ bindings: module_path: aprender::models::bert function: cls_embedding signature: 'fn cls_embedding(&self, encoder_output: &Tensor) -> Tensor' - status: implemented + status: not_implemented notes: "CLS pooling: encoder_output[0] (first token embedding)" # --- format-parity-v1 bindings (model format conversion) --- - contract: ../format-parity-v1.yaml @@ -827,19 +827,19 @@ bindings: module_path: aprender::format::layout function: swap_axes signature: 'fn swap_axes(tensor: &Tensor) -> Tensor' - status: implemented + status: not_implemented notes: "swap(swap(shape)) == shape — transpose is its own inverse" - contract: ../format-parity-v1.yaml equation: element_count module_path: aprender::format::layout function: validate_element_count signature: 'fn validate_element_count(gguf_shape: &[usize], apr_shape: &[usize]) -> bool' - status: implemented + status: not_implemented notes: product(gguf_shape) == product(apr_shape) across format conversion - contract: ../format-parity-v1.yaml equation: name_bijection - module_path: aprender::format::converter - function: map_tensor_name + module_path: aprender::format::converter_types + function: Architecture::map_name signature: 'fn map_tensor_name(name: &str, arch: Architecture) -> String' status: implemented notes: "Bijective tensor name mapping: GGUF ↔ APR ↔ SafeTensors" @@ -849,7 +849,7 @@ bindings: module_path: aprender::nn::transformer function: bidirectional_attention signature: 'fn bidirectional_attention(q: &Tensor, k: &Tensor, v: &Tensor) -> Tensor' - status: implemented + status: not_implemented notes: Full attention matrix (no causal mask) for BERT-class models # ============================================================================ # Phase 2 bindings — contrastive-pair-protocol-v1 (24 equations) and @@ -959,7 +959,7 @@ bindings: module_path: entrenar::train::setfit::baseline function: fit signature: 'fn fit(self) -> Result<(MultinomialLogisticRegression, FrozenProbeReport), SetFitTrainError>' - status: implemented + status: not_implemented notes: 'FrozenProbeRun — a separate type that is NOT a SetFitRun state and has no conversion to one. Encodes the selected rows in eval mode inside autograd::no_grad with no tuning step, fits MultinomialLogisticRegression on the frozen embeddings, and reports kind @@ -1030,7 +1030,7 @@ bindings: equation: doc_bundle_bijection module_path: entrenar::train::setfit::apr_codec function: deserialize - status: implemented + status: not_implemented notes: 'Lands with plan 04-05. The closure equation serialize(deserialize(bytes)) == bytes at Tolerance::EXACT; depends on bundle field 20 (ProvenanceRecord) from 04-13.' - contract: setfit-apr-v1.yaml equation: canonical_tensor_names @@ -1104,7 +1104,7 @@ bindings: equation: selection_lock_lifecycle module_path: entrenar::train::setfit::lock function: mint_test_token - status: implemented + status: not_implemented notes: 'The durable lock artifact and the CLI workflow land with 04-07 / 04-14 (review B4); the underlying lock, token minting and grant already exist from Phase 3 and are consumed as shipped.' - contract: setfit-apr-v1.yaml equation: d02_typed_key_amendment diff --git a/contracts/binding-allowlist.json b/contracts/binding-allowlist.json new file mode 100644 index 0000000000..ca43180bbf --- /dev/null +++ b/contracts/binding-allowlist.json @@ -0,0 +1,9 @@ +{ + "entries": [ + { + "symbol": "aprender::inference::forward::forward_pass", + "reason": "ghost: no `mod inference` or `use \u2026 inference` in `crates/aprender-core/src/lib.rs`", + "ticket": "#4094" + } + ] +} diff --git a/contracts/binding.yaml b/contracts/binding.yaml index 1391a732f4..f2801f8f0f 100644 --- a/contracts/binding.yaml +++ b/contracts/binding.yaml @@ -30,15 +30,15 @@ bindings: status: implemented - contract: pagerank-kernel-v1.yaml equation: pagerank - module_path: aprender::graph::pagerank - function: pagerank + module_path: aprender::graph::centrality + function: Graph::pagerank signature: 'fn pagerank(adj: &Tensor, damping: f64, max_iter: usize, tol: f64) -> Tensor' status: implemented notes: Power iteration PageRank - contract: pagerank-kernel-v1.yaml equation: power_iteration - module_path: aprender::graph::pagerank - function: pagerank + module_path: aprender::graph::centrality + function: Graph::pagerank signature: 'fn pagerank(adj: &Tensor, damping: f64, max_iter: usize, tol: f64) -> Tensor' status: implemented notes: Iteration loop inside pagerank() @@ -186,7 +186,7 @@ bindings: notes: Weighted Gini impurity for binary split - contract: decision-tree-v1.yaml equation: mse_split - module_path: aprender::tree::helpers_part_02 + module_path: aprender::tree::helpers function: compute_mse signature: 'fn compute_mse(y_left: &[f32], y_right: &[f32]) -> f32' status: implemented @@ -235,61 +235,61 @@ bindings: notes: Max aggregation of node features per graph - contract: ../gguf-format-safety-v1.yaml equation: magic_validation - module_path: aprender::gguf::reader + module_path: aprender::bundle::format function: validate_magic status: implemented notes: Magic byte check before any allocation - contract: ../gguf-format-safety-v1.yaml equation: metadata_kv_safety - module_path: aprender::gguf::reader - function: parse_metadata + module_path: aprender::format::gguf::reader + function: read_metadata_value status: implemented notes: String length checked before alloc (CVE-2024-25664 mitigation) - contract: ../safetensors-format-safety-v1.yaml equation: header_size_validation - module_path: aprender::safetensors::reader - function: validate_header + module_path: aprender::format::core_io + function: parse_and_validate_header status: implemented notes: Header size bounded before JSON parsing - contract: model-format-conversion-v1.yaml equation: format_conversion_roundtrip - module_path: aprender::convert + module_path: apr_cli::commands::convert function: run status: implemented notes: Tensor count/names/shapes preserved across format conversion - contract: model-format-conversion-v1.yaml equation: quantization_bounds - module_path: aprender::quantize - function: quantize_tensor + module_path: aprender::format + function: quantize_data status: implemented notes: Error bounded by dtype-specific tolerance - contract: model-format-conversion-v1.yaml equation: import_integrity - module_path: aprender::import + module_path: apr_cli::commands::import function: run status: implemented notes: Content-based format detection, not extension-based - contract: model-format-conversion-v1.yaml equation: export_fidelity - module_path: aprender::export + module_path: apr_cli::commands::export function: run status: implemented notes: Atomic write via temp file + rename - contract: model-format-conversion-v1.yaml equation: apr_tokenizer_embedding - module_path: aprender::convert + module_path: aprender::format::converter function: save_model_tensors_with_gguf_config_and_tokenizer status: implemented notes: "P0 invariant \u2014 every APR file embeds tokenizer (PMAT-154 fix, Q4K passthrough/fallback paths)" - contract: apr-cli-operations-v1.yaml equation: side_effect_classification - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_core_command status: implemented notes: All 48 commands classified as ReadOnly/Mutating/LongRunning - contract: apr-cli-operations-v1.yaml equation: resource_cleanup - module_path: apr_cli::dispatch + module_path: apr_cli function: dispatch_core_command status: implemented notes: GPU/tmp/thread cleanup on all exit paths via RAII @@ -297,36 +297,36 @@ bindings: equation: inference_determinism module_path: aprender::inference function: generate - status: implemented + status: not_implemented notes: temperature=0 greedy decoding is deterministic - contract: apr-cli-operations-v1.yaml equation: tokenizer_consistency - module_path: aprender::tokenizer - function: encode + module_path: aprender::text::tokenize::bpe_impl + function: BpeTokenizer::encode status: implemented notes: BPE encode/decode roundtrip for valid UTF-8 - contract: apr-cli-operations-v1.yaml equation: concurrent_model_access - module_path: aprender::serve + module_path: apr_cli::commands::serve function: run status: implemented notes: Per-request KV cache, immutable model weights - contract: apr-cli-v1.yaml equation: contract_gate_enforcement - module_path: apr_cli::dispatch + module_path: apr_cli function: execute_command status: implemented notes: "PMAT-237 contract gate \u2014 extract_model_paths + validate_model_contract before dispatch" - contract: apr-cli-v1.yaml equation: training_plan_apply_semantics module_path: apr_cli::commands::train - function: train::run + function: run_apply status: implemented notes: Plan is pure (no GPU/filesystem), Apply writes to --output directory only - contract: apr-cli-v1.yaml equation: tokenizer_training_correctness module_path: apr_cli::commands::tokenize - function: tokenize::run + function: run_apply status: implemented notes: "BPE plan/apply \u2014 vocab.json size equals requested vocab_size" - contract: apr-cli-v1.yaml @@ -392,20 +392,20 @@ bindings: - contract: apr-data-pipeline-v1.yaml equation: data_validation module_path: apr_cli::commands::data - function: run + function: run_audit status: implemented notes: JSONL/CSV/text validation with line-level error reporting - contract: apr-data-pipeline-v1.yaml equation: data_split_determinism module_path: apr_cli::commands::data - function: run + function: run_split status: implemented notes: Seed-deterministic train/val/test split - contract: apr-data-pipeline-v1.yaml equation: streaming_data_loader module_path: aprender::data::dataloader function: DataLoader::next_batch - status: implemented + status: not_implemented notes: Epoch-seeded shuffle, no sample drop on final partial batch - contract: apr-model-qa-v1.yaml equation: model_integrity_check @@ -440,26 +440,26 @@ bindings: - contract: apr-architecture-schema-v1.yaml equation: architecture_config_invariants module_path: aprender::format::gguf::api - function: GgufModelConfig::validate + function: GgufModelConfig::warn_out_of_bounds status: implemented notes: Validates hidden_size/num_heads divisibility, GQA ratio, MoE fields - contract: apr-architecture-schema-v1.yaml equation: rope_position_encoding - module_path: aprender::nn::rope - function: RotaryPositionEmbedding::validate_config + module_path: aprender::nn::transformer::attention_helpers + function: RotaryPositionEmbedding::with_base status: implemented notes: RoPE theta, type (NORM vs NEOX), frequency vector length - contract: apr-format-safety-v1.yaml equation: magic_byte_validation module_path: aprender::gguf::reader function: detect_format - status: implemented + status: not_implemented notes: GGUF/SafeTensors/APR magic byte prefix detection, panic-free on truncated input - contract: apr-format-safety-v1.yaml equation: header_integrity module_path: aprender::gguf::reader function: validate_header - status: implemented + status: not_implemented notes: Bounded allocation, no OOM from crafted tensor_count - contract: apr-chat-session-v1.yaml equation: session_state_machine @@ -469,7 +469,7 @@ bindings: notes: Init->WaitInput->Generating->WaitInput->Exit state machine - contract: apr-chat-session-v1.yaml equation: session_persistence - module_path: apr_cli::commands::chat_session + module_path: apr_cli::commands::chat function: run status: implemented notes: JSON serialization, KV-cache not persisted (rebuilt from history) @@ -521,14 +521,14 @@ bindings: # --- apr-model-security-v1 (encrypt, decrypt, publish) --- - contract: apr-model-security-v1.yaml equation: encryption_roundtrip - module_path: apr_cli::commands::encrypt - function: run + module_path: aprender::format::encryption + function: save_encrypted status: implemented notes: AES-256-GCM with Argon2id key derivation - contract: apr-model-security-v1.yaml equation: publish_manifest_integrity module_path: apr_cli::commands::publish - function: run + function: execute status: implemented notes: SHA-256 manifest with signed model publishing # --- apr-model-diagnostics-v1 (hex, rosetta, oracle, diagnose) --- @@ -547,7 +547,7 @@ bindings: - contract: apr-model-diagnostics-v1.yaml equation: rosetta_fingerprint_determinism module_path: apr_cli::commands::rosetta - function: run + function: run_fingerprint status: implemented notes: Cross-format fingerprint comparison (GGUF/SafeTensors/APR) # ── apr-code-harness-ir (Pillar-5): the first L5 contract. Every obligation diff --git a/contracts/census.json b/contracts/census.json index f004942d87..7c02fa1515 100644 --- a/contracts/census.json +++ b/contracts/census.json @@ -1,21 +1,21 @@ { "schema": "ont.paiml.dev/census/v1alpha1", "git_sha": null, - "n_files": 1837, - "n_parsed": 1837, + "n_files": 1851, + "n_parsed": 1851, "n_parse_errors": 0, "parse_errors": [], "quarantined_n": 0, "by_kind": { "beat-benchmark": 24, "corpus-assembly": 1, - "kernel": 387, + "kernel": 388, "model-family": 28, "model-family-variant": 1, - "pattern": 95, + "pattern": 103, "pretraining-corpus": 2, "registry": 521, - "schema": 768, + "schema": 773, "tokenizer": 1, "training-loop": 8, "training-precondition-gate": 1 @@ -23,15 +23,35 @@ "by_entity_type": { "gguf": 2, "json": 3, - "pv-contract": 2, - "release-evidence": 1 + "pv-contract": 3, + "release-evidence": 1, + "repo": 1 + }, + "by_concept": { + "Code": 161, + "Contract": 1786, + "Dataset": 0, + "Doc": 0, + "Entity": 0, + "Example": 980, + "Issue": 1, + "Json": 4, + "Kernel": 837, + "Milestone": 1, + "Model": 0, + "Proof": 0, + "PullRequest": 1, + "Repo": 1, + "Statement": 415, + "Symbol": 161, + "Test": 0 }, "by_anchoring": { - "unanchored": 1829, - "class": 5, - "instance": 3 + "unanchored": 1841, + "class": 6, + "instance": 4 }, - "id_set_sha256": "2055792eb2f6a0f696509d418e57868758e669988ea6e167867b02ec9983f1d7", + "id_set_sha256": "c44190d6e7f659398f0fa8e4317bb1611145426db8952dc2168e94bc493d2147", "declared_external": [ { "name": "provable-contracts", diff --git a/contracts/ci-infra-v1.yaml b/contracts/ci-infra-v1.yaml index 4b62a2239b..902a1c5e24 100644 --- a/contracts/ci-infra-v1.yaml +++ b/contracts/ci-infra-v1.yaml @@ -231,7 +231,7 @@ falsification_tests: if_fails: "RUSTFLAGS differ between workflows — code passes one, fails another" verification_summary: - total_obligations: 6 + total_obligations: 0 proven: 0 tested: 0 status: proposed diff --git a/contracts/contracts.nt b/contracts/contracts.nt index dd58e94bcf..38bc01c7a5 100644 --- a/contracts/contracts.nt +++ b/contracts/contracts.nt @@ -339,6 +339,16 @@ "PMAT-342"^^ . "schema"^^ . "PMAT-342"^^ . + . + . + "contracts/work/PMAT-4080.yaml"^^ . + "PMAT-4080"^^ . + "pattern"^^ . + . + . + "contracts/work/PMAT-4081.yaml"^^ . + "PMAT-4081"^^ . + "pattern"^^ . . . "contracts/work/PMAT-480.yaml"^^ . @@ -1840,42 +1850,52 @@ "model-family"^^ . . . + . "contracts/absolute-position-v1.yaml"^^ . "absolute-position-v1"^^ . . . + . "contracts/accelerator-request-v1.yaml"^^ . "accelerator-request-v1"^^ . . . + . "contracts/activation-kernel-v1.yaml"^^ . "activation-kernel-v1"^^ . . . + . "contracts/active-learning-v1.yaml"^^ . "active-learning-v1"^^ . . . + . "contracts/adamw-kernel-v1.yaml"^^ . "adamw-kernel-v1"^^ . . . + . "contracts/batuta/agent-loop-v1.yaml"^^ . "agent-loop-v1"^^ . . . + . "contracts/duende/agent-orchestration-v1.yaml"^^ . "agent-orchestration-v1"^^ . . . + . "contracts/batuta/agent-ux-v1.yaml"^^ . "agent-ux-v1"^^ . . . + . "contracts/alibi-kernel-v1.yaml"^^ . "alibi-kernel-v1"^^ . . . + . "contracts/alibi-slopes-v1.yaml"^^ . "alibi-slopes-v1"^^ . "kernel"^^ . @@ -1889,6 +1909,7 @@ "1.0.0"^^ . . . + . "contracts/apr-architecture-schema-v1.yaml"^^ . "contracts/aprender/apr-architecture-schema-v1.yaml"^^ . "apr-architecture-schema-v1"^^ . @@ -1902,90 +1923,112 @@ "1.0.0"^^ . . . + . "contracts/apr-bench-receipt-v1.yaml"^^ . "apr-bench-receipt-v1"^^ . . . + . "contracts/apr-book-build-v1.yaml"^^ . "apr-book-build-v1"^^ . . . + . "contracts/apr-book-ch01-v1.yaml"^^ . "apr-book-ch01-v1"^^ . . . + . "contracts/apr-book-ch02-v1.yaml"^^ . "apr-book-ch02-v1"^^ . . . + . "contracts/apr-book-ch03-v1.yaml"^^ . "apr-book-ch03-v1"^^ . . . + . "contracts/apr-book-ch04-v1.yaml"^^ . "apr-book-ch04-v1"^^ . . . + . "contracts/apr-book-ch05-v1.yaml"^^ . "apr-book-ch05-v1"^^ . . . + . "contracts/apr-book-ch06-v1.yaml"^^ . "apr-book-ch06-v1"^^ . . . + . "contracts/apr-book-ch07-v1.yaml"^^ . "apr-book-ch07-v1"^^ . . . + . "contracts/apr-book-ch08-v1.yaml"^^ . "apr-book-ch08-v1"^^ . . . + . "contracts/apr-book-ch09-v1.yaml"^^ . "apr-book-ch09-v1"^^ . . . + . "contracts/apr-book-ch10-v1.yaml"^^ . "apr-book-ch10-v1"^^ . . . + . "contracts/apr-book-ch11-v1.yaml"^^ . "apr-book-ch11-v1"^^ . . . + . "contracts/apr-book-ch12-v1.yaml"^^ . "apr-book-ch12-v1"^^ . . . + . "contracts/apr-book-ch13-v1.yaml"^^ . "apr-book-ch13-v1"^^ . . . + . "contracts/apr-book-ch14-v1.yaml"^^ . "apr-book-ch14-v1"^^ . . . + . "contracts/apr-book-ch15-v1.yaml"^^ . "apr-book-ch15-v1"^^ . . . + . "contracts/apr-book-ch16-v1.yaml"^^ . "apr-book-ch16-v1"^^ . . . + . "contracts/apr-book-ch17-v1.yaml"^^ . "apr-book-ch17-v1"^^ . . . + . "contracts/apr-book-ch18-v1.yaml"^^ . "apr-book-ch18-v1"^^ . . . + . "contracts/apr-book-ch19-v1.yaml"^^ . "apr-book-ch19-v1"^^ . . . + . "contracts/apr-book-ch20-v1.yaml"^^ . "apr-book-ch20-v1"^^ . . @@ -2051,15 +2094,18 @@ "1"^^ . . . + . "contracts/apr-chat-session-v1.yaml"^^ . "contracts/aprender/apr-chat-session-v1.yaml"^^ . "apr-chat-session-v1"^^ . . . + . "contracts/entrenar/apr-checkpoint-v1.yaml"^^ . "apr-checkpoint-v1"^^ . . . + . "contracts/apr-chrome-trace-v1.yaml"^^ . "apr-chrome-trace-v1"^^ . . @@ -2072,20 +2118,24 @@ "1.0.0"^^ . . . + . "contracts/apr-cli-command-safety-v1.yaml"^^ . "apr-cli-command-safety-v1"^^ . . . + . "contracts/apr-cli-commands-v1.yaml"^^ . "apr-cli-commands-v1"^^ . "apr-cli-commands"^^ . "1.1.0"^^ . . . + . "contracts/apr-cli-coverage-v1.yaml"^^ . "apr-cli-coverage-v1"^^ . . . + . "contracts/apr-cli-dep-migration-v1.yaml"^^ . "apr-cli-dep-migration-v1"^^ . . @@ -2095,6 +2145,7 @@ "schema"^^ . . . + . "contracts/aprender/apr-cli-longrunning-v1.yaml"^^ . "apr-cli-longrunning-v1"^^ . . @@ -2104,19 +2155,23 @@ "schema"^^ . . . + . "contracts/aprender/apr-cli-mutating-v1.yaml"^^ . "apr-cli-mutating-v1"^^ . . . + . "contracts/apr-cli-operations-v1.yaml"^^ . "contracts/aprender/apr-cli-operations-v1.yaml"^^ . "apr-cli-operations-v1"^^ . . . + . "contracts/apr-cli-publish-extra-v1.yaml"^^ . "apr-cli-publish-extra-v1"^^ . . . + . "contracts/apr-cli-publish-v1.yaml"^^ . "apr-cli-publish-v1"^^ . . @@ -2126,18 +2181,22 @@ "schema"^^ . . . + . "contracts/apr-cli-qa-v1.yaml"^^ . "apr-cli-qa-v1"^^ . . . + . "contracts/aprender/apr-cli-readonly-v1.yaml"^^ . "apr-cli-readonly-v1"^^ . . . + . "contracts/apr-cli-safety-v1.yaml"^^ . "apr-cli-safety-v1"^^ . . . + . "contracts/aprender/apr-cli-sampling-v1.yaml"^^ . "apr-cli-sampling-v1"^^ . . @@ -2157,6 +2216,7 @@ "schema"^^ . . . + . "contracts/apr-cli-v1.yaml"^^ . "contracts/aprender/apr-cli-v1.yaml"^^ . "apr-cli-v1"^^ . @@ -2170,6 +2230,7 @@ "1.0.0"^^ . . . + . "contracts/apr-code-no-arg-startup-v1.yaml"^^ . "apr-code-no-arg-startup-v1"^^ . "kernel"^^ . @@ -2182,15 +2243,18 @@ "ACTIVE"^^ . . . + . "contracts/apr-code-toolcall-retention-v1.yaml"^^ . "apr-code-toolcall-retention-v1"^^ . "kernel"^^ . . . + . "contracts/batuta/apr-code-v1.yaml"^^ . "apr-code-v1"^^ . . . + . "contracts/apr-compare-hf-nonvacuous-v1.yaml"^^ . "apr-compare-hf-nonvacuous-v1"^^ . . @@ -2215,54 +2279,67 @@ "1.0.0"^^ . . . + . "contracts/apr-corpus-algorithm-competition-corpus-v1.yaml"^^ . "apr-corpus-algorithm-competition-corpus-v1"^^ . . . + . "contracts/apr-corpus-databricks-ground-truth-corpus-v1.yaml"^^ . "apr-corpus-databricks-ground-truth-corpus-v1"^^ . . . + . "contracts/apr-corpus-databricks-scala-ground-truth-corpus-v1.yaml"^^ . "apr-corpus-databricks-scala-ground-truth-corpus-v1"^^ . . . + . "contracts/apr-corpus-hugging-face-ground-truth-corpus-v1.yaml"^^ . "apr-corpus-hugging-face-ground-truth-corpus-v1"^^ . . . + . "contracts/apr-corpus-jax-ground-truth-corpus-v1.yaml"^^ . "apr-corpus-jax-ground-truth-corpus-v1"^^ . . . + . "contracts/apr-corpus-lean-ground-truth-v1.yaml"^^ . "apr-corpus-lean-ground-truth-v1"^^ . . . + . "contracts/apr-corpus-ludwig-ground-truth-corpus-v1.yaml"^^ . "apr-corpus-ludwig-ground-truth-corpus-v1"^^ . . . + . "contracts/apr-corpus-mixed-python-rust-ground-truth-v1.yaml"^^ . "apr-corpus-mixed-python-rust-ground-truth-v1"^^ . . . + . "contracts/apr-corpus-mixed-rust-lean-ground-truth-v1.yaml"^^ . "apr-corpus-mixed-rust-lean-ground-truth-v1"^^ . . . + . "contracts/apr-corpus-safe-lua-groundtruth-v1.yaml"^^ . "apr-corpus-safe-lua-groundtruth-v1"^^ . . . + . "contracts/apr-corpus-tgi-ground-truth-corpus-v1.yaml"^^ . "apr-corpus-tgi-ground-truth-corpus-v1"^^ . . . + . "contracts/apr-corpus-tiny-model-ground-truth-v1.yaml"^^ . "apr-corpus-tiny-model-ground-truth-v1"^^ . . . + . "contracts/apr-corpus-vllm-ground-truth-corpus-v1.yaml"^^ . "apr-corpus-vllm-ground-truth-corpus-v1"^^ . . @@ -2272,6 +2349,7 @@ "schema"^^ . . . + . "contracts/apr-data-pipeline-v1.yaml"^^ . "contracts/aprender/apr-data-pipeline-v1.yaml"^^ . "apr-data-pipeline-v1"^^ . @@ -2285,38 +2363,45 @@ "1.0.0"^^ . . . + . "contracts/apr-distill-smoke-validation-v1.yaml"^^ . "apr-distill-smoke-validation-v1"^^ . "apr-distill-smoke-validation"^^ . "1.0.0"^^ . . . + . "contracts/apr-distill-teacher-backend-selection-v1.yaml"^^ . "apr-distill-teacher-backend-selection-v1"^^ . "apr-distill-teacher-backend-selection"^^ . "1.0.0"^^ . . . + . "contracts/apr-distill-teacher-vocab-alignment-v1.yaml"^^ . "apr-distill-teacher-vocab-alignment-v1"^^ . "apr-distill-teacher-vocab-alignment"^^ . "1.0.0"^^ . . . + . "contracts/apr-docs-v1.yaml"^^ . "apr-docs-v1"^^ . . . + . "contracts/apr-dogfood-coverage-v1.yaml"^^ . "apr-dogfood-coverage-v1"^^ . . . + . "contracts/apr-eval-humaneval-harness-invariant-v1.yaml"^^ . "apr-eval-humaneval-harness-invariant-v1"^^ . "kernel"^^ . "ACTIVE"^^ . . . + . "contracts/apr-eval-humaneval-inference-failure-handling-v1.yaml"^^ . "apr-eval-humaneval-inference-failure-handling-v1"^^ . "apr-eval-humaneval-inference-failure-handling"^^ . @@ -2342,28 +2427,34 @@ "1"^^ . . . + . "contracts/apr-finetune-metrics-v1.yaml"^^ . "apr-finetune-metrics-v1"^^ . . . + . "contracts/aprender/apr-finetune-v1.yaml"^^ . "apr-finetune-v1"^^ . . . + . "contracts/apr-format-extraction-v1.yaml"^^ . "apr-format-extraction-v1"^^ . "kernel"^^ . . . + . "contracts/apr-format-invariants-v1.yaml"^^ . "apr-format-invariants-v1"^^ . . . + . "contracts/apr-format-leaf-sovereignty-v1.yaml"^^ . "apr-format-leaf-sovereignty-v1"^^ . "kernel"^^ . . . + . "contracts/apr-format-safety-v1.yaml"^^ . "contracts/aprender/apr-format-safety-v1.yaml"^^ . "apr-format-safety-v1"^^ . @@ -2377,29 +2468,35 @@ "1.0.0"^^ . . . + . "contracts/apr-gguf-export-symmetry-v1.yaml"^^ . "apr-gguf-export-symmetry-v1"^^ . "apr-gguf-export-symmetry"^^ . "1.0.0"^^ . . . + . "contracts/apr-global-verbosity-wiring-v1.yaml"^^ . "apr-global-verbosity-wiring-v1"^^ . "kernel"^^ . . . + . "contracts/aprender/apr-gpu-backend-v1.yaml"^^ . "apr-gpu-backend-v1"^^ . . . + . "contracts/apr-gpu-diagnostics-v1.yaml"^^ . "apr-gpu-diagnostics-v1"^^ . . . + . "contracts/apr-gpu-parity-consistency-v1.yaml"^^ . "apr-gpu-parity-consistency-v1"^^ . . . + . "contracts/apr-gpu-presence-v1.yaml"^^ . "apr-gpu-presence-v1"^^ . . @@ -2440,18 +2537,22 @@ "schema"^^ . . . + . "contracts/apr-inspect-dtype-naming-v1.yaml"^^ . "apr-inspect-dtype-naming-v1"^^ . . . + . "contracts/apr-inspect-flags-v1.yaml"^^ . "apr-inspect-flags-v1"^^ . . . + . "contracts/apr-inspect-metadata-propagation-v1.yaml"^^ . "apr-inspect-metadata-propagation-v1"^^ . . . + . "contracts/apr-inspect-quantization-v1.yaml"^^ . "apr-inspect-quantization-v1"^^ . . @@ -2461,15 +2562,18 @@ "pattern"^^ . . . + . "contracts/apr-lint-producers-v1.yaml"^^ . "apr-lint-producers-v1"^^ . "kernel"^^ . . . + . "contracts/apr-list-disk-reconciliation-v1.yaml"^^ . "apr-list-disk-reconciliation-v1"^^ . . . + . "contracts/apr-list-quiet-wiring-v1.yaml"^^ . "apr-list-quiet-wiring-v1"^^ . . @@ -2502,6 +2606,7 @@ "1"^^ . . . + . "contracts/apr-mcp-serve-liveness-v1.yaml"^^ . "apr-mcp-serve-liveness-v1"^^ . . @@ -2514,6 +2619,7 @@ "1.0.0"^^ . . . + . "contracts/apr-mcp-stdio-drain-v1.yaml"^^ . "apr-mcp-stdio-drain-v1"^^ . "kernel"^^ . @@ -2530,6 +2636,7 @@ "1.0.0"^^ . . . + . "contracts/apr-mcp-tool-schemas-v1.yaml"^^ . "apr-mcp-tool-schemas-v1"^^ . "apr-mcp-tool-schemas"^^ . @@ -2553,36 +2660,44 @@ "1.0.0"^^ . . . + . "contracts/apr-model-diagnostics-v1.yaml"^^ . "apr-model-diagnostics-v1"^^ . . . + . "contracts/batuta/apr-model-discovery-v1.yaml"^^ . "apr-model-discovery-v1"^^ . . . + . "contracts/apr-model-graph-v1.yaml"^^ . "apr-model-graph-v1"^^ . . . + . "contracts/apr-model-lifecycle-v1.yaml"^^ . "contracts/aprender/apr-model-lifecycle-v1.yaml"^^ . "apr-model-lifecycle-v1"^^ . . . + . "contracts/apr-model-optimization-v1.yaml"^^ . "apr-model-optimization-v1"^^ . . . + . "contracts/apr-model-qa-v1.yaml"^^ . "contracts/aprender/apr-model-qa-v1.yaml"^^ . "apr-model-qa-v1"^^ . . . + . "contracts/apr-model-security-v1.yaml"^^ . "apr-model-security-v1"^^ . . . + . "contracts/apr-mono-binary-rule-v1.yaml"^^ . "apr-mono-binary-rule-v1"^^ . . @@ -2602,6 +2717,7 @@ "1.0.0"^^ . . . + . "contracts/apr-org-taxonomy-v1.yaml"^^ . "apr-org-taxonomy-v1"^^ . . @@ -5378,6 +5494,7 @@ "1.2.0"^^ . . . + . "contracts/apr-publish-hf-large-file-v1.yaml"^^ . "apr-publish-hf-large-file-v1"^^ . . @@ -5389,22 +5506,27 @@ "1"^^ . . . + . "contracts/apr-qa-chaos-v1.yaml"^^ . "apr-qa-chaos-v1"^^ . . . + . "contracts/apr-qa-coverage-v1.yaml"^^ . "apr-qa-coverage-v1"^^ . . . + . "contracts/apr-qa-differential-v1.yaml"^^ . "apr-qa-differential-v1"^^ . . . + . "contracts/apr-qa-metamorphic-v1.yaml"^^ . "apr-qa-metamorphic-v1"^^ . . . + . "contracts/apr-qa-silent-fallback-v1.yaml"^^ . "apr-qa-silent-fallback-v1"^^ . . @@ -5487,6 +5609,7 @@ "1.0.0"^^ . . . + . "contracts/apr-serve-cancellation-v1.yaml"^^ . "apr-serve-cancellation-v1"^^ . . @@ -5505,11 +5628,13 @@ "1"^^ . . . + . "contracts/apr-serve-v1.yaml"^^ . "contracts/aprender/apr-serve-v1.yaml"^^ . "apr-serve-v1"^^ . . . + . "contracts/apr-ship-007-gpu-stage-bisection-v1.yaml"^^ . "apr-ship-007-gpu-stage-bisection-v1"^^ . "kernel"^^ . @@ -5544,6 +5669,7 @@ "1"^^ . . . + . "contracts/apr-stochastic-lr-v1.yaml"^^ . "apr-stochastic-lr-v1"^^ . . @@ -5558,94 +5684,117 @@ "pattern"^^ . . . + . "contracts/apr-tool-bashrs-v1.yaml"^^ . "apr-tool-bashrs-v1"^^ . . . + . "contracts/apr-tool-ccpo-v1.yaml"^^ . "apr-tool-ccpo-v1"^^ . . . + . "contracts/apr-tool-cohete-v1.yaml"^^ . "apr-tool-cohete-v1"^^ . . . + . "contracts/apr-tool-copia-v1.yaml"^^ . "apr-tool-copia-v1"^^ . . . + . "contracts/apr-tool-decy-v1.yaml"^^ . "apr-tool-decy-v1"^^ . . . + . "contracts/apr-tool-depyler-v1.yaml"^^ . "apr-tool-depyler-v1"^^ . . . + . "contracts/apr-tool-duende-v1.yaml"^^ . "apr-tool-duende-v1"^^ . . . + . "contracts/apr-tool-forjar-v1.yaml"^^ . "apr-tool-forjar-v1"^^ . . . + . "contracts/apr-tool-manzana-v1.yaml"^^ . "apr-tool-manzana-v1"^^ . . . + . "contracts/apr-tool-microgpt-v1.yaml"^^ . "apr-tool-microgpt-v1"^^ . . . + . "contracts/apr-tool-organizational-intelligence-plugin-v1.yaml"^^ . "apr-tool-organizational-intelligence-plugin-v1"^^ . . . + . "contracts/apr-tool-paiml-mcp-agent-toolkit-v1.yaml"^^ . "apr-tool-paiml-mcp-agent-toolkit-v1"^^ . . . + . "contracts/apr-tool-pcode-v1.yaml"^^ . "apr-tool-pcode-v1"^^ . . . + . "contracts/apr-tool-pdmt-v1.yaml"^^ . "apr-tool-pdmt-v1"^^ . . . + . "contracts/apr-tool-pepita-v1.yaml"^^ . "apr-tool-pepita-v1"^^ . . . + . "contracts/apr-tool-pforge-v1.yaml"^^ . "apr-tool-pforge-v1"^^ . . . + . "contracts/apr-tool-rascal-v1.yaml"^^ . "apr-tool-rascal-v1"^^ . . . + . "contracts/apr-tool-rmedia-v1.yaml"^^ . "apr-tool-rmedia-v1"^^ . . . + . "contracts/apr-tool-rust-mcp-sdk-v1.yaml"^^ . "apr-tool-rust-mcp-sdk-v1"^^ . . . + . "contracts/apr-tool-rust-mdipierro-nlib-v1.yaml"^^ . "apr-tool-rust-mdipierro-nlib-v1"^^ . . . + . "contracts/apr-tool-spydecy-v1.yaml"^^ . "apr-tool-spydecy-v1"^^ . . . + . "contracts/apr-train-shell-model-provenance-v1.yaml"^^ . "apr-train-shell-model-provenance-v1"^^ . . . + . "contracts/entrenar/apr-training-parity-v1.yaml"^^ . "apr-training-parity-v1"^^ . . @@ -5667,6 +5816,7 @@ "pattern"^^ . . . + . "contracts/apr-version-traceability-v1.yaml"^^ . "apr-version-traceability-v1"^^ . . @@ -5676,6 +5826,7 @@ "schema"^^ . . . + . "contracts/apr-wgpu-adapter-enumeration-excludes-gles-v1.yaml"^^ . "apr-wgpu-adapter-enumeration-excludes-gles-v1"^^ . "kernel"^^ . @@ -5689,30 +5840,36 @@ "1.0.0"^^ . . . + . "contracts/apr-zero-feature-gate-v1.yaml"^^ . "apr-zero-feature-gate-v1"^^ . . . + . "contracts/arch-constraints-v1.yaml"^^ . "arch-constraints-v1"^^ . . . + . "contracts/architecture-requirements-v1.yaml"^^ . "architecture-requirements-v1"^^ . . . + . "contracts/archive-repos-v1.yaml"^^ . "archive-repos-v1"^^ . "archive-repos"^^ . "1.0.0"^^ . . . + . "contracts/arima-ar-centering-v1.yaml"^^ . "arima-ar-centering-v1"^^ . "arima-ar-centering"^^ . "1.0.0"^^ . . . + . "contracts/arima-v1.yaml"^^ . "arima-v1"^^ . . @@ -5729,6 +5886,7 @@ "1"^^ . . . + . "contracts/attention-backward-v1.yaml"^^ . "contracts/entrenar/attention-backward-v1.yaml"^^ . "attention-backward-v1"^^ . @@ -5736,23 +5894,28 @@ "1.0.0"^^ . . . + . "contracts/entrenar/attention-head-extraction-v1.yaml"^^ . "attention-head-extraction-v1"^^ . . . + . "contracts/attention-kernel-v1.yaml"^^ . "contracts/realizar/attention-kernel-v1.yaml"^^ . "attention-kernel-v1"^^ . . . + . "contracts/attention-scaling-v1.yaml"^^ . "attention-scaling-v1"^^ . . . + . "contracts/avx2-fma-dot-v1.yaml"^^ . "avx2-fma-dot-v1"^^ . . . + . "contracts/avx512-blis-v1.yaml"^^ . "contracts/trueno/avx512-blis-v1.yaml"^^ . "avx512-blis-v1"^^ . @@ -5760,6 +5923,7 @@ "1.0.0"^^ . . . + . "contracts/avx512-q4k-v1.yaml"^^ . "contracts/trueno/avx512-q4k-v1.yaml"^^ . "avx512-q4k-v1"^^ . @@ -5767,6 +5931,7 @@ "1.1.0"^^ . . . + . "contracts/backend-dispatch-v1.yaml"^^ . "backend-dispatch-v1"^^ . . @@ -5777,39 +5942,47 @@ "baseline-v1"^^ . . . + . "contracts/batch-admission-v1.yaml"^^ . "batch-admission-v1"^^ . . . + . "contracts/aprender/batch-training-v1.yaml"^^ . "contracts/batch-training-v1.yaml"^^ . "batch-training-v1"^^ . . . + . "contracts/batched-beam-search-v1.yaml"^^ . "batched-beam-search-v1"^^ . . . + . "contracts/batchnorm-kernel-v1.yaml"^^ . "batchnorm-kernel-v1"^^ . . . + . "contracts/batchnorm-running-stats-v1.yaml"^^ . "batchnorm-running-stats-v1"^^ . "batchnorm-running-stats"^^ . "1.0.0"^^ . . . + . "contracts/bayesian-logistic-map-v1.yaml"^^ . "bayesian-logistic-map-v1"^^ . "bayesian-logistic-map"^^ . "1.0.0"^^ . . . + . "contracts/bayesian-v1.yaml"^^ . "bayesian-v1"^^ . . . + . "contracts/faro/beacon-dispatch-v1.yaml"^^ . "beacon-dispatch-v1"^^ . . @@ -5913,6 +6086,7 @@ "1"^^ . . . + . "contracts/beat-sklearn-nmi-v1.yaml"^^ . "beat-sklearn-nmi-v1"^^ . . @@ -5929,28 +6103,34 @@ "model-family"^^ . . . + . "contracts/bf16-dequant-v1.yaml"^^ . "bf16-dequant-v1"^^ . "bf16-dequant"^^ . "1.0.0"^^ . . . + . "contracts/bias-add-v1.yaml"^^ . "bias-add-v1"^^ . . . + . "contracts/bidirectional-attention-v1.yaml"^^ . "bidirectional-attention-v1"^^ . . . + . "contracts/forjar/blake3-state-v1.yaml"^^ . "blake3-state-v1"^^ . . . + . "contracts/trueno/blis-gemm-v1.yaml"^^ . "blis-gemm-v1"^^ . . . + . "contracts/trueno/blis-thread-cap-v1.yaml"^^ . "blis-thread-cap-v1"^^ . . @@ -5960,16 +6140,19 @@ "model-family"^^ . . . + . "contracts/bpe-encode-bytes-to-unicode-v1.yaml"^^ . "bpe-encode-bytes-to-unicode-v1"^^ . "bpe-encode-bytes-to-unicode"^^ . "1.0.0"^^ . . . + . "contracts/bpe-tokenization-v1.yaml"^^ . "bpe-tokenization-v1"^^ . . . + . "contracts/bpe-training-perf-v1.yaml"^^ . "bpe-training-perf-v1"^^ . "kernel"^^ . @@ -5977,35 +6160,42 @@ "1.2.0"^^ . . . + . "contracts/builder-pattern-v1.yaml"^^ . "contracts/repartir/builder-pattern-v1.yaml"^^ . "builder-pattern-v1"^^ . . . + . "contracts/calibration-v1.yaml"^^ . "calibration-v1"^^ . . . + . "contracts/entrenar/canary-metrics-schema-v1.yaml"^^ . "canary-metrics-schema-v1"^^ . . . + . "contracts/entrenar/canary-score-gate-v1.yaml"^^ . "canary-score-gate-v1"^^ . . . + . "contracts/cgp-monorepo-build-v1.yaml"^^ . "cgp-monorepo-build-v1"^^ . "cgp-monorepo-build"^^ . "1.0.0"^^ . . . + . "contracts/cgp-monorepo-consolidation-v1.yaml"^^ . "cgp-monorepo-consolidation-v1"^^ . "cgp-monorepo-consolidation"^^ . "1.2.0"^^ . . . + . "contracts/chat-template-v1.yaml"^^ . "contracts/realizar/chat-template-v1.yaml"^^ . "chat-template-v1"^^ . @@ -6025,14 +6215,17 @@ "pattern"^^ . . . + . "contracts/ci-infra-v1.yaml"^^ . "ci-infra-v1"^^ . . . + . "contracts/classification-finetune-v1.yaml"^^ . "classification-finetune-v1"^^ . . . + . "contracts/classifier-pipeline-v1.yaml"^^ . "classifier-pipeline-v1"^^ . . @@ -6045,51 +6238,62 @@ "1.32.0"^^ . . . + . "contracts/clean-chat-output-v1.yaml"^^ . "clean-chat-output-v1"^^ . "clean-chat-output"^^ . "1.0.0"^^ . . . + . "contracts/rclean/cleanup-safety-v1.yaml"^^ . "cleanup-safety-v1"^^ . . . + . "contracts/aprender/cli-dispatch-v1.yaml"^^ . "contracts/cli-dispatch-v1.yaml"^^ . "cli-dispatch-v1"^^ . . . + . "contracts/pmat/cli-interface-v1.yaml"^^ . "cli-interface-v1"^^ . . . + . "contracts/bashrs/cli-lint-v1.yaml"^^ . "cli-lint-v1"^^ . . . + . "contracts/batuta/cli-oracle-v1.yaml"^^ . "cli-oracle-v1"^^ . . . + . "contracts/depyler/cli-transpile-v1.yaml"^^ . "cli-transpile-v1"^^ . . . + . "contracts/clustering-metrics-relabel-invariant-v1.yaml"^^ . "clustering-metrics-relabel-invariant-v1"^^ . "clustering-metrics-relabel-invariant"^^ . "1.0.0"^^ . . . + . "contracts/cma-es-kernel-v1.yaml"^^ . "cma-es-kernel-v1"^^ . . . + . "contracts/codebert-tokenizer-validation-v1.yaml"^^ . "codebert-tokenizer-validation-v1"^^ . . . + . "contracts/codegen-dispatch-v1.yaml"^^ . "contracts/forjar/codegen-dispatch-v1.yaml"^^ . "codegen-dispatch-v1"^^ . @@ -6097,10 +6301,12 @@ "1.0.0"^^ . . . + . "contracts/trueno-db/columnar-storage-v1.yaml"^^ . "columnar-storage-v1"^^ . . . + . "contracts/comply-check-v1.yaml"^^ . "contracts/pmat/comply-check-v1.yaml"^^ . "comply-check-v1"^^ . @@ -6114,10 +6320,12 @@ "PROPOSED"^^ . . . + . "contracts/trueno-zram/compression-codec-v1.yaml"^^ . "compression-codec-v1"^^ . . . + . "contracts/compression-roundtrip-v1.yaml"^^ . "contracts/pmat/compression-roundtrip-v1.yaml"^^ . "contracts/trueno-zram/compression-roundtrip-v1.yaml"^^ . @@ -6131,14 +6339,17 @@ "pattern"^^ . . . + . "contracts/pmat/concurrency-safety-v1.yaml"^^ . "concurrency-safety-v1"^^ . . . + . "contracts/pmat/configuration-schema-v1.yaml"^^ . "configuration-schema-v1"^^ . . . + . "contracts/alimentar/configuration-v1.yaml"^^ . "contracts/certeza/configuration-v1.yaml"^^ . "contracts/configuration-v1.yaml"^^ . @@ -6149,6 +6360,7 @@ "configuration-v1"^^ . . . + . "contracts/context-generation-v1.yaml"^^ . "contracts/pmat/context-generation-v1.yaml"^^ . "context-generation-v1"^^ . @@ -6156,32 +6368,39 @@ "1.0.0"^^ . . . + . "contracts/continuous-batching-v1.yaml"^^ . "continuous-batching-v1"^^ . . . + . "contracts/contrastive-pair-protocol-v1.yaml"^^ . "contrastive-pair-protocol-v1"^^ . . . + . "contracts/conv1d-kernel-v1.yaml"^^ . "conv1d-kernel-v1"^^ . . . + . "contracts/conversation-generation-v1.yaml"^^ . "conversation-generation-v1"^^ . . . + . "contracts/converter-moe-headdim-import-v1.yaml"^^ . "converter-moe-headdim-import-v1"^^ . "converter-moe-headdim-import"^^ . "1.0.0"^^ . . . + . "contracts/cooperative-matrix-gemm-v1.yaml"^^ . "cooperative-matrix-gemm-v1"^^ . . . + . "contracts/forjar/copia-delta-v1.yaml"^^ . "copia-delta-v1"^^ . . @@ -6193,6 +6412,7 @@ "1.0.0"^^ . . . + . "contracts/cpp-type-preservation-v1.yaml"^^ . "cpp-type-preservation-v1"^^ . . @@ -6202,1316 +6422,1638 @@ "pattern"^^ . . . + . "contracts/cpu-q4k-activation-quant-v1.yaml"^^ . "cpu-q4k-activation-quant-v1"^^ . . . + . "contracts/cpu-work-stealing-v1.yaml"^^ . "cpu-work-stealing-v1"^^ . . . + . "contracts/crate-hygiene-v1.yaml"^^ . "crate-hygiene-v1"^^ . . . + . "contracts/crate-readme-v1.yaml"^^ . "crate-readme-v1"^^ . . . + . "contracts/cross-entropy-kernel-v1.yaml"^^ . "cross-entropy-kernel-v1"^^ . . . + . "contracts/crux-A-01-v1.yaml"^^ . "crux-A-01-v1"^^ . . . + . "contracts/crux-A-02-v1.yaml"^^ . "crux-A-02-v1"^^ . . . + . "contracts/crux-A-03-v1.yaml"^^ . "crux-A-03-v1"^^ . . . + . "contracts/crux-A-04-v1.yaml"^^ . "crux-A-04-v1"^^ . . . + . "contracts/crux-A-05-v1.yaml"^^ . "crux-A-05-v1"^^ . . . + . "contracts/crux-A-06-v1.yaml"^^ . "crux-A-06-v1"^^ . . . + . "contracts/crux-A-07-v1.yaml"^^ . "crux-A-07-v1"^^ . . . + . "contracts/crux-A-08-v1.yaml"^^ . "crux-A-08-v1"^^ . . . + . "contracts/crux-A-09-v1.yaml"^^ . "crux-A-09-v1"^^ . . . + . "contracts/crux-A-10-v1.yaml"^^ . "crux-A-10-v1"^^ . . . + . "contracts/crux-A-11-v1.yaml"^^ . "crux-A-11-v1"^^ . . . + . "contracts/crux-A-12-v1.yaml"^^ . "crux-A-12-v1"^^ . . . + . "contracts/crux-A-13-v1.yaml"^^ . "crux-A-13-v1"^^ . . . + . "contracts/crux-A-14-v1.yaml"^^ . "crux-A-14-v1"^^ . . . + . "contracts/crux-A-15-v1.yaml"^^ . "crux-A-15-v1"^^ . . . + . "contracts/crux-A-16-v1.yaml"^^ . "crux-A-16-v1"^^ . . . + . "contracts/crux-A-17-v1.yaml"^^ . "crux-A-17-v1"^^ . . . + . "contracts/crux-A-18-v1.yaml"^^ . "crux-A-18-v1"^^ . . . + . "contracts/crux-A-19-v1.yaml"^^ . "crux-A-19-v1"^^ . . . + . "contracts/crux-A-20-v1.yaml"^^ . "crux-A-20-v1"^^ . . . + . "contracts/crux-A-21-v1.yaml"^^ . "crux-A-21-v1"^^ . . . + . "contracts/crux-A-22-v1.yaml"^^ . "crux-A-22-v1"^^ . . . + . "contracts/crux-A-23-v1.yaml"^^ . "crux-A-23-v1"^^ . . . + . "contracts/crux-A-24-v1.yaml"^^ . "crux-A-24-v1"^^ . . . + . "contracts/crux-A-25-v1.yaml"^^ . "crux-A-25-v1"^^ . . . + . "contracts/crux-B-01-v1.yaml"^^ . "crux-B-01-v1"^^ . . . + . "contracts/crux-B-02-v1.yaml"^^ . "crux-B-02-v1"^^ . . . + . "contracts/crux-B-03-v1.yaml"^^ . "crux-B-03-v1"^^ . . . + . "contracts/crux-B-04-v1.yaml"^^ . "crux-B-04-v1"^^ . . . + . "contracts/crux-B-05-v1.yaml"^^ . "crux-B-05-v1"^^ . "kernel"^^ . . . + . "contracts/crux-B-06-v1.yaml"^^ . "crux-B-06-v1"^^ . . . + . "contracts/crux-B-07-v1.yaml"^^ . "crux-B-07-v1"^^ . . . + . "contracts/crux-B-08-v1.yaml"^^ . "crux-B-08-v1"^^ . . . + . "contracts/crux-B-09-v1.yaml"^^ . "crux-B-09-v1"^^ . . . + . "contracts/crux-B-10-v1.yaml"^^ . "crux-B-10-v1"^^ . . . + . "contracts/crux-B-11-v1.yaml"^^ . "crux-B-11-v1"^^ . . . + . "contracts/crux-B-12-v1.yaml"^^ . "crux-B-12-v1"^^ . . . + . "contracts/crux-B-13-v1.yaml"^^ . "crux-B-13-v1"^^ . . . + . "contracts/crux-B-14-v1.yaml"^^ . "crux-B-14-v1"^^ . . . + . "contracts/crux-B-15-v1.yaml"^^ . "crux-B-15-v1"^^ . . . + . "contracts/crux-B-16-v1.yaml"^^ . "crux-B-16-v1"^^ . . . + . "contracts/crux-B-17-v1.yaml"^^ . "crux-B-17-v1"^^ . . . + . "contracts/crux-B-18-v1.yaml"^^ . "crux-B-18-v1"^^ . . . + . "contracts/crux-B-19-v1.yaml"^^ . "crux-B-19-v1"^^ . "kernel"^^ . . . + . "contracts/crux-B-20-v1.yaml"^^ . "crux-B-20-v1"^^ . "kernel"^^ . . . + . "contracts/crux-C-01-v1.yaml"^^ . "crux-C-01-v1"^^ . . . + . "contracts/crux-C-02-v1.yaml"^^ . "crux-C-02-v1"^^ . . . + . "contracts/crux-C-03-v1.yaml"^^ . "crux-C-03-v1"^^ . . . + . "contracts/crux-C-04-v1.yaml"^^ . "crux-C-04-v1"^^ . "kernel"^^ . . . + . "contracts/crux-C-05-v1.yaml"^^ . "crux-C-05-v1"^^ . . . + . "contracts/crux-C-06-v1.yaml"^^ . "crux-C-06-v1"^^ . . . + . "contracts/crux-C-07-v1.yaml"^^ . "crux-C-07-v1"^^ . . . + . "contracts/crux-C-08-v1.yaml"^^ . "crux-C-08-v1"^^ . . . + . "contracts/crux-C-09-v1.yaml"^^ . "crux-C-09-v1"^^ . . . + . "contracts/crux-C-10-v1.yaml"^^ . "crux-C-10-v1"^^ . . . + . "contracts/crux-C-11-v1.yaml"^^ . "crux-C-11-v1"^^ . . . + . "contracts/crux-C-12-v1.yaml"^^ . "crux-C-12-v1"^^ . . . + . "contracts/crux-C-13-v1.yaml"^^ . "crux-C-13-v1"^^ . . . + . "contracts/crux-C-15-v1.yaml"^^ . "crux-C-15-v1"^^ . . . + . "contracts/crux-C-16-v1.yaml"^^ . "crux-C-16-v1"^^ . . . + . "contracts/crux-C-17-v1.yaml"^^ . "crux-C-17-v1"^^ . . . + . "contracts/crux-C-18-v1.yaml"^^ . "crux-C-18-v1"^^ . . . + . "contracts/crux-C-19-v1.yaml"^^ . "crux-C-19-v1"^^ . . . + . "contracts/crux-C-20-v1.yaml"^^ . "crux-C-20-v1"^^ . . . + . "contracts/crux-C-21-v1.yaml"^^ . "crux-C-21-v1"^^ . . . + . "contracts/crux-C-22-v1.yaml"^^ . "crux-C-22-v1"^^ . "kernel"^^ . . . + . "contracts/crux-C-23-v1.yaml"^^ . "crux-C-23-v1"^^ . "kernel"^^ . . . + . "contracts/crux-C-24-v1.yaml"^^ . "crux-C-24-v1"^^ . . . + . "contracts/crux-C-25-v1.yaml"^^ . "crux-C-25-v1"^^ . . . + . "contracts/crux-C-26-v1.yaml"^^ . "crux-C-26-v1"^^ . . . + . "contracts/crux-C-27-v1.yaml"^^ . "crux-C-27-v1"^^ . . . + . "contracts/crux-C-28-v1.yaml"^^ . "crux-C-28-v1"^^ . . . + . "contracts/crux-C-29-v1.yaml"^^ . "crux-C-29-v1"^^ . . . + . "contracts/crux-C-30-v1.yaml"^^ . "crux-C-30-v1"^^ . . . + . "contracts/crux-C-31-v1.yaml"^^ . "crux-C-31-v1"^^ . . . + . "contracts/crux-C-32-v1.yaml"^^ . "crux-C-32-v1"^^ . . . + . "contracts/crux-C-33-v1.yaml"^^ . "crux-C-33-v1"^^ . . . + . "contracts/crux-C-34-v1.yaml"^^ . "crux-C-34-v1"^^ . . . + . "contracts/crux-C-35-v1.yaml"^^ . "crux-C-35-v1"^^ . . . + . "contracts/crux-C-36-v1.yaml"^^ . "crux-C-36-v1"^^ . . . + . "contracts/crux-C-37-v1.yaml"^^ . "crux-C-37-v1"^^ . . . + . "contracts/crux-D-01-v1.yaml"^^ . "crux-D-01-v1"^^ . . . + . "contracts/crux-D-02-v1.yaml"^^ . "crux-D-02-v1"^^ . . . + . "contracts/crux-D-03-v1.yaml"^^ . "crux-D-03-v1"^^ . . . + . "contracts/crux-D-04-v1.yaml"^^ . "crux-D-04-v1"^^ . . . + . "contracts/crux-D-05-v1.yaml"^^ . "crux-D-05-v1"^^ . . . + . "contracts/crux-D-06-v1.yaml"^^ . "crux-D-06-v1"^^ . . . + . "contracts/crux-D-07-v1.yaml"^^ . "crux-D-07-v1"^^ . . . + . "contracts/crux-D-08-v1.yaml"^^ . "crux-D-08-v1"^^ . . . + . "contracts/crux-D-09-v1.yaml"^^ . "crux-D-09-v1"^^ . . . + . "contracts/crux-D-10-v1.yaml"^^ . "crux-D-10-v1"^^ . . . + . "contracts/crux-D-11-v1.yaml"^^ . "crux-D-11-v1"^^ . "kernel"^^ . . . + . "contracts/crux-D-12-v1.yaml"^^ . "crux-D-12-v1"^^ . . . + . "contracts/crux-D-13-v1.yaml"^^ . "crux-D-13-v1"^^ . . . + . "contracts/crux-D-14-v1.yaml"^^ . "crux-D-14-v1"^^ . . . + . "contracts/crux-D-15-v1.yaml"^^ . "crux-D-15-v1"^^ . . . + . "contracts/crux-D-16-v1.yaml"^^ . "crux-D-16-v1"^^ . . . + . "contracts/crux-D-17-v1.yaml"^^ . "crux-D-17-v1"^^ . . . + . "contracts/crux-D-18-v1.yaml"^^ . "crux-D-18-v1"^^ . . . + . "contracts/crux-D-19-v1.yaml"^^ . "crux-D-19-v1"^^ . . . + . "contracts/crux-D-20-v1.yaml"^^ . "crux-D-20-v1"^^ . . . + . "contracts/crux-D-21-v1.yaml"^^ . "crux-D-21-v1"^^ . . . + . "contracts/crux-D-22-v1.yaml"^^ . "crux-D-22-v1"^^ . . . + . "contracts/crux-D-23-v1.yaml"^^ . "crux-D-23-v1"^^ . . . + . "contracts/crux-D-24-v1.yaml"^^ . "crux-D-24-v1"^^ . . . + . "contracts/crux-D-25-v1.yaml"^^ . "crux-D-25-v1"^^ . . . + . "contracts/crux-D-26-v1.yaml"^^ . "crux-D-26-v1"^^ . . . + . "contracts/crux-D-27-v1.yaml"^^ . "crux-D-27-v1"^^ . . . + . "contracts/crux-D-28-v1.yaml"^^ . "crux-D-28-v1"^^ . . . + . "contracts/crux-D-29-v1.yaml"^^ . "crux-D-29-v1"^^ . . . + . "contracts/crux-D-30-v1.yaml"^^ . "crux-D-30-v1"^^ . . . + . "contracts/crux-D-31-v1.yaml"^^ . "crux-D-31-v1"^^ . . . + . "contracts/crux-D-32-v1.yaml"^^ . "crux-D-32-v1"^^ . . . + . "contracts/crux-D-33-v1.yaml"^^ . "crux-D-33-v1"^^ . . . + . "contracts/crux-D-34-v1.yaml"^^ . "crux-D-34-v1"^^ . . . + . "contracts/crux-D-35-v1.yaml"^^ . "crux-D-35-v1"^^ . . . + . "contracts/crux-E-01-v1.yaml"^^ . "crux-E-01-v1"^^ . . . + . "contracts/crux-E-02-v1.yaml"^^ . "crux-E-02-v1"^^ . "kernel"^^ . . . + . "contracts/crux-E-03-v1.yaml"^^ . "crux-E-03-v1"^^ . . . + . "contracts/crux-E-04-v1.yaml"^^ . "crux-E-04-v1"^^ . . . + . "contracts/crux-E-05-v1.yaml"^^ . "crux-E-05-v1"^^ . . . + . "contracts/crux-E-06-v1.yaml"^^ . "crux-E-06-v1"^^ . . . + . "contracts/crux-E-07-v1.yaml"^^ . "crux-E-07-v1"^^ . "kernel"^^ . . . + . "contracts/crux-E-08-v1.yaml"^^ . "crux-E-08-v1"^^ . . . + . "contracts/crux-E-09-v1.yaml"^^ . "crux-E-09-v1"^^ . . . + . "contracts/crux-E-10-v1.yaml"^^ . "crux-E-10-v1"^^ . . . + . "contracts/crux-E-11-v1.yaml"^^ . "crux-E-11-v1"^^ . . . + . "contracts/crux-E-12-v1.yaml"^^ . "crux-E-12-v1"^^ . . . + . "contracts/crux-E-13-v1.yaml"^^ . "crux-E-13-v1"^^ . . . + . "contracts/crux-E-14-v1.yaml"^^ . "crux-E-14-v1"^^ . . . + . "contracts/crux-E-15-v1.yaml"^^ . "crux-E-15-v1"^^ . . . + . "contracts/crux-E-16-v1.yaml"^^ . "crux-E-16-v1"^^ . . . + . "contracts/crux-E-17-v1.yaml"^^ . "crux-E-17-v1"^^ . . . + . "contracts/crux-E-18-v1.yaml"^^ . "crux-E-18-v1"^^ . . . + . "contracts/crux-E-19-v1.yaml"^^ . "crux-E-19-v1"^^ . . . + . "contracts/crux-E-20-v1.yaml"^^ . "crux-E-20-v1"^^ . . . + . "contracts/crux-E-21-v1.yaml"^^ . "crux-E-21-v1"^^ . . . + . "contracts/crux-E-22-v1.yaml"^^ . "crux-E-22-v1"^^ . . . + . "contracts/crux-E-23-v1.yaml"^^ . "crux-E-23-v1"^^ . . . + . "contracts/crux-E-24-v1.yaml"^^ . "crux-E-24-v1"^^ . . . + . "contracts/crux-E-25-v1.yaml"^^ . "crux-E-25-v1"^^ . . . + . "contracts/crux-F-01-v1.yaml"^^ . "crux-F-01-v1"^^ . . . + . "contracts/crux-F-02-v1.yaml"^^ . "crux-F-02-v1"^^ . . . + . "contracts/crux-F-03-v1.yaml"^^ . "crux-F-03-v1"^^ . . . + . "contracts/crux-F-04-v1.yaml"^^ . "crux-F-04-v1"^^ . . . + . "contracts/crux-F-05-v1.yaml"^^ . "crux-F-05-v1"^^ . . . + . "contracts/crux-F-06-v1.yaml"^^ . "crux-F-06-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-07-v1.yaml"^^ . "crux-F-07-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-08-v1.yaml"^^ . "crux-F-08-v1"^^ . . . + . "contracts/crux-F-09-v1.yaml"^^ . "crux-F-09-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-11-v1.yaml"^^ . "crux-F-11-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-12-v1.yaml"^^ . "crux-F-12-v1"^^ . . . + . "contracts/crux-F-13-v1.yaml"^^ . "crux-F-13-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-14-v1.yaml"^^ . "crux-F-14-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-15-v1.yaml"^^ . "crux-F-15-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-16-v1.yaml"^^ . "crux-F-16-v1"^^ . . . + . "contracts/crux-F-17-v1.yaml"^^ . "crux-F-17-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-18-v1.yaml"^^ . "crux-F-18-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-19-v1.yaml"^^ . "crux-F-19-v1"^^ . "kernel"^^ . . . + . "contracts/crux-F-20-v1.yaml"^^ . "crux-F-20-v1"^^ . . . + . "contracts/crux-F-21-v1.yaml"^^ . "crux-F-21-v1"^^ . . . + . "contracts/crux-G-01-v1.yaml"^^ . "crux-G-01-v1"^^ . . . + . "contracts/crux-G-02-v1.yaml"^^ . "crux-G-02-v1"^^ . . . + . "contracts/crux-G-03-v1.yaml"^^ . "crux-G-03-v1"^^ . . . + . "contracts/crux-G-04-v1.yaml"^^ . "crux-G-04-v1"^^ . . . + . "contracts/crux-G-05-v1.yaml"^^ . "crux-G-05-v1"^^ . "kernel"^^ . . . + . "contracts/crux-G-06-v1.yaml"^^ . "crux-G-06-v1"^^ . . . + . "contracts/crux-G-07-v1.yaml"^^ . "crux-G-07-v1"^^ . . . + . "contracts/crux-G-08-v1.yaml"^^ . "crux-G-08-v1"^^ . . . + . "contracts/crux-G-09-v1.yaml"^^ . "crux-G-09-v1"^^ . . . + . "contracts/crux-G-10-v1.yaml"^^ . "crux-G-10-v1"^^ . . . + . "contracts/crux-G-11-v1.yaml"^^ . "crux-G-11-v1"^^ . . . + . "contracts/crux-G-12-v1.yaml"^^ . "crux-G-12-v1"^^ . . . + . "contracts/crux-G-13-v1.yaml"^^ . "crux-G-13-v1"^^ . . . + . "contracts/crux-G-14-v1.yaml"^^ . "crux-G-14-v1"^^ . . . + . "contracts/crux-G-15-v1.yaml"^^ . "crux-G-15-v1"^^ . . . + . "contracts/crux-H-01-v1.yaml"^^ . "crux-H-01-v1"^^ . . . + . "contracts/crux-H-02-v1.yaml"^^ . "crux-H-02-v1"^^ . . . + . "contracts/crux-H-03-v1.yaml"^^ . "crux-H-03-v1"^^ . . . + . "contracts/crux-H-05-v1.yaml"^^ . "crux-H-05-v1"^^ . . . + . "contracts/crux-H-06-v1.yaml"^^ . "crux-H-06-v1"^^ . . . + . "contracts/crux-H-07-v1.yaml"^^ . "crux-H-07-v1"^^ . . . + . "contracts/crux-H-08-v1.yaml"^^ . "crux-H-08-v1"^^ . . . + . "contracts/crux-H-09-v1.yaml"^^ . "crux-H-09-v1"^^ . . . + . "contracts/crux-H-10-v1.yaml"^^ . "crux-H-10-v1"^^ . . . + . "contracts/crux-H-11-v1.yaml"^^ . "crux-H-11-v1"^^ . . . + . "contracts/crux-H-12-v1.yaml"^^ . "crux-H-12-v1"^^ . . . + . "contracts/crux-H-13-v1.yaml"^^ . "crux-H-13-v1"^^ . "kernel"^^ . . . + . "contracts/crux-H-14-v1.yaml"^^ . "crux-H-14-v1"^^ . . . + . "contracts/crux-H-15-v1.yaml"^^ . "crux-H-15-v1"^^ . . . + . "contracts/crux-H-16-v1.yaml"^^ . "crux-H-16-v1"^^ . . . + . "contracts/crux-H-17-v1.yaml"^^ . "crux-H-17-v1"^^ . . . + . "contracts/crux-H-18-v1.yaml"^^ . "crux-H-18-v1"^^ . . . + . "contracts/crux-H-19-v1.yaml"^^ . "crux-H-19-v1"^^ . . . + . "contracts/crux-H-20-v1.yaml"^^ . "crux-H-20-v1"^^ . . . + . "contracts/crux-H-21-v1.yaml"^^ . "crux-H-21-v1"^^ . . . + . "contracts/crux-I-01-v1.yaml"^^ . "crux-I-01-v1"^^ . . . + . "contracts/crux-I-02-v1.yaml"^^ . "crux-I-02-v1"^^ . . . + . "contracts/crux-I-03-v1.yaml"^^ . "crux-I-03-v1"^^ . . . + . "contracts/crux-I-04-v1.yaml"^^ . "crux-I-04-v1"^^ . "kernel"^^ . . . + . "contracts/crux-I-06-v1.yaml"^^ . "crux-I-06-v1"^^ . "kernel"^^ . . . + . "contracts/crux-I-07-v1.yaml"^^ . "crux-I-07-v1"^^ . . . + . "contracts/crux-I-08-v1.yaml"^^ . "crux-I-08-v1"^^ . . . + . "contracts/crux-I-09-v1.yaml"^^ . "crux-I-09-v1"^^ . . . + . "contracts/crux-I-10-v1.yaml"^^ . "crux-I-10-v1"^^ . . . + . "contracts/crux-I-11-v1.yaml"^^ . "crux-I-11-v1"^^ . . . + . "contracts/crux-I-12-v1.yaml"^^ . "crux-I-12-v1"^^ . . . + . "contracts/crux-I-13-v1.yaml"^^ . "crux-I-13-v1"^^ . . . + . "contracts/crux-I-14-v1.yaml"^^ . "crux-I-14-v1"^^ . . . + . "contracts/crux-I-15-v1.yaml"^^ . "crux-I-15-v1"^^ . . . + . "contracts/crux-I-16-v1.yaml"^^ . "crux-I-16-v1"^^ . . . + . "contracts/crux-J-01-v1.yaml"^^ . "crux-J-01-v1"^^ . . . + . "contracts/crux-J-02-v1.yaml"^^ . "crux-J-02-v1"^^ . . . + . "contracts/crux-J-03-v1.yaml"^^ . "crux-J-03-v1"^^ . . . + . "contracts/crux-J-04-v1.yaml"^^ . "crux-J-04-v1"^^ . . . + . "contracts/crux-J-05-v1.yaml"^^ . "crux-J-05-v1"^^ . . . + . "contracts/crux-J-06-v1.yaml"^^ . "crux-J-06-v1"^^ . . . + . "contracts/crux-J-07-v1.yaml"^^ . "crux-J-07-v1"^^ . . . + . "contracts/crux-J-08-v1.yaml"^^ . "crux-J-08-v1"^^ . . . + . "contracts/crux-J-09-v1.yaml"^^ . "crux-J-09-v1"^^ . . . + . "contracts/crux-J-10-v1.yaml"^^ . "crux-J-10-v1"^^ . . . + . "contracts/crux-J-11-v1.yaml"^^ . "crux-J-11-v1"^^ . . . + . "contracts/crux-J-12-v1.yaml"^^ . "crux-J-12-v1"^^ . . . + . "contracts/crux-J-13-v1.yaml"^^ . "crux-J-13-v1"^^ . . . + . "contracts/crux-J-14-v1.yaml"^^ . "crux-J-14-v1"^^ . . . + . "contracts/crux-J-15-v1.yaml"^^ . "crux-J-15-v1"^^ . . . + . "contracts/crux-J-16-v1.yaml"^^ . "crux-J-16-v1"^^ . . . + . "contracts/crux-J-17-v1.yaml"^^ . "crux-J-17-v1"^^ . . . + . "contracts/crux-J-18-v1.yaml"^^ . "crux-J-18-v1"^^ . . . + . "contracts/crux-J-19-v1.yaml"^^ . "crux-J-19-v1"^^ . . . + . "contracts/crux-J-20-v1.yaml"^^ . "crux-J-20-v1"^^ . . . + . "contracts/crux-K-01-v1.yaml"^^ . "crux-K-01-v1"^^ . . . + . "contracts/crux-K-02-v1.yaml"^^ . "crux-K-02-v1"^^ . . . + . "contracts/crux-K-03-v1.yaml"^^ . "crux-K-03-v1"^^ . . . + . "contracts/crux-K-04-v1.yaml"^^ . "crux-K-04-v1"^^ . . . + . "contracts/crux-K-05-v1.yaml"^^ . "crux-K-05-v1"^^ . . . + . "contracts/crux-K-07-v1.yaml"^^ . "crux-K-07-v1"^^ . "kernel"^^ . . . + . "contracts/crux-K-08-v1.yaml"^^ . "crux-K-08-v1"^^ . "kernel"^^ . . . + . "contracts/crux-K-09-v1.yaml"^^ . "crux-K-09-v1"^^ . . . + . "contracts/crux-K-10-v1.yaml"^^ . "crux-K-10-v1"^^ . . . + . "contracts/crux-K-11-v1.yaml"^^ . "crux-K-11-v1"^^ . . . + . "contracts/crux-K-12-v1.yaml"^^ . "crux-K-12-v1"^^ . . . + . "contracts/crux-K-13-v1.yaml"^^ . "crux-K-13-v1"^^ . . . + . "contracts/crux-K-14-v1.yaml"^^ . "crux-K-14-v1"^^ . . . + . "contracts/crux-K-15-v1.yaml"^^ . "crux-K-15-v1"^^ . . . + . "contracts/crux-K-16-v1.yaml"^^ . "crux-K-16-v1"^^ . . . + . "contracts/crux-K-17-v1.yaml"^^ . "crux-K-17-v1"^^ . . . + . "contracts/crux-K-18-v1.yaml"^^ . "crux-K-18-v1"^^ . . . + . "contracts/crux-K-19-v1.yaml"^^ . "crux-K-19-v1"^^ . . . + . "contracts/crux-K-20-v1.yaml"^^ . "crux-K-20-v1"^^ . . . + . "contracts/crux-K-21-v1.yaml"^^ . "crux-K-21-v1"^^ . . . + . "contracts/crux-L-01-v1.yaml"^^ . "crux-L-01-v1"^^ . . . + . "contracts/crux-L-02-v1.yaml"^^ . "crux-L-02-v1"^^ . "kernel"^^ . . . + . "contracts/crux-L-03-v1.yaml"^^ . "crux-L-03-v1"^^ . . . + . "contracts/crux-L-04-v1.yaml"^^ . "crux-L-04-v1"^^ . . . + . "contracts/crux-L-05-v1.yaml"^^ . "crux-L-05-v1"^^ . . . + . "contracts/crux-L-06-v1.yaml"^^ . "crux-L-06-v1"^^ . . . + . "contracts/crux-L-07-v1.yaml"^^ . "crux-L-07-v1"^^ . . . + . "contracts/crux-L-08-v1.yaml"^^ . "crux-L-08-v1"^^ . . . + . "contracts/crux-L-09-v1.yaml"^^ . "crux-L-09-v1"^^ . . . + . "contracts/crux-L-10-v1.yaml"^^ . "crux-L-10-v1"^^ . . . + . "contracts/crux-L-11-v1.yaml"^^ . "crux-L-11-v1"^^ . . . + . "contracts/crux-L-12-v1.yaml"^^ . "crux-L-12-v1"^^ . . . + . "contracts/crux-L-13-v1.yaml"^^ . "crux-L-13-v1"^^ . . . + . "contracts/crux-L-14-v1.yaml"^^ . "crux-L-14-v1"^^ . . . + . "contracts/crux-L-15-v1.yaml"^^ . "crux-L-15-v1"^^ . . . + . "contracts/crux-M-01-v1.yaml"^^ . "crux-M-01-v1"^^ . . . + . "contracts/crux-M-02-v1.yaml"^^ . "crux-M-02-v1"^^ . . . + . "contracts/crux-M-04-v1.yaml"^^ . "crux-M-04-v1"^^ . . . + . "contracts/crux-M-05-v1.yaml"^^ . "crux-M-05-v1"^^ . . . + . "contracts/crux-M-06-v1.yaml"^^ . "crux-M-06-v1"^^ . . . + . "contracts/crux-M-07-v1.yaml"^^ . "crux-M-07-v1"^^ . . . + . "contracts/crux-M-08-v1.yaml"^^ . "crux-M-08-v1"^^ . . . + . "contracts/crux-M-09-v1.yaml"^^ . "crux-M-09-v1"^^ . . . + . "contracts/crux-M-10-v1.yaml"^^ . "crux-M-10-v1"^^ . . . + . "contracts/crux-N-01-v1.yaml"^^ . "crux-N-01-v1"^^ . . . + . "contracts/crux-N-02-v1.yaml"^^ . "crux-N-02-v1"^^ . . . + . "contracts/crux-N-03-v1.yaml"^^ . "crux-N-03-v1"^^ . . . + . "contracts/crux-N-04-v1.yaml"^^ . "crux-N-04-v1"^^ . . . + . "contracts/crux-N-05-v1.yaml"^^ . "crux-N-05-v1"^^ . . . + . "contracts/crux-N-06-v1.yaml"^^ . "crux-N-06-v1"^^ . . . + . "contracts/crux-N-07-v1.yaml"^^ . "crux-N-07-v1"^^ . . . + . "contracts/crux-N-08-v1.yaml"^^ . "crux-N-08-v1"^^ . . . + . "contracts/crux-N-09-v1.yaml"^^ . "crux-N-09-v1"^^ . . . + . "contracts/crux-N-10-v1.yaml"^^ . "crux-N-10-v1"^^ . . . + . "contracts/crux-N-11-v1.yaml"^^ . "crux-N-11-v1"^^ . . . + . "contracts/crux-N-12-v1.yaml"^^ . "crux-N-12-v1"^^ . . . + . "contracts/crux-N-13-v1.yaml"^^ . "crux-N-13-v1"^^ . . . + . "contracts/crux-N-14-v1.yaml"^^ . "crux-N-14-v1"^^ . . . + . "contracts/crux-N-15-v1.yaml"^^ . "crux-N-15-v1"^^ . . . + . "contracts/crux-N-16-v1.yaml"^^ . "crux-N-16-v1"^^ . . . + . "contracts/crux-N-17-v1.yaml"^^ . "crux-N-17-v1"^^ . . . + . "contracts/crux-O-01-v1.yaml"^^ . "crux-O-01-v1"^^ . . . + . "contracts/crux-O-02-v1.yaml"^^ . "crux-O-02-v1"^^ . . . + . "contracts/crux-O-03-v1.yaml"^^ . "crux-O-03-v1"^^ . . . + . "contracts/crux-O-04-v1.yaml"^^ . "crux-O-04-v1"^^ . . . + . "contracts/crux-O-05-v1.yaml"^^ . "crux-O-05-v1"^^ . . . + . "contracts/crux-O-06-v1.yaml"^^ . "crux-O-06-v1"^^ . . . + . "contracts/crux-O-07-v1.yaml"^^ . "crux-O-07-v1"^^ . . . + . "contracts/crux-O-08-v1.yaml"^^ . "crux-O-08-v1"^^ . . . + . "contracts/crux-O-09-v1.yaml"^^ . "crux-O-09-v1"^^ . . . + . "contracts/crux-O-10-v1.yaml"^^ . "crux-O-10-v1"^^ . . . + . "contracts/crux-O-11-v1.yaml"^^ . "crux-O-11-v1"^^ . . . + . "contracts/crux-O-12-v1.yaml"^^ . "crux-O-12-v1"^^ . . . + . "contracts/crux-O-13-v1.yaml"^^ . "crux-O-13-v1"^^ . . . + . "contracts/crux-O-14-v1.yaml"^^ . "crux-O-14-v1"^^ . . . + . "contracts/crux-O-15-v1.yaml"^^ . "crux-O-15-v1"^^ . . . + . "contracts/crux-O-16-v1.yaml"^^ . "crux-O-16-v1"^^ . . . + . "contracts/crux-O-17-v1.yaml"^^ . "crux-O-17-v1"^^ . . . + . "contracts/crux-O-18-v1.yaml"^^ . "crux-O-18-v1"^^ . . . + . "contracts/crux-O-19-v1.yaml"^^ . "crux-O-19-v1"^^ . . . + . "contracts/crux-O-20-v1.yaml"^^ . "crux-O-20-v1"^^ . . . + . "contracts/crux-O-21-v1.yaml"^^ . "crux-O-21-v1"^^ . . . + . "contracts/crux-O-22-v1.yaml"^^ . "crux-O-22-v1"^^ . . . + . "contracts/crux-O-23-v1.yaml"^^ . "crux-O-23-v1"^^ . . . + . "contracts/crux-O-24-v1.yaml"^^ . "crux-O-24-v1"^^ . . . + . "contracts/crux-competitive-research-ux-v1.yaml"^^ . "crux-competitive-research-ux-v1"^^ . . @@ -7531,6 +8073,7 @@ "pattern"^^ . . . + . "contracts/cuda-classify-training-v1.yaml"^^ . "contracts/entrenar/cuda-classify-training-v1.yaml"^^ . "cuda-classify-training-v1"^^ . @@ -7543,18 +8086,22 @@ "pattern"^^ . . . + . "contracts/cuda-graph-backward-v1.yaml"^^ . "cuda-graph-backward-v1"^^ . . . + . "contracts/cuda-graph-batched-inference-v1.yaml"^^ . "cuda-graph-batched-inference-v1"^^ . . . + . "contracts/entrenar/cuda-graph-training-step-v1.yaml"^^ . "cuda-graph-training-step-v1"^^ . . . + . "contracts/cuda-kernel-safety-v1.yaml"^^ . "cuda-kernel-safety-v1"^^ . . @@ -7569,27 +8116,32 @@ "pattern"^^ . . . + . "contracts/cuda-oxide-rope-parity-v1.yaml"^^ . "cuda-oxide-rope-parity-v1"^^ . "kernel"^^ . . . + . "contracts/cuda-q4k-frozen-teacher-v1.yaml"^^ . "cuda-q4k-frozen-teacher-v1"^^ . "cuda-q4k-frozen-teacher"^^ . "1.0.0"^^ . . . + . "contracts/trueno-gpu/cuda-unified-memory-allocator-v1.yaml"^^ . "cuda-unified-memory-allocator-v1"^^ . "cuda-unified-memory-allocator"^^ . "1.0.0"^^ . . . + . "contracts/forjar/dag-ordering-v1.yaml"^^ . "dag-ordering-v1"^^ . . . + . "contracts/alimentar/data-feed-v1.yaml"^^ . "data-feed-v1"^^ . . @@ -7601,29 +8153,35 @@ "1.0.0"^^ . . . + . "contracts/decy/decision-engine-v1.yaml"^^ . "decision-engine-v1"^^ . . . + . "contracts/decision-tree-v1.yaml"^^ . "decision-tree-v1"^^ . . . + . "contracts/decode-gpu-resident-sampling-v1.yaml"^^ . "decode-gpu-resident-sampling-v1"^^ . "FALSIFIED"^^ . . . + . "contracts/decode-hot-path-first-tokens-diagnostic-v1.yaml"^^ . "decode-hot-path-first-tokens-diagnostic-v1"^^ . "SHIPPED"^^ . . . + . "contracts/decode-hot-path-prefix-cache-diagnostic-v1.yaml"^^ . "decode-hot-path-prefix-cache-diagnostic-v1"^^ . "SHIPPED"^^ . . . + . "contracts/decode-hot-path-zero-syscalls-v1.yaml"^^ . "decode-hot-path-zero-syscalls-v1"^^ . . @@ -7633,36 +8191,43 @@ "model-family"^^ . . . + . "contracts/copia/delta-sync-v1.yaml"^^ . "delta-sync-v1"^^ . . . + . "contracts/dimension-independent-kernels-v1.yaml"^^ . "dimension-independent-kernels-v1"^^ . "dimension-independent-kernels"^^ . "1.0.0"^^ . . . + . "contracts/discriminant-analysis-v1.yaml"^^ . "discriminant-analysis-v1"^^ . . . + . "contracts/display-format-v1.yaml"^^ . "display-format-v1"^^ . . . + . "contracts/distill-per-position-kd-v1.yaml"^^ . "distill-per-position-kd-v1"^^ . "distill-per-position-kd"^^ . "1.0.0"^^ . . . + . "contracts/distill-pipeline-observability-v1.yaml"^^ . "distill-pipeline-observability-v1"^^ . "distill-pipeline-observability"^^ . "1.0.0"^^ . . . + . "contracts/distributed-training-v1.yaml"^^ . "contracts/entrenar/distributed-training-v1.yaml"^^ . "distributed-training-v1"^^ . @@ -7670,49 +8235,60 @@ "1.0.0"^^ . . . + . "contracts/repartir/distribution-v1.yaml"^^ . "distribution-v1"^^ . . . + . "contracts/document-integrity-v1.yaml"^^ . "document-integrity-v1"^^ . "kernel"^^ . . . + . "contracts/dogfood-runner-unification-v1.yaml"^^ . "dogfood-runner-unification-v1"^^ . . . + . "contracts/dpo-loss-v1.yaml"^^ . "dpo-loss-v1"^^ . . . + . "contracts/drift-detection-v1.yaml"^^ . "drift-detection-v1"^^ . . . + . "contracts/dropout-v1.yaml"^^ . "dropout-v1"^^ . . . + . "contracts/dry-penalty-repeat-len-v1.yaml"^^ . "dry-penalty-repeat-len-v1"^^ . "dry-penalty-repeat-len"^^ . "1.0.0"^^ . . . + . "contracts/embedding-algebra-v1.yaml"^^ . "embedding-algebra-v1"^^ . . . + . "contracts/embedding-lookup-v1.yaml"^^ . "embedding-lookup-v1"^^ . . . + . "contracts/encoder-forward-v1.yaml"^^ . "encoder-forward-v1"^^ . . . + . "contracts/bashrs/encoder-roundtrip-v1.yaml"^^ . "contracts/encoder-roundtrip-v1.yaml"^^ . "contracts/rmedia/encoder-roundtrip-v1.yaml"^^ . @@ -7721,45 +8297,54 @@ "1.0.0"^^ . . . + . "contracts/error-handling-v1.yaml"^^ . "contracts/pepita/error-handling-v1.yaml"^^ . "error-handling-v1"^^ . . . + . "contracts/eval-harness-humaneval-v1.yaml"^^ . "eval-harness-humaneval-v1"^^ . "FALSIFIED"^^ . . . + . "contracts/eval-passk-single-sample-v1.yaml"^^ . "eval-passk-single-sample-v1"^^ . "eval-passk-single-sample"^^ . "1.0.0"^^ . . . + . "contracts/eval-sharding-v1.yaml"^^ . "eval-sharding-v1"^^ . "ACTIVE"^^ . . . + . "contracts/forjar/event-rulebook-v1.yaml"^^ . "event-rulebook-v1"^^ . . . + . "contracts/forjar/execution-safety-v1.yaml"^^ . "execution-safety-v1"^^ . . . + . "contracts/export-user-metadata-roundtrip-v1.yaml"^^ . "export-user-metadata-roundtrip-v1"^^ . "export-user-metadata-roundtrip"^^ . "1.0.0"^^ . . . + . "contracts/f16-conversion-v1.yaml"^^ . "f16-conversion-v1"^^ . . . + . "contracts/f16-to-f32-subnormal-v1.yaml"^^ . "f16-to-f32-subnormal-v1"^^ . "f16-to-f32-subnormal"^^ . @@ -7791,6 +8376,7 @@ "pattern"^^ . . . + . "contracts/flash-attention-v1.yaml"^^ . "flash-attention-v1"^^ . . @@ -7800,48 +8386,59 @@ "pattern"^^ . . . + . "contracts/format-parity-v1.yaml"^^ . "format-parity-v1"^^ . . . + . "contracts/fp16-cublas-gemm-v1.yaml"^^ . "fp16-cublas-gemm-v1"^^ . "fp16-cublas-gemm"^^ . "1.0.0"^^ . . . + . "contracts/fp8-interchange-v1.yaml"^^ . "fp8-interchange-v1"^^ . . . + . "contracts/entrenar/fused-backward-gemm-v1.yaml"^^ . "fused-backward-gemm-v1"^^ . . . + . "contracts/fused-qkv-projection-v1.yaml"^^ . "fused-qkv-projection-v1"^^ . . . + . "contracts/garbage-oracle-v1.yaml"^^ . "garbage-oracle-v1"^^ . . . + . "contracts/gated-delta-net-v1.yaml"^^ . "gated-delta-net-v1"^^ . . . + . "contracts/gateway-contract-v1.yaml"^^ . "gateway-contract-v1"^^ . . . + . "contracts/gbm-v1.yaml"^^ . "gbm-v1"^^ . . . + . "contracts/gelu-kernel-v1.yaml"^^ . "gelu-kernel-v1"^^ . . . + . "contracts/gemm-backward-tiled-v1.yaml"^^ . "contracts/trueno-gpu/gemm-backward-tiled-v1.yaml"^^ . "gemm-backward-tiled-v1"^^ . @@ -7861,20 +8458,24 @@ "model-family"^^ . . . + . "contracts/ggml-type-v1.yaml"^^ . "ggml-type-v1"^^ . "ggml-type"^^ . "1.0.0"^^ . . . + . "contracts/gguf-cpu-cache-v1.yaml"^^ . "gguf-cpu-cache-v1"^^ . . . + . "contracts/gguf-format-safety-v1.yaml"^^ . "gguf-format-safety-v1"^^ . . . + . "contracts/gguf-kquant-element-size-v1.yaml"^^ . "gguf-kquant-element-size-v1"^^ . "gguf-kquant-element-size"^^ . @@ -7884,22 +8485,37 @@ "contracts/gguf-prompt-sensitivity-v1.yaml"^^ . "gguf-prompt-sensitivity-v1"^^ . "pattern"^^ . + . + . + . + . + "repo"^^ . + "contracts/github-entities-v1.yaml"^^ . + "github-entities-v1"^^ . + "pattern"^^ . + "github-entities-v1"^^ . + "active"^^ . + "1.0.0"^^ . . . + . "contracts/glm-irls-link-derivative-v1.yaml"^^ . "glm-irls-link-derivative-v1"^^ . "glm-irls-link-derivative"^^ . "1.0.0"^^ . . . + . "contracts/glm-v1.yaml"^^ . "glm-v1"^^ . . . + . "contracts/gnn-v1.yaml"^^ . "gnn-v1"^^ . . . + . "contracts/golden-trace-v1.yaml"^^ . "contracts/renacer/golden-trace-v1.yaml"^^ . "golden-trace-v1"^^ . @@ -7912,6 +8528,7 @@ "model-family"^^ . . . + . "contracts/gpt2-bpe-decode-roundtrip-v1.yaml"^^ . "gpt2-bpe-decode-roundtrip-v1"^^ . "gpt2-bpe-decode-roundtrip"^^ . @@ -7928,6 +8545,7 @@ "model-family"^^ . . . + . "contracts/gpu-context-health-v1.yaml"^^ . "gpu-context-health-v1"^^ . . @@ -7937,11 +8555,13 @@ "pattern"^^ . . . + . "contracts/gpu-decode-profiling-v1.yaml"^^ . "contracts/rmedia/gpu-decode-profiling-v1.yaml"^^ . "gpu-decode-profiling-v1"^^ . . . + . "contracts/gpu-multi-backend-parity-v1.yaml"^^ . "gpu-multi-backend-parity-v1"^^ . . @@ -7953,20 +8573,24 @@ "1.5.0"^^ . . . + . "contracts/gpu-weight-residency-v1.yaml"^^ . "gpu-weight-residency-v1"^^ . . . + . "contracts/gqa-kernel-v1.yaml"^^ . "gqa-kernel-v1"^^ . . . + . "contracts/gqa-kv-dim-fail-closed-v1.yaml"^^ . "gqa-kv-dim-fail-closed-v1"^^ . "gqa-kv-dim-fail-closed"^^ . "1.0.0"^^ . . . + . "contracts/gradient-accumulation-mean-v1.yaml"^^ . "gradient-accumulation-mean-v1"^^ . "gradient-accumulation-mean"^^ . @@ -7978,55 +8602,67 @@ "model-family"^^ . . . + . "contracts/graph-centrality-v1.yaml"^^ . "graph-centrality-v1"^^ . . . + . "contracts/pmat/graph-index-v1.yaml"^^ . "graph-index-v1"^^ . . . + . "contracts/trueno-graph/graph-query-v1.yaml"^^ . "graph-query-v1"^^ . . . + . "contracts/hero-svg-v1.yaml"^^ . "hero-svg-v1"^^ . . . + . "contracts/aprender/http-api-v1.yaml"^^ . "contracts/batuta/http-api-v1.yaml"^^ . "contracts/http-api-v1.yaml"^^ . "http-api-v1"^^ . . . + . "contracts/rurl/http-client-v1.yaml"^^ . "http-client-v1"^^ . . . + . "contracts/hybrid-layer-dispatch-v1.yaml"^^ . "hybrid-layer-dispatch-v1"^^ . . . + . "contracts/ica-v1.yaml"^^ . "ica-v1"^^ . . . + . "contracts/ica-whitening-v1.yaml"^^ . "ica-whitening-v1"^^ . . . + . "contracts/incomplete-beta-correctness-v1.yaml"^^ . "incomplete-beta-correctness-v1"^^ . "incomplete-beta-correctness"^^ . "1.1.0"^^ . . . + . "contracts/inference-pipeline-v1.yaml"^^ . "contracts/realizar/inference-pipeline-v1.yaml"^^ . "inference-pipeline-v1"^^ . . . + . "contracts/int8-symmetric-quant-v1.yaml"^^ . "int8-symmetric-quant-v1"^^ . . @@ -8036,82 +8672,100 @@ "model-family"^^ . . . + . "contracts/isotonic-pav-flatness-v1.yaml"^^ . "isotonic-pav-flatness-v1"^^ . . . + . "contracts/iterator-v1.yaml"^^ . "iterator-v1"^^ . . . + . "contracts/kd-loss-forward-kl-v1.yaml"^^ . "kd-loss-forward-kl-v1"^^ . . . + . "contracts/aprender/kernel-fusion-v1.yaml"^^ . "contracts/kernel-fusion-v1.yaml"^^ . "kernel-fusion-v1"^^ . . . + . "contracts/kernel-launch-budget-v1.yaml"^^ . "kernel-launch-budget-v1"^^ . . . + . "contracts/kmeans-kernel-v1.yaml"^^ . "kmeans-kernel-v1"^^ . . . + . "contracts/knn-tie-smallest-label-v1.yaml"^^ . "knn-tie-smallest-label-v1"^^ . "knn-tie-smallest-label"^^ . "1.1.0"^^ . . . + . "contracts/kv-cache-equivalence-v1.yaml"^^ . "kv-cache-equivalence-v1"^^ . . . + . "contracts/kv-cache-sizing-v1.yaml"^^ . "kv-cache-sizing-v1"^^ . . . + . "contracts/lasso-elasticnet-alpha-v1.yaml"^^ . "lasso-elasticnet-alpha-v1"^^ . "lasso-elasticnet-alpha"^^ . "1.0.0"^^ . . . + . "contracts/aprender/layer-parity-v1.yaml"^^ . "contracts/layer-parity-v1.yaml"^^ . "layer-parity-v1"^^ . . . + . "contracts/layernorm-kernel-v1.yaml"^^ . "layernorm-kernel-v1"^^ . . . + . "contracts/lbfgs-kernel-v1.yaml"^^ . "lbfgs-kernel-v1"^^ . . . + . "contracts/learned-position-embedding-v1.yaml"^^ . "learned-position-embedding-v1"^^ . . . + . "contracts/linear-bias-init-v1.yaml"^^ . "linear-bias-init-v1"^^ . "linear-bias-init"^^ . "1.0.0"^^ . . . + . "contracts/linear-models-v1.yaml"^^ . "linear-models-v1"^^ . . . + . "contracts/linear-probe-classifier-v1.yaml"^^ . "linear-probe-classifier-v1"^^ . . . + . "contracts/linear-projection-v1.yaml"^^ . "linear-projection-v1"^^ . . @@ -8128,12 +8782,14 @@ "1.6.0"^^ . . . + . "contracts/lora-adapter-merge-cli-v1.yaml"^^ . "lora-adapter-merge-cli-v1"^^ . "lora-adapter-merge-cli"^^ . "1.0.0"^^ . . . + . "contracts/lora-adapter-scale-roundtrip-v1.yaml"^^ . "lora-adapter-scale-roundtrip-v1"^^ . "lora-adapter-scale-roundtrip"^^ . @@ -8147,14 +8803,17 @@ "1"^^ . . . + . "contracts/lora-algebra-v1.yaml"^^ . "lora-algebra-v1"^^ . . . + . "contracts/lora-dropout-placement-v1.yaml"^^ . "lora-dropout-placement-v1"^^ . . . + . "contracts/entrenar/lora-gradient-flow-v1.yaml"^^ . "contracts/lora-gradient-flow-v1.yaml"^^ . "lora-gradient-flow-v1"^^ . @@ -8162,16 +8821,19 @@ "1.1.0"^^ . . . + . "contracts/lora-merge-forward-equivalence-v1.yaml"^^ . "lora-merge-forward-equivalence-v1"^^ . . . + . "contracts/lora-merge-peft-layout-v1.yaml"^^ . "lora-merge-peft-layout-v1"^^ . "lora-merge-peft-layout"^^ . "1.0.0"^^ . . . + . "contracts/entrenar/lora-target-selection-v1.yaml"^^ . "contracts/lora-target-selection-v1.yaml"^^ . "lora-target-selection-v1"^^ . @@ -8179,10 +8841,12 @@ "1.0.0"^^ . . . + . "contracts/loss-functions-v1.yaml"^^ . "loss-functions-v1"^^ . . . + . "contracts/machine-specific-paths-v1.yaml"^^ . "machine-specific-paths-v1"^^ . . @@ -8192,27 +8856,33 @@ "model-family"^^ . . . + . "contracts/matmul-kernel-v1.yaml"^^ . "matmul-kernel-v1"^^ . . . + . "contracts/pmcp/mcp-protocol-sdk-v1.yaml"^^ . "mcp-protocol-sdk-v1"^^ . . . + . "contracts/pmat/mcp-protocol-v1.yaml"^^ . "mcp-protocol-v1"^^ . . . + . "contracts/aprender/mcp-tool-schema-v1.yaml"^^ . "contracts/mcp-tool-schema-v1.yaml"^^ . "mcp-tool-schema-v1"^^ . . . + . "contracts/rmedia/media-pipeline-v1.yaml"^^ . "media-pipeline-v1"^^ . . . + . "contracts/depyler/memory-safety-v1.yaml"^^ . "contracts/memory-safety-v1.yaml"^^ . "contracts/pmat/memory-safety-v1.yaml"^^ . @@ -8221,36 +8891,44 @@ "1.0.0"^^ . . . + . "contracts/metaheuristics-v1.yaml"^^ . "metaheuristics-v1"^^ . . . + . "contracts/metrics-classification-v1.yaml"^^ . "metrics-classification-v1"^^ . . . + . "contracts/metrics-clustering-v1.yaml"^^ . "metrics-clustering-v1"^^ . . . + . "contracts/metrics-macro-average-v1.yaml"^^ . "metrics-macro-average-v1"^^ . "metrics-macro-average"^^ . "1.0.0"^^ . . . + . "contracts/metrics-ranking-v1.yaml"^^ . "metrics-ranking-v1"^^ . . . + . "contracts/metrics-regression-v1.yaml"^^ . "metrics-regression-v1"^^ . . . + . "contracts/metrics-sklearn-eps-parity-v1.yaml"^^ . "metrics-sklearn-eps-parity-v1"^^ . . . + . "contracts/mirostat-bits-v1.yaml"^^ . "mirostat-bits-v1"^^ . "mirostat-bits"^^ . @@ -8264,52 +8942,62 @@ . . "gguf"^^ . + "L2"^^ . "contracts/model-capability-ladder-v1.yaml"^^ . "model-capability-ladder-v1"^^ . "pattern"^^ . . . + . "contracts/model-config-algebra-v1.yaml"^^ . "model-config-algebra-v1"^^ . . . + . "contracts/model-family-parity-v1.yaml"^^ . "model-family-parity-v1"^^ . "model-family-parity"^^ . "1.0.0"^^ . . . + . "contracts/aprender/model-format-conversion-v1.yaml"^^ . "contracts/model-format-conversion-v1.yaml"^^ . "model-format-conversion-v1"^^ . . . + . "contracts/model-metadata-bounds-v1.yaml"^^ . "model-metadata-bounds-v1"^^ . . . + . "contracts/apr-model-qa-playbook/model-qa-v1.yaml"^^ . "model-qa-v1"^^ . . . + . "contracts/moe-expert-dispatch-v1.yaml"^^ . "moe-expert-dispatch-v1"^^ . "moe-expert-dispatch"^^ . "1.0.0"^^ . . . + . "contracts/moe-load-balance-loss-v1.yaml"^^ . "moe-load-balance-loss-v1"^^ . "moe-load-balance-loss"^^ . "1.0.0"^^ . . . + . "contracts/moe-router-v1.yaml"^^ . "moe-router-v1"^^ . "moe-router"^^ . "1.0.0"^^ . . . + . "contracts/zenith/monitor-metrics-v1.yaml"^^ . "monitor-metrics-v1"^^ . . @@ -8319,19 +9007,23 @@ "model-family"^^ . . . + . "contracts/apr-model-qa-playbook/mqs-scoring-v1.yaml"^^ . "contracts/mqs-scoring-v1.yaml"^^ . "mqs-scoring-v1"^^ . . . + . "contracts/multinomial-head-v1.yaml"^^ . "multinomial-head-v1"^^ . . . + . "contracts/naive-bayes-v1.yaml"^^ . "naive-bayes-v1"^^ . . . + . "contracts/pepita/namespace-isolation-v1.yaml"^^ . "namespace-isolation-v1"^^ . . @@ -8341,10 +9033,12 @@ "model-family"^^ . . . + . "contracts/trueno/neon-dequant-v1.yaml"^^ . "neon-dequant-v1"^^ . . . + . "contracts/nf4-backward-tensor-core-gemm-v1.yaml"^^ . "contracts/trueno/nf4-backward-tensor-core-gemm-v1.yaml"^^ . "nf4-backward-tensor-core-gemm-v1"^^ . @@ -8352,22 +9046,27 @@ "1.0.0"^^ . . . + . "contracts/nf4-fused-gate-up-swiglu-v1.yaml"^^ . "nf4-fused-gate-up-swiglu-v1"^^ . . . + . "contracts/nf4-fused-qkv-gemm-v1.yaml"^^ . "nf4-fused-qkv-gemm-v1"^^ . . . + . "contracts/nf4-fused-rmsnorm-gemv-v1.yaml"^^ . "nf4-fused-rmsnorm-gemv-v1"^^ . . . + . "contracts/nf4-tensor-core-gemm-v1.yaml"^^ . "nf4-tensor-core-gemm-v1"^^ . . . + . "contracts/nn-softmax-dim-v1.yaml"^^ . "nn-softmax-dim-v1"^^ . "nn-softmax-dim"^^ . @@ -8388,6 +9087,7 @@ "1"^^ . . . + . "contracts/forjar/oci-manifest-v1.yaml"^^ . "oci-manifest-v1"^^ . . @@ -8397,8 +9097,10 @@ "model-family"^^ . . . + . "contracts/online-softmax-v1.yaml"^^ . "online-softmax-v1"^^ . + . . . . @@ -8409,6 +9111,28 @@ "ont-code-symbols"^^ . "active"^^ . "1.0.0"^^ . + . + . + "contracts/ont-consistency-v1.yaml"^^ . + "ont-consistency-v1"^^ . + "pattern"^^ . + . + . + . + . + "contracts/ont-docs-corpus-v1.yaml"^^ . + "ont-docs-corpus-v1"^^ . + "pattern"^^ . + . + . + . + . + "contracts/ont-evidence-v1.yaml"^^ . + "ont-evidence-v1"^^ . + "pattern"^^ . + "ont-evidence"^^ . + "active"^^ . + "1.0.0"^^ . . . . @@ -8433,6 +9157,11 @@ "ont-model-receipts"^^ . "active"^^ . "1.0.0"^^ . + . + . + "contracts/ont-refines-v1.yaml"^^ . + "ont-refines-v1"^^ . + "pattern"^^ . . . . @@ -8443,6 +9172,17 @@ "ont-relations"^^ . "active"^^ . "1.0.0"^^ . + . + . + . + . + . + . + "contracts/"^^ . + "pv-contract"^^ . + "contracts/ont-self-v1.yaml"^^ . + "ont-self-v1"^^ . + "kernel"^^ . . . . @@ -8451,6 +9191,7 @@ . "contracts/"^^ . "pv-contract"^^ . + "L1"^^ . "contracts/ont-shapes-v1.yaml"^^ . "ont-shapes-v1"^^ . "pattern"^^ . @@ -8478,11 +9219,13 @@ "1.0.0"^^ . . . + . "contracts/ont-verdict-lattice-v1.yaml"^^ . "ont-verdict-lattice-v1"^^ . "kernel"^^ . . . + . "contracts/openai-serve-sampling-determinism-v1.yaml"^^ . "openai-serve-sampling-determinism-v1"^^ . "openai-serve-sampling-determinism"^^ . @@ -8499,16 +9242,19 @@ "model-family"^^ . . . + . "contracts/optimization-v1.yaml"^^ . "optimization-v1"^^ . . . + . "contracts/orchestrate-env-test-hermeticity-v1.yaml"^^ . "orchestrate-env-test-hermeticity-v1"^^ . "orchestrate-env-test-hermeticity"^^ . "1.0.0"^^ . . . + . "contracts/orchestrate-macos-portability-v1.yaml"^^ . "orchestrate-macos-portability-v1"^^ . "orchestrate-macos-portability"^^ . @@ -8522,23 +9268,28 @@ "1"^^ . . . + . "contracts/pacha/package-resolve-v1.yaml"^^ . "package-resolve-v1"^^ . . . + . "contracts/paged-attention-v1.yaml"^^ . "paged-attention-v1"^^ . . . + . "contracts/paged-kv-cache-v1.yaml"^^ . "paged-kv-cache-v1"^^ . . . + . "contracts/pagerank-kernel-v1.yaml"^^ . "contracts/trueno-graph/pagerank-kernel-v1.yaml"^^ . "pagerank-kernel-v1"^^ . . . + . "contracts/entrenar/parity-profiling-system-v1.yaml"^^ . "parity-profiling-system-v1"^^ . . @@ -8568,6 +9319,7 @@ "2.0.0"^^ . . . + . "contracts/bashrs/parser-soundness-v1.yaml"^^ . "contracts/parser-soundness-v1.yaml"^^ . "contracts/ruchy/parser-soundness-v1.yaml"^^ . @@ -8576,14 +9328,17 @@ "1.0.0"^^ . . . + . "contracts/pca-v1.yaml"^^ . "pca-v1"^^ . . . + . "contracts/entrenar/per-operation-training-profiling-v1.yaml"^^ . "per-operation-training-profiling-v1"^^ . . . + . "contracts/performance-grading-v1.yaml"^^ . "performance-grading-v1"^^ . . @@ -8593,6 +9348,7 @@ "model-family"^^ . . . + . "contracts/pipeline-cache-v1.yaml"^^ . "contracts/trueno/pipeline-cache-v1.yaml"^^ . "pipeline-cache-v1"^^ . @@ -8600,10 +9356,12 @@ "1.0.0"^^ . . . + . "contracts/forjar/plugin-lifecycle-v1.yaml"^^ . "plugin-lifecycle-v1"^^ . . . + . "contracts/pmat/pmat-work-lifecycle-v1.yaml"^^ . "pmat-work-lifecycle-v1"^^ . . @@ -8635,11 +9393,13 @@ "pattern"^^ . . . + . "contracts/pr-review-skill-v2.yaml"^^ . "pr-review-skill-v2"^^ . "kernel"^^ . . . + . "contracts/preprocessing-normalization-v1.yaml"^^ . "preprocessing-normalization-v1"^^ . . @@ -8659,15 +9419,18 @@ "1.0.0"^^ . . . + . "contracts/profile-graph-vs-per-op-methodology-v1.yaml"^^ . "profile-graph-vs-per-op-methodology-v1"^^ . "PROPOSED"^^ . . . + . "contracts/projected-gradient-armijo-v1.yaml"^^ . "projected-gradient-armijo-v1"^^ . . . + . "contracts/probar/property-testing-v1.yaml"^^ . "property-testing-v1"^^ . . @@ -8677,29 +9440,35 @@ "pattern"^^ . . . + . "contracts/batuta/provider-routing-v1.yaml"^^ . "provider-routing-v1"^^ . . . + . "contracts/prune-sparsity-correctness-v1.yaml"^^ . "prune-sparsity-correctness-v1"^^ . "prune-sparsity-correctness"^^ . "1.0.0"^^ . . . + . "contracts/trueno/ptx-codegen-safety-v1.yaml"^^ . "ptx-codegen-safety-v1"^^ . . . + . "contracts/ptx-target-parity-v1.yaml"^^ . "ptx-target-parity-v1"^^ . . . + . "contracts/publish-manifest-v1.yaml"^^ . "publish-manifest-v1"^^ . "DRAFT"^^ . . . + . "contracts/publish-workspace-v1.yaml"^^ . "publish-workspace-v1"^^ . . @@ -8720,50 +9489,68 @@ "contracts/pv-version-identity-v1.yaml"^^ . "pv-version-identity-v1"^^ . "pattern"^^ . + . + . + . + "contracts/pvl-lint-ratchets-v1.yaml"^^ . + "pvl-lint-ratchets-v1"^^ . + "pattern"^^ . + "pvl-lint-ratchets"^^ . + "active"^^ . + "1.0.0"^^ . . . + . "contracts/q2k-dequant-parity-v1.yaml"^^ . "q2k-dequant-parity-v1"^^ . "q2k-dequant-parity"^^ . "1.0.0"^^ . . . + . "contracts/q3k-dequant-correctness-v1.yaml"^^ . "q3k-dequant-correctness-v1"^^ . "q3k-dequant-correctness"^^ . "1.0.0"^^ . . . + . "contracts/q3k-dequant-v1.yaml"^^ . "q3k-dequant-v1"^^ . "q3k-dequant"^^ . "1.0.0"^^ . . . + . "contracts/q4k-interleaved-scale-min-v1.yaml"^^ . "q4k-interleaved-scale-min-v1"^^ . "q4k-interleaved-scale-min"^^ . "1.0.0"^^ . . . + . "contracts/q4k-q6k-superblock-v1.yaml"^^ . "q4k-q6k-superblock-v1"^^ . . . + . "contracts/q5k-dequant-correctness-v1.yaml"^^ . "q5k-dequant-correctness-v1"^^ . "q5k-dequant-correctness"^^ . "1.0.0"^^ . . . + . "contracts/qk-norm-apr-loader-v1.yaml"^^ . "qk-norm-apr-loader-v1"^^ . . . + . "contracts/qk-norm-v1.yaml"^^ . "qk-norm-v1"^^ . . . + . "contracts/entrenar/qlora-hyperparameters-v1.yaml"^^ . "contracts/qlora-hyperparameters-v1.yaml"^^ . "qlora-hyperparameters-v1"^^ . @@ -8776,30 +9563,36 @@ "pattern"^^ . . . + . "contracts/certeza/quality-validation-v1.yaml"^^ . "quality-validation-v1"^^ . . . + . "contracts/quant-roundtrip-fidelity-v1.yaml"^^ . "quant-roundtrip-fidelity-v1"^^ . "quant-roundtrip-fidelity"^^ . "1.0.0"^^ . . . + . "contracts/quant-solve-f16-round-v1.yaml"^^ . "quant-solve-f16-round-v1"^^ . "quant-solve-f16-round"^^ . "1.1.0"^^ . . . + . "contracts/quantization-ordering-v1.yaml"^^ . "quantization-ordering-v1"^^ . . . + . "contracts/trueno/quantize-dequant-roundtrip-v1.yaml"^^ . "quantize-dequant-roundtrip-v1"^^ . . . + . "contracts/aprender/quantized-dot-product-v1.yaml"^^ . "contracts/quantized-dot-product-v1.yaml"^^ . "quantized-dot-product-v1"^^ . @@ -8815,14 +9608,17 @@ "model-family"^^ . . . + . "contracts/qwen2-e2e-verification-v1.yaml"^^ . "qwen2-e2e-verification-v1"^^ . . . + . "contracts/qwen2-shapes-v1.yaml"^^ . "qwen2-shapes-v1"^^ . . . + . "contracts/aprender/qwen2-weight-loading-v1.yaml"^^ . "contracts/qwen2-weight-loading-v1.yaml"^^ . "qwen2-weight-loading-v1"^^ . @@ -8833,10 +9629,12 @@ "model-family"^^ . . . + . "contracts/qwen3-e2e-verification-v1.yaml"^^ . "qwen3-e2e-verification-v1"^^ . . . + . "contracts/qwen3-moe-forward-gpu-v1.yaml"^^ . "qwen3-moe-forward-gpu-v1"^^ . "kernel"^^ . @@ -8845,6 +9643,7 @@ "1.7.2"^^ . . . + . "contracts/qwen3-moe-forward-v1.yaml"^^ . "qwen3-moe-forward-v1"^^ . "qwen3-moe-forward"^^ . @@ -8852,30 +9651,35 @@ "1.5.0"^^ . . . + . "contracts/qwen3-moe-repetition-penalty-v1.yaml"^^ . "qwen3-moe-repetition-penalty-v1"^^ . "qwen3-moe-repetition-penalty"^^ . "1.1.0"^^ . . . + . "contracts/qwen3-moe-sampling-v1.yaml"^^ . "qwen3-moe-sampling-v1"^^ . "qwen3-moe-sampling"^^ . "1.1.0"^^ . . . + . "contracts/qwen3-moe-serve-dispatch-v1.yaml"^^ . "qwen3-moe-serve-dispatch-v1"^^ . "qwen3-moe-serve-dispatch"^^ . "1.2.0"^^ . . . + . "contracts/qwen3-moe-streaming-sse-v1.yaml"^^ . "qwen3-moe-streaming-sse-v1"^^ . "qwen3-moe-streaming-sse"^^ . "1.0.0"^^ . . . + . "contracts/qwen3-shapes-v1.yaml"^^ . "qwen3-shapes-v1"^^ . . @@ -8887,20 +9691,24 @@ "1.0.0"^^ . . . + . "contracts/qwen35-e2e-verification-v1.yaml"^^ . "qwen35-e2e-verification-v1"^^ . . . + . "contracts/qwen35-hybrid-forward-v1.yaml"^^ . "qwen35-hybrid-forward-v1"^^ . . . + . "contracts/qwen35-hybrid-serve-dispatch-v1.yaml"^^ . "qwen35-hybrid-serve-dispatch-v1"^^ . "qwen35-hybrid-serve-dispatch"^^ . "1.0.0"^^ . . . + . "contracts/qwen35-shapes-v1.yaml"^^ . "qwen35-shapes-v1"^^ . . @@ -8910,28 +9718,34 @@ "model-family"^^ . . . + . "contracts/qwen3moe-e2e-verification-v1.yaml"^^ . "qwen3moe-e2e-verification-v1"^^ . . . + . "contracts/qwen3moe-rope-theta-v1.yaml"^^ . "qwen3moe-rope-theta-v1"^^ . "qwen3moe-rope-theta"^^ . "1.0.0"^^ . . . + . "contracts/qwen3moe-shapes-v1.yaml"^^ . "qwen3moe-shapes-v1"^^ . . . + . "contracts/trueno-rag/rag-pipeline-v1.yaml"^^ . "rag-pipeline-v1"^^ . . . + . "contracts/random-forest-v1.yaml"^^ . "random-forest-v1"^^ . . . + . "contracts/ratatui-migration-v1.yaml"^^ . "ratatui-migration-v1"^^ . . @@ -8946,10 +9760,12 @@ "pattern"^^ . . . + . "contracts/forjar/recipe-determinism-v1.yaml"^^ . "recipe-determinism-v1"^^ . . . + . "contracts/reduce-lr-plateau-v1.yaml"^^ . "reduce-lr-plateau-v1"^^ . "reduce-lr-plateau-patience-strictly-greater"^^ . @@ -8959,6 +9775,7 @@ . "evidence/verbs/refusals.json"^^ . "json"^^ . + "L1"^^ . "contracts/refusal-receipt-v1.yaml"^^ . "refusal-receipt-v1"^^ . "pattern"^^ . @@ -8967,6 +9784,7 @@ "1.0.0"^^ . . . + . "contracts/pacha/registry-integrity-v1.yaml"^^ . "registry-integrity-v1"^^ . . @@ -8976,6 +9794,7 @@ . . "release-evidence"^^ . + "L1"^^ . "contracts/release-readiness-v1.yaml"^^ . "release-readiness-v1"^^ . "pattern"^^ . @@ -8989,30 +9808,62 @@ "pattern"^^ . . . + . "contracts/trueno-viz/render-primitives-v1.yaml"^^ . "render-primitives-v1"^^ . . . + . "contracts/repo-filesystem-v1.yaml"^^ . "repo-filesystem-v1"^^ . . . + . "contracts/trueno-rag/retrieval-quality-v1.yaml"^^ . "retrieval-quality-v1"^^ . + . + . + "contracts/review-corpus-contamination-v1.yaml"^^ . + "review-corpus-contamination-v1"^^ . + "schema"^^ . + . + . + "contracts/review-corpus-v1.yaml"^^ . + "review-corpus-v1"^^ . + "schema"^^ . + . + . + "contracts/review-experiment-receipt-v1.yaml"^^ . + "review-experiment-receipt-v1"^^ . + "schema"^^ . + . + . + "contracts/rex-cell-admission-v1.yaml"^^ . + "rex-cell-admission-v1"^^ . + "schema"^^ . + . + . + "contracts/rex-prereg-v1.yaml"^^ . + "rex-prereg-v1"^^ . + "schema"^^ . . . + . "contracts/rmsnorm-kernel-v1.yaml"^^ . "rmsnorm-kernel-v1"^^ . . . + . "contracts/roofline-model-v1.yaml"^^ . "roofline-model-v1"^^ . . . + . "contracts/rope-extrapolation-v1.yaml"^^ . "rope-extrapolation-v1"^^ . . . + . "contracts/rope-kernel-v1.yaml"^^ . "rope-kernel-v1"^^ . . @@ -9022,110 +9873,133 @@ "model-family"^^ . . . + . "contracts/safetensors-bf16-round-v1.yaml"^^ . "safetensors-bf16-round-v1"^^ . "safetensors-bf16-round"^^ . "1.0.0"^^ . . . + . "contracts/safetensors-cpu-dispatch-v1.yaml"^^ . "safetensors-cpu-dispatch-v1"^^ . . . + . "contracts/safetensors-f16-round-v1.yaml"^^ . "safetensors-f16-round-v1"^^ . "safetensors-f16-round"^^ . "1.0.0"^^ . . . + . "contracts/safetensors-format-safety-v1.yaml"^^ . "safetensors-format-safety-v1"^^ . . . + . "contracts/bashrs/safety-classifier-v1.yaml"^^ . "safety-classifier-v1"^^ . . . + . "contracts/sampling-algorithms-v1.yaml"^^ . "sampling-algorithms-v1"^^ . . . + . "contracts/forjar/sandbox-isolation-v1.yaml"^^ . "sandbox-isolation-v1"^^ . . . + . "contracts/pmat/score-composite-v1.yaml"^^ . "score-composite-v1"^^ . . . + . "contracts/forjar/secret-provider-v1.yaml"^^ . "secret-provider-v1"^^ . . . + . "contracts/depyler/semantic-equivalence-v1.yaml"^^ . "semantic-equivalence-v1"^^ . . . + . "contracts/alimentar/serialization-v1.yaml"^^ . "contracts/serialization-v1.yaml"^^ . "serialization-v1"^^ . . . + . "contracts/serve-batched-gpu-gqa-dispatch-v1.yaml"^^ . "serve-batched-gpu-gqa-dispatch-v1"^^ . "serve-batched-gpu-gqa-dispatch"^^ . "1.0.0"^^ . . . + . "contracts/batuta/session-v1.yaml"^^ . "session-v1"^^ . . . + . "contracts/setfit-apr-v1.yaml"^^ . "setfit-apr-v1"^^ . "kernel"^^ . . . + . "contracts/setfit-encoder-conformance-v1.yaml"^^ . "setfit-encoder-conformance-v1"^^ . . . + . "contracts/sgd-momentum-lrsched-v1.yaml"^^ . "sgd-momentum-lrsched-v1"^^ . "sgd-momentum-lrsched"^^ . "1.0.0"^^ . . . + . "contracts/shannon-entropy-v1.yaml"^^ . "shannon-entropy-v1"^^ . . . + . "contracts/sharded-gguf-merge-v1.yaml"^^ . "sharded-gguf-merge-v1"^^ . "sharded-gguf-merge"^^ . "1.0.0"^^ . . . + . "contracts/sharded-gguf-pull-v1.yaml"^^ . "sharded-gguf-pull-v1"^^ . "sharded-gguf-pull"^^ . "1.0.0"^^ . . . + . "contracts/pzsh/shell-execution-v1.yaml"^^ . "shell-execution-v1"^^ . . . + . "contracts/silhouette-singleton-v1.yaml"^^ . "silhouette-singleton-v1"^^ . "silhouette-singleton-zero"^^ . "1.0.0"^^ . . . + . "contracts/silu-kernel-v1.yaml"^^ . "silu-kernel-v1"^^ . . . + . "contracts/simd-scalar-parity-v1.yaml"^^ . "contracts/trueno/simd-scalar-parity-v1.yaml"^^ . "simd-scalar-parity-v1"^^ . @@ -9133,40 +10007,49 @@ "1.0.0"^^ . . . + . "contracts/simular/simulation-determinism-v1.yaml"^^ . "simulation-determinism-v1"^^ . . . + . "contracts/simular/simulation-step-v1.yaml"^^ . "simulation-step-v1"^^ . . . + . "contracts/sliding-window-attention-v1.yaml"^^ . "sliding-window-attention-v1"^^ . . . + . "contracts/softmax-kernel-v1.yaml"^^ . "softmax-kernel-v1"^^ . . . + . "contracts/entrenar/sovereign-tensor-v1.yaml"^^ . "sovereign-tensor-v1"^^ . . . + . "contracts/sparse-spmv-v1.yaml"^^ . "sparse-spmv-v1"^^ . "sparse-spmv"^^ . "1.0.0"^^ . . . + . "contracts/special-tokens-registry-v1.yaml"^^ . "special-tokens-registry-v1"^^ . . . + . "contracts/speculative-decoding-v1.yaml"^^ . "speculative-decoding-v1"^^ . . . + . "contracts/ssm-kernel-v1.yaml"^^ . "ssm-kernel-v1"^^ . . @@ -9181,32 +10064,39 @@ "model-family"^^ . . . + . "contracts/pmat/state-machine-v1.yaml"^^ . "state-machine-v1"^^ . . . + . "contracts/forjar/store-cas-v1.yaml"^^ . "store-cas-v1"^^ . . . + . "contracts/stratified-kfold-balance-v1.yaml"^^ . "stratified-kfold-balance-v1"^^ . "stratified-kfold-balance"^^ . "1.0.0"^^ . . . + . "contracts/streaming-tpot-v1.yaml"^^ . "streaming-tpot-v1"^^ . . . + . "contracts/svc-rbf-v1.yaml"^^ . "svc-rbf-v1"^^ . . . + . "contracts/svm-v1.yaml"^^ . "svm-v1"^^ . . . + . "contracts/swiglu-kernel-v1.yaml"^^ . "swiglu-kernel-v1"^^ . . @@ -9216,10 +10106,12 @@ "pattern"^^ . . . + . "contracts/forjar/task-pipeline-v1.yaml"^^ . "task-pipeline-v1"^^ . . . + . "contracts/pmat/tdg-scoring-v1.yaml"^^ . "contracts/tdg-scoring-v1.yaml"^^ . "tdg-scoring-v1"^^ . @@ -9227,31 +10119,38 @@ "1.0.0"^^ . . . + . "contracts/tensor-inventory-v1.yaml"^^ . "tensor-inventory-v1"^^ . . . + . "contracts/aprender/tensor-layout-v1.yaml"^^ . "contracts/tensor-layout-v1.yaml"^^ . "tensor-layout-v1"^^ . . . + . "contracts/tensor-names-v1.yaml"^^ . "tensor-names-v1"^^ . . . + . "contracts/entrenar/tensor-rc-data-v1.yaml"^^ . "tensor-rc-data-v1"^^ . . . + . "contracts/tensor-shape-flow-v1.yaml"^^ . "tensor-shape-flow-v1"^^ . . . + . "contracts/tensor-transpose-roundtrip-v1.yaml"^^ . "tensor-transpose-roundtrip-v1"^^ . . . + . "contracts/tfidf-l2-norm-v1.yaml"^^ . "tfidf-l2-norm-v1"^^ . "tfidf-l2-norm"^^ . @@ -9268,10 +10167,12 @@ "pattern"^^ . . . + . "contracts/tied-embeddings-v1.yaml"^^ . "tied-embeddings-v1"^^ . . . + . "contracts/trueno/tiled-matmul-shader-v1.yaml"^^ . "tiled-matmul-shader-v1"^^ . . @@ -9283,11 +10184,13 @@ "1.2.0"^^ . . . + . "contracts/aprender/tokenizer-loading-v1.yaml"^^ . "contracts/tokenizer-loading-v1.yaml"^^ . "tokenizer-loading-v1"^^ . . . + . "contracts/batuta/tokenizer-v1.yaml"^^ . "contracts/tokenizer-v1.yaml"^^ . "tokenizer-v1"^^ . @@ -9295,6 +10198,7 @@ "1.1.0"^^ . . . + . "contracts/tokenizer-vocab-v1.yaml"^^ . "tokenizer-vocab-v1"^^ . . @@ -9309,11 +10213,13 @@ "pattern"^^ . . . + . "contracts/trace-ffn-sub-block-v1.yaml"^^ . "trace-ffn-sub-block-v1"^^ . "PROPOSED"^^ . . . + . "contracts/renacer/trace-integrity-v1.yaml"^^ . "trace-integrity-v1"^^ . . @@ -9323,6 +10229,7 @@ "pattern"^^ . . . + . "contracts/pmat/tracing-observability-v1.yaml"^^ . "contracts/tracing-observability-v1.yaml"^^ . "tracing-observability-v1"^^ . @@ -9330,12 +10237,14 @@ "1.0.0"^^ . . . + . "contracts/train-test-split-ceil-v1.yaml"^^ . "train-test-split-ceil-v1"^^ . "train-test-split-ceil"^^ . "1.0.0"^^ . . . + . "contracts/trainer-grad-clip-v1.yaml"^^ . "trainer-grad-clip-v1"^^ . "trainer-grad-clip"^^ . @@ -9349,15 +10258,18 @@ "1.5.0"^^ . . . + . "contracts/aprender/training-loop-v1.yaml"^^ . "contracts/training-loop-v1.yaml"^^ . "training-loop-v1"^^ . . . + . "contracts/entrenar/training-step-profiling-v1.yaml"^^ . "training-step-profiling-v1"^^ . . . + . "contracts/probar/training-step-scorecard-v1.yaml"^^ . "training-step-scorecard-v1"^^ . . @@ -9369,10 +10281,12 @@ "1"^^ . . . + . "contracts/decy/transpile-pipeline-v1.yaml"^^ . "transpile-pipeline-v1"^^ . . . + . "contracts/ruchy/transpile-soundness-v1.yaml"^^ . "transpile-soundness-v1"^^ . . @@ -9382,78 +10296,95 @@ "pattern"^^ . . . + . "contracts/transpose-kernel-v1.yaml"^^ . "transpose-kernel-v1"^^ . . . + . "contracts/tree-feature-importances-mdi-v1.yaml"^^ . "tree-feature-importances-mdi-v1"^^ . "tree-feature-importances-mdi"^^ . "1.0.0"^^ . . . + . "contracts/trueno-f16-rne-v1.yaml"^^ . "trueno-f16-rne-v1"^^ . "trueno-f16-rne"^^ . "1.0.0"^^ . . . + . "contracts/ttest-exact-pvalue-v1.yaml"^^ . "ttest-exact-pvalue-v1"^^ . "ttest-exact-pvalue"^^ . "1.0.0"^^ . . . + . "contracts/presentar/tui-lifecycle-v1.yaml"^^ . "tui-lifecycle-v1"^^ . . . + . "contracts/presentar/tui-panels-v1.yaml"^^ . "tui-panels-v1"^^ . . . + . "contracts/tui-rendering-ux-v1.yaml"^^ . "tui-rendering-ux-v1"^^ . . . + . "contracts/presentar/tui-rendering-v1.yaml"^^ . "tui-rendering-v1"^^ . . . + . "contracts/depyler/type-preservation-v1.yaml"^^ . "type-preservation-v1"^^ . . . + . "contracts/unified-specs-v1.yaml"^^ . "unified-specs-v1"^^ . . . + . "contracts/validated-tensor-v1.yaml"^^ . "validated-tensor-v1"^^ . . . + . "contracts/verificar/verification-engine-v1.yaml"^^ . "verification-engine-v1"^^ . . . + . "contracts/trueno-viz/visualization-render-v1.yaml"^^ . "visualization-render-v1"^^ . . . + . "contracts/ward-linkage-v1.yaml"^^ . "ward-linkage-v1"^^ . "ward-linkage"^^ . "1.0.0"^^ . . . + . "contracts/wasmtime-upgrade-v1.yaml"^^ . "wasmtime-upgrade-v1"^^ . . . + . "contracts/entrenar/wgpu-production-training-v1.yaml"^^ . "wgpu-production-training-v1"^^ . . . + . "contracts/entrenar/wgpu-resident-weights-v1.yaml"^^ . "wgpu-resident-weights-v1"^^ . . @@ -9463,22 +10394,7421 @@ "model-family"^^ . . . + . "contracts/pmat/work-dbc-v1.yaml"^^ . "contracts/work-dbc-v1.yaml"^^ . "work-dbc-v1"^^ . "pattern"^^ . . . + . "contracts/xtc-sampling-correctness-v1.yaml"^^ . "xtc-sampling-correctness-v1"^^ . "xtc-sampling-correctness"^^ . "1.0.0"^^ . . . + . "contracts/yarn-rope-original-base-v1.yaml"^^ . "yarn-rope-original-base-v1"^^ . "yarn-rope-original-base"^^ . "1.0.0"^^ . + . + . + "apr-cli"^^ . + "crates/apr-cli/examples/gpu_chat_inference.rs"^^ . + "false"^^ . + "gpu_chat_inference"^^ . + "qwen2.5"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "apr-cli"^^ . + "crates/apr-cli/examples/ptx_parity_validation.rs"^^ . + "false"^^ . + "ptx_parity_validation"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "apr-cli"^^ . + "crates/apr-cli/examples/serve_with_tracing.rs"^^ . + "true"^^ . + "serve_with_tracing"^^ . + "false"^^ . + . + . + "apr-cli"^^ . + "crates/apr-cli/examples/tool_calling_demo.rs"^^ . + "true"^^ . + "tool_calling_demo"^^ . + "false"^^ . + . + . + "aprender-cbtop"^^ . + "crates/aprender-cbtop/examples/adaptive_ml_demo.rs"^^ . + "true"^^ . + "adaptive_ml_demo"^^ . + "false"^^ . + . + . + "aprender-cbtop"^^ . + "crates/aprender-cbtop/examples/federated_metrics_demo.rs"^^ . + "true"^^ . + "federated_metrics_demo"^^ . + "false"^^ . + . + . + "aprender-cbtop"^^ . + "crates/aprender-cbtop/examples/incremental_snapshot_demo.rs"^^ . + "true"^^ . + "incremental_snapshot_demo"^^ . + "false"^^ . + . + . + "aprender-cbtop"^^ . + "crates/aprender-cbtop/examples/predictive_scheduler_demo.rs"^^ . + "true"^^ . + "predictive_scheduler_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/activation_functions.rs"^^ . + "true"^^ . + "activation_functions"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/b4_fault_simulation.rs"^^ . + "true"^^ . + "b4_fault_simulation"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/backend_detection.rs"^^ . + "true"^^ . + "backend_detection"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/bench_profiling_primitives.rs"^^ . + "true"^^ . + "bench_profiling_primitives"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/benchmark_matrix_suite.rs"^^ . + "false"^^ . + "benchmark_matrix_suite"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/benchmark_matvec.rs"^^ . + "true"^^ . + "benchmark_matvec"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/benchmark_matvec_parallel.rs"^^ . + "true"^^ . + "benchmark_matvec_parallel"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/benchmark_parallel.rs"^^ . + "true"^^ . + "benchmark_parallel"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/blis_benchmark.rs"^^ . + "true"^^ . + "blis_benchmark"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/brick_profiler_v2.rs"^^ . + "true"^^ . + "brick_profiler_v2"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/design_by_contract.rs"^^ . + "true"^^ . + "design_by_contract"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/execution_graph.rs"^^ . + "true"^^ . + "execution_graph"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/gpu_batch_demo.rs"^^ . + "true"^^ . + "gpu_batch_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/gpu_monitor_demo.rs"^^ . + "true"^^ . + "gpu_monitor_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/gpu_tiled_reduction.rs"^^ . + "true"^^ . + "gpu_tiled_reduction"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/hash_demo.rs"^^ . + "true"^^ . + "hash_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/inference_demo.rs"^^ . + "false"^^ . + "inference_demo"^^ . + "llama"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/matrix_operations.rs"^^ . + "true"^^ . + "matrix_operations"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/ml_similarity.rs"^^ . + "true"^^ . + "ml_similarity"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/ml_tuner_demo.rs"^^ . + "false"^^ . + "ml_tuner_demo"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/ml_tuner_evolution.rs"^^ . + "true"^^ . + "ml_tuner_evolution"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/model_tracing.rs"^^ . + "true"^^ . + "model_tracing"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/perf_tui.rs"^^ . + "false"^^ . + "perf_tui"^^ . + "gpt2"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/performance_demo.rs"^^ . + "true"^^ . + "performance_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/profile_vocab.rs"^^ . + "true"^^ . + "profile_vocab"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/quickstart.rs"^^ . + "true"^^ . + "quickstart"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/regression_test.rs"^^ . + "true"^^ . + "regression_test"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/simd_comparison.rs"^^ . + "true"^^ . + "simd_comparison"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/simd_softmax_quant.rs"^^ . + "true"^^ . + "simd_softmax_quant"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/symmetric_eigen.rs"^^ . + "true"^^ . + "symmetric_eigen"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/tile_profiler_demo.rs"^^ . + "true"^^ . + "tile_profiler_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/tiled_reduction_demo.rs"^^ . + "true"^^ . + "tiled_reduction_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/tiling_demo.rs"^^ . + "true"^^ . + "tiling_demo"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/tuner_usage.rs"^^ . + "true"^^ . + "tuner_usage"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/vocab_bench.rs"^^ . + "true"^^ . + "vocab_bench"^^ . + "false"^^ . + . + . + "aprender-compute"^^ . + "crates/aprender-compute/examples/wgpu_backward_demo.rs"^^ . + "true"^^ . + "wgpu_backward_demo"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/audit.rs"^^ . + "true"^^ . + "audit"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/codegen.rs"^^ . + "true"^^ . + "codegen"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/coq.rs"^^ . + "true"^^ . + "coq"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/coverage.rs"^^ . + "true"^^ . + "coverage"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/cross_project_query.rs"^^ . + "true"^^ . + "cross_project_query"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/design_by_contract.rs"^^ . + "false"^^ . + "design_by_contract"^^ . + "llama"^^ . + "mistral"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/doc_integrity.rs"^^ . + "true"^^ . + "doc_integrity"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/explain.rs"^^ . + "true"^^ . + "explain"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/extract_pytorch.rs"^^ . + "true"^^ . + "extract_pytorch"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/flux.rs"^^ . + "true"^^ . + "flux"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/fuzz.rs"^^ . + "true"^^ . + "fuzz"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/infrastructure_contracts.rs"^^ . + "true"^^ . + "infrastructure_contracts"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/invariants.rs"^^ . + "true"^^ . + "invariants"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/lean_codegen.rs"^^ . + "true"^^ . + "lean_codegen"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/lean_proofs.rs"^^ . + "true"^^ . + "lean_proofs"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/lean_status.rs"^^ . + "true"^^ . + "lean_status"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/lint.rs"^^ . + "true"^^ . + "lint"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/mirai.rs"^^ . + "true"^^ . + "mirai"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/pipeline.rs"^^ . + "true"^^ . + "pipeline"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/proof_status.rs"^^ . + "true"^^ . + "proof_status"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/query.rs"^^ . + "true"^^ . + "query"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/scaffold.rs"^^ . + "true"^^ . + "scaffold"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/scaffold_generation.rs"^^ . + "true"^^ . + "scaffold_generation"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/score.rs"^^ . + "true"^^ . + "score"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/score_contracts.rs"^^ . + "true"^^ . + "score_contracts"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/tla.rs"^^ . + "true"^^ . + "tla"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/validate.rs"^^ . + "true"^^ . + "validate"^^ . + "false"^^ . + . + . + "aprender-contracts"^^ . + "crates/aprender-contracts/examples/validate_contracts.rs"^^ . + "true"^^ . + "validate_contracts"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/aco_tsp.rs"^^ . + "true"^^ . + "aco_tsp"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/admm_optimization.rs"^^ . + "true"^^ . + "admm_optimization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/advanced_merge.rs"^^ . + "true"^^ . + "advanced_merge"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_cache.rs"^^ . + "true"^^ . + "apr_cache"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_checkpoint_lifecycle.rs"^^ . + "false"^^ . + "apr_checkpoint_lifecycle"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_cli_commands.rs"^^ . + "false"^^ . + "apr_cli_commands"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_embed.rs"^^ . + "true"^^ . + "apr_embed"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_inspection.rs"^^ . + "true"^^ . + "apr_inspection"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_loading_modes.rs"^^ . + "true"^^ . + "apr_loading_modes"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_scoring.rs"^^ . + "true"^^ . + "apr_scoring"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/apr_with_metadata.rs"^^ . + "true"^^ . + "apr_with_metadata"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/automl_clustering.rs"^^ . + "true"^^ . + "automl_clustering"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/batch_optimization.rs"^^ . + "true"^^ . + "batch_optimization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/bayesian_blocks_histogram.rs"^^ . + "true"^^ . + "bayesian_blocks_histogram"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/bayesian_linear_regression.rs"^^ . + "true"^^ . + "bayesian_linear_regression"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/bayesian_logistic_regression.rs"^^ . + "true"^^ . + "bayesian_logistic_regression"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/bench_bpe.rs"^^ . + "true"^^ . + "bench_bpe"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/bench_comparison.rs"^^ . + "false"^^ . + "bench_comparison"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/beta_binomial_inference.rs"^^ . + "true"^^ . + "beta_binomial_inference"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/boston_housing.rs"^^ . + "true"^^ . + "boston_housing"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/bundle_trace_demo.rs"^^ . + "true"^^ . + "bundle_trace_demo"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch01_hello_apr.rs"^^ . + "true"^^ . + "ch01_hello_apr"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch02_tensors.rs"^^ . + "true"^^ . + "ch02_tensors"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch03_apr_format.rs"^^ . + "true"^^ . + "ch03_apr_format"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch04_supervised.rs"^^ . + "true"^^ . + "ch04_supervised"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch05_unsupervised.rs"^^ . + "true"^^ . + "ch05_unsupervised"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch06_ensembles.rs"^^ . + "true"^^ . + "ch06_ensembles"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch07_model_selection.rs"^^ . + "true"^^ . + "ch07_model_selection"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch08_transformer.rs"^^ . + "false"^^ . + "ch08_transformer"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch09_inference.rs"^^ . + "true"^^ . + "ch09_inference"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch10_training.rs"^^ . + "true"^^ . + "ch10_training"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch11_formats.rs"^^ . + "true"^^ . + "ch11_formats"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch12_serving.rs"^^ . + "true"^^ . + "ch12_serving"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch13_profiling.rs"^^ . + "true"^^ . + "ch13_profiling"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch14_contracts.rs"^^ . + "true"^^ . + "ch14_contracts"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch15_orchestrate.rs"^^ . + "true"^^ . + "ch15_orchestrate"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch16_timeseries.rs"^^ . + "true"^^ . + "ch16_timeseries"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch17_bayesian.rs"^^ . + "true"^^ . + "ch17_bayesian"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch18_graphs.rs"^^ . + "true"^^ . + "ch18_graphs"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch19_text.rs"^^ . + "true"^^ . + "ch19_text"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch20_rag.rs"^^ . + "true"^^ . + "ch20_rag"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch21_vs_candle.rs"^^ . + "false"^^ . + "ch21_vs_candle"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch22_vs_llamacpp.rs"^^ . + "false"^^ . + "ch22_vs_llamacpp"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch23_training_bench.rs"^^ . + "false"^^ . + "ch23_training_bench"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch24_switch_pytorch.rs"^^ . + "true"^^ . + "ch24_switch_pytorch"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch25_switch_ollama.rs"^^ . + "false"^^ . + "ch25_switch_ollama"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch26_switch_ndarray.rs"^^ . + "true"^^ . + "ch26_switch_ndarray"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/ch27_switch_unsloth.rs"^^ . + "false"^^ . + "ch27_switch_unsloth"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/chat_template.rs"^^ . + "false"^^ . + "chat_template"^^ . + "llama"^^ . + "mistral"^^ . + "phi"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/classification_training.rs"^^ . + "true"^^ . + "classification_training"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/code_analysis.rs"^^ . + "true"^^ . + "code_analysis"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/community_detection.rs"^^ . + "true"^^ . + "community_detection"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/constrained_optimization.rs"^^ . + "true"^^ . + "constrained_optimization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/continual_pretraining.rs"^^ . + "true"^^ . + "continual_pretraining"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/conv_layout_dogfood.rs"^^ . + "true"^^ . + "conv_layout_dogfood"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/convex_optimization.rs"^^ . + "true"^^ . + "convex_optimization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/create_test_apr.rs"^^ . + "true"^^ . + "create_test_apr"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/create_test_transformer_apr.rs"^^ . + "false"^^ . + "create_test_transformer_apr"^^ . + "llama"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/cross_validation.rs"^^ . + "true"^^ . + "cross_validation"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/cuda_backend.rs"^^ . + "true"^^ . + "cuda_backend"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/dam_merge.rs"^^ . + "true"^^ . + "dam_merge"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/data_preprocessing_scalers.rs"^^ . + "true"^^ . + "data_preprocessing_scalers"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/data_quality_pipeline.rs"^^ . + "true"^^ . + "data_quality_pipeline"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/dataframe_basics.rs"^^ . + "true"^^ . + "dataframe_basics"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/dbscan_clustering.rs"^^ . + "true"^^ . + "dbscan_clustering"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/decision_tree_iris.rs"^^ . + "true"^^ . + "decision_tree_iris"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/decision_tree_regression.rs"^^ . + "true"^^ . + "decision_tree_regression"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/descriptive_statistics.rs"^^ . + "true"^^ . + "descriptive_statistics"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/design_by_contract.rs"^^ . + "true"^^ . + "design_by_contract"^^ . + "qwen3.5"^^ . + "true"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/dirichlet_multinomial_inference.rs"^^ . + "true"^^ . + "dirichlet_multinomial_inference"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/distillation_advanced.rs"^^ . + "true"^^ . + "distillation_advanced"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/dpo_preference.rs"^^ . + "true"^^ . + "dpo_preference"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/eval_harness.rs"^^ . + "true"^^ . + "eval_harness"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/evolutionary_merge.rs"^^ . + "true"^^ . + "evolutionary_merge"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/gamma_poisson_inference.rs"^^ . + "true"^^ . + "gamma_poisson_inference"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/gbm_iris.rs"^^ . + "true"^^ . + "gbm_iris"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/gmm_clustering.rs"^^ . + "true"^^ . + "gmm_clustering"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/gpu_fallback_dogfood.rs"^^ . + "false"^^ . + "gpu_fallback_dogfood"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/graph_algorithms_comprehensive.rs"^^ . + "true"^^ . + "graph_algorithms_comprehensive"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/graph_social_network.rs"^^ . + "true"^^ . + "graph_social_network"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/grid_search_tuning.rs"^^ . + "true"^^ . + "grid_search_tuning"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/hex_forensics.rs"^^ . + "true"^^ . + "hex_forensics"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/hierarchical_clustering.rs"^^ . + "true"^^ . + "hierarchical_clustering"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/iris_clustering.rs"^^ . + "true"^^ . + "iris_clustering"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/isolation_forest_anomaly.rs"^^ . + "true"^^ . + "isolation_forest_anomaly"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/knn_iris.rs"^^ . + "true"^^ . + "knn_iris"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/layout_guard_probe.rs"^^ . + "true"^^ . + "layout_guard_probe"^^ . + "qwen2"^^ . + "qwen3"^^ . + "qwen3.5"^^ . + "true"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/lof_anomaly.rs"^^ . + "true"^^ . + "lof_anomaly"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/logic_family_tree.rs"^^ . + "true"^^ . + "logic_family_tree"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/logistic_regression.rs"^^ . + "true"^^ . + "logistic_regression"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/lottery_ticket_pruning.rs"^^ . + "true"^^ . + "lottery_ticket_pruning"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/market_basket_apriori.rs"^^ . + "true"^^ . + "market_basket_apriori"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/mem_test.rs"^^ . + "false"^^ . + "mem_test"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/metaheuristics_optimization.rs"^^ . + "true"^^ . + "metaheuristics_optimization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/mixture_of_experts.rs"^^ . + "true"^^ . + "mixture_of_experts"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/model_merge_strategies.rs"^^ . + "true"^^ . + "model_merge_strategies"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/model_serialization.rs"^^ . + "true"^^ . + "model_serialization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/model_zoo.rs"^^ . + "true"^^ . + "model_zoo"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/moe_construction.rs"^^ . + "true"^^ . + "moe_construction"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/naive_bayes_iris.rs"^^ . + "true"^^ . + "naive_bayes_iris"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/negative_binomial_glm.rs"^^ . + "true"^^ . + "negative_binomial_glm"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/neural_network_training.rs"^^ . + "true"^^ . + "neural_network_training"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/nlp_advanced.rs"^^ . + "true"^^ . + "nlp_advanced"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/normal_inverse_gamma_inference.rs"^^ . + "true"^^ . + "normal_inverse_gamma_inference"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/online_learning.rs"^^ . + "true"^^ . + "online_learning"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/optimizer_demo.rs"^^ . + "true"^^ . + "optimizer_demo"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/pca_iris.rs"^^ . + "true"^^ . + "pca_iris"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/per_layer_merge.rs"^^ . + "false"^^ . + "per_layer_merge"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/phi_hf_import.rs"^^ . + "false"^^ . + "phi_hf_import"^^ . + "llama"^^ . + "phi"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/pii_filtering.rs"^^ . + "true"^^ . + "pii_filtering"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/pipeline_verification.rs"^^ . + "true"^^ . + "pipeline_verification"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/poka_yoke_validation.rs"^^ . + "true"^^ . + "poka_yoke_validation"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/predator_prey_optimization.rs"^^ . + "true"^^ . + "predator_prey_optimization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/pruning_magnitude.rs"^^ . + "true"^^ . + "pruning_magnitude"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/publish_shell_safety.rs"^^ . + "false"^^ . + "publish_shell_safety"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/qa_chat.rs"^^ . + "false"^^ . + "qa_chat"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/qa_falsify.rs"^^ . + "false"^^ . + "qa_falsify"^^ . + "gpt2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/qa_run.rs"^^ . + "false"^^ . + "qa_run"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/qa_serve.rs"^^ . + "false"^^ . + "qa_serve"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/qa_verify.rs"^^ . + "true"^^ . + "qa_verify"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/qwen_apr_native.rs"^^ . + "false"^^ . + "qwen_apr_native"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/qwen_chat.rs"^^ . + "false"^^ . + "qwen_chat"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/random_forest_iris.rs"^^ . + "true"^^ . + "random_forest_iris"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/random_forest_regression.rs"^^ . + "true"^^ . + "random_forest_regression"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/recommend_content.rs"^^ . + "true"^^ . + "recommend_content"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/regularized_regression.rs"^^ . + "true"^^ . + "regularized_regression"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/rlvr.rs"^^ . + "true"^^ . + "rlvr"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/rosetta_stone.rs"^^ . + "true"^^ . + "rosetta_stone"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/shell_encryption_demo.rs"^^ . + "true"^^ . + "shell_encryption_demo"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/shell_model_format.rs"^^ . + "true"^^ . + "shell_model_format"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/shell_safety_inference.rs"^^ . + "true"^^ . + "shell_safety_inference"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/shell_safety_training.rs"^^ . + "true"^^ . + "shell_safety_training"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/showcase_benchmark.rs"^^ . + "false"^^ . + "showcase_benchmark"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/sovereign_offline.rs"^^ . + "false"^^ . + "sovereign_offline"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/sovereign_stack.rs"^^ . + "true"^^ . + "sovereign_stack"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/spectral_clustering.rs"^^ . + "true"^^ . + "spectral_clustering"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/svm_iris.rs"^^ . + "true"^^ . + "svm_iris"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/synthetic_data_generation.rs"^^ . + "true"^^ . + "synthetic_data_generation"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/tabu_tsp.rs"^^ . + "true"^^ . + "tabu_tsp"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/text_classification.rs"^^ . + "true"^^ . + "text_classification"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/text_preprocessing.rs"^^ . + "true"^^ . + "text_preprocessing"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/time_series_forecasting.rs"^^ . + "true"^^ . + "time_series_forecasting"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/tokenizer_surgery.rs"^^ . + "true"^^ . + "tokenizer_surgery"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/topic_sentiment_analysis.rs"^^ . + "true"^^ . + "topic_sentiment_analysis"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/trueno_compute_integration.rs"^^ . + "true"^^ . + "trueno_compute_integration"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/tsne_visualization.rs"^^ . + "true"^^ . + "tsne_visualization"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/validated_tensors.rs"^^ . + "true"^^ . + "validated_tensors"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/whisper_transcribe.rs"^^ . + "true"^^ . + "whisper_transcribe"^^ . + "false"^^ . + . + . + "aprender-core"^^ . + "crates/aprender-core/examples/xor_training.rs"^^ . + "true"^^ . + "xor_training"^^ . + "false"^^ . + . + . + "aprender-cuda-edge"^^ . + "crates/aprender-cuda-edge/examples/falsification_report_demo.rs"^^ . + "true"^^ . + "falsification_report_demo"^^ . + "false"^^ . + . + . + "aprender-cuda-edge"^^ . + "crates/aprender-cuda-edge/examples/null_fuzzer_demo.rs"^^ . + "true"^^ . + "null_fuzzer_demo"^^ . + "false"^^ . + . + . + "aprender-cuda-edge"^^ . + "crates/aprender-cuda-edge/examples/ptx_verifier_demo.rs"^^ . + "true"^^ . + "ptx_verifier_demo"^^ . + "false"^^ . + . + . + "aprender-cuda-edge"^^ . + "crates/aprender-cuda-edge/examples/quant_oracle_demo.rs"^^ . + "true"^^ . + "quant_oracle_demo"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/basic_loading.rs"^^ . + "true"^^ . + "basic_loading"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/cli_batch_commands.rs"^^ . + "true"^^ . + "cli_batch_commands"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/dataloader_batching.rs"^^ . + "true"^^ . + "dataloader_batching"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/doctest_extraction.rs"^^ . + "true"^^ . + "doctest_extraction"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/drift_detection.rs"^^ . + "true"^^ . + "drift_detection"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/federated_split.rs"^^ . + "true"^^ . + "federated_split"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/generate_fixtures.rs"^^ . + "true"^^ . + "generate_fixtures"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/hub_publishing.rs"^^ . + "true"^^ . + "hub_publishing"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/prose_detection.rs"^^ . + "true"^^ . + "prose_detection"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/quality_check.rs"^^ . + "true"^^ . + "quality_check"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/registry_publish.rs"^^ . + "true"^^ . + "registry_publish"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/repl_commands.rs"^^ . + "true"^^ . + "repl_commands"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/repl_completer.rs"^^ . + "true"^^ . + "repl_completer"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/repl_display_config.rs"^^ . + "true"^^ . + "repl_display_config"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/repl_health_status.rs"^^ . + "true"^^ . + "repl_health_status"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/repl_session.rs"^^ . + "true"^^ . + "repl_session"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/streaming_large.rs"^^ . + "true"^^ . + "streaming_large"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/transforms_pipeline.rs"^^ . + "true"^^ . + "transforms_pipeline"^^ . + "false"^^ . + . + . + "aprender-data"^^ . + "crates/aprender-data/examples/tui_viewer.rs"^^ . + "true"^^ . + "tui_viewer"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/backend_selection.rs"^^ . + "true"^^ . + "backend_selection"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/basic_usage.rs"^^ . + "true"^^ . + "basic_usage"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/benchmark_shootout.rs"^^ . + "true"^^ . + "benchmark_shootout"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/complete_pipeline.rs"^^ . + "true"^^ . + "complete_pipeline"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/compressed_kv.rs"^^ . + "true"^^ . + "compressed_kv"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/experiment_tracking.rs"^^ . + "true"^^ . + "experiment_tracking"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/gaming_leaderboards.rs"^^ . + "true"^^ . + "gaming_leaderboards"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/gpu_aggregations.rs"^^ . + "true"^^ . + "gpu_aggregations"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/gpu_sales_analytics.rs"^^ . + "true"^^ . + "gpu_sales_analytics"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/kv_store.rs"^^ . + "true"^^ . + "kv_store"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/market_crashes.rs"^^ . + "true"^^ . + "market_crashes"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/simd_acceleration.rs"^^ . + "true"^^ . + "simd_acceleration"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/sql_query_interface.rs"^^ . + "true"^^ . + "sql_query_interface"^^ . + "false"^^ . + . + . + "aprender-db"^^ . + "crates/aprender-db/examples/topk_selection.rs"^^ . + "true"^^ . + "topk_selection"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/checkpoint_example.rs"^^ . + "true"^^ . + "checkpoint_example"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/gpu_detect.rs"^^ . + "true"^^ . + "gpu_detect"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/hello_repartir.rs"^^ . + "true"^^ . + "hello_repartir"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/pubsub_example.rs"^^ . + "true"^^ . + "pubsub_example"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/pushpull_example.rs"^^ . + "true"^^ . + "pushpull_example"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/tensor_example.rs"^^ . + "true"^^ . + "tensor_example"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/test_mac_worker.rs"^^ . + "true"^^ . + "test_mac_worker"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/tls_example.rs"^^ . + "true"^^ . + "tls_example"^^ . + "false"^^ . + . + . + "aprender-distribute"^^ . + "crates/aprender-distribute/examples/v1_1_showcase.rs"^^ . + "true"^^ . + "v1_1_showcase"^^ . + "false"^^ . + . + . + "aprender-explain"^^ . + "crates/aprender-explain/examples/analyze_realizar.rs"^^ . + "true"^^ . + "analyze_realizar"^^ . + "false"^^ . + . + . + "aprender-explain"^^ . + "crates/aprender-explain/examples/deep_bug_hunt.rs"^^ . + "true"^^ . + "deep_bug_hunt"^^ . + "false"^^ . + . + . + "aprender-explain"^^ . + "crates/aprender-explain/examples/ptx_inspector.rs"^^ . + "true"^^ . + "ptx_inspector"^^ . + "false"^^ . + . + . + "aprender-fft"^^ . + "crates/aprender-fft/examples/fft_demo.rs"^^ . + "true"^^ . + "fft_demo"^^ . + "false"^^ . + . + . + "aprender-graph"^^ . + "crates/aprender-graph/examples/comprehensive_demo.rs"^^ . + "true"^^ . + "comprehensive_demo"^^ . + "false"^^ . + . + . + "aprender-graph"^^ . + "crates/aprender-graph/examples/graph_algorithms.rs"^^ . + "true"^^ . + "graph_algorithms"^^ . + "false"^^ . + . + . + "aprender-graph"^^ . + "crates/aprender-graph/examples/paging_demo.rs"^^ . + "true"^^ . + "paging_demo"^^ . + "false"^^ . + . + . + "aprender-graph"^^ . + "crates/aprender-graph/examples/simple_graph.rs"^^ . + "true"^^ . + "simple_graph"^^ . + "false"^^ . + . + . + "aprender-image"^^ . + "crates/aprender-image/examples/image_demo.rs"^^ . + "true"^^ . + "image_demo"^^ . + "false"^^ . + . + . + "aprender-monte-carlo"^^ . + "crates/aprender-monte-carlo/examples/aprender-monte-carlo.rs"^^ . + "true"^^ . + "aprender-monte-carlo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/agent_contracts.rs"^^ . + "true"^^ . + "agent_contracts"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/agent_demo.rs"^^ . + "false"^^ . + "agent_demo"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/agent_guard.rs"^^ . + "true"^^ . + "agent_guard"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/agent_memory.rs"^^ . + "false"^^ . + "agent_memory"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/agent_pool.rs"^^ . + "true"^^ . + "agent_pool"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/agent_routing.rs"^^ . + "true"^^ . + "agent_routing"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/agent_signing.rs"^^ . + "false"^^ . + "agent_signing"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/backend_selection.rs"^^ . + "true"^^ . + "backend_selection"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/bug_hunter_demo.rs"^^ . + "true"^^ . + "bug_hunter_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/content_demo.rs"^^ . + "true"^^ . + "content_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/custom_plugin.rs"^^ . + "true"^^ . + "custom_plugin"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/design_by_contract.rs"^^ . + "true"^^ . + "design_by_contract"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/full_transpilation.rs"^^ . + "true"^^ . + "full_transpilation"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/graph_tui_demo.rs"^^ . + "true"^^ . + "graph_tui_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/hf_catalog_demo.rs"^^ . + "true"^^ . + "hf_catalog_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/int8_rescore_demo.rs"^^ . + "true"^^ . + "int8_rescore_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/mcp_demo.rs"^^ . + "false"^^ . + "mcp_demo"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/moe_routing.rs"^^ . + "true"^^ . + "moe_routing"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/multi_machine_demo.rs"^^ . + "true"^^ . + "multi_machine_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/numpy_conversion.rs"^^ . + "true"^^ . + "numpy_conversion"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/oracle_demo.rs"^^ . + "true"^^ . + "oracle_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/oracle_local_demo.rs"^^ . + "true"^^ . + "oracle_local_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/parf_analysis.rs"^^ . + "true"^^ . + "parf_analysis"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/pepita_kernel_demo.rs"^^ . + "true"^^ . + "pepita_kernel_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/pipeline_demo.rs"^^ . + "true"^^ . + "pipeline_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/playbook_demo.rs"^^ . + "true"^^ . + "playbook_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/pmat_query_demo.rs"^^ . + "true"^^ . + "pmat_query_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/publish_status_demo.rs"^^ . + "true"^^ . + "publish_status_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/pytorch_conversion.rs"^^ . + "false"^^ . + "pytorch_conversion"^^ . + "gpt2"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/rag_oracle_demo.rs"^^ . + "true"^^ . + "rag_oracle_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/rag_profiling_demo.rs"^^ . + "true"^^ . + "rag_profiling_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/repartir_distributed.rs"^^ . + "true"^^ . + "repartir_distributed"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/serve_demo.rs"^^ . + "false"^^ . + "serve_demo"^^ . + "llama"^^ . + "mistral"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/sklearn_conversion.rs"^^ . + "true"^^ . + "sklearn_conversion"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/sovereign_stack_e2e.rs"^^ . + "false"^^ . + "sovereign_stack_e2e"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/stack_comply_demo.rs"^^ . + "true"^^ . + "stack_comply_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/stack_diagnostics_demo.rs"^^ . + "true"^^ . + "stack_diagnostics_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/stack_dogfood.rs"^^ . + "true"^^ . + "stack_dogfood"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/stack_graph_tui.rs"^^ . + "true"^^ . + "stack_graph_tui"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/stack_quality_demo.rs"^^ . + "true"^^ . + "stack_quality_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/svg_generation_demo.rs"^^ . + "true"^^ . + "svg_generation_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/trueno_ublk_demo.rs"^^ . + "true"^^ . + "trueno_ublk_demo"^^ . + "false"^^ . + . + . + "aprender-orchestrate"^^ . + "crates/aprender-orchestrate/examples/trueno_zram_demo.rs"^^ . + "true"^^ . + "trueno_zram_demo"^^ . + "false"^^ . + . + . + "aprender-present-core"^^ . + "crates/aprender-present-core/examples/hello_widget.rs"^^ . + "true"^^ . + "hello_widget"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/ald_batch_upload.rs"^^ . + "true"^^ . + "ald_batch_upload"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/ald_correlation_heatmap.rs"^^ . + "true"^^ . + "ald_correlation_heatmap"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/ald_lineage.rs"^^ . + "true"^^ . + "ald_lineage"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/apr_ald_display.rs"^^ . + "true"^^ . + "apr_ald_display"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/apr_architecture.rs"^^ . + "true"^^ . + "apr_architecture"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/apr_size_breakdown.rs"^^ . + "true"^^ . + "apr_size_breakdown"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/apr_version_history.rs"^^ . + "true"^^ . + "apr_version_history"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/apr_weight_histograms.rs"^^ . + "true"^^ . + "apr_weight_histograms"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/brick_computer.rs"^^ . + "true"^^ . + "brick_computer"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/cht_area_stacked.rs"^^ . + "true"^^ . + "cht_area_stacked"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/cht_boxplot.rs"^^ . + "true"^^ . + "cht_boxplot"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/cht_donut.rs"^^ . + "true"^^ . + "cht_donut"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/cht_heatmap_basic.rs"^^ . + "true"^^ . + "cht_heatmap_basic"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/cht_multi_axis.rs"^^ . + "true"^^ . + "cht_multi_axis"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/cht_scatter_bubble.rs"^^ . + "true"^^ . + "cht_scatter_bubble"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/cht_sparkline.rs"^^ . + "true"^^ . + "cht_sparkline"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/dashboard.rs"^^ . + "true"^^ . + "dashboard"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/demo_10x_comparison.rs"^^ . + "true"^^ . + "demo_10x_comparison"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/dsh_alerts.rs"^^ . + "true"^^ . + "dsh_alerts"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/dsh_infrastructure.rs"^^ . + "true"^^ . + "dsh_infrastructure"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/dsh_performance.rs"^^ . + "true"^^ . + "dsh_performance"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/dsh_pipeline.rs"^^ . + "true"^^ . + "dsh_pipeline"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/dsh_research.rs"^^ . + "true"^^ . + "dsh_research"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_a11y_audit.rs"^^ . + "true"^^ . + "edg_a11y_audit"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_high_cardinality.rs"^^ . + "true"^^ . + "edg_high_cardinality"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_memory_soak.rs"^^ . + "true"^^ . + "edg_memory_soak"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_numeric.rs"^^ . + "true"^^ . + "edg_numeric"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_rtl.rs"^^ . + "true"^^ . + "edg_rtl"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_slow_data.rs"^^ . + "true"^^ . + "edg_slow_data"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_theme_switching.rs"^^ . + "true"^^ . + "edg_theme_switching"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/edg_unicode.rs"^^ . + "true"^^ . + "edg_unicode"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/form_inputs.rs"^^ . + "true"^^ . + "form_inputs"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/generate_demo_assets.rs"^^ . + "true"^^ . + "generate_demo_assets"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/hello_world.rs"^^ . + "true"^^ . + "hello_world"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/showcase_gpu.rs"^^ . + "true"^^ . + "showcase_gpu"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/showcase_shell.rs"^^ . + "true"^^ . + "showcase_shell"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/visual_demo.rs"^^ . + "true"^^ . + "visual_demo"^^ . + "false"^^ . + . + . + "aprender-present-lib"^^ . + "crates/aprender-present-lib/examples/yaml_manifest.rs"^^ . + "true"^^ . + "yaml_manifest"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/batch_progress.rs"^^ . + "true"^^ . + "batch_progress"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/cbtop-bench.rs"^^ . + "true"^^ . + "cbtop-bench"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/cluster_status.rs"^^ . + "true"^^ . + "cluster_status"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/cpu_monitor.rs"^^ . + "true"^^ . + "cpu_monitor"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/direct_benchmark.rs"^^ . + "true"^^ . + "direct_benchmark"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/direct_canvas_demo.rs"^^ . + "true"^^ . + "direct_canvas_demo"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/gpu_compute.rs"^^ . + "true"^^ . + "gpu_compute"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/inference_server.rs"^^ . + "false"^^ . + "inference_server"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/memory_monitor.rs"^^ . + "true"^^ . + "memory_monitor"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/ml_visualization.rs"^^ . + "true"^^ . + "ml_visualization"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/network_traffic.rs"^^ . + "true"^^ . + "network_traffic"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/queue_monitor.rs"^^ . + "true"^^ . + "queue_monitor"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/sensor_dashboard.rs"^^ . + "true"^^ . + "sensor_dashboard"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/system_dashboard.rs"^^ . + "true"^^ . + "system_dashboard"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/test_connections.rs"^^ . + "true"^^ . + "test_connections"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/test_display_rules.rs"^^ . + "true"^^ . + "test_display_rules"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/training_metrics.rs"^^ . + "true"^^ . + "training_metrics"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/ttop_cpu_pane.rs"^^ . + "true"^^ . + "ttop_cpu_pane"^^ . + "false"^^ . + . + . + "aprender-present-terminal"^^ . + "crates/aprender-present-terminal/examples/tui-compare.rs"^^ . + "true"^^ . + "tui-compare"^^ . + "false"^^ . + . + . + "aprender-present-yaml"^^ . + "crates/aprender-present-yaml/examples/validate_prs.rs"^^ . + "true"^^ . + "validate_prs"^^ . + "false"^^ . + . + . + "aprender-profile"^^ . + "crates/aprender-profile/examples/brick_trace_demo.rs"^^ . + "true"^^ . + "brick_trace_demo"^^ . + "false"^^ . + . + . + "aprender-profile"^^ . + "crates/aprender-profile/examples/buggy_server.rs"^^ . + "true"^^ . + "buggy_server"^^ . + "false"^^ . + . + . + "aprender-profile"^^ . + "crates/aprender-profile/examples/build_time_assertions.rs"^^ . + "true"^^ . + "build_time_assertions"^^ . + "false"^^ . + . + . + "aprender-profile"^^ . + "crates/aprender-profile/examples/process_tracer_demo.rs"^^ . + "true"^^ . + "process_tracer_demo"^^ . + "false"^^ . + . + . + "aprender-profile"^^ . + "crates/aprender-profile/examples/validate_golden_trace.rs"^^ . + "true"^^ . + "validate_golden_trace"^^ . + "false"^^ . + . + . + "aprender-qa-cli"^^ . + "crates/aprender-qa-cli/examples/fail_fast_demo.rs"^^ . + "false"^^ . + "fail_fast_demo"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-qa-cli"^^ . + "crates/aprender-qa-cli/examples/integrity_lock_demo.rs"^^ . + "true"^^ . + "integrity_lock_demo"^^ . + "false"^^ . + . + . + "aprender-qa-gen"^^ . + "crates/aprender-qa-gen/examples/bootstrap_playbook.rs"^^ . + "false"^^ . + "bootstrap_playbook"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-qa-gen"^^ . + "crates/aprender-qa-gen/examples/generate_scenarios.rs"^^ . + "false"^^ . + "generate_scenarios"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-qa-report"^^ . + "crates/aprender-qa-report/examples/calculate_mqs.rs"^^ . + "false"^^ . + "calculate_mqs"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-qa-report"^^ . + "crates/aprender-qa-report/examples/generate_certificate.rs"^^ . + "false"^^ . + "generate_certificate"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-qa-report"^^ . + "crates/aprender-qa-report/examples/generate_rag_markdown.rs"^^ . + "false"^^ . + "generate_rag_markdown"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-qa-runner"^^ . + "crates/aprender-qa-runner/examples/collect_evidence.rs"^^ . + "false"^^ . + "collect_evidence"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-qa-runner"^^ . + "crates/aprender-qa-runner/examples/contract_demo.rs"^^ . + "true"^^ . + "contract_demo"^^ . + "false"^^ . + . + . + "aprender-qa-runner"^^ . + "crates/aprender-qa-runner/examples/provable_contracts_demo.rs"^^ . + "true"^^ . + "provable_contracts_demo"^^ . + "false"^^ . + . + . + "aprender-qa-runner"^^ . + "crates/aprender-qa-runner/examples/rosetta_testing.rs"^^ . + "false"^^ . + "rosetta_testing"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/basic_rag.rs"^^ . + "true"^^ . + "basic_rag"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/chunking_strategies.rs"^^ . + "true"^^ . + "chunking_strategies"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/compressed_index.rs"^^ . + "true"^^ . + "compressed_index"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/eval_hybrid.rs"^^ . + "true"^^ . + "eval_hybrid"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/hybrid_search.rs"^^ . + "true"^^ . + "hybrid_search"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/metrics_evaluation.rs"^^ . + "true"^^ . + "metrics_evaluation"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/nemotron_embeddings.rs"^^ . + "true"^^ . + "nemotron_embeddings"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/semantic_embeddings.rs"^^ . + "true"^^ . + "semantic_embeddings"^^ . + "false"^^ . + . + . + "aprender-rag"^^ . + "crates/aprender-rag/examples/sqlite_export.rs"^^ . + "true"^^ . + "sqlite_export"^^ . + "false"^^ . + . + . + "aprender-rand"^^ . + "crates/aprender-rand/examples/rng_demo.rs"^^ . + "true"^^ . + "rng_demo"^^ . + "false"^^ . + . + . + "aprender-registry"^^ . + "crates/aprender-registry/examples/content_addressing.rs"^^ . + "true"^^ . + "content_addressing"^^ . + "false"^^ . + . + . + "aprender-registry"^^ . + "crates/aprender-registry/examples/crypto_demo.rs"^^ . + "true"^^ . + "crypto_demo"^^ . + "false"^^ . + . + . + "aprender-registry"^^ . + "crates/aprender-registry/examples/experiment_tracking.rs"^^ . + "true"^^ . + "experiment_tracking"^^ . + "false"^^ . + . + . + "aprender-registry"^^ . + "crates/aprender-registry/examples/lineage_tracking.rs"^^ . + "false"^^ . + "lineage_tracking"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-registry"^^ . + "crates/aprender-registry/examples/model_versioning.rs"^^ . + "true"^^ . + "model_versioning"^^ . + "false"^^ . + . + . + "aprender-registry"^^ . + "crates/aprender-registry/examples/quick_start.rs"^^ . + "true"^^ . + "quick_start"^^ . + "false"^^ . + . + . + "aprender-registry"^^ . + "crates/aprender-registry/examples/signing_demo.rs"^^ . + "true"^^ . + "signing_demo"^^ . + "false"^^ . + . + . + "aprender-review-experiment"^^ . + "crates/aprender-review-experiment/examples/rex.rs"^^ . + "true"^^ . + "rex"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/api_server.rs"^^ . + "true"^^ . + "api_server"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/apr_benchmark.rs"^^ . + "true"^^ . + "apr_benchmark"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/apr_gpu_benchmark.rs"^^ . + "false"^^ . + "apr_gpu_benchmark"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/apr_loading.rs"^^ . + "true"^^ . + "apr_loading"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/apr_mmap_loading.rs"^^ . + "true"^^ . + "apr_mmap_loading"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/attention_manual_verify.rs"^^ . + "true"^^ . + "attention_manual_verify"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_apr_gpu.rs"^^ . + "false"^^ . + "bench_apr_gpu"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_apr_vs_gguf.rs"^^ . + "false"^^ . + "bench_apr_vs_gguf"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_barrier_overhead.rs"^^ . + "true"^^ . + "bench_barrier_overhead"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_batched_forward.rs"^^ . + "false"^^ . + "bench_batched_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_batched_gemv.rs"^^ . + "false"^^ . + "bench_batched_gemv"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_chunk_sizes.rs"^^ . + "true"^^ . + "bench_chunk_sizes"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_chunked_matmul.rs"^^ . + "true"^^ . + "bench_chunked_matmul"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_comparison.rs"^^ . + "false"^^ . + "bench_comparison"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_continuous_batching.rs"^^ . + "false"^^ . + "bench_continuous_batching"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_cpu_util.rs"^^ . + "true"^^ . + "bench_cpu_util"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_flash_decoding.rs"^^ . + "false"^^ . + "bench_flash_decoding"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_forward.rs"^^ . + "true"^^ . + "bench_forward"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_gemv.rs"^^ . + "true"^^ . + "bench_gemv"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_kernel_variants.rs"^^ . + "true"^^ . + "bench_kernel_variants"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_manual_threads.rs"^^ . + "true"^^ . + "bench_manual_threads"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_matmul_breakdown.rs"^^ . + "false"^^ . + "bench_matmul_breakdown"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_matmul_only.rs"^^ . + "false"^^ . + "bench_matmul_only"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_multisequence_graph.rs"^^ . + "false"^^ . + "bench_multisequence_graph"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_q4k_detect.rs"^^ . + "true"^^ . + "bench_q4k_detect"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_q4k_parallel.rs"^^ . + "true"^^ . + "bench_q4k_parallel"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_q4k_simd.rs"^^ . + "true"^^ . + "bench_q4k_simd"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_q8k_speedup.rs"^^ . + "true"^^ . + "bench_q8k_speedup"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_qwen.rs"^^ . + "false"^^ . + "bench_qwen"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_rayon_overhead.rs"^^ . + "true"^^ . + "bench_rayon_overhead"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_realistic.rs"^^ . + "false"^^ . + "bench_realistic"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_scaling.rs"^^ . + "true"^^ . + "bench_scaling"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_scratch.rs"^^ . + "false"^^ . + "bench_scratch"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_seq_vs_par.rs"^^ . + "false"^^ . + "bench_seq_vs_par"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_simd_dot.rs"^^ . + "true"^^ . + "bench_simd_dot"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_speculative.rs"^^ . + "false"^^ . + "bench_speculative"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_tiled_q4k.rs"^^ . + "false"^^ . + "bench_tiled_q4k"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_toks.rs"^^ . + "true"^^ . + "bench_toks"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_v2_kernel.rs"^^ . + "true"^^ . + "bench_v2_kernel"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/bench_vnni_vs_avx2.rs"^^ . + "true"^^ . + "bench_vnni_vs_avx2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/benchmark_cpu.rs"^^ . + "false"^^ . + "benchmark_cpu"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/brick_divergence_trace.rs"^^ . + "false"^^ . + "brick_divergence_trace"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/chat_template.rs"^^ . + "false"^^ . + "chat_template"^^ . + "llama"^^ . + "mistral"^^ . + "phi"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_apr_tensors.rs"^^ . + "true"^^ . + "check_apr_tensors"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_arch_detection.rs"^^ . + "false"^^ . + "check_arch_detection"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_bias.rs"^^ . + "false"^^ . + "check_bias"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_bias_format.rs"^^ . + "false"^^ . + "check_bias_format"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_bias_raw.rs"^^ . + "false"^^ . + "check_bias_raw"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_biases.rs"^^ . + "false"^^ . + "check_biases"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_dims.rs"^^ . + "false"^^ . + "check_dims"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_embed.rs"^^ . + "false"^^ . + "check_embed"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_embed_layout.rs"^^ . + "false"^^ . + "check_embed_layout"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_embedding.rs"^^ . + "false"^^ . + "check_embedding"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_embeddings.rs"^^ . + "false"^^ . + "check_embeddings"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_ffn_down_col_5475.rs"^^ . + "false"^^ . + "check_ffn_down_col_5475"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_ffn_down_types.rs"^^ . + "false"^^ . + "check_ffn_down_types"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_ffn_down_weight.rs"^^ . + "false"^^ . + "check_ffn_down_weight"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_ffn_hidden.rs"^^ . + "false"^^ . + "check_ffn_hidden"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_final_hidden.rs"^^ . + "false"^^ . + "check_final_hidden"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_forward.rs"^^ . + "false"^^ . + "check_forward"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_gate_up_correlation.rs"^^ . + "false"^^ . + "check_gate_up_correlation"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_gguf_meta.rs"^^ . + "false"^^ . + "check_gguf_meta"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_gguf_metadata.rs"^^ . + "false"^^ . + "check_gguf_metadata"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_gguf_model_version.rs"^^ . + "false"^^ . + "check_gguf_model_version"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_gpu_logits.rs"^^ . + "false"^^ . + "check_gpu_logits"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_idx_5475.rs"^^ . + "false"^^ . + "check_idx_5475"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_layer2_ffn_down.rs"^^ . + "false"^^ . + "check_layer2_ffn_down"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_layer4.rs"^^ . + "true"^^ . + "check_layer4"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_layer_structure.rs"^^ . + "false"^^ . + "check_layer_structure"^^ . + "llama"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_layer_weights.rs"^^ . + "false"^^ . + "check_layer_weights"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_lm_head.rs"^^ . + "false"^^ . + "check_lm_head"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_matmul.rs"^^ . + "false"^^ . + "check_matmul"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_newlines.rs"^^ . + "false"^^ . + "check_newlines"^^ . + "gpt2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_norm_weight.rs"^^ . + "false"^^ . + "check_norm_weight"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_norm_weights.rs"^^ . + "false"^^ . + "check_norm_weights"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_paris.rs"^^ . + "false"^^ . + "check_paris"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_prefill_logits.rs"^^ . + "false"^^ . + "check_prefill_logits"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_q4k_layout.rs"^^ . + "false"^^ . + "check_q4k_layout"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_q4k_population.rs"^^ . + "false"^^ . + "check_q4k_population"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_q8k_accuracy.rs"^^ . + "true"^^ . + "check_q8k_accuracy"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_qkv_bias.rs"^^ . + "false"^^ . + "check_qkv_bias"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_qkv_dims.rs"^^ . + "false"^^ . + "check_qkv_dims"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_qkv_tensors.rs"^^ . + "false"^^ . + "check_qkv_tensors"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_qtype.rs"^^ . + "false"^^ . + "check_qtype"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_qtypes.rs"^^ . + "false"^^ . + "check_qtypes"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_raw_bias.rs"^^ . + "false"^^ . + "check_raw_bias"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_raw_bias_v2.rs"^^ . + "false"^^ . + "check_raw_bias_v2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_raw_tensors.rs"^^ . + "false"^^ . + "check_raw_tensors"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_rmsnorm_params.rs"^^ . + "false"^^ . + "check_rmsnorm_params"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_space_token.rs"^^ . + "false"^^ . + "check_space_token"^^ . + "gpt2"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_tensor_names.rs"^^ . + "false"^^ . + "check_tensor_names"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_tensor_order.rs"^^ . + "false"^^ . + "check_tensor_order"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_tensors.rs"^^ . + "false"^^ . + "check_tensors"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_theta.rs"^^ . + "false"^^ . + "check_theta"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_token0.rs"^^ . + "false"^^ . + "check_token0"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_token0_emb.rs"^^ . + "false"^^ . + "check_token0_emb"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_token_74403.rs"^^ . + "false"^^ . + "check_token_74403"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_token_scores.rs"^^ . + "false"^^ . + "check_token_scores"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_token_scores_v2.rs"^^ . + "false"^^ . + "check_token_scores_v2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_tokenization.rs"^^ . + "false"^^ . + "check_tokenization"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_tokenizer.rs"^^ . + "false"^^ . + "check_tokenizer"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_tokens.rs"^^ . + "false"^^ . + "check_tokens"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_v_weight.rs"^^ . + "false"^^ . + "check_v_weight"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_v_weights.rs"^^ . + "false"^^ . + "check_v_weights"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_weight_layout.rs"^^ . + "false"^^ . + "check_weight_layout"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_weight_scales.rs"^^ . + "false"^^ . + "check_weight_scales"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/check_weight_stats.rs"^^ . + "false"^^ . + "check_weight_stats"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_all_layers.rs"^^ . + "false"^^ . + "compare_all_layers"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_apr_gguf_forward.rs"^^ . + "false"^^ . + "compare_apr_gguf_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_configs.rs"^^ . + "false"^^ . + "compare_configs"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_cpu_gpu.rs"^^ . + "false"^^ . + "compare_cpu_gpu"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_cpu_paths.rs"^^ . + "false"^^ . + "compare_cpu_paths"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_embed.rs"^^ . + "false"^^ . + "compare_embed"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_embedding_index.rs"^^ . + "false"^^ . + "compare_embedding_index"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_forward_methods.rs"^^ . + "false"^^ . + "compare_forward_methods"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_forward_paths.rs"^^ . + "false"^^ . + "compare_forward_paths"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_hidden_before_norm.rs"^^ . + "false"^^ . + "compare_hidden_before_norm"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_l0_v.rs"^^ . + "false"^^ . + "compare_l0_v"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_layer0.rs"^^ . + "false"^^ . + "compare_layer0"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_layer0_full.rs"^^ . + "false"^^ . + "compare_layer0_full"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_layer_outputs.rs"^^ . + "false"^^ . + "compare_layer_outputs"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_layers.rs"^^ . + "false"^^ . + "compare_layers"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_lm_head_input.rs"^^ . + "false"^^ . + "compare_lm_head_input"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_logits.rs"^^ . + "false"^^ . + "compare_logits"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_matmul.rs"^^ . + "false"^^ . + "compare_matmul"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_matmul2.rs"^^ . + "false"^^ . + "compare_matmul2"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_q4k_bytes.rs"^^ . + "false"^^ . + "compare_q4k_bytes"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_q_projection.rs"^^ . + "false"^^ . + "compare_q_projection"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_qkv_structure.rs"^^ . + "false"^^ . + "compare_qkv_structure"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_rope.rs"^^ . + "false"^^ . + "compare_rope"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_v_weight.rs"^^ . + "false"^^ . + "compare_v_weight"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/compare_weights.rs"^^ . + "false"^^ . + "compare_weights"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/constrain_mask_overhead.rs"^^ . + "true"^^ . + "constrain_mask_overhead"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/contract_pipeline_demo.rs"^^ . + "true"^^ . + "contract_pipeline_demo"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/convert_and_bench_apr.rs"^^ . + "false"^^ . + "convert_and_bench_apr"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/convert_apr_q4k.rs"^^ . + "false"^^ . + "convert_apr_q4k"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/cpu_hidden.rs"^^ . + "false"^^ . + "cpu_hidden"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/cpu_hidden_state_trace.rs"^^ . + "false"^^ . + "cpu_hidden_state_trace"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/cpu_hidden_trace.rs"^^ . + "false"^^ . + "cpu_hidden_trace"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/cublas_fp8_7b_reproducer.rs"^^ . + "false"^^ . + "cublas_fp8_7b_reproducer"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/cuda_chat_completions.rs"^^ . + "false"^^ . + "cuda_chat_completions"^^ . + "qwen2.5"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/cuda_debug.rs"^^ . + "true"^^ . + "cuda_debug"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_apr_divergence.rs"^^ . + "false"^^ . + "debug_apr_divergence"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_apr_embedding.rs"^^ . + "false"^^ . + "debug_apr_embedding"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_attention_output.rs"^^ . + "false"^^ . + "debug_attention_output"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_chat_template.rs"^^ . + "false"^^ . + "debug_chat_template"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_cpu_forward.rs"^^ . + "false"^^ . + "debug_cpu_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_cpu_gpu_divergence.rs"^^ . + "false"^^ . + "debug_cpu_gpu_divergence"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_early_layers.rs"^^ . + "false"^^ . + "debug_early_layers"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_embed_test.rs"^^ . + "false"^^ . + "debug_embed_test"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_embedding.rs"^^ . + "false"^^ . + "debug_embedding"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_embedding_qtype.rs"^^ . + "false"^^ . + "debug_embedding_qtype"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_embedding_simple.rs"^^ . + "false"^^ . + "debug_embedding_simple"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_embeddings.rs"^^ . + "false"^^ . + "debug_embeddings"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_first_q_proj.rs"^^ . + "false"^^ . + "debug_first_q_proj"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_forward.rs"^^ . + "false"^^ . + "debug_forward"^^ . + "llama"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_forward_bias.rs"^^ . + "false"^^ . + "debug_forward_bias"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_gen_detailed.rs"^^ . + "true"^^ . + "debug_gen_detailed"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_gpu_divergence.rs"^^ . + "false"^^ . + "debug_gpu_divergence"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_gpu_layer4.rs"^^ . + "false"^^ . + "debug_gpu_layer4"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_hidden_state.rs"^^ . + "false"^^ . + "debug_hidden_state"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_inference.rs"^^ . + "false"^^ . + "debug_inference"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer0_compare.rs"^^ . + "false"^^ . + "debug_layer0_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer0_divergence.rs"^^ . + "false"^^ . + "debug_layer0_divergence"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer0_stepwise.rs"^^ . + "false"^^ . + "debug_layer0_stepwise"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer0_trace.rs"^^ . + "false"^^ . + "debug_layer0_trace"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer21_ffn.rs"^^ . + "false"^^ . + "debug_layer21_ffn"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer2_ffn.rs"^^ . + "false"^^ . + "debug_layer2_ffn"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer2_gate.rs"^^ . + "false"^^ . + "debug_layer2_gate"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer_by_layer.rs"^^ . + "false"^^ . + "debug_layer_by_layer"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_layer_compare.rs"^^ . + "false"^^ . + "debug_layer_compare"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_lm_head.rs"^^ . + "false"^^ . + "debug_lm_head"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_lm_head_direct.rs"^^ . + "false"^^ . + "debug_lm_head_direct"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_lm_head_divergence.rs"^^ . + "false"^^ . + "debug_lm_head_divergence"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_lm_head_weights.rs"^^ . + "false"^^ . + "debug_lm_head_weights"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_matmul_convention.rs"^^ . + "false"^^ . + "debug_matmul_convention"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_matvec_compare.rs"^^ . + "false"^^ . + "debug_matvec_compare"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_normed_hidden.rs"^^ . + "false"^^ . + "debug_normed_hidden"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_normed_hidden_compare.rs"^^ . + "false"^^ . + "debug_normed_hidden_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_o_weight.rs"^^ . + "false"^^ . + "debug_o_weight"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_o_weight_layout.rs"^^ . + "false"^^ . + "debug_o_weight_layout"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_pos1.rs"^^ . + "false"^^ . + "debug_pos1"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_ptx.rs"^^ . + "false"^^ . + "debug_ptx"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4_0.rs"^^ . + "false"^^ . + "debug_q4_0"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_attn_output.rs"^^ . + "false"^^ . + "debug_q4k_attn_output"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_controlled.rs"^^ . + "false"^^ . + "debug_q4k_controlled"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_down_weight.rs"^^ . + "false"^^ . + "debug_q4k_down_weight"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_embedding_raw.rs"^^ . + "false"^^ . + "debug_q4k_embedding_raw"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_gemv.rs"^^ . + "false"^^ . + "debug_q4k_gemv"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_gemv_layer0.rs"^^ . + "false"^^ . + "debug_q4k_gemv_layer0"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_gemv_tiled.rs"^^ . + "false"^^ . + "debug_q4k_gemv_tiled"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_real_input.rs"^^ . + "false"^^ . + "debug_q4k_real_input"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q4k_rmsnorm_input.rs"^^ . + "false"^^ . + "debug_q4k_rmsnorm_input"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q6k_controlled.rs"^^ . + "true"^^ . + "debug_q6k_controlled"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q6k_gemv.rs"^^ . + "false"^^ . + "debug_q6k_gemv"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q6k_layout.rs"^^ . + "false"^^ . + "debug_q6k_layout"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q6k_lm_head_test.rs"^^ . + "false"^^ . + "debug_q6k_lm_head_test"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q6k_row.rs"^^ . + "false"^^ . + "debug_q6k_row"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q6k_single_row.rs"^^ . + "false"^^ . + "debug_q6k_single_row"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_q_weight_compare.rs"^^ . + "false"^^ . + "debug_q_weight_compare"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_qkv_compare.rs"^^ . + "false"^^ . + "debug_qkv_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_qkv_matmul.rs"^^ . + "false"^^ . + "debug_qkv_matmul"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_qwen.rs"^^ . + "false"^^ . + "debug_qwen"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_rmsnorm_layer0.rs"^^ . + "false"^^ . + "debug_rmsnorm_layer0"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_single_row.rs"^^ . + "false"^^ . + "debug_single_row"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_speculative.rs"^^ . + "false"^^ . + "debug_speculative"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_tensor_layout.rs"^^ . + "false"^^ . + "debug_tensor_layout"^^ . + "deepseek"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_tiled_q4k.rs"^^ . + "false"^^ . + "debug_tiled_q4k"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_v_weight.rs"^^ . + "false"^^ . + "debug_v_weight"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_v_weight_layout.rs"^^ . + "false"^^ . + "debug_v_weight_layout"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/debug_weights.rs"^^ . + "false"^^ . + "debug_weights"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/design_by_contract.rs"^^ . + "false"^^ . + "design_by_contract"^^ . + "gpt2"^^ . + "llama"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/detailed_profile.rs"^^ . + "false"^^ . + "detailed_profile"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/diag_apr_determinism.rs"^^ . + "false"^^ . + "diag_apr_determinism"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/diag_apr_qkv_layer0.rs"^^ . + "false"^^ . + "diag_apr_qkv_layer0"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/diag_compare_layer3_ffn.rs"^^ . + "false"^^ . + "diag_compare_layer3_ffn"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/diag_compare_qkv_bias.rs"^^ . + "false"^^ . + "diag_compare_qkv_bias"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/diag_q4k_dequant_cpu_vs_gpu.rs"^^ . + "false"^^ . + "diag_q4k_dequant_cpu_vs_gpu"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/diag_qkv_bisection_layer0.rs"^^ . + "false"^^ . + "diag_qkv_bisection_layer0"^^ . + "qwen2.5"^^ . + "qwen3"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/digit_combo_test.rs"^^ . + "false"^^ . + "digit_combo_test"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/dump_config.rs"^^ . + "false"^^ . + "dump_config"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/dump_def_embedding.rs"^^ . + "false"^^ . + "dump_def_embedding"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/dump_gguf_dims.rs"^^ . + "false"^^ . + "dump_gguf_dims"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/dump_layer0_tensors.rs"^^ . + "false"^^ . + "dump_layer0_tensors"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/dump_q6k_ptx.rs"^^ . + "true"^^ . + "dump_q6k_ptx"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/final_hidden_compare.rs"^^ . + "false"^^ . + "final_hidden_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/find_ffn_outliers.rs"^^ . + "false"^^ . + "find_ffn_outliers"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/fresh_compare.rs"^^ . + "false"^^ . + "fresh_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/gguf_debug.rs"^^ . + "false"^^ . + "gguf_debug"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/gguf_loading.rs"^^ . + "true"^^ . + "gguf_loading"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/gpu_gemm_benchmark.rs"^^ . + "false"^^ . + "gpu_gemm_benchmark"^^ . + "llama"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/gpu_matvec_benchmark.rs"^^ . + "false"^^ . + "gpu_matvec_benchmark"^^ . + "llama"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/gpu_showcase_benchmark.rs"^^ . + "false"^^ . + "gpu_showcase_benchmark"^^ . + "deepseek"^^ . + "llama"^^ . + "phi"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/hidden_compare.rs"^^ . + "false"^^ . + "hidden_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/imp800_gpu_parity.rs"^^ . + "false"^^ . + "imp800_gpu_parity"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/imp900_optimized_gpu.rs"^^ . + "false"^^ . + "imp900_optimized_gpu"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/imp_1010_full_cuda_benchmark.rs"^^ . + "false"^^ . + "imp_1010_full_cuda_benchmark"^^ . + "llama"^^ . + "phi"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/imp_700_realworld_verification.rs"^^ . + "false"^^ . + "imp_700_realworld_verification"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/imp_701_performance_gap.rs"^^ . + "false"^^ . + "imp_701_performance_gap"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/imp_800_kv_cache_falsification.rs"^^ . + "false"^^ . + "imp_800_kv_cache_falsification"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/imp_801_flash_attention_falsification.rs"^^ . + "false"^^ . + "imp_801_flash_attention_falsification"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/inference.rs"^^ . + "true"^^ . + "inference"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/instrumented_forward.rs"^^ . + "false"^^ . + "instrumented_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/layer0_attention_compare.rs"^^ . + "false"^^ . + "layer0_attention_compare"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/layer0_step_compare.rs"^^ . + "false"^^ . + "layer0_step_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/layer_by_layer_debug.rs"^^ . + "false"^^ . + "layer_by_layer_debug"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/layer_by_layer_trace.rs"^^ . + "false"^^ . + "layer_by_layer_trace"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/layer_compare.rs"^^ . + "false"^^ . + "layer_compare"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/layer_profile.rs"^^ . + "false"^^ . + "layer_profile"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/measure_forward_time.rs"^^ . + "false"^^ . + "measure_forward_time"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/micro_profile.rs"^^ . + "false"^^ . + "micro_profile"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/model_cache.rs"^^ . + "false"^^ . + "model_cache"^^ . + "gpt2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/observability_demo.rs"^^ . + "false"^^ . + "observability_demo"^^ . + "llama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_check_dims.rs"^^ . + "false"^^ . + "par_001_check_dims"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_check_embed.rs"^^ . + "false"^^ . + "par_001_check_embed"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_check_embeddings.rs"^^ . + "false"^^ . + "par_001_check_embeddings"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_check_lm_head.rs"^^ . + "false"^^ . + "par_001_check_lm_head"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_check_output_norm.rs"^^ . + "false"^^ . + "par_001_check_output_norm"^^ . + "llama"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_check_q6k_superblocks.rs"^^ . + "false"^^ . + "par_001_check_q6k_superblocks"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_compare_data.rs"^^ . + "false"^^ . + "par_001_compare_data"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_debug_forward.rs"^^ . + "false"^^ . + "par_001_debug_forward"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_full_forward.rs"^^ . + "false"^^ . + "par_001_full_forward"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_fused_vs_naive.rs"^^ . + "false"^^ . + "par_001_fused_vs_naive"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_list_q6k.rs"^^ . + "false"^^ . + "par_001_list_q6k"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_qkv_parity.rs"^^ . + "false"^^ . + "par_001_qkv_parity"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_qv_comparison.rs"^^ . + "false"^^ . + "par_001_qv_comparison"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_tensor_offset.rs"^^ . + "false"^^ . + "par_001_tensor_offset"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_test_chat.rs"^^ . + "false"^^ . + "par_001_test_chat"^^ . + "llama"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_test_chatml.rs"^^ . + "false"^^ . + "par_001_test_chatml"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_test_math.rs"^^ . + "false"^^ . + "par_001_test_math"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_trace_all_layers.rs"^^ . + "false"^^ . + "par_001_trace_all_layers"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_trace_hidden.rs"^^ . + "false"^^ . + "par_001_trace_hidden"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_trace_layers.rs"^^ . + "false"^^ . + "par_001_trace_layers"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_trace_pos1.rs"^^ . + "false"^^ . + "par_001_trace_pos1"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_trace_token.rs"^^ . + "false"^^ . + "par_001_trace_token"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_trace_v.rs"^^ . + "false"^^ . + "par_001_trace_v"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_transpose_test.rs"^^ . + "false"^^ . + "par_001_transpose_test"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_verify_colmajor.rs"^^ . + "false"^^ . + "par_001_verify_colmajor"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_verify_ffn_down.rs"^^ . + "false"^^ . + "par_001_verify_ffn_down"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_verify_lm_head.rs"^^ . + "false"^^ . + "par_001_verify_lm_head"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_verify_q4k_dot.rs"^^ . + "false"^^ . + "par_001_verify_q4k_dot"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_verify_q4k_matvec.rs"^^ . + "false"^^ . + "par_001_verify_q4k_matvec"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_verify_q6k_rowmajor.rs"^^ . + "false"^^ . + "par_001_verify_q6k_rowmajor"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_verify_v_real.rs"^^ . + "false"^^ . + "par_001_verify_v_real"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/par_001_weight_alignment.rs"^^ . + "false"^^ . + "par_001_weight_alignment"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/parity_035_m4_verification.rs"^^ . + "false"^^ . + "parity_035_m4_verification"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/parity_036_gpu_attention.rs"^^ . + "false"^^ . + "parity_036_gpu_attention"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/parity_038_async_streams.rs"^^ . + "false"^^ . + "parity_038_async_streams"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/parity_039_flash_attention.rs"^^ . + "false"^^ . + "parity_039_flash_attention"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/parity_040_fp16_attention.rs"^^ . + "false"^^ . + "parity_040_fp16_attention"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/performance_parity.rs"^^ . + "false"^^ . + "performance_parity"^^ . + "llama"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/pipeline_tui.rs"^^ . + "true"^^ . + "pipeline_tui"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/pmat_benchmark_matrix.rs"^^ . + "false"^^ . + "pmat_benchmark_matrix"^^ . + "llama"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/position_trace.rs"^^ . + "false"^^ . + "position_trace"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/predict_after_layer1.rs"^^ . + "false"^^ . + "predict_after_layer1"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_7b.rs"^^ . + "false"^^ . + "profile_7b"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_all_layers.rs"^^ . + "false"^^ . + "profile_all_layers"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_all_matmuls.rs"^^ . + "false"^^ . + "profile_all_matmuls"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_attention.rs"^^ . + "true"^^ . + "profile_attention"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_cached_forward.rs"^^ . + "false"^^ . + "profile_cached_forward"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_cpu_breakdown.rs"^^ . + "false"^^ . + "profile_cpu_breakdown"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_cpu_kernel.rs"^^ . + "true"^^ . + "profile_cpu_kernel"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_detailed.rs"^^ . + "false"^^ . + "profile_detailed"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_ffn.rs"^^ . + "false"^^ . + "profile_ffn"^^ . + "phi"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_forward_breakdown.rs"^^ . + "false"^^ . + "profile_forward_breakdown"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_forward_detailed.rs"^^ . + "false"^^ . + "profile_forward_detailed"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_forward_instrumented.rs"^^ . + "false"^^ . + "profile_forward_instrumented"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_forward_pass.rs"^^ . + "false"^^ . + "profile_forward_pass"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_full_forward.rs"^^ . + "false"^^ . + "profile_full_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_inference.rs"^^ . + "false"^^ . + "profile_inference"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_layer_breakdown.rs"^^ . + "false"^^ . + "profile_layer_breakdown"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_lm_head.rs"^^ . + "false"^^ . + "profile_lm_head"^^ . + "qwen2.5"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_matmul_cold.rs"^^ . + "false"^^ . + "profile_matmul_cold"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_matmul_detail.rs"^^ . + "false"^^ . + "profile_matmul_detail"^^ . + "qwen2.5"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_matmul_sizes.rs"^^ . + "false"^^ . + "profile_matmul_sizes"^^ . + "llama"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_non_matmul.rs"^^ . + "true"^^ . + "profile_non_matmul"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_phi2_simple.rs"^^ . + "false"^^ . + "profile_phi2_simple"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_q8k_quant.rs"^^ . + "true"^^ . + "profile_q8k_quant"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_rayon_overhead.rs"^^ . + "true"^^ . + "profile_rayon_overhead"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/profile_scratch_vs_cache.rs"^^ . + "false"^^ . + "profile_scratch_vs_cache"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/q6k_bench.rs"^^ . + "false"^^ . + "q6k_bench"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/quick_generate.rs"^^ . + "false"^^ . + "quick_generate"^^ . + "gpt2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/qwen35_parity.rs"^^ . + "true"^^ . + "qwen35_parity"^^ . + "llama"^^ . + "qwen3.5"^^ . + "true"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/qwen35_prefill_parity.rs"^^ . + "true"^^ . + "qwen35_prefill_parity"^^ . + "llama"^^ . + "qwen3.5"^^ . + "true"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/qwen_apr_demo.rs"^^ . + "false"^^ . + "qwen_apr_demo"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/safetensors_loading.rs"^^ . + "true"^^ . + "safetensors_loading"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_0_5b_raw.rs"^^ . + "false"^^ . + "test_0_5b_raw"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_1_5b_raw.rs"^^ . + "false"^^ . + "test_1_5b_raw"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_apr_q4k_forward.rs"^^ . + "false"^^ . + "test_apr_q4k_forward"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_apr_q4k_generate.rs"^^ . + "false"^^ . + "test_apr_q4k_generate"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_apr_quantized_cache.rs"^^ . + "true"^^ . + "test_apr_quantized_cache"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_attention_debug.rs"^^ . + "true"^^ . + "test_attention_debug"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_attention_phi2_dims.rs"^^ . + "false"^^ . + "test_attention_phi2_dims"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_chat_format.rs"^^ . + "false"^^ . + "test_chat_format"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_coherence.rs"^^ . + "false"^^ . + "test_coherence"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_cpu_chat.rs"^^ . + "false"^^ . + "test_cpu_chat"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_cuda_minimal.rs"^^ . + "true"^^ . + "test_cuda_minimal"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_ffn_q4k.rs"^^ . + "false"^^ . + "test_ffn_q4k"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_forward.rs"^^ . + "false"^^ . + "test_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_gemv_correctness.rs"^^ . + "true"^^ . + "test_gemv_correctness"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_generation.rs"^^ . + "false"^^ . + "test_generation"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_gguf_baseline.rs"^^ . + "false"^^ . + "test_gguf_baseline"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_gpu_bias.rs"^^ . + "false"^^ . + "test_gpu_bias"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_graphed.rs"^^ . + "false"^^ . + "test_graphed"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_inference.rs"^^ . + "false"^^ . + "test_inference"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_lm_head_direct.rs"^^ . + "false"^^ . + "test_lm_head_direct"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_lm_head_only.rs"^^ . + "false"^^ . + "test_lm_head_only"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_m16.rs"^^ . + "false"^^ . + "test_m16"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_multiple_prompts.rs"^^ . + "false"^^ . + "test_multiple_prompts"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_no_bias.rs"^^ . + "false"^^ . + "test_no_bias"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_ollama_match.rs"^^ . + "false"^^ . + "test_ollama_match"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_q4_0_parity.rs"^^ . + "false"^^ . + "test_q4_0_parity"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_q4k_cuda.rs"^^ . + "true"^^ . + "test_q4k_cuda"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_q4k_phi2_dims.rs"^^ . + "false"^^ . + "test_q4k_phi2_dims"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_q6k_correctness.rs"^^ . + "false"^^ . + "test_q6k_correctness"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_q6k_gemv_direct.rs"^^ . + "false"^^ . + "test_q6k_gemv_direct"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_q6k_single_row.rs"^^ . + "false"^^ . + "test_q6k_single_row"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_qkv_matmul.rs"^^ . + "false"^^ . + "test_qkv_matmul"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_qkv_with_bias.rs"^^ . + "false"^^ . + "test_qkv_with_bias"^^ . + "qwen2.5"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_qwen_prompt.rs"^^ . + "false"^^ . + "test_qwen_prompt"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_qwen_seq.rs"^^ . + "false"^^ . + "test_qwen_seq"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_rope_override.rs"^^ . + "false"^^ . + "test_rope_override"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_tc_attention.rs"^^ . + "true"^^ . + "test_tc_attention"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_tiled_vs_cpu.rs"^^ . + "false"^^ . + "test_tiled_vs_cpu"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_tinyllama.rs"^^ . + "false"^^ . + "test_tinyllama"^^ . + "llama"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_tinyllama_simple.rs"^^ . + "false"^^ . + "test_tinyllama_simple"^^ . + "llama"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_transpose.rs"^^ . + "false"^^ . + "test_transpose"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/test_v_matvec.rs"^^ . + "false"^^ . + "test_v_matvec"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/think_ab.rs"^^ . + "true"^^ . + "think_ab"^^ . + "qwen3.5"^^ . + "true"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/tinyllama_survey.rs"^^ . + "false"^^ . + "tinyllama_survey"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/token_survey.rs"^^ . + "false"^^ . + "token_survey"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/tokenization.rs"^^ . + "true"^^ . + "tokenization"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_2plus2.rs"^^ . + "false"^^ . + "trace_2plus2"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_all_layers.rs"^^ . + "false"^^ . + "trace_all_layers"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_attention.rs"^^ . + "false"^^ . + "trace_attention"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_attention_scores.rs"^^ . + "false"^^ . + "trace_attention_scores"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_attn_weights.rs"^^ . + "false"^^ . + "trace_attn_weights"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_buggy_vs_ok.rs"^^ . + "false"^^ . + "trace_buggy_vs_ok"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_causal_attn.rs"^^ . + "false"^^ . + "trace_causal_attn"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_cpu_forward.rs"^^ . + "false"^^ . + "trace_cpu_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_divergence.rs"^^ . + "false"^^ . + "trace_divergence"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_final.rs"^^ . + "false"^^ . + "trace_final"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_final_hidden.rs"^^ . + "false"^^ . + "trace_final_hidden"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_forward.rs"^^ . + "false"^^ . + "trace_forward"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_forward_detailed.rs"^^ . + "false"^^ . + "trace_forward_detailed"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_full_layer0.rs"^^ . + "false"^^ . + "trace_full_layer0"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_hidden_flow.rs"^^ . + "false"^^ . + "trace_hidden_flow"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_layer0_detailed.rs"^^ . + "false"^^ . + "trace_layer0_detailed"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_layer0_qkv.rs"^^ . + "false"^^ . + "trace_layer0_qkv"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_layer0_qkv_fixed.rs"^^ . + "false"^^ . + "trace_layer0_qkv_fixed"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_layer21.rs"^^ . + "false"^^ . + "trace_layer21"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_layer2_detail.rs"^^ . + "false"^^ . + "trace_layer2_detail"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_lm_head.rs"^^ . + "false"^^ . + "trace_lm_head"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_pos1_divergence.rs"^^ . + "false"^^ . + "trace_pos1_divergence"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_qkv_output.rs"^^ . + "false"^^ . + "trace_qkv_output"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_qkv_step.rs"^^ . + "false"^^ . + "trace_qkv_step"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_qwen_qkv.rs"^^ . + "false"^^ . + "trace_qwen_qkv"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_safetensors.rs"^^ . + "false"^^ . + "trace_safetensors"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_single_layer.rs"^^ . + "false"^^ . + "trace_single_layer"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_single_token_full.rs"^^ . + "false"^^ . + "trace_single_token_full"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trace_single_vs_multi.rs"^^ . + "false"^^ . + "trace_single_vs_multi"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trueno_ab_test.rs"^^ . + "false"^^ . + "trueno_ab_test"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/trueno_dot_test.rs"^^ . + "false"^^ . + "trueno_dot_test"^^ . + "phi"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/validated_tensors.rs"^^ . + "true"^^ . + "validated_tensors"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_apr_q4k.rs"^^ . + "false"^^ . + "verify_apr_q4k"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_attention_kernel.rs"^^ . + "true"^^ . + "verify_attention_kernel"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_embed.rs"^^ . + "false"^^ . + "verify_embed"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_embedding.rs"^^ . + "false"^^ . + "verify_embedding"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_embeddings.rs"^^ . + "false"^^ . + "verify_embeddings"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_input_token.rs"^^ . + "false"^^ . + "verify_input_token"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_lm_head.rs"^^ . + "false"^^ . + "verify_lm_head"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_offsets.rs"^^ . + "false"^^ . + "verify_offsets"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_q4_0_correct.rs"^^ . + "false"^^ . + "verify_q4_0_correct"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_q4_0_full.rs"^^ . + "false"^^ . + "verify_q4_0_full"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_q4_0_matmul.rs"^^ . + "false"^^ . + "verify_q4_0_matmul"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_q4k_layout.rs"^^ . + "false"^^ . + "verify_q4k_layout"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_q8_0.rs"^^ . + "false"^^ . + "verify_q8_0"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_rmsnorm.rs"^^ . + "false"^^ . + "verify_rmsnorm"^^ . + "llama"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_rope.rs"^^ . + "false"^^ . + "verify_rope"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_rope_pos0.rs"^^ . + "true"^^ . + "verify_rope_pos0"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_rope_type.rs"^^ . + "false"^^ . + "verify_rope_type"^^ . + "qwen2"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_tensor_offsets.rs"^^ . + "false"^^ . + "verify_tensor_offsets"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_tinyllama_multitoken.rs"^^ . + "false"^^ . + "verify_tinyllama_multitoken"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/verify_v_parallel_matvec.rs"^^ . + "false"^^ . + "verify_v_parallel_matvec"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/weight_check.rs"^^ . + "false"^^ . + "weight_check"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/wgpu_parity_test.rs"^^ . + "false"^^ . + "wgpu_parity_test"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-serve"^^ . + "crates/aprender-serve/examples/wine_lambda.rs"^^ . + "true"^^ . + "wine_lambda"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/contract_pipeline_demo.rs"^^ . + "true"^^ . + "contract_pipeline_demo"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/edd_falsification.rs"^^ . + "true"^^ . + "edd_falsification"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/edd_model_card.rs"^^ . + "true"^^ . + "edd_model_card"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/edd_operations.rs"^^ . + "true"^^ . + "edd_operations"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/edd_tps_validation.rs"^^ . + "true"^^ . + "edd_tps_validation"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/edd_yaml_loader.rs"^^ . + "true"^^ . + "edd_yaml_loader"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/gui_coverage_demo.rs"^^ . + "true"^^ . + "gui_coverage_demo"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/jidoka_guards.rs"^^ . + "true"^^ . + "jidoka_guards"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/monte_carlo.rs"^^ . + "true"^^ . + "monte_carlo"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/optimization.rs"^^ . + "true"^^ . + "optimization"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/orbit_demo.rs"^^ . + "true"^^ . + "orbit_demo"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/physics_simulation.rs"^^ . + "true"^^ . + "physics_simulation"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/reproducibility.rs"^^ . + "true"^^ . + "reproducibility"^^ . + "false"^^ . + . + . + "aprender-simulate"^^ . + "crates/aprender-simulate/examples/tsp_grasp_demo.rs"^^ . + "true"^^ . + "tsp_grasp_demo"^^ . + "false"^^ . + . + . + "aprender-solve"^^ . + "crates/aprender-solve/examples/solver_demo.rs"^^ . + "true"^^ . + "solver_demo"^^ . + "false"^^ . + . + . + "aprender-sparse"^^ . + "crates/aprender-sparse/examples/sparse_spmv.rs"^^ . + "true"^^ . + "sparse_spmv"^^ . + "false"^^ . + . + . + "aprender-tensor"^^ . + "crates/aprender-tensor/examples/tensor_demo.rs"^^ . + "true"^^ . + "tensor_demo"^^ . + "false"^^ . + . + . + "aprender-test-cli"^^ . + "crates/aprender-test-cli/examples/stress_testing.rs"^^ . + "true"^^ . + "stress_testing"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/accessibility_demo.rs"^^ . + "true"^^ . + "accessibility_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/audio_emulation.rs"^^ . + "true"^^ . + "audio_emulation"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/basic_test.rs"^^ . + "true"^^ . + "basic_test"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/brick_demo.rs"^^ . + "true"^^ . + "brick_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/brick_tui_demo.rs"^^ . + "true"^^ . + "brick_tui_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/comply_demo.rs"^^ . + "true"^^ . + "comply_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/coverage_demo.rs"^^ . + "true"^^ . + "coverage_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/distributed_worker_demo.rs"^^ . + "true"^^ . + "distributed_worker_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/docker_demo.rs"^^ . + "true"^^ . + "docker_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/element_assertions.rs"^^ . + "true"^^ . + "element_assertions"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/equation_verify.rs"^^ . + "true"^^ . + "equation_verify"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/execution_trace.rs"^^ . + "true"^^ . + "execution_trace"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/gui_coverage.rs"^^ . + "true"^^ . + "gui_coverage"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/locator_demo.rs"^^ . + "true"^^ . + "locator_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/locator_operations.rs"^^ . + "true"^^ . + "locator_operations"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/mouse_actions.rs"^^ . + "true"^^ . + "mouse_actions"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/multi_context.rs"^^ . + "true"^^ . + "multi_context"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/network_intercept.rs"^^ . + "true"^^ . + "network_intercept"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/page_object.rs"^^ . + "true"^^ . + "page_object"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/panic_paths_demo.rs"^^ . + "true"^^ . + "panic_paths_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/performance_profile.rs"^^ . + "true"^^ . + "performance_profile"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/pixel_coverage_heatmap.rs"^^ . + "true"^^ . + "pixel_coverage_heatmap"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/playbook_testing.rs"^^ . + "true"^^ . + "playbook_testing"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/pmat_bridge_demo.rs"^^ . + "true"^^ . + "pmat_bridge_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/pong_simulation.rs"^^ . + "true"^^ . + "pong_simulation"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/presentar_demo.rs"^^ . + "true"^^ . + "presentar_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/retry_assertions.rs"^^ . + "true"^^ . + "retry_assertions"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/semantic_locators.rs"^^ . + "true"^^ . + "semantic_locators"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/soft_assertions.rs"^^ . + "true"^^ . + "soft_assertions"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/streaming_ux_demo.rs"^^ . + "true"^^ . + "streaming_ux_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/visual_regression_demo.rs"^^ . + "true"^^ . + "visual_regression_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/wait_mechanisms.rs"^^ . + "true"^^ . + "wait_mechanisms"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/wasm_capabilities.rs"^^ . + "true"^^ . + "wasm_capabilities"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/wasm_pixel_gui_demo.rs"^^ . + "true"^^ . + "wasm_pixel_gui_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/watch_mode.rs"^^ . + "true"^^ . + "watch_mode"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/web_builders_demo.rs"^^ . + "true"^^ . + "web_builders_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/web_sys_gen_demo.rs"^^ . + "true"^^ . + "web_sys_gen_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/web_validation_demo.rs"^^ . + "true"^^ . + "web_validation_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/websocket_monitor.rs"^^ . + "true"^^ . + "websocket_monitor"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/worker_brick_demo.rs"^^ . + "true"^^ . + "worker_brick_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/worker_harness_demo.rs"^^ . + "true"^^ . + "worker_harness_demo"^^ . + "false"^^ . + . + . + "aprender-test-lib"^^ . + "crates/aprender-test-lib/examples/zero_js_demo.rs"^^ . + "true"^^ . + "zero_js_demo"^^ . + "false"^^ . + . + . + "aprender-test-showcase"^^ . + "crates/aprender-test-showcase/examples/calculator_tui.rs"^^ . + "true"^^ . + "calculator_tui"^^ . + "false"^^ . + . + . + "aprender-test-showcase"^^ . + "crates/aprender-test-showcase/examples/calculator_tui_demo.rs"^^ . + "true"^^ . + "calculator_tui_demo"^^ . + "false"^^ . + . + . + "aprender-test-showcase"^^ . + "crates/aprender-test-showcase/examples/calculator_wasm_demo.rs"^^ . + "true"^^ . + "calculator_wasm_demo"^^ . + "false"^^ . + . + . + "aprender-test-showcase"^^ . + "crates/aprender-test-showcase/examples/gui_coverage_report.rs"^^ . + "true"^^ . + "gui_coverage_report"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/calibration_check.rs"^^ . + "true"^^ . + "calibration_check"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/citl.rs"^^ . + "true"^^ . + "citl"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/classify_tune_demo.rs"^^ . + "false"^^ . + "classify_tune_demo"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/cli_audit.rs"^^ . + "true"^^ . + "cli_audit"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/cli_bench.rs"^^ . + "true"^^ . + "cli_bench"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/cli_inspect.rs"^^ . + "true"^^ . + "cli_inspect"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/cli_monitor.rs"^^ . + "true"^^ . + "cli_monitor"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/cluster_training.rs"^^ . + "true"^^ . + "cluster_training"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/contract_pipeline_demo.rs"^^ . + "true"^^ . + "contract_pipeline_demo"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/cuda_backend.rs"^^ . + "true"^^ . + "cuda_backend"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/cuda_training_benchmark.rs"^^ . + "true"^^ . + "cuda_training_benchmark"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/design_by_contract.rs"^^ . + "false"^^ . + "design_by_contract"^^ . + "llama"^^ . + "mistral"^^ . + "qwen2"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/distillation.rs"^^ . + "true"^^ . + "distillation"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/drift_simulation.rs"^^ . + "true"^^ . + "drift_simulation"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/encoder_linear_probe.rs"^^ . + "true"^^ . + "encoder_linear_probe"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/explainability.rs"^^ . + "true"^^ . + "explainability"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/finetune_real.rs"^^ . + "false"^^ . + "finetune_real"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/finetune_test_gen.rs"^^ . + "false"^^ . + "finetune_test_gen"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/gemm_bench.rs"^^ . + "true"^^ . + "gemm_bench"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/gpu_ledger.rs"^^ . + "true"^^ . + "gpu_ledger"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/hf_distillation.rs"^^ . + "false"^^ . + "hf_distillation"^^ . + "llama"^^ . + "tinyllama"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/inference_monitor.rs"^^ . + "true"^^ . + "inference_monitor"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/merge_models.rs"^^ . + "true"^^ . + "merge_models"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/mnist_train.rs"^^ . + "true"^^ . + "mnist_train"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/mnist_train_gpu.rs"^^ . + "true"^^ . + "mnist_train_gpu"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/model_io.rs"^^ . + "true"^^ . + "model_io"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/monitoring.rs"^^ . + "true"^^ . + "monitoring"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/multi_adapter_training.rs"^^ . + "true"^^ . + "multi_adapter_training"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/nvml_test.rs"^^ . + "true"^^ . + "nvml_test"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/open_asr_leaderboard.rs"^^ . + "true"^^ . + "open_asr_leaderboard"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/profile_cuda_trainer.rs"^^ . + "true"^^ . + "profile_cuda_trainer"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/pruning_pipeline.rs"^^ . + "true"^^ . + "pruning_pipeline"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/research.rs"^^ . + "true"^^ . + "research"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/shell_safety_classify.rs"^^ . + "false"^^ . + "shell_safety_classify"^^ . + "qwen2"^^ . + "qwen2.5"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/sovereign.rs"^^ . + "true"^^ . + "sovereign"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/ssc_eval.rs"^^ . + "false"^^ . + "ssc_eval"^^ . + "qwen3"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/ssc_preflight.rs"^^ . + "false"^^ . + "ssc_preflight"^^ . + "qwen3"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/train_from_yaml.rs"^^ . + "true"^^ . + "train_from_yaml"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/train_from_yaml_example.rs"^^ . + "true"^^ . + "train_from_yaml_example"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/training_loop.rs"^^ . + "true"^^ . + "training_loop"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/wgpu_canary.rs"^^ . + "true"^^ . + "wgpu_canary"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/wgpu_eval.rs"^^ . + "false"^^ . + "wgpu_eval"^^ . + "qwen3"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/wgpu_train.rs"^^ . + "false"^^ . + "wgpu_train"^^ . + "qwen3"^^ . + "false"^^ . + . + . + "aprender-train"^^ . + "crates/aprender-train/examples/wgpu_train_step.rs"^^ . + "true"^^ . + "wgpu_train_step"^^ . + "false"^^ . + . + . + "aprender-tsp"^^ . + "crates/aprender-tsp/examples/aprender-tsp.rs"^^ . + "true"^^ . + "aprender-tsp"^^ . + "false"^^ . + . + . + "aprender-tsp"^^ . + "crates/aprender-tsp/examples/tsp_algorithm_comparison.rs"^^ . + "true"^^ . + "tsp_algorithm_comparison"^^ . + "false"^^ . + . + . + "aprender-tsp"^^ . + "crates/aprender-tsp/examples/tsp_benchmark.rs"^^ . + "true"^^ . + "tsp_benchmark"^^ . + "false"^^ . + . + . + "aprender-tsp"^^ . + "crates/aprender-tsp/examples/tsp_model_persistence.rs"^^ . + "true"^^ . + "tsp_model_persistence"^^ . + "false"^^ . + . + . + "aprender-verify-ml"^^ . + "crates/aprender-verify-ml/examples/backend_selector.rs"^^ . + "true"^^ . + "backend_selector"^^ . + "false"^^ . + . + . + "aprender-verify-ml"^^ . + "crates/aprender-verify-ml/examples/experiment_tracking.rs"^^ . + "true"^^ . + "experiment_tracking"^^ . + "false"^^ . + . + . + "aprender-verify-ml"^^ . + "crates/aprender-verify-ml/examples/generator_basic.rs"^^ . + "true"^^ . + "generator_basic"^^ . + "false"^^ . + . + . + "aprender-verify-ml"^^ . + "crates/aprender-verify-ml/examples/grammar_validation.rs"^^ . + "true"^^ . + "grammar_validation"^^ . + "false"^^ . + . + . + "aprender-verify"^^ . + "crates/aprender-verify/examples/quality_check.rs"^^ . + "true"^^ . + "quality_check"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/aprender_integration.rs"^^ . + "true"^^ . + "aprender_integration"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/box_violin.rs"^^ . + "true"^^ . + "box_violin"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/confusion_matrix_ml.rs"^^ . + "true"^^ . + "confusion_matrix_ml"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/dashboard_widgets.rs"^^ . + "true"^^ . + "dashboard_widgets"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/force_graph.rs"^^ . + "true"^^ . + "force_graph"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/grammar_of_graphics.rs"^^ . + "true"^^ . + "grammar_of_graphics"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/heatmap_correlation.rs"^^ . + "true"^^ . + "heatmap_correlation"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/loss_training.rs"^^ . + "true"^^ . + "loss_training"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/readme_demo.rs"^^ . + "true"^^ . + "readme_demo"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/roc_pr_curves.rs"^^ . + "true"^^ . + "roc_pr_curves"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/scatter_basic.rs"^^ . + "true"^^ . + "scatter_basic"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/svg_output.rs"^^ . + "true"^^ . + "svg_output"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/terminal_output.rs"^^ . + "true"^^ . + "terminal_output"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/text_prompt.rs"^^ . + "true"^^ . + "text_prompt"^^ . + "false"^^ . + . + . + "aprender-viz"^^ . + "crates/aprender-viz/examples/trueno_graph_integration.rs"^^ . + "true"^^ . + "trueno_graph_integration"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/compress_benchmark.rs"^^ . + "true"^^ . + "compress_benchmark"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/debug_batch_compressor.rs"^^ . + "true"^^ . + "debug_batch_compressor"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/debug_decompress.rs"^^ . + "true"^^ . + "debug_decompress"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/decompress_throughput.rs"^^ . + "true"^^ . + "decompress_throughput"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/g119_parallel_benchmark.rs"^^ . + "true"^^ . + "g119_parallel_benchmark"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/gpu_decompress_benchmark.rs"^^ . + "true"^^ . + "gpu_decompress_benchmark"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/gpu_info.rs"^^ . + "true"^^ . + "gpu_info"^^ . + "false"^^ . + . + . + "aprender-zram-core"^^ . + "crates/aprender-zram-core/examples/parallel_scaling.rs"^^ . + "true"^^ . + "parallel_scaling"^^ . + "false"^^ . + . + . + . + . + "3022"^^ . + "paiml/aprender#3022@2026-09-10T03:13:40Z"^^ . + "paiml/aprender"^^ . + "CLOSED"^^ . + "apr chat silently loads the toy Demo model for sharded SafeTensors (.safetensors.index.json) instead of the real model"^^ . + "2026-09-10T03:13:40Z"^^ . + "https://github.com/paiml/aprender/issues/3022"^^ . + . + . + . + "3"^^ . + "paiml/aprender#3@2026-09-24T07:03:22Z"^^ . + "paiml/aprender"^^ . + "CLOSED"^^ . + "0.66.0"^^ . + "2026-09-24T07:03:22Z"^^ . + "https://github.com/paiml/aprender/milestone/3"^^ . . . "qwen35"^^ . @@ -9738,6 +18068,20 @@ "model_sha256: NOT RECORDED at measurement time and deliberately not back-filled. Hashing the file on the host today would attach a claim about a different world to a receipt about 2026-09-09. Absent means no measurement, never a match (ONT-4c1)."^^ . . . + . + . + . + "main"^^ . + . + "PMAT-3704-ont-4c3-pc-extract"^^ . + "2026-09-21T16:17:54Z"^^ . + "3706"^^ . + "paiml/aprender#3706@2026-09-21T16:17:56Z"^^ . + "paiml/aprender"^^ . + "MERGED"^^ . + "PMAT-3704 / ONT-4c3: pc_extract draws one planted defect per implemented extractor — parity-receipt, json and pv-contract gain their controls, and the set is tied to Σ"^^ . + "2026-09-21T16:17:56Z"^^ . + "https://github.com/paiml/aprender/pull/3706"^^ . . "cpu=ok"^^ . "cuda=ok"^^ . @@ -10372,7 +18716,16 @@ "apr bench refuses qwen35 outright: the subcommand routes through the dense QuantizedGGUFTransformer loader, which does not carry the Gated-DeltaNet hybrid, so the benchmark verb cannot measure the architecture the last release shipped."^^ . "unscheduled"^^ . "bench"^^ . + . + . + . + "main"^^ . + "paiml/aprender"^^ . + "paiml/aprender@aa7c6ef03ee7b7f8d8dc09dc393e97619952d95f"^^ . + "aa7c6ef03ee7b7f8d8dc09dc393e97619952d95f"^^ . + "https://github.com/paiml/aprender"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10387,6 +18740,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10400,13 +18754,29 @@ "run"^^ . "true"^^ . "pub"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "apr_cli"^^ . + "byte_encoder_coverage"^^ . + "crates/apr-cli/src/commands/chat_load_tokenizers.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::chat::realizar_chat"^^ . + "load_tokenizers"^^ . + "true"^^ . + "private"^^ . . + . . "allow"^^ . "doc"^^ . "provable_contracts_macros::contract"^^ . "implemented"^^ . "apr_cli"^^ . + "session_persistence"^^ . "session_state_machine"^^ . "crates/apr-cli/src/commands/chat.rs"^^ . . @@ -10415,27 +18785,8 @@ "run"^^ . "true"^^ . "pub(crate)"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "byte_encoder_coverage"^^ . - . - "apr_cli::commands::chat_load_tokenizers"^^ . - "load_tokenizers"^^ . - "false"^^ . - "no `mod chat_load_tokenizers` or `use … chat_load_tokenizers` in `crates/apr-cli/src/commands/mod.rs`"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "session_persistence"^^ . - . - "apr_cli::commands::chat_session"^^ . - "run"^^ . - "false"^^ . - "no `mod chat_session` or `use … chat_session` in `crates/apr-cli/src/commands/mod.rs`"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10449,18 +18800,53 @@ "run"^^ . "true"^^ . "pub(crate)"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "data_split_determinism"^^ . - "data_validation"^^ . - . - "apr_cli::commands::data"^^ . - "run"^^ . - "false"^^ . - "no `fn run` (free or in an impl) in `crates/apr-cli/src/commands/data.rs`"^^ . + . + . + . + "allow"^^ . + "doc"^^ . + "provable_contracts_macros::contract"^^ . + "implemented"^^ . + "apr_cli"^^ . + "format_conversion_roundtrip"^^ . + "crates/apr-cli/src/commands/convert.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::convert"^^ . + "run"^^ . + "true"^^ . + "pub(crate)"^^ . + . + . + . + "doc"^^ . + "provable_contracts_macros::contract"^^ . + "implemented"^^ . + "apr_cli"^^ . + "data_validation"^^ . + "crates/apr-cli/src/commands/data.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::data"^^ . + "run_audit"^^ . + "true"^^ . + "pub(crate)"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "apr_cli"^^ . + "data_split_determinism"^^ . + "crates/apr-cli/src/commands/data.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::data"^^ . + "run_split"^^ . + "true"^^ . + "pub(crate)"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10475,17 +18861,8 @@ "run"^^ . "true"^^ . "pub(crate)"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "encryption_roundtrip"^^ . - . - "apr_cli::commands::encrypt"^^ . - "run"^^ . - "false"^^ . - "no `mod encrypt` or `use … encrypt` in `crates/apr-cli/src/commands/mod.rs`"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -10499,6 +18876,7 @@ "true"^^ . "pub(super)"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10506,16 +18884,19 @@ "implemented"^^ . "apr_cli"^^ . "atomic_write_safety"^^ . + "export_fidelity"^^ . "export_roundtrip"^^ . "crates/apr-cli/src/commands/export.rs"^^ . . . + . "fn"^^ . "apr_cli::commands::export"^^ . "run"^^ . "true"^^ . "pub(crate)"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10530,6 +18911,7 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -10543,6 +18925,7 @@ "true"^^ . "private"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10563,6 +18946,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "provable_contracts_macros::contract"^^ . "implemented"^^ . @@ -10576,6 +18960,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10590,20 +18975,24 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . "implemented"^^ . "apr_cli"^^ . "import_format_detection"^^ . + "import_integrity"^^ . "crates/apr-cli/src/commands/import.rs"^^ . . + . "fn"^^ . "apr_cli::commands::import"^^ . "run"^^ . "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10618,6 +19007,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10633,26 +19023,39 @@ "true"^^ . "pub(crate)"^^ . . + . . + "allow"^^ . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "apr_cli"^^ . "oracle_family_detection"^^ . + "crates/apr-cli/src/commands/oracle_flags.rs"^^ . . + "fn"^^ . "apr_cli::commands::oracle"^^ . "run"^^ . - "false"^^ . - "no `fn run` (free or in an impl) in `crates/apr-cli/src/commands/oracle.rs`"^^ . + "true"^^ . + "pub(crate)"^^ . . + . . + "cfg"^^ . + "cfg_attr"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "apr_cli"^^ . "gpu_cpu_parity"^^ . + "crates/apr-cli/src/commands/parity_03.rs"^^ . . + "fn"^^ . "apr_cli::commands::parity"^^ . "run"^^ . - "false"^^ . - "no `fn run` (free or in an impl) in `crates/apr-cli/src/commands/parity.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10667,6 +19070,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10682,6 +19086,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10695,17 +19100,24 @@ "run"^^ . "true"^^ . "pub(crate)"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "publish_manifest_integrity"^^ . - . - "apr_cli::commands::publish"^^ . - "run"^^ . - "false"^^ . - "no `fn run` (free or in an impl) in `crates/apr-cli/src/commands/publish.rs`"^^ . + . + . + . + "allow"^^ . + "doc"^^ . + "provable_contracts_macros::contract"^^ . + "implemented"^^ . + "apr_cli"^^ . + "publish_manifest_integrity"^^ . + "crates/apr-cli/src/commands/publish.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::publish"^^ . + "execute"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -10720,6 +19132,7 @@ "true"^^ . "pub"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10735,6 +19148,7 @@ "true"^^ . "pub"^^ . . + . . "allow"^^ . "doc"^^ . @@ -10749,17 +19163,21 @@ "run"^^ . "true"^^ . "pub(crate)"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "rosetta_fingerprint_determinism"^^ . - . - "apr_cli::commands::rosetta"^^ . - "run"^^ . - "false"^^ . - "no `fn run` (free or in an impl) in `crates/apr-cli/src/commands/rosetta.rs`"^^ . + . + . + . + "implemented"^^ . + "apr_cli"^^ . + "rosetta_fingerprint_determinism"^^ . + "crates/apr-cli/src/commands/rosetta_diff_tensor.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::rosetta"^^ . + "run_fingerprint"^^ . + "true"^^ . + "pub"^^ . . + . . "cfg"^^ . "doc"^^ . @@ -10774,6 +19192,7 @@ "true"^^ . "private"^^ . . + . . "cfg"^^ . "doc"^^ . @@ -10788,6 +19207,7 @@ "true"^^ . "private"^^ . . + . . "allow"^^ . "cfg"^^ . @@ -10803,12 +19223,14 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . "implemented"^^ . "apr_cli"^^ . "concurrent_isolation"^^ . + "concurrent_model_access"^^ . "cors_negotiation"^^ . "graceful_shutdown"^^ . "request_routing"^^ . @@ -10817,6 +19239,7 @@ "session_state_machine"^^ . "crates/apr-cli/src/commands/serve/mod.rs"^^ . . + . . . . @@ -10826,6 +19249,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -10839,6 +19263,7 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -10851,85 +19276,104 @@ "format_tools_prompt"^^ . "true"^^ . "pub(super)"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "tokenizer_training_correctness"^^ . - . - "apr_cli::commands::tokenize"^^ . - "run"^^ . - "false"^^ . - "no `fn run` (free or in an impl) in `crates/apr-cli/src/commands/tokenize.rs`"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "training_plan_apply_semantics"^^ . - . - "apr_cli::commands::train"^^ . - "run"^^ . - "false"^^ . - "no `fn run` (free or in an impl) in `crates/apr-cli/src/commands/train.rs`"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "dispatch_completeness"^^ . - "output_format_fidelity"^^ . - "resource_cleanup"^^ . - "side_effect_classification"^^ . - . - . - "apr_cli::dispatch"^^ . - "dispatch_core_command"^^ . - "false"^^ . - "no `mod dispatch` or `use … dispatch` in `crates/apr-cli/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "idempotent_inspection"^^ . - "idempotent_output"^^ . - "no_side_effects"^^ . - . - . - "apr_cli::dispatch"^^ . - "dispatch_inspection_commands"^^ . - "false"^^ . - "no `mod dispatch` or `use … dispatch` in `crates/apr-cli/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "output_path_validation"^^ . - "rm_confirmation_gate"^^ . - . - "apr_cli::dispatch"^^ . - "dispatch_model_commands"^^ . - "false"^^ . - "no `mod dispatch` or `use … dispatch` in `crates/apr-cli/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "contract_gate_enforcement"^^ . - . - "apr_cli::dispatch"^^ . - "execute_command"^^ . - "false"^^ . - "no `mod dispatch` or `use … dispatch` in `crates/apr-cli/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "apr_cli"^^ . - "feature_gated_dispatch"^^ . - . - "apr_cli::dispatch_analysis"^^ . - "dispatch_extended_command"^^ . - "false"^^ . - "no `mod dispatch_analysis` or `use … dispatch_analysis` in `crates/apr-cli/src/lib.rs`"^^ . + . + . + . + "doc"^^ . + "provable_contracts_macros::contract"^^ . + "implemented"^^ . + "apr_cli"^^ . + "tokenizer_training_correctness"^^ . + "crates/apr-cli/src/commands/tokenize.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::tokenize"^^ . + "run_apply"^^ . + "true"^^ . + "pub(crate)"^^ . + . + . + . + "allow"^^ . + "doc"^^ . + "implemented"^^ . + "apr_cli"^^ . + "training_plan_apply_semantics"^^ . + "crates/apr-cli/src/commands/train.rs"^^ . + . + "fn"^^ . + "apr_cli::commands::train"^^ . + "run_apply"^^ . + "true"^^ . + "pub(crate)"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "apr_cli"^^ . + "dispatch_completeness"^^ . + "output_format_fidelity"^^ . + "resource_cleanup"^^ . + "side_effect_classification"^^ . + "crates/apr-cli/src/dispatch.rs"^^ . + . + . + "fn"^^ . + "apr_cli"^^ . + "dispatch_core_command"^^ . + "true"^^ . + "private"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "apr_cli"^^ . + "feature_gated_dispatch"^^ . + "crates/apr-cli/src/dispatch_analysis.rs"^^ . + . + "fn"^^ . + "apr_cli"^^ . + "dispatch_extended_command"^^ . + "true"^^ . + "private"^^ . + . + . + . + "allow"^^ . + "doc"^^ . + "implemented"^^ . + "apr_cli"^^ . + "idempotent_inspection"^^ . + "idempotent_output"^^ . + "no_side_effects"^^ . + "crates/apr-cli/src/dispatch.rs"^^ . + . + . + "fn"^^ . + "apr_cli"^^ . + "dispatch_inspection_commands"^^ . + "true"^^ . + "private"^^ . + . + . + . + "doc"^^ . + "provable_contracts_macros::contract"^^ . + "implemented"^^ . + "apr_cli"^^ . + "output_path_validation"^^ . + "rm_confirmation_gate"^^ . + "crates/apr-cli/src/dispatch.rs"^^ . + . + "fn"^^ . + "apr_cli"^^ . + "dispatch_model_commands"^^ . + "true"^^ . + "private"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -10950,6 +19394,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -10962,7 +19407,22 @@ "resolve_model_path"^^ . "true"^^ . "pub"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "apr_cli"^^ . + "contract_gate_enforcement"^^ . + "crates/apr-cli/src/dispatch_run.rs"^^ . + . + "fn"^^ . + "apr_cli"^^ . + "execute_command"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -10976,6 +19436,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -10990,6 +19451,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11005,6 +19467,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11020,6 +19483,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11034,6 +19498,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11047,6 +19512,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11060,6 +19526,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -11074,6 +19541,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -11088,6 +19556,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11101,6 +19570,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11115,29 +19585,10 @@ "select_backend"^^ . "true"^^ . "pub"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "format_conversion_roundtrip"^^ . - . - "aprender::convert"^^ . - "run"^^ . - "false"^^ . - "no `mod convert` or `use … convert` in `crates/aprender-core/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "apr_tokenizer_embedding"^^ . - . - "aprender::convert"^^ . - "save_model_tensors_with_gguf_config_and_tokenizer"^^ . - "false"^^ . - "no `mod convert` or `use … convert` in `crates/aprender-core/src/lib.rs`"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "streaming_data_loader"^^ . . @@ -11145,37 +19596,51 @@ "next_batch"^^ . "false"^^ . "no `mod dataloader` or `use … dataloader` in `crates/aprender-core/src/data/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "export_fidelity"^^ . - . - "aprender::export"^^ . - "run"^^ . - "false"^^ . - "no `mod export` or `use … export` in `crates/aprender-core/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "name_bijection"^^ . - . - "aprender::format::converter"^^ . - "map_tensor_name"^^ . - "false"^^ . - "no `fn map_tensor_name` (free or in an impl) in `crates/aprender-core/src/format/converter/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "identity"^^ . - . - "aprender::format::converter::tokenizer_loader"^^ . - "load_tokenizer_from_explicit_path"^^ . - "false"^^ . - "no `mod tokenizer_loader` or `use … tokenizer_loader` in `crates/aprender-core/src/format/converter/mod.rs`"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "aprender"^^ . + "identity"^^ . + "crates/aprender-core/src/format/converter/tokenizer_loader.rs"^^ . + . + "fn"^^ . + "aprender::format::converter::import"^^ . + "load_tokenizer_from_explicit_path"^^ . + "true"^^ . + "pub(crate)"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "aprender"^^ . + "apr_tokenizer_embedding"^^ . + "crates/aprender-core/src/format/converter/inferred_q4k_config.rs"^^ . + . + "fn"^^ . + "aprender::format::converter"^^ . + "save_model_tensors_with_gguf_config_and_tokenizer"^^ . + "true"^^ . + "private"^^ . + . + . + . + "doc"^^ . + "must_use"^^ . + "implemented"^^ . + "aprender"^^ . + "name_bijection"^^ . + "crates/aprender-core/src/format/tensor_expectation.rs"^^ . + . + "method"^^ . + "aprender::format::converter_types::Architecture"^^ . + "map_name"^^ . + "true"^^ . + "pub"^^ . . + . . "cfg"^^ . "doc"^^ . @@ -11189,17 +19654,38 @@ "parse_and_validate_header"^^ . "true"^^ . "pub(crate)"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "architecture_config_invariants"^^ . - . - "aprender::format::gguf::api::GgufModelConfig"^^ . - "validate"^^ . - "false"^^ . - "no `mod GgufModelConfig` or `use … GgufModelConfig` in `crates/aprender-core/src/format/gguf/api.rs`"^^ . + . + . + . + "allow"^^ . + "cfg"^^ . + "doc"^^ . + "implemented"^^ . + "aprender"^^ . + "encryption_roundtrip"^^ . + "crates/aprender-core/src/format/encryption.rs"^^ . + . + "fn"^^ . + "aprender::format::encryption"^^ . + "save_encrypted"^^ . + "true"^^ . + "pub"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "aprender"^^ . + "architecture_config_invariants"^^ . + "crates/aprender-core/src/format/gguf/api.rs"^^ . + . + "method"^^ . + "aprender::format::gguf::api::GgufModelConfig"^^ . + "warn_out_of_bounds"^^ . + "true"^^ . + "pub"^^ . . + . . "implemented"^^ . "aprender"^^ . @@ -11212,46 +19698,59 @@ "true"^^ . "pub(crate)"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "transpose_involution"^^ . . "aprender::format::layout"^^ . "swap_axes"^^ . "false"^^ . - "no `mod layout` or `use … layout` in `crates/aprender-core/src/format/mod.rs`"^^ . + "no `mod layout` or `use … layout` in `crates/aprender-core/src/format/encryption.rs`"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "element_count"^^ . . "aprender::format::layout"^^ . "validate_element_count"^^ . "false"^^ . - "no `mod layout` or `use … layout` in `crates/aprender-core/src/format/mod.rs`"^^ . + "no `mod layout` or `use … layout` in `crates/aprender-core/src/format/encryption.rs`"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender"^^ . "import_completeness_gate"^^ . + "crates/aprender-core/src/format/layout_contract_enforce.rs"^^ . . + "fn"^^ . "aprender::format::layout_contract"^^ . "enforce_architecture_completeness"^^ . - "false"^^ . - "no `fn enforce_architecture_completeness` (free or in an impl) in `crates/aprender-core/src/format/layout_contract.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "must_use"^^ . "implemented"^^ . "aprender"^^ . "tensor_transpose_reindex"^^ . + "crates/aprender-core/src/format/layout_contract_specs.rs"^^ . . + "method"^^ . "aprender::format::layout_contract"^^ . "should_transpose_gguf"^^ . - "false"^^ . - "no `fn should_transpose_gguf` (free or in an impl) in `crates/aprender-core/src/format/layout_contract.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11266,6 +19765,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11280,6 +19780,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11294,6 +19795,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11308,6 +19810,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11321,9 +19824,23 @@ "throughput_model"^^ . "true"^^ . "pub"^^ . + . + . + . + "implemented"^^ . + "aprender"^^ . + "quantization_bounds"^^ . + "crates/aprender-core/src/format/quantize.rs"^^ . + . + "method"^^ . + "aprender::format"^^ . + "quantize_data"^^ . + "true"^^ . + "private"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "magic_byte_validation"^^ . . @@ -11331,19 +19848,10 @@ "detect_format"^^ . "false"^^ . "no `mod gguf` or `use … gguf` in `crates/aprender-core/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "metadata_kv_safety"^^ . - . - "aprender::gguf::reader"^^ . - "parse_metadata"^^ . - "false"^^ . - "no `mod gguf` or `use … gguf` in `crates/aprender-core/src/lib.rs`"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "header_integrity"^^ . . @@ -11351,17 +19859,8 @@ "validate_header"^^ . "false"^^ . "no `mod gguf` or `use … gguf` in `crates/aprender-core/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "magic_validation"^^ . - . - "aprender::gguf::reader"^^ . - "validate_magic"^^ . - "false"^^ . - "no `mod gguf` or `use … gguf` in `crates/aprender-core/src/lib.rs`"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11375,6 +19874,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11389,47 +19889,51 @@ "true"^^ . "pub"^^ . . + . . + "doc"^^ . "implemented"^^ . "aprender"^^ . "global_max_pool"^^ . + "crates/aprender-core/src/gnn/accumulate.rs"^^ . . + "fn"^^ . "aprender::gnn"^^ . "global_max_pool"^^ . - "false"^^ . - "no `fn global_max_pool` (free or in an impl) in `crates/aprender-core/src/gnn/mod.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "must_use"^^ . "implemented"^^ . "aprender"^^ . "global_mean_pool"^^ . + "crates/aprender-core/src/gnn/accumulate.rs"^^ . . + "fn"^^ . "aprender::gnn"^^ . "global_mean_pool"^^ . - "false"^^ . - "no `fn global_mean_pool` (free or in an impl) in `crates/aprender-core/src/gnn/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "pagerank"^^ . - "power_iteration"^^ . - . - "aprender::graph"^^ . - "pagerank"^^ . - "false"^^ . - "no `fn pagerank` (free or in an impl) in `crates/aprender-core/src/graph/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "import_integrity"^^ . - . - "aprender::import"^^ . - "run"^^ . - "false"^^ . - "no `mod import` or `use … import` in `crates/aprender-core/src/lib.rs`"^^ . + "true"^^ . + "pub"^^ . + . + . + . + "provable_contracts_macros::contract"^^ . + "implemented"^^ . + "aprender"^^ . + "pagerank"^^ . + "power_iteration"^^ . + "crates/aprender-core/src/graph/centrality.rs"^^ . + . + "method"^^ . + "aprender::graph::centrality::Graph"^^ . + "pagerank"^^ . + "true"^^ . + "private"^^ . . + . . "implemented"^^ . "aprender"^^ . @@ -11440,8 +19944,9 @@ "false"^^ . "no `mod inference` or `use … inference` in `crates/aprender-core/src/lib.rs`"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "generation_temperature_zero"^^ . "inference_determinism"^^ . @@ -11452,6 +19957,7 @@ "false"^^ . "no `mod inference` or `use … inference` in `crates/aprender-core/src/lib.rs`"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11467,16 +19973,23 @@ "true"^^ . "pub"^^ . . + . . + "doc"^^ . + "must_use"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender"^^ . "confusion_matrix"^^ . + "crates/aprender-core/src/metrics/classification_include_01.rs"^^ . . + "fn"^^ . "aprender::metrics::classification"^^ . "confusion_matrix"^^ . - "false"^^ . - "no `fn confusion_matrix` (free or in an impl) in `crates/aprender-core/src/metrics/classification.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11492,6 +20005,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11507,6 +20021,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11522,6 +20037,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11537,6 +20053,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11552,6 +20069,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11567,6 +20085,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11582,6 +20101,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11596,6 +20116,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11610,6 +20131,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11624,6 +20146,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11638,6 +20161,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11653,6 +20177,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11666,6 +20191,7 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11681,46 +20207,63 @@ "true"^^ . "pub"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "cls_pooling"^^ . . "aprender::models::bert"^^ . "cls_embedding"^^ . "false"^^ . - "no `fn cls_embedding` (free or in an impl) in `crates/aprender-core/src/models/bert/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "encoder_layer"^^ . - . - "aprender::models::bert"^^ . - "forward"^^ . - "false"^^ . - "no `fn forward` (free or in an impl) in `crates/aprender-core/src/models/bert/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "swiglu_expansion"^^ . - . - "aprender::models::qwen2::constructors"^^ . - "new"^^ . - "false"^^ . - "no `mod constructors` or `use … constructors` in `crates/aprender-core/src/models/qwen2/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "total_parameters"^^ . - . - "aprender::models::qwen2::constructors"^^ . - "weight_names"^^ . - "false"^^ . - "no `mod constructors` or `use … constructors` in `crates/aprender-core/src/models/qwen2/mod.rs`"^^ . + "no `fn cls_embedding` (free or in an impl) or item `cls_embedding` in `crates/aprender-core/src/models/bert/mod.rs`"^^ . + . + . + . + "doc"^^ . + "must_use"^^ . + "implemented"^^ . + "aprender"^^ . + "encoder_layer"^^ . + "crates/aprender-core/src/models/bert/layer.rs"^^ . + . + "method"^^ . + "aprender::models::bert::layer::BertLayer"^^ . + "forward"^^ . + "true"^^ . + "pub"^^ . + . + . + . + "doc"^^ . + "must_use"^^ . + "implemented"^^ . + "aprender"^^ . + "swiglu_expansion"^^ . + "crates/aprender-core/src/models/qwen2/constructors.rs"^^ . + . + "method"^^ . + "aprender::models::qwen2::Qwen2Model"^^ . + "new"^^ . + "true"^^ . + "pub"^^ . + . + . + . + "doc"^^ . + "must_use"^^ . + "implemented"^^ . + "aprender"^^ . + "total_parameters"^^ . + "crates/aprender-core/src/models/qwen2/constructors.rs"^^ . + . + "method"^^ . + "aprender::models::qwen2::Qwen2Model"^^ . + "weight_names"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11736,6 +20279,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11751,6 +20295,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11766,6 +20311,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11783,6 +20329,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11798,6 +20345,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11811,27 +20359,34 @@ "with_top_p"^^ . "true"^^ . "pub"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "rope_position_encoding"^^ . - . - "aprender::nn::rope::RotaryPositionEmbedding"^^ . - "validate_config"^^ . - "false"^^ . - "no `mod rope` or `use … rope` in `crates/aprender-core/src/nn/mod.rs`"^^ . + . + . + . + "doc"^^ . + "must_use"^^ . + "implemented"^^ . + "aprender"^^ . + "rope_position_encoding"^^ . + "crates/aprender-core/src/nn/transformer/attention_helpers.rs"^^ . + . + "method"^^ . + "aprender::nn::transformer::attention_helpers::RotaryPositionEmbedding"^^ . + "with_base"^^ . + "true"^^ . + "pub"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "aprender"^^ . "bidirectional_attention"^^ . . "aprender::nn::transformer"^^ . "bidirectional_attention"^^ . "false"^^ . - "no `fn bidirectional_attention` (free or in an impl) in `crates/aprender-core/src/nn/transformer/mod.rs`"^^ . + "no `fn bidirectional_attention` (free or in an impl) or item `bidirectional_attention` in `crates/aprender-core/src/nn/transformer/attention_helpers.rs`"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -11850,6 +20405,7 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -11864,6 +20420,7 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -11877,47 +20434,24 @@ "minimize"^^ . "true"^^ . "private"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "quantization_bounds"^^ . - . - "aprender::quantize"^^ . - "quantize_tensor"^^ . - "false"^^ . - "no `mod quantize` or `use … quantize` in `crates/aprender-core/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "header_size_validation"^^ . - . - "aprender::safetensors::reader"^^ . - "validate_header"^^ . - "false"^^ . - "no `mod safetensors` or `use … safetensors` in `crates/aprender-core/src/lib.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "concurrent_model_access"^^ . - . - "aprender::serve"^^ . - "run"^^ . - "false"^^ . - "no `mod serve` or `use … serve` in `crates/aprender-core/src/lib.rs`"^^ . . + . . + "derive"^^ . + "doc"^^ . + "serde"^^ . "implemented"^^ . "aprender"^^ . "artifact_doc_schema"^^ . + "crates/aprender-core/src/setfit/artifact.rs"^^ . . + "struct"^^ . "aprender::setfit::artifact"^^ . "SetFitArtifactDoc"^^ . - "false"^^ . - "no `fn SetFitArtifactDoc` (free or in an impl) in `crates/aprender-core/src/setfit/artifact.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11932,6 +20466,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -11946,6 +20481,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11959,6 +20495,7 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11973,6 +20510,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11986,6 +20524,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -11999,6 +20538,7 @@ "true"^^ . "private"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12012,6 +20552,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12026,38 +20567,68 @@ "true"^^ . "pub"^^ . . + . . + "derive"^^ . + "doc"^^ . + "serde"^^ . "implemented"^^ . "aprender"^^ . "classify_response_schema"^^ . + "crates/aprender-core/src/setfit/classify.rs"^^ . . + "struct"^^ . "aprender::setfit::classify"^^ . "ClassifyResponse"^^ . - "false"^^ . - "no `fn ClassifyResponse` (free or in an impl) in `crates/aprender-core/src/setfit/classify.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "must_use"^^ . "implemented"^^ . "aprender"^^ . "backend_identity"^^ . + "crates/aprender-core/src/setfit/encoder.rs"^^ . . + "method"^^ . "aprender::setfit::encoder::ExecutionBackend"^^ . "identity"^^ . - "false"^^ . - "no `mod ExecutionBackend` or `use … ExecutionBackend` in `crates/aprender-core/src/setfit/encoder.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "roundtrip_encoding"^^ . - "tokenizer_consistency"^^ . - . - . - "aprender::tokenizer"^^ . - "encode"^^ . - "false"^^ . - "no `mod tokenizer` or `use … tokenizer` in `crates/aprender-core/src/lib.rs`"^^ . + "true"^^ . + "pub"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "aprender"^^ . + "roundtrip_encoding"^^ . + "tokenizer_consistency"^^ . + "crates/aprender-core/src/text/tokenize/bpe_encoding.rs"^^ . + . + . + "method"^^ . + "aprender::text::tokenize::bpe_impl::BpeTokenizer"^^ . + "encode"^^ . + "true"^^ . + "pub"^^ . + . + . + . + "doc"^^ . + "implemented"^^ . + "aprender"^^ . + "mse_split"^^ . + "crates/aprender-core/src/tree/regression_helpers.rs"^^ . + . + "fn"^^ . + "aprender::tree::helpers"^^ . + "compute_mse"^^ . + "true"^^ . + "pub(super)"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12071,6 +20642,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12083,37 +20655,23 @@ "gini_split"^^ . "true"^^ . "pub"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "mse_split"^^ . - . - "aprender::tree::helpers_part_02"^^ . - "compute_mse"^^ . - "false"^^ . - "no `mod helpers_part_02` or `use … helpers_part_02` in `crates/aprender-core/src/tree/mod.rs`"^^ . - . - . - "implemented"^^ . - "aprender"^^ . - "mse_split"^^ . - . - "aprender::tree::regression_helpers"^^ . - "compute_mse"^^ . - "false"^^ . - "no `mod regression_helpers` or `use … regression_helpers` in `crates/aprender-core/src/tree/mod.rs`"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "dataset_attestation"^^ . + "crates/aprender-contrastive-data/src/attestation.rs"^^ . . + "method"^^ . "aprender_contrastive_data::attestation::PreparedDataset::"^^ . "from_attested_bytes"^^ . - "false"^^ . - "no `mod PreparedDataset` or `use … PreparedDataset` in `crates/aprender-contrastive-data/src/prepared.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12128,6 +20686,7 @@ "true"^^ . "pub(crate)"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12142,36 +20701,52 @@ "true"^^ . "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "access_ledger_persistence"^^ . + "crates/aprender-contrastive-data/src/ledger.rs"^^ . . + "method"^^ . "aprender_contrastive_data::ledger::AccessLedger"^^ . "to_canonical_bytes"^^ . - "false"^^ . - "no `mod AccessLedger` or `use … AccessLedger` in `crates/aprender-contrastive-data/src/ledger.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "pair_manifest_replay"^^ . + "crates/aprender-contrastive-data/src/manifest.rs"^^ . . + "method"^^ . "aprender_contrastive_data::manifest::PairReplayRecord"^^ . "to_config"^^ . - "false"^^ . - "no `mod PairReplayRecord` or `use … PairReplayRecord` in `crates/aprender-contrastive-data/src/manifest.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "selection_canonical_payload"^^ . + "crates/aprender-contrastive-data/src/manifest.rs"^^ . . + "method"^^ . "aprender_contrastive_data::manifest::SelectionPayload"^^ . "to_canonical_bytes"^^ . - "false"^^ . - "no `mod SelectionPayload` or `use … SelectionPayload` in `crates/aprender-contrastive-data/src/manifest.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12186,36 +20761,52 @@ "true"^^ . "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "canonical_pair"^^ . + "crates/aprender-contrastive-data/src/pairs.rs"^^ . . + "method"^^ . "aprender_contrastive_data::pairs::CanonicalPair"^^ . "new"^^ . - "false"^^ . - "no `mod CanonicalPair` or `use … CanonicalPair` in `crates/aprender-contrastive-data/src/pairs.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "pair_stream"^^ . + "crates/aprender-contrastive-data/src/pairs.rs"^^ . . + "method"^^ . "aprender_contrastive_data::pairs::PairLayout"^^ . "raw_pair_at"^^ . - "false"^^ . - "no `mod PairLayout` or `use … PairLayout` in `crates/aprender-contrastive-data/src/pairs.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "singleton_policy"^^ . + "crates/aprender-contrastive-data/src/pairs.rs"^^ . . + "method"^^ . "aprender_contrastive_data::pairs::PairSampler"^^ . "new"^^ . - "false"^^ . - "no `mod PairSampler` or `use … PairSampler` in `crates/aprender-contrastive-data/src/pairs.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12230,6 +20821,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12244,6 +20836,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12258,6 +20851,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12272,6 +20866,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12286,6 +20881,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12301,16 +20897,22 @@ "true"^^ . "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "prepared_dataset_typestate"^^ . + "crates/aprender-contrastive-data/src/prepared.rs"^^ . . + "method"^^ . "aprender_contrastive_data::prepared::PreparedDataset::"^^ . "from_labeled_rows"^^ . - "false"^^ . - "no `mod PreparedDataset` or `use … PreparedDataset` in `crates/aprender-contrastive-data/src/prepared.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12325,6 +20927,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12339,6 +20942,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12353,26 +20957,37 @@ "true"^^ . "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "few_shot_selection"^^ . + "crates/aprender-contrastive-data/src/select.rs"^^ . . + "method"^^ . "aprender_contrastive_data::select::FewShotSelector"^^ . "select"^^ . - "false"^^ . - "no `mod FewShotSelector` or `use … FewShotSelector` in `crates/aprender-contrastive-data/src/select.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "aprender_contrastive_data"^^ . "selection_replay"^^ . + "crates/aprender-contrastive-data/src/select.rs"^^ . . + "method"^^ . "aprender_contrastive_data::select::Selection"^^ . "replay"^^ . - "false"^^ . - "no `mod Selection` or `use … Selection` in `crates/aprender-contrastive-data/src/select.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "provable_contracts_macros::contract"^^ . @@ -12387,16 +21002,22 @@ "true"^^ . "private"^^ . . + . . + "doc"^^ . + "provable_contracts_macros::contract"^^ . "implemented"^^ . "entrenar"^^ . "matvec_matmul_assoc"^^ . + "crates/aprender-train/src/autograd/ops/matmul.rs"^^ . . + "fn"^^ . "entrenar::autograd"^^ . "matmul"^^ . - "false"^^ . - "no `fn matmul` (free or in an impl) in `crates/aprender-train/src/autograd/mod.rs`"^^ . + "true"^^ . + "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12410,6 +21031,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12423,8 +21045,9 @@ "true"^^ . "pub"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "entrenar"^^ . "doc_bundle_bijection"^^ . . @@ -12433,8 +21056,9 @@ "false"^^ . "no `mod setfit` or `use … setfit` in `crates/aprender-train/src/train/mod.rs`"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "entrenar"^^ . "linear_probe"^^ . . @@ -12443,8 +21067,9 @@ "false"^^ . "no `mod setfit` or `use … setfit` in `crates/aprender-train/src/train/mod.rs`"^^ . . + . . - "implemented"^^ . + "notimplemented"^^ . "entrenar"^^ . "selection_lock_lifecycle"^^ . . @@ -12453,6 +21078,7 @@ "false"^^ . "no `mod setfit` or `use … setfit` in `crates/aprender-train/src/train/mod.rs`"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12466,6 +21092,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "implemented"^^ . @@ -12479,6 +21106,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -12493,6 +21121,7 @@ "true"^^ . "pub"^^ . . + . . "doc"^^ . "must_use"^^ . @@ -12830,15 +21459,15 @@ "ProvableContracts.Theorems.Alibi.Slopes"^^ . "alibi_slope_lt_one"^^ . "true"^^ . - . - . - "grounded"^^ . - "Alibi"^^ . - "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/Alibi/Slopes.lean"^^ . - . - "ProvableContracts.Theorems.Alibi.Slopes"^^ . - "alibi_slope_pos"^^ . - "true"^^ . + . + . + "grounded"^^ . + "Alibi"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/Alibi/Slopes.lean"^^ . + . + "ProvableContracts.Theorems.Alibi.Slopes"^^ . + "alibi_slope_real_pos"^^ . + "true"^^ . . . "grounded"^^ . @@ -14906,6 +23535,14 @@ "ProvableContracts.Theorems.MatMul.Associativity"^^ . "matmul_assoc"^^ . "true"^^ . + . + . + "grounded"^^ . + "MatMul"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/MatMul/CooperativeTiling.lean"^^ . + "ProvableContracts.Theorems.MatMul.CooperativeTiling"^^ . + "f16_input_rounding_error_bound"^^ . + "true"^^ . . . "grounded"^^ . @@ -14915,6 +23552,22 @@ "ProvableContracts.Theorems.MatMul.CooperativeTiling"^^ . "matmul_block_sum"^^ . "true"^^ . + . + . + "grounded"^^ . + "MatMul"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/MatMul/CooperativeTiling.lean"^^ . + "ProvableContracts.Theorems.MatMul.CooperativeTiling"^^ . + "rounded_product_error"^^ . + "true"^^ . + . + . + "grounded"^^ . + "MatMul"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/MatMul/CooperativeTiling.lean"^^ . + "ProvableContracts.Theorems.MatMul.CooperativeTiling"^^ . + "roundingModel_id"^^ . + "true"^^ . . . "grounded"^^ . @@ -15235,6 +23888,38 @@ "ProvableContracts.Theorems.Quantization.NF4Dequant"^^ . "gpu_cpu_parity"^^ . "true"^^ . + . + . + "grounded"^^ . + "Quantization"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/Quantization/NF4Dequant.lean"^^ . + "ProvableContracts.Theorems.Quantization.NF4Dequant"^^ . + "nf4_lut_bounded"^^ . + "true"^^ . + . + . + "grounded"^^ . + "Quantization"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/Quantization/NF4Dequant.lean"^^ . + "ProvableContracts.Theorems.Quantization.NF4Dequant"^^ . + "nf4_lut_monotone"^^ . + "true"^^ . + . + . + "grounded"^^ . + "Quantization"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/Quantization/NF4Dequant.lean"^^ . + "ProvableContracts.Theorems.Quantization.NF4Dequant"^^ . + "nf4_lut_q_bounded"^^ . + "true"^^ . + . + . + "grounded"^^ . + "Quantization"^^ . + "crates/aprender-contracts-staging/lean/ProvableContracts/Theorems/Quantization/NF4Dequant.lean"^^ . + "ProvableContracts.Theorems.Quantization.NF4Dequant"^^ . + "nf4_lut_q_monotone"^^ . + "true"^^ . . . "grounded"^^ . diff --git a/contracts/cooperative-matrix-gemm-v1.yaml b/contracts/cooperative-matrix-gemm-v1.yaml index 3ad3cf8b68..3fd03c371a 100644 --- a/contracts/cooperative-matrix-gemm-v1.yaml +++ b/contracts/cooperative-matrix-gemm-v1.yaml @@ -21,10 +21,15 @@ equations: - "F16 input, F32 accumulation (GB10 config 3: M=16 K=16 N=16)" lean_theorem: ProvableContracts.CooperativeMatrix.matmul_block_sum f16_error_bound: - formula: "|C_f32_accum - C_exact| ≤ K * ε_f16 * max|A| * max|B|" - domain: "K reduction dimension, ε_f16 = 2^{-10}" + formula: "|Σ_k fl(A[m,k])·fl(B[k,n]) - Σ_k A[m,k]·B[k,n]| ≤ K * (2u + u²) * max|A| * max|B|" + domain: "K reduction dimension; fl any rounding with |fl(x) - x| ≤ u|x| (F16 round-to-nearest: u = 2^{-11}, so 2u + u² = 2^{-10} + 2^{-22})" codomain: "error bound ∈ ℝ≥0" - lean_theorem: ProvableContracts.CooperativeMatrix.f16_accumulation_error_bound + lean_theorem: ProvableContracts.CooperativeMatrix.f16_input_rounding_error_bound + lean_scope: >- + #4347: proved with the rounding model as a HYPOTHESIS, not an axiom. Covers F16 input + rounding with exact accumulation; F32 accumulation rounding is not modelled. The former + formula K * 2^{-10} * max|A| * max|B| is false even for exact accumulation (it drops the + u² term), and its Lean "proof" was an axiom asserting only that the number exists. notes: - "Uses wgpu::Features::SUBGROUP_VERTEX_STAGE or cooperative_matrix feature" - "Dispatch: ceil(M/coop_M) × ceil(N/coop_N) workgroups" diff --git a/contracts/crate-hygiene-v1.yaml b/contracts/crate-hygiene-v1.yaml index 3b6a12fbb7..d9d03c24b6 100644 --- a/contracts/crate-hygiene-v1.yaml +++ b/contracts/crate-hygiene-v1.yaml @@ -126,7 +126,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 5 + total_obligations: 3 proven: 0 tested: 5 status: tested diff --git a/contracts/crate-readme-v1.yaml b/contracts/crate-readme-v1.yaml index b37ea810b5..d1bcff05e2 100644 --- a/contracts/crate-readme-v1.yaml +++ b/contracts/crate-readme-v1.yaml @@ -58,7 +58,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 2 + total_obligations: 1 proven: 0 tested: 2 status: tested diff --git a/contracts/github-entities-v1.yaml b/contracts/github-entities-v1.yaml new file mode 100644 index 0000000000..fb99676395 --- /dev/null +++ b/contracts/github-entities-v1.yaml @@ -0,0 +1,157 @@ +# ────────────────────────────────────────────── +# github-entities-v1 — GitHub repos, issues, pull requests and milestones as focus nodes (ONT-001 §5 ONT-4f; +# issue aprender#4330) +# +# WHAT THIS IS. Four Σ entity types — `repo`, `issue`, `pull-request`, `milestone` — on the `json` extractor, each +# with its own vocabulary (contracts/ontology.yaml `entity_types[].vocabulary`), and the four shapes below over +# them. Each class is ⊑ Json in Σ `subsumes`, so a shape on Json would reach all four through the R-19 closure. +# +# COMMITTED SNAPSHOTS, NEVER A LIVE API. The extractor reads `evidence/github//.json` and nothing +# else (`ontology/extract/json/github.rs`). A snapshot records ONE moment, and its `ref` says which: +# · repo: /@ — must equal the snapshot's own `sha` +# · issue, pull-request, milestone: /#@ — must equal the snapshot's own `updatedAt` +# A disagreement is refused BY THE EXTRACTOR naming both values (PV-ONT-012, Fail), as is a `state: merged` with +# no `mergedAt` (named). Refreshing a snapshot is committing a new one; the gate never goes to the network, so +# its verdict is a function of the tree. +# +# `resolves:` BETWEEN TRACKED SNAPSHOTS ONLY. `pr:baseRepo resolves: repo` and `issue:milestone resolves: +# milestone`: a value naming a tracked snapshot (by `owner/repo[#n]` or by its full ref) becomes an IRI edge to +# that node, which `class:` then checks; a value naming nothing tracked is materialized on `…Unresolved`, held at +# `maxCount: 0` — FAIL CLOSED, never `Unknown`, never looked up live. The extractor's RESOLVES table is asserted +# equal to these declarations by a unit test, so the two cannot drift apart. +# +# ARMED. The four ids are in `contracts/lint-baseline.json` `armed_shapes[]`: a violation here is a Fail. +# +# KIND: pattern. Vocabulary, an extractor and shapes over a graph; the proof is the gate's own case table. +# ────────────────────────────────────────────── +name: github-entities-v1 +version: "1.0.0" +scope: > + How a committed GitHub snapshot under evidence/github// becomes a focus node typed ont:Repo, ont:Issue, + ont:PullRequest or ont:Milestone, and the four shapes over them. Out of scope: fetching or refreshing the + snapshots (a commit does that), and any GitHub entity type Σ does not declare. +status: active + +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-24' + last_modified: '2026-09-24' + author: PAIML Engineering + description: > + GitHub repositories, issues, pull requests and milestones are focus nodes read from committed snapshots; a + snapshot's ref names the version it records and must agree with it, a merge carries its time, and a + reference between snapshots resolves to a tracked node or fails closed. + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §3.6 (the implemented SHACL subset), §3.7, §5 ONT-4f' + - 'aprender#4330 (this row); aprender#4323 (ONT-4c, the Σ entity_type_target_class map)' + - 'crates/aprender-contracts/src/ontology/extract/json/github.rs — the extractor and its case table' + - 'contracts/ontology.yaml — entity_types[].vocabulary, concepts Json/Repo/Issue/PullRequest/Milestone, subsumes' + +entity: + # The contract governs four entity types; `entity:` names one Σ type, so it anchors on `repo`, the type every + # other one resolves back to. The shapes below target all four by class. + type: repo + +relations: + depends_on: [ont-shapes-v1, ont-sigma-v1] + +shapes: + - id: github-repo + targetClass: ont:Repo + properties: + - {path: repo:ref, minCount: 1, maxCount: 1} + - {path: repo:nameWithOwner, minCount: 1, maxCount: 1} + - {path: repo:sha, minCount: 1, maxCount: 1, pattern: "^[0-9a-f]{40}$"} + - {path: repo:url, minCount: 1, maxCount: 1} + + - id: github-issue + targetClass: ont:Issue + properties: + - {path: issue:ref, minCount: 1, maxCount: 1} + - {path: issue:repo, minCount: 1, maxCount: 1} + - {path: issue:number, minCount: 1, maxCount: 1, datatype: xsd:integer} + - {path: issue:title, minCount: 1, maxCount: 1} + - {path: issue:state, minCount: 1, maxCount: 1, in: [OPEN, CLOSED]} + - {path: issue:updatedAt, minCount: 1, maxCount: 1} + - {path: issue:milestone, maxCount: 1, nodeKind: IRI, class: ont:Milestone, resolves: milestone} + - {path: issue:milestoneUnresolved, maxCount: 0} + + - id: github-pull-request + targetClass: ont:PullRequest + properties: + - {path: pr:ref, minCount: 1, maxCount: 1} + - {path: pr:repo, minCount: 1, maxCount: 1} + - {path: pr:number, minCount: 1, maxCount: 1, datatype: xsd:integer} + - {path: pr:title, minCount: 1, maxCount: 1} + - {path: pr:state, minCount: 1, maxCount: 1, in: [OPEN, CLOSED, MERGED]} + - {path: pr:updatedAt, minCount: 1, maxCount: 1} + - {path: pr:mergedAt, maxCount: 1} + - {path: pr:baseRepo, minCount: 1, maxCount: 1, nodeKind: IRI, class: ont:Repo, resolves: repo} + - {path: pr:baseRepoUnresolved, maxCount: 0} + + - id: github-milestone + targetClass: ont:Milestone + properties: + - {path: milestone:ref, minCount: 1, maxCount: 1} + - {path: milestone:repo, minCount: 1, maxCount: 1} + - {path: milestone:number, minCount: 1, maxCount: 1, datatype: xsd:integer} + - {path: milestone:title, minCount: 1, maxCount: 1} + - {path: milestone:state, minCount: 1, maxCount: 1, in: [OPEN, CLOSED]} + - {path: milestone:updatedAt, minCount: 1, maxCount: 1} + +equations: + version_slot: + formula: "accepted(s) ⇒ version(ref(s)) = s[vocabulary(type(s)).version]" + domain: "every *.json under evidence/github// for a Σ snapshot type, walked in byte order" + codomain: "one root node typed the type's root_class, or a refusal naming both values" + invariants: + - "a refused snapshot emits no triple" + - "a file or directory the walk cannot place is refused by name, never skipped" + preconditions: + - "Σ declares the type with a vocabulary on the json extractor" + postconditions: + - "two extractions are byte-identical (R-15)" + lean_theorem: none — L4 not declared + resolves: + formula: "value(s, k) ∈ tracked(target(k)) ⇒ edge(s, k, node(value)) ∧ value(s, k) ∉ tracked(target(k)) ⇒ unresolved(s, k)" + domain: "the RESOLVES table: pr:baseRepo → repo, issue:milestone → milestone" + codomain: "an IRI edge to a tracked node, or a literal on the Unresolved predicate" + invariants: + - "no network lookup: only snapshots in the same walk are candidates" + preconditions: + - "the RESOLVES table equals this contract's resolves: declarations" + postconditions: + - "an unresolved reference fails its shape's maxCount 0" + lean_theorem: none — L4 not declared + +invariants: + - id: GHE-INV-001 + property: a snapshot whose ref version disagrees with its own recorded version is refused naming both + formal: 'version(ref(s)) ≠ s[version] ⇒ s ∈ errors ∧ |nodes(s)| = 0' + prose: false + - id: GHE-INV-002 + property: a merged snapshot with no mergedAt is refused naming the field + formal: 'state(s) = merged ∧ |mergedAt(s)| = 0 ⇒ s ∈ errors' + prose: false + - id: GHE-INV-003 + property: a reference to an untracked snapshot fails closed + formal: 'value(s, k) ∉ tracked(target(k)) ⇒ |unresolved(s, k)| = 1 ⇒ Fail(shape(s), s)' + prose: false + +falsification_tests: + - id: FALSIFY-GHE-001 + rule: the three named mutations + prediction: > + a merged pull-request snapshot with no mergedAt is refused naming `mergedAt`; an issue naming an untracked + milestone materializes milestoneUnresolved and no edge; a repo whose sha disagrees with its ref is refused + naming both shas + test: cargo test -p aprender-contracts --lib ontology::extract::json::github + if_fails: a snapshot that contradicts itself, or points at nothing, reads as a clean corpus + - id: FALSIFY-GHE-002 + rule: the gate discriminates on the CLI + prediction: > + the green fixture passes with every GitHub type counted; each mutation fixture fails with exit 1 naming the + file or the focus node and the property + test: cargo test -p aprender-contracts-cli --test ont4f_github_entities + if_fails: the shapes decorate the corpus instead of grading it diff --git a/contracts/hero-svg-v1.yaml b/contracts/hero-svg-v1.yaml index 6d162f35ff..edabaeeb8d 100644 --- a/contracts/hero-svg-v1.yaml +++ b/contracts/hero-svg-v1.yaml @@ -65,7 +65,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 3 + total_obligations: 1 proven: 0 tested: 3 status: tested diff --git a/contracts/lint-baseline.json b/contracts/lint-baseline.json index ab4181e755..4ee873ec73 100644 --- a/contracts/lint-baseline.json +++ b/contracts/lint-baseline.json @@ -1,18 +1,23 @@ { "_spec": "APR-RELEASE-001 §11.2 — moves only through `make ont-ratchet` (ONT R-6)", - "armed_gates": ["validate", "audit", "score", "verify", "enforce", "enforcement-level", "duplicate-stems", "composition", "sigma", "relations", "shapes"], - "armed_shapes": ["ont-shapes-v1", "ladder-measured", "ladder-green"], + "armed_gates": ["validate", "audit", "score", "verify", "enforce", "enforcement-level", "duplicate-stems", "composition", "sigma", "relations", "shapes", "proved-is-derived"], + "armed_shapes": ["ont-shapes-v1", "ladder-measured", "ladder-green", "github-repo", "github-issue", "github-pull-request", "github-milestone"], "contracts_without_valid_under": 386, + "command": "make lint-ratchet", + "unpaired_theorem_modules": 130, + "contracts_without_depends_on": 278, + "underived_proved_claims": 95, "ont": { "consumer_present": true, - "contracts_total": 1889, - "entity_types_registered": 11, - "extractors_implemented": 8, - "contracts_anchored": 8, - "contracts_shaped": 6, + "contracts_total": 1898, + "entity_types_registered": 16, + "extractors_implemented": 9, + "contracts_anchored": 10, + "contracts_shaped": 8, "unanchored_but_bindable": 297, "formal_prose": 1464, "legacy_unresolved_depends_on": 8, - "shapes_unarmed": 15 + "liskov_prose": 0, + "shapes_unarmed": 17 } } diff --git a/contracts/model-capability-ladder-v1.yaml b/contracts/model-capability-ladder-v1.yaml index f3c13618ce..8bbfde59fc 100644 --- a/contracts/model-capability-ladder-v1.yaml +++ b/contracts/model-capability-ladder-v1.yaml @@ -473,3 +473,14 @@ qa_gate: - every_inventory_model_measured_and_green_on_cuda pass_criteria: "check_model_ladder.sh exits 0 with executed >= 1 and a non-empty measured inventory on every required host" falsification: "plant a receipt with cuda.fallback=true → exit 1" + +# ONT-8: one evidence block — PROV-O names, one L-enum (ProofLevel). The level is MEASURED, not declared: it is what +# the cited command printed for this contract at the time below. Checked by `pv lint --gate evidence`. +evidence: + level: L2 + mark: C + provenance: + wasGeneratedBy: + command: "pv proof-status contracts/ --format json" + wasAttributedTo: pv + generatedAtTime: "2026-09-24T08:22:07Z" diff --git a/contracts/online-softmax-v1.yaml b/contracts/online-softmax-v1.yaml index 0414218f83..be0de976f0 100644 --- a/contracts/online-softmax-v1.yaml +++ b/contracts/online-softmax-v1.yaml @@ -8,6 +8,24 @@ metadata: - Rabe & Staats (2022) Self-attention Does Not Need O(n²) Memory depends_on: - softmax-kernel-v1.yaml +# ONT-4e (R-20): online softmax refines the kernel contract. Same precondition (it accepts every input the kernel +# does), the kernel's postcondition plus order preservation (it promises at least as much). +relations: + refines: [softmax-kernel-v1] +requires: +- id: PRE-1 + statement: every input score is finite and the vector is non-empty + formal: '∀i: ¬isNaN(x_i) ∧ ¬isInf(x_i) ∧ len(x) > 0' + formal_status: parsed +ensures: +- id: POST-1 + statement: the output is a probability vector of the input's length + formal: 'len(σ(x)) = len(x) ∧ ∀i: 0 < σ(x)_i < 1 ∧ |Σ σ(x)_i - 1| < ε' + formal_status: parsed +- id: POST-2 + statement: the output preserves the order of the scores + formal: 'x_i > x_j ⟹ σ(x)_i > σ(x)_j' + formal_status: parsed equations: online_normalizer: formula: "Online update rule (streaming max + sum_exp):\n Given running state (m_{i-1}, d_{i-1}) and new score x_i:\n\ diff --git a/contracts/ont-consistency-v1.yaml b/contracts/ont-consistency-v1.yaml new file mode 100644 index 0000000000..ff3849d484 --- /dev/null +++ b/contracts/ont-consistency-v1.yaml @@ -0,0 +1,165 @@ +# ───────────────────────────────────────────────────────────────────────────── +# ont-consistency-v1 — the corpus's typed relations are jointly satisfiable, and +# the witness that says so checks (ONT-001 §3.5 + §5 row ONT-5; issue #4074; +# ticket PMAT-4074) +# +# WHAT IT GOVERNS +# provable_contracts::ontology::witness — ClauseSet (the Horn encoding), +# census_id_set_sha256, relations_sha256, Witness, check, pc_checker. +# provable_contracts::lint::consistency_gate — the `ont-consistency` gate: +# PV-ONT-022 (inconsistent, core named), PV-ONT-023 (witness does not check), +# declines NoCheckable / WitnessStale / PositiveControlFailed, exit 3 on a +# malformed Σ. +# `pv-sat` (crates/aprender-contracts-cli/src/bin/pv-sat/) — the UNTRUSTED +# reasoner, private to its bin target (R-1, F-7), and its pc_reasoner plant. +# +# PROOF LADDER, STATED HONESTLY +# * L2 — unit tests over the encoding, the digests and the checker; the gate's +# tests; pv-sat's 500-seed sweep in which every answer it gives checks; +# the CLI test runs the §5 probe verbatim on this repo's corpus. +# * L3 — not declared. There are no Kani harnesses for this checker. +# * L4 — not declared. There is no Lean theorem. +# The gate is COMPUTED and NOT ARMED: it is absent from `armed_gates`, so its +# verdict does not reach the armed meet until an arming PR adds it. +# ───────────────────────────────────────────────────────────────────────────── +metadata: + version: 1.0.0 + created: '2026-09-24' + last_modified: '2026-09-24' + author: PAIML Engineering + kind: pattern + description: > + ONT-001 consistency — the typed relation graph as propositional Horn clauses, decided offline by the + untrusted pv-sat, and re-verified in the gate by a linear witness checker with two positive controls + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §3.5 (the checkable subset), §5 ONT-5, R-1 (the reasoner is untrusted), R-2 (zero is a decline), R-3 (positive controls), F-5, F-7' + - 'Dowling & Gallier (1984) Linear-time algorithms for testing the satisfiability of propositional Horn formulae' + - 'crates/aprender-contracts/src/ontology/witness.rs — the encoding, the digests, check, pc_checker' + - 'crates/aprender-contracts/src/lint/consistency_gate.rs — the ont-consistency gate' + - 'crates/aprender-contracts-cli/src/bin/pv-sat/ — the reasoner and its plant' +equations: + encode: + formula: 'clauses(G) = {A : A live} ∪ {¬A ∨ B : A depends_on B ∨ A refines B} ∪ {¬A ∨ ¬B : A contradicts B}' + domain: G the typed relation graph over the census id set; live = not the target of any supersedes + codomain: a propositional Horn clause set over contract ids + invariants: + - every clause names only ids in the census id set + - a superseded id is not asserted, but an implication from a live id still derives it + preconditions: + - G is the relations gate's typed graph with symmetric roles closed + postconditions: + - unencoded_edges(G) lists every edge whose role has no clause, so nothing is dropped silently + lean_theorem: none — L4 not declared + check: + formula: 'check(W, C) = Ok ⇔ (W = model M ∧ ∀c ∈ C: M ⊨ c) ∨ (W = core K ∧ K is a unit-propagation derivation in C ending in a contradicts clause both sides of which K derived)' + domain: W a witness, C = clauses(G) for the current corpus + codomain: '{Ok(Consistent), Ok(Inconsistent(core)), Err(reason)}' + invariants: + - every clause a core names exists in C (a stale or invented core is refused) + - a premise used before it is derived is refused + - the check is linear in |W| + |C| + preconditions: + - W.census_id_set_sha256 and W.relations_sha256 equal the corpus's; otherwise the gate declines WitnessStale before checking + postconditions: + - Ok(Consistent) ⇒ C is satisfiable; Ok(Inconsistent(K)) ⇒ C is unsatisfiable and K names why + lean_theorem: none — L4 not declared + gate: + formula: 'verdict = Unknown(NoCheckable) if |C| = 0; Unknown(WitnessStale) if no witness for the digests; Unknown(PositiveControlFailed) unless pc_checker and pc_reasoner fired; Fail if check errs or yields a core; Pass otherwise' + domain: a contracts/ directory with Σ and a witness directory + codomain: V (the ONT-6 lattice) + invariants: + - zero clauses is a decline, never a Pass (R-2) + - no verdict without both positive controls fired (R-3) + preconditions: + - Σ parses; otherwise exit 3 + postconditions: + - verdict == Pass ⇒ a checked model of the current graph exists + lean_theorem: none — L4 not declared +proof_obligations: +- id: ONT5-INV-001 + type: invariant + property: the encoding types each role and asserts only live contracts + formal: 'units(C) = ids \ targets(supersedes) ∧ roles(C) ⊆ {depends_on, refines, contradicts}' + applies_to: all +- id: ONT5-INV-002 + type: invariant + property: the witness name is order-free and sensitive to every edge + formal: 'relations_sha256(permute(G)) = relations_sha256(G) ∧ ∀e ∈ G: relations_sha256(G \ {e}) ≠ relations_sha256(G)' + applies_to: all +- id: ONT5-INV-003 + type: invariant + property: the checker refuses any core that is not a derivation in the current graph + formal: 'check(K, C) = Ok(Inconsistent) ⇒ every step of K is a unit of C or a clause of C whose premise an earlier step derived, and its final contradicts clause has both sides derived' + applies_to: all +- id: ONT5-INV-004 + type: invariant + property: the checker refuses a model that falsifies any clause + formal: 'check(M, C) = Ok(Consistent) ⇒ ∀c ∈ C: eval(M, c) = true' + applies_to: all +- id: ONT5-POST-005 + type: postcondition + property: the gate's exit is the lattice element, and a stale, absent or uncontrolled witness never passes + formal: 'stale ∨ absent ⇒ exit 2 `decline: WitnessStale` naming make contracts; ¬pc_checker ∨ ¬pc_reasoner ⇒ exit 2; |C| = 0 ⇒ exit 2 `decline: NoCheckable`; malformed Σ ⇒ exit 3' + applies_to: all +- id: ONT5-INV-006 + type: invariant + property: the reasoner is private to its bin target + formal: 'lib.rs contains no `mod sat`; the pv CLI main.rs names no pv_sat (F-7)' + applies_to: all +verification_summary: + total_obligations: 6 + l2_property_tested: 6 + l3_kani_proved: 0 + l4_lean_proved: 0 + l4_sorry_count: 0 + l4_not_applicable: 6 +falsification_tests: +- id: FALSIFY-ONT5-001 + rule: the encoding + prediction: live contracts are units, depends_on/refines are implications, contradicts is a conflict pair, supersedes withdraws a unit + test: cargo test -p aprender-contracts --lib ontology::witness::tests::the_encoding_asserts_live_contracts_and_types_each_role + if_fails: a role is encoded under the wrong clause shape, so the reasoner answers a different question from the corpus +- id: FALSIFY-ONT5-002 + rule: the digests + prediction: permuting the edges leaves both digests unchanged; removing any one edge changes relations_sha256 + test: cargo test -p aprender-contracts --lib ontology::witness::tests::the_digests_are_order_free_and_sensitive_to_every_edge + if_fails: a witness for one graph is accepted for another +- id: FALSIFY-ONT5-003 + rule: the core checker + prediction: a real derivation checks; a premise used early, a clause the graph lacks and an underived conflict side are each refused + test: cargo test -p aprender-contracts --lib ontology::witness::tests::a_premise_used_before_it_is_derived_is_refused + if_fails: the checker accepts a core that proves nothing — pc_checker then fires on the shipped fixture +- id: FALSIFY-ONT5-004 + rule: pc_checker + prediction: the checker refuses fixtures/unsat-core-corrupt.json on every run + test: cargo test -p aprender-contracts --lib ontology::witness::tests::pc_checker_fires_on_the_shipped_fixture + if_fails: a checker that returns Ok for everything goes unnoticed (measured 2026-09-24 — 14 lib tests red) +- id: FALSIFY-ONT5-005 + rule: the gate's lattice + prediction: 'consistent → Pass; inconsistent → PV-ONT-022 with the core named; tampered → PV-ONT-023; missing or foreign → WitnessStale; control not fired → PositiveControlFailed; zero clauses → NoCheckable; malformed Σ → exit 3' + test: cargo test -p aprender-contracts --lib lint::consistency_gate::tests + if_fails: the gate reports a verdict about a graph nobody reasoned over +- id: FALSIFY-ONT5-006 + rule: the reasoner is untrusted but honest here + prediction: over a 500-seed sweep every answer pv-sat gives checks; the plant draws the core [A, B, C]; a rerun leaves the witness byte-identical + test: cargo test -p aprender-contracts-cli --bin pv-sat + if_fails: pv-sat writes witnesses the gate refuses, or its positive control is a constant (measured 2026-09-24 — an all-true reasoner turns 4 tests red) +- id: FALSIFY-ONT5-007 + rule: the §5 probe end to end, and F-7 + prediction: 'pv lint contracts/ --gate ont-consistency on this repo: checkable_n > 0, pc_checker fired, witness.pc_reasoner fired, witness.stale false; exits 0/1/2/3 on witnesses the real pv-sat wrote; lib.rs has no mod sat' + test: cargo test -p aprender-contracts-cli --test ont5_consistency_gate + if_fails: the probe the ledger binds is not the behaviour shipped (measured 2026-09-24 — `pub mod sat` in the lib turns it red) +qa_gate: + id: F-ONT5-001 + name: ONT-001 relation consistency + description: Horn encoding, witness digests, linear checker, two positive controls, pv-sat behind a bin boundary (ONT-5) + checks: + - encoding + - digests + - core_checker + - pc_checker + - gate_lattice + - reasoner_answers_check + - spec_probe + pass_criteria: All 7 falsification tests pass + falsification: 'a checker that returns Ok for everything, `pub mod sat` in the lib, or a reasoner that answers all-true — each turns a named test RED (measured 2026-09-24)' diff --git a/contracts/ont-docs-corpus-v1.yaml b/contracts/ont-docs-corpus-v1.yaml new file mode 100644 index 0000000000..35096b1975 --- /dev/null +++ b/contracts/ont-docs-corpus-v1.yaml @@ -0,0 +1,139 @@ +# ───────────────────────────────────────────────────────────────────────────── +# ont-docs-corpus-v1 — the docs corpus as ontology focus nodes +# (aprender#3560 "docs, examples and cookbook pv-SHACL validated for Qwen3.5"; row R1) +# +# R1 (this file's first row): extract:example — every cargo example target of a +# workspace member (examples/*.rs, examples/*/main.rs) is an ont:Example with +# its file, crate, target name, and the model families its text names. +# LATER ROWS (not claimed here): R2 book pages (after ONT-4c's readme reader), +# R3 apr-cookbook (its own repo), R5 every example builds and runs. +# R4: the model-currency shape — an example that names a model family must name +# the current one (qwen3.5), or say why not. +# +# ARMING: both shapes are REPORTED, not armed. `examples-well-formed` holds on +# this tree. `examples-model-current` (R4) does not: 430 examples name an +# older model. R4's gate is the shrink-only pin, which CI's --lib run holds: +# a NEW stale example is RED today; the shape arms when the pin reaches 0. +# PIN: an example that names an older model on purpose says so with +# `// ont:model-pinned: `; the reason is kept as example:modelPinned. +# ───────────────────────────────────────────────────────────────────────────── +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-24' + last_modified: '2026-09-24' + author: PAIML Engineering + description: > + extract:example — the cargo example targets of the workspace members, found by the same [workspace] + members/exclude reading extract:code uses, each an ont:Example carrying example:file, example:crate, + example:name, one example:namesModel per model family a token scan finds, and example:namesQwen35. + Zero examples read at a [workspace] root is an extractor error (PV-ONT-012), never a green. + references: + - 'paiml/aprender#3560 (docs, examples and cookbook pv-SHACL validated for Qwen3.5)' + - 'paiml/infra docs/specifications/paiml-ontology.md §3.7 (extractors), R-3, R-15' + - 'crates/aprender-contracts/src/ontology/extract/example.rs, example_tests.rs' + - 'crates/aprender-contracts/src/ontology/extract/code.rs — workspace_membership, manifest_names (reused)' + +relations: + depends_on: [ont-shapes-v1, ont-sigma-v1] + +equations: + extract_example: + formula: "examples(root) = { e | m ∈ members(root), e ∈ m/examples/*.rs ∪ m/examples/*/main.rs } ; namesModel(e) = families(tokens(lower(text(e))))" + domain: "the repo root the contract dir sits in; members by [workspace] members/exclude, or every manifest when the root has no [workspace]" + codomain: "one ont:Example node per target, plus ExampleStats {packages, examples, non_member_examples, naming_a_model, naming_qwen35, by_family, errors}" + invariants: + - "a target under a manifest the workspace excludes is counted in non_member_examples and not extracted" + - "a helper module below examples// other than main.rs is not a target" + - "the family scan is by token: `graphics` is not phi, `llama.cpp` is a runtime, `qwen3.5` is tried before `qwen3`" + preconditions: + - "the root's Cargo.toml, when it is a [workspace], lists its members" + postconditions: + - "at a [workspace] root, examples = 0 ∧ (members = 0 ∨ non_member_examples > 0) ⇒ one extractor error" + - "two extractions of the same tree are byte-identical (R-15)" + lean_theorem: none — L4 not declared + model_current: + formula: "current(e) ⇔ namesModel(e) = ∅ ∨ qwen3.5 ∈ namesModel(e) ∨ pinned(e) ≠ ⊥ ; stale = |{e | ¬current(e)}| ≤ STALE_EXAMPLES_PINNED" + domain: "every extracted ont:Example" + codomain: "example:modelCurrent true|false, example:modelPinned the stated reason" + invariants: + - "a pin is a `//` comment carrying `ont:model-pinned:` and a non-empty reason; the marker in a string is not a pin" + - "the pin only shrinks: a count below it fails until the constant is lowered to it" + preconditions: + - "CURRENT_FAMILY names the family the project ships against (qwen3.5, #3560)" + postconditions: + - "a new example naming an older model without a pin fails CI's --lib run naming the file" + lean_theorem: none — L4 not declared + +invariants: + - id: DOCS-INV-001 + property: deterministic, no blank nodes + formal: '∀ tree: extract(tree) = extract(tree) ∧ ∀ t ∈ extract(tree): subject(t) ∈ IRI' + prose: false + - id: DOCS-INV-002 + property: a walk that read nothing it could have is RED, never green; a workspace with no examples measures zero + formal: 'workspace(root) ∧ |examples(root)| = 0 ∧ (|members(root)| = 0 ∨ non_member_examples(root) > 0) ⇒ PV-ONT-012 ∈ findings' + prose: false + - id: DOCS-INV-003 + property: only targets cargo builds are extracted + formal: '∀ e ∈ examples(root): admits(membership(root), dir(e)) ∧ (ext(e) = rs ∧ depth(e) = 1 ∨ e = examples/*/main.rs)' + prose: false + +falsification_tests: + - id: FALSIFY-DOCS-001 + rule: the walk extracts exactly the member targets + prediction: a planted workspace yields its two example forms and not the helper module, the excluded crate, a README or target/ + test: cargo test -p aprender-contracts --lib ontology::extract::example::tests::a_planted_workspace_yields_exactly_its_member_targets + if_fails: an example cargo never builds is graded, or a real one is missed + - id: FALSIFY-DOCS-002 + rule: vacuity is RED + prediction: "a [workspace] root that admits no member, or whose example targets all sit outside the membership, reports one extractor error; a member with no examples/, or a bare dir, reports none" + test: cargo test -p aprender-contracts --lib ontology::extract::example::tests::a_workspace_root_that_read_nothing_is_an_error_and_one_with_no_examples_is_not + if_fails: a walk that read nothing reports a clean corpus + - id: FALSIFY-DOCS-003 + rule: the family scan tells models from look-alikes + prediction: 13 cases — Qwen3.5/3/2.5/2, TinyLlama vs Llama, phi vs graphics and phishing, llama.cpp named as no model — each give the stated family set + test: cargo test -p aprender-contracts --lib ontology::extract::example::tests::the_family_scan_separates_models_from_look_alikes + if_fails: a Qwen3 example counts as Qwen3.5, or llama.cpp counts as a Llama example + - id: FALSIFY-DOCS-004 + rule: the repo's own examples + prediction: the workspace yields more than 500 examples across more than 20 packages, with no extractor error + test: cargo test -p aprender-contracts --lib ontology::extract::example::tests::the_repo_examples_are_extracted + if_fails: the membership reading or the walk lost the corpus + - id: FALSIFY-DOCS-005 + rule: the extractor is registered and its control fires + prediction: Σ lists example as an implemented entity type, the shapes gate counts it in by_entity_type and its pc_extract control fires + test: cargo test -p aprender-contracts --lib lint::shapes_gate + if_fails: the extractor runs and no gate knows, or its control is silent + - id: FALSIFY-DOCS-006 + rule: currency and the pin marker + prediction: "an example naming only Qwen2.5 is stale; the same with a `// ont:model-pinned: why` line, or naming no model, is current; a marker in a string literal or with no reason is not a pin" + test: cargo test -p aprender-contracts --lib ontology::extract::example::tests::a_pin_needs_the_marker_in_a_comment_and_a_reason + if_fails: a stale example passes as current, or a pin without a reason exempts it + - id: FALSIFY-DOCS-007 + rule: the stale count is pinned shrink-only (R4's drift gate) + prediction: the repo's stale examples equal STALE_EXAMPLES_PINNED (430 on 2026-09-24) + test: cargo test -p aprender-contracts --lib ontology::extract::example::tests::the_repo_stale_examples_are_pinned_shrink_only + if_fails: a new example on an older model landed (the message lists the stale files), or examples migrated and the pin was not lowered + +qa_gate: + required_checks: + - "cargo test -p aprender-contracts --lib ontology::extract::example" + - "cargo test -p aprender-contracts --lib lint::shapes_gate" + +shapes: + # REPORTED, not armed (see the header). + - id: examples-well-formed + targetClass: ont:Example + properties: + - {path: example:file, minCount: 1, maxCount: 1} + - {path: example:crate, minCount: 1, maxCount: 1} + - {path: example:name, minCount: 1, maxCount: 1} + - {path: example:namesQwen35, minCount: 1, maxCount: 1, in: [true, false]} + - {path: example:modelCurrent, minCount: 1, maxCount: 1, in: [true, false]} + # R4, REPORTED, not armed: 430 of the 432 examples that name a model name an older one. The drift gate is the + # shrink-only pin STALE_EXAMPLES_PINNED (FALSIFY-DOCS-007), not this shape; arm it when the pin reaches 0. + - id: examples-model-current + targetClass: ont:Example + properties: + - {path: example:modelCurrent, minCount: 1, maxCount: 1, in: [true]} diff --git a/contracts/ont-evidence-v1.yaml b/contracts/ont-evidence-v1.yaml new file mode 100644 index 0000000000..ae3e451d46 --- /dev/null +++ b/contracts/ont-evidence-v1.yaml @@ -0,0 +1,132 @@ +# ───────────────────────────────────────────────────────────────────────────── +# ont-evidence-v1 — one evidence block, PROV-O names, one L-enum (ONT-8) +# (ONT-001 §4.3 evidence, §5 ONT-8, F-16, R-17; issue #4077; ticket PMAT-4077) +# +# WHAT IT GOVERNS +# provable_contracts::lint::evidence_gate — PV-ONT-017..021; +# `pv lint --gate evidence` and gate 16 of the full run (R-8: computed +# everywhere, armed per repo; not armed here). +# +# THE LEVEL IS proof_status::ProofLevel, NOT A SECOND LIST. `levels_source` +# reports "enum" because the gate deserialises into that one type; a level +# outside it is PV-ONT-018. EV-3 removed L0, so L0 is refused. +# +# ENTITY-BLIND (R-17). `entity.type` is counted into `entity_types_checked` +# and never branched on: a README, a model and a code contract pass or fail +# the same block the same way. +# +# [V] BINDING. census.json `git_sha` is null by operator ruling (2026-09-16), +# so a [V] sha binds to the census when it records one, and otherwise must +# resolve as a commit in the contract directory's repository. A git that +# cannot look (absent, shallow) is Unknown(ToolAbsent), never a finding. +# ───────────────────────────────────────────────────────────────────────────── +name: ont-evidence +version: "1.0.0" +scope: > + The shape of a contract's `evidence` block: its level on the one proof-level enum, its mark, and its PROV-O + provenance (wasGeneratedBy, wasAttributedTo, generatedAtTime), across every entity type. Out of scope: whether + the cited command, re-run today, still yields the level claimed (that is `pv proof-status`), and what pv-sat + does with a level (ONT-5). +status: active + +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-24' + last_modified: '2026-09-24' + author: PAIML Engineering + description: > + A contract may state its evidence in one block; the evidence gate refuses undeclared keys (F-16 `author:` + among them), a level outside the one enum, an unknown mark or a cited/verified claim with no command, a + verified sha that does not resolve, and an agent Σ does not declare, on every entity type alike. + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §4.3 (evidence), §5 ONT-8, F-16, R-2, R-8, R-17' + - 'crates/aprender-contracts/src/lint/evidence_gate.rs — PV-ONT-017..021' + - 'crates/aprender-contracts/src/proof_status.rs — ProofLevel, the one L-enum' + - 'contracts/ontology.yaml — agents, reader lint/evidence_gate.rs' + +relations: + depends_on: [ont-sigma-v1, ont-verdict-lattice-v1] + +equations: + evidence_admission: + formula: "admits(corpus) ⇔ ∀ c ∈ corpus: evidence(c) = ∅ ∨ (keys(evidence(c)) ⊆ K_e ∧ level(c) ∈ ProofLevel ∧ mark(c) ∈ {V,C,A,U} ∧ (mark(c) ∈ {V,C} ⇒ command(c) ≠ ∅) ∧ (mark(c) = V ⇒ resolves(sha(c))) ∧ agent(c) ∈ Σ.agents)" + domain: "a contract corpus and the Σ that declares its agents" + codomain: "Pass | Fail | Unknown" + invariants: + - "a corpus whose every evidence block is well-formed and resolves is admitted" + - "a corpus with no evidence block is Unknown, never Pass" + preconditions: + - "Σ parses and satisfies its own integrity rules" + postconditions: + - "verdict ∈ {Pass, Fail, Unknown}" + - "the verdict does not depend on entity.type" + lean_theorem: none — L4 not declared + +invariants: + - id: EV-INV-001 + property: evidence, provenance and wasGeneratedBy carry only their declared keys + formal: 'k ∈ keys(evidence) ⇒ k ∈ K_e ∧ k ∈ keys(provenance) ⇒ k ∈ K_p' + prose: false + - id: EV-INV-002 + property: the level is a member of the one proof-level enum + formal: 'level(c) ∈ {L1, L2, L3, L4, L5}' + prose: false + - id: EV-INV-003 + property: a cited or verified claim names the command that produced it + formal: 'mark(c) ∈ {V, C} ⇒ command(c) ≠ ∅' + prose: false + - id: EV-INV-004 + property: a verified claim's sha resolves + formal: 'mark(c) = V ⇒ sha(c) = census.git_sha ∨ (census.git_sha = ⊥ ∧ sha(c) ∈ commits(repo))' + prose: false + - id: EV-INV-005 + property: the attributed agent is declared in Σ + formal: 'agent(c) ∈ Σ.agents' + prose: false + +falsification_tests: + - id: FALSIFY-EV-001 + rule: F-16 + prediction: "`author:` under evidence, provenance or wasGeneratedBy is PV-ONT-017 at each level" + test: cargo test -p aprender-contracts --lib lint::evidence_gate::tests::f16_author_is_pv_ont_017_wherever_it_is_written + if_fails: a non-PROV name enters the provenance record + - id: FALSIFY-EV-002 + rule: one L-enum + prediction: L0, l2, L6 and a missing level are PV-ONT-018; L1..L5 are all admitted + test: cargo test -p aprender-contracts --lib lint::evidence_gate::tests::a_level_outside_the_one_enum_is_pv_ont_018 + if_fails: a second level vocabulary exists beside ProofLevel + - id: FALSIFY-EV-003 + rule: a cited claim names its command + prediction: "mark C with no wasGeneratedBy.command is PV-ONT-019" + test: cargo test -p aprender-contracts --lib lint::evidence_gate::tests::a_cited_claim_without_a_command_is_pv_ont_019 + if_fails: a claim is marked cited with nothing to re-run + - id: FALSIFY-EV-004 + rule: a verified sha resolves + prediction: HEAD of a real repository passes; a well-formed sha that is not a commit is PV-ONT-020 + test: cargo test -p aprender-contracts --lib lint::evidence_gate::tests::a_null_census_binds_verified_claims_to_the_repository + if_fails: a [V] mark can cite a commit that never existed + - id: FALSIFY-EV-005 + rule: R-17 + prediction: the same block passes on readme, apr-model and code; the same defect is rejected on readme, apr-model, code, gguf and csv + test: cargo test -p aprender-contracts --lib lint::evidence_gate::tests::r17_the_same_defect_is_rejected_on_every_entity_type + if_fails: the gate judges an entity type differently + - id: FALSIFY-EV-006 + rule: zero is a decline; R-8 + prediction: no evidence block and no Σ both exit 2; the full `pv lint` run computes evidence, reported and not armed + test: cargo test -p aprender-contracts-cli --test ont8_evidence_gate + if_fails: a decline and a pass become indistinguishable, or armed_gates could name a gate no run computes + +qa_gate: + id: F-EV-001 + name: evidence block + description: One evidence block, PROV-O names, one L-enum, on every entity type (ONT-8) + checks: + - closed_key_set + - level_in_the_one_enum + - cited_claim_names_command + - verified_sha_resolves + - agent_in_sigma + - entity_blind + pass_criteria: All 6 falsification tests pass; `pv lint contracts/ --gate evidence` is Pass on the real corpus with levels_source enum and entity_types_checked ≥ 3 + falsification: 'a level check that accepts anything, and a skip on entity.type == readme, each turn tests red (3 failures, measured 2026-09-24)' diff --git a/contracts/ont-refines-v1.yaml b/contracts/ont-refines-v1.yaml new file mode 100644 index 0000000000..1e9a8ddb5d --- /dev/null +++ b/contracts/ont-refines-v1.yaml @@ -0,0 +1,165 @@ +# ───────────────────────────────────────────────────────────────────────────── +# ont-refines-v1 — `A refines B` obeys the Liskov rule, and the witness that +# says so checks (ONT-001 §3.5, R-20, §5 row ONT-4e; issue #4075; ticket +# PMAT-4075) +# +# WHAT IT GOVERNS +# provable_contracts::schema::Clause — `requires[]` and `ensures[]` as +# first-class `{id, statement, formal, formal_status}` lists. +# provable_contracts::ontology::liskov — the three obligations, Pair, +# liskov_sha256, LiskovWitness, check, pc_checker. +# provable_contracts::lint::refines_gate — the `refines` gate: PV-ONT-024 +# (Liskov violation, clause named), PV-ONT-025 (witness does not check), +# PV-ONT-026 (malformed clause), PV-ONT-027 (liskov_prose above the baseline); +# declines NoCheckable / WitnessStale / PositiveControlFailed, exit 3 on a +# malformed Σ. +# `pv-sat` liskov.rs — the UNTRUSTED reasoner, with its own direction table and +# its pc_liskov_reasoner plant. +# +# THE OBLIGATIONS (atoms are opaque: `formal`, whitespace collapsed) +# pre — every clause of A.requires is implied by B.requires (else strengthened) +# post — every clause of B.ensures is implied by A.ensures (else weakened) +# inv — every clause of B.invariants is implied by A.invariants (else dropped) +# An invariant is a clause only when it carries `formal_status`; a legacy +# invariants list or mapping is not one, and a pair with no clauses on either +# side is `legacy` — Pass, with liskov_pairs_checked saying nothing was checked. +# +# PROOF LADDER, STATED HONESTLY +# * L2 — unit tests over the obligations, the checker and the digest; the +# gate's tests; pv-sat's reasoner test; the CLI test runs the §5 probe +# verbatim on this repo's corpus. +# * L3 — not declared. There are no Kani harnesses for this checker. +# * L4 — not declared. There is no Lean theorem. +# The gate is COMPUTED and NOT ARMED: it is absent from `armed_gates`, so its +# verdict does not reach the armed meet until an arming PR adds it. +# ───────────────────────────────────────────────────────────────────────────── +metadata: + version: 1.0.0 + created: '2026-09-24' + last_modified: '2026-09-24' + author: PAIML Engineering + kind: pattern + description: > + ONT-001 Liskov rule — requires/ensures as first-class clauses; every refines pair's pre, post and invariant + obligations decided offline by the untrusted pv-sat and re-verified in the gate by a witness checker with two + positive controls + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §3.5, §5 ONT-4e, R-20 (Liskov on refines), R-1 (the reasoner is untrusted), R-3 (positive controls)' + - 'Liskov & Wing (1994) A behavioral notion of subtyping' + - 'crates/aprender-contracts/src/ontology/liskov.rs — the obligations, the digest, check, pc_checker' + - 'crates/aprender-contracts/src/lint/refines_gate.rs — the refines gate' + - 'crates/aprender-contracts-cli/src/bin/pv-sat/liskov.rs — the reasoner and its plant' +equations: + obligations: + formula: 'A refines B ⇒ (∀c ∈ A.requires: implies(B.requires, c)) ∧ (∀c ∈ B.ensures: implies(A.ensures, c)) ∧ (∀c ∈ B.invariants: implies(A.invariants, c))' + domain: a refines edge of the relations gate's typed graph, both endpoints in the census + codomain: three obligations, each a chain or a counter-model + invariants: + - implies(P, c) holds exactly when some premise in P has the same atom as c + - a prose clause is never decided; it is reported by name + preconditions: + - every clause is well formed; otherwise PV-ONT-026 names its file and id + postconditions: + - a violation names the pair, the obligation and the clause id + lean_theorem: none — L4 not declared + check: + formula: 'check(W, P) = Ok(V) ⇔ W.liskov_sha256 = liskov_sha256(P) ∧ ∀ obligation o: (o = chain ∧ every step pairs a conclusion clause with a same-atom premise clause) ∨ (o = counter_model ∧ violated = exactly the conclusion clauses with no same-atom premise)' + domain: W a Liskov witness, P the checkable pairs of the current corpus + codomain: '{Ok(violations), Err(reason)}' + invariants: + - a counter-model that hides or invents a violation is refused + - a witness for other pairs or with a missing obligation is refused + preconditions: + - W names the current pairs; otherwise the gate declines WitnessStale before checking + postconditions: + - Ok(V) ⇒ V is exactly the Liskov violations of P + lean_theorem: none — L4 not declared + gate: + formula: 'verdict = Unknown(NoCheckable) if no Σ; Unknown(WitnessStale) if checkable pairs and no witness for them; Unknown(PositiveControlFailed) unless pc_checker and pc_reasoner fired; Fail on any finding; Unknown(Prose) if a prose clause; Pass otherwise' + domain: a contracts/ directory with Σ, lint-baseline.json and a witness directory + codomain: V (the ONT-6 lattice) + invariants: + - no verdict over checkable pairs without both positive controls fired (R-3) + - liskov_prose never rises above the committed baseline + preconditions: + - Σ parses; otherwise exit 3 + postconditions: + - verdict == Pass ⇒ every checkable refines pair keeps the Liskov rule by a checked witness + lean_theorem: none — L4 not declared +proof_obligations: +- id: ONT4E-INV-001 + type: invariant + property: each obligation runs in its own direction + formal: 'premise(pre) = B.requires ∧ conclusion(pre) = A.requires ∧ premise(post) = A.ensures ∧ conclusion(post) = B.ensures ∧ premise(inv) = A.invariants ∧ conclusion(inv) = B.invariants' + applies_to: all +- id: ONT4E-INV-002 + type: invariant + property: the checker refuses a chain step whose premise atom differs from its clause + formal: 'step = (c, p) ∧ atom(p) ≠ atom(c) ⇒ check(W, P) = Err' + applies_to: all +- id: ONT4E-INV-003 + type: invariant + property: the checker refuses a counter-model that hides or invents a violation + formal: 'violated(o) ≠ {c ∈ conclusion(o) : ¬ ∃ p ∈ premise(o): atom(p) = atom(c)} ⇒ check(W, P) = Err' + applies_to: all +- id: ONT4E-INV-004 + type: invariant + property: the witness name is order-free and sensitive to every clause + formal: 'liskov_sha256(permute(P)) = liskov_sha256(P) ∧ ∀c: liskov_sha256(P with c changed) ≠ liskov_sha256(P)' + applies_to: all +- id: ONT4E-POST-005 + type: postcondition + property: the gate's exit is the lattice element, and a stale, absent or uncontrolled witness never passes + formal: 'violation ⇒ exit 1 naming the clause; prose ⇒ Unknown(Prose) naming the clause; stale ∨ absent ⇒ exit 2 naming make contracts; ¬pc_checker ∨ ¬pc_reasoner ⇒ exit 2; legacy pair ⇒ Pass with liskov_pairs_checked = 0' + applies_to: all +verification_summary: + total_obligations: 5 + l2_property_tested: 5 + l3_kani_proved: 0 + l4_lean_proved: 0 + l4_sorry_count: 0 + l4_not_applicable: 5 +falsification_tests: +- id: FALSIFY-ONT4E-001 + rule: the precondition direction + prediction: a strengthened precondition is named; a weakened one is Liskov + test: cargo test -p aprender-contracts --lib ontology::liskov::tests::a_strengthened_precondition_is_named + if_fails: refinement runs backwards for requires (measured 2026-09-24 — swapping the Pre sides turns 2 lib tests and 2 pv-sat tests red) +- id: FALSIFY-ONT4E-002 + rule: the postcondition and invariant directions + prediction: a weakened postcondition and a dropped invariant are named; a strengthened postcondition is Liskov + test: cargo test -p aprender-contracts --lib ontology::liskov::tests::a_dropped_invariant_is_named + if_fails: a refinement that breaks a promise passes +- id: FALSIFY-ONT4E-003 + rule: pc_checker + prediction: the checker refuses fixtures/liskov-corrupt.json on every run + test: cargo test -p aprender-contracts --lib ontology::liskov::tests::pc_checker_fires_on_the_shipped_fixture + if_fails: a checker that accepts any implication goes unnoticed (measured 2026-09-24 — skipping the atom check turns 10 lib tests and the pv-sat reasoner test red) +- id: FALSIFY-ONT4E-004 + rule: the gate's lattice + prediction: 'honest witness → Pass; control not fired → PositiveControlFailed; no witness → WitnessStale naming make contracts; legacy → Pass with nothing checked; prose → Unknown(Prose) naming the clause; prose above the baseline → PV-ONT-027; malformed clause → PV-ONT-026; no Σ → NoCheckable' + test: cargo test -p aprender-contracts --lib lint::refines_gate::tests + if_fails: the gate reports a verdict about pairs nobody reasoned over +- id: FALSIFY-ONT4E-005 + rule: the reasoner is untrusted but honest here + prediction: pv-sat decides each direction so that the library checker proves it, and its self-test fires pc_liskov_reasoner and pc_liskov_checker + test: cargo test -p aprender-contracts-cli --bin pv-sat + if_fails: pv-sat writes Liskov witnesses the gate refuses +- id: FALSIFY-ONT4E-006 + rule: the §5 probe end to end + prediction: 'pv lint contracts/ --gate refines on this repo: liskov_pairs_checked > 0, pc_checker fired, verdict Pass; each fixture exits with the verdict its name states, on witnesses the real pv-sat wrote' + test: cargo test -p aprender-contracts-cli --test ont4e_refines_gate + if_fails: the probe the ledger binds is not the behaviour shipped +qa_gate: + id: F-ONT4E-001 + name: ONT-001 Liskov rule on refines + description: requires/ensures as first-class clauses, three directed obligations, a witness checker with two positive controls, the refines gate (ONT-4e) + checks: + - precondition_direction + - postcondition_and_invariant_directions + - pc_checker + - gate_lattice + - reasoner_answers_check + - spec_probe + pass_criteria: All 6 falsification tests pass + falsification: 'a checker that accepts any implication, or a swapped precondition direction — each turns named tests RED (measured 2026-09-24)' diff --git a/contracts/ont-self-v1.yaml b/contracts/ont-self-v1.yaml new file mode 100644 index 0000000000..ea5eb05e23 --- /dev/null +++ b/contracts/ont-self-v1.yaml @@ -0,0 +1,227 @@ +# ───────────────────────────────────────────────────────────────────────────── +# ont-self-v1 — the ontology's contract on itself: the corpus of contracts is +# a well-formed, acyclic, fully-kinded, non-empty graph whose consistency +# witness names the census it was computed over, whose armed gates only grow, +# and whose witness checker accepts nothing that is false (ONT-001 §5 row +# ONT-9, B.16; issue #4078) +# +# WHAT IT GOVERNS +# The corpus itself — entity `pv-contract`, ref `contracts/`. Every invariant +# below is enforced by a gate or refuser that already runs in `make contracts` +# or `pv lint`; this contract names them as ONE claim and binds each to the +# test that turns red when it breaks. +# provable_contracts::lint::relations_gate — cycle_sweep, now including the +# `depends_on ∪ supersedes` union sweep (ONT-9 found it missing: a cycle that +# alternates the two roles is a cycle in neither alone, and passed). +# provable_contracts::ontology::witness — check (the linear witness checker), +# ont_planted, and the KANI-ONT-9-1 harness over ontology::witness_small. +# +# PROOF LADDER, STATED HONESTLY +# * L2 — unit and CLI tests per invariant; `ont_planted` (proptest, 512 seeds, +# graphs of 2-8 ids consistent by construction or carrying a planted +# contradiction; verdict == construction, and every adversarial core +# or model is refused); `kani_ont_9_1_twin` (2048 samples of the +# harness body). +# * L3 — DECLARED, NOT EXECUTED. KANI-ONT-9-1 is a real #[kani::proof] in +# witness.rs. `cargo kani` has not been run on it (the host that +# wrote it is not cleared for Kani), so l3_kani_proved is 0 and +# proof.status is `declared`. The bound is 3 ids and cores of at most +# 3 steps; nothing is claimed for 4 ids. +# * L4 — not declared. There is no Lean theorem. +# RESIDUAL — spec-level common mode: the checker, the oracle and the planted +# constructions all read the same Horn reading of the relations (§3.5). A +# mistake in THAT reading is invisible to every rung here. +# ───────────────────────────────────────────────────────────────────────────── +metadata: + version: 1.0.0 + created: '2026-09-24' + last_modified: '2026-09-24' + author: PAIML Engineering + kind: kernel + depends_on: [ont-consistency-v1, ont-relations-v1, ont-verdict-lattice-v1] + # ONT-7: every invariant is a fact about the committed tree and the gates run over it. + valid_under: + world: committed + description: > + ONT-001 self — the contract corpus as an entity: unique ids, an acyclic depends_on ∪ supersedes, every + contract kinded, a non-empty census, a witness bound to the census it names, monotone arming, and a witness + checker that accepts only what is true under relation semantics (ONT-9) + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §5 ONT-9, B.16 (the example this contract instantiates), F-12 (planted-solution graphs, verdict == construction)' + - 'crates/aprender-contracts/src/lint/relations_gate.rs — cycle_sweep and the depends_on ∪ supersedes union sweep' + - 'crates/aprender-contracts/src/ontology/witness.rs — check, ont_planted, kani_proofs::kani_ont_9_1' + - 'crates/aprender-contracts/src/ontology/witness_small.rs — the bounded universe and relation-semantics oracle' + - 'crates/aprender-contracts/src/ontology/witness_planted.rs — the planted-solution generators' + - 'crates/aprender-contracts/src/ontology/arming.rs — check_monotone' + +entity: + type: pv-contract + ref: contracts/ + +relations: + depends_on: [ont-consistency-v1, ont-relations-v1, ont-verdict-lattice-v1] + +equations: + corpus: + formula: 'wf(K) = unique(ids(K)) ∧ acyclic(depends_on ∪ supersedes) ∧ ∀c ∈ K: kind(c) ∈ KINDS ∧ n_files(K) > 0 ∧ witness.census_id_set_sha256 = census_sha(K) ∧ armed(K) ⊇ armed(committed)' + domain: K the contracts/ directory at the committed tree + codomain: '{true, false}, each conjunct reported by its own gate' + invariants: + - an id names exactly one contract + - no id reaches itself along depends_on and supersedes edges taken together + - a contract with no metadata.kind is defaulted and named, never silently untyped + - an empty corpus is refused, never a vacuous pass + - a witness computed over another census is stale, never checked + - an armed gate is never silently dropped + preconditions: + - Σ (contracts/ontology.yaml) parses + postconditions: + - each failed conjunct is a named finding, not a count + lean_theorem: none — L4 not declared + checker_soundness: + formula: 'check(G, W) = Ok(Unsat(K)) ⇒ ¬∃σ: σ ⊨ G, and check(G, W) = Ok(Sat) ⇒ ∃σ: σ ⊨ G' + domain: G a Horn clause set over contract ids (units, implications, conflict pairs); W a witness + codomain: '{Ok(Sat), Ok(Unsat(core)), Err(reason)}' + invariants: + - an accepted core names only ids the graph has + - a refusal claims nothing and is always sound + preconditions: + - W was produced by the untrusted reasoner or by anyone else; the checker trusts none of it + postconditions: + - verdict == construction on every planted graph + lean_theorem: none — L4 not declared + +proof_obligations: +- id: ONTSELF-INV-001 + type: invariant + property: contract ids are unique — a stem shared by two contracts is reported, never resolved silently + formal: '∀a, b ∈ K: a ≠ b ⇒ id(a) ≠ id(b)' + applies_to: all +- id: ONTSELF-INV-002 + type: invariant + property: depends_on and supersedes are acyclic taken TOGETHER, not only each alone + formal: '∀x ∈ ids(K): ¬(x reaches x along depends_on ∪ supersedes)' + applies_to: all +- id: ONTSELF-INV-003 + type: invariant + property: kind totality — a contract without metadata.kind is defaulted and named on its first error + formal: '∀c ∈ K: kind(c) ∈ KINDS' + applies_to: all +- id: ONTSELF-INV-004 + type: invariant + property: the corpus is non-empty — zero contracts is a refusal, never a vacuous pass + formal: 'len(K) ≥ 1' + applies_to: all +- id: ONTSELF-INV-005 + type: invariant + property: the witness names the census it was computed over; a missing or foreign witness is stale + formal: 'check_run ⇒ witness.census_id_set_sha256 = census_sha(K) ∧ witness.relations_sha256 = relations_sha(K)' + applies_to: all +- id: ONTSELF-INV-006 + type: invariant + property: armed_gates is monotone against the committed comparand + formal: 'armed(committed) ⊆ armed(K)' + applies_to: all +- id: ONTSELF-INV-007 + type: invariant + property: the witness checker is sound under relation semantics — an accepted core is a real contradiction, an accepted model a real model + formal: 'check(G, W) = Ok(Unsat(core)) ⇒ ¬satisfiable(G) ∧ core ⊆ ids(G) ∧ len(core) ≥ 1' + applies_to: all + +verification_summary: + total_obligations: 7 + l2_property_tested: 7 + l3_kani_proved: 0 + l4_lean_proved: 0 + l4_sorry_count: 0 + l4_not_applicable: 7 + +proof: + status: declared + kani: + harnesses: [KANI-ONT-9-1] + +kani_harnesses: +- id: KANI-ONT-9-1 + obligation: ONTSELF-INV-007 + property: every verdict check accepts over 3 ids is true under relation semantics — an accepted core means no assignment satisfies the graph, an accepted model means one does + bound: 3 + strategy: exhaustive + solver: cadical + harness: kani_ont_9_1 +kani_harnesses_note: > + DECLARED, NOT EXECUTED. kani_ont_9_1 (witness.rs, cfg(kani)) is bounded at CORE_BOUND = 3 steps over 3 ids, + unwind 12. The bound is measured, not chosen: the pv-sat plant's core is 3 steps and the committed corpus witness + is a model. Over 3 ids a core of at most 3 steps is complete (each step derives one id). `cargo kani` has not + been run; until it has, proof.status stays `declared` and l3_kani_proved stays 0 (ont9_self_contract enforces the + pair). kani_ont_9_1_twin samples the same body under proptest. + +falsification_tests: +- id: FALSIFY-ONTSELF-001 + rule: unique ids + prediction: a stem shared by two contract files is reported as ambiguous, not resolved to either + test: cargo test -p aprender-contracts --lib lint::duplicate_stems::tests::ambiguous_stem_is_reported_not_silently_resolved + if_fails: a relation to a shared id binds to whichever file the walk met first +- id: FALSIFY-ONTSELF-002 + rule: acyclic depends_on ∪ supersedes + prediction: a → b by depends_on and b → a by supersedes is PV-ONT-009 naming `a -> b -> a` (red before ONT-9, measured 2026-09-24) + test: cargo test -p aprender-contracts --lib lint::relations_gate::tests::a_cycle_through_depends_on_and_supersedes_together_is_rejected + if_fails: a cycle that alternates the two roles passes the relations gate +- id: FALSIFY-ONTSELF-003 + rule: a per-role cycle + prediction: a cycle through one acyclic role is PV-ONT-009 naming the closing path + test: cargo test -p aprender-contracts --lib lint::relations_gate::tests::a_cycle_through_an_acyclic_role_is_rejected_naming_the_path + if_fails: introducing a cycle into the corpus stays green +- id: FALSIFY-ONTSELF-004 + rule: kind totality + prediction: a kindless contract that fails names the defaulted kind on its first error + test: cargo test -p aprender-contracts-cli --test ont6b_kind_default kindless_failing_names_the_default_on_its_first_error + if_fails: a contract is linted under a kind nobody declared or saw +- id: FALSIFY-ONTSELF-005 + rule: n_files > 0 + prediction: pv lint over an empty directory is refused + test: cargo test -p aprender-contracts-cli --test pvl_zero_contracts lint_empty_dir_is_refused + if_fails: 0 violations over 0 files reads as a pass +- id: FALSIFY-ONTSELF-006 + rule: witness bound to the census + prediction: a missing or foreign witness declines WitnessStale and is never checked + test: cargo test -p aprender-contracts --lib lint::consistency_gate::tests::a_missing_or_foreign_witness_is_stale_not_a_pass + if_fails: a witness about another corpus passes this one +- id: FALSIFY-ONTSELF-007 + rule: armed_gates monotone + prediction: dropping an armed gate is a shrink that names it + test: cargo test -p aprender-contracts --lib ontology::arming::tests::a_dropped_gate_is_a_shrink_naming_it + if_fails: a gate is disarmed without anyone deciding to +- id: FALSIFY-ONTSELF-008 + rule: checker soundness, planted (F-12) + prediction: on 512 planted graphs verdict == construction and every adversarial certificate is refused; a checker weakened to accept any two ids goes RED (measured 2026-09-24) + test: cargo test -p aprender-contracts --lib ontology::witness::planted::ont_planted + if_fails: the checker accepts a core that proves nothing or a model that falsifies a clause +- id: FALSIFY-ONTSELF-009 + rule: checker soundness, bounded (L2 twin of KANI-ONT-9-1) + prediction: over 3 ids every accepted verdict agrees with the brute-force oracle + test: cargo test -p aprender-contracts --lib ontology::witness_small::tests::kani_ont_9_1_twin + if_fails: the harness body is false and the Kani run would find a counterexample +- id: FALSIFY-ONTSELF-010 + rule: the §5 probe end to end, the L3 rung not claimed early, a corpus cycle RED on the CLI + prediction: the contract is tracked and validates; witness.rs carries ont_planted and KANI-ONT-9-1; declared ⇒ l3_kani_proved = 0; every falsifier names a test fn that exists + test: cargo test -p aprender-contracts-cli --test ont9_self_contract + if_fails: the probe the ledger binds is not the behaviour shipped + +qa_gate: + id: F-ONTSELF-001 + name: ONT-001 ontology self-contract + description: the corpus is a well-formed acyclic kinded non-empty graph, its witness names its census, arming is monotone, and the checker is sound (ONT-9) + checks: + - unique_ids + - union_acyclic + - per_role_acyclic + - kind_totality + - non_empty + - witness_census_bound + - arming_monotone + - checker_planted + - checker_bounded_twin + - spec_probe + pass_criteria: All 10 falsification tests pass + falsification: 'a checker that accepts any two ids, a model check that skips conflicts, or a relations gate without the union sweep — each turns a named test RED (measured 2026-09-24)' diff --git a/contracts/ont-shapes-v1.yaml b/contracts/ont-shapes-v1.yaml index 3441267582..fd5b821820 100644 --- a/contracts/ont-shapes-v1.yaml +++ b/contracts/ont-shapes-v1.yaml @@ -131,3 +131,14 @@ qa_gate: - warnings_never_pass pass_criteria: All 5 falsification tests pass; `pv lint contracts/ --gate shapes` is Pass on the real corpus with shapes_n ≥ 1, focus_nodes_n ≥ 1000, pc_shape fired falsification: 'remove minCount from ont:id → Unknown{PositiveControlFailed} (measured 2026-09-18); add a kind outside the list to one contract → 1 violation naming it' + +# ONT-8: one evidence block — PROV-O names, one L-enum (ProofLevel). The level is MEASURED, not declared: it is what +# the cited command printed for this contract at the time below. Checked by `pv lint --gate evidence`. +evidence: + level: L1 + mark: C + provenance: + wasGeneratedBy: + command: "pv proof-status contracts/ --format json" + wasAttributedTo: pv + generatedAtTime: "2026-09-24T08:22:07Z" diff --git a/contracts/ontology.ofn b/contracts/ontology.ofn new file mode 100644 index 0000000000..865a33dd14 --- /dev/null +++ b/contracts/ontology.ofn @@ -0,0 +1,58 @@ +# ontology.ofn — Σ as OWL 2 functional syntax. GENERATED from its ontology.yaml by +# `pv ontology export --owl`; do not edit (ONT-001 ONT-2c, R-18). Not expressed here, by design: +# acyclic: irreflexive ∧ transitive is disallowed in OWL 2 DL; acyclicity is checked in Rust (R-19) +# agents: agents are provenance actors (prov), not classes of the TBox +# entity_types: entity types name extractors; their classes are concepts, which ARE written +# extractors: extractors are readers (code), not ontology +# symbols: the `formal:` token vocabulary is a lexicon, not a TBox +# worlds: worlds scope contracts; OWL 2 EL has no modal or context construct +Ontology( +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(Class()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +SubClassOf( ) +SubClassOf( ) +SubClassOf( ) +SubClassOf( ) +SubClassOf( ) +SubClassOf( ) +ObjectPropertyDomain( ) +ObjectPropertyDomain( ) +ObjectPropertyDomain( ) +ObjectPropertyDomain( ) +ObjectPropertyDomain( ) +ObjectPropertyDomain( ) +ObjectPropertyDomain( ) +ObjectPropertyDomain( ) +ObjectPropertyRange( ) +ObjectPropertyRange( ) +ObjectPropertyRange( ) +ObjectPropertyRange( ) +ObjectPropertyRange( ) +ObjectPropertyRange( ) +ObjectPropertyRange( ) +ObjectPropertyRange( ) +SymmetricObjectProperty() +) diff --git a/contracts/ontology.yaml b/contracts/ontology.yaml index 8e0fc9d82c..4ccb2b3104 100644 --- a/contracts/ontology.yaml +++ b/contracts/ontology.yaml @@ -27,6 +27,14 @@ concepts: Statement: {doc: "a Lean theorem in the in-tree Theorems tree; the focus node of extract:lean (ONT-4b2: ont:Statement with lean:sorryFree, lean:modelOf)"} Model: {doc: "a model file — an .apr container or a GGUF; the ladder's rungs are the ones the release measures (ONT-4c1: model:Model, model:RequiredModel, model:Receipt)"} Dataset: {doc: "a CSV or corpus file"} + # ONT-4f (aprender#4330): the tracked GitHub snapshots under evidence/github//, read by extract:json + # through the `vocabulary` each entity type below carries. Each is ⊑ Json (see `subsumes`). + Json: {doc: "a JSON document extract:json reads — a tool's --json output, or a committed snapshot"} + Repo: {doc: "a GitHub repository at one commit, snapshotted under evidence/github/repo/ (repo:ref = /@)"} + Issue: {doc: "a GitHub issue at one updatedAt, snapshotted under evidence/github/issue/ (issue:ref = /#@)"} + PullRequest: {doc: "a GitHub pull request at one updatedAt, snapshotted under evidence/github/pull-request/ (pr:ref = /#@)"} + Milestone: {doc: "a GitHub milestone at one updatedAt, snapshotted under evidence/github/milestone/ (milestone:ref = /#@)"} + Example: {doc: "a cargo example target — examples/*.rs or examples/*/main.rs of a workspace member; the focus node of extract:example (#3560 R1: example:file, example:crate, example:name, example:namesModel, example:namesQwen35)"} roles: binds: @@ -135,6 +143,7 @@ entity_types: - {name: json, extractor: json, implemented: true} - {name: code, extractor: code, implemented: true} - {name: lean, extractor: lean, implemented: true} + - {name: example, extractor: example, implemented: true} - {name: readme, extractor: readme, implemented: false} - {name: llm-context, extractor: llm_context, implemented: false} - {name: apr-model, extractor: apr_model, implemented: true} @@ -147,12 +156,20 @@ entity_types: # aprender#3715: a release's receipts (per-host inventory + cells, kernel diffs, context rungs, the dogfood # receipt R5 judged), extracted ONLY when a release subject is given — an ordinary PR has no release. - {name: release-evidence, extractor: release_evidence, implemented: true} + # ONT-4f (aprender#4330): GitHub entities as COMMITTED SNAPSHOTS (evidence/github//.json), read by + # the json extractor through the vocabulary here — never a live API call. `version` is the snapshot's own field + # the ref's `@` slot must equal; a disagreement is refused naming both values. + - {name: repo, extractor: json, implemented: true, vocabulary: {prefix: repo, root_class: "ont:Repo", version: sha}} + - {name: issue, extractor: json, implemented: true, vocabulary: {prefix: issue, root_class: "ont:Issue", version: updatedAt}} + - {name: pull-request, extractor: json, implemented: true, vocabulary: {prefix: pr, root_class: "ont:PullRequest", version: updatedAt}} + - {name: milestone, extractor: json, implemented: true, vocabulary: {prefix: milestone, root_class: "ont:Milestone", version: updatedAt}} extractors: - {name: pv_contract, reader: ontology/extract/pv_contract.rs, implemented: true} - {name: json, reader: ontology/extract/json.rs, implemented: true} - {name: code, reader: ontology/extract/code.rs, implemented: true} - {name: lean, reader: ontology/extract/lean.rs, implemented: true} + - {name: example, reader: ontology/extract/example.rs, implemented: true} - {name: readme, reader: ontology/extract/readme.rs, implemented: false} - {name: llm_context, reader: ontology/extract/llm_context.rs, implemented: false} - {name: apr_model, reader: ontology/extract/apr_model.rs, implemented: true} @@ -167,13 +184,35 @@ not_expressible: - {key: taste, reader: ontology/sigma.rs} - {key: urgency, reader: ontology/sigma.rs} - {key: authorship_intent, reader: ontology/sigma.rs} + # ONT-2c: Σ content the OWL 2 EL writer (ontology/owl.rs) deliberately does not turn into axioms. Each key's + # reason is printed in the header of contracts/ontology.ofn; a populated key missing here refuses the export. + - {key: acyclic, reader: ontology/owl.rs} + - {key: symbols, reader: ontology/owl.rs} + - {key: worlds, reader: ontology/owl.rs} + - {key: agents, reader: ontology/owl.rs} + - {key: entity_types, reader: ontology/owl.rs} + - {key: extractors, reader: ontology/owl.rs} + +# ONT-4d (R-19): the subsumption hierarchy. Each edge is read off the two concepts' own `doc` above, never +# asserted for convenience: `Kernel` is "a contract whose kind is kernel", and `Symbol` is "a Rust symbol … a free +# fn or an impl method", which is a `Code` item ("a Rust item — a function, type or module"). Shapes on the super +# apply to every instance of the sub, through the rdf:type closure `pv extract` materializes into contracts.nt. +subsumes: + - {sub: Kernel, sup: Contract} + - {sub: Symbol, sup: Code} + # ONT-4f: each GitHub snapshot IS a JSON document extract:json reads (the Json doc above names both kinds). + - {sub: Repo, sup: Json} + - {sub: Issue, sup: Json} + - {sub: PullRequest, sup: Json} + - {sub: Milestone, sup: Json} readers: concepts: ontology/sigma.rs + subsumes: ontology/sigma.rs, ontology/extract/mod.rs, lint/subsumption.rs, ontology/owl.rs roles: lint/sigma_gate.rs, lint/relations_gate.rs symbols: lint/sigma_symbols.rs worlds: ontology/sigma.rs, lint/valid_under_gate.rs - agents: ontology/sigma.rs - entity_types: lint/sigma_gate.rs + agents: ontology/sigma.rs, lint/evidence_gate.rs + entity_types: lint/sigma_gate.rs, ontology/extract/json/github.rs extractors: ontology/sigma.rs not_expressible: ontology/sigma.rs diff --git a/contracts/pvl-lint-ratchets-v1.yaml b/contracts/pvl-lint-ratchets-v1.yaml new file mode 100644 index 0000000000..e8160db4af --- /dev/null +++ b/contracts/pvl-lint-ratchets-v1.yaml @@ -0,0 +1,121 @@ +# ───────────────────────────────────────────────────────────────────────────── +# pvl-lint-ratchets-v1 — theorem pairing + depends_on, shrink-only (PVL-001 EV-11) +# (paiml/infra PVL-001 @00553b0b §EV-11; issue #4166; ticket PMAT-4166) +# +# WHAT IT GOVERNS +# provable_contracts::lint::ratchet_gates — PV-RAT-001 (theorem-pairing) and +# PV-RAT-002 (depends-on-present); `pv lint --gate theorem-pairing --gate +# depends-on-present` and gates 14/15 of the full run (R-8: computed +# everywhere, armed per repo); `make lint-ratchet` (scripts/lint_ratchet.sh), +# the only writer of the two baselines, which only turns them down. +# +# THE PAIRING RULE IS THIS ROW'S DESIGN. The row says "module name appears in +# book/"; the full dotted module name on identifier boundaries is the reading +# that cannot be satisfied by accident (the book names 80 of 165 file STEMS, +# mostly as ordinary words; measured 2026-09-24). +# ───────────────────────────────────────────────────────────────────────────── +name: pvl-lint-ratchets +version: "1.0.0" +scope: > + Two debts that may fall and never rise: Lean theorem modules no book page names, and kernel-kind contracts + with an empty `metadata.depends_on`. Out of scope: whether a book page's mention is a real explanation, and + whether a declared dependency is the right one. +status: active + +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-24' + last_modified: '2026-09-24' + author: PAIML Engineering + description: > + `pv lint --gate theorem-pairing` counts Lean theorem modules whose full module name no book page mentions; + `--gate depends-on-present` counts kernel-kind contracts with no depends_on. Each rejects a count above its + top-level baseline in contracts/lint-baseline.json, reports (never passes) with no baseline, declines when + nothing was measured, and never writes the file. + references: + - 'paiml/infra docs/specifications/pvl-001 @00553b0b §EV-11 (probe + mutation)' + - 'crates/aprender-contracts/src/lint/ratchet_gates.rs — PV-RAT-001, PV-RAT-002' + - 'scripts/lint_ratchet.sh — `make lint-ratchet`, the downward-only writer' + +relations: + depends_on: [ont-verdict-lattice-v1] + +equations: + shrink_only_ratchet: + formula: "admits(corpus) ⟺ baseline ≠ ⊥ ∧ debt(corpus) ≤ baseline" + domain: "a contract corpus, its repo root (lean/, book/) and contracts/lint-baseline.json" + codomain: "Pass | Fail | Unknown" + invariants: + - "no baseline is Unknown(Report) with the count printed, never Pass" + - "nothing measured (no Lean base, no module, no book page, no kernel contract) is a decline" + preconditions: + - "the baseline, when present, is a non-negative integer at the top level" + postconditions: + - "verdict ∈ {Pass, Fail, Unknown}" + - "contracts/lint-baseline.json is byte-identical after the gate" + lean_theorem: none — L4 not declared + +invariants: + - id: RAT-INV-001 + property: the unpaired theorem module debt is shrink-only + formal: 'unpaired(corpus) ≤ baseline(unpaired_theorem_modules)' + prose: false + - id: RAT-INV-002 + property: the kernel contracts without depends_on debt is shrink-only + formal: 'without_depends_on(corpus) ≤ baseline(contracts_without_depends_on)' + prose: false + - id: RAT-INV-003 + property: a module is paired only by its full dotted name on identifier boundaries + formal: 'paired(m) ⟺ ∃ p ∈ book: mentions(p, name(m))' + prose: false + - id: RAT-INV-004 + property: a gate never writes its baseline + formal: '¬(after_gate(baseline) ≠ before_gate(baseline))' + prose: false + +falsification_tests: + - id: FALSIFY-RAT-001 + rule: the spec's mutation + prediction: adding an unpaired Theorem module turns `pv lint --gate theorem-pairing --gate depends-on-present` RED with PV-RAT-001 + test: cargo test -p aprender-contracts-cli --test ev11_lint_ratchets + if_fails: a theorem module can be added that no book page names, unrecorded + - id: FALSIFY-RAT-002 + rule: the depends_on ratchet + prediction: a kernel-kind contract with no depends_on above the baseline is PV-RAT-002; equal or lower passes + test: cargo test -p aprender-contracts --lib lint::ratchet_gates::tests::adding_a_kernel_contract_with_no_depends_on_is_red + if_fails: a kernel contract that names no composition can be added unrecorded + - id: FALSIFY-RAT-003 + rule: the pairing boundary + prediction: a file stem, or a longer module name that starts with it, does not pair a module + test: cargo test -p aprender-contracts --lib lint::ratchet_gates::tests::the_file_stem_or_a_longer_name_does_not_pair_a_module + if_fails: the book's ordinary words pair modules by accident + - id: FALSIFY-RAT-004 + rule: no baseline is reported, never judged + prediction: with no baseline key both gates are Unknown(Report), exit 2, the count printed + test: cargo test -p aprender-contracts --lib lint::ratchet_gates::tests::no_baseline_is_reported_and_never_a_pass + if_fails: deleting the baseline turns the ratchet into a pass + - id: FALSIFY-RAT-005 + rule: the gates never write + prediction: the baseline is byte-identical after both gates, on fixtures and on the real corpus + test: cargo test -p aprender-contracts --lib lint::ratchet_gates::tests::neither_gate_writes_the_baseline + if_fails: a gate can raise its own baseline + - id: FALSIFY-RAT-006 + rule: R-8 + prediction: the full `pv lint` run computes theorem-pairing and depends-on-present, reported and not armed + test: cargo test -p aprender-contracts --lib lint::tests + if_fails: armed_gates could name a gate no run computes + +qa_gate: + id: F-RAT-001 + name: lint ratchets + description: Theorem pairing and depends_on debts may fall, never rise (PVL-001 EV-11) + checks: + - unpaired_theorem_modules_shrink_only + - contracts_without_depends_on_shrink_only + - strict_pairing_boundary + - no_baseline_is_report + - gate_never_writes + - computed_in_every_run + pass_criteria: All 6 falsification tests pass; the EV-11 probe exits 0 on the real corpus and `git diff --exit-code contracts/lint-baseline.json` is clean after it + falsification: 'add lean/ProvableContracts/Theorems/S/B.lean named by no book page → exit 1, PV-RAT-001 (measured on a fixture 2026-09-24)' diff --git a/contracts/ratatui-migration-v1.yaml b/contracts/ratatui-migration-v1.yaml index ebdfa1d982..f5b4442660 100644 --- a/contracts/ratatui-migration-v1.yaml +++ b/contracts/ratatui-migration-v1.yaml @@ -86,7 +86,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 4 + total_obligations: 2 proven: 4 tested: 4 status: COMPLETE diff --git a/contracts/refusal-receipt-v1.yaml b/contracts/refusal-receipt-v1.yaml index f0d98bc34c..4e9cbb4746 100644 --- a/contracts/refusal-receipt-v1.yaml +++ b/contracts/refusal-receipt-v1.yaml @@ -171,3 +171,14 @@ falsification_tests: prediction: an entry with no exit_code, or a one-word reason, is refused test: cargo test -p aprender-contracts-cli --test ont_refusal_receipt if_fails: a refusal is indistinguishable from success to anything not reading prose + +# ONT-8: one evidence block — PROV-O names, one L-enum (ProofLevel). The level is MEASURED, not declared: it is what +# the cited command printed for this contract at the time below. Checked by `pv lint --gate evidence`. +evidence: + level: L1 + mark: C + provenance: + wasGeneratedBy: + command: "pv proof-status contracts/ --format json" + wasAttributedTo: pv + generatedAtTime: "2026-09-24T08:22:07Z" diff --git a/contracts/release-readiness-v1.yaml b/contracts/release-readiness-v1.yaml index bf8bc8d8db..3966b44355 100644 --- a/contracts/release-readiness-v1.yaml +++ b/contracts/release-readiness-v1.yaml @@ -260,3 +260,14 @@ qa_gate: - no_subject_declines pass_criteria: All 3 falsification tests pass; `pv lint contracts --gate shapes --shape release-readiness-v1 --release-version V --release-commit MC --receipts DIR --dogfood-receipt FILE` exits 0 only when every cell passes falsification: 'flip one cell row of tests/fixtures/ont/release-green to verdict skip → the gate exits 1 naming that cell' + +# ONT-8: one evidence block — PROV-O names, one L-enum (ProofLevel). The level is MEASURED, not declared: it is what +# the cited command printed for this contract at the time below. Checked by `pv lint --gate evidence`. +evidence: + level: L1 + mark: C + provenance: + wasGeneratedBy: + command: "pv proof-status contracts/ --format json" + wasAttributedTo: pv + generatedAtTime: "2026-09-24T08:22:07Z" diff --git a/contracts/repo-filesystem-v1.yaml b/contracts/repo-filesystem-v1.yaml index 86bbf91a4d..2965703e98 100644 --- a/contracts/repo-filesystem-v1.yaml +++ b/contracts/repo-filesystem-v1.yaml @@ -102,7 +102,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 6 + total_obligations: 1 proven: 0 tested: 0 status: pending diff --git a/contracts/review-corpus-contamination-v1.yaml b/contracts/review-corpus-contamination-v1.yaml new file mode 100644 index 0000000000..790babea85 --- /dev/null +++ b/contracts/review-corpus-contamination-v1.yaml @@ -0,0 +1,38 @@ +metadata: + version: 1.0.0 + kind: schema + description: | + PROMETHEUS (PRM-001) sealed-test contamination check, spec §2.2 and rule R-2. A sealed + test item leaks when a training file carries its diff sha256 literally, or any of + its hunks (header stripped, >= 4 lines, at least one change) — including inside + JSON string values, so a diff stored in a JSONL field is seen unescaped. Training + roots: docs/audits/review-corpus/train/ (contamination::TRAIN_ROOTS). Any hit is + a hard FAIL (S-3). + references: + - "docs/specifications/review-experiment-protocol.md" + - "docs/audits/review-corpus/test-manifest-v1.txt" + - "https://github.com/paiml/aprender/issues/4357" + +equations: + leak: + formula: "leak(f) = exists t in sealed: t.diff_sha256 in text(f) or hunks(t) ∩ hunks(text(f) ∪ json_strings(f)) != {}" + +falsification_tests: + - id: FALSIFY-RCC-001 + name: planted_jsonl_leak_is_caught + prediction: "a sealed diff inside a JSONL training record is a hit" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rcc_001" + expected_output: "exit 0" + if_fails: "Escaped diffs in JSONL pass the check; B2 teacher sets could train on test items." + - id: FALSIFY-RCC-002 + name: rebased_copy_and_sha_literal_leak + prediction: "a copy with other hunk line numbers, and a bare diff sha, are hits; an unrelated diff is not" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rcc_002" + expected_output: "exit 0" + if_fails: "The check keys on line numbers or misses sha references." + - id: FALSIFY-RCC-003 + name: train_roots_are_clean + prediction: "no file under the training roots carries a sealed test item" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rcc_003" + expected_output: "exit 0" + if_fails: "S-3: a sealed test item is in training data. STOP; rotate the leaked items out." diff --git a/contracts/review-corpus-v1.yaml b/contracts/review-corpus-v1.yaml new file mode 100644 index 0000000000..b6bd17367a --- /dev/null +++ b/contracts/review-corpus-v1.yaml @@ -0,0 +1,44 @@ +metadata: + version: 1.0.0 + kind: schema + description: | + PROMETHEUS (PRM-001, was REX-001) review corpus v1, spec §2.2. 150 items: P (planted + cargo-mutants diffs, markers stripped), R (merged fix PRs with a linked issue, + code hunks reverse-applied), G (merged PRs before the 14-day cutoff, green + ci / gate + workspace-test, not reverted, no regression label). Strata on a + bytes/4 token proxy (S < 2000, M 2000–8000, L > 8000). Seeded (4354), stratified + 30/70 dev/test split. Item metadata: docs/audits/review-corpus/corpus-v1.jsonl. + Sealed test manifest: docs/audits/review-corpus/test-manifest-v1.txt (version + review-corpus-v1@787d2026256cc08b). Diffs: items-v1.tar (sha256 70dffe5625e1849661303b82bb1fdf5254ab1d4560e2fe3eacc147e5d2588bba), outside git. + references: + - "docs/specifications/review-experiment-protocol.md" + - "docs/audits/rex-001/rex-02-receipt.md" + - "https://github.com/paiml/aprender/issues/4357" + +equations: + stratum: + formula: "t = ceil(bytes/4); S if t < 2000, M if t <= 8000, else L" + split: + formula: "per (class, stratum): order by SplitMix64(4354) key in id order; first round(0.3 n) are dev" + corpus_version: + formula: "'review-corpus-v1@' || sha256(test-manifest-v1.txt)[0..16]" + +falsification_tests: + - id: FALSIFY-RCV-001 + name: mutation_markers_never_reach_a_prompt + prediction: "a sanitised P diff names neither cargo-mutants nor the replacement" + test_harness: "cargo test -p aprender-review-experiment --lib sanitize_removes_every_mutation_marker" + expected_output: "exit 0" + if_fails: "P items leak their answer; every P verdict is void." + - id: FALSIFY-RCV-002 + name: split_is_seeded_stratified_and_order_free + prediction: "the same seed gives the same split whatever the input order; a different seed moves it; each cell is 30% dev" + test_harness: "cargo test -p aprender-review-experiment --lib split_is_stratified_seeded_and_stable" + expected_output: "exit 0" + if_fails: "The split is not reproducible; the sealed manifest cannot be re-derived." + - id: FALSIFY-RCV-003 + name: comment_only_hunks_are_not_defects + prediction: "R items keep only hunks that change code; defect locations point at code lines" + test_harness: "cargo test -p aprender-review-experiment --lib comment_only_hunks_are_not_the_fix" + expected_output: "exit 0" + if_fails: "R labels name comments; localization is measured against the wrong line." diff --git a/contracts/review-experiment-receipt-v1.yaml b/contracts/review-experiment-receipt-v1.yaml new file mode 100644 index 0000000000..cc576d04b4 --- /dev/null +++ b/contracts/review-experiment-receipt-v1.yaml @@ -0,0 +1,54 @@ +metadata: + version: 1.0.0 + kind: schema + description: | + PROMETHEUS (PRM-001, was REX-001) receipt, spec §2.3/§2.4. One JSONL row per + (item, cell, arm) written by the REX-03 harness (crates/aprender-review-experiment: + harness.rs) against a resident `apr serve`. A row is admissible only if it + deserializes with every required field (no unknown fields), carries no empty or + `unknown` identity field, carries 64-hex shas for the item, prompt, request, prereg, + apr binary and weights (hosted arms say `hosted`), names the locked prereg sha and + the corpus version under analysis, is greedy, and, when it ran, carries token + counts, timings, raw-output path+sha and a load snapshot. The scorer (score.rs) + scores the EXPECTED item set: an item without exactly one admissible row whose raw + output exists with the recorded sha and re-parses to the recorded verdict is + NotRun{Inadmissible}. Unparsed and NotRun are never correct and never a FAIL (R-6). + Signed receipts are verified with minisign; unsigned results are labelled + exploratory. + references: + - "docs/specifications/review-experiment-protocol.md" + - "docs/audits/rex-001/analysis-plan.md" + - "docs/audits/rex-001/rex-03-receipt.md" + - "https://github.com/paiml/aprender/issues/4358" + +equations: + correct: + formula: "correct(item) = (verdict == FAIL and class in {P,R}) or (verdict == PASS and class == G)" + recall: + formula: "|FAIL on P∪R| / |P∪R expected| (NotRun and Unparsed stay in the denominator)" + precision: + formula: "|FAIL on P∪R| / |FAIL|" + false_refute: + formula: "|FAIL on G| / |G expected|" + parse_rate: + formula: "|PASS ∪ FAIL| / |executed|" + +falsification_tests: + - id: FALSIFY-RXR-001 + name: missing_model_sha_is_inadmissible + prediction: "a receipt without weights_sha256 (or with `unknown`, or a sha prefix) is inadmissible" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rxr_001" + expected_output: "exit 0" + if_fails: "Receipts can no longer be tied to the weights that produced them; every cross-cell and cross-model comparison is unanchored." + - id: FALSIFY-RXR-002 + name: not_run_is_never_correct + prediction: "NotRun (any reason, including a missing receipt) is never correct and never a FAIL; it stays in the recall and correctness denominators" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rxr_002" + expected_output: "exit 0" + if_fails: "A cell that cannot run, or a dropped receipt, would raise a score (R-6)." + - id: FALSIFY-RXR-003 + name: unparsed_is_never_a_pass + prediction: "Unparsed is never correct on a G item, never a FAIL on a defect, and never enters a paired rate" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rxr_003" + expected_output: "exit 0" + if_fails: "A model that answers garbage would score as a clean reviewer on good diffs." diff --git a/contracts/rex-cell-admission-v1.yaml b/contracts/rex-cell-admission-v1.yaml new file mode 100644 index 0000000000..a6d1ce2aca --- /dev/null +++ b/contracts/rex-cell-admission-v1.yaml @@ -0,0 +1,45 @@ +metadata: + version: 1.0.0 + kind: schema + description: | + PROMETHEUS (PRM-001, was REX-001) REX-04 per-cell admission, spec §2.1/§7. One JSONL + row per §2.1 device cell (C1 intel-wgpu, C2 intel-cpu, C3 lambda-cpu, C4 gx10-cuda, + C5a mini-cpu, C5b mini-metal) written by `rex admit` (crates/aprender-review-experiment: + admission.rs). A file is admissible only if every cell has exactly one row, each + row names its declared host and backend, carries the apr tag, the apr and weights + sha256, the model id and the locked prereg sha, and resolves to Admitted (a parity + receipt sha, an oracle, a cosine at or above a threshold with a cited basis), + Refused{removed_by}, or NotRun{reason}. A silent cell rejects the whole file. When + no cell is Admitted, the summary raises S-7 and `rex admission-check` exits 10. + references: + - "docs/specifications/review-experiment-protocol.md" + - "docs/audits/rex-001/rex-04-receipt.md" + - "https://github.com/paiml/aprender/issues/4359" + +equations: + resolved: + formula: "∀ c ∈ CELLS: |rows(c)| = 1 ∧ state(c) ∈ {Admitted, Refused, NotRun}" + admitted: + formula: "Admitted(c) ⇒ cosine(c) ≥ threshold(c) ∧ receipt_sha256(c) is 64-hex ∧ threshold_basis(c) ≠ ∅" + s7: + formula: "S-7 ⇔ |{c : Admitted(c)}| = 0" + +falsification_tests: + - id: FALSIFY-RCA-001 + name: a_silent_cell_rejects_the_file + prediction: "dropping any one of the six rows (or duplicating one) makes the admission file inadmissible" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rca_001" + expected_output: "exit 0" + if_fails: "A cell could vanish from the report without a refusal or NotRun on record (0 silent cells, §7 REX-04)." + - id: FALSIFY-RCA-002 + name: admitted_needs_a_passing_parity_receipt + prediction: "Admitted below threshold, with a NaN cosine, or without a receipt, basis or oracle is rejected; Refused without removed_by is rejected" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rca_002" + expected_output: "exit 0" + if_fails: "A cell that diverges from the oracle would be admitted into the hardware ruling." + - id: FALSIFY-RCA-003 + name: no_admitted_cell_raises_s7 + prediction: "a file whose cells are all Refused or NotRun is admissible and raises S-7" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rca_003" + expected_output: "exit 0" + if_fails: "The experiment would proceed to REX-05 with no admissible cell for (A)." diff --git a/contracts/rex-prereg-v1.yaml b/contracts/rex-prereg-v1.yaml new file mode 100644 index 0000000000..830a420991 --- /dev/null +++ b/contracts/rex-prereg-v1.yaml @@ -0,0 +1,39 @@ +metadata: + version: 1.0.0 + kind: schema + description: | + REX-001 pre-registration lock (rule R-1). Spec §2–§5, the analysis code + (crates/aprender-review-experiment/src/stats.rs), the analysis plan and prompt v1 + are frozen by sha256 in docs/audits/rex-001/prereg.lock. The prereg sha is carried + by every review-experiment receipt; a tree that no longer matches the lock is a + new spec version and its data is exploratory. + references: + - "docs/specifications/review-experiment-protocol.md" + - "docs/audits/rex-001/analysis-plan.md" + - "https://github.com/paiml/aprender/issues/4355" + +equations: + prereg_sha: + formula: "sha256('rex-prereg-v1\\n' || 'spec_s2_s5 ' || H(spec[§2..§6)) || '\\nstats_rs ' || H(stats.rs) || '\\nanalysis_plan ' || H(plan) || '\\nprompt_v1 ' || H(prompt) || '\\n')" + locked: + formula: "prereg.lock[k] == H(component_k) for every k, and prereg.lock[prereg_sha] == prereg_sha" + +falsification_tests: + - id: FALSIFY-REX-PREREG-001 + name: lock_matches_tree + prediction: "the committed lock matches every frozen component" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rex_prereg_001" + expected_output: "exit 0" + if_fails: "A frozen component changed after REX-00. That is a new spec version (R-1): label prior data exploratory and cut v2; never edit the lock to match." + - id: FALSIFY-REX-PREREG-002 + name: planted_section3_edit_is_caught + prediction: "an edit to §3 (hypotheses) changes the prereg sha" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rex_prereg_002" + expected_output: "exit 0" + if_fails: "The §2–§5 span no longer covers §3; the lock is vacuous." + - id: FALSIFY-REX-PREREG-003 + name: out_of_scope_edit_is_not_a_version + prediction: "an edit to the status line (outside §2–§5) leaves the prereg sha unchanged" + test_harness: "cargo test -p aprender-review-experiment --lib falsify_rex_prereg_003" + expected_output: "exit 0" + if_fails: "The span leaks past §6; routine status edits would invalidate data." diff --git a/contracts/shapes.ttl b/contracts/shapes.ttl index 667b5d4245..4c873c0414 100644 --- a/contracts/shapes.ttl +++ b/contracts/shapes.ttl @@ -2,6 +2,164 @@ @prefix xsd: . @prefix ont: . + a sh:NodeShape ; + sh:targetClass ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:pattern "^[0-9a-f]{40}$" ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; +. + + a sh:NodeShape ; + sh:targetClass ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:datatype ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:in ( "OPEN" "CLOSED" ) ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:maxCount 1 ; + sh:class ; + sh:nodeKind sh:IRI ; + ] ; + sh:property [ + sh:path ; + sh:maxCount 0 ; + ] ; +. + + a sh:NodeShape ; + sh:targetClass ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:datatype ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:in ( "OPEN" "CLOSED" "MERGED" ) ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:class ; + sh:nodeKind sh:IRI ; + ] ; + sh:property [ + sh:path ; + sh:maxCount 0 ; + ] ; +. + + a sh:NodeShape ; + sh:targetClass ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:datatype ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:in ( "OPEN" "CLOSED" ) ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; +. + a sh:NodeShape ; sh:targetClass ; sh:property [ @@ -59,6 +217,47 @@ ] ; . + a sh:NodeShape ; + sh:targetClass ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:in ( "true"^^ "false"^^ ) ; + ] ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:in ( "true"^^ "false"^^ ) ; + ] ; +. + + a sh:NodeShape ; + sh:targetClass ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:in ( "true"^^ ) ; + ] ; +. + a sh:NodeShape ; sh:targetClass ; sh:property [ diff --git a/contracts/softmax-kernel-v1.yaml b/contracts/softmax-kernel-v1.yaml index bb775b0b79..3a27158186 100644 --- a/contracts/softmax-kernel-v1.yaml +++ b/contracts/softmax-kernel-v1.yaml @@ -6,6 +6,17 @@ metadata: references: - Bridle (1990) Training Stochastic Model Recognition Algorithms as Networks - Milakov & Gimelshein (2018) Online normalizer calculation for softmax +# ONT-4e (R-20): the kernel contract every softmax refinement must be Liskov against. +requires: +- id: PRE-1 + statement: every input score is finite and the vector is non-empty + formal: '∀i: ¬isNaN(x_i) ∧ ¬isInf(x_i) ∧ len(x) > 0' + formal_status: parsed +ensures: +- id: POST-1 + statement: the output is a probability vector of the input's length + formal: 'len(σ(x)) = len(x) ∧ ∀i: 0 < σ(x)_i < 1 ∧ |Σ σ(x)_i - 1| < ε' + formal_status: parsed equations: softmax: formula: σ(x)_i = exp(x_i - max(x)) / Σ_j exp(x_j - max(x)) diff --git a/contracts/tbox-report.json b/contracts/tbox-report.json new file mode 100644 index 0000000000..f47ddf58cc --- /dev/null +++ b/contracts/tbox-report.json @@ -0,0 +1,46 @@ +{ + "schema": "ont-tbox-report/v1", + "advisory": true, + "method": "told-closure", + "precondition": { + "holds": true, + "admitted_axiom_kinds": [ + "Declaration(Class)", + "Declaration(ObjectProperty)", + "SubClassOf(atomic, atomic)", + "ObjectPropertyDomain", + "ObjectPropertyRange", + "SymmetricObjectProperty" + ], + "refused": [] + }, + "consistent": true, + "classes": 17, + "entailed_subsumptions": [ + [ + "Issue", + "Json" + ], + [ + "Kernel", + "Contract" + ], + [ + "Milestone", + "Json" + ], + [ + "PullRequest", + "Json" + ], + [ + "Repo", + "Json" + ], + [ + "Symbol", + "Code" + ] + ], + "unintended_subsumptions": [] +} diff --git a/contracts/tui-rendering-ux-v1.yaml b/contracts/tui-rendering-ux-v1.yaml index c7e20153d9..51851fd4f9 100644 --- a/contracts/tui-rendering-ux-v1.yaml +++ b/contracts/tui-rendering-ux-v1.yaml @@ -282,7 +282,7 @@ proof_obligations: property: "60 FPS frame budget with smart diffing" verification_summary: - total_obligations: 8 + total_obligations: 6 proven: 0 tested: 0 status: pending diff --git a/contracts/unified-specs-v1.yaml b/contracts/unified-specs-v1.yaml index 628ab11130..cb1396e81c 100644 --- a/contracts/unified-specs-v1.yaml +++ b/contracts/unified-specs-v1.yaml @@ -80,7 +80,7 @@ kani_harnesses: bound: 4 verification_summary: - total_obligations: 3 + total_obligations: 1 proven: 0 tested: 3 status: tested diff --git a/contracts/witness/39ba3915ef50c630ace73f9938a02484b2f8b8176f33a04f53f364887ea0364c.json b/contracts/witness/39ba3915ef50c630ace73f9938a02484b2f8b8176f33a04f53f364887ea0364c.json new file mode 100644 index 0000000000..1e4abc12d3 --- /dev/null +++ b/contracts/witness/39ba3915ef50c630ace73f9938a02484b2f8b8176f33a04f53f364887ea0364c.json @@ -0,0 +1,14 @@ +{ + "census_id_set_sha256": "c44190d6e7f659398f0fa8e4317bb1611145426db8952dc2168e94bc493d2147", + "relations_sha256": "39ba3915ef50c630ace73f9938a02484b2f8b8176f33a04f53f364887ea0364c", + "reasoner_git_sha": "7afa087e77c304e2b9cd091e2c25f2ac966fed70", + "checkable_n": 35, + "result": { + "kind": "model", + "payload": { + "false": [] + } + }, + "pc_reasoner": "fired", + "cpu_ms": 2 +} diff --git a/contracts/witness/liskov/469bcc39934277e680a45936484c28f6d5f9735bfc3cc1ffd213e97050678d70.json b/contracts/witness/liskov/469bcc39934277e680a45936484c28f6d5f9735bfc3cc1ffd213e97050678d70.json new file mode 100644 index 0000000000..fb08ff3eff --- /dev/null +++ b/contracts/witness/liskov/469bcc39934277e680a45936484c28f6d5f9735bfc3cc1ffd213e97050678d70.json @@ -0,0 +1,36 @@ +{ + "liskov_sha256": "469bcc39934277e680a45936484c28f6d5f9735bfc3cc1ffd213e97050678d70", + "pairs_checked": 1, + "reasoner_git_sha": "c0435711a3bad24afd55b108ee71253e22d079d0", + "pc_reasoner": "fired", + "pairs": [ + { + "a": "online-softmax-v1", + "b": "softmax-kernel-v1", + "obligations": [ + { + "kind": "pre", + "chain": [ + { + "clause": "PRE-1", + "from": "PRE-1" + } + ] + }, + { + "kind": "post", + "chain": [ + { + "clause": "POST-1", + "from": "POST-1" + } + ] + }, + { + "kind": "inv", + "chain": [] + } + ] + } + ] +} diff --git a/contracts/work/PMAT-4080.yaml b/contracts/work/PMAT-4080.yaml new file mode 100644 index 0000000000..1476bb3820 --- /dev/null +++ b/contracts/work/PMAT-4080.yaml @@ -0,0 +1,105 @@ +contract: pvl-2-ghost-binding-reject +metadata: + kind: pattern + version: "1.0.0" + description: > + PVL-2 (PMAT-4080; paiml/infra docs/specifications/PVL-001-pv-lean-gate.md + row EV-2): `pv proof-status --binding` RESOLVES every binding, and a ghost + binding (claimed `implemented`, function absent from source) is a REJECT. + + MEASURED before this contract (origin/main 49fe19c28, 2026-09-23): given + tests/fixtures/pvl/ghost-binding.yaml (status implemented, + aprender::nonexistent::function_that_does_not_exist_xyz), `pv proof-status + --binding` printed its report and exited 0. It counted binding entries and + never resolved them (scripts/dogfood.sh:796 records the same), and + `--verify-bindings` downgraded ghosts silently, still exiting 0. + + Now every `implemented` binding is looked up with the `pv verify-bindings` + resolver (scan_all_sources / derive_src_root, one shared short_name + normalization). A ghost is downgraded, listed under `GHOST BINDINGS (n)` + with its contract, equation and function, and the command exits 1. + `--verify-bindings` is a no-op alias. The resolver now sees every `fn` item + whatever its visibility or qualifiers: the old four-prefix scanner missed + `pub(super) fn compute_mse` (crates/aprender-core/src/tree/regression_helpers.rs:27) + and reported it a ghost, which under a reject gate is a false reject. It also + sees type items (struct/enum/type/trait): setfit-apr-v1 binds two real + `pub struct`s. And the source root no longer assumes the pre-monorepo layout + (`contracts//binding.yaml` -> `../..//`, absent here): it falls back + to the binding's nearest ancestor holding `crates/` or `src/`, so the verdict + no longer depends on the caller's cwd. Measured with the final resolver: + contracts/binding.yaml 1 genuine ghost, contracts/aprender/binding.yaml 5 + genuine ghosts (all filed on #4094). + references: + - "docs/specifications/PVL-001-pv-lean-gate.md (paiml/infra), row EV-2" + - "crates/aprender-contracts-cli/src/commands/proof_status.rs" + - "crates/aprender-contracts-cli/src/commands/verify_bindings.rs" + - "crates/aprender-contracts-cli/tests/pvl_ghost_binding.rs" + - "tests/fixtures/pvl/ghost-binding.yaml" + +equations: + ghost_is_rejected: + formula: > + forall b in bindings(B) . status(b) = implemented and short(function(b)) not in fns(src(B)) + => exit_code(pv proof-status --binding B) = 1 + and stdout contains "GHOST BINDINGS (" ++ |ghosts(B)| ++ ")" + and stdout names function(b) + domain: "B a binding registry file; src(B) the verify-bindings resolver's scan set" + codomain: "{1}" + invariants: + - "a ghost is downgraded before levels are computed, so no L5 is credited on a ghost" + resolved_is_accepted: + formula: > + forall b in bindings(B) . status(b) = implemented => short(function(b)) in fns(src(B)) + => no GHOST BINDINGS line + domain: "B whose every implemented binding resolves" + codomain: "{0}" + invariants: + - "a read error of B is not the ghost verdict: it prints no GHOST BINDINGS line" + +proof_obligations: + - id: P2-INV-001 + type: invariant + property: "An implemented binding whose function the resolver cannot find is rejected, counted and named" + formal: '∀ b ∈ bindings: implemented(b) ∧ short(b) ∉ fns(src) ⟹ exitCode ≡ 1 ∧ b ∈ ghostBlock' + applies_to: all + - id: P2-INV-002 + type: invariant + property: "Every fn item declaration form is visible to the resolver" + formal: '∀ v ∈ visibilities: ∀ q ∈ qualifiers: fnItemName(v · q · fnDecl(n)) ≡ n' + applies_to: all + +falsification_tests: + - id: FALSIFY-PVL-2-001 + rule: "PVL-2-GHOST-REJECTED" + prediction: > + Bypassing the resolver in proof_status::resolve_bindings (`unresolved = + false && …`) turns a_ghost_binding_is_a_reject and + verify_bindings_is_a_noop_alias RED, while the control and the + missing-file case stay GREEN. + test_harness: "cargo test -p aprender-contracts-cli --test pvl_ghost_binding" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23: mutation engaged -> FAILED. 2 passed; 2 failed; RED: + verify_bindings_is_a_noop_alias a_ghost_binding_is_a_reject; restored -> ok. + 4 passed; 0 failed. + - id: FALSIFY-PVL-2-002 + rule: "PVL-2-EVERY-FN-FORM" + prediction: > + Dropping the `pub(...)` branch of fn_item_name turns the resolver case table + RED (pub(super) fn compute_mse reads as not-a-declaration). + test_harness: "cargo test -p aprender-contracts-cli --lib -- verify_bindings" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23: mutation engaged -> FAILED. 0 passed; 1 failed (left: None); + restored -> ok. 1 passed; 0 failed. + - id: FALSIFY-PVL-2-003 + rule: "PVL-2-GHOST-REJECTED" + prediction: > + The shipped proof-status (origin/main) accepts the ghost fixture: exit 0, no + GHOST BINDINGS line, so the accept test is RED there. + test_harness: "cargo test -p aprender-contracts-cli --test pvl_ghost_binding" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23 on origin/main 49fe19c28 + the test: FAILED. 2 passed; 2 + failed; RED: a_ghost_binding_is_a_reject verify_bindings_is_a_noop_alias (both + exit 0 with the report printed). diff --git a/contracts/work/PMAT-4081.yaml b/contracts/work/PMAT-4081.yaml new file mode 100644 index 0000000000..e69e2dcf7c --- /dev/null +++ b/contracts/work/PMAT-4081.yaml @@ -0,0 +1,122 @@ +contract: pvl-3-one-ladder +metadata: + kind: pattern + version: "1.0.0" + description: > + PVL-3 (PMAT-4081; paiml/infra docs/specifications/PVL-001-pv-lean-gate.md + row EV-3): ONE definition of the proof levels. + + MEASURED before this contract (origin/main 49fe19c28, 2026-09-23): the + `ProofLevel` enum (crates/aprender-contracts/src/proof_status.rs) defines + L3 = Kani bounded model check, L4 = Lean 4 theorem proved, L5 = L4 + every + binding verified. `readme_gen`'s Verification Ladder table printed its OWN + strings one level off (the Kani row one level too high, L5 "Lean 4 theorem", + L1 "Type system"), and all three copies of the ladder doc taught the same + off-by-one ladder plus an L0 the enum does not have. A README reader was told + an L4 contract was Kani-checked when pv had computed it was Lean-proved. + + Now `ProofLevel::method` is the one definition. `readme_gen` prints it, and + each ladder copy carries `levels::ladder_block()` verbatim between + `` and its end marker, with the + statement that L4/L5 are grounded only textually until PVL-001 EV-8b lands + (a claim with no sorry-free in-tree Lean theorem is self-declared and excluded). + references: + - "docs/specifications/PVL-001-pv-lean-gate.md (paiml/infra), row EV-3" + - "crates/aprender-contracts/src/levels.rs" + - "crates/aprender-contracts/src/readme_gen.rs" + - "crates/aprender-contracts/src/proof_status.rs" + +equations: + readme_prints_the_enum: + formula: > + forall l in ProofLevel . row(verification_ladder_table(c), l) + = "| " ++ l ++ " | " ++ c(l) ++ " | " ++ method(l) ++ " |" + domain: "l in {L1..L5}; c the per-level contract counts" + codomain: "the README Verification Ladder table" + invariants: + - "readme_gen holds no level-description string of its own" + every_ladder_copy_carries_the_block: + formula: > + forall d in {the three ladder doc copies PVL-001 EV-3 names} . + ladder_block() is a substring of d + domain: "d a tracked markdown file" + codomain: "{true}" + invariants: + - "the block starts with the marker line and ends with the end marker" + - "the block names each of L5..L1 exactly once, highest first" + +proof_obligations: + - id: P3-INV-001 + type: invariant + property: "The README ladder table's method for every level is ProofLevel::method" + formal: '∀ l ∈ ProofLevel: methodColumn(readmeLadder, l) ≡ method(l)' + applies_to: all + - id: P3-INV-002 + type: invariant + property: "Every ladder doc copy carries the block generated from ProofLevel, byte for byte" + formal: '∀ d ∈ ladderCopies: ladderBlock ⊆ d' + applies_to: all + +falsification_tests: + - id: FALSIFY-PVL-3-001 + rule: "PVL-3-README-IS-ENUM" + prediction: > + Hard-coding one readme_gen string again (L4 -> "Kani BMC" in + verification_ladder_table) turns levels::readme_and_ladder_docs_match_enum RED. + test_harness: "cargo test -p aprender-contracts --lib -- levels::readme_and_ladder_docs_match_enum" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23: mutation engaged -> FAILED. 0 passed; 1 failed; 1707 filtered + out ("README ladder row for L4 is not the enum's definition"); restored -> ok. + 1 passed; 0 failed. + - id: FALSIFY-PVL-3-002 + rule: "PVL-3-DOCS-ARE-ENUM" + prediction: > + Editing one row of one ladder copy's generated block (the book copy's L3 + row) turns levels::readme_and_ladder_docs_match_enum RED, naming that file. + test_harness: "cargo test -p aprender-contracts --lib -- levels::readme_and_ladder_docs_match_enum" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23: mutation engaged -> FAILED. 0 passed; 1 failed ("…book/src/ + verification-ladder.md does not carry the block generated from ProofLevel"); + restored -> ok. 1 passed; 0 failed. + - id: FALSIFY-PVL-3-003 + rule: "PVL-3-README-IS-ENUM" + prediction: > + The shipped state (readme_gen's own strings, the three docs at origin/main) + fails the accept test. The accept path names the test directly: under + levels::tests:: the spec's accept command ran ZERO tests and passed. + test_harness: "cargo test -p aprender-contracts --lib -- levels::readme_and_ladder_docs_match_enum" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23: shipped strings + origin/main docs -> FAILED. 0 passed; 1 + failed ("README ladder row for L5 is not the enum's definition"); this tree -> + ok. 1 passed; 0 failed; 1707 filtered out. + - id: FALSIFY-PVL-3-004 + rule: "PVL-3-DOCS-ARE-ENUM" + prediction: > + Restoring the old "Where Each Tool Lives" header (`Level 4 (Kani) | Level 5 + (Lean)`) in the book copy turns levels::readme_and_ladder_docs_match_enum RED: + the generated block is not enough, prose OUTSIDE it must not pair L4/L5 with + Kani or L5 with Lean alone. Found by quorum lane 2 on PR #4092 round 1; the + enforcement-layer table in copies 1/3 is relabelled E0-E5 for the same reason. + test_harness: "cargo test -p aprender-contracts --lib -- levels::readme_and_ladder_docs_match_enum" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23: mutation engaged -> FAILED. 0 passed; 1 failed ("…book/src/ + verification-ladder.md still pairs a level with the wrong tool outside the generated + block"); restored -> ok. 1 passed; 0 failed. + - id: FALSIFY-PVL-3-005 + rule: "PVL-3-DOCS-ARE-ENUM" + prediction: > + The stale-pairing detector pairs each tool with its NEAREST level token in either + direction. Disabling the level-after-tool arm leaves "Falsification is L2 then + Kani at L4." unflagged; disabling the level-before-tool arm leaves "Level 4 (Kani)" + unflagged. Either turns levels::stale_level_pairings_case_table RED. (Quorum + lane 1, PR #4092 round 3, found the forward-only detector blind to reversed order.) + test_harness: "cargo test -p aprender-contracts --lib -- levels::" + expected_output: "exit 0 on an unmutated tree" + if_fails: > + MEASURED 2026-09-23: after-arm disabled -> FAILED 2 passed; 1 failed ("must flag: + Falsification is L2 then Kani at L4."); before-arm disabled -> FAILED 1 passed; 2 + failed; unmutated -> ok 3 passed. diff --git a/crates/aprender-contracts-cli/Cargo.toml b/crates/aprender-contracts-cli/Cargo.toml index 461b21ce0a..426f40729b 100644 --- a/crates/aprender-contracts-cli/Cargo.toml +++ b/crates/aprender-contracts-cli/Cargo.toml @@ -12,6 +12,11 @@ repository.workspace = true name = "pv" path = "src/main.rs" +# ONT-5: the reasoner. Private to this target (F-7); the library holds only the graph and the checker. +[[bin]] +name = "pv-sat" +path = "src/bin/pv-sat/main.rs" + [dependencies] aprender-contracts = { path = "../aprender-contracts", version = "0.69.3" } clap = { workspace = true } diff --git a/crates/aprender-contracts-cli/src/bin/pv-sat/liskov.rs b/crates/aprender-contracts-cli/src/bin/pv-sat/liskov.rs new file mode 100644 index 0000000000..bd4b222e94 --- /dev/null +++ b/crates/aprender-contracts-cli/src/bin/pv-sat/liskov.rs @@ -0,0 +1,200 @@ +//! ONT-4e (R-20): the Liskov half of pv-sat. For every checkable `A refines B` it decides the three obligations and +//! writes the certificate the `refines` gate re-checks: `contracts/witness/liskov/.json`. +//! +//! | obligation | premise | conclusion | broken when | +//! |------------|----------------|----------------|--------------------------------------------| +//! | pre | B.requires | A.requires | A demands something B did not (strengthened) | +//! | post | A.ensures | B.ensures | A drops a promise B made (weakened) | +//! | inv | A.invariants | B.invariants | A drops an invariant B kept (dropped) | +//! +//! Atoms are opaque: a conclusion clause is implied only by a premise clause with the same `formal` (whitespace +//! collapsed). The direction is spelled out HERE, not borrowed from the library's `Kind::sides`, so a reasoner and a +//! checker that disagree about which way refinement runs disagree on every pair, and the checker refuses the +//! witness. Before it writes, pv-sat runs `pc_liskov_reasoner`: a planted strengthened precondition must come back as +//! a counter-model naming it, and pass the library checker. + +use std::path::Path; + +use provable_contracts::ontology::liskov::{ + check, liskov_corpus, liskov_sha256, liskov_witness_path, Clauses, CounterModel, Kind, + LiskovWitness, Obligation, Pair, PairClass, PairWitness, Step, +}; +use provable_contracts::ontology::witness::FIRED; +use provable_contracts::schema::{Clause, FormalStatus}; + +/// Decide one obligation: a chain when every conclusion clause has a same-atom premise, else the counter-model +/// making exactly the premises true. +fn decide(kind: Kind, premise: &[Clause], conclusion: &[Clause]) -> Obligation { + let mut chain = Vec::new(); + let mut violated = Vec::new(); + for c in conclusion { + match premise + .iter() + .find(|p| p.atom().is_some() && p.atom() == c.atom()) + { + Some(p) => chain.push(Step { + clause: c.id.clone(), + from: p.id.clone(), + }), + None => violated.push(c.id.clone()), + } + } + if violated.is_empty() { + Obligation { + kind, + chain: Some(chain), + counter_model: None, + } + } else { + Obligation { + kind, + chain: None, + counter_model: Some(CounterModel { + violated, + holds: premise.iter().map(|p| p.id.clone()).collect(), + }), + } + } +} + +/// The three obligations of `a refines b`, each in its own direction. +pub fn reason(p: &Pair) -> PairWitness { + let (a, b) = (&p.a_clauses, &p.b_clauses); + PairWitness { + a: p.a.clone(), + b: p.b.clone(), + obligations: vec![ + decide(Kind::Pre, &b.requires, &a.requires), + decide(Kind::Post, &a.ensures, &b.ensures), + decide(Kind::Inv, &a.invariants, &b.invariants), + ], + } +} + +/// The witness for `pairs`, before `pc_reasoner` and the git sha are stamped on it. +pub fn witness( + pairs: &[Pair], + pc_reasoner: &str, + reasoner_git_sha: Option, +) -> LiskovWitness { + LiskovWitness { + liskov_sha256: liskov_sha256(pairs), + pairs_checked: pairs.len(), + reasoner_git_sha, + pc_reasoner: pc_reasoner.into(), + pairs: pairs.iter().map(reason).collect(), + } +} + +fn parsed(id: &str, formal: &str) -> Clause { + Clause { + id: id.into(), + statement: id.into(), + formal: Some(formal.into()), + formal_status: FormalStatus::Parsed, + } +} + +/// The plant: `a` keeps `b`'s precondition and adds `PRE-2`; its postcondition and invariant are `b`'s. +pub fn plant() -> Pair { + Pair { + a: "a".into(), + b: "b".into(), + a_clauses: Clauses { + requires: vec![parsed("PRE-1", "len(x) > 0"), parsed("PRE-2", "len(x) > 1")], + ensures: vec![parsed("POST-1", "len(y) = len(x)")], + invariants: vec![parsed("INV-1", "y ≥ 0")], + }, + b_clauses: Clauses { + requires: vec![parsed("PRE-1", "len(x) > 0")], + ensures: vec![parsed("POST-1", "len(y) = len(x)")], + invariants: vec![parsed("INV-1", "y ≥ 0")], + }, + } +} + +/// `pc_liskov_reasoner`: `Ok(FIRED)` when the plant's ONLY violation is `a refines b: precondition strengthened +/// (PRE-2)`, and the library checker proves exactly that from the reasoner's witness. +pub fn pc_reasoner() -> Result<&'static str, String> { + let pairs = [plant()]; + let w = witness(&pairs, FIRED, None); + let found = check(&pairs, &w) + .map_err(|e| format!("the checker refused the reasoner's witness for the plant: {e}"))?; + let found: Vec = found.iter().map(ToString::to_string).collect(); + if found == ["a refines b: precondition strengthened (PRE-2)"] { + Ok(FIRED) + } else { + Err(format!( + "the plant strengthens exactly PRE-2, and the reasoner proved {found:?}" + )) + } +} + +/// The checkable pairs of `dir`, read the way the gate reads them. +pub fn checkable_pairs( + dir: &Path, + edges: &std::collections::BTreeSet, +) -> Vec { + let docs = provable_contracts::lint::relations_gate::corpus_documents(dir); + liskov_corpus(&docs, edges) + .pairs + .into_iter() + .filter(|p| p.class() == PairClass::Checkable) + .collect() +} + +/// Write (or confirm) the Liskov witness for `pairs` under `dir`. Nothing is written when there is no checkable +/// pair. Exit-code semantics are the caller's: `Err(1)` a control or the self-check failed, `Err(3)` an I/O error. +pub fn write(dir: &Path, pairs: &[Pair], git_sha: Option) -> Result<(), u8> { + let pc = pc_reasoner().map_err(|e| { + eprintln!("pv-sat: pc_liskov_reasoner did not fire, no Liskov witness written: {e}"); + 1 + })?; + let sha = liskov_sha256(pairs); + let path = liskov_witness_path(dir, &sha); + if pairs.is_empty() { + // Nothing to certify: a witness left from a corpus that had pairs names nothing, so it goes. + let dir_exists = path.parent().is_some_and(Path::is_dir); + return if dir_exists { + super::prune(&path).map_err(|()| 3) + } else { + Ok(()) + }; + } + let fresh = std::fs::read_to_string(&path) + .ok() + .and_then(|t| serde_json::from_str::(&t).ok()) + .filter(|w| w.liskov_sha256 == sha && w.pc_reasoner == FIRED && check(pairs, w).is_ok()); + if fresh.is_some() { + println!( + "pv-sat: {} is fresh ({} pair(s))", + path.display(), + pairs.len() + ); + return super::prune(&path).map_err(|()| 3); + } + let w = witness(pairs, pc, git_sha); + let violations = check(pairs, &w).map_err(|e| { + eprintln!("pv-sat: the reasoner's own Liskov witness does not check, none written: {e}"); + 1 + })?; + let written = path + .parent() + .map_or(Ok(()), std::fs::create_dir_all) + .and_then(|()| { + let mut text = serde_json::to_string_pretty(&w).map_err(std::io::Error::other)?; + text.push('\n'); + std::fs::write(&path, text) + }); + if let Err(e) = written { + eprintln!("pv-sat: cannot write {}: {e}", path.display()); + return Err(3); + } + println!( + "pv-sat: wrote {} ({} pair(s); {} Liskov violation(s))", + path.display(), + pairs.len(), + violations.len() + ); + super::prune(&path).map_err(|()| 3) +} diff --git a/crates/aprender-contracts-cli/src/bin/pv-sat/main.rs b/crates/aprender-contracts-cli/src/bin/pv-sat/main.rs new file mode 100644 index 0000000000..97d3c84545 --- /dev/null +++ b/crates/aprender-contracts-cli/src/bin/pv-sat/main.rs @@ -0,0 +1,230 @@ +//! `pv-sat` — ONT-5's reasoner. Decides whether a corpus's typed relations are jointly satisfiable and writes the +//! certificate the `ont-consistency` gate re-checks: `contracts/witness/.json`. +//! +//! ```text +//! pv-sat [CONTRACT_DIR] write (or confirm) the witness; default `contracts` +//! pv-sat --self-test the reasoner's plant, its satisfiable twin, and the checker's corrupt core +//! (and ONT-4e's: `pc_liskov_reasoner`, `pc_liskov_checker`) +//! ``` +//! +//! Private to this bin target (F-7): the library exports the graph and the checker, and nothing in it can reach +//! the reasoner. Before it writes, pv-sat runs its plant (`pc_reasoner`); a reasoner that cannot find the planted +//! core never writes a witness. A witness already on disk that names the same graph and still checks is left +//! untouched, so `make contracts` leaves a clean tree clean. Other `.json` files in `witness/` are pruned — +//! one graph, one witness. +//! +//! ONT-4e: once the consistency witness stands, pv-sat also certifies every checkable `A refines B` (R-20) in +//! `contracts/witness/liskov/.json` — see `liskov.rs`. +//! +//! Exit: 0 written or confirmed · 1 a control failed · 2 nothing to reason over (no Σ, no typed relation) · 3 Σ +//! malformed or the witness could not be written. + +mod liskov; +mod plant; +mod sat; + +use std::path::{Path, PathBuf}; +use std::process::ExitCode; +use std::time::Instant; + +use provable_contracts::lint::relations_gate::{typed_graph, TypedGraph}; +use provable_contracts::ontology::witness::{ + census_id_set_sha256, check, pc_checker, relations_sha256, witness_path, ClauseSet, Witness, + FIRED, +}; + +fn main() -> ExitCode { + let args: Vec = std::env::args().skip(1).collect(); + match args.as_slice() { + [flag] if flag == "--self-test" => self_test(), + [flag] if flag == "-h" || flag == "--help" => { + println!("usage: pv-sat [CONTRACT_DIR] | pv-sat --self-test"); + ExitCode::SUCCESS + } + [] => write_witness(Path::new("contracts")), + [dir] => write_witness(Path::new(dir)), + _ => { + eprintln!("usage: pv-sat [CONTRACT_DIR] | pv-sat --self-test"); + ExitCode::from(3) + } + } +} + +fn self_test() -> ExitCode { + let checks: [(&str, Result<(), String>); 5] = [ + ("pc_reasoner", plant::pc_reasoner().map(|_| ())), + ("pc_model", plant::pc_model()), + ("pc_checker", pc_checker().map(|_| ())), + ("pc_liskov_reasoner", liskov::pc_reasoner().map(|_| ())), + ( + "pc_liskov_checker", + provable_contracts::ontology::liskov::pc_checker().map(|_| ()), + ), + ]; + let mut ok = true; + for (name, r) in &checks { + match r { + Ok(()) => println!("pv-sat self-test: {name} {FIRED}"), + Err(e) => { + ok = false; + eprintln!("pv-sat self-test: {name} FAILED: {e}"); + } + } + } + if ok { + ExitCode::SUCCESS + } else { + ExitCode::from(1) + } +} + +/// The consistency witness, then (ONT-4e) the Liskov witness for every checkable `refines` pair. +fn write_witness(dir: &Path) -> ExitCode { + let code = write_consistency(dir); + if code != ExitCode::SUCCESS { + return code; + } + let TypedGraph::Read { edges, .. } = typed_graph(dir) else { + return code; + }; + let pairs = liskov::checkable_pairs(dir, &edges); + liskov::write(dir, &pairs, git_head(dir)).map_or_else(ExitCode::from, |()| ExitCode::SUCCESS) +} + +fn write_consistency(dir: &Path) -> ExitCode { + let pc = match plant::pc_reasoner() { + Ok(fired) => fired, + Err(e) => { + eprintln!("pv-sat: pc_reasoner did not fire, no witness written: {e}"); + return ExitCode::from(1); + } + }; + let (ids, edges) = match typed_graph(dir) { + TypedGraph::NoSigma => { + eprintln!( + "pv-sat: no {}/ontology.yaml — nothing to reason over", + dir.display() + ); + return ExitCode::from(2); + } + TypedGraph::Malformed(e) => { + eprintln!("pv-sat: Σ is malformed: {e}"); + return ExitCode::from(3); + } + TypedGraph::Read { ids, edges } => (ids, edges), + }; + let cs = ClauseSet::from_graph(&ids, &edges); + if cs.checkable_n() == 0 { + eprintln!( + "pv-sat: no typed relation clause in {} contracts — nothing to reason over", + ids.len() + ); + return ExitCode::from(2); + } + let census_sha = census_id_set_sha256(&ids); + let relations_sha = relations_sha256(&edges); + let path = witness_path(dir, &relations_sha); + + if let Some(w) = fresh_witness(&path, &cs, &census_sha, &relations_sha) { + println!( + "pv-sat: {} is fresh ({}; checkable_n {})", + path.display(), + kind(&w), + w.checkable_n + ); + return prune(&path).map_or(ExitCode::from(3), |()| ExitCode::SUCCESS); + } + + let start = Instant::now(); + let result = sat::solve(&cs); + let cpu_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); + if let Err(e) = check(&cs, &result) { + eprintln!("pv-sat: the reasoner's own answer does not check, no witness written: {e}"); + return ExitCode::from(1); + } + let witness = Witness { + census_id_set_sha256: census_sha, + relations_sha256: relations_sha, + reasoner_git_sha: git_head(dir), + checkable_n: cs.checkable_n(), + result, + pc_reasoner: pc.to_string(), + cpu_ms, + }; + let written = path + .parent() + .map_or(Ok(()), std::fs::create_dir_all) + .and_then(|()| { + let mut text = serde_json::to_string_pretty(&witness).map_err(std::io::Error::other)?; + text.push('\n'); + std::fs::write(&path, text) + }); + if let Err(e) = written { + eprintln!("pv-sat: cannot write {}: {e}", path.display()); + return ExitCode::from(3); + } + println!( + "pv-sat: wrote {} ({}; checkable_n {}; {} ms)", + path.display(), + kind(&witness), + witness.checkable_n, + cpu_ms + ); + prune(&path).map_or(ExitCode::from(3), |()| ExitCode::SUCCESS) +} + +fn kind(w: &Witness) -> &'static str { + match w.result { + provable_contracts::ontology::witness::WitnessResult::UnsatCore(_) => "unsat_core", + provable_contracts::ontology::witness::WitnessResult::Model(_) => "model", + } +} + +/// The witness on disk, when it names this graph, its reasoner's plant fired, and it still checks. +fn fresh_witness(path: &Path, cs: &ClauseSet, census: &str, relations: &str) -> Option { + let w: Witness = serde_json::from_str(&std::fs::read_to_string(path).ok()?).ok()?; + let fresh = w.census_id_set_sha256 == census + && w.relations_sha256 == relations + && w.pc_reasoner == FIRED + && w.checkable_n == cs.checkable_n() + && check(cs, &w.result).is_ok(); + fresh.then_some(w) +} + +/// Remove every other `<64 hex>.json` beside `keep`. +fn prune(keep: &Path) -> Result<(), ()> { + let Some(dir) = keep.parent() else { + return Ok(()); + }; + let entries = std::fs::read_dir(dir) + .map_err(|e| eprintln!("pv-sat: cannot list {}: {e}", dir.display()))?; + for entry in entries.flatten() { + let p: PathBuf = entry.path(); + let is_witness = p.extension().is_some_and(|e| e == "json") + && p.file_stem().and_then(|s| s.to_str()).is_some_and(|s| { + s.len() == 64 + && s.bytes() + .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase()) + }); + if is_witness && p != keep { + std::fs::remove_file(&p) + .map_err(|e| eprintln!("pv-sat: cannot prune {}: {e}", p.display()))?; + println!("pv-sat: pruned {}", p.display()); + } + } + Ok(()) +} + +fn git_head(dir: &Path) -> Option { + let out = std::process::Command::new("git") + .arg("-C") + .arg(dir) + .args(["rev-parse", "HEAD"]) + .output() + .ok()?; + let sha = String::from_utf8(out.stdout).ok()?.trim().to_string(); + (out.status.success() && sha.len() == 40).then_some(sha) +} + +#[cfg(test)] +mod tests; diff --git a/crates/aprender-contracts-cli/src/bin/pv-sat/plant.rs b/crates/aprender-contracts-cli/src/bin/pv-sat/plant.rs new file mode 100644 index 0000000000..7928d14625 --- /dev/null +++ b/crates/aprender-contracts-cli/src/bin/pv-sat/plant.rs @@ -0,0 +1,63 @@ +//! `pc_reasoner`: the plant pv-sat must see through before it may write a witness. +//! +//! `A⇒B, B⇒C, contradicts(A,C)` with only `A` asserted is unsatisfiable, and its only core is `[A, B, C]` — +//! a reasoner that stops one implication early, or that "finds" the conflict without deriving `C`, draws +//! something else. The core must also pass the library's checker: a reasoner and a checker that disagree about +//! the plant cannot both be trusted with the corpus. + +use std::collections::BTreeSet; + +use provable_contracts::ontology::witness::{check, Checked, ClauseSet, WitnessResult, FIRED}; + +use crate::sat::solve; + +/// The planted clause set. +pub fn plant() -> ClauseSet { + let s = |x: &str| x.to_string(); + ClauseSet { + units: [s("A")].into(), + implies: [(s("A"), s("B")), (s("B"), s("C"))].into(), + conflicts: [(s("A"), s("C"))].into(), + } +} + +/// `Ok(FIRED)` when the reasoner draws the core `[A, B, C]` and the checker accepts it. +pub fn pc_reasoner() -> Result<&'static str, String> { + let cs = plant(); + let result = solve(&cs); + let WitnessResult::UnsatCore(_) = &result else { + return Err(format!( + "the plant is unsatisfiable and the reasoner answered {result:?}" + )); + }; + match check(&cs, &result) { + Ok(Checked::Unsat { core }) => { + let want: BTreeSet = ["A", "B", "C"].map(String::from).into(); + if core == want { + Ok(FIRED) + } else { + Err(format!( + "the plant's core is [A, B, C] and the reasoner drew {core:?}" + )) + } + } + Ok(Checked::Sat) => Err("the checker read the plant's core as a model".into()), + Err(e) => Err(format!( + "the checker refused the reasoner's core for the plant: {e}" + )), + } +} + +/// The satisfiable twin of the plant — the conflict removed — must yield a model the checker accepts, so a +/// reasoner that answers "unsat" to everything cannot pass `--self-test`. +pub fn pc_model() -> Result<(), String> { + let mut cs = plant(); + cs.conflicts.clear(); + let result = solve(&cs); + match check(&cs, &result) { + Ok(Checked::Sat) => Ok(()), + other => Err(format!( + "the plant without its conflict is satisfiable, and got {other:?}" + )), + } +} diff --git a/crates/aprender-contracts-cli/src/bin/pv-sat/sat.rs b/crates/aprender-contracts-cli/src/bin/pv-sat/sat.rs new file mode 100644 index 0000000000..279890be4c --- /dev/null +++ b/crates/aprender-contracts-cli/src/bin/pv-sat/sat.rs @@ -0,0 +1,75 @@ +//! The reasoner: Horn-SAT by unit propagation, with a certificate for either answer. +//! +//! Propagation is breadth-first from the units in sorted order, so the derivation — and the witness — is the same +//! bytes on every run for the same clause set. On a conflict the core is the derivation of its two sides and +//! nothing else, in the order propagation reached each variable, which is an order the checker accepts: every +//! premise is derived before the implication that uses it. + +use std::collections::{BTreeMap, VecDeque}; + +use provable_contracts::ontology::witness::{ + implication_adjacency, variables, ClauseSet, Core, Model, Step, WitnessResult, +}; + +/// Each derived variable: (derivation index, the premise that derived it; `None` for a unit). +type Derived<'a> = BTreeMap<&'a str, (usize, Option<&'a str>)>; + +/// Decide `cs`: an `unsat_core` for the first violated conflict (in sorted order), else the least model. +pub fn solve(cs: &ClauseSet) -> WitnessResult { + let derived = propagate(cs); + let violated = cs + .conflicts + .iter() + .find(|(a, b)| derived.contains_key(a.as_str()) && derived.contains_key(b.as_str())); + if let Some((a, b)) = violated { + let mut needed: BTreeMap = BTreeMap::new(); + trace(&derived, a, &mut needed); + trace(&derived, b, &mut needed); + return WitnessResult::UnsatCore(Core { + steps: needed.into_values().collect(), + conflict: (a.clone(), b.clone()), + }); + } + WitnessResult::Model(Model { + false_vars: variables(cs) + .into_iter() + .filter(|v| !derived.contains_key(v.as_str())) + .collect(), + }) +} + +/// Breadth-first unit propagation from the sorted units. +fn propagate(cs: &ClauseSet) -> Derived<'_> { + let adj = implication_adjacency(cs); + let mut derived: Derived<'_> = BTreeMap::new(); + let mut queue: VecDeque<&str> = VecDeque::new(); + for u in &cs.units { + derived.insert(u.as_str(), (derived.len(), None)); + queue.push_back(u.as_str()); + } + while let Some(v) = queue.pop_front() { + for &w in adj.get(v).map_or(&[][..], Vec::as_slice) { + if !derived.contains_key(w) { + derived.insert(w, (derived.len(), Some(v))); + queue.push_back(w); + } + } + } + derived +} + +/// Add the derivation of `side` to `needed`, back to its unit or to a step already there. +fn trace(derived: &Derived<'_>, side: &str, needed: &mut BTreeMap) { + let mut v = side; + while let Some(&(idx, premise)) = derived.get(v) { + if needed.contains_key(&idx) { + return; + } + let Some(p) = premise else { + needed.insert(idx, Step::Unit(v.to_string())); + return; + }; + needed.insert(idx, Step::Implies(p.to_string(), v.to_string())); + v = p; + } +} diff --git a/crates/aprender-contracts-cli/src/bin/pv-sat/tests.rs b/crates/aprender-contracts-cli/src/bin/pv-sat/tests.rs new file mode 100644 index 0000000000..248f3af12c --- /dev/null +++ b/crates/aprender-contracts-cli/src/bin/pv-sat/tests.rs @@ -0,0 +1,220 @@ +//! The reasoner is only trusted through the checker, so the property here is "whatever `solve` says, `check` +//! accepts" — swept over seeded random clause sets (stdlib LCG, no proptest) — plus the controls and the writer's +//! file discipline over a copy of `tests/fixtures/ont/relations-ok`. + +use std::collections::BTreeSet; +use std::path::Path; + +use provable_contracts::lint::consistency_gate::{run_consistency_gate, ConsistencyOutcome}; +use provable_contracts::ontology::witness::{Checked, Core, Step, WitnessResult}; + +use super::*; + +fn s(x: &str) -> String { + x.to_string() +} + +#[test] +fn the_plant_draws_its_core_in_derivation_order() { + assert_eq!( + sat::solve(&plant::plant()), + WitnessResult::UnsatCore(Core { + steps: vec![ + Step::Unit(s("A")), + Step::Implies(s("A"), s("B")), + Step::Implies(s("B"), s("C")), + ], + conflict: (s("A"), s("C")), + }) + ); + assert_eq!(plant::pc_reasoner(), Ok(FIRED)); + assert_eq!(plant::pc_model(), Ok(())); + assert_eq!(pc_checker(), Ok(FIRED)); +} + +/// Deterministic LCG (Knuth MMIX constants). +struct Lcg(u64); +impl Lcg { + fn next(&mut self, n: u64) -> usize { + self.0 = self + .0 + .wrapping_mul(6_364_136_223_846_793_005) + .wrapping_add(1_442_695_040_888_963_407); + usize::try_from((self.0 >> 33) % n).expect("small") + } +} + +#[test] +fn every_answer_the_reasoner_gives_checks() { + let names: Vec = (0..8).map(|i| format!("v{i}")).collect(); + let (mut sat_n, mut unsat_n) = (0, 0); + for seed in 0..500_u64 { + let mut r = Lcg(seed); + let mut cs = ClauseSet { + units: BTreeSet::new(), + implies: BTreeSet::new(), + conflicts: BTreeSet::new(), + }; + for _ in 0..=r.next(3) { + cs.units.insert(names[r.next(8)].clone()); + } + for _ in 0..r.next(10) { + let (a, b) = (r.next(8), r.next(8)); + if a != b { + cs.implies.insert((names[a].clone(), names[b].clone())); + } + } + for _ in 0..r.next(5) { + let (a, b) = (r.next(8), r.next(8)); + if a != b { + cs.conflicts + .insert((names[a.min(b)].clone(), names[a.max(b)].clone())); + } + } + let first = sat::solve(&cs); + assert_eq!(first, sat::solve(&cs), "seed {seed}: not deterministic"); + match check(&cs, &first) { + Ok(Checked::Sat) => sat_n += 1, + Ok(Checked::Unsat { .. }) => unsat_n += 1, + Err(e) => panic!( + "seed {seed}: the reasoner's own answer does not check: {e}\n{cs:?}\n{first:?}" + ), + } + } + // The sweep must visit both answers, or it proves half the claim. + assert!(sat_n > 50 && unsat_n > 50, "sat {sat_n} / unsat {unsat_n}"); +} + +fn corpus() -> tempfile::TempDir { + let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/ont/relations-ok"); + let dir = tempfile::tempdir().expect("tempdir"); + for entry in std::fs::read_dir(src).expect("fixture") { + let entry = entry.expect("entry"); + std::fs::copy(entry.path(), dir.path().join(entry.file_name())).expect("copy"); + } + dir +} + +fn exit(code: ExitCode) -> String { + format!("{code:?}") +} + +#[test] +fn the_written_witness_is_the_one_the_gate_checks_and_a_rerun_leaves_it() { + let dir = corpus(); + let stray = dir + .path() + .join("witness") + .join(format!("{}.json", "a".repeat(64))); + std::fs::create_dir_all(stray.parent().expect("dir")).expect("mkdir"); + std::fs::write(&stray, "{}").expect("stray"); + let notes = dir.path().join("witness").join("README.json"); + std::fs::write(¬es, "{}").expect("notes"); + + assert_eq!(exit(write_witness(dir.path())), exit(ExitCode::SUCCESS)); + assert!(!stray.exists(), "another graph's witness was not pruned"); + assert!(notes.exists(), "a file that is not a witness was pruned"); + + // relations-ok is inconsistent (`a contradicts d`, both live): the gate must reach a verdict, and it is a Fail. + match run_consistency_gate(dir.path()) { + ConsistencyOutcome::Ran { findings, .. } => { + let ids: Vec<&str> = findings.iter().map(|f| f.rule_id.as_str()).collect(); + assert_eq!(ids, ["PV-ONT-022"]); + } + other => panic!("expected a verdict, got {other:?}"), + } + + let written: Vec<_> = std::fs::read_dir(dir.path().join("witness")) + .expect("witness dir") + .flatten() + .map(|e| e.path()) + .filter(|p| p.file_name() != notes.file_name()) + .collect(); + assert_eq!(written.len(), 1); + let before = std::fs::read(&written[0]).expect("witness"); + let mtime = std::fs::metadata(&written[0]) + .and_then(|m| m.modified()) + .expect("mtime"); + assert_eq!(exit(write_witness(dir.path())), exit(ExitCode::SUCCESS)); + assert_eq!(std::fs::read(&written[0]).expect("witness"), before); + assert_eq!( + std::fs::metadata(&written[0]) + .and_then(|m| m.modified()) + .expect("mtime"), + mtime + ); +} + +#[test] +fn nothing_to_reason_over_is_exit_2_and_a_broken_sigma_exit_3() { + let dir = corpus(); + std::fs::remove_file(dir.path().join("ontology.yaml")).expect("rm"); + assert_eq!(exit(write_witness(dir.path())), exit(ExitCode::from(2))); + assert!(!dir.path().join("witness").exists()); + + let dir = corpus(); + std::fs::write(dir.path().join("ontology.yaml"), "roles: [unclosed\n").expect("write"); + assert_eq!(exit(write_witness(dir.path())), exit(ExitCode::from(3))); +} + +/// ONT-4e: the Liskov plant is found, and the reasoner's directions are the ones the checker proves — a strengthened +/// precondition, a weakened postcondition and a dropped invariant each name their clause, and a weakened +/// precondition / strengthened postcondition are Liskov. +#[test] +fn the_liskov_reasoner_decides_each_direction_the_checker_proves() { + use provable_contracts::ontology::liskov::{check, Clauses, Pair}; + use provable_contracts::schema::{Clause, FormalStatus}; + + assert_eq!(liskov::pc_reasoner(), Ok(FIRED)); + assert_eq!( + provable_contracts::ontology::liskov::pc_checker(), + Ok(FIRED) + ); + + let c = |id: &str, f: &str| Clause { + id: s(id), + statement: s(id), + formal: Some(s(f)), + formal_status: FormalStatus::Parsed, + }; + let verdict = |a: Clauses, b: Clauses| -> Vec { + let pairs = [Pair { + a: s("a"), + b: s("b"), + a_clauses: a, + b_clauses: b, + }]; + let w = liskov::witness(&pairs, FIRED, None); + check(&pairs, &w) + .expect("the reasoner's witness checks") + .iter() + .map(ToString::to_string) + .collect() + }; + let req = |v: Vec| Clauses { + requires: v, + ..Clauses::default() + }; + let ens = |v: Vec| Clauses { + ensures: v, + ..Clauses::default() + }; + let inv = |v: Vec| Clauses { + invariants: v, + ..Clauses::default() + }; + assert_eq!( + verdict(req(vec![c("PRE-1", "p")]), Clauses::default()), + ["a refines b: precondition strengthened (PRE-1)"] + ); + assert!(verdict(Clauses::default(), req(vec![c("PRE-1", "p")])).is_empty()); + assert_eq!( + verdict(Clauses::default(), ens(vec![c("POST-1", "q")])), + ["a refines b: postcondition weakened (POST-1)"] + ); + assert!(verdict(ens(vec![c("POST-1", "q")]), Clauses::default()).is_empty()); + assert_eq!( + verdict(Clauses::default(), inv(vec![c("INV-1", "r")])), + ["a refines b: invariant dropped (INV-1)"] + ); +} diff --git a/crates/aprender-contracts-cli/src/cli.rs b/crates/aprender-contracts-cli/src/cli.rs index c790ff16f8..0464efb54b 100644 --- a/crates/aprender-contracts-cli/src/cli.rs +++ b/crates/aprender-contracts-cli/src/cli.rs @@ -84,6 +84,17 @@ pub enum Commands { /// Path to the new contract YAML file new: PathBuf, }, + /// What the Lean proofs rest on: axiom subset pins and the compiler-escape allowlist (PVL-001 EV-6a, #4139) + Discharge { + #[command(subcommand)] + action: DischargeAction, + }, + /// Pin each contract-bound theorem's STATEMENT apart from its proof: `/Challenge/.lean` + /// (PVL-001 EV-7a, #4200) + Challenge { + #[command(subcommand)] + action: ChallengeAction, + }, /// Census the contract corpus: one cardinality, by_anchoring, by_entity_type (ONT-001 ONT-1) Census { /// Directory containing contract YAML files @@ -112,6 +123,11 @@ pub enum Commands { #[command(flatten)] release: Box, }, + /// Σ as OWL and its advisory TBox (ONT-001 §3.8, ONT-2c) + Ontology { + #[command(subcommand)] + command: OntologyCommand, + }, /// Show cross-contract obligation coverage report Coverage { /// Directory containing contract YAML files @@ -182,10 +198,10 @@ pub enum Commands { /// Path to binding registry YAML (adds binding coverage) #[arg(long)] binding: Option, - /// L5 gate: before counting a binding as implemented, verify its - /// `function` actually exists in source (scanned from the given root, - /// default `.`). Phantom "implemented" bindings are downgraded, so L5 - /// means "verified as implemented", not self-declared. + /// No-op alias (PVL-001 EV-2): `--binding` now ALWAYS resolves every + /// `implemented` binding against source with the `pv verify-bindings` + /// resolver, lists ghosts under `GHOST BINDINGS (n)` and exits 1. Kept so + /// existing invocations still parse; its root argument is ignored. #[arg(long, num_args = 0..=1, default_missing_value = ".")] verify_bindings: Option, /// Output format: text (default) or json @@ -287,9 +303,11 @@ pub enum Commands { /// merge-base(HEAD, origin/main), else the origin/main tip; with neither, NOT CHECKED is printed. #[arg(long)] armed_baseline_ref: Option, - /// Run ONE named gate and report only it (ONT-001 section 5 ONT-2b): `--gate sigma`. + /// Run ONE named gate and report only it (ONT-001 section 5 ONT-2b): `--gate sigma`. Repeatable + /// (PVL-001 EV-11): every named gate runs and reports, and the exit is their meet — a refusal over a + /// reject over a decline over a pass. #[arg(long)] - gate: Option, + gate: Vec, /// With `--gate shapes`: grade only this shape family (the shape and every `.*` shape), armed /// whatever `armed_shapes` says (aprender#3715: `--shape release-readiness-v1`). #[arg(long)] @@ -374,6 +392,17 @@ pub enum Commands { #[arg(long, default_value = "20")] top: usize, }, + /// Obligation gate (PVL-001 EV-10): every contract under ROOT/contracts validates, hides no + /// test under `falsification:`, and binds each `applies_to` to a `fn` under ROOT/src that + /// mentions the contract's `proved_type`. Replaces pmat's `scripts/pv-obligation-gate.py`. + Obligations { + /// Repository root holding `contracts/` and `src/` + #[arg(default_value = ".")] + root: PathBuf, + /// Exit 1 (`reject:`) when any problem is found; without it the report exits 0 + #[arg(long)] + gate: bool, + }, /// Remove enforcement level lock from a contract (requires --reason) Unlock { /// Path to the contract YAML file @@ -473,6 +502,131 @@ pub enum Commands { }, } +/// `pv discharge` actions (PVL-001 EV-6a, #4139). +#[derive(Subcommand, Clone, Debug)] +pub enum DischargeAction { + /// Generate `/Axioms.lean`: a subset axiom pin per contract-bound theorem in the root's import cone + GenAxioms { + /// The Lean dir (holds ProvableContracts.lean) + lean_dir: PathBuf, + /// Directory of the contracts whose `lean_theorem:` references bind the roots + #[arg(long, default_value = "contracts")] + contracts: PathBuf, + /// Do not write: rc 1 when the tracked Axioms.lean differs from its regeneration + #[arg(long)] + check: bool, + }, + /// Judge the tree: escapes vs escape-allowlist.yaml, exact-name roots, the label ratchet, Axioms.lean + /// freshness, then `lake env lean Axioms.lean` (after `build.sh`) unless `--no-lake` + Check { + lean_dir: PathBuf, + #[arg(long, default_value = "contracts")] + contracts: PathBuf, + /// Skip the Lean elaboration of Axioms.lean + #[arg(long)] + no_lake: bool, + /// Allowlist entries still `confirmed_by: pending` are RED + #[arg(long)] + strict: bool, + /// Also judge `/formalization.yaml`: `main_results` listed by discharge-summary.json, + /// `status.axioms` the pinned kernel set, `sorry_count` the measured count; a missing file is RED + /// (PVL-001 EV-8b, #4082) + #[arg(long)] + validate_formalization: bool, + /// Also re-check the BUILT tree's .olean files: `timeout lake env leanchecker ProvableContracts` + /// (non-fresh; `--fresh`, which replays Mathlib, is the nightly's, PVL-F7). rc != 0 rejects; no + /// `leanchecker` in the toolchain declines (PVL-001 EV-6b, #4199) + #[arg(long, conflicts_with = "no_lake")] + leanchecker: bool, + /// `--leanchecker`'s wall-clock limit, seconds + #[arg(long, default_value_t = 3600, requires = "leanchecker")] + leanchecker_timeout: u64, + /// `--leanchecker` under `ulimit -v ` (virtual memory, KiB); unset = no limit + #[arg(long, requires = "leanchecker")] + leanchecker_ulimit_v: Option, + /// `--leanchecker` with at most N Lean worker threads (`LEAN_NUM_THREADS`), 1..=8: the default is also the ceiling. leanchecker replays one full + /// environment per concurrent module task, so memory scales with this: 51 threads took 58-67 GB (#4348) + #[arg(long, default_value_t = crate::commands::discharge::LEANCHECKER_THREADS, value_parser = clap::value_parser!(u32).range(1..=i64::from(crate::commands::discharge::LEANCHECKER_THREADS)), requires = "leanchecker")] + leanchecker_threads: u32, + /// `--leanchecker` inside `systemd-run --user --scope -p MemoryMax=G -p CPUQuota=%`: this memory cap, + /// GiB, 1..=24: a caller may lower it, never raise it (#4348: the host must stay usable) + #[arg(long, default_value_t = crate::commands::discharge::LEANCHECKER_MEMORY_MAX_GIB, value_parser = clap::value_parser!(u32).range(1..=i64::from(crate::commands::discharge::LEANCHECKER_MEMORY_MAX_GIB)), requires = "leanchecker")] + leanchecker_memory_max_gib: u32, + /// `--leanchecker`'s scope CPU quota, percent of one core, 1..=800 + #[arg(long, default_value_t = crate::commands::discharge::LEANCHECKER_CPU_QUOTA_PCT, value_parser = clap::value_parser!(u32).range(1..=i64::from(crate::commands::discharge::LEANCHECKER_CPU_QUOTA_PCT)), requires = "leanchecker")] + leanchecker_cpu_quota_pct: u32, + /// Run `--leanchecker` WITHOUT the systemd scope (a host with no user systemd). The thread cap still applies + #[arg(long, requires = "leanchecker")] + leanchecker_unscoped: bool, + /// Also run the comparator: `lake env lean --run scripts/Comparator.lean Challenge/*.lean` on the BUILT + /// tree. Each EV-7a challenge must be closed by a sorry-free solution of the SAME statement (sha256 of + /// the canonical type). No Challenge file, or zero rows, declines (PVL-001 EV-7b, #4201) + #[arg(long, conflicts_with = "no_lake")] + comparator: bool, + /// Wall-clock limit on each `lake env lean` call (Axioms.lean, the comparator), seconds. A call that + /// exceeds it is killed with its process group and rejects: a hang is RED, not a wait (#4239) + #[arg(long, default_value_t = crate::commands::discharge::LAKE_TIMEOUT_S)] + lake_timeout: u64, + }, + /// `build.sh`, then `check` with every arm (`--strict`, the comparator, `--leanchecker`), then write the + /// untracked full log `/discharge.json` and the TRACKED `/../discharge-summary.json` -- + /// on failure too. The Lean steps run only after `build.sh` exits 0 (PVL-001 EV-8a, #4202) + Run { + lean_dir: PathBuf, + #[arg(long, default_value = "contracts")] + contracts: PathBuf, + /// The leanchecker arm's wall-clock limit, seconds + #[arg(long, default_value_t = 3600)] + leanchecker_timeout: u64, + /// The leanchecker arm under `ulimit -v ` (virtual memory, KiB); unset = no limit + #[arg(long)] + leanchecker_ulimit_v: Option, + /// the leanchecker arm with at most N Lean worker threads (`LEAN_NUM_THREADS`), 1..=8: the default is also the ceiling. leanchecker replays one full + /// environment per concurrent module task, so memory scales with this: 51 threads took 58-67 GB (#4348) + #[arg(long, default_value_t = crate::commands::discharge::LEANCHECKER_THREADS, value_parser = clap::value_parser!(u32).range(1..=i64::from(crate::commands::discharge::LEANCHECKER_THREADS)))] + leanchecker_threads: u32, + /// the leanchecker arm inside `systemd-run --user --scope -p MemoryMax=G -p CPUQuota=%`: this memory cap, + /// GiB, 1..=24: a caller may lower it, never raise it (#4348: the host must stay usable) + #[arg(long, default_value_t = crate::commands::discharge::LEANCHECKER_MEMORY_MAX_GIB, value_parser = clap::value_parser!(u32).range(1..=i64::from(crate::commands::discharge::LEANCHECKER_MEMORY_MAX_GIB)))] + leanchecker_memory_max_gib: u32, + /// the leanchecker arm's scope CPU quota, percent of one core, 1..=800 + #[arg(long, default_value_t = crate::commands::discharge::LEANCHECKER_CPU_QUOTA_PCT, value_parser = clap::value_parser!(u32).range(1..=i64::from(crate::commands::discharge::LEANCHECKER_CPU_QUOTA_PCT)))] + leanchecker_cpu_quota_pct: u32, + /// Run the leanchecker arm WITHOUT the systemd scope (a host with no user systemd). The thread cap still applies + #[arg(long)] + leanchecker_unscoped: bool, + /// Wall-clock limit on each `lake env` call outside the leanchecker arm, seconds; a timeout rejects (#4239) + #[arg(long, default_value_t = crate::commands::discharge::LAKE_TIMEOUT_S)] + lake_timeout: u64, + }, + /// `make label-ratchet`: rewrite /unresolved-labels.json DOWNWARD (it never gains a label; a missing + /// file is seeded). `check` never writes it. + LabelRatchet { + lean_dir: PathBuf, + #[arg(long, default_value = "contracts")] + contracts: PathBuf, + }, +} + +/// `pv challenge` actions (PVL-001 EV-7a, #4200). +#[derive(Subcommand, Clone, Debug)] +pub enum ChallengeAction { + /// Write `/Challenge/.lean`: every bound theorem restated as `PvlChallenge.` with + /// its proof replaced by `sorry`. Stale files are removed. + Gen { + /// Directory of the contracts whose `lean_theorem:` references bind the roots + contracts: PathBuf, + /// The Lean dir (holds ProvableContracts.lean) + lean_dir: PathBuf, + }, + /// Regenerate in memory and compare with `/Challenge/`: rc 1 on any difference, rc 2 on zero + /// challenges + Check { + contracts: PathBuf, + lean_dir: PathBuf, + }, +} + /// `pv census` output format (ONT-001 ONT-1). #[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] pub enum CensusFormat { @@ -551,3 +705,29 @@ impl ReleaseArgs { Ok(Some(s)) } } + +/// `pv ontology …` (ONT-001 §3.8, row ONT-2c). +#[derive(Subcommand, Clone, Debug)] +pub enum OntologyCommand { + /// Write Σ as OWL 2 EL functional syntax (the in-house writer; byte-deterministic) + Export { + /// Σ, the ontology declaration + #[arg(default_value = "contracts/ontology.yaml")] + sigma: PathBuf, + /// OWL 2 functional syntax. The only format this command writes; required so the output is named + #[arg(long)] + owl: bool, + /// Write `ontology.ofn` next to Σ instead of printing it + #[arg(long)] + write: bool, + }, + /// The told-closure TBox report (advisory; `tbox-report.json`). Exit 3 if its precondition fails + Tbox { + /// Σ, the ontology declaration + #[arg(default_value = "contracts/ontology.yaml")] + sigma: PathBuf, + /// Write `tbox-report.json` next to Σ instead of printing it + #[arg(long)] + write: bool, + }, +} diff --git a/crates/aprender-contracts-cli/src/commands/census.rs b/crates/aprender-contracts-cli/src/commands/census.rs index ab379a1d48..56e8c29f1f 100644 --- a/crates/aprender-contracts-cli/src/commands/census.rs +++ b/crates/aprender-contracts-cli/src/commands/census.rs @@ -120,6 +120,11 @@ pub struct Census { pub quarantined_n: usize, pub by_kind: BTreeMap, pub by_entity_type: BTreeMap, + /// ONT-4d: instances per Σ concept in the extracted graph AFTER the subsumption closure, so a concept + /// counts its sub-concepts' instances too. Empty when there is no well-formed Σ or the extraction refused + /// (the `sigma` / `shapes` gates report why); never a guess. + #[serde(default)] + pub by_concept: BTreeMap, pub by_anchoring: AnchoringCounts, /// sha256 over the sorted, unique contract ids (file stems), newline /// separated. Two corpora with the same ids hash the same; adding, removing @@ -271,9 +276,23 @@ pub fn census_of(dir: &Path) -> Result> { } census.id_set_sha256 = id_set_sha256(&ids); census.declared_external = declared_external(dir)?; + census.by_concept = by_concept(dir); Ok(census) } +/// ONT-4d: `concept → instances` over the closed graph (see [`Census::by_concept`]). +fn by_concept(dir: &Path) -> BTreeMap { + use provable_contracts::ontology::{extract, rdf::ont}; + let (Some(sigma), Ok(x)) = (extract::sigma_of(dir), extract::all(dir)) else { + return BTreeMap::new(); + }; + sigma + .concepts + .keys() + .map(|c| (c.clone(), x.graph.instances_of(&ont(c)).len())) + .collect() +} + /// Contracts held OUT of the corpus, counted but never parsed. The shared walker /// skips `quarantine/`, so this is its own scan: a number the census reports is a /// number the census measured. @@ -311,6 +330,7 @@ fn empty_census(n_files: usize, quarantined_n: usize) -> Census { quarantined_n, by_kind: BTreeMap::new(), by_entity_type: BTreeMap::new(), + by_concept: BTreeMap::new(), by_anchoring: AnchoringCounts::default(), id_set_sha256: String::new(), declared_external: Vec::new(), diff --git a/crates/aprender-contracts-cli/src/commands/challenge.rs b/crates/aprender-contracts-cli/src/commands/challenge.rs new file mode 100644 index 0000000000..8673cb8ac8 --- /dev/null +++ b/crates/aprender-contracts-cli/src/commands/challenge.rs @@ -0,0 +1,89 @@ +//! `pv challenge gen | check` (PVL-001 EV-7a, #4200). The rendering lives in +//! [`provable_contracts::discharge::challenge`]; this module writes or compares, and prints. +//! +//! Exit: 0 fresh · 1 reject (a statement could not be lifted, or `Challenge/` differs from its regeneration) +//! · 2 decline (the tree does not load, or zero challenges: nothing is pinned, which must not read as fresh). + +use std::path::Path; + +use provable_contracts::discharge::challenge::{self, Rendered, CHALLENGE_DIR}; + +use super::discharge::{DischargeDeclined, DischargeRejected}; +use crate::cli::ChallengeAction; + +type Res = Result<(), Box>; + +pub fn run(action: ChallengeAction) -> Res { + match action { + ChallengeAction::Gen { + contracts, + lean_dir, + } => gen(&contracts, &lean_dir), + ChallengeAction::Check { + contracts, + lean_dir, + } => check(&contracts, &lean_dir), + } +} + +fn load(contracts: &Path, lean_dir: &Path) -> Result { + let r = challenge::render(lean_dir, contracts).map_err(DischargeDeclined)?; + if r.files.is_empty() && r.unrestated.is_empty() { + return Err(DischargeDeclined(format!( + "zero challenges: no contract under {} binds a theorem of {}", + contracts.display(), + lean_dir.display() + ))); + } + Ok(r) +} + +/// One FAIL line per root whose statement could not be lifted; the count. +fn report_unrestated(r: &Rendered) -> usize { + for (stem, fqn, why) in &r.unrestated { + println!("FAIL UNRESTATED {stem}: {fqn}: {why}"); + } + r.unrestated.len() +} + +fn gen(contracts: &Path, lean_dir: &Path) -> Res { + let r = load(contracts, lean_dir)?; + let written = challenge::write(lean_dir, &r) + .map_err(|e| DischargeDeclined(format!("{}/{CHALLENGE_DIR}: {e}", lean_dir.display())))?; + for rel in &written { + println!("wrote {rel}"); + } + println!( + "{} challenge file(s) under {}/{CHALLENGE_DIR} ({} rewritten)", + r.files.len(), + lean_dir.display(), + written.len() + ); + match report_unrestated(&r) { + 0 => Ok(()), + n => Err(DischargeRejected(format!("{n} bound theorem(s) have no challenge")).into()), + } +} + +fn check(contracts: &Path, lean_dir: &Path) -> Res { + let r = load(contracts, lean_dir)?; + let diffs = challenge::diff(lean_dir, &r); + for d in &diffs { + println!("FAIL STALE {d}"); + } + let n = diffs.len() + report_unrestated(&r); + if n > 0 { + return Err(DischargeRejected(format!( + "{n} failure(s); regenerate with `pv challenge gen {} {}`", + contracts.display(), + lean_dir.display() + )) + .into()); + } + println!( + "challenge-fresh: {} file(s) under {}/{CHALLENGE_DIR} match their regeneration", + r.files.len(), + lean_dir.display() + ); + Ok(()) +} diff --git a/crates/aprender-contracts-cli/src/commands/discharge.rs b/crates/aprender-contracts-cli/src/commands/discharge.rs new file mode 100644 index 0000000000..c1b8b9201a --- /dev/null +++ b/crates/aprender-contracts-cli/src/commands/discharge.rs @@ -0,0 +1,1991 @@ +//! `pv discharge gen-axioms | check | run` (PVL-001 EV-6a, #4139; `--leanchecker` EV-6b, #4199; `--comparator` +//! EV-7b, #4201; `run` EV-8a, #4202). The judging lives in +//! [`provable_contracts::discharge`]; this module prints the report and runs Lean. +//! +//! Exit: 0 accept · 1 reject (`reject:`) · 2 decline (`decline:` — no root file, zero roots, no `lake`, no +//! `leanchecker` in the toolchain, no `Challenge/*.lean` or zero comparator rows). +//! +//! `--leanchecker` is NON-fresh: it re-checks the tree's own .olean files and trusts the Mathlib .oleans they +//! import. `--fresh` replays Mathlib and is the nightly's (PVL-F7). `formalization.yaml` `scope` says so. + +use std::ffi::OsStr; +use std::fmt; +use std::path::Path; +use std::process::Command; +use std::time::{Duration, Instant}; + +use provable_contracts::discharge::comparator::{self, CHALLENGE_DIR, COMPARATOR}; +use provable_contracts::discharge::summary::{self, LOG_FILE}; +use provable_contracts::discharge::{self, CheckOpts, Report, Tree, AXIOMS_FILE}; + +use crate::cli::DischargeAction; + +/// Nothing could be judged. Exit 2. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DischargeDeclined(pub String); + +impl fmt::Display for DischargeDeclined { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for DischargeDeclined {} + +/// Judged, and failed. Exit 1. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DischargeRejected(pub String); + +impl fmt::Display for DischargeRejected { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for DischargeRejected {} + +type Res = Result<(), Box>; + +pub fn run(action: DischargeAction) -> Res { + match action { + DischargeAction::GenAxioms { + lean_dir, + contracts, + check, + } => gen_axioms(&lean_dir, &contracts, check), + DischargeAction::Check { + lean_dir, + contracts, + no_lake, + strict, + validate_formalization, + leanchecker, + leanchecker_timeout, + leanchecker_ulimit_v, + leanchecker_threads, + leanchecker_memory_max_gib, + leanchecker_cpu_quota_pct, + leanchecker_unscoped, + comparator, + lake_timeout, + } => { + let opts = CheckOpts { + strict, + validate_formalization, + }; + let r = discharge::check(&lean_dir, &contracts, opts); + let lc = leanchecker.then_some(Leanchecker::from_flags( + leanchecker_timeout, + leanchecker_ulimit_v, + leanchecker_threads, + leanchecker_memory_max_gib, + leanchecker_cpu_quota_pct, + leanchecker_unscoped, + )); + finish_with( + Lake::new(lake_timeout), + r, + &lean_dir, + no_lake, + comparator, + lc, + ) + } + DischargeAction::Run { + lean_dir, + contracts, + leanchecker_timeout, + leanchecker_ulimit_v, + leanchecker_threads, + leanchecker_memory_max_gib, + leanchecker_cpu_quota_pct, + leanchecker_unscoped, + lake_timeout, + } => run_all( + Lake::new(lake_timeout), + "build.sh", + &lean_dir, + &contracts, + Leanchecker::from_flags( + leanchecker_timeout, + leanchecker_ulimit_v, + leanchecker_threads, + leanchecker_memory_max_gib, + leanchecker_cpu_quota_pct, + leanchecker_unscoped, + ), + ), + DischargeAction::LabelRatchet { + lean_dir, + contracts, + } => finish_with( + Lake::new(LAKE_TIMEOUT_S), + discharge::ratchet_labels(&lean_dir, &contracts), + &lean_dir, + true, + false, + None, + ), + } +} + +fn gen_axioms(lean_dir: &Path, contracts: &Path, check: bool) -> Res { + let g = discharge::generate(lean_dir, contracts).map_err(DischargeDeclined)?; + let (text, b) = (g.text, g.binding); + let path = lean_dir.join(AXIOMS_FILE); + if check { + return match std::fs::read_to_string(&path) { + Ok(on_disk) if on_disk == text => { + println!( + "ok {} is its regeneration ({} root(s) bound)", + path.display(), + b.roots.len() + ); + Ok(()) + } + Ok(_) => Err(DischargeRejected(format!( + "{} differs from its regeneration", + path.display() + )) + .into()), + Err(e) => Err(DischargeRejected(format!("{}: {e}", path.display())).into()), + }; + } + std::fs::write(&path, &text)?; + println!("wrote {} ({} root(s) bound)", path.display(), b.roots.len()); + Ok(()) +} + +/// How `--leanchecker` runs (PVL-001 EV-6b, #4199; the caps #4348). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Leanchecker { + pub timeout_s: u64, + pub ulimit_v_kib: Option, + /// `LEAN_NUM_THREADS`: leanchecker replays one full environment (Mathlib included) per concurrent module + /// task, so its memory is threads x environment. Uncapped it took 51 threads and 58-67 GB on lambda (#4348). + pub threads: u32, + /// `None` = no systemd scope (`--leanchecker-unscoped`). + pub scope: Option, +} + +impl Leanchecker { + /// The `--leanchecker-*` flags, shared by `check` and `run`. + fn from_flags( + timeout_s: u64, + ulimit_v_kib: Option, + threads: u32, + memory_max_gib: u32, + cpu_quota_pct: u32, + unscoped: bool, + ) -> Self { + Self { + timeout_s, + ulimit_v_kib, + threads, + scope: (!unscoped).then_some(Scope { + memory_max_gib, + cpu_quota_pct, + }), + } + } +} + +/// `systemd-run --user --scope -p MemoryMax=G -p CPUQuota=%` around leanchecker (#4348, operator: "this +/// host must be able to do other work, so never let it get overloaded"). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Scope { + pub memory_max_gib: u32, + pub cpu_quota_pct: u32, +} + +/// The defaults the cop ruled for lambda (#4348): <= 8 Lean threads, 24G, 800%. +pub const LEANCHECKER_THREADS: u32 = 8; +pub const LEANCHECKER_MEMORY_MAX_GIB: u32 = 24; +pub const LEANCHECKER_CPU_QUOTA_PCT: u32 = 800; + +/// The slice the scope is created under. lambda's `agent-slice-sweep` adopts any lean/lake process OUTSIDE it into +/// it -- measured 2026-09-25: a leanchecker in a 24G `run-*.scope` in app.slice was moved to agent.slice (96G) and +/// grew to 71G. A scope nested in agent.slice is left alone and its own, tighter MemoryMax still binds. On a host +/// without that slice systemd creates it as a plain transient slice. +const AGENT_SLICE: &str = "agent.slice"; + +/// The script `sh -c` runs; `$1` ulimit, `$2` timeout, `$3` lake. +const RECHECK_SH: &str = r#"if [ -n "$1" ]; then ulimit -v "$1" || exit 125; fi; exec timeout -k 30 "$2" "$3" env leanchecker ProvableContracts"#; + +/// The argv of the leanchecker arm: `[systemd-run --user --scope -q -p MemoryMax=.. -p CPUQuota=.. --] sh -c ..`. +/// `LEAN_NUM_THREADS` is set on the command, which a `--scope` unit inherits (it runs in the caller's process). +fn recheck_argv(lake_bin: &str, lc: Leanchecker) -> Vec { + let mut v: Vec = Vec::new(); + if let Some(s) = lc.scope { + v.extend( + [ + "systemd-run".to_string(), + "--user".into(), + "--scope".into(), + format!("--slice={AGENT_SLICE}"), + "-q".into(), + "-p".into(), + format!("MemoryMax={}G", s.memory_max_gib), + "-p".into(), + format!("CPUQuota={}%", s.cpu_quota_pct), + "--".into(), + ] + .into_iter(), + ); + } + let limit = lc.ulimit_v_kib.map(|k| k.to_string()).unwrap_or_default(); + v.extend([ + "sh".to_string(), + "-c".into(), + RECHECK_SH.into(), + "pv-leanchecker".into(), + limit, + lc.timeout_s.to_string(), + lake_bin.into(), + ]); + v +} + +/// The Lean steps run only on a tree nothing else has already failed or declined: a 60-minute re-check of a tree +/// that is already RED would only delay the verdict. They record their raw exits in `r.lake_exit` and +/// `r.leanchecker_exit`, and the comparator's closure in `r.challenges` (`None` = never ran), for +/// `discharge-summary.json` (EV-8a). The comparator runs before the (much slower) leanchecker. +pub(crate) fn lean_steps( + lake: Lake<'_>, + r: &mut Report, + lean_dir: &Path, + no_lake: bool, + cmp: bool, + lc: Option, +) { + let open = |r: &Report| !r.reject && r.decline.is_none(); + if open(r) && !no_lake { + elaborate(lake, lean_dir, r); + } + if cmp && open(r) { + compare(lake, lean_dir, r); + } + if let Some(lc) = lc { + if open(r) { + recheck(lake, lean_dir, lc, r); + } + } +} + +fn finish_with( + lake: Lake<'_>, + mut r: Report, + lean_dir: &Path, + no_lake: bool, + cmp: bool, + lc: Option, +) -> Res { + lean_steps(lake, &mut r, lean_dir, no_lake, cmp, lc); + verdict(r, lean_dir) +} + +/// Print the report and turn it into the exit: reject (1) before decline (2) before accept (0). +fn verdict(r: Report, lean_dir: &Path) -> Res { + for l in &r.lines { + println!("{l}"); + } + if r.reject { + let n = r.lines.iter().filter(|l| l.starts_with("FAIL")).count(); + return Err( + DischargeRejected(format!("{n} failure(s) under {}", lean_dir.display())).into(), + ); + } + if let Some(why) = r.decline { + return Err(DischargeDeclined(why).into()); + } + println!("ok discharge {}", lean_dir.display()); + Ok(()) +} + +/// `pv discharge run` (PVL-001 EV-8a, #4202): `build` (run by bash in ``), then `check --strict` and +/// every Lean arm, then the two files -- written whatever the verdict, so a RED run leaves a RED summary, never +/// none. `build.sh` rc 2 is its cache-miss decline and stays a decline; any other non-zero rejects; either way the +/// Lean steps do not run on a tree that did not build. A summary that cannot be written rejects: it is the output. +pub(crate) fn run_all( + lake: Lake<'_>, + build: &str, + lean_dir: &Path, + contracts: &Path, + lc: Leanchecker, +) -> Res { + let (build_exit, build_out) = match Command::new("bash") + .arg(build) + .current_dir(lean_dir) + .output() + { + Ok(o) => ( + Some(raw_exit(o.status)), + format!( + "{}{}", + String::from_utf8_lossy(&o.stdout), + String::from_utf8_lossy(&o.stderr) + ), + ), + Err(e) => (None, e.to_string()), + }; + let mut r = discharge::check( + lean_dir, + contracts, + CheckOpts { + strict: true, + validate_formalization: false, + }, + ); + match build_exit { + Some(0) => { + r.lines.insert(0, format!("ok {build}")); + lean_steps(lake, &mut r, lean_dir, false, true, Some(lc)); + } + Some(2) => { + r.lines.push(format!("{build} declined (rc 2):")); + r.lines.extend(tail(&build_out)); + r.decline.get_or_insert_with(|| { + format!("{build} declined: the Lean steps did not run -- not a verdict") + }); + } + Some(rc) => { + r.lines.push(format!("FAIL {build} exited {rc}")); + r.lines.extend(tail(&build_out)); + r.reject = true; + } + None => { + r.decline + .get_or_insert_with(|| format!("{build} could not be run ({build_out})")); + } + } + let tree = Tree::load(lean_dir).ok(); + let s = summary::summarize( + &r, + tree.as_ref(), + lean_dir, + summary::current_tree_sha(lean_dir), + build_exit, + ); + let spath = summary::summary_path(lean_dir); + // The summary first: the log is built after it, so a summary that cannot be written is in the log's verdict. + write_or_reject(&mut r, &spath, Ok(s.render())); + let log = RunLog { + verdict: if r.reject { + "reject" + } else if r.decline.is_some() { + "decline" + } else { + "accept" + }, + decline: r.decline.as_deref(), + lines: &r.lines, + summary: &s, + }; + let log_text = serde_json::to_string_pretty(&log) + .map(|t| t + "\n") + .map_err(|e| e.to_string()); + write_or_reject(&mut r, &lean_dir.join(LOG_FILE), log_text); + verdict(r, lean_dir) +} + +/// Write `text` to `p`, or record the failure and reject the run. +fn write_or_reject(r: &mut Report, p: &Path, text: Result) { + match text.and_then(|t| std::fs::write(p, t).map_err(|e| e.to_string())) { + Ok(()) => r.lines.push(format!("wrote {}", p.display())), + Err(e) => { + r.lines + .push(format!("FAIL cannot write {}: {e}", p.display())); + r.reject = true; + } + } +} + +/// `/discharge.json`, the untracked full log of one `run`: the verdict, every line, and the summary. +#[derive(serde::Serialize)] +struct RunLog<'a> { + verdict: &'a str, + decline: Option<&'a str>, + lines: &'a [String], + summary: &'a summary::Summary, +} + +/// `lake env lean Axioms.lean`: the subset and capstone pins, elaborated against the BUILT tree (run `build.sh` +/// first; `lake env` builds nothing). +fn elaborate(lake: Lake<'_>, lean_dir: &Path, r: &mut Report) { + let what = format!("lake env lean {AXIOMS_FILE}"); + match lake.run(&["env", "lean", AXIOMS_FILE], lean_dir) { + Ok(Bounded::TimedOut(pgid)) => { + r.lake_exit = Some(TIMED_OUT); + r.lines.push(lake.timed_out(&what, pgid)); + r.reject = true; + } + Err(e) => { + r.decline = Some(format!( + "lake could not be run ({e}): Axioms.lean was not elaborated" + )) + } + Ok(Bounded::Done(o)) if o.status.success() => { + r.lake_exit = Some(raw_exit(o.status)); + r.lines.push(format!("ok {what}")); + } + Ok(Bounded::Done(o)) => { + r.lake_exit = Some(raw_exit(o.status)); + let text = format!( + "{}{}", + String::from_utf8_lossy(&o.stdout), + String::from_utf8_lossy(&o.stderr) + ); + r.lines.push(format!( + "FAIL lake env lean {AXIOMS_FILE} exited {:?}", + o.status.code() + )); + r.lines.extend( + text.lines() + .filter(|l| l.contains("error")) + .take(20) + .map(|l| format!(" {l}")), + ); + r.reject = true; + } + } +} + +/// `lake env lean --run scripts/Comparator.lean Challenge/*.lean` (PVL-001 EV-7b): the script MEASURES, one NDJSON +/// row per challenge, and [`comparator::judge_rows`] judges. No Challenge file, no script or no `lake` declines; +/// a Challenge file that does not elaborate, or output that is not rows, rejects: its rows were never judged. +fn compare(lake: Lake<'_>, lean_dir: &Path, r: &mut Report) { + let files = comparator::challenge_files(lean_dir); + if files.is_empty() { + r.decline = Some(format!( + "comparator: no {CHALLENGE_DIR}/*.lean under {} -- EV-7a writes them; nothing to compare", + lean_dir.display() + )); + return; + } + if !lean_dir.join(COMPARATOR).is_file() { + r.decline = Some(format!( + "comparator: {} does not exist -- not a verdict", + lean_dir.join(COMPARATOR).display() + )); + return; + } + if !self_test(lake, lean_dir, r) { + return; + } + let what = format!("lake env lean --run {COMPARATOR} ({} file(s))", files.len()); + let mut args: Vec<&OsStr> = ["env", "lean", "--run", COMPARATOR] + .map(OsStr::new) + .to_vec(); + args.extend(files.iter().map(|f| f.as_os_str())); + let out = match lake.run(&args, lean_dir) { + Ok(Bounded::Done(o)) => o, + Ok(Bounded::TimedOut(pgid)) => { + r.lines.push(lake.timed_out(&what, pgid)); + r.reject = true; + return; + } + Err(e) => { + r.decline = Some(format!( + "lake could not be run ({e}): the comparator did not run" + )); + return; + } + }; + let stdout = String::from_utf8_lossy(&out.stdout); + if !out.status.success() { + r.lines.push(format!( + "FAIL {what} exited {} -- a Challenge file did not elaborate; its rows were withheld", + raw_exit(out.status) + )); + r.lines.extend(tail(&String::from_utf8_lossy(&out.stderr))); + r.reject = true; + return; + } + match comparator::parse_rows(&stdout) { + Ok(rows) => { + r.lines.push(format!("ok {what}")); + let mut c = comparator::judge_rows(&rows, r); + match comparator::expected_roots(lean_dir, &files) { + Ok(roots) => comparator::cross_check(&rows, &roots, &mut c, r), + Err(e) => { + r.lines.push(format!( + "FAIL comparator: a Challenge file could not be read, its roots were never counted: {e}" + )); + r.reject = true; + } + } + r.challenges = Some(c); + } + Err(e) => { + r.lines.push(format!("FAIL {what}: {e}")); + r.reject = true; + } + } +} + +/// The FIPS 180-4 vectors `Comparator.lean --self-test` checks: "", "abc" and the two-block 448-bit message. +const FIPS_VECTORS: usize = 3; + +/// `lake env lean --run scripts/Comparator.lean --self-test` (#4238), before any row is trusted: the pure-Lean +/// sha256 against the FIPS vectors, then the defeq controls. A regressed sha256 still hashes consistently, so the +/// rows alone cannot catch it. rc != 0, a timeout, or fewer than [`FIPS_VECTORS`] `ok … sha256` lines rejects -- +/// a self-test that checked nothing is not a pass. +fn self_test(lake: Lake<'_>, lean_dir: &Path, r: &mut Report) -> bool { + let what = format!("lake env lean --run {COMPARATOR} --self-test"); + let o = match lake.run( + &["env", "lean", "--run", COMPARATOR, "--self-test"], + lean_dir, + ) { + Ok(Bounded::Done(o)) => o, + Ok(Bounded::TimedOut(pgid)) => { + r.lines.push(lake.timed_out(&what, pgid)); + r.reject = true; + return false; + } + Err(e) => { + r.decline = Some(format!( + "lake could not be run ({e}): the comparator did not run (nor its self-test)" + )); + return false; + } + }; + let stdout = String::from_utf8_lossy(&o.stdout); + let vectors = stdout + .lines() + .filter(|l| l.starts_with("ok") && l.contains(" sha256 ")) + .count(); + if o.status.success() && vectors >= FIPS_VECTORS { + r.lines + .push(format!("ok {what} ({vectors} FIPS vectors)")); + return true; + } + r.lines.push(format!( + "FAIL {what} exited {} with {vectors}/{FIPS_VECTORS} FIPS vectors ok -- the statement hash is not trusted", + raw_exit(o.status) + )); + r.lines.extend(tail(&format!( + "{stdout}{}", + String::from_utf8_lossy(&o.stderr) + ))); + r.reject = true; + false +} + +/// The last 20 lines of `text`, indented. +fn tail(text: &str) -> Vec { + let mut v: Vec = text + .lines() + .rev() + .take(20) + .map(|l| format!(" {l}")) + .collect(); + v.reverse(); + v +} + +/// The process's exit code, or 128+signal when a signal ended it (the shell's convention). +fn raw_exit(s: std::process::ExitStatus) -> i32 { + use std::os::unix::process::ExitStatusExt; + s.code().unwrap_or_else(|| 128 + s.signal().unwrap_or(0)) +} + +/// The default bound on one `lake` call, seconds. `check`/`run` take `--lake-timeout` (#4239). +pub(crate) const LAKE_TIMEOUT_S: u64 = 1800; + +/// The exit recorded for a step the bound killed: `timeout(1)`'s, as the leanchecker arm records. +const TIMED_OUT: i32 = 124; + +/// A `lake` binary and the wall-clock bound on each call to it (#4239: a hang is RED, not a wait). +#[derive(Clone, Copy, Debug)] +pub(crate) struct Lake<'a> { + pub bin: &'a str, + pub timeout_s: u64, +} + +/// How a bounded call ended. `TimedOut` carries the killed process group (the child's own PID). +enum Bounded { + Done(std::process::Output), + TimedOut(u32), +} + +impl Lake<'static> { + fn new(timeout_s: u64) -> Self { + Lake { + bin: "lake", + timeout_s, + } + } +} + +impl Lake<'_> { + /// `lake ` in `dir`, in its own process group, waited on for at most `timeout_s`. At the deadline the + /// group is SIGKILLed by the child's own PID (pgid == pid) -- never by pattern -- so a `lean` under `lake env` + /// dies with it. The deadline also bounds the output drain: a grandchild holding a pipe cannot stall it. + fn run>(self, args: &[S], dir: &Path) -> std::io::Result { + let mut child = self.spawn(args, dir)?; + let pgid = child.id(); + let rx = drain_pipes(&mut child); + let deadline = Instant::now() + Duration::from_secs(self.timeout_s); + let Some(status) = wait_until(&mut child, deadline)? else { + return Ok(kill_group(&mut child, pgid)); + }; + let Some([stdout, stderr]) = collect(&rx, deadline) else { + return Ok(kill_group(&mut child, pgid)); + }; + Ok(Bounded::Done(std::process::Output { + status, + stdout, + stderr, + })) + } + + /// `lake ` spawned in its own process group, piped. ETXTBSY (26): a just-written `lake` whose write fd a + /// concurrent fork still holds until its exec. Transient, so it is retried. + fn spawn>( + self, + args: &[S], + dir: &Path, + ) -> std::io::Result { + use std::os::unix::process::CommandExt; + use std::process::Stdio; + let spawn = || { + Command::new(self.bin) + .args(args) + .current_dir(dir) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .process_group(0) + .spawn() + }; + let mut child = spawn(); + for _ in 0..20 { + match &child { + Err(e) if e.raw_os_error() == Some(26) => { + std::thread::sleep(Duration::from_millis(25)); + child = spawn(); + } + _ => break, + } + } + child + } + + fn timed_out(self, what: &str, pgid: u32) -> String { + format!( + "FAIL {what} timed out after {}s -- killed its process group {pgid}; a hang is RED, not a wait", + self.timeout_s + ) + } +} + +type Drained = std::sync::mpsc::Receiver<(usize, Vec)>; + +/// Read the child's stdout (0) and stderr (1) on their own threads, so a full pipe cannot block its exit. +fn drain_pipes(child: &mut std::process::Child) -> Drained { + use std::io::Read; + let (tx, rx) = std::sync::mpsc::channel(); + let drain = |mut pipe: Box, which: usize| { + let tx = tx.clone(); + std::thread::spawn(move || { + let mut buf = Vec::new(); + let _ = pipe.read_to_end(&mut buf); + let _ = tx.send((which, buf)); + }); + }; + if let Some(p) = child.stdout.take() { + drain(Box::new(p), 0); + } + if let Some(p) = child.stderr.take() { + drain(Box::new(p), 1); + } + rx +} + +/// The child's exit status, or `None` once `deadline` passes with it still running. +fn wait_until( + child: &mut std::process::Child, + deadline: Instant, +) -> std::io::Result> { + loop { + if let Some(status) = child.try_wait()? { + return Ok(Some(status)); + } + if Instant::now() >= deadline { + return Ok(None); + } + std::thread::sleep(Duration::from_millis(20)); + } +} + +/// Both drained pipes, or `None` if `deadline` passes first: a grandchild holding a pipe cannot stall the drain. +fn collect(rx: &Drained, deadline: Instant) -> Option<[Vec; 2]> { + use std::sync::mpsc::RecvTimeoutError; + let mut out = [Vec::new(), Vec::new()]; + for _ in 0..2 { + let left = deadline.saturating_duration_since(Instant::now()); + match rx.recv_timeout(left) { + Ok((which, buf)) => out[which] = buf, + Err(RecvTimeoutError::Disconnected) => break, + Err(RecvTimeoutError::Timeout) => return None, + } + } + Some(out) +} + +/// SIGKILL the whole process group by the child's own PID (pgid == pid) -- never by pattern -- then reap the child. +fn kill_group(child: &mut std::process::Child, pgid: u32) -> Bounded { + let _ = Command::new("kill") + .args(["-s", "KILL", "--", &format!("-{pgid}")]) + .status(); + let _ = child.kill(); + let _ = child.wait(); + Bounded::TimedOut(pgid) +} + +/// The toolchain `lake env` puts on PATH for this tree: `lake env printenv LEAN_SYSROOT`. `Err` is the decline; +/// a timeout rejects in place and yields `Err(None)` (#4239). +fn sysroot( + lake: Lake<'_>, + lean_dir: &Path, + r: &mut Report, +) -> Result> { + let o = match lake.run(&["env", "printenv", "LEAN_SYSROOT"], lean_dir) { + Ok(Bounded::Done(o)) => o, + Ok(Bounded::TimedOut(pgid)) => { + r.lines + .push(lake.timed_out("lake env printenv LEAN_SYSROOT", pgid)); + r.reject = true; + return Err(None); + } + Err(e) => { + return Err(Some(format!( + "lake could not be run ({e}): leanchecker did not run" + ))) + } + }; + let root = String::from_utf8_lossy(&o.stdout).trim().to_string(); + if !o.status.success() || root.is_empty() { + return Err(Some(format!( + "`lake env printenv LEAN_SYSROOT` exited {:?} and named no toolchain: leanchecker did not run", + o.status.code() + ))); + } + Ok(root.into()) +} + +/// `timeout lake env leanchecker ProvableContracts` (PVL-001 EV-6b): rc != 0 rejects, a timeout rejects, and a +/// toolchain without `leanchecker` declines -- measured: elan's `lake env leanchecker` on v4.15.0 exits 1 "does not +/// have the binary", which would otherwise read as a failed check. `timeout` signals the whole process group, so +/// the `leanchecker` under `lake` does not outlive it. +fn recheck(lake: Lake<'_>, lean_dir: &Path, lc: Leanchecker, r: &mut Report) { + let root = match sysroot(lake, lean_dir, r) { + Ok(root) => root, + Err(why) => { + r.decline = why; + return; + } + }; + let bin = root.join("bin").join("leanchecker"); + if !bin.is_file() { + r.decline = Some(format!( + "leanchecker not in toolchain: {} does not exist", + bin.display() + )); + return; + } + // A scope that cannot be created would otherwise read as leanchecker's own non-zero exit: probe it first. + if lc.scope.is_some() { + let probe = Command::new("systemd-run") + .args([ + "--user", + "--scope", + &format!("--slice={AGENT_SLICE}"), + "-q", + "--", + "true", + ]) + .output(); + if !matches!(&probe, Ok(o) if o.status.success()) { + r.decline = Some( + "`systemd-run --user --scope` is unavailable here: leanchecker did not run (pass \ + --leanchecker-unscoped only on a host where an uncapped run cannot starve other work, #4348)" + .to_string(), + ); + return; + } + } + let argv = recheck_argv(lake.bin, lc); + let out = Command::new(&argv[0]) + .args(&argv[1..]) + .env("LEAN_NUM_THREADS", lc.threads.to_string()) + .current_dir(lean_dir) + .output(); + let what = format!( + "lake env leanchecker ProvableContracts (timeout {}s, {} threads, {})", + lc.timeout_s, + lc.threads, + lc.scope.map_or("unscoped".to_string(), |s| format!( + "scope MemoryMax={}G CPUQuota={}%", + s.memory_max_gib, s.cpu_quota_pct + )) + ); + match out { + Err(e) => { + r.decline = Some(format!( + "sh could not be run ({e}): leanchecker did not run" + )) + } + Ok(o) if o.status.success() => { + r.leanchecker_exit = Some(0); + r.lines.push(format!("ok {what}")); + } + Ok(o) => match o.status.code() { + // timeout/ulimit/lake could not start leanchecker: it never ran, so no exit is recorded. + Some(c @ 125..=127) => { + r.decline = Some(format!( + "{what} could not be started (rc {c}: timeout/ulimit/lake): not a verdict" + )); + } + code => { + r.leanchecker_exit = Some(raw_exit(o.status)); + let why = if matches!(code, Some(124 | 137)) { + "timed out".to_string() + } else { + format!("exited {code:?}") + }; + r.lines.push(format!("FAIL {what} {why}")); + let text = format!( + "{}{}", + String::from_utf8_lossy(&o.stdout), + String::from_utf8_lossy(&o.stderr) + ); + r.lines.extend( + text.lines() + .rev() + .take(20) + .collect::>() + .into_iter() + .rev() + .map(|l| format!(" {l}")), + ); + r.reject = true; + } + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::contract_walk::{exit_code_for, verdict_for}; + use std::path::PathBuf; + + /// A one-theorem tree bound by the label `Theorems.Gelu` (the integration fixture, in-process). + fn tree() -> (tempfile::TempDir, PathBuf, PathBuf) { + let d = tempfile::tempdir().expect("tempdir"); + let lean = d.path().join("lean"); + let contracts = d.path().join("contracts"); + std::fs::create_dir_all(lean.join("ProvableContracts/Theorems/Gelu")).expect("mkdir"); + std::fs::create_dir_all(&contracts).expect("mkdir"); + std::fs::write( + lean.join("ProvableContracts.lean"), + "import ProvableContracts.Theorems.Gelu.Bound\n", + ) + .expect("w"); + std::fs::write( + lean.join("ProvableContracts/Theorems/Gelu/Bound.lean"), + "namespace ProvableContracts.Gelu\ntheorem gelu_bound : True := trivial\nend ProvableContracts.Gelu\n", + ) + .expect("w"); + std::fs::write( + contracts.join("gelu-v1.yaml"), + "equations:\n e:\n lean_theorem: Theorems.Gelu\n", + ) + .expect("w"); + (d, lean, contracts) + } + + fn gen(lean: &Path, contracts: &Path, check: bool) -> Res { + run(DischargeAction::GenAxioms { + lean_dir: lean.into(), + contracts: contracts.into(), + check, + }) + } + + fn check(lean: &Path, contracts: &Path, strict: bool) -> Res { + run(DischargeAction::Check { + lean_dir: lean.into(), + contracts: contracts.into(), + no_lake: true, + strict, + validate_formalization: false, + leanchecker: false, + leanchecker_timeout: 3600, + leanchecker_ulimit_v: None, + leanchecker_threads: LEANCHECKER_THREADS, + leanchecker_memory_max_gib: LEANCHECKER_MEMORY_MAX_GIB, + leanchecker_cpu_quota_pct: LEANCHECKER_CPU_QUOTA_PCT, + leanchecker_unscoped: false, + comparator: false, + lake_timeout: LAKE_TIMEOUT_S, + }) + } + + fn is_reject(r: &Res) -> bool { + r.as_ref() + .err() + .is_some_and(|e| e.downcast_ref::().is_some()) + } + + fn is_decline(r: &Res) -> bool { + r.as_ref() + .err() + .is_some_and(|e| e.downcast_ref::().is_some()) + } + + #[test] + fn the_verdict_words_and_exit_codes_are_pvl_1s() { + let d: Box = Box::new(DischargeDeclined("x".into())); + let r: Box = Box::new(DischargeRejected("y".into())); + assert_eq!( + (exit_code_for(d.as_ref()), verdict_for(d.as_ref())), + (2, "decline") + ); + assert_eq!( + (exit_code_for(r.as_ref()), verdict_for(r.as_ref())), + (1, "reject") + ); + assert_eq!( + (d.to_string(), r.to_string()), + ("x".to_string(), "y".to_string()) + ); + } + + #[test] + fn gen_axioms_writes_and_check_mode_judges_freshness() { + let (_d, lean, contracts) = tree(); + assert!( + is_reject(&gen(&lean, &contracts, true)), + "no Axioms.lean yet must reject" + ); + gen(&lean, &contracts, false).expect("write"); + let text = std::fs::read_to_string(lean.join(AXIOMS_FILE)).expect("written"); + assert!( + text.contains("`ProvableContracts.Gelu.gelu_bound"), + "{text}" + ); + gen(&lean, &contracts, true).expect("fresh"); + std::fs::write(lean.join(AXIOMS_FILE), format!("{text}-- edit\n")).expect("w"); + assert!(is_reject(&gen(&lean, &contracts, true))); + std::fs::remove_file(lean.join("ProvableContracts.lean")).expect("rm"); + assert!(is_decline(&gen(&lean, &contracts, false))); + } + + #[test] + fn check_accepts_rejects_and_declines() { + let (_d, lean, contracts) = tree(); + gen(&lean, &contracts, false).expect("write"); + check(&lean, &contracts, false).expect("clean tree"); + let f = lean.join("ProvableContracts/Theorems/Gelu/Bound.lean"); + let src = std::fs::read_to_string(&f).expect("r"); + std::fs::write( + &f, + src.replace( + "end ProvableContracts.Gelu", + "axiom m : False\nend ProvableContracts.Gelu", + ), + ) + .expect("w"); + assert!(is_reject(&check(&lean, &contracts, false))); + std::fs::write( + lean.join("escape-allowlist.yaml"), + "- file: ProvableContracts/Theorems/Gelu/Bound.lean\n decl: ProvableContracts.Gelu.m\n kind: axiom\n reason: r\n ticket: t\n confirmed_by: pending\n", + ) + .expect("w"); + gen(&lean, &contracts, false).expect("regen"); + check(&lean, &contracts, false).expect("pending is accepted"); + assert!( + is_reject(&check(&lean, &contracts, true)), + "--strict rejects pending" + ); + std::fs::write( + contracts.join("gelu-v1.yaml"), + "equations:\n e:\n lean_theorem: none\n", + ) + .expect("w"); + gen(&lean, &contracts, false).expect("regen"); + assert!( + is_decline(&check(&lean, &contracts, false)), + "zero roots declines" + ); + } + + #[test] + fn label_ratchet_writes_the_set() { + let (_d, lean, contracts) = tree(); + run(DischargeAction::LabelRatchet { + lean_dir: lean.clone(), + contracts, + }) + .expect("seed"); + assert!(lean.join(discharge::LABELS).is_file()); + } + + /// A `lake` under a 60 s bound: long enough for any fake, short enough that a hang fails the test. + fn lk(bin: &str) -> Lake<'_> { + Lake { bin, timeout_s: 60 } + } + + /// A fake `lake`: exits `rc` after printing `out`. + fn fake_lake(dir: &Path, rc: i32, out: &str) -> String { + let p = dir.join(format!("lake-{rc}")); + std::fs::write(&p, format!("#!/bin/sh\necho '{out}'\nexit {rc}\n")).expect("w"); + let mut perm = std::fs::metadata(&p).expect("meta").permissions(); + std::os::unix::fs::PermissionsExt::set_mode(&mut perm, 0o755); + std::fs::set_permissions(&p, perm).expect("chmod"); + p.to_string_lossy().into_owned() + } + + #[test] + fn the_lean_elaboration_decides_the_verdict() { + let (d, lean, _) = tree(); + let ok = fake_lake(d.path(), 0, "fine"); + let bad = fake_lake(d.path(), 1, "Axioms.lean:3:0: error: AXIOMS x"); + finish_with(lk(&ok), Report::default(), &lean, false, false, None).expect("lake ok"); + assert!( + is_reject(&finish_with( + lk(&bad), + Report::default(), + &lean, + false, + false, + None + )), + "a failing elaboration rejects" + ); + assert!( + is_decline(&finish_with( + lk("/nonexistent/lake"), + Report::default(), + &lean, + false, + false, + None + )), + "no lake declines" + ); + finish_with(lk(&bad), Report::default(), &lean, true, false, None) + .expect("--no-lake skips it"); + let rejected = Report { + reject: true, + ..Report::default() + }; + assert!( + is_reject(&finish_with(lk(&ok), rejected, &lean, false, false, None)), + "a prior failure is not cleared by lake" + ); + } + + /// A `lake` for `--leanchecker`: `lake env printenv LEAN_SYSROOT` names `/sysroot` (with `bin/leanchecker` + /// when `has_checker`), `lake env lean …` passes, and `lake env leanchecker …` prints `out` and exits `rc`. + fn checker_lake(dir: &Path, has_checker: bool, rc: i32, out: &str) -> String { + let root = dir.join(format!("sysroot-{has_checker}")); + std::fs::create_dir_all(root.join("bin")).expect("mkdir"); + if has_checker { + std::fs::write(root.join("bin").join("leanchecker"), "").expect("w"); + } + let p = dir.join(format!("checker-lake-{has_checker}-{rc}")); + let script = format!( + "#!/bin/sh\ncase \"$2\" in\n printenv) echo '{}' ;;\n lean) exit 0 ;;\n leanchecker) echo '{out}'; exit {rc} ;;\n *) exit 99 ;;\nesac\n", + root.display() + ); + std::fs::write(&p, script).expect("w"); + let mut perm = std::fs::metadata(&p).expect("meta").permissions(); + std::os::unix::fs::PermissionsExt::set_mode(&mut perm, 0o755); + std::fs::set_permissions(&p, perm).expect("chmod"); + p.to_string_lossy().into_owned() + } + + const LC: Option = Some(Leanchecker { + timeout_s: 60, + ulimit_v_kib: None, + threads: 8, + scope: None, + }); + + /// PVL-001 EV-6b: leanchecker's rc decides; an ABSENT leanchecker declines and is never read as a failure. + #[test] + fn leanchecker_rc_rejects_and_an_absent_checker_declines() { + let (d, lean, _) = tree(); + let pass = checker_lake(d.path(), true, 0, "ok"); + let fail = checker_lake( + d.path(), + true, + 1, + "error: kernel rejected Theorems.Gelu.bound", + ); + let absent = checker_lake(d.path(), false, 0, "never reached"); + finish_with(lk(&pass), Report::default(), &lean, false, false, LC) + .expect("leanchecker rc 0 accepts"); + assert!( + is_reject(&finish_with( + lk(&fail), + Report::default(), + &lean, + false, + false, + LC + )), + "leanchecker rc 1 rejects" + ); + assert!( + is_decline(&finish_with( + lk(&absent), + Report::default(), + &lean, + false, + false, + LC + )), + "no leanchecker in the toolchain declines" + ); + assert!( + is_decline(&finish_with( + lk("/nonexistent/lake"), + Report::default(), + &lean, + true, + false, + LC + )), + "no lake declines under --leanchecker" + ); + finish_with(lk(&fail), Report::default(), &lean, false, false, None) + .expect("without --leanchecker it never runs"); + } + + #[test] + fn a_timed_out_leanchecker_rejects_and_the_ulimit_reaches_the_checker() { + let (d, lean, _) = tree(); + let slow = checker_lake(d.path(), true, 0, "x"); + let body = std::fs::read_to_string(&slow) + .expect("r") + .replace("echo 'x'; exit 0", "sleep 30"); + std::fs::write(&slow, body).expect("w"); + let t1 = Some(Leanchecker { + timeout_s: 1, + ulimit_v_kib: None, + threads: 8, + scope: None, + }); + assert!( + is_reject(&finish_with( + lk(&slow), + Report::default(), + &lean, + true, + false, + t1 + )), + "a timeout rejects" + ); + // The limit reaches the checker: a stub that prints its own `ulimit -v` and fails shows it in the reject. + let shows = checker_lake(d.path(), true, 1, "x"); + let body = std::fs::read_to_string(&shows) + .expect("r") + .replace("echo 'x'", "echo \"vlimit=$(ulimit -v)\""); + std::fs::write(&shows, body).expect("w"); + let mut r = Report::default(); + recheck( + lk(&shows), + &lean, + Leanchecker { + timeout_s: 60, + ulimit_v_kib: Some(4_194_304), + threads: 8, + scope: None, + }, + &mut r, + ); + assert!(r.reject, "{:?}", r.lines); + assert!( + r.lines.iter().any(|l| l.contains("vlimit=4194304")), + "{:?}", + r.lines + ); + } + + /// #4348: the thread cap reaches the checker -- a stub that prints its own `LEAN_NUM_THREADS` and fails shows it. + #[test] + fn the_thread_cap_reaches_the_checker() { + let (d, lean, _) = tree(); + let shows = checker_lake(d.path(), true, 1, "x"); + let body = std::fs::read_to_string(&shows) + .expect("r") + .replace("echo 'x'", "echo \"threads=$LEAN_NUM_THREADS\""); + std::fs::write(&shows, body).expect("w"); + let mut r = Report::default(); + let lc = Leanchecker { + threads: 3, + ..LC.expect("LC") + }; + recheck(lk(&shows), &lean, lc, &mut r); + assert!(r.reject, "{:?}", r.lines); + assert!( + r.lines.iter().any(|l| l.contains("threads=3")), + "{:?}", + r.lines + ); + } + + /// #4348: scoped, the arm is `systemd-run --user --scope` with the memory and CPU caps around the same `sh -c`; + /// unscoped it is the bare `sh -c`. + #[test] + fn the_scope_wraps_the_checker_with_both_caps() { + let scoped = recheck_argv( + "lake", + Leanchecker { + scope: Some(Scope { + memory_max_gib: 24, + cpu_quota_pct: 800, + }), + ..LC.expect("LC") + }, + ); + assert_eq!( + scoped[..10], + [ + "systemd-run", + "--user", + "--scope", + "--slice=agent.slice", + "-q", + "-p", + "MemoryMax=24G", + "-p", + "CPUQuota=800%", + "--" + ] + ); + let bare = recheck_argv("lake", LC.expect("LC")); + assert_eq!(bare[..2], ["sh", "-c"]); + assert_eq!(scoped[10..], bare[..]); + } + + /// #4348: the DEFAULT `--leanchecker` is capped -- 8 threads inside a 24G/800% scope -- on `check` and `run` + /// alike; only an explicit `--leanchecker-unscoped` drops the scope. + #[test] + fn the_default_leanchecker_is_capped() { + #[derive(clap::Parser)] + struct T { + #[command(subcommand)] + a: crate::cli::DischargeAction, + } + let parse = |args: &[&str]| { + ::try_parse_from(std::iter::once("t").chain(args.iter().copied())) + .expect("parses") + .a + }; + for a in [ + parse(&["check", "L", "--leanchecker"]), + parse(&["run", "L"]), + ] { + let (t, g, q, u) = match a { + DischargeAction::Check { + leanchecker_threads: t, + leanchecker_memory_max_gib: g, + leanchecker_cpu_quota_pct: q, + leanchecker_unscoped: u, + .. + } + | DischargeAction::Run { + leanchecker_threads: t, + leanchecker_memory_max_gib: g, + leanchecker_cpu_quota_pct: q, + leanchecker_unscoped: u, + .. + } => (t, g, q, u), + _ => unreachable!(), + }; + let lc = Leanchecker::from_flags(3600, None, t, g, q, u); + assert_eq!( + (lc.threads, lc.scope), + ( + 8, + Some(Scope { + memory_max_gib: 24, + cpu_quota_pct: 800 + }) + ) + ); + } + match parse(&["check", "L", "--leanchecker", "--leanchecker-unscoped"]) { + DischargeAction::Check { + leanchecker_unscoped, + .. + } => assert!(leanchecker_unscoped), + _ => unreachable!(), + } + } + + /// The defaults are also the ceilings: a caller may lower a cap, never raise it (51 threads took 58-67 GB). + #[test] + fn a_leanchecker_cap_can_be_lowered_never_raised() { + #[derive(clap::Parser)] + struct T { + #[command(subcommand)] + a: crate::cli::DischargeAction, + } + let parses = |args: &[&str]| { + ::try_parse_from(std::iter::once("t").chain(args.iter().copied())) + .is_ok() + }; + for (flag, max) in [ + ("--leanchecker-threads", LEANCHECKER_THREADS), + ("--leanchecker-memory-max-gib", LEANCHECKER_MEMORY_MAX_GIB), + ("--leanchecker-cpu-quota-pct", LEANCHECKER_CPU_QUOTA_PCT), + ] { + for base in [&["check", "L", "--leanchecker"][..], &["run", "L"][..]] { + let with = |v: u32| { + let v = v.to_string(); + let mut a = base.to_vec(); + a.extend([flag, v.as_str()]); + parses(&a) + }; + assert!( + with(1) && with(max), + "{flag} {base:?}: 1 and {max} must parse" + ); + assert!( + !with(max + 1), + "{flag} {base:?}: {} must be refused", + max + 1 + ); + assert!(!with(0), "{flag} {base:?}: 0 must be refused"); + } + } + } + + /// EV-8a reads the raw exits: `Some(n)` for a step that ran (124 on a timeout), `None` for one that never ran. + #[test] + fn the_lean_steps_record_their_raw_exits_and_none_when_they_did_not_run() { + let (d, lean, _) = tree(); + let exits = |lake: &str, no_lake: bool, lc: Option| { + let mut r = Report::default(); + lean_steps(lk(lake), &mut r, &lean, no_lake, false, lc); + (r.lake_exit, r.leanchecker_exit) + }; + let pass = checker_lake(d.path(), true, 0, "ok"); + assert_eq!(exits(&pass, false, LC), (Some(0), Some(0))); + assert_eq!( + exits(&pass, false, None), + (Some(0), None), + "no --leanchecker" + ); + assert_eq!(exits(&pass, true, None), (None, None), "--no-lake"); + let fail = checker_lake(d.path(), true, 3, "kernel error"); + assert_eq!(exits(&fail, true, LC), (None, Some(3))); + let absent = checker_lake(d.path(), false, 0, "never reached"); + assert_eq!( + exits(&absent, true, LC), + (None, None), + "absent checker never ran" + ); + let slow = checker_lake(d.path(), true, 0, "x"); + let body = std::fs::read_to_string(&slow) + .expect("r") + .replace("echo 'x'; exit 0", "sleep 30"); + std::fs::write(&slow, body).expect("w"); + let t1 = Some(Leanchecker { + timeout_s: 1, + ulimit_v_kib: None, + threads: 8, + scope: None, + }); + assert_eq!(exits(&slow, true, t1), (None, Some(124)), "timeout"); + } + + /// A passing `Comparator.lean --self-test`: the three FIPS vector lines, rc 0 (#4238). + const SELF_TEST_OK: &str = "if [ \"$5\" = --self-test ]; then printf 'ok sha256 \"\" = e3\\nok sha256 \"abc\" = ba\\nok sha256 \"abcdbcde\" = 24\\n'; exit 0; fi"; + + /// The root EV-7a's `render` declares for the fixture's one solution. The cross-check (#4240) counts + /// these lines, so an empty Challenge file would make every fixture row an UNEXPECTED-ROW. + const GELU_CHALLENGE: &str = + "theorem _root_.PvlChallenge.ProvableContracts.Gelu.gelu_bound : True := by\n sorry\n"; + + /// A `lake` for `--comparator`: `env lean --run …` writes `rows` to stdout, `stderr` to stderr, and exits + /// `rc`; every other `env lean` passes. The tree gets `Challenge/gelu-v1.lean` and the comparator script. + fn comparator_lake(dir: &Path, lean: &Path, rc: i32, rows: &str, stderr: &str) -> String { + std::fs::create_dir_all(lean.join(CHALLENGE_DIR)).expect("mkdir"); + // The Challenge file declares exactly the roots the canned rows name (#4240 cross-checks the two); + // output that is not rows declares nothing, so those cases still judge the output alone. + let decls: String = comparator::parse_rows(rows) + .map(|rs| { + rs.iter() + .map(|r| { + format!( + "theorem _root_.PvlChallenge.{} : True := by\n sorry\n", + r.name + ) + }) + .collect() + }) + .unwrap_or_default(); + std::fs::write(lean.join(CHALLENGE_DIR).join("gelu-v1.lean"), decls).expect("w"); + std::fs::create_dir_all(lean.join("scripts")).expect("mkdir"); + std::fs::write(lean.join(COMPARATOR), "").expect("w"); + std::fs::write(dir.join(format!("rows-{rc}")), rows).expect("w"); + let p = dir.join(format!("cmp-lake-{rc}")); + let script = format!( + "#!/bin/sh\n{SELF_TEST_OK}\nif [ \"$3\" = --run ]; then\n [ \"$4 $5\" = \"{COMPARATOR} {CHALLENGE_DIR}/gelu-v1.lean\" ] || {{ echo \"bad args: $*\" >&2; exit 98; }}\n cat '{}'; echo '{stderr}' >&2; exit {rc}\nfi\nexit 0\n", + dir.join(format!("rows-{rc}")).display() + ); + std::fs::write(&p, script).expect("w"); + let mut perm = std::fs::metadata(&p).expect("meta").permissions(); + std::os::unix::fs::PermissionsExt::set_mode(&mut perm, 0o755); + std::fs::set_permissions(&p, perm).expect("chmod"); + p.to_string_lossy().into_owned() + } + + fn cmp_row(name: &str, ch: &str, sol: &str, axioms: &str) -> String { + format!( + "{{\"name\": \"{name}\", \"challenge_type_hash\": \"{ch}\", \"solution_type_hash\": {sol}, \"axioms\": {axioms}}}\n" + ) + } + + fn compared(lake: &str, lean: &Path) -> Report { + let mut r = Report::default(); + lean_steps(lk(lake), &mut r, lean, false, true, None); + r + } + + /// PVL-001 EV-7b: a matching, sorry-free solution closes its challenge; the closure is recorded for EV-8a. + #[test] + fn the_comparator_closes_a_matching_challenge() { + let (d, lean, _) = tree(); + let h = "ab".repeat(32); + let lake = comparator_lake( + d.path(), + &lean, + 0, + &cmp_row( + "ProvableContracts.Gelu.gelu_bound", + &h, + &format!("\"{h}\""), + "[]", + ), + "", + ); + let r = compared(&lake, &lean); + assert!(!r.reject && r.decline.is_none(), "{:?}", r.lines); + assert_eq!( + r.challenges, + Some(comparator::Closure { + closed: 1, + total: 1 + }) + ); + assert!( + r.lines + .iter() + .any(|l| l == "COMPARATOR 1/1 challenge(s) closed"), + "{:?}", + r.lines + ); + finish_with(lk(&lake), Report::default(), &lean, false, true, None).expect("accepts"); + } + + /// The spec's RED: a solution of a WEAKER statement is a mismatch, and a sorry'd one closes nothing. + #[test] + fn a_mismatched_or_sorry_solution_rejects() { + let (d, lean, _) = tree(); + let (c, s) = ("ab".repeat(32), "cd".repeat(32)); + let rows = format!( + "{}{}", + cmp_row("A.weak", &c, &format!("\"{s}\""), "[]"), + cmp_row("A.sorried", &c, &format!("\"{c}\""), "[\"sorryAx\"]") + ); + let lake = comparator_lake(d.path(), &lean, 0, &rows, ""); + let r = compared(&lake, &lean); + assert!(r.reject, "{:?}", r.lines); + assert!(r + .lines + .iter() + .any(|l| l.starts_with("FAIL MISMATCH A.weak"))); + assert!(r + .lines + .iter() + .any(|l| l.starts_with("FAIL SORRY A.sorried"))); + assert_eq!(r.challenges.map(|c| (c.closed, c.total)), Some((0, 2))); + } + + #[test] + fn a_challenge_that_does_not_elaborate_rejects_with_its_errors() { + let (d, lean, _) = tree(); + let lake = comparator_lake( + d.path(), + &lean, + 1, + "", + "Challenge/gelu-v1.lean:5:0: error: unknown g", + ); + let r = compared(&lake, &lean); + assert!(r.reject, "{:?}", r.lines); + assert!( + r.lines.iter().any(|l| l.contains("exited 1")), + "{:?}", + r.lines + ); + assert!( + r.lines.iter().any(|l| l.contains("unknown g")), + "{:?}", + r.lines + ); + assert_eq!(r.challenges, None, "rows withheld: nothing was judged"); + } + + #[test] + fn unreadable_comparator_output_rejects() { + let (d, lean, _) = tree(); + let lake = comparator_lake(d.path(), &lean, 0, "not a row\n", ""); + let r = compared(&lake, &lean); + assert!(r.reject, "{:?}", r.lines); + assert!( + r.lines.iter().any(|l| l.contains("line 1")), + "{:?}", + r.lines + ); + } + + /// Zero measured is a decline, never a pass: no Challenge file, no script, zero rows, no lake. + #[test] + fn the_comparator_declines_when_nothing_was_compared() { + let (d, lean, _) = tree(); + let lake = comparator_lake(d.path(), &lean, 0, "", ""); + let r = compared(&lake, &lean); + assert!(!r.reject, "{:?}", r.lines); + assert!( + r.decline + .as_deref() + .is_some_and(|w| w.contains("0 challenge rows")), + "{r:?}" + ); + assert_eq!(r.challenges.map(|c| c.total), Some(0)); + + std::fs::remove_file(lean.join(COMPARATOR)).expect("rm"); + let r = compared(&lake, &lean); + assert!( + r.decline + .as_deref() + .is_some_and(|w| w.contains("does not exist")), + "{r:?}" + ); + + std::fs::remove_dir_all(lean.join(CHALLENGE_DIR)).expect("rm"); + let r = compared(&lake, &lean); + assert!( + r.decline + .as_deref() + .is_some_and(|w| w.contains("no Challenge/*.lean")), + "{r:?}" + ); + assert!(is_decline(&finish_with( + lk(&lake), + Report::default(), + &lean, + false, + true, + None + ))); + + let (d2, lean2, _) = tree(); + comparator_lake(d2.path(), &lean2, 0, "", ""); + let mut r = Report::default(); + lean_steps(lk("/nonexistent/lake"), &mut r, &lean2, true, true, None); + assert!( + r.decline + .as_deref() + .is_some_and(|w| w.contains("comparator did not run")), + "{r:?}" + ); + } + + #[test] + fn without_the_flag_the_comparator_never_runs() { + let (d, lean, _) = tree(); + let lake = comparator_lake(d.path(), &lean, 1, "", "would reject"); + let mut r = Report::default(); + lean_steps(lk(&lake), &mut r, &lean, false, false, None); + assert!(!r.reject && r.challenges.is_none(), "{:?}", r.lines); + } + + /// PVL-001 EV-8a: a `lake` for `run` — every `env lean` passes, `--run` prints one closing comparator row, + /// `printenv` names a sysroot with a leanchecker, and `env leanchecker` exits `lc_rc`. + fn run_lake(dir: &Path, lean: &Path, lc_rc: i32) -> String { + std::fs::create_dir_all(lean.join(CHALLENGE_DIR)).expect("mkdir"); + std::fs::write( + lean.join(CHALLENGE_DIR).join("gelu-v1.lean"), + GELU_CHALLENGE, + ) + .expect("w"); + std::fs::create_dir_all(lean.join("scripts")).expect("mkdir"); + std::fs::write(lean.join(COMPARATOR), "").expect("w"); + let root = dir.join("run-sysroot"); + std::fs::create_dir_all(root.join("bin")).expect("mkdir"); + std::fs::write(root.join("bin").join("leanchecker"), "").expect("w"); + let h = "ab".repeat(32); + let rows = dir.join("run-rows"); + std::fs::write( + &rows, + cmp_row( + "ProvableContracts.Gelu.gelu_bound", + &h, + &format!("\"{h}\""), + "[]", + ), + ) + .expect("w"); + let p = dir.join(format!("run-lake-{lc_rc}")); + let script = format!( + "#!/bin/sh\n{SELF_TEST_OK}\ncase \"$2 $3\" in\n 'lean --run') cat '{}' ;;\n printenv*) echo '{}' ;;\n leanchecker*) exit {lc_rc} ;;\nesac\nexit 0\n", + rows.display(), + root.display() + ); + std::fs::write(&p, script).expect("w"); + let mut perm = std::fs::metadata(&p).expect("meta").permissions(); + std::os::unix::fs::PermissionsExt::set_mode(&mut perm, 0o755); + std::fs::set_permissions(&p, perm).expect("chmod"); + p.to_string_lossy().into_owned() + } + + fn build_sh(dir: &Path, rc: i32) -> String { + let p = dir.join(format!("build-{rc}.sh")); + std::fs::write(&p, format!("echo building\nexit {rc}\n")).expect("w"); + p.to_string_lossy().into_owned() + } + + fn summary_of(lean: &Path) -> summary::Summary { + summary::load(&summary::summary_path(lean)).expect("a summary was written") + } + + fn lc() -> Leanchecker { + LC.expect("LC") + } + + /// The accept: every step green → a green summary deriving the tree's theorem, and the untracked log beside it. + #[test] + fn a_green_run_writes_a_green_summary_and_the_log() { + let (d, lean, contracts) = tree(); + gen(&lean, &contracts, false).expect("gen-axioms"); + let lake = run_lake(d.path(), &lean, 0); + let r = run_all(lk(&lake), &build_sh(d.path(), 0), &lean, &contracts, lc()); + let s = summary_of(&lean); + assert!(r.is_ok(), "{r:?} {s:?}"); + assert!(s.is_green(), "{s:?}"); + assert_eq!(s.challenges_closed.as_deref(), Some("1/1")); + assert!(s.derived().contains("ProvableContracts.Gelu.gelu_bound")); + assert!(s.tree_sha.is_none(), "a tempdir is no git checkout"); + let log: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(lean.join(LOG_FILE)).expect("log")) + .expect("json"); + assert_eq!(log["verdict"], "accept"); + assert_eq!(log["summary"]["leanchecker_exit"], 0); + } + + /// The spec's mutation: a summary hand-edited toward pass differs from its regeneration, byte for byte. + #[test] + fn a_regeneration_reproduces_the_summary_and_catches_a_hand_edit() { + let (d, lean, contracts) = tree(); + gen(&lean, &contracts, false).expect("gen-axioms"); + let lake = run_lake(d.path(), &lean, 1); + let build = build_sh(d.path(), 0); + assert!(is_reject(&run_all( + lk(&lake), + &build, + &lean, + &contracts, + lc() + ))); + let spath = summary::summary_path(&lean); + let first = std::fs::read_to_string(&spath).expect("r"); + assert!(first.contains("\"leanchecker_exit\": 1"), "{first}"); + let edited = first.replace("\"leanchecker_exit\": 1", "\"leanchecker_exit\": 0"); + std::fs::write(&spath, &edited).expect("w"); + assert!(summary_of(&lean).is_green(), "the edit alone would derive"); + assert!(is_reject(&run_all( + lk(&lake), + &build, + &lean, + &contracts, + lc() + ))); + let again = std::fs::read_to_string(&spath).expect("r"); + assert_eq!( + again, first, + "the regeneration is byte-identical to the honest run" + ); + assert_ne!( + again, edited, + "and so differs from the edit: git diff --exit-code fails" + ); + } + + /// A tree that did not build runs no Lean step, and still leaves a summary saying so. + #[test] + fn a_failed_build_rejects_a_cache_miss_declines_and_both_leave_a_red_summary() { + let (d, lean, contracts) = tree(); + gen(&lean, &contracts, false).expect("gen-axioms"); + let lake = run_lake(d.path(), &lean, 0); + for (rc, want_reject) in [(1, true), (2, false)] { + let r = run_all(lk(&lake), &build_sh(d.path(), rc), &lean, &contracts, lc()); + assert_eq!(is_reject(&r), want_reject, "build rc {rc}: {r:?}"); + assert_eq!(is_decline(&r), !want_reject, "build rc {rc}: {r:?}"); + let s = summary_of(&lean); + assert_eq!(s.build_exit, Some(rc)); + assert_eq!( + (s.lake_exit, s.leanchecker_exit), + (None, None), + "no Lean step ran" + ); + assert!(!s.is_green() && s.derived().is_empty()); + assert!(lean.join(LOG_FILE).is_file()); + } + } + + /// A summary that cannot be written is the run's output missing: reject. + #[test] + fn an_unwritable_summary_rejects() { + let (d, lean, contracts) = tree(); + gen(&lean, &contracts, false).expect("gen-axioms"); + std::fs::create_dir_all(summary::summary_path(&lean)).expect("a dir where the file goes"); + let lake = run_lake(d.path(), &lean, 0); + assert!(is_reject(&run_all( + lk(&lake), + &build_sh(d.path(), 0), + &lean, + &contracts, + lc() + ))); + // The log is written after the summary, so it carries the failed write and the reject (EV-8a quorum). + let log = std::fs::read_to_string(lean.join(LOG_FILE)).expect("the log is still written"); + assert!(log.contains("\"verdict\": \"reject\""), "{log}"); + assert!(log.contains("cannot write"), "{log}"); + } + + /// A `lake` whose `env ` call runs `body` (every other call passes); `$PIDF` is a file for a pid. + fn hang_lake(dir: &Path, step: &str, body: &str) -> (String, PathBuf) { + let pidf = dir.join(format!("grandchild-{step}")); + let p = dir.join(format!("hang-lake-{step}")); + let script = format!( + "#!/bin/sh\nPIDF='{}'\nif [ \"$2\" = {step} ]; then\n {body}\nfi\nexit 0\n", + pidf.display() + ); + std::fs::write(&p, script).expect("w"); + let mut perm = std::fs::metadata(&p).expect("meta").permissions(); + std::os::unix::fs::PermissionsExt::set_mode(&mut perm, 0o755); + std::fs::set_permissions(&p, perm).expect("chmod"); + (p.to_string_lossy().into_owned(), pidf) + } + + /// Whether `pid` is gone (or a zombie awaiting its reaper), polled for up to 5 s. + fn gone(pid: &str) -> bool { + (0..250).any(|_| { + let alive = std::fs::read_to_string(format!("/proc/{pid}/stat")).is_ok_and(|st| { + st.rsplit(')') + .next() + .is_some_and(|t| !t.trim_start().starts_with('Z')) + }); + if alive { + std::thread::sleep(Duration::from_millis(20)); + } + !alive + }) + } + + /// #4239: a hung `lake env lean Axioms.lean` rejects with a named reason inside its bound, records 124, and + /// takes the `lean` under it (a grandchild in its process group) down with it. + #[test] + fn a_hung_elaboration_rejects_within_its_bound_and_kills_its_process_group() { + let (d, lean, _) = tree(); + let (lake, pidf) = hang_lake(d.path(), "lean", "sleep 60 & echo $! > \"$PIDF\"; wait"); + let mut r = Report::default(); + let t0 = Instant::now(); + lean_steps( + Lake { + bin: &lake, + timeout_s: 1, + }, + &mut r, + &lean, + false, + false, + None, + ); + assert!( + t0.elapsed() < Duration::from_secs(20), + "the bound held: {:?}", + t0.elapsed() + ); + assert!( + r.reject && r.decline.is_none(), + "a hang is RED, not a decline: {:?}", + r.lines + ); + assert_eq!(r.lake_exit, Some(TIMED_OUT)); + assert!( + r.lines + .iter() + .any(|l| l.contains("lake env lean Axioms.lean timed out after 1s")), + "{:?}", + r.lines + ); + let pid = std::fs::read_to_string(&pidf).expect("the fake recorded its grandchild"); + assert!( + gone(pid.trim()), + "grandchild {} outlived the kill", + pid.trim() + ); + } + + /// #4239: the comparator and the leanchecker arm's `printenv LEAN_SYSROOT` are bounded too, and a child that + /// exits while a grandchild holds its stdout cannot stall the drain past the bound. + #[test] + fn every_lake_call_is_bounded_and_a_held_pipe_cannot_stall_it() { + let (d, lean, _) = tree(); + std::fs::create_dir_all(lean.join(CHALLENGE_DIR)).expect("mkdir"); + std::fs::write( + lean.join(CHALLENGE_DIR).join("gelu-v1.lean"), + GELU_CHALLENGE, + ) + .expect("w"); + std::fs::create_dir_all(lean.join("scripts")).expect("mkdir"); + std::fs::write(lean.join(COMPARATOR), "").expect("w"); + let bounded = |lake: &str, cmp: bool, lc: Option| { + let mut r = Report::default(); + let t0 = Instant::now(); + lean_steps( + Lake { + bin: lake, + timeout_s: 1, + }, + &mut r, + &lean, + lc.is_some(), + cmp, + lc, + ); + assert!(t0.elapsed() < Duration::from_secs(20), "{:?}", t0.elapsed()); + r + }; + // `env lean --run …`: $2 is `lean` for elaboration too, so hang only on `--run`. + let (cmp, _) = hang_lake( + d.path(), + "lean", + &format!("{SELF_TEST_OK}; [ \"$3\" = --run ] && sleep 60"), + ); + let r = bounded(&cmp, true, None); + assert!(r.reject && r.decline.is_none(), "{:?}", r.lines); + assert!( + r.lines + .iter() + .any(|l| l.contains("file(s)) timed out after 1s")), + "{:?}", + r.lines + ); + let (sys, _) = hang_lake(d.path(), "printenv", "sleep 60"); + let r = bounded(&sys, false, LC); + assert!(r.reject && r.decline.is_none(), "{:?}", r.lines); + assert!( + r.lines + .iter() + .any(|l| l.contains("printenv LEAN_SYSROOT timed out")), + "{:?}", + r.lines + ); + assert_eq!(r.leanchecker_exit, None, "leanchecker never ran"); + // `lake` exits 0 at once, but a background child keeps stdout open: still bounded, still RED. + let (held, pidf) = hang_lake(d.path(), "lean", "sleep 60 & echo $! > \"$PIDF\"; exit 0"); + let r = bounded(&held, false, None); + assert!(r.reject, "{:?}", r.lines); + let pid = std::fs::read_to_string(&pidf).expect("pid"); + assert!( + gone(pid.trim()), + "pipe holder {} outlived the kill", + pid.trim() + ); + } + + /// #4238: the comparator's sha256 is checked against the FIPS vectors before any row is judged. A failing + /// self-test, a hung one, or one that exits 0 having checked fewer than three vectors rejects, and the rows + /// never run; a passing one is recorded first. + #[test] + fn the_comparator_self_test_gates_the_rows() { + let (d, lean, _) = tree(); + let h = "ab".repeat(32); + let lake = comparator_lake( + d.path(), + &lean, + 0, + &cmp_row( + "ProvableContracts.Gelu.gelu_bound", + &h, + &format!("\"{h}\""), + "[]", + ), + "", + ); + let r = compared(&lake, &lean); + let st = r + .lines + .iter() + .position(|l| l.contains("--self-test (3 FIPS vectors)")); + let rows = r.lines.iter().position(|l| l.contains("file(s))")); + assert!(st.is_some() && st < rows, "self-test first: {:?}", r.lines); + let body = std::fs::read_to_string(&lake).expect("r"); + for (name, stub) in [ + ("fails", "printf 'FAIL sha256 \\\"abc\\\" = 00\\n'; exit 1"), + ( + "two-vectors", + "printf 'ok sha256 a\\nok sha256 b\\n'; exit 0", + ), + ("silent", "exit 0"), + ("hangs", "sleep 60"), + ] { + let p = d.path().join(format!("self-test-{name}")); + std::fs::write( + &p, + body.replace( + SELF_TEST_OK, + &format!("if [ \"$5\" = --self-test ]; then {stub}; fi"), + ), + ) + .expect("w"); + let mut perm = std::fs::metadata(&p).expect("meta").permissions(); + std::os::unix::fs::PermissionsExt::set_mode(&mut perm, 0o755); + std::fs::set_permissions(&p, perm).expect("chmod"); + let mut r = Report::default(); + lean_steps( + Lake { + bin: &p.to_string_lossy(), + timeout_s: 2, + }, + &mut r, + &lean, + false, + true, + None, + ); + assert!(r.reject && r.decline.is_none(), "{name}: {:?}", r.lines); + assert!(r.challenges.is_none(), "{name}: rows judged: {:?}", r.lines); + assert!( + r.lines + .iter() + .any(|l| l.starts_with("FAIL") && l.contains("--self-test")), + "{name}: {:?}", + r.lines + ); + } + } +} diff --git a/crates/aprender-contracts-cli/src/commands/lint.rs b/crates/aprender-contracts-cli/src/commands/lint.rs index 0f590b07a1..525e2ef6cf 100644 --- a/crates/aprender-contracts-cli/src/commands/lint.rs +++ b/crates/aprender-contracts-cli/src/commands/lint.rs @@ -48,7 +48,7 @@ pub fn run( watch: bool, strict_test_binding: bool, armed_baseline_ref: Option<&str>, - gate: Option<&str>, + gate: &[String], shapes_opts: ShapesOptions, ) -> Result<(), Box> { refuse_missing_corpus(contract_dir)?; @@ -57,8 +57,10 @@ pub fn run( // is asked for, because run_single_gate would otherwise report every ref // missing on exactly the input the refusal exists for. refuse_single_file_strict_binding(contract_dir, strict_test_binding)?; - if let Some(name) = gate { - return run_single_gate(contract_dir, name, &shapes_opts); + match gate { + [] => {} + [name] => return run_single_gate(contract_dir, name, &shapes_opts), + names => return run_gates(contract_dir, names, &shapes_opts), } if watch { return run_watch( @@ -237,6 +239,19 @@ fn run_single_gate( }; println!("{}", serde_json::to_string_pretty(&report)?); + // ONT-4e: `refines` says WHICH clause broke Liskov on the reject line, and which prose clause left it Unknown. + if result.name == provable_contracts::lint::refines_gate::GATE { + let lines = provable_contracts::lint::refines_gate::explain(&result, &findings); + if result.verdict == provable_contracts::ontology::verdict::Verdict::Fail + && !lines.is_empty() + { + return Err(crate::contract_walk::GateRejected(lines.join("; ")).into()); + } + for line in &lines { + eprintln!("{line}"); + } + } + // The exit is the gate's VERDICT, not its `passed` bit: a gate that ran and answered `Unknown{Warn}` (ONT-4b, warnings // and no violation) is a decline, exit 2 — `passed` alone would print 0 for a corpus nobody judged clean. match result.verdict { @@ -252,6 +267,47 @@ fn run_single_gate( } } +/// PVL-001 EV-11: `--gate a --gate b` runs every named gate, each printing its own report as `--gate a` alone +/// would, and exits with their MEET: any refusal (exit 3) over any reject (1) over any decline (2) over pass (0). +/// A name this build does not compute is refused before any gate runs, so a typo never reports a partial pass. +fn run_gates( + contract_dir: &Path, + names: &[String], + shapes_opts: &ShapesOptions, +) -> Result<(), Box> { + use provable_contracts::lint::NAMED_GATES; + if let Some(bad) = names.iter().find(|n| !NAMED_GATES.contains(&n.as_str())) { + return Err(crate::contract_walk::UnknownGate { + asked: bad.clone(), + known: NAMED_GATES.iter().map(|g| (*g).to_string()).collect(), + } + .into()); + } + let outcomes: Vec<_> = names + .iter() + .map(|n| run_single_gate(contract_dir, n, shapes_opts)) + .collect(); + let rank = |r: &Result<(), Box>| match r { + Ok(()) => 0, + Err(e) if e.is::() => 1, + Err(e) if e.is::() => 2, + Err(_) => 3, + }; + let passed = outcomes.iter().filter(|r| rank(r) == 0).count(); + let worst = outcomes + .into_iter() + .max_by_key(|r| rank(r)) + .unwrap_or(Ok(())); + match worst { + Err(e) if e.is::() => Err(LintRejected { + passed, + armed: names.len(), + } + .into()), + other => other, + } +} + /// One gate's run, mapped to a report or to the refusal/decline that stands in its place. Every non-verdict /// answer says WHY on stderr before it returns, because an exit code without a reason is the thing this gate /// exists to refuse. @@ -266,6 +322,7 @@ fn decide_named_gate( shapes_opts: &ShapesOptions, ) -> Result> { use provable_contracts::lint::{ + evidence_gate::EvidenceOutcome, ratchet_gates::RatchetOutcome, relations_gate::RelationsOutcome, sigma_gate::SigmaOutcome, valid_under_gate::ValidUnderOutcome, NamedGateOutcome, NAMED_GATES, }; @@ -293,6 +350,25 @@ fn decide_named_gate( Err(crate::contract_walk::SigmaMalformed(e.to_string()).into()) } NamedGateOutcome::Shapes(outcome) => decide_shapes_gate(outcome), + NamedGateOutcome::Consistency(outcome) => decide_consistency_gate(outcome), + NamedGateOutcome::Refines(outcome) => decide_refines_gate(outcome), + NamedGateOutcome::Tbox(outcome) => decide_tbox_gate(outcome), + NamedGateOutcome::Evidence(EvidenceOutcome::NoSigma) => Err(LintDeclined { + reason: provable_contracts::ontology::verdict::Reason::NoCheckable, + } + .into()), + NamedGateOutcome::Evidence(EvidenceOutcome::NoEvidence { contracts_checked }) => { + eprintln!( + "evidence: no evidence block in {contracts_checked} contract(s) — nothing was measured" + ); + Err(LintDeclined { + reason: provable_contracts::ontology::verdict::Reason::NoCheckable, + } + .into()) + } + NamedGateOutcome::Evidence(EvidenceOutcome::Malformed(e)) => { + Err(crate::contract_walk::SigmaMalformed(e.to_string()).into()) + } NamedGateOutcome::ValidUnder(ValidUnderOutcome::NoSigma) => Err(LintDeclined { reason: provable_contracts::ontology::verdict::Reason::NoCheckable, } @@ -309,13 +385,103 @@ fn decide_named_gate( NamedGateOutcome::ValidUnder(ValidUnderOutcome::Malformed(e)) => { Err(crate::contract_walk::SigmaMalformed(e.to_string()).into()) } + NamedGateOutcome::Ratchet(RatchetOutcome::Declined(why)) => { + eprintln!("{name}: {why}"); + Err(LintDeclined { + reason: provable_contracts::ontology::verdict::Reason::NoCheckable, + } + .into()) + } NamedGateOutcome::Sigma(SigmaOutcome::Ran { result, findings }) + | NamedGateOutcome::Ratchet(RatchetOutcome::Ran { result, findings }) | NamedGateOutcome::Relations(RelationsOutcome::Ran { result, findings }) | NamedGateOutcome::ValidUnder(ValidUnderOutcome::Ran { result, findings }) + | NamedGateOutcome::Evidence(EvidenceOutcome::Ran { result, findings }) | NamedGateOutcome::Ran { result, findings } => Ok((result, findings)), } } +/// The `ont-consistency` gate's answers (ONT-5). Only `Ran` is a verdict; every decline prints WHY first — a stale +/// witness names the command that regenerates it. +fn decide_consistency_gate( + outcome: provable_contracts::lint::consistency_gate::ConsistencyOutcome, +) -> Result> { + use provable_contracts::lint::consistency_gate::{decline_reason, why, ConsistencyOutcome}; + + match outcome { + ConsistencyOutcome::Ran { result, findings } => Ok((result, findings)), + ConsistencyOutcome::Malformed(e) => { + Err(crate::contract_walk::SigmaMalformed(e.to_string()).into()) + } + other => { + eprintln!("ont-consistency: {}", why(&other)); + let reason = decline_reason(&other) + .unwrap_or(provable_contracts::ontology::verdict::Reason::NoCheckable); + Err(LintDeclined { reason }.into()) + } + } +} + +/// The `refines` gate's answers (ONT-4e). Only `Ran` is a verdict; every decline prints WHY first — a stale Liskov +/// witness names `make contracts`. +fn decide_refines_gate( + outcome: provable_contracts::lint::refines_gate::RefinesOutcome, +) -> Result> { + use provable_contracts::lint::refines_gate::{decline_reason, why, RefinesOutcome, GATE}; + + match outcome { + RefinesOutcome::Ran { result, findings } => Ok((result, findings)), + RefinesOutcome::Malformed(e) => { + Err(crate::contract_walk::SigmaMalformed(e.to_string()).into()) + } + other => { + eprintln!("{GATE}: {}", why(&other)); + let reason = decline_reason(&other) + .unwrap_or(provable_contracts::ontology::verdict::Reason::NoCheckable); + Err(LintDeclined { reason }.into()) + } + } +} + +/// The `tbox` gate's answers (ONT-2c). There is no verdict arm: a clean classification is `Unknown{Advisory}` +/// (R-7, no inferred fact arms a merge); everything else is the declaration's fault (exit 3). +fn decide_tbox_gate( + outcome: provable_contracts::lint::tbox_gate::TboxOutcome, +) -> Result> { + use provable_contracts::lint::tbox_gate::TboxOutcome; + use provable_contracts::ontology::verdict::Reason; + + match outcome { + TboxOutcome::NoSigma => Err(LintDeclined { + reason: Reason::NoCheckable, + } + .into()), + TboxOutcome::Malformed(e) | TboxOutcome::Stale(e) => { + Err(crate::contract_walk::SigmaMalformed(e).into()) + } + TboxOutcome::PreconditionFailed(refused) => { + Err(crate::contract_walk::SigmaMalformed(format!( + "told-closure precondition fails, so no classification is claimed: {}", + refused.join("; ") + )) + .into()) + } + TboxOutcome::Advisory(report) => { + eprintln!( + "tbox: {} classes, consistent={}, unintended_subsumptions={} (method {}, advisory; never arms)", + report.classes, + report.consistent, + report.unintended_subsumptions.len(), + report.method + ); + Err(LintDeclined { + reason: Reason::Advisory, + } + .into()) + } + } +} + /// The `shapes` gate's answers (ONT-4b, ONT-4c1, ONT-4b2). Only `Ran` is a verdict about the corpus; every /// other arm prints what could not be checked before it returns its decline or refusal. fn decide_shapes_gate( diff --git a/crates/aprender-contracts-cli/src/commands/mod.rs b/crates/aprender-contracts-cli/src/commands/mod.rs index c9734eccc5..0d2d4a9e82 100644 --- a/crates/aprender-contracts-cli/src/commands/mod.rs +++ b/crates/aprender-contracts-cli/src/commands/mod.rs @@ -2,11 +2,13 @@ pub mod audit; pub mod book; pub mod census; pub mod certify; +pub mod challenge; pub mod check_parity; pub mod codegen; pub mod coq; pub mod coverage; pub mod diff; +pub mod discharge; pub mod equations; pub mod explain; pub mod extract; @@ -24,6 +26,8 @@ pub mod lean_status; pub mod lint; pub mod migrate; pub mod mirai; +pub mod obligations; +pub mod ontology; pub mod pipeline; pub mod probar; pub mod proof_status; diff --git a/crates/aprender-contracts-cli/src/commands/obligations.rs b/crates/aprender-contracts-cli/src/commands/obligations.rs new file mode 100644 index 0000000000..afcbbb912e --- /dev/null +++ b/crates/aprender-contracts-cli/src/commands/obligations.rs @@ -0,0 +1,444 @@ +//! `pv obligations [ROOT] [--gate]` — PVL-001 EV-10 (paiml/aprender#4198). +//! +//! A native replacement for pmat's `scripts/pv-obligation-gate.py` (sha256 `e4fa9719…`, pmat +//! `7c93535e`), which pmat's `Makefile:227` and `quality-gate.yml:246` run as a blocking gate. +//! Every contract must be readable by pv, and its obligations must bind to code. Three checks, +//! run per contract in the script's order, over `ROOT/contracts/*.yaml` (not recursive; a name +//! ending in `binding.yaml` is skipped) with functions searched under `ROOT/src/`: +//! +//! 1. `pv validate` passes — here in-process ([`validate_artifact`], the same decision +//! `pv validate ` makes: any error-severity violation, or no verdict at all, fails). +//! 2. No test-bearing entry (a mapping with a `test` key) hides under `falsification:`, the key +//! pv does not read. Entries without `test` are alert thresholds and are left alone. +//! 3. Every `applies_to` other than `all`, a falsy value, or an equation of the contract names a +//! `fn` under `src/`; where the contract declares `metadata.proved_type`, at least one file +//! defining that `fn` mentions the type as a word. +//! +//! Output is the script's, byte for byte: one `::error::` line per problem, then +//! `pv obligation gate: N problem(s) over M contracts`. Without `--gate` the exit is 0; with it, +//! any problem is `reject:` at exit 1. `tests/pvl_obligations_golden.rs` holds the two against +//! outputs recorded from the script. +//! +//! **Where it deliberately differs from the script** (each is an input the script does not +//! survive, or PVL-1): +//! - zero contracts is `decline:` at exit 2 (PVL-1), where the script reports 0 over 0 at exit 0; +//! - an input the script dies on with a Python traceback is a named problem here: a file that +//! cannot be read or is not YAML; a truthy document that is not a mapping; a truthy +//! `equations`/`metadata` that is not a mapping, or `proof_obligations`/`falsification` that +//! is not a list (a `falsification` mapping or string counts 0, as the script's loop does); +//! a `proof_obligations` entry that is not a mapping; a truthy `applies_to` that is not a +//! string; a truthy `proved_type` that is not a string, where a bound `fn` would be searched +//! for it; +//! - YAML is read by `serde_yaml` (YAML 1.2), the script's by PyYAML (YAML 1.1): a plain +//! `yes`/`no`/`on`/`off` is a string here and a boolean there; +//! - a document `serde_yaml` cannot read that PyYAML can (a duplicate key, an integer beyond +//! 64 bits, …) gets no checks 2 and 3 here. `pv validate` reads with the same parser, so +//! check 1 fails on it on both sides, and pv adds no parse line of its own: the report is +//! the script's unless PyYAML also finds a check-2 or check-3 problem in it, which pv then +//! does not name (the verdict still agrees); +//! - `src/` is walked on disk (symlinks not followed), where the script's `git grep` searches +//! tracked files only: an untracked file under `src/` counts here; +//! - a word character is `char::is_alphanumeric` or `_`; next to non-ASCII text this can differ +//! from Python's `re` and from `git grep`'s locale. +//! +//! **Where it deliberately agrees** (each held by `tests/fixtures/pvl/obligations/edge/`): merge +//! keys (`<<`) are resolved as PyYAML resolves them, and falsy values follow Python truthiness — +//! a falsy document is `{}`, a falsy `applies_to` is skipped, a falsy `proved_type` is none. +//! +//! Line-by-line mapping (script → here): `CONTRACTS` → [`contract_files`]; `_fn_files` → +//! [`SrcTree::fn_files`]; `check_validate` → [`check_validate`]; `check_visible` → +//! [`check_visible`]; `check_bindings` → [`check_bindings`]; `main` → [`run`]; `{x!r}` → +//! [`py_repr`]; `{files}` → [`py_list`]; `re` `\b` → [`boundary`]. + +use std::path::{Path, PathBuf}; + +use provable_contracts::error::Severity; +use provable_contracts::schema::validate_artifact; +use serde_yaml::{Mapping, Value}; + +use crate::contract_walk::{ObligationsRejected, ZeroContracts}; + +/// Run the three checks over `root`. With `gate`, any problem is an error (exit 1). +/// +/// # Errors +/// [`ZeroContracts`] when `root/contracts` holds no contract; [`ObligationsRejected`] under +/// `gate` when any problem was found. +pub fn run(root: &Path, gate: bool) -> Result<(), Box> { + let contracts = contract_files(root); + if contracts.is_empty() { + return Err(ZeroContracts { + path: root.join("contracts"), + filter: None, + } + .into()); + } + let mut src = SrcTree::new(root.join("src")); + let mut problems = Vec::new(); + for rel in &contracts { + problems.extend(check_contract(root, rel, &mut src)); + } + for p in &problems { + println!("::error::{p}"); + } + println!( + "pv obligation gate: {} problem(s) over {} contracts", + problems.len(), + contracts.len() + ); + if gate && !problems.is_empty() { + return Err(ObligationsRejected { + problems: problems.len(), + contracts: contracts.len(), + } + .into()); + } + Ok(()) +} + +/// `contracts/*.yaml` under `root`, relative and `/`-joined, sorted, `*binding.yaml` excluded. +/// Hidden names are excluded as `glob`'s `*` excludes them. Like `glob`, the name alone decides: +/// a symlink is followed when read, and an entry that cannot be read is a named problem. +fn contract_files(root: &Path) -> Vec { + let Ok(entries) = std::fs::read_dir(root.join("contracts")) else { + return Vec::new(); + }; + let mut out: Vec = entries + .filter_map(Result::ok) + .map(|e| e.file_name().to_string_lossy().into_owned()) + .filter(|n| !n.starts_with('.') && n.ends_with(".yaml") && !n.ends_with("binding.yaml")) + .map(|n| format!("contracts/{n}")) + .collect(); + out.sort(); + out +} + +fn check_contract(root: &Path, rel: &str, src: &mut SrcTree) -> Vec { + let mut problems = check_validate(root, rel); + let doc = match load(&root.join(rel)) { + Ok(doc) => doc, + // A document `serde_yaml` refuses also fails `pv validate`, the script's only + // report on it when PyYAML finds nothing else; a second line would miscount it. + Err(Load::Parse(_)) if !problems.is_empty() => return problems, + Err(Load::Parse(why) | Load::Other(why)) => { + problems.push(format!("{rel}: {why}")); + return problems; + } + }; + problems.extend(check_visible(rel, &doc)); + problems.extend(check_bindings(rel, &doc, src)); + problems +} + +/// Why a contract could not be loaded. `Parse` is `serde_yaml` refusing the text. +enum Load { + Parse(String), + Other(String), +} + +/// `yaml.safe_load(...) or {}`: merge keys (`<<`) resolved as PyYAML resolves them, and a +/// falsy document (empty, `false`, `0`, `[]`) is an empty mapping. +fn load(path: &Path) -> Result { + let text = + std::fs::read_to_string(path).map_err(|e| Load::Other(format!("cannot be read: {e}")))?; + let mut doc: Value = serde_yaml::from_str(&text) + .map_err(|e| Load::Parse(format!("does not parse as YAML: {e}")))?; + // One pass resolves one level; a merged mapping that itself merges needs another. + loop { + let before = doc.clone(); + doc.apply_merge() + .map_err(|e| Load::Other(format!("has a merge key PyYAML refuses: {e}")))?; + if doc == before { + break; + } + } + match doc { + Value::Mapping(m) => Ok(m), + v if !truthy(&v) => Ok(Mapping::new()), + _ => Err(Load::Other("is not a YAML mapping".into())), + } +} + +/// Python's `bool(x)` over a YAML value — what the script's `x or {}`, `if not target` and +/// `if proved` decide on. +fn truthy(v: &Value) -> bool { + match v { + Value::Null => false, + Value::Bool(b) => *b, + Value::Number(n) => n.as_f64().is_none_or(|f| f != 0.0), + Value::String(s) => !s.is_empty(), + Value::Sequence(s) => !s.is_empty(), + Value::Mapping(m) => !m.is_empty(), + Value::Tagged(_) => true, + } +} + +/// `m.get(key) or `: `None` when the key is absent or its value is falsy. +fn get_truthy<'a>(m: &'a Mapping, key: &str) -> Option<&'a Value> { + m.get(key).filter(|v| truthy(v)) +} + +fn check_validate(root: &Path, rel: &str) -> Vec { + let passes = validate_artifact(&root.join(rel)) + .is_ok_and(|(_, v)| v.iter().all(|v| v.severity != Severity::Error)); + if passes { + Vec::new() + } else { + vec![format!("{rel}: pv validate failed")] + } +} + +fn check_visible(rel: &str, doc: &Mapping) -> Vec { + let hidden = match get_truthy(doc, "falsification") { + None => 0, + Some(Value::Sequence(entries)) => entries + .iter() + .filter(|e| e.as_mapping().is_some_and(|m| m.contains_key("test"))) + .count(), + // Iterating a dict or a str yields keys or characters, never a dict. + Some(Value::Mapping(_) | Value::String(_)) => 0, + Some(_) => return vec![format!("{rel}: falsification is not a list")], + }; + if hidden == 0 { + return Vec::new(); + } + vec![format!( + "{rel}: {hidden} test-bearing obligation(s) under `falsification:`, \ + which pv cannot read — use `falsification_tests:`" + )] +} + +/// `metadata.proved_type` as the script reads it: `(doc.get("metadata") or {}).get(...)`, then +/// `if proved`. A truthy non-string is only an error where the script would `re.escape` it. +#[derive(Clone, Copy)] +enum Proved<'a> { + Absent, + Type(&'a str), + NotAString, +} + +fn check_bindings(rel: &str, doc: &Mapping, src: &mut SrcTree) -> Vec { + let (equations, proved, obligations) = match bindings_of(doc) { + Ok(parts) => parts, + Err(why) => return vec![format!("{rel}: {why}")], + }; + let mut problems = Vec::new(); + for ob in obligations { + let Some(ob) = ob.as_mapping() else { + problems.push(format!("{rel}: a proof_obligations entry is not a mapping")); + continue; + }; + // `if not target or target == "all" or target in equations: continue` + let Some(target) = get_truthy(ob, "applies_to") else { + continue; + }; + if equations.contains(&target) { + continue; + } + let Some(target) = target.as_str() else { + problems.push(format!("{rel}: an applies_to is not a string")); + continue; + }; + if target != "all" { + problems.extend(check_target(rel, target, proved, src)); + } + } + problems +} + +/// The equation names, the proved type and the obligations, each read as the script reads +/// them; a shape the script would die on is an error naming it. +fn bindings_of(doc: &Mapping) -> Result<(Vec<&Value>, Proved<'_>, &[Value]), String> { + let equations = match get_truthy(doc, "equations") { + None => Vec::new(), + Some(Value::Mapping(m)) => m.keys().collect(), + Some(_) => return Err("equations is not a mapping".into()), + }; + let proved = match get_truthy(doc, "metadata") { + None => Proved::Absent, + Some(Value::Mapping(m)) => match get_truthy(m, "proved_type") { + None => Proved::Absent, + Some(Value::String(s)) => Proved::Type(s), + Some(_) => Proved::NotAString, + }, + Some(_) => return Err("metadata is not a mapping".into()), + }; + let obligations = match get_truthy(doc, "proof_obligations") { + None => &[][..], + Some(Value::Sequence(obs)) => obs.as_slice(), + Some(_) => return Err("proof_obligations is not a list".into()), + }; + Ok((equations, proved, obligations)) +} + +fn check_target(rel: &str, target: &str, proved: Proved<'_>, src: &mut SrcTree) -> Option { + let files = src.fn_files(target); + if files.is_empty() { + return Some(format!( + "{rel}: applies_to {} names neither an equation of this contract nor any `fn` under src/", + py_repr(target) + )); + } + let proved = match proved { + Proved::Absent => return None, + Proved::Type(t) => t, + Proved::NotAString => { + return Some(format!( + "{rel}: applies_to {} is proved against a metadata.proved_type that is not a string", + py_repr(target) + )) + } + }; + if files.iter().any(|f| src.mentions(f, proved)) { + return None; + } + Some(format!( + "{rel}: applies_to {} is proved against {}, but none of {} mentions it — the proof does \ + not reach the code it names", + py_repr(target), + py_repr(proved), + py_list(&files) + )) +} + +/// Every file under `src/`, read once, lossily, on first use. Keys are `src/…`, `/`-joined, +/// in byte order (`git grep -l`'s order). +struct SrcTree { + dir: PathBuf, + files: Option>, +} + +impl SrcTree { + fn new(dir: PathBuf) -> Self { + Self { dir, files: None } + } + + fn loaded(&mut self) -> &[(String, String)] { + let dir = &self.dir; + self.files.get_or_insert_with(|| { + let mut paths = Vec::new(); + walk(dir, &mut paths); + let mut files: Vec<(String, String)> = paths + .into_iter() + .filter_map(|p| { + let bytes = std::fs::read(&p).ok()?; + let rel = p.strip_prefix(dir).ok()?; + let key = std::iter::once("src".to_owned()) + .chain( + rel.components() + .map(|c| c.as_os_str().to_string_lossy().into_owned()), + ) + .collect::>() + .join("/"); + Some((key, String::from_utf8_lossy(&bytes).into_owned())) + }) + .collect(); + files.sort(); + files + }) + } + + /// `git grep -l -E "fn \b" -- src/`. + fn fn_files(&mut self, name: &str) -> Vec { + let needle = format!("fn {name}"); + self.loaded() + .iter() + .filter(|(_, text)| { + text.match_indices(&needle) + .any(|(i, m)| boundary(&text[..i + m.len()], &text[i + m.len()..])) + }) + .map(|(k, _)| k.clone()) + .collect() + } + + /// `re.search(rf"\b{re.escape(word)}\b", text)`. + fn mentions(&mut self, key: &str, word: &str) -> bool { + self.loaded().iter().any(|(k, text)| { + k == key + && text.match_indices(word).any(|(i, m)| { + boundary(&text[..i], &text[i..]) + && boundary(&text[..i + m.len()], &text[i + m.len()..]) + }) + }) + } +} + +fn walk(dir: &Path, out: &mut Vec) { + let Ok(entries) = std::fs::read_dir(dir) else { + return; + }; + for entry in entries.filter_map(Result::ok) { + let Ok(kind) = entry.file_type() else { + continue; + }; + if kind.is_dir() { + walk(&entry.path(), out); + } else if kind.is_file() { + out.push(entry.path()); + } + } +} + +/// A regex `\b` between `before` and `after`: exactly one side is a word character. +fn boundary(before: &str, after: &str) -> bool { + let word = |c: Option| c.is_some_and(|c| c.is_alphanumeric() || c == '_'); + word(before.chars().next_back()) != word(after.chars().next()) +} + +/// Python's `repr` of a `str`: single quotes unless the text holds `'` and no `"`. +fn py_repr(s: &str) -> String { + let quote = if s.contains('\'') && !s.contains('"') { + '"' + } else { + '\'' + }; + let mut out = String::from(quote); + for c in s.chars() { + match c { + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + c if c == quote => { + out.push('\\'); + out.push(c); + } + c => out.push(c), + } + } + out.push(quote); + out +} + +/// Python's `repr` of a `list[str]`. +fn py_list(items: &[String]) -> String { + let inner: Vec = items.iter().map(|s| py_repr(s)).collect(); + format!("[{}]", inner.join(", ")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn repr_matches_python() { + assert_eq!(py_repr("from_score"), "'from_score'"); + assert_eq!(py_repr("it's"), "\"it's\""); + assert_eq!(py_repr("a'b\"c"), "'a\\'b\"c'"); + assert_eq!(py_repr("a\\b"), "'a\\\\b'"); + assert_eq!( + py_list(&["src/a.rs".into(), "src/b.rs".into()]), + "['src/a.rs', 'src/b.rs']" + ); + assert_eq!(py_list(&[]), "[]"); + } + + #[test] + fn boundary_is_regex_backslash_b() { + assert!(boundary("fn score", "(x)")); + assert!(boundary("fn score", "")); + assert!(!boundary("fn score", "_v2")); + assert!(!boundary("fn score", "2")); + assert!(boundary(" ", "Grade")); + assert!(!boundary("Tdg", "Grade")); + } +} diff --git a/crates/aprender-contracts-cli/src/commands/ontology.rs b/crates/aprender-contracts-cli/src/commands/ontology.rs new file mode 100644 index 0000000000..471556b112 --- /dev/null +++ b/crates/aprender-contracts-cli/src/commands/ontology.rs @@ -0,0 +1,81 @@ +//! `pv ontology export --owl` / `pv ontology tbox` — ONT-001 §3.8, row ONT-2c. +//! +//! `export --owl` writes Σ as OWL 2 functional syntax with the in-house writer, byte-deterministic, so CI can +//! `cmp` a fresh export against the tracked `contracts/ontology.ofn` (R-18). `tbox` writes the told-closure +//! classification, `contracts/tbox-report.json`, which is ADVISORY: the `tbox` lint gate maps it to +//! `Unknown{Advisory}` and it never arms (R-7: no inferred fact arms a merge). +//! +//! Exit codes follow `pv lint`: 3 `error:` when Σ is malformed, cannot be written as OWL, or the TBox +//! precondition fails. In that last case the report is still printed, so the refusal names its axiom. + +use std::path::Path; + +use provable_contracts::ontology::owl; +use provable_contracts::ontology::sigma::Sigma; + +use crate::cli::OntologyCommand; + +pub fn run(command: &OntologyCommand) -> Result<(), Box> { + match command { + OntologyCommand::Export { + sigma, + owl: as_owl, + write, + } => { + if !as_owl { + eprintln!("error: `pv ontology export` writes one format today; pass --owl"); + std::process::exit(2); + } + let export = load_export(sigma); + emit(sigma, "ontology.ofn", &owl::to_ofn(&export), *write) + } + OntologyCommand::Tbox { sigma, write } => { + let export = load_export(sigma); + let report = owl::tbox(&export); + emit( + sigma, + "tbox-report.json", + &owl::report_json(&report), + *write, + )?; + if !report.precondition.holds { + for r in &report.precondition.refused { + eprintln!("error: told-closure precondition fails: {r}"); + } + std::process::exit(3); + } + Ok(()) + } + } +} + +fn load_export(sigma_path: &Path) -> owl::OwlExport { + let fail = |msg: String| -> ! { + eprintln!("error: {msg}"); + std::process::exit(3); + }; + let text = std::fs::read_to_string(sigma_path) + .unwrap_or_else(|e| fail(format!("cannot read Σ {}: {e}", sigma_path.display()))); + let sigma = Sigma::from_yaml(&text).unwrap_or_else(|e| fail(format!("Σ: {e}"))); + if let Err(e) = sigma.check_integrity() { + fail(format!("Σ: {e}")); + } + owl::export(&sigma).unwrap_or_else(|e| fail(e.to_string())) +} + +fn emit( + sigma_path: &Path, + name: &str, + body: &str, + write: bool, +) -> Result<(), Box> { + if write { + let dir = sigma_path.parent().unwrap_or_else(|| Path::new(".")); + let out = dir.join(name); + std::fs::write(&out, body)?; + eprintln!("wrote {}", out.display()); + } else { + print!("{body}"); + } + Ok(()) +} diff --git a/crates/aprender-contracts-cli/src/commands/proof_status.rs b/crates/aprender-contracts-cli/src/commands/proof_status.rs index f3fa582ffc..edf8671823 100644 --- a/crates/aprender-contracts-cli/src/commands/proof_status.rs +++ b/crates/aprender-contracts-cli/src/commands/proof_status.rs @@ -1,6 +1,6 @@ use std::path::Path; -use provable_contracts::binding::parse_binding; +use provable_contracts::binding::{parse_binding, BindingRegistry, ImplStatus}; use provable_contracts::obligation_matrix::{format_obligation_table, obligation_matrix}; use provable_contracts::proof_status::{format_text, proof_status_report}; use provable_contracts::schema::ContractKind; @@ -15,15 +15,20 @@ pub fn run( table: bool, kind_filter: Option<&str>, ) -> Result<(), Box> { - let binding = match binding_path { - // L5 gate: when --verify-bindings is set, downgrade any `implemented` - // binding whose function is absent from source, so L5 requires bindings - // that are VERIFIED as implemented rather than merely self-declared. - Some(bp) => Some(match verify_root { - Some(root) => parse_binding(bp)?.verified(root), - None => parse_binding(bp)?, - }), - None => None, + // PVL-001 EV-2 (PVL-2): a binding is RESOLVED, always. `--binding` used to count + // entries without resolving them (a binding naming a function that exists + // nowhere printed its report and exited 0), and `--verify-bindings` downgraded + // silently. Now every `implemented` binding is looked up with the + // `pv verify-bindings` resolver; a ghost is downgraded, listed under + // `GHOST BINDINGS (n)`, and the command exits 1. `--verify-bindings` is a no-op + // alias, kept so existing invocations still parse. + let _ = verify_root; + let (binding, ghosts) = match binding_path { + Some(bp) => { + let (reg, ghosts) = resolve_bindings(bp, parse_binding(bp)?); + (Some(reg), ghosts) + } + None => (None, Vec::new()), }; let kind = kind_filter.map(parse_kind).transpose()?; @@ -61,7 +66,72 @@ pub fn run( print!("{}", format_obligation_table(&matrices)); } - Ok(()) + if ghosts.is_empty() { + return Ok(()); + } + // Text mode prints the block on stdout with the report; JSON mode keeps stdout a + // single JSON document and prints the block on stderr. + let block = ghost_block(&ghosts); + if format == "json" { + eprint!("{block}"); + } else { + print!("{block}"); + } + Err(format!( + "{} ghost binding(s): claimed implemented, not found in source", + ghosts.len() + ) + .into()) +} + +/// One binding that claims `implemented` for a function the resolver cannot find. +struct Ghost { + contract: String, + equation: String, + function: String, +} + +/// Resolve every `implemented` binding against source with the `pv verify-bindings` +/// resolver (`scan_all_sources`: the binding's derived source root, its `crates/`, +/// and the local `src/`). A ghost is downgraded to `not_implemented` so the report's +/// levels are honest, and returned so the caller can name it and reject. +fn resolve_bindings(binding_path: &Path, reg: BindingRegistry) -> (BindingRegistry, Vec) { + use crate::commands::verify_bindings::{scan_all_sources, short_name}; + let found = scan_all_sources(binding_path, ®.target_crate); + let mut ghosts = Vec::new(); + let bindings = reg + .bindings + .into_iter() + .map(|mut b| { + let unresolved = b.status == ImplStatus::Implemented + && b.function + .as_deref() + .and_then(short_name) + .is_some_and(|s| !found.contains(&s)); + if unresolved { + ghosts.push(Ghost { + contract: b.contract.clone(), + equation: b.equation.clone(), + function: b.function.clone().unwrap_or_default(), + }); + b.status = ImplStatus::NotImplemented; + } + b + }) + .collect(); + (BindingRegistry { bindings, ..reg }, ghosts) +} + +/// `GHOST BINDINGS (n)`, then one line per ghost: the line PVL-001 EV-2's probe reads. +fn ghost_block(ghosts: &[Ghost]) -> String { + let mut out = format!("\nGHOST BINDINGS ({})\n", ghosts.len()); + for g in ghosts { + out.push_str(&format!( + " {} {}: {}\n", + g.contract, g.equation, g.function + )); + } + out } fn print_kind_breakdown(contracts: &[(String, provable_contracts::schema::Contract)]) { diff --git a/crates/aprender-contracts-cli/src/commands/verify_bindings.rs b/crates/aprender-contracts-cli/src/commands/verify_bindings.rs index 60b76cb115..d55d12b4c5 100644 --- a/crates/aprender-contracts-cli/src/commands/verify_bindings.rs +++ b/crates/aprender-contracts-cli/src/commands/verify_bindings.rs @@ -50,21 +50,28 @@ fn parse_expected_functions(content: &str) -> HashSet { let Some(rest) = line.trim().strip_prefix("function:") else { continue; }; - let func = rest.trim().trim_matches('"').trim_matches('\'').trim(); - if func.is_empty() || func == "N/A" { - continue; - } - let short = func.rsplit("::").next().unwrap_or(func).to_lowercase(); - if !short.is_empty() { + if let Some(short) = short_name(rest) { expected.insert(short); } } expected } +/// The name a `function:` value is resolved by: its last `::` segment, lowercased. +/// `None` for an empty value or `N/A`. ONE normalization, shared with +/// `pv proof-status --binding` (PVL-001 EV-2), so the two commands cannot disagree. +pub(crate) fn short_name(function: &str) -> Option { + let func = function.trim().trim_matches('"').trim_matches('\'').trim(); + if func.is_empty() || func == "N/A" { + return None; + } + let short = func.rsplit("::").next().unwrap_or(func).to_lowercase(); + (!short.is_empty()).then_some(short) +} + /// Scan the crate's `src/`, `crates/`, and the current-dir `src/` (if different) /// for `fn` declarations. -fn scan_all_sources(binding_path: &Path, label: &str) -> HashSet { +pub(crate) fn scan_all_sources(binding_path: &Path, label: &str) -> HashSet { let src_dir = derive_src_root(binding_path, label); let mut found: HashSet = HashSet::new(); let src = src_dir.join("src"); @@ -82,16 +89,30 @@ fn scan_all_sources(binding_path: &Path, label: &str) -> HashSet { found } -/// binding.yaml lives in `contracts//` — source is `../..//`. -/// Falls back to `.` when the path has no usable parent chain. +/// Where a binding's source lives. +/// +/// The multi-repo layout (`contracts//binding.yaml`, source at `../..//`) +/// is used when that directory exists. In this monorepo it does not +/// (`contracts/aprender/binding.yaml` -> `./aprender/`, absent), and scanning an +/// absent root made nearly every binding a ghost: under PVL-001 EV-2's reject that +/// is a false reject, and one that depended on the caller's cwd. So otherwise the +/// root is the nearest ancestor of the binding file holding a `crates/` or `src/` +/// tree (the workspace root), and `.` only when there is none. fn derive_src_root(binding_path: &Path, label: &str) -> std::path::PathBuf { - let Some(parent) = binding_path.parent() else { - return Path::new(".").to_path_buf(); - }; - parent + let has_tree = |d: &Path| d.join("src").is_dir() || d.join("crates").is_dir(); + let legacy = binding_path .parent() - .and_then(|p| p.parent()) - .map_or_else(|| Path::new(".").to_path_buf(), |p| p.join(label)) + .and_then(Path::parent) + .and_then(Path::parent) + .map(|p| p.join(label)); + if let Some(l) = legacy.filter(|l| has_tree(l)) { + return l; + } + let abs = std::fs::canonicalize(binding_path).unwrap_or_else(|_| binding_path.to_path_buf()); + abs.ancestors() + .skip(1) + .find(|d| has_tree(d)) + .map_or_else(|| Path::new(".").to_path_buf(), Path::to_path_buf) } /// Sort the expected names missing from `found` for stable reporting. @@ -159,33 +180,118 @@ fn scan_fns(dir: &Path, found: &mut HashSet) { } } -/// Extract lowercased `fn`/`pub fn`/`pub async fn`/`pub(crate) fn` names from source. +/// Extract lowercased function names from source: every `fn` item, whatever its +/// visibility (`pub`, `pub(crate)`, `pub(super)`, `pub(in path)`) and qualifiers +/// (`const`, `async`, `unsafe`, `extern "ABI"`). PVL-001 EV-2: `pv proof-status` +/// now REJECTS on a ghost, so a real function the scanner cannot see is a false +/// reject. Measured on aprender's contracts/binding.yaml: `compute_mse` is +/// `pub(super) fn` (crates/aprender-core/src/tree/regression_helpers.rs:27) and was +/// reported a ghost by the old four-prefix scanner (scripts/dogfood.sh records the +/// same defect for rmedia's `apply_loudnorm`). fn extract_fn_names(content: &str, found: &mut HashSet) { for line in content.lines() { - let t = line.trim(); - if !(t.starts_with("pub fn ") - || t.starts_with("pub async fn ") - || t.starts_with("pub(crate) fn ") - || t.starts_with("fn ")) - { - continue; - } - let part = t - .trim_start_matches("pub async fn ") - .trim_start_matches("pub(crate) fn ") - .trim_start_matches("pub fn ") - .trim_start_matches("fn "); - let name = part - .split('(') - .next() - .unwrap_or("") - .split('<') - .next() - .unwrap_or("") - .trim() - .to_lowercase(); - if !name.is_empty() { + if let Some(name) = fn_item_name(line) { found.insert(name); } } } + +/// The lowercased name of the item (`fn`, `struct`, `enum`, `type`, `trait`) a source +/// line declares, if it declares one. +fn fn_item_name(line: &str) -> Option { + let mut t = line.trim_start(); + // visibility: `pub` or `pub(...)` + if let Some(rest) = t.strip_prefix("pub") { + let rest_trim = rest.trim_start(); + if let Some(inner) = rest_trim.strip_prefix('(') { + t = inner.split_once(')')?.1.trim_start(); + } else if rest.starts_with(char::is_whitespace) { + t = rest_trim; + } else { + return None; // `pubfoo`, `pub_x`: an identifier, not a visibility + } + } + // qualifiers, in any order the grammar allows them to appear + loop { + let before = t; + for q in ["const ", "async ", "unsafe ", "default "] { + if let Some(rest) = t.strip_prefix(q) { + t = rest.trim_start(); + } + } + if let Some(rest) = t.strip_prefix("extern ") { + let rest = rest.trim_start(); + t = match rest.strip_prefix('"') { + Some(abi) => abi.split_once('"')?.1.trim_start(), + None => rest, + }; + } + if t == before { + break; + } + } + // A binding may name a function or a type (setfit-apr-v1 binds + // `SetFitArtifactDoc` and `ClassifyResponse`, both `pub struct`): the resolver + // sees every item kind a binding can name. + let part = ["fn ", "struct ", "enum ", "type ", "trait "] + .iter() + .find_map(|kw| t.strip_prefix(kw))?; + let name = part + .split(|c: char| matches!(c, '(' | '<' | ';' | '{' | ':' | '=') || c.is_whitespace()) + .next() + .unwrap_or("") + .trim() + .to_lowercase(); + (!name.is_empty()).then_some(name) +} + +#[cfg(test)] +mod tests { + use super::fn_item_name; + + /// The resolver's case table: every declaration form a binding can name must be + /// seen (a miss is a false GHOST reject), and non-declarations must not be. + #[test] + fn fn_item_name_case_table() { + let must_match = [ + ("fn plain() {}", "plain"), + ("pub fn public(x: u8) -> u8 {", "public"), + ("pub(crate) fn in_crate() {", "in_crate"), + ( + "pub(super) fn compute_mse(y_left: &[f32], y_right: &[f32]) -> f32 {", + "compute_mse", + ), + ("pub(in crate::tree) fn scoped() {", "scoped"), + ("pub async fn serve() {", "serve"), + ("pub(crate) async fn load() {", "load"), + ("pub const fn size() -> usize {", "size"), + ("pub unsafe fn raw() {", "raw"), + ("pub const unsafe fn both() {", "both"), + ("pub extern \"C\" fn ffi() {", "ffi"), + (" fn indented(t: T) {", "indented"), + ("fn Mixed_Case() {", "mixed_case"), + ("pub struct SetFitArtifactDoc {", "setfitartifactdoc"), + ("pub struct ClassifyResponse {", "classifyresponse"), + ("pub(crate) enum Mode {", "mode"), + ("pub type Alias = Vec;", "alias"), + ("pub trait Estimator {", "estimator"), + ("pub unsafe trait Marker {}", "marker"), + ("pub struct Fn;", "fn"), + ]; + for (line, want) in must_match { + assert_eq!(fn_item_name(line).as_deref(), Some(want), "{line}"); + } + let must_not_match = [ + "// fn commented_out() {}", + "let f = fn_pointer;", + "pubfn not_a_decl() {}", + "impl Fn for X {}", + "call(fn_like);", + "let structure = 3;", + "// struct Commented {}", + ]; + for line in must_not_match { + assert_eq!(fn_item_name(line), None, "{line}"); + } + } +} diff --git a/crates/aprender-contracts-cli/src/contract_walk.rs b/crates/aprender-contracts-cli/src/contract_walk.rs index 45dff87517..e58b85dbb2 100644 --- a/crates/aprender-contracts-cli/src/contract_walk.rs +++ b/crates/aprender-contracts-cli/src/contract_walk.rs @@ -193,6 +193,39 @@ impl fmt::Display for LintRejected { impl std::error::Error for LintRejected {} +/// `pv obligations --gate` found problems (PVL-001 EV-10): measured, and failed. Exit 1. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ObligationsRejected { + /// Problems reported. + pub problems: usize, + /// Contracts checked. + pub contracts: usize, +} + +impl fmt::Display for ObligationsRejected { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "obligation gate failed ({} problem(s) over {} contracts)", + self.problems, self.contracts + ) + } +} + +impl std::error::Error for ObligationsRejected {} +/// One `--gate` run measured and failed, and says WHAT failed: the line after `reject:` is the gate's own findings +/// (ONT-4e: `reject: A refines B: precondition strengthened (PRE-1)`), not a gate count. Exit 1. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GateRejected(pub String); + +impl fmt::Display for GateRejected { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl std::error::Error for GateRejected {} + /// `pv lint --gate sigma` found Σ itself malformed (ONT-001 §5 ONT-2b): the DECLARATION is wrong, not the corpus, /// so it is `error:` at exit 3 and never `reject:`. #[derive(Debug, Clone, PartialEq, Eq)] @@ -246,7 +279,11 @@ pub const ARMED_GATES_SHRANK_EXIT: i32 = 3; /// empty corpus or a declined lint meet, [`ARMED_GATES_SHRANK_EXIT`] for a shrunk /// armed set, 1 for everything else (a parse failure and a rejected meet included). pub fn exit_code_for(err: &(dyn std::error::Error + 'static)) -> i32 { - if err.downcast_ref::().is_some() || err.downcast_ref::().is_some() + if err.downcast_ref::().is_some() + || err.downcast_ref::().is_some() + || err + .downcast_ref::() + .is_some() { ZERO_CONTRACTS_EXIT } else if err.downcast_ref::().is_some() @@ -267,11 +304,20 @@ pub fn exit_code_for(err: &(dyn std::error::Error + 'static)) -> i32 { /// ONT-1 asserts both halves of the line. #[must_use] pub fn verdict_for(err: &(dyn std::error::Error + 'static)) -> &'static str { - if err.downcast_ref::().is_some() || err.downcast_ref::().is_some() + if err.downcast_ref::().is_some() + || err.downcast_ref::().is_some() + || err + .downcast_ref::() + .is_some() { "decline" } else if err.downcast_ref::().is_some() || err.downcast_ref::().is_some() + || err.downcast_ref::().is_some() + || err.downcast_ref::().is_some() + || err + .downcast_ref::() + .is_some() { "reject" } else { diff --git a/crates/aprender-contracts-cli/src/lib.rs b/crates/aprender-contracts-cli/src/lib.rs index a1050b0411..4619966aea 100644 --- a/crates/aprender-contracts-cli/src/lib.rs +++ b/crates/aprender-contracts-cli/src/lib.rs @@ -114,6 +114,8 @@ pub fn dispatch(command: Commands) -> Result<(), Box> { contract, binding, .. } => commands::audit::run(&contract, binding.as_deref()), Commands::Diff { old, new } => commands::diff::run(&old, &new), + Commands::Discharge { action } => commands::discharge::run(action), + Commands::Challenge { action } => commands::challenge::run(action), Commands::Census { contract_dir, format, @@ -133,6 +135,7 @@ pub fn dispatch(command: Commands) -> Result<(), Box> { .map_err(crate::contract_walk::ReleaseArgsRefused)?; commands::extract_rdf::run(&contract_dir, check, subject.as_ref(), out.as_deref()) } + Commands::Ontology { command } => commands::ontology::run(&command), Commands::Coverage { contract_dir, binding, @@ -244,8 +247,12 @@ pub fn dispatch(command: Commands) -> Result<(), Box> { watch, strict_test_binding, armed_baseline_ref.as_deref(), - gate.as_deref(), - commands::lint::shapes_options(gate.as_deref(), shape, &release)?, + &gate, + commands::lint::shapes_options( + gate.iter().any(|g| g == "shapes").then_some("shapes"), + shape, + &release, + )?, ) } Commands::Score { @@ -326,6 +333,7 @@ pub fn dispatch(command: Commands) -> Result<(), Box> { contract_dir, top, } => commands::infer::run(&crate_dir, &binding, &contract_dir, top), + Commands::Obligations { root, gate } => commands::obligations::run(&root, gate), Commands::Unlock { contract, reason } => commands::unlock::run(&contract, &reason), Commands::Roofline { contract_dir, diff --git a/crates/aprender-contracts-cli/tests/ev11_lint_ratchets.rs b/crates/aprender-contracts-cli/tests/ev11_lint_ratchets.rs new file mode 100644 index 0000000000..6d76110cf4 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ev11_lint_ratchets.rs @@ -0,0 +1,219 @@ +//! PVL-001 EV-11 (PMAT-4166) — `pv lint --gate theorem-pairing --gate depends-on-present`: two shrink-only +//! ratchets read from `contracts/lint-baseline.json`, which the gates never write. +//! +//! The row's probe, verbatim (paiml/infra PVL-001 @00553b0b): `json_object contracts/lint-baseline.json && jq -e +//! '(.unpaired_theorem_modules|numbers) and (.contracts_without_depends_on|numbers) and (.command|strings)' +//! contracts/lint-baseline.json && "$PV" lint contracts/ --gate theorem-pairing --gate depends-on-present`, and the +//! accept adds `git diff --exit-code contracts/lint-baseline.json` after it. The first test is that probe on the +//! real corpus; the rest run on a throwaway repo built in a tempdir, because the gates read the repo ROOT +//! (`lean/`, `book/`) as well as the contract dir. +//! +//! | case | expected | +//! |---|---| +//! | real corpus, both gates | exit 0, both Pass, the baseline byte-identical after | +//! | tempdir at the baseline | exit 0 | +//! | the spec's mutation: add an unpaired Theorem module | exit 1, PV-RAT-001 — the meet rejects | +//! | a kernel contract with no `depends_on` above the baseline | exit 1, PV-RAT-002 | +//! | no baseline key | exit 2, `Unknown(Report)` with the count printed — reported, never a pass | +//! | no Lean base | exit 2, decline naming what is missing | +//! | an unknown name among several | exit 1 before anything runs | + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn pv_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_pv")) +} + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn pv(args: &[&str]) -> Run { + let scratch = tempfile::tempdir().expect("scratch cwd is creatable"); + let out = Command::new(pv_bin()) + .current_dir(scratch.path()) + .args(args) + .output() + .expect("failed to spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn s(p: &Path) -> &str { + p.to_str().expect("utf-8 path") +} + +fn repo_contracts() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts") +} + +fn write(root: &Path, rel: &str, text: &str) { + let p = root.join(rel); + std::fs::create_dir_all(p.parent().expect("has a parent")).expect("mkdir"); + std::fs::write(p, text).expect("write"); +} + +/// `pv lint --gate X` prints one JSON object per gate, pretty-printed and back to back. +fn reports(stdout: &str) -> Vec { + serde_json::Deserializer::from_str(stdout) + .into_iter::() + .map(|v| v.expect("each gate report is JSON")) + .collect() +} + +const BOTH: [&str; 4] = ["--gate", "theorem-pairing", "--gate", "depends-on-present"]; + +/// A repo with one paired theorem module and one kernel contract (the PVL-1 control, which has no +/// `depends_on`), at baselines 0 and 1. +fn repo() -> tempfile::TempDir { + let t = tempfile::tempdir().expect("tempdir"); + write( + t.path(), + "lean/ProvableContracts/Theorems/S/A.lean", + "theorem a : True := trivial\n", + ); + write( + t.path(), + "book/a.md", + "see ProvableContracts.Theorems.S.A\n", + ); + std::fs::create_dir_all(t.path().join("contracts")).expect("mkdir"); + std::fs::copy( + repo_contracts().join("softmax-kernel-v1.yaml"), + t.path().join("contracts/softmax-kernel-v1.yaml"), + ) + .expect("control contract copies"); + write( + t.path(), + "contracts/lint-baseline.json", + "{\n \"unpaired_theorem_modules\": 0,\n \"contracts_without_depends_on\": 1\n}\n", + ); + t +} + +fn lint(root: &Path, gates: &[&str]) -> Run { + let dir = root.join("contracts"); + let mut args = vec!["lint", s(&dir)]; + args.extend_from_slice(gates); + pv(&args) +} + +#[test] +fn the_probe_passes_on_the_real_corpus_and_writes_nothing() { + let baseline = repo_contracts().join("lint-baseline.json"); + let before = std::fs::read(&baseline).expect("baseline readable"); + let doc: serde_json::Value = serde_json::from_slice(&before).expect("baseline is JSON"); + assert!(doc.is_object()); + assert!(doc["unpaired_theorem_modules"].is_u64(), "{doc}"); + assert!(doc["contracts_without_depends_on"].is_u64(), "{doc}"); + assert_eq!(doc["command"], "make lint-ratchet"); + + let dir = repo_contracts(); + let mut args = vec!["lint", s(&dir)]; + args.extend_from_slice(&BOTH); + let r = pv(&args); + assert_eq!(r.code, 0, "{}", show(&r)); + let got = reports(&r.stdout); + assert_eq!(got.len(), 2, "{}", show(&r)); + for g in &got { + assert_eq!(g["verdict"], "Pass", "{g}"); + } + assert_eq!( + got[0]["unpaired_theorem_modules"], doc["unpaired_theorem_modules"], + "the recorded baseline is the measured count (`make lint-ratchet` wrote it)" + ); + assert_eq!( + got[1]["contracts_without_depends_on"], + doc["contracts_without_depends_on"] + ); + assert_eq!( + std::fs::read(&baseline).expect("baseline readable"), + before, + "a gate wrote the baseline" + ); +} + +#[test] +fn at_the_baseline_both_gates_pass() { + let t = repo(); + let r = lint(t.path(), &BOTH); + assert_eq!(r.code, 0, "{}", show(&r)); +} + +/// PVL-001 EV-11's mutation, verbatim: "add an unpaired Theorem module → RED". +#[test] +fn adding_an_unpaired_theorem_module_is_red() { + let t = repo(); + write( + t.path(), + "lean/ProvableContracts/Theorems/S/B.lean", + "theorem b : True := trivial\n", + ); + let r = lint(t.path(), &BOTH); + assert_eq!(r.code, 1, "{}", show(&r)); + assert!(r.stdout.contains("PV-RAT-001"), "{}", show(&r)); + assert!(!r.stdout.contains("PV-RAT-002"), "{}", show(&r)); + assert!( + r.stderr.contains("1/2 armed gates passed"), + "the meet names the one that held: {}", + show(&r) + ); +} + +#[test] +fn a_kernel_contract_without_depends_on_above_the_baseline_is_red() { + let t = repo(); + let text = std::fs::read_to_string(repo_contracts().join("softmax-kernel-v1.yaml")) + .expect("control contract readable"); + write(t.path(), "contracts/softmax-kernel-copy-v1.yaml", &text); + let r = lint(t.path(), &["--gate", "depends-on-present"]); + assert_eq!(r.code, 1, "{}", show(&r)); + assert!(r.stdout.contains("PV-RAT-002"), "{}", show(&r)); +} + +#[test] +fn no_baseline_reports_the_count_and_is_never_a_pass() { + let t = repo(); + write(t.path(), "contracts/lint-baseline.json", "{}\n"); + let r = lint(t.path(), &BOTH); + assert_eq!(r.code, 2, "{}", show(&r)); + let got = reports(&r.stdout); + assert_eq!(got.len(), 2, "{}", show(&r)); + for g in &got { + assert_eq!(g["verdict"], "Unknown(Report)", "{g}"); + } + // the count `make lint-ratchet` records as the first baseline + assert_eq!(got[0]["unpaired_theorem_modules"], 0); + assert_eq!(got[1]["contracts_without_depends_on"], 1); +} + +#[test] +fn no_lean_base_is_a_decline_naming_what_is_missing() { + let t = repo(); + std::fs::remove_dir_all(t.path().join("lean")).expect("rm lean"); + let r = lint(t.path(), &["--gate", "theorem-pairing"]); + assert_eq!(r.code, 2, "{}", show(&r)); + assert!(r.stderr.contains("no Lean theorem base"), "{}", show(&r)); +} + +#[test] +fn an_unknown_name_among_several_is_refused_before_anything_runs() { + let t = repo(); + let r = lint(t.path(), &["--gate", "theorem-pairing", "--gate", "bogus"]); + assert_eq!(r.code, 1, "{}", show(&r)); + assert!(r.stderr.contains("--gate bogus"), "{}", show(&r)); + assert!(r.stdout.is_empty(), "nothing ran: {}", show(&r)); +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/README.md b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/README.md new file mode 100644 index 0000000000..d12f1c1558 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/README.md @@ -0,0 +1,86 @@ +# PVL-001 EV-10 golden fixtures — `pv obligations --gate` vs `pv-obligation-gate.py` + +`tests/pvl_obligations_golden.rs` holds `pv obligations --gate` to `.stdout` +byte for byte and to `.rc`. Both files were written by the Python script it replaces, +never by pv. + +| fixture | what | script verdict | +|---|---|---| +| `pmat/` | pmat's 35 `contracts/*.yaml` + `binding.yaml`, verbatim, and the 10 `src/` files that `git grep -l -E "fn \b" -- src/` returns for the 5 function-valued `applies_to` (all in `tdg-grade-order-v1.yaml`, `proved_type: Grade`) | 0 problems over 35, exit 0 | +| `broken/` | one planted defect per check, plus a control for every rule that must stay silent | 6 problems over 4, exit 1 | +| `edge/` | YAML the script reads through PyYAML and Python truthiness: merge keys and falsy values | 3 problems over 4, exit 1 | + +## Provenance + +- pmat (`paiml/paiml-mcp-agent-toolkit`) at `d70a78f67` (2026-09-15). +- script `scripts/pv-obligation-gate.py`, sha256 + `e4fa971964f7d902d2491ef81eb667f8f1eee68a4ec411590b138c70dfad4e12` (last changed in `7c93535e`). +- PyYAML 6.0.3. The script's `pv validate` resolved to the pv built from this tree (first on `PATH`). + +## Why the `src/` files end in `.rs.txt` + +They are pmat's sources, and ten aprender gates scan every tracked `*.rs`. They were renamed +after copying and not otherwise changed. Neither side cares about the extension: the script's `git grep … -- src/` searches +every tracked file under `src/`, and so does pv. A name only reaches the output inside a check-3b +problem (`['src/gate.rs.txt']` in `broken.stdout`). + +## `broken/`, line by line + +- `a-invalid-v1.yaml`: no `metadata:`, so `pv validate` fails (check 1). +- `b-hidden-v1.yaml`: two `test:` entries under `falsification:` (check 2). The `action:`-only + entry is an alert threshold and is not counted. +- `c-unbound-v1.yaml`: `ghost_fn_xyz` names no `fn` (check 3a). `bounded` is only found as a + prefix of `fn bounded_v2`, and `\b` refuses that match. The controls are `all`, the equation + `relu`, `real_fn` in a nested file, and an obligation with no `applies_to`. +- `d-proved-v1.yaml`: `grade_gate`'s file only says `GradeTable`, and `grade_prefixed`'s file + only says `TdgGrade`. Neither says the word `Grade` (check 3b, one case for each side of + `\b`). The control is `grade_ok`, whose file does say `Grade`. +- Excluded, and never reported: + - `z-binding.yaml` (its name ends in `binding.yaml`); + - `.hidden-v1.yaml` (`glob`'s `*` skips dotfiles); + - `sub/nested-v1.yaml` (the walk is not recursive); + - `notes.yml` (not `.yaml`). + +## `edge/`, line by line + +- `e-merge-v1.yaml`: two entries under `falsification:` have no `test:` of their own. `F-1` + merges one in with `<<: *hidden`, and `F-2` merges it through `chained`, a mapping that + merges in turn. PyYAML resolves both, so the script counts 2 (check 2). `F-0` holds the + templates one level down and is not counted. +- `f-falsy-v1.yaml`: `proved_type: 0`, and `applies_to` set to `0`, `[]`, `{}`, `false` and `""`. + Python treats every one of them as absent, so the only problem is `pv validate` (check 1). + `real_fn` is bound and never checked against the falsy type. +- `g-unused-type-v1.yaml`: `proved_type: 7`, which no function target reaches, so the script + never evaluates it and reports nothing. +- `h-false-doc-v1.yaml`: the document is `false`. `yaml.safe_load(...) or {}` makes it `{}`, so + only `pv validate` fails. + +## Re-recording + +The fixture must be tracked (`git add`) first, because the script's `git grep` only sees +tracked files. + +```bash +cargo build -p aprender-contracts-cli --bin pv +S=~/src/paiml-mcp-agent-toolkit/scripts/pv-obligation-gate.py # sha256 as above +for fx in pmat broken edge; do + (cd crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/$fx && + PATH="$PWD/../../../../../../../target/debug:$PATH" python3 "$S" > ../$fx.stdout; echo $? > ../$fx.rc) +done +``` + +## Where pv deliberately differs + +These are documented in `src/commands/obligations.rs`. No fixture here exercises them, because the +script has no verdict to record: + +- zero contracts is `decline:` at exit 2 (PVL-1), where the script reports 0 over 0 at exit 0; +- an input the script dies on with a traceback is a named problem. The module doc lists each + one, and `a_non_string_proved_type_is_named_where_a_bound_fn_reaches_it` tests one; +- YAML 1.2 (`serde_yaml`) against PyYAML's YAML 1.1: a plain `no` is a string here; +- a document `serde_yaml` refuses and PyYAML reads (a duplicate key, an integer beyond 64 bits) + fails `pv validate` on both sides. pv reports only that, as the script does, but cannot run + checks 2 and 3 on it, so a problem PyYAML would also find there goes unnamed (the verdict + agrees). `a_document_serde_yaml_refuses_fails_validate_so_the_verdict_agrees` holds both + shapes to the script's measured output; +- `src/` is walked on disk, so untracked files count. diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken.rc b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken.rc new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken.rc @@ -0,0 +1 @@ +1 diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken.stdout b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken.stdout new file mode 100644 index 0000000000..7c03f01043 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken.stdout @@ -0,0 +1,7 @@ +::error::contracts/a-invalid-v1.yaml: pv validate failed +::error::contracts/b-hidden-v1.yaml: 2 test-bearing obligation(s) under `falsification:`, which pv cannot read — use `falsification_tests:` +::error::contracts/c-unbound-v1.yaml: applies_to 'ghost_fn_xyz' names neither an equation of this contract nor any `fn` under src/ +::error::contracts/c-unbound-v1.yaml: applies_to 'bounded' names neither an equation of this contract nor any `fn` under src/ +::error::contracts/d-proved-v1.yaml: applies_to 'grade_gate' is proved against 'Grade', but none of ['src/gate.rs.txt'] mentions it — the proof does not reach the code it names +::error::contracts/d-proved-v1.yaml: applies_to 'grade_prefixed' is proved against 'Grade', but none of ['src/prefixed.rs.txt'] mentions it — the proof does not reach the code it names +pv obligation gate: 6 problem(s) over 4 contracts diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/.hidden-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/.hidden-v1.yaml new file mode 100644 index 0000000000..1363bfc814 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/.hidden-v1.yaml @@ -0,0 +1 @@ +not a contract diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/a-invalid-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/a-invalid-v1.yaml new file mode 100644 index 0000000000..266414ebf4 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/a-invalid-v1.yaml @@ -0,0 +1,8 @@ +# CHECK 1: no `metadata:` — `pv validate` fails. The script and pv both report it. +equations: + id: + formula: "y = x" +proof_obligations: + - type: invariant + property: "identity" + applies_to: all diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/b-hidden-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/b-hidden-v1.yaml new file mode 100644 index 0000000000..26ea07fe41 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/b-hidden-v1.yaml @@ -0,0 +1,29 @@ +metadata: + version: "1.0.0" + kind: pattern + references: + - "PVL-001 EV-10 golden fixture" + description: "CHECK 2: two test-bearing entries hide under falsification:; the action-only entry is an alert threshold and is not counted" + +equations: + relu: + formula: "y_i = max(0, x_i)" + invariants: + - "y_i >= 0" + +proof_obligations: + - type: invariant + property: "Non-negativity" + applies_to: all +falsification: + - id: HIDDEN-1 + test: "relu_nonneg" + - id: HIDDEN-2 + test: "relu_idempotent" + - id: ALERT-1 + action: "page on-call" +falsification_tests: + - id: FALSIFY-OBL-001 + rule: "Non-negativity" + prediction: "relu(x)_i >= 0" + if_fails: "Comparison inverted" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/c-unbound-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/c-unbound-v1.yaml new file mode 100644 index 0000000000..9489752389 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/c-unbound-v1.yaml @@ -0,0 +1,36 @@ +metadata: + version: "1.0.0" + kind: pattern + references: + - "PVL-001 EV-10 golden fixture" + description: "CHECK 3a: an applies_to that names no fn; controls: all, an equation, a real fn, a missing/empty target" + +equations: + relu: + formula: "y_i = max(0, x_i)" + invariants: + - "y_i >= 0" + +proof_obligations: + - type: invariant + property: "ghost" + applies_to: ghost_fn_xyz + - type: invariant + property: "prefix only: src has fn bounded_v2, not fn bounded" + applies_to: bounded + - type: invariant + property: "control: all" + applies_to: all + - type: invariant + property: "control: an equation of this contract" + applies_to: relu + - type: invariant + property: "control: a real fn, found in a nested file" + applies_to: real_fn + - type: invariant + property: "control: no applies_to" +falsification_tests: + - id: FALSIFY-OBL-001 + rule: "Non-negativity" + prediction: "relu(x)_i >= 0" + if_fails: "Comparison inverted" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/d-proved-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/d-proved-v1.yaml new file mode 100644 index 0000000000..e2562672a5 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/d-proved-v1.yaml @@ -0,0 +1,28 @@ +metadata: + version: "1.0.0" + kind: pattern + references: + - "PVL-001 EV-10 golden fixture" + description: "CHECK 3b: proved_type Grade; grade_gate s file only says GradeTable and grade_prefixed s only TdgGrade; grade_ok s says Grade" + proved_type: Grade +equations: + relu: + formula: "y_i = max(0, x_i)" + invariants: + - "y_i >= 0" + +proof_obligations: + - type: invariant + property: "gate is proved against Grade" + applies_to: grade_gate + - type: invariant + property: "prefixed is proved against Grade, but its file only says TdgGrade" + applies_to: grade_prefixed + - type: invariant + property: "control: reaches Grade" + applies_to: grade_ok +falsification_tests: + - id: FALSIFY-OBL-001 + rule: "Non-negativity" + prediction: "relu(x)_i >= 0" + if_fails: "Comparison inverted" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/notes.yml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/notes.yml new file mode 100644 index 0000000000..1363bfc814 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/notes.yml @@ -0,0 +1 @@ +not a contract diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/sub/nested-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/sub/nested-v1.yaml new file mode 100644 index 0000000000..1363bfc814 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/sub/nested-v1.yaml @@ -0,0 +1 @@ +not a contract diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/z-binding.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/z-binding.yaml new file mode 100644 index 0000000000..49a6a7df4a --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/contracts/z-binding.yaml @@ -0,0 +1 @@ +not: [a, contract diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/deep/real.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/deep/real.rs.txt new file mode 100644 index 0000000000..2e03ada5a4 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/deep/real.rs.txt @@ -0,0 +1,3 @@ +pub fn real_fn() {} + +fn bounded_v2() {} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/gate.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/gate.rs.txt new file mode 100644 index 0000000000..755c25730f --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/gate.rs.txt @@ -0,0 +1,4 @@ +// The type is only ever named as part of a longer word here. +pub fn grade_gate(t: &GradeTable) -> bool { + t.is_empty() +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/ok.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/ok.rs.txt new file mode 100644 index 0000000000..05b44998dd --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/ok.rs.txt @@ -0,0 +1,3 @@ +pub fn grade_ok(g: Grade) -> bool { + matches!(g, Grade::A) +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/prefixed.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/prefixed.rs.txt new file mode 100644 index 0000000000..c982ac7b15 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/broken/src/prefixed.rs.txt @@ -0,0 +1,4 @@ +// The type is only ever named as the tail of a longer word here. +pub fn grade_prefixed(g: &TdgGrade) -> bool { + g.is_passing() +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge.rc b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge.rc new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge.rc @@ -0,0 +1 @@ +1 diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge.stdout b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge.stdout new file mode 100644 index 0000000000..41ec5d3647 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge.stdout @@ -0,0 +1,4 @@ +::error::contracts/e-merge-v1.yaml: 2 test-bearing obligation(s) under `falsification:`, which pv cannot read — use `falsification_tests:` +::error::contracts/f-falsy-v1.yaml: pv validate failed +::error::contracts/h-false-doc-v1.yaml: pv validate failed +pv obligation gate: 3 problem(s) over 4 contracts diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/e-merge-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/e-merge-v1.yaml new file mode 100644 index 0000000000..d306a8de79 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/e-merge-v1.yaml @@ -0,0 +1,33 @@ +metadata: + version: "1.0.0" + kind: pattern + references: + - "PVL-001 EV-10 golden fixture" + description: "CHECK 2 through a YAML merge key: F-1 merges a test from F-0's template, F-2 merges it through a merge that itself merges; the script (PyYAML) counts both, F-0 is not counted" +equations: + relu: + formula: "y_i = max(0, x_i)" + invariants: + - "y_i >= 0" + +proof_obligations: + - type: invariant + property: "Non-negativity" + applies_to: all +falsification: + - id: F-0 + action: "page on-call" + template: &hidden + test: "relu_nonneg" + chained: &chained + <<: *hidden + note: "merges the template in turn" + - <<: *hidden + id: F-1 + - <<: *chained + id: F-2 +falsification_tests: + - id: FALSIFY-OBL-001 + rule: "Non-negativity" + prediction: "relu(x)_i >= 0" + if_fails: "Comparison inverted" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/f-falsy-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/f-falsy-v1.yaml new file mode 100644 index 0000000000..f5ebee3833 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/f-falsy-v1.yaml @@ -0,0 +1,38 @@ +metadata: + version: "1.0.0" + kind: pattern + references: + - "PVL-001 EV-10 golden fixture" + description: "Python falsiness: a falsy proved_type is no proved_type, and falsy applies_to values are skipped like an absent one; real_fn is bound and never checked against a type" + proved_type: 0 +equations: + relu: + formula: "y_i = max(0, x_i)" + invariants: + - "y_i >= 0" + +proof_obligations: + - type: invariant + property: "zero" + applies_to: 0 + - type: invariant + property: "empty list" + applies_to: [] + - type: invariant + property: "empty map" + applies_to: {} + - type: invariant + property: "false" + applies_to: false + - type: invariant + property: "empty string" + applies_to: "" + - type: invariant + property: "bound, and not checked against the falsy type" + applies_to: real_fn +falsification: [] +falsification_tests: + - id: FALSIFY-OBL-001 + rule: "Non-negativity" + prediction: "relu(x)_i >= 0" + if_fails: "Comparison inverted" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/g-unused-type-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/g-unused-type-v1.yaml new file mode 100644 index 0000000000..95eee374b9 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/g-unused-type-v1.yaml @@ -0,0 +1,25 @@ +metadata: + version: "1.0.0" + kind: pattern + references: + - "PVL-001 EV-10 golden fixture" + description: "A truthy non-string proved_type that no function target ever reaches: the script never evaluates it" + proved_type: 7 +equations: + relu: + formula: "y_i = max(0, x_i)" + invariants: + - "y_i >= 0" + +proof_obligations: + - type: invariant + property: "equation, not a function" + applies_to: relu + - type: invariant + property: "everything" + applies_to: all +falsification_tests: + - id: FALSIFY-OBL-001 + rule: "Non-negativity" + prediction: "relu(x)_i >= 0" + if_fails: "Comparison inverted" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/h-false-doc-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/h-false-doc-v1.yaml new file mode 100644 index 0000000000..c508d5366f --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/contracts/h-false-doc-v1.yaml @@ -0,0 +1 @@ +false diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/src/real.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/src/real.rs.txt new file mode 100644 index 0000000000..3ea2e60028 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/edge/src/real.rs.txt @@ -0,0 +1 @@ +pub fn real_fn() {} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat.rc b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat.rc new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat.rc @@ -0,0 +1 @@ +0 diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat.stdout b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat.stdout new file mode 100644 index 0000000000..a5e76d2a4c --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat.stdout @@ -0,0 +1 @@ +pv obligation gate: 0 problem(s) over 35 contracts diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/benchmarking-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/benchmarking-v1.yaml new file mode 100644 index 0000000000..1a525015a0 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/benchmarking-v1.yaml @@ -0,0 +1,64 @@ +metadata: + version: "1.0.0" + created: "2026-04-08" + author: PAIML Engineering + references: + - "benches/coverage_gaps_bench.rs (criterion harness these budgets gate)" + - ".github/workflows/nightly-bench.yml (where the budgets are enforced)" + - "docs/specifications/components/repo-health.md (Build Performance category, 15pts)" + registry: true + description: > + Benchmarking and profiling contract — performance budgets for all + critical pmat operations. Regression detection via recorded baselines. + contract: benchmarking + status: enforced + +equations: + query_latency: + formula: latency = time(pmat query "q" --limit N) + domain: q in String, N in {1..100} + codomain: latency in Duration + invariants: + - warm_latency < 500ms for semantic/regex/literal queries + - cold_latency < 5s for local-only index load + preconditions: + - .pmat/context.db exists (index pre-built) + lean_theorem: Theorems.Query_Latency_Bounded + + index_build: + formula: duration = time(build_index(project_path)) + domain: project_path in Path + codomain: duration in Duration + invariants: + - duration < 60s for projects with <5000 .rs files + - incremental_duration < 5s when <10 files changed + preconditions: + - project_path.join("Cargo.toml").exists() + + scoring_latency: + formula: latency = time(pmat rust-project-score) + domain: project_path in Path + codomain: latency in Duration + invariants: + - fast_mode < 5s + - full_mode < 300s + preconditions: + - project_path.join("Cargo.toml").exists() + +preconditions: + - "pmat binary installed" + - ".pmat/context.db exists" + +falsification: + - condition: "pmat query 'test' --limit 1 takes >500ms warm" + severity: P1 + action: investigate_regression + - condition: "pmat rust-project-score takes >5s in fast mode" + severity: P1 + action: investigate_regression + - condition: "pmat comply check takes >30s" + severity: P2 + action: investigate_regression + - condition: "Index build >60s for <5000 files" + severity: P2 + action: optimize_indexer diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/binding.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/binding.yaml new file mode 100644 index 0000000000..c7a90ad95b --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/binding.yaml @@ -0,0 +1,308 @@ +version: 1.0.0 +target_crate: pmat +bindings: +- contract: pmat-core.yaml + equation: refresh_bindings + source_file: src/cli/handlers/comply_handlers/check_handlers/check_commit_enforcement.rs + function: handle_refresh_bindings + status: implemented +- contract: pmat-core.yaml + equation: check_compliance + source_file: src/cli/handlers/comply_handlers/check_handlers/check.rs + function: handle_check + status: implemented +- contract: pmat-core.yaml + equation: check_compliance + source_file: src/services/contract_index.rs + function: ContractIndex::load + status: implemented +- contract: pmat-core.yaml + equation: check_compliance + source_file: src/services/asset_validator.rs + function: validate_all_assets + status: implemented +- contract: pmat-core.yaml + equation: score_range + source_file: src/cli/handlers/score_handler.rs + function: handle_score + status: implemented +- contract: pmat-core.yaml + equation: score_range + source_file: src/cli/handlers/score_handler.rs + function: compute_composite + status: implemented +- contract: pmat-core.yaml + equation: score_range + source_file: src/services/rust_project_score/orchestrator.rs + function: score_with_mode + status: implemented +- contract: pmat-core.yaml + equation: score_range + source_file: src/cli/handlers/rust_project_score_handlers.rs + function: handle_rust_project_score + status: implemented +- contract: pmat-core.yaml + equation: score_range + source_file: src/services/popper_score/orchestrator.rs + function: score + status: implemented +- contract: pmat-core.yaml + equation: score_range + source_file: src/cli/handlers/popper_score_handlers.rs + function: handle_popper_score + status: implemented +- contract: pmat-core.yaml + equation: score_range + source_file: src/cli/handlers/work_contract_scoring.rs + function: score_contract + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/query_handler/query_execution.rs + function: handle_query + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/advanced_analysis_handlers.rs + function: handle_analyze_tdg + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/dead_code_handlers.rs + function: handle_analyze_dead_code + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/comprehensive_handler.rs + function: handle_analyze_comprehensive + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/five_whys_handlers.rs + function: handle_debug + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/kaizen_handler/mod.rs + function: handle_kaizen + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/demo_handlers.rs + function: handle_quality_gate + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/bottleneck_handler.rs + function: handle_bottleneck + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/infra_score_handlers.rs + function: handle_infra_score + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/repo_score_handlers.rs + function: handle_repo_score + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/brick_score_handlers.rs + function: handle_brick_score + status: implemented +- contract: pmat-core.yaml + equation: path_exists + source_file: src/cli/handlers/demo_score_handlers.rs + function: handle_demo_score + status: implemented +- contract: pmat-core.yaml + equation: lint_valid + source_file: src/cli/handlers/work_contract_lint.rs + function: lint_contract + status: implemented +- contract: pmat-core.yaml + equation: non_empty_index + source_file: src/services/agent_context/function_index/build_persistence.rs + function: save + status: implemented +- contract: macs-provenance-v1.yaml + equation: provenance_roundtrip + source_file: src/cli/handlers/work_ledger_types.rs + function: AgentProvenance + status: pending +- contract: macs-provenance-v1.yaml + equation: hash_stability + source_file: src/cli/handlers/work_ledger_receipt.rs + function: FalsificationReceipt::compute_content_hash + status: pending +- contract: macs-provenance-v1.yaml + equation: refusal_gates_completion + source_file: src/cli/handlers/work_handlers/core_handlers/handlers.rs + function: check_unacked_refusals + status: pending +- contract: macs-ladder-v1.yaml + equation: parse_total_strict + source_file: src/cli/handlers/work_verification_level.rs + function: VerificationLevel::parse_strict + status: pending +- contract: macs-ladder-v1.yaml + equation: gate_monotone + source_file: src/quality/ladder_evidence.rs + function: achieved_level + status: pending +- contract: macs-cot-v1.yaml + equation: chain_integrity + source_file: src/models/work_cot.rs + function: check_chain + status: pending +- contract: macs-cot-v1.yaml + equation: derivation_complete + source_file: src/models/work_cot.rs + function: derive + status: pending +- contract: macs-sweep-v1.yaml + equation: sweep_deterministic + source_file: src/cli/handlers/qa_mcp_sweep.rs + function: handle_mcp_sweep + status: pending +- contract: macs-artifacts-v1.yaml + equation: manifest_faithful + source_file: src/mcp_pmcp/tool_manifest.rs + function: render_manifest + status: pending +- contract: macs-artifacts-v1.yaml + equation: roadmap_canonical + source_file: src/roadmap/sync.rs + function: render_roadmap + status: pending +- contract: macs-skill-effort-v1.yaml + equation: skill_effort_pinned + source_file: src/cli/handlers/comply_handlers/check_handlers/check_macs_skill_effort.rs + function: check_skill_effort_pinned + status: pending +- contract: macs-ladder-kernel-v1.yaml + equation: parse_strict + source_file: src/cli/handlers/work_verification_level.rs + function: VerificationLevel::parse_strict + status: implemented +- contract: macs-ladder-kernel-v1.yaml + equation: ord_monotone + source_file: src/cli/handlers/work_verification_level.rs + function: VerificationLevel::as_str + status: implemented +- contract: pmat-quality-acceptance-v1.yaml + equation: quality_acceptance + source_file: src/cli/handlers/work_quality_handlers.rs + function: run_popper_falsification + status: implemented +# --- pmat-no-fabrication-v1: every function fixed in the anti-fabrication sweep +# binds to the equation it now satisfies. A binding here is the claim that this +# function no longer reports a value it did not measure; the equation's +# falsification_tests are what make that claim refutable. +- contract: pmat-no-fabrication-v1.yaml + equation: measured_or_absent + source_file: src/services/deep_context/analyzer_core/quality.rs + function: DeepContextAnalyzer::calculate_quality_scorecard + status: implemented +- contract: pmat-no-fabrication-v1.yaml + equation: measured_or_absent + source_file: src/services/deep_context/deep_context_quality_types.rs + function: QualityScorecard::render + status: implemented +- contract: pmat-no-fabrication-v1.yaml + equation: output_derived_from_input + source_file: src/services/deep_context/analyzer_core/quality.rs + function: DeepContextAnalyzer::measured_line_coverage + status: implemented +- contract: pmat-no-fabrication-v1.yaml + equation: detection_mode_superset + source_file: src/cli/analysis/duplicates_extraction.rs + function: extract_blocks + status: implemented +- contract: pmat-no-fabrication-v1.yaml + equation: measured_or_absent + source_file: src/cli/analysis/duplicates_output.rs + function: format_json_output + status: implemented +- contract: pmat-no-fabrication-v1.yaml + equation: bounded_time_arithmetic + source_file: src/services/git_analysis.rs + function: GitAnalysisService::analyze_code_churn + status: implemented +- contract: pmat-no-fabrication-v1.yaml + equation: session_survives_recoverable_frame + source_file: src/mcp_pmcp/simple_unified_server.rs + function: EofSignalingTransport::is_session_over + status: implemented +- contract: pmat-no-fabrication-v1.yaml + equation: session_survives_recoverable_frame + source_file: src/mcp_pmcp/simple_unified_server.rs + function: EofSignalingTransport::receive + status: implemented +- contract: comply-gate-effect-v1.yaml + equation: gate_effect + source_file: src/cli/handlers/comply_handlers/check_handlers/check_gate_effect.rs + function: check_comply_gate_effect + status: implemented +- contract: comply-gate-effect-v1.yaml + equation: reachable + source_file: src/services/gate_effect/kernel.rs + function: reachable + status: implemented +- contract: comply-gate-effect-v1.yaml + equation: select_by_context + source_file: src/services/gate_effect/kernel.rs + function: select_by_context + status: implemented +- contract: comply-gate-effect-v1.yaml + equation: gates + source_file: src/services/gate_effect/kernel.rs + function: gates + status: implemented +- contract: comply-gate-effect-v1.yaml + equation: context_string_resolution + source_file: src/services/gate_effect/resolve.rs + function: resolve_context + status: implemented +- contract: comply-gate-effect-v1.yaml + equation: failure_propagation + source_file: src/services/gate_effect/effect.rs + function: assess + status: implemented +- contract: comply-gate-effect-v1.yaml + equation: enforcement_ledger + source_file: src/services/gate_effect/ledger.rs + function: render + status: implemented +# CB-2102 (ratchet) — the module was annotated with #[contract(...)] since it was +# written and never registered here, so `AllImplemented` had nothing to check and +# the `#[contract]` macro emitted no `=bound` env var for it. Registered now +# alongside CB-2101 rather than left as a second silent gap in the pair of rules +# that exist to find silent gaps. +- contract: comply-ratchet-v1.yaml + equation: verdict + source_file: src/services/metrics_ratchet/kernel.rs + function: ratchet_verdict + status: implemented +- contract: comply-ratchet-v1.yaml + equation: next + source_file: src/services/metrics_ratchet/kernel.rs + function: next_baseline + status: implemented +- contract: comply-ratchet-v1.yaml + equation: ratchet + source_file: src/cli/handlers/comply_handlers/check_handlers/check_metrics_ratchet.rs + function: check_metrics_ratchet + status: implemented +# CB-2101 (threshold coherence). +- contract: comply-threshold-coherence-v1.yaml + equation: classify + source_file: src/services/metrics_ratchet/kernel.rs + function: classify + status: implemented +- contract: comply-threshold-coherence-v1.yaml + equation: audit + source_file: src/cli/handlers/comply_handlers/check_handlers/check_threshold_coherence.rs + function: check_threshold_coherence + status: implemented diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/cli-usage-lines-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/cli-usage-lines-v1.yaml new file mode 100644 index 0000000000..cdf681aa2e --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/cli-usage-lines-v1.yaml @@ -0,0 +1,71 @@ +metadata: + version: "1.0.0" + created: "2026-09-02" + author: PAIML Engineering + references: + - "docs/specifications/pmat-architecture-crux-audit.md section 8.5 (CRUX-05, issue 1148, ticket PMAT-634)" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + Every pmat subcommand prints a usable Usage line, every CLI error names the + offending token, and no guard in the tree may certify a usage section by the + bare substring "Usage:". Cargo.toml built clap with default-features = false + and without usage/error-context/suggestions; 70 of 71 subcommands printed an + empty "Usage: " line and five separate guards passed on it. + contract: cli-usage-lines + status: draft + +equations: + usage_line_present: + formula: "for every subcommand c, help(c) contains a line matching ^Usage:[ \\t]+\\S" + domain: "pmat --help and pmat --help for every advertised c" + codomain: bool + invariants: + - "the line names the binary and the full command path, not a placeholder alone" + - "generated by clap's usage feature, never by Command::override_usage or a hand-rolled printer" + preconditions: [] + + error_names_token: + formula: "for an unknown flag f, stderr(pmat ... f) contains f" + domain: "any subcommand invoked with an unrecognised flag or a misspelt subcommand" + codomain: bool + invariants: + - "a near-miss subcommand is suggested (clap suggestions)" + - "the error carries clap's 'For more information, try' footer (error-context)" + preconditions: [] + + no_blind_usage_guard: + formula: "count of contains(\"Usage:\") predicates in src/ and tests/ = 0" + domain: "src/**, tests/**" + codomain: bool + invariants: + - "a guard asserts CONTENT after the heading, so an empty heading cannot satisfy it" + preconditions: [] + +falsification: + - condition: "A subcommand's --help prints an empty Usage: heading with green CI (CRUX-05, issue 1148)" + severity: P0 + action: reject_push + - condition: "A CLI error does not name the token it rejected" + severity: P1 + action: reject_push + - condition: "A bare contains(\"Usage:\") predicate returns to src/ or tests/" + severity: P1 + action: reject_push + +falsification_tests: + - id: usage_line_present + rule: "an empty Usage: heading is not a usage section" + prediction: "help text 'Usage: \\n\\nOptions:' fails has_usage_section; 'Usage: pmat analyze complexity [OPTIONS]' passes" + test: "cargo test --lib an_empty_usage_heading_is_not_a_usage_section" + if_fails: "pmat's own docs checker certifies the defect it exists to catch" + - id: error_names_token + rule: "the error for --zzz-nope contains --zzz-nope, and a near-miss subcommand is suggested" + prediction: "legs 3 and 4 of the spec section 8.5 script fail on the pre-fix binary and pass after" + test: "bash scripts/cli-usage-audit.sh (the spec section 8.5 acceptance script) against the release binary" + if_fails: "an agent cannot tell which of its arguments was wrong" + - id: no_blind_usage_guard + rule: "no bare contains(\"Usage:\") predicate in src/ or tests/" + prediction: "grep -rn 'contains(\"Usage:\")' src/ tests/ | wc -l == 0" + test: "grep -rn 'contains(\"Usage:\")' src/ tests/" + if_fails: "the regression returns the way it arrived, past five green guards" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/commit-enforcement-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/commit-enforcement-v1.yaml new file mode 100644 index 0000000000..88236c2694 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/commit-enforcement-v1.yaml @@ -0,0 +1,60 @@ +metadata: + version: "1.0.0" + created: "2026-09-04" + author: PAIML Engineering + references: + - "docs/specifications/agentic-delivery-pmat.md section 4.7 / 9.3 (AD-03)" + - "issue 1126 — the generated hook's SATD and task-ID checks warn and exit 0" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + The hooks pmat installs must be able to refuse a commit. Under [hooks] + strict = true (or pmat hooks install --strict) the pre-commit SATD check + exits 1 over the threshold, and a generated commit-msg hook refuses a + message that carries neither a Pmat-Ticket trailer nor a ticket or issue + reference matching [hooks] ticket_pattern. Non-strict keeps warning so an + upgrade cannot lock a repository out. The trailer is the machine-readable + link between a commit and its work: git log --format=%(trailers:key=Pmat-Ticket,valueonly). + contract: commit-enforcement + status: draft + +equations: + strict_refuses_unlinked_commits: + formula: "strict and not (has_trailer(msg) or matches(msg, ticket_pattern)) => commit-msg exits 1 naming Pmat-Ticket and no commit is created" + domain: "any git commit through a repository where pmat hooks install --strict ran" + codomain: bool + invariants: + - "a message with the trailer is accepted and git reads the trailer back" + - "a message with an issue reference (#NNN) is accepted" + - "non-strict warns (stderr names Pmat-Ticket) and accepts" + - "a pre-commit hook receives no message, so the ticket check lives in commit-msg only" + - "hooks verify --fix, hooks update and the comply auto-install re-generate the hook with the strictness it was installed with (installed_strict reads PMAT_HOOKS_STRICT=1 back)" + preconditions: [] + + strict_satd_is_a_refusal: + formula: "strict and satd_count > PMAT_MAX_SATD_COMMENTS => pre-commit exits 1 before the success banner" + domain: "the generated pre-commit hook" + codomain: bool + invariants: + - "PMAT_HOOKS_STRICT is exported into the pre-commit hook from --strict OR [hooks] strict, resolved once for both hooks; the ticket pattern lives in the commit-msg hook only (the pre-commit export was dead — nothing read it)" + preconditions: [] + +falsification: + - condition: "under strict, a commit without a trailer or ticket reference is created (AD-03)" + severity: P0 + action: reject_push + - condition: "non-strict refuses a commit, or strict refuses a commit that carries the trailer" + severity: P0 + action: reject_push + +falsification_tests: + - id: strict_refuses_unlinked_commits + rule: "legs 1-4 of scripts/commit-enforcement-audit.sh; of the commit_enforcement_tests, four drive git commit in a temp repository and the rest assert the generated hook text, the strict plumbing and the --strict/--stack refusal" + prediction: "pre-fix binary: FAIL at leg 1 (no --strict, no commit-msg hook); fixed binary: PASS" + test: "PMAT= bash scripts/commit-enforcement-audit.sh" + if_fails: "ticket linking stays a habit and #1126's warning-that-passes returns" + - id: strict_satd_is_a_refusal + rule: "the generated hook text carries the strict SATD refusal branch" + prediction: "the_pre_commit_satd_block_exits_1_under_strict passes" + test: "cargo test --lib the_pre_commit_satd_block_exits_1_under_strict" + if_fails: "SATD over the threshold commits under a green banner" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-gate-effect-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-gate-effect-v1.yaml new file mode 100644 index 0000000000..60b6be77fd --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-gate-effect-v1.yaml @@ -0,0 +1,410 @@ +metadata: + version: "2.0.0" + kind: kernel + created: "2026-08-18" + author: PAIML Engineering + registry: true + description: > + Gate-effect verification (CB-2100). pmat enforces "gates or theatre" across + the fleet and, until this rule, had nothing that checked whether its own + rules block anything. Every "enforced in CI" claim rests on one chain: a + REQUIRED STATUS CHECK CONTEXT STRING -> the job that reports that context -> + a step (or a Makefile/script one hop away) that invokes the rule -> whose + non-zero exit can still fail the job. Break any link and the rule is + decoration. + + The roots are the UNION of the repository's required contexts, taken from + branch protection. Nothing in the rule may name a gate: a rule that + hardcodes `gate` stops checking the day a repository renames a job, which + is the failure INV-2100-3 already describes, self-inflicted. + + Rule id: CB-2100. Every id the source backlog proposed collides with a live + rule (CB-1400 "Agent Contract Existence", CB-1403 "Assume-Guarantee Chain", + CB-1404 "Agent Comply Usage", CB-1211 "Codegen Fidelity", CB-1300 "CLI Arg + Contracts", CB-1302 "MCP Schema Contracts", CB-1305 "Contract Surface + Classification"), and reusing one would have deleted it. CB-21xx was + audited free across src/, contracts/, docs/, .github/, the work list and + the whole git history. v1 of this contract shipped the same engine as + CB-1411 with three invariants; v2 renames it to CB-2100 and adds four. + contract: comply-gate-effect + status: enforced + verification: + kani_status: not_discharged + measured_at: "pmat 3.32.0, kani 0.67.0, 2026-08-18" + detail: > + The three `kani_harness` names below are REAL harnesses — `#[kani::proof]` + functions at the bottom of src/services/gate_effect/kernel.rs, held to + their names by + tests_cb2100.rs::every_kani_harness_the_contract_names_exists. They are + NOT discharged: nothing in this repository has ever run them, and with + kani installed they cannot be run at all. Measured, not assumed: + + (1) `cargo kani --harness verify_gates_only_on_exit_code` fails with + "error: target `pmat-agent` in package `pmat` requires the features: + `mcp-integration`" — kani enumerates every target in the package, and one + of them does not build under default features. + + (2) `cargo kani --harness verify_gates_only_on_exit_code --features + mcp-integration` fails with "error: rustc 1.93.0-nightly is not supported + by the following package: sysinfo@0.39.6 requires rustc 1.95" — kani + 0.67.0 pins a nightly older than a transitive dependency's MSRV. + + (3) No .github/workflows job, Makefile target or script invokes kani, so + not one of the repository's `#[kani::proof]` harnesses is compiled by any + gate (count them with `grep -rn '#\[kani::proof\]' src/ | wc -l`; the + number moves, the fact that nothing runs them does not). `#[cfg(kani)]` code is not built by `cargo test` either. + + These harnesses are therefore ASPIRATIONAL: they state the intended + theorems and are the specification a future proof will discharge, but + `kind: kernel` here must not be read as "proved". Naming that is the + honest alternative to deleting the harnesses or letting the reader infer + a proof that never ran — a contract that names a proof which does not + discharge is exactly the defect class this backlog exists to rule on. + + To make them real: kernel.rs has no dependencies beyond the contract + attribute macro, so lifting it into its own workspace crate would give + kani a dependency graph it can build. That is a separate work item and + has not been done. + references: + - src/services/gate_effect/kernel.rs + - src/services/gate_effect/mod.rs + - src/services/gate_effect/graph.rs + - src/services/gate_effect/resolve.rs + - src/services/gate_effect/effect.rs + - src/services/gate_effect/reach.rs + - src/services/gate_effect/required.rs + - src/services/gate_effect/roster.rs + - src/services/gate_effect/ledger.rs + - src/cli/handlers/comply_handlers/check_handlers/check_gate_effect.rs + - docs/status/comply-enforcement-ledger.md + +equations: + reachable: + formula: > + reachable(G, R, n) = exists r in R, exists a path r -> ... -> n in G whose + every edge is live + domain: > + G a finite directed graph over node indices 0..node_count with edges + carrying a `live` flag; R a set of root indices; n a node index + codomain: bool + invariants: + - "INV-2100-1: every severity=error comply rule is reachable from SOME required context — the roots are unioned, never taken one at a time" + - "INV-2100-2: a reachable invocation whose failure cannot propagate is NOT reachable (not continue_on_error and not suppressed and exit_code_compared)" + - "reachable agrees with an independent transitive closure over any bounded graph" + - "neutering every edge leaves exactly the roots reachable" + - "an empty root set reaches nothing: fail closed, never a vacuous pass" + - "an out-of-range target is unreachable, never an error and never a pass" + preconditions: + - "node indices are less than node_count; out-of-range indices are ignored rather than trusted" + kani_harness: verify_reachable_matches_transitive_closure + + select_by_context: + formula: > + select_by_context(C, q) = min { i : C[i].context = q }, where C[i] = + (context, display_name) + domain: a list of (context, display) candidates and one required context string + codomain: Option + invariants: + - "INV-2100-3: reachability is computed against the required-check CONTEXT STRING, never the job display name" + - "two candidate lists differing only in display names select the same candidate" + - "a selected candidate's context equals the required string" + - "a display name equal to a required context never, on its own, satisfies reachability" + - "`gate` and `ci / gate` are different contexts and may resolve to different jobs" + - "INV-2100-7: no gate name is hardcoded anywhere in the rule; the roots are branch protection UNIONED WITH repository rulesets (PMAT-717) — two independent mechanisms, either of which may be the only one a repository configures" + preconditions: + - "the candidate list enumerates every context this repository's workflows can produce; an external reusable callee is Opaque, never a Job" + kani_harness: verify_select_ignores_display_names + + gates: + formula: "gates(printed_failure_verdict, exit_code) = not printed_failure_verdict or exit_code != 0" + domain: "one command's observed output classification and its exit status" + codomain: bool + invariants: + - "INV-2100-4: a command that PRINTS a failure verdict and exits 0 does not gate" + - "a command that printed no failure verdict gates: there was no verdict to contradict" + - "any non-zero exit gates" + preconditions: [] + kani_harness: verify_gates_only_on_exit_code + + gate_effect: + formula: > + enforced(rule) = exists ctx in required_contexts, exists job in + resolve(ctx) union needs_closure(resolve(ctx)), exists step in job.steps, + invokes(step, rule) and propagates(step) + domain: > + required_contexts is a non-empty list of GitHub branch-protection context + strings; workflows is the parsed set of .github/workflows/*.y[a]ml + codomain: > + GateEffectReport with passed() true only when every severity=error rule is + enforced and no hole was recorded + invariants: + - "INV-2100-1: every severity=error comply rule is reachable from a required check context" + - "INV-2100-2: a reachable invocation whose failure cannot propagate is NOT reachable" + - "INV-2100-3: reachability is computed against the required-check CONTEXT STRING, not the job display name" + - "INV-2100-4: a command that prints a failure verdict and exits 0 does not gate" + - "INV-2100-5: an invocation that can never succeed does not gate" + - "INV-2100-6: a job that COMPILES tests without executing them does not establish reachability for those tests" + - "INV-2100-7: no hardcoded gate name anywhere in the rule" + - "an empty severity=error roster is a failure, not a vacuous pass" + - "an empty or unresolvable required-context list is a failure, not a pass" + - "a required context produced by no job (phantom gate) is a failure" + - "a required context resolving into an unreadable external reusable workflow is a failure, because no rule can be SHOWN to run through it" + - "a workflow that does not parse is a hole in the reachability graph, and a hole fails" + - "zero jobs across all workflows is a failure, not a pass" + - "a required context that reaches no invocation is named in the verdict, not silently counted as coverage" + - "a required context that was READ IN FULL and reaches nothing is a measured zero; one that is opaque, phantom or unresolved is a HOLE, and the two must never render identically" + preconditions: + - ".github/workflows exists (otherwise the rule Skips: there is no Actions gate to verify)" + + context_string_resolution: + formula: > + context(job) = job.name if present else job.id; + context(caller.uses(callee)) = context(caller) + " / " + context(callee_job) + domain: a parsed workflow set and one required context string + codomain: Resolution in {Job, Opaque, Phantom} + invariants: + - "a top-level job reports as its display name when it has one, else its job id" + - "a reusable-workflow call reports one context per callee job, namespaced ` / `" + - "an external `owner/repo/...@ref` callee resolves to Opaque, never to a Job" + - "Opaque is never evidence of enforcement" + preconditions: + - "workflow YAML parses" + + failure_propagation: + formula: > + propagates(inv) = not job.continue_on_error and not step.continue_on_error + and not suppressed(line) and exit_code_compared(script, line) + and not compiles_only(line) and not dead(line) and not verdict_without_exit(script, line) + domain: one shell line invoking the rule, within its script and its job + codomain: boolean, with the reasons it is false + invariants: + - "`continue-on-error: true` at job or step level suppresses" + - "a `continue-on-error` EXPRESSION suppresses: `might be false` is not `provably propagates`" + - "`|| true`, `|| :`, `|| echo ...`, `|| exit 0` suppress" + - "a Makefile recipe line prefixed with `-` suppresses" + - "a pipeline suppresses unless the script sets pipefail (GitHub runs `run:` under `bash -e`, pipefail off)" + - "an exit code captured into a variable, consumed by `if`, or disarmed by `set +e` propagates only when a later line exits non-zero" + - "a `needs` edge under `if: always()` propagates only when the job inspects needs..result" + - "INV-2100-4: a wrapper that prints a failure verdict and then exits 0 suppresses" + - "INV-2100-5: a line after an unconditional failure under errexit never runs, and a job containing one can never succeed" + - "INV-2100-5 control: `exit 1` inside an `if` is a working gate, not an impossibility" + - "INV-2100-6: `--no-run`, `--dry-run` and `--list` build the invocation without executing it" + preconditions: + - "the invocation line was found in a `run:` block, or one hop away in a Makefile recipe or shell script" + + enforcement_ledger: + formula: > + ledger = { (rule, severity, status, carrier, file:line) : rule in roster }, + status in {ENFORCED, NEUTERED, UNREACHABLE} + domain: > + the comply rule REGISTRY — the clause ids the check builders register with + filter_check_by_config, as enumerated by enumerate_comply_rule_ids, the + same authority CB-1703 holds the documentation to + codomain: a deterministic markdown document + invariants: + - "the ledger covers exactly the registered rule set: no invented rows, no omissions" + - "one row per CB rule, each with a file:line citation" + - "a rule whose status cannot be established is UNREACHABLE, never blank" + - "a registered rule with no findable definition site is UNREACHABLE and says so" + - "an unenumerable or empty registry is an error, not a clean sheet" + - "the rendering is deterministic: no timestamps, no version stamps, rows sorted by rule id" + - "a required context that carries no rule is listed as carrying none" + - "the population is read from the existing registry, never recounted: one rule, one answer" + - "drift is keyed on the ledger's DATA — rule id, title, severity, status, carrier, and the FILE a rule is declared in — never on its presentation" + - "a moved line number is NOT drift: an edit above a rule declaration changes nothing about what the repository enforces" + - "the provenance label of an IDENTICAL root list is NOT drift: the same four contexts read from PMAT_REQUIRED_STATUS_CHECKS instead of the manifest are the same four contexts" + - "loosening drift must not blind it: a changed status, carrier, declaring file, severity, title or id, and a missing or added row, are each still drift" + preconditions: + - "the repository declares CB rules in-tree (src/cli/handlers/comply_handlers exists); a repo that merely runs the rules owes no ledger" + +preconditions: + - "pmat >= 3.32.0 with `pmat comply check` running CB-2100" + - "required contexts resolvable from PMAT_REQUIRED_STATUS_CHECKS, .github/required-status-checks.txt, or the GitHub branch-protection API" + +postconditions: + - "CB-2100 reports Skip only when .github/workflows/ is absent" + - "CB-2100 reports Fail with severity=error whenever any invariant above is violated" + - "CB-2100's Fail message names the job and the key responsible" + - "CB-2100 fails when docs/status/comply-enforcement-ledger.md is missing or has drifted" + +falsification_tests: + - id: f_1 + rule: "F-1: the job invoking `pmat comply check` carries `continue-on-error: true` and CB-2100 still passes" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::f1_job_level_continue_on_error_fails_citing_job_and_key + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_2 + rule: "F-2: the step is `pmat comply check || true` and CB-2100 still passes" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::f2_or_true_fails + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_3 + rule: "F-3: a job whose DISPLAY NAME equals the required context string is credited as the job that reports it" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::f3_a_display_name_equal_to_the_required_context_is_not_a_match + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_4 + rule: "F-4: a workflow with zero jobs yields a pass instead of a fail" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::f4_zero_jobs_fails_closed + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_5 + rule: "F-5: an unfetchable branch-protection API yields a pass instead of a fail" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::f5_unresolvable_required_contexts_fail_closed + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_6_control + rule: "F-6 (the control): the live shape — required context `ci / gate` beside an unrequired job literally named `gate` — fails, so every other fixture proves nothing" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::f6_required_context_beside_an_unrequired_job_named_the_same_thing_passes + if_fails: "P0 — this contract's guarantee does not hold" + - id: f + rule: "INV-2100-4: a wrapper that prints a failure verdict and exits 0 is credited as a gate" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::inv_2100_4_a_wrapper_that_prints_failed_and_exits_zero_does_not_gate + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_6 + rule: "INV-2100-5: an invocation inside a job that can never succeed is credited as a gate" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::inv_2100_5_a_job_that_can_never_succeed_does_not_gate + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_control + rule: "INV-2100-5 control: `exit 1` inside an `if` is misread as an impossibility, failing every correctly-written workflow" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::inv_2100_5_a_guarded_exit_is_a_gate_not_an_impossibility + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_7 + rule: "INV-2100-6: a job that compiles a test suite without running it establishes reachability for those tests" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::inv_2100_6_compiling_a_test_is_not_running_it + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_8 + rule: "INV-2100-7: the rule hardcodes a gate name and so stops checking the day a job is renamed" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::inv_2100_7_no_gate_name_is_hardcoded_in_the_rule + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_9 + rule: "INV-2100-1: the roots are taken one at a time, so one healthy required check answers for four" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::inv_2100_1_any_root_may_carry_the_roster + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_10 + rule: "an empty severity=error roster yields a vacuous pass" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests.rs::control_roster_is_not_empty + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_11 + rule: "an empty CB rule roster yields an empty ledger instead of an error" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::ledger_over_an_empty_roster_is_an_error_not_a_clean_sheet + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_12 + rule: "the ledger invents or omits rows because it recounts the rules instead of reading the registry" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::the_roster_is_exactly_the_registered_rule_set + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_13 + rule: "a configuration that declares rules and grades none of them error is reported as a clean roster" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::a_config_whose_checks_are_all_sub_error_is_a_hole_with_a_diagnosis + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_14 + rule: "the contract names kani harnesses that do not exist, or that were renamed out from under it — `#[cfg(kani)]` code is never compiled by `cargo test`, so nothing would notice" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::every_kani_harness_the_contract_names_exists + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_15 + rule: "`kind: kernel` plus a `kani_harness` per equation is read as a discharged proof, though nothing in this repository can run one" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::the_contract_states_whether_its_kani_harnesses_discharge + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_16 + rule: "the not_discharged note rots into a false claim: the contract says the harnesses are discharged while nothing runs kani, or a runner is added and the note is left stale" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::a_discharged_claim_requires_something_that_actually_runs_kani + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_17 + rule: "an opaque required context — one resolving into a workflow this repository cannot read — renders identically to a context that was read in full and reaches nothing, so a hole is reported as a measured zero" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::an_opaque_root_is_not_rendered_as_a_measured_zero + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_18 + rule: "a phantom required context renders as a measured zero" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::a_phantom_root_is_not_rendered_as_a_measured_zero + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_control_2 + rule: "the control: every root is rendered `unknown`, which satisfies the two tests above and destroys the table" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::a_root_that_carries_the_roster_still_reads_as_yes + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_19 + rule: "a ledger row renders a blank Title, which is indistinguishable from `has no title`, `never names itself` and `the scanner lost it`" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::the_ledger_never_renders_a_blank_title + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_20 + rule: "the roster's `#[cfg(test)]` guard is a substring match, so a file that MENTIONS the attribute in a comment loses every rule declared below the mention" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::a_cfg_test_mention_in_a_comment_does_not_truncate_the_roster_scan + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_control_3 + rule: "the control: loosening that guard makes the roster cite lines that do not declare the rule they are attributed to" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::every_citation_points_at_a_line_that_names_its_rule + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_21 + rule: "a check owns a CB id and never reports it, so no finding it emits can be tied back to its ledger row" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::every_rule_that_owns_a_cb_id_reports_it_at_runtime + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_22 + rule: "an edit two lines above a rule declaration moves its citation and is reported as ledger drift, so the gate reddens on changes it does not measure" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::an_edit_above_a_rule_declaration_is_not_ledger_drift + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_23 + rule: "supplying the IDENTICAL required contexts through PMAT_REQUIRED_STATUS_CHECKS instead of the manifest is reported as ledger drift" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::the_provenance_of_the_root_list_is_not_ledger_drift + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_control_4 + rule: "the control: drift is loosened until a changed status, carrier, declaring file, severity, title or id no longer registers" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::a_changed_status_carrier_or_file_is_still_ledger_drift + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_control_5 + rule: "the control: drift is loosened until a ledger missing a whole rule still reads as up to date" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::a_missing_row_is_still_ledger_drift + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_24 + rule: "the ledger is non-deterministic, so nobody can diff it and drift goes unnoticed" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests_cb2100.rs::the_rendered_ledger_is_deterministic + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_25 + rule: "a required context that no job produces is treated as absence of evidence rather than a phantom gate" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests.rs::a_phantom_required_context_fails + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_26 + rule: "an unreadable external reusable workflow is credited as enforcement" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests.rs::falsify_2100_3b_external_reusable_workflow_is_opaque_not_compliant + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_27 + rule: "a `run:` block that pipes the invocation is credited with its exit code although bash -e leaves pipefail off" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests.rs::falsify_2100_2c_piped_invocation_loses_its_status + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_28 + rule: "an `if: always()` summary job is credited with its needs' failures although it never inspects needs..result" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests.rs::if_always_without_a_result_check_breaks_the_edge + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_29 + rule: "every fixture fails, including the healthy one, so the rule proves nothing" + prediction: "the named test fails when this condition holds" + test: src/services/gate_effect/tests.rs::control_a_plain_enforcing_job_passes + if_fails: "P0 — this contract's guarantee does not hold" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-numeric-claims-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-numeric-claims-v1.yaml new file mode 100644 index 0000000000..6a88cfc4f8 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-numeric-claims-v1.yaml @@ -0,0 +1,283 @@ +metadata: + version: "1.0.0" + kind: kernel + created: "2026-08-24" + author: PAIML Engineering + registry: true + description: > + CB-2104 `pmat comply numeric-claims`. Numbers a repository writes down about + itself, judged against each other. + + Two rules over one corpus of git-tracked files. They cover disjoint surfaces + and neither subsumes the other — on the two reference repositories R2 found + none of R1's findings and R1 found none of R2's: + + R1 REPLICATED DIVERGENT CLAIM reads UNNAMED prose numerals. N files carry + the same sentence with a number in it and the numbers disagree, so at least + N - m of them are wrong. Its specimen: 45 per-crate READMEs in aprender + saying `70 workspace crates` where 5 say `75` and `cargo metadata` says 78. + + R2 CONTRADICTION reads NAMED quantities — config keys, Rust consts — and + their same-line annotations. Its specimen: this repository's + src/tests/binary_size.rs declaring `50 * 1024 * 1024` (52,428,800) with the + comment "aligned with .pmat-metrics.toml binary_max_bytes", which is + 50,000,000. + + SEVERITY IS WARN AND THE CHECK NEVER BLOCKS. Findings exit 0, whatever their + count. Exit 2 is reserved for UNMEASURABLE, and that distinction is the + point of the rule's design: "I analysed 12,693 numbers and found nothing" + must never be byte-identical to "`git ls-files` returned nothing and I + analysed nothing", which is exactly what the researched design printed for + both. + + WHY IT IS NOT A DUPLICATE OF CB-2101 OR CB-2102. The unit of analysis is + different. CB-2101 binds ONE DECLARED key to one hand-written binding; + CB-2102 binds ONE DECLARED metric to its reproducing command; both are total + over a curated set and both cost roughly fifteen lines of hand-written TOML + per number. CB-2104 compares N UNDECLARED copies against each other and + requires no prior human declaration at all — which is the whole point, + because nobody declares the copy. Measured: CB-2101 covers 17 of this + repository's 539 config keys and 0 of aprender's 14,385, because it opens + exactly one path and aprender's are in crates/*/. CB-2104 reads 3,883 + mentions here and 25,522 there. CB-2101 also DOCUMENTED the binary-size + contradiction inside a justification string and then passed it, because + binary_max_bytes is kind = "budget". + + The relationship runs the other way: every R1 finding's remediation is + "declare this quantity once, with the command that reproduces it", i.e. + enroll it in .pmat-ratchet.toml or a CB-2101 binding. CB-2104 is the + discovery front-end that populates the two curated checks. It can never do + their job — it cannot tell you WHICH of 45 values is correct. Only an anchor + can, so every finding reports `anchored: false` and a disagreement FLOOR, + never a wrong-count. + + WHAT WAS RESEARCHED AND DELIBERATELY NOT BUILT, each rejected on a + measurement rather than on taste: + + (a) Benford's Law. Rust source scores MAD 0.0382 against Nigrini's 0.015 + nonconformity band BEFORE any contamination, and injecting fabricated + numbers moves the corpus TOWARD conformance. A check that gets happier when + you plant fraud is worse than no check. + + (b) Anchored-claim execution (run the documented predicate, compare). 0.1% + coverage — 12 anchored numerals in 11,282 claim-shaped ones — and 0/4 + precision on this repository. All four findings were well-written documents, + including one correctly pinned to a SHA. Acting on the flagship finding + would have replaced a correctly pinned number with one that rots next week. + + (c) Automatic anchor resolution for R1. Built and measured: 8 labels on + aprender, all of them spec-doc examples, 0 on pmat, and it misses the one + anchor that matters because aprender's README cites `cargo metadata + --no-deps`, which names a method without printing a number. + + (d) The cohort rule AS DESIGNED, at 1/3 precision. Two of three flagship + findings were machine-generated per-crate headers whose counts co-vary + correctly. Two guards, each derived from one of those audited false + positives, restore it to 1/1 and both ship on. + contract: comply-numeric-claims + # ADVISORY, not enforced, and the word is chosen. CB-2104 is a standalone + # subcommand; no `pmat comply check` clause runs it, no CI job gates on it, + # and `default_checks` registers it at severity Warning on purpose. Writing + # `enforced` here would be a claim about this repository that nothing in it + # backs — the exact shape CB-2100 exists to find. + status: advisory + verification: + kani_status: not_applicable + measured_at: "pmat 3.32.0, 2026-08-24; pmat 583ea9ac2, aprender d6c6c6f8f" + detail: > + No kani harness is declared, and none is claimed. Every rule here is a + comparison over bytes that are already checked in: there is no arithmetic + kernel with an invariant a bounded proof would add confidence to, and a + `kind: kernel` with aspirational harnesses would be the decorative + verification claim CB-2100 exists to find. + + What actually runs is the module's own test suite — + src/services/numeric_claims/{census,cohort,corpus,extract,frame,annotate,render,rules}_tests.rs + — reachable from `cargo test --lib numeric_claims`, plus the committed + self-test fixture, which runs on EVERY invocation of the shipped binary + and not only under `cargo test`. + references: + - src/services/numeric_claims/mod.rs + - src/services/numeric_claims/census.rs + - src/services/numeric_claims/render.rs + - src/services/numeric_claims/corpus.rs + - src/services/numeric_claims/frame.rs + - src/services/numeric_claims/cohort.rs + - src/services/numeric_claims/extract.rs + - src/services/numeric_claims/annotate.rs + - src/services/numeric_claims/rules.rs + - src/cli/handlers/comply_handlers/numeric_claims_handler.rs + - tests/fixtures/numeric_claims/README.md + +equations: + exit_policy: + formula: "exit(report) = 2 <=> status(report) = UNMEASURABLE ; else 0" + domain: "one NumericClaimsReport over one repository" + codomain: "{0, 2}" + invariants: + - "INV-2104-1: findings never change the exit code. A repository with 45 divergent claims exits 0, because this check is advisory and the user's constraint was explicit" + - "INV-2104-2: UNMEASURABLE is the ONLY non-zero exit, and it never carries findings — when the check cannot vouch for its own rules, a list of things it thinks it found is worse than silence" + - "an empty corpus is UNMEASURABLE, not clean: 'no contradictions' over nothing is a claim the run did not earn" + preconditions: [] + + self_test: + formula: > + measurable(run) => self_test(committed fixture) = (4/4 planted recovered, + 0 innocent flagged, 0 unexpected) + domain: "tests/fixtures/numeric_claims, include_str!-ed into the binary" + codomain: "SelfTest { passed, recovered, missed, false_positives, unexpected }" + invariants: + - "INV-2104-3: the self-test runs BEFORE the real corpus on every invocation, and a failure suppresses the real result entirely. A rule that has silently stopped firing and a repository that is genuinely clean produce the same empty output; only a corpus that MUST fire separates them" + - "INV-2104-4: one planted defect per rule family. A fixture that exercises three of four rules leaves the fourth's silence indistinguishable from a rule that no longer runs" + - "INV-2104-5: the self-test always runs at CohortConfig::default(), never at the user's settings. It is a control on the RULES, not on the configuration, and a --min-sites 20 that made it fail would be reporting the wrong thing" + - "the innocent half is REPLICATED — each of the 26 classes across eight files with a 6/2 value split, the exact shape R1 hunts — because an innocent control that could not fire even if the rules rotted measures nothing" + - "the fixture lives under tests/fixtures/, which the corpus rules exclude, so the four defects this check plants in its own repository can never reach a user's report" + preconditions: [] + + vacuity: + formula: > + UNMEASURABLE <= files_scanned = 0, or (files_scanned > 20 and + framed_numerals = 0), or (files_scanned > 20 and mentions = 0), or not a + git work tree, or the self-test did not round-trip + domain: "the census of one run" + codomain: "Option" + invariants: + - "INV-2104-6: stated in .pmat-ratchet.toml's idiom — a metric that measures 0 against a baseline above 0 is UNMEASURABLE, not passed" + - "INV-2104-7: the census is UNCONDITIONAL and machine-readable. A clean result that does not carry files_scanned, r1_framed_numerals, r2_mentions and every suppression counter is a bug, not a pass" + - "INV-2104-8: every guard that can hide a finding reports how often it did — G1 generated, G2 multi-slot, derivation, unit-ambiguity, unresolved-xref" + - "the guard counters are ORDERED: G1 runs before G2, so a 0 in the G2 column records that G1 got there first and never that G2 is idle. The renderer says so rather than letting a reader infer that G2 is dead weight" + - "a corpus that is merely SMALL is not a rotted one: the plausibility rules only accuse the extractor once more than 20 files have been scanned" + preconditions: + - "git is available and the path is inside a work tree" + + precision: + formula: "precision(min_sites, guards) is monotone in min_sites and in guards-on" + domain: "the reference corpus: pmat, aprender, bashrs, batuta, renacer, trueno, actix-web" + codomain: "a measured hit rate" + invariants: + - "INV-2104-9: --min-sites below the default 7 MUST print a precision warning. Measured at --min-sites 3: 1/10 on the reference corpus, the rejected nine being step numbers in per-language example workflows, sprint records written weeks apart, and three tickets about three different modules" + - "INV-2104-10: --include-generated (G1 off) MUST print a precision warning. With either guard off the reference corpus measured 1/3" + - "the rule reports DISAGREEMENT, never a verdict on which value is right. Nothing in the output says a value is wrong, or fabricated: a number that is wrong and a number that was invented are indistinguishable to every rule here" + - > + MEASURED BLIND SPOT, found by running the shipped check over the reference + corpus rather than by testing it. On bashrs 90e68cf0af R1 reports one + finding: `- All N rules already verified as implemented` across eight + docs/RULE-CLASSIFICATION-BATCH*.md files, saying 20 six times, then 10, + then 6. It is a false positive of exactly the class G2 exists to kill — + per-entity parameterisation — and G2 cannot see it, because the entity + (the batch number) is in the FILENAME rather than in the line, so the line + has exactly one varying slot and looks like a replicated singleton. + + The spec's negative-control table records bashrs at 0 findings. That row + was an R2-only measurement: R1 had never been run over bashrs, or over any + of the five control repositories. This is therefore the first R1 + measurement on them, and it moves the honest claim from "5 healthy repos, + 0 false positives" to "4 of 5 silent; bashrs yields 1 R1 false positive". + batuta, renacer and trueno stay at 0/0; actix-web was not re-measured here. + + Reported rather than patched. A third guard keyed on path tokens would + change the rule's measured ablation table, and the spec's own instruction + is that any change to the check's loudness must be assumed to break + precision until re-measured on the reference corpus. + preconditions: [] + +preconditions: + - "a pmat whose `pmat comply` carries the `numeric-claims` subcommand (added after 3.32.0; no released version is claimed here, because none has shipped it)" + - "the project root is inside a git work tree" + +postconditions: + - "the census is printed on every run, findings or not, measurable or not" + - "findings exit 0; only UNMEASURABLE exits 2" + - "every reported site carries file:line, so a developer can act without re-running the tool" + +falsification_tests: + - id: falsify_nc_001 + rule: "FALSIFY-NC-001: the self-test cannot fail — '4/4 recovered' is a constant rather than a measurement. Ablate each planted defect in turn and the runner must go red for each" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::removing_a_planted_defect_fails_the_self_test + if_fails: "P0 — the strongest guard in the design is decoration" + - id: falsify_nc_002 + rule: "FALSIFY-NC-002: a planted defect is not recovered, or a rule family has no planted defect at all so its silence proves nothing" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::the_self_test_recovers_all_four_planted_defects + if_fails: "P0 — a rule that has stopped firing would report a clean tree" + - id: falsify_nc_003 + rule: "FALSIFY-NC-003: an innocent number is flagged, or the innocent half is too small to be a control" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::the_self_test_flags_none_of_the_innocent_numbers + if_fails: "P0 — precision is above 70% only because the check is quiet" + - id: falsify_nc_004 + rule: "FALSIFY-NC-004: an empty or non-git corpus reports as clean rather than UNMEASURABLE" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::r13_unmeasurable_corpus_exits_two + if_fails: "P0 — 'we could not measure it' would read as 'it did not regress'" + - id: falsify_nc_005 + rule: "FALSIFY-NC-005: a rotted extractor over a real corpus passes as clean, or the guard fires on a corpus that is merely small" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::plausibility_separates_nothing_to_say_from_nothing_measured + if_fails: "P0 — this contract's guarantee does not hold" + - id: falsify_nc_006 + rule: "FALSIFY-NC-006: findings change the exit code. The user's hard constraint: this check warns, it never blocks" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::r12_exit_code_is_zero_with_findings + if_fails: "P0 — the check would start blocking builds it was promised never to block" + - id: falsify_nc_007 + rule: "FALSIFY-NC-007: a run reports without a populated census, so a silent pass carries no proof it ran" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::r14_census_is_always_emitted + if_fails: "P0 — the defect the vacuity guard exists to prevent" + - id: falsify_nc_008 + rule: "FALSIFY-NC-008: a guard hides a finding without counting it, so a suppression leaves no trace and is indistinguishable from a rule that never ran" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::r15_suppression_counters_are_reported + if_fails: "P0 — this contract's guarantee does not hold" + - id: falsify_nc_009 + rule: "FALSIFY-NC-009: --min-sites is lowered below the default without the precision warning, which is the one knob that destroys the check (1/10 measured at 3)" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::r16_min_sites_below_default_warns + if_fails: "P0 — precision would silently fall by an order of magnitude" + - id: falsify_nc_010 + rule: "FALSIFY-NC-010: the fixture's planted defects reach the real report, because the fixture-tree exclusion rotted and pmat started reporting its own test data" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::the_fixture_is_excluded_from_the_real_corpus + if_fails: "P0 — every run on this repository would carry four false findings" + - id: falsify_nc_011 + rule: "FALSIFY-NC-011: R1 reads JSON, pulling 3.3M numerals out of machine-written contract.json files, or R2 stops reading it" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::r2_reads_json_and_r1_does_not + if_fails: "P1 — 93% of the corpus would be machine-written interchange" + - id: falsify_nc_012 + rule: "FALSIFY-NC-012: the census stops leading the output, or a clean measured run prints the same bytes as an empty corpus" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/render_tests.rs::a_clean_run_still_shows_its_working + if_fails: "P0 — the exact failure of the researched design" + - id: falsify_nc_013 + rule: "FALSIFY-NC-013: a finding is reported without naming file:line for every site, so a developer has to re-run the tool to find out where to look" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/render_tests.rs::every_site_is_named_with_file_and_line + if_fails: "P1 — this contract's guarantee does not hold" + - id: falsify_nc_014 + rule: "FALSIFY-NC-014: the output claims to know which of two disagreeing values is wrong, or calls a number fabricated. The check reports contradiction; only an anchor can report truth" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/render_tests.rs::the_output_never_claims_to_know_which_value_is_wrong + if_fails: "P0 — the check would assert something no rule in it can establish" + - id: falsify_nc_015 + rule: "FALSIFY-NC-015: an UNMEASURABLE run prints findings it cannot vouch for, or does not say it was unmeasurable" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/render_tests.rs::unmeasurable_says_so_and_prints_no_findings + if_fails: "P0 — this contract's guarantee does not hold" + - id: falsify_nc_016 + rule: "FALSIFY-NC-016: the JSON drops the exit code, the status or the self-test result, so a CI job cannot tell a clean run from an unmeasurable one" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/render_tests.rs::json_carries_the_exit_code_the_status_and_the_self_test + if_fails: "P1 — this contract's guarantee does not hold" + - id: falsify_nc_017 + rule: "FALSIFY-NC-017: the G2 suppression counter's zero is presented as evidence G2 is idle, when G1 simply ran first" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/render_tests.rs::the_g2_zero_is_not_presented_as_evidence_g2_is_idle + if_fails: "P1 — a working guard would be read as dead weight and removed" + - id: falsify_nc_018 + rule: "FALSIFY-NC-018: CB-2104 is registered under an id .pmat.yaml cannot address, or registered at severity=error, which would put an advisory check into the roster CB-2100 checks reachability for and make it block" + prediction: "the named test fails when this condition holds" + test: src/services/numeric_claims/census_tests.rs::cb_2104_is_registered_as_a_warning_and_can_be_disabled + if_fails: "P0 — the check would either be unaddressable or would block" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-ratchet-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-ratchet-v1.yaml new file mode 100644 index 0000000000..35e5e64256 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-ratchet-v1.yaml @@ -0,0 +1,323 @@ +metadata: + version: "1.0.0" + kind: kernel + created: "2026-08-19" + author: PAIML Engineering + registry: true + description: > + Ratchet baselines (CB-2102). A threshold is a number somebody guessed once; + a ratchet is the number the repository actually had. `.pmat-metrics.toml:45` + is this project's own worked example of both failure modes at once: it + declares `max_unwrap_calls = 100`, annotates it `Current: 570`, and sits in + a tree that measures 20,390 by the predicate `.pmat-ratchet.toml` pins. + Three numbers, no two of which agree, none of which anything in the tree + reads, and a green build throughout. + + CB-2102 replaces the guess with a captured value and a direction. + `.pmat-ratchet.toml` records what each metric WAS; the gate asserts only + `observed <= baseline`; a scheduled pass LOWERS a baseline whenever the + measurement drops; and RAISING one requires a written `justification` on + that entry, checked against the previous committed version of the file. + + The load-bearing decision is the SCOPE PREDICATE, so it is not in prose and + not in source: every entry carries the exact command that reproduces its + baseline plus an explicit `includes_test_files`, and the gate RUNS that + command rather than reading the number. The "unwrap count" of this + repository has been quoted as 570, 11,002, 20,326 and 20,378 inside a + single programme of work; two of those differ by 9,324 because they meant + different file sets, and one moved by 52 within a single session. + + PROVENANCE, and the defect this contract itself closes. The pure half of + this rule — `src/services/metrics_ratchet/{kernel,config}.rs`, 1,836 lines + with 34 falsification tests — was already committed on release/3.32.0 and + had NO caller anywhere in the tree: no comply check, no `.pmat-ratchet.toml` + to read, a doc comment naming a verifier (`command_reproduces_measurement`) + that did not exist, and two `contracts/` files named in its module headers + that were never written, this one included. It was the exact defect class + CB-2100 exists to find, sitting inside the module written to find it. This + version adds the drive (measurement, git history, write-back), the comply + check, the baseline file, and this contract — and fixes one live bug the + orphaning had hidden: `evaluate_ratchet` folded `Outcome::Ok` over an empty + metric map, so a ratchet declaring nothing passed every run forever. + + Rule ids: CB-2102 (ratchet) and CB-2101 (threshold coherence). The module + previously used CB-1421/CB-1420 in prose only — neither was ever registered, + so neither could be addressed from `.pmat.yaml` or appear in CB-2100's + enforcement ledger. They were renamed rather than left as a second name for + the same rule. CB-1403/CB-1404 were NOT reused: both are live rules + ("Assume-Guarantee Chain", "Agent Comply Usage"), and taking either id would + have deleted it. + contract: comply-ratchet + status: enforced + verification: + kani_status: not_discharged + measured_at: "pmat 3.32.0, kani 0.67.0, 2026-08-19" + detail: > + The two `kani_harness` names below are REAL harnesses — `#[kani::proof]` + functions in `mod kani_proofs` at the bottom of + src/services/metrics_ratchet/kernel.rs, held to their names by + drive_tests.rs::every_kani_harness_the_contract_names_exists. They are NOT + discharged: nothing in this repository has ever run them, and with kani + installed they cannot be run at all. Measured, not assumed, and identical + to the blockers recorded for CB-2100: + + (1) `cargo kani --harness verify_next_baseline_monotone_idempotent` fails + with "error: target `pmat-agent` in package `pmat` requires the features: + `mcp-integration`" — kani enumerates every target in the package and one + of them does not build under default features. + + (2) Adding `--features mcp-integration` fails with "error: rustc + 1.93.0-nightly is not supported by the following package: sysinfo@0.39.6 + requires rustc 1.95" — kani 0.67.0 pins a nightly older than a transitive + dependency's MSRV. + + (3) No .github/workflows job, Makefile target or script invokes kani, so + none of this repository's `#[kani::proof]` harnesses is compiled by any + gate. `#[cfg(kani)]` code is not built by `cargo test` either, which is + why a test pins the names rather than the compiler. + + These harnesses are therefore ASPIRATIONAL: they state the intended + theorems and are the specification a future proof will discharge, but + `kind: kernel` here must NOT be read as "proved". What actually runs is + kernel_tests.rs, which checks the same theorems over a bounded box and at + the i64 extremes. That is a test, not a proof, and this contract says so + rather than letting a reader infer a proof that never ran — a contract + naming a harness that silently does not run is precisely the defect class + this backlog exists to rule on. + references: + - src/services/metrics_ratchet/kernel.rs + - src/services/metrics_ratchet/config.rs + - src/services/metrics_ratchet/measure.rs + - src/services/metrics_ratchet/history.rs + - src/services/metrics_ratchet/rewrite.rs + - src/services/metrics_ratchet/mod.rs + - src/cli/handlers/comply_handlers/check_handlers/check_metrics_ratchet.rs + - .pmat-ratchet.toml + +equations: + verdict: + formula: "ratchet_verdict(b, o) = Fail <=> o > b" + domain: "an i64 captured baseline and an i64 observation, both in the metric's declared unit" + codomain: "Pass | Fail" + invariants: + - "INV-2102-1: verdict(b, o) = Fail <=> o > b" + - "equal to the baseline is not 'exceeds': verdict(b, b) = Pass, or every metric is red the day it is captured" + - "no direction parameter exists: every ratcheted metric is normalised so that bigger is worse, because a direction on a comparator is one more thing that can be set the wrong way round and silently invert a gate" + preconditions: + - "the metric declares the debt, never the virtue (uncovered lines, not covered ones)" + kani_harness: verify_next_baseline_monotone_idempotent + + next: + formula: "next_baseline(b, o) = o when o <= b ; = b when o > b" + domain: "an i64 captured baseline and an i64 observation" + codomain: i64 + invariants: + - "INV-2102-2: monotone non-increasing — next(b, o) <= b for every o, so no automated pass can ever loosen a baseline whatever it measures" + - "INV-2102-3: idempotent — next(next(b, o), o) = next(b, o)" + - "a Fail leaves the baseline untouched" + preconditions: [] + kani_harness: verify_next_baseline_monotone_idempotent + + ratchet: + formula: > + outcome(report) = Fail when any metric verdict is Fail, or any baseline + rose against the previous committed file without a justification, or any + hole was recorded + domain: > + .pmat-ratchet.toml, the output of running each metric's own `command` + from the repository root under `bash -o pipefail -c`, and the newest + COMMITTED version of the same file that differs from the one on disk + codomain: "RatchetReport { metrics, unjustified_raises, holes, outcome }" + invariants: + - "INV-2102-4: a metric absent from the measurement run, or measured as Unavailable, is FAIL — 'we could not measure it' must never read as 'it did not regress'" + - "INV-2102-5: RAISING a baseline requires an explicit justification on that entry" + - "an empty [metric.*] set is a FAILURE, not a vacuous pass: a ratchet with no metrics cannot fail and is not a gate" + - "an unreadable or unparsable .pmat-ratchet.toml is a FAILURE" + - "an unknown schema version is a FAILURE: a future file must not be read with today's meaning" + - "a metric whose command did not run, exited outside {0,1}, or printed something that is not a count, is FAIL" + - "exit 1 is a legitimate count of zero: the grep family uses it for 'no matches'" + - "INV-2102-6: a measurement of 0 against a baseline above 0 is UNAVAILABLE unless the metric declares `zero_is_reachable = true`. A rotted `git grep` pathspec and a genuine zero are byte-identical at the shell — both print 0, both exit 1, neither writes to stderr — so the exit-code guard cannot separate them and the baseline is the only place they differ. Measured on the shipped binary before the guard existed: repointing one metric's pathspec at a directory that does not exist made the coherence audit report `FIRING measured 0 against limit 100` and exit 0, while the ratchet read `0 <= 20390` as a Pass" + - "the measurement shell is `bash -o pipefail`: without pipefail the canonical ` | wc -l` reports wc's status, so a producer that failed outright reads as a clean zero — and a ratchet, which only ever looks upward, greets that as the largest improvement in the project's history" + - "an unreadable previous version of the file is a HOLE, never `None`: `None` legitimately means 'initial capture, nothing was raised', and letting an unreadable history borrow that meaning hides an unjustified raise on exactly the machines where history is hardest to read" + - "deleting .pmat-ratchet.toml from a repository that once committed one is a FAILURE, not a Skip" + - "the lowering pass writes back min(baseline, observed) and never anything larger, never for an unmeasured metric, and preserves the file's comments byte for byte — a job that strips a config's documentation each time it runs is worse than no job" + - "the lowering pass re-parses what it wrote and verifies it says exactly what was asked, because a plausible-looking wrong number silently becomes the new truth" + - "INV-2102-7: every measurement is bounded by the budget the metric declares in `timeout_secs`, defaulting to 300s when it declares none, and exceeding that budget is UNAVAILABLE — which is FAIL by INV-2102-4 — never a pass. The budget is per-metric because one constant has to bound a runaway AND let a legitimately expensive measurement finish, and no single number does both: `unwrap_calls_shipped_code` is a cold full-crate clippy that measured 203s and passed on intel-clean-room-8 (run 34676994867) and was killed at the 300s default on the more loaded intel-clean-room-6 (run 34680577045) — the same commit and the same command, reported as a broken measurement on one host and a clean one on the other. Declaring a wider budget therefore buys time, never a verdict; and the lowering pass must preserve the declaration, since a budget a nightly job silently dropped would re-arm this on the next slow runner with nothing in the diff but a baseline. Held by src/services/metrics_ratchet/drive_tests.rs::a_metric_declares_its_own_budget" + preconditions: + - ".pmat-ratchet.toml exists (otherwise CB-2102 Skips: the project declares no baselines), and bash and git are available" + +preconditions: + - "pmat >= 3.32.0 with `pmat comply check` running CB-2102" + - "the project root is inside a git work tree" + +postconditions: + - "CB-2102 reports Skip only when .pmat-ratchet.toml is absent AND was never committed" + - "CB-2102 reports Fail with severity=error whenever any invariant above is violated" + - "CB-2102's Fail message names the metric and both numbers" + +falsification_tests: + - id: f_1 + rule: "F-1: a change adds one to a metric (B+1) and the ratchet still passes" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::falsify_2102_1_one_added_unwrap_fails_against_the_measured_baseline + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_2 + rule: "F-2: an improvement (B-3) fails, or the lowering pass does not rewrite the baseline to B-3" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::falsify_2102_2_removing_three_passes_and_lowers_the_baseline + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_3 + rule: "F-3: the file is edited upward with no justification and the ratchet still passes — the ratchet must not be silently loosenable, which is the whole point" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::falsify_2102_3_raising_a_baseline_without_justification_fails + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_3_control + rule: "F-3 control: raise detection reads the direction of the edit rather than the sign of the metric" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::falsify_2102_3_raise_detection_is_direction_free + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_4 + rule: "F-4: a metric absent from the measurement run passes" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::falsify_2102_4_missing_measurement_fails_it_does_not_pass + if_fails: "P0 — this contract's guarantee does not hold" + - id: f + rule: "INV-2102-1: the verdict does not fail exactly when the observation exceeds the baseline" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::inv_2102_1_verdict_fails_exactly_when_observed_exceeds_baseline + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_5 + rule: "INV-2102-2: the lowering arithmetic raises a baseline" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::inv_2102_2_next_baseline_is_monotone_non_increasing + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_6 + rule: "INV-2102-3: running the lowering pass twice differs from running it once" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::inv_2102_3_next_baseline_is_idempotent + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_7 + rule: "a ratchet declaring no metrics folds to Ok and passes every run forever while reading as a gate — the live bug this rule's own module carried while it was orphaned" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::an_empty_metric_set_is_a_failure_not_a_clean_sheet + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_8 + rule: "` | wc -l` with a failing producer is read as a count of zero — the single most dangerous shape in the design, and invisible to a gate that only looks upward" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::a_failing_producer_in_a_pipeline_is_unavailable_not_a_zero + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_control + rule: "the control: an honest pipeline of exactly that shape stops measuring, so the guard above has broken every metric" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::an_honest_pipeline_measures + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_9 + rule: "grep's exit 1 for 'no matches' is treated as a broken measurement, so a metric can never legitimately reach zero" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::no_matches_is_a_zero_not_a_failure + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_10 + rule: "prose, or nothing, is parsed as a count" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::non_numeric_output_is_not_a_measurement + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_11 + rule: "an unreadable git history is passed through as `None`, which means 'initial capture' — so an unjustified raise becomes invisible" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::an_unreadable_prior_is_a_hole_not_an_initial_capture + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_12 + rule: "deleting .pmat-ratchet.toml is a way of passing CB-2102" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::deleting_the_ratchet_file_is_not_a_way_of_passing + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_13 + rule: "the lowering pass destroys the file's comments, or writes a number it did not verify" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::lowering_preserves_comments_and_verifies_what_it_wrote + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_14 + rule: "the lowering pass raises a baseline when the measurement grew" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::lowering_never_raises_a_baseline + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_15 + rule: "a metric's pathspec rots so that it matches no files, prints a clean 0, and both gates greet the largest possible regression in measurement quality as the largest improvement in the project's history" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::a_zero_against_a_nonzero_baseline_is_a_hole_not_the_best_day_in_project_history + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_control_2 + rule: "the control: the zero guard rejects every zero, so a metric can never reach the one number every ratchet is trying to get to" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::zero_is_still_reachable_when_it_is_declared_or_already_the_baseline + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_16 + rule: "this repository's own committed ratchet is red, or was captured from a number rather than from its command" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::the_committed_ratchet_holds_at_head + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_17 + rule: "a committed metric's command no longer runs, so its baseline has rotted while the number still looks plausible" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::every_committed_metric_command_still_measures + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_18 + rule: "the committed baselines carry slack the tree has already beaten, so a regression can hide inside the gap" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::the_committed_baselines_have_no_slack_left_in_them + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_19 + rule: "two metrics share a command, so a scope that was supposed to be pinned twice is pinned once — the 9,324-wide question of which files count is answered once and copied" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::no_two_metrics_share_a_command + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_20 + rule: "the contract names kani harnesses that do not exist, or that were renamed out from under it — `#[cfg(kani)]` code is never compiled by `cargo test`, so nothing would notice" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::every_kani_harness_the_contract_names_exists + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_21 + rule: "`kind: kernel` plus a `kani_harness` per equation is read as a discharged proof, though nothing in this repository can run one" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::the_contract_states_whether_its_kani_harnesses_discharge + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_22 + rule: "the not_discharged note rots into a false claim: a kani runner is added and the note is left stale" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::a_discharged_claim_requires_something_that_actually_runs_kani + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_23 + rule: "CB-2102 is registered with no declared severity, so it defaults to Warning, does not fail a non-strict comply run, and falls outside the severity=error roster CB-2100 checks reachability for" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::cb_2102_is_declared_error_not_left_at_the_warning_default + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_24 + rule: "CB-2102 is registered under an id .pmat.yaml cannot address, so it can be neither configured nor found in CB-2100's enforcement ledger" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::cb_2102_is_in_the_comply_rule_registry + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_25 + rule: "the rule is orphaned again: the engine exists and no comply check drives it, which is the state this contract was written to end" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::cb_2102_is_in_the_comply_rule_registry + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_26 + rule: "INV-2102-7: a metric's declared `timeout_secs` is ignored and every measurement is bounded by the flat default, so the cap that exists to stop a runaway also kills a legitimately slow measurement — and the failure reads as UNAVAILABLE, which is indistinguishable from a rotted command" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::a_metric_declares_its_own_budget + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_26_control + rule: "the control: a metric that declares no budget stops getting the 300s default — a per-metric budget implemented by shortening every measurement would kill the cold clippy metric on every runner instead of only the loaded ones" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::a_metric_without_a_budget_keeps_the_default + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_27 + rule: "the lowering pass drops a metric's `timeout_secs`, re-arming the flat-budget defect on the next slow runner with nothing in the nightly job's diff to review but a baseline" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::lowering_preserves_a_timeout_secs_line + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_28 + rule: "this repository's own cold full-crate clippy metric stops declaring a budget and goes back to the 300s default, which does not make the gate stricter — it makes it flaky, and a flaky UNAVAILABLE reads as a failed measurement rather than a regression anyone can act on" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::the_committed_unwrap_metric_declares_a_budget + if_fails: "P1 — this contract's guarantee does not hold" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-threshold-coherence-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-threshold-coherence-v1.yaml new file mode 100644 index 0000000000..37da6c065b --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/comply-threshold-coherence-v1.yaml @@ -0,0 +1,269 @@ +metadata: + version: "1.0.0" + kind: kernel + created: "2026-08-19" + author: PAIML Engineering + registry: true + description: > + Threshold coherence (CB-2101). CB-2102 asks whether a metric got worse. + CB-2101 asks the prior question nobody was asking: does the number written + down mean anything at all? Every scalar in a declared threshold section of + `.pmat-metrics.toml` is classified against a live measurement as exactly one + of FIRING, VIOLATED or VACUOUS, and the classification decides the verdict. + + VIOLATED — the bound is breached at HEAD and the build is green. This is + strictly worse than having no threshold: it reads as enforcement, it + enforces nothing, and every reader who greps the config comes away with a + false belief about the tree. `.pmat-metrics.toml:45` is this repository's + own case, `max_unwrap_calls = 100` annotated `Current: 570` against a + measured 20,390 — three numbers, no two of which agree. + + VACUOUS — the limit is further from the measurement than the band, so no + movement the ratchet tolerates can reach it. A vacuous threshold is green on + the day the project is abandoned. It is permitted, because a recorded budget + is a legitimate thing to write down, but only with an explicit + `justification`; without one it fails, so decoration cannot pass itself off + as enforcement by silence. + + The classification is TOTAL. There is no fourth "unknown" class: a threshold + whose metric could not be measured FAILS, because "we could not measure it" + must never render as "it is fine". Making unmeasurability a class is exactly + how an unenforceable number gets to look like a gate in a report. + + PROVENANCE, and the defect this contract itself closes. The pure evaluator — + `classify` in kernel.rs and `evaluate_coherence` in config.rs, with 14 + falsification tests — shipped on release/3.32.0 with NO caller, NO rule id + that `.pmat.yaml` could address, and THIS FILE named in three module headers + while not existing. A classifier nobody calls classifies nothing. This + version adds the driver, the comply check, the rule registration, the + seventeen bindings, and the contract. + + Scope, stated once so it cannot drift: CB-2101 audits the CONFIG, not the + tree. It never asserts that a metric is low enough — that is CB-2102's job, + and the two must not be able to disagree, which is why both read the same + `.pmat-ratchet.toml` and both judge against the number a metric's own + `command` prints rather than the number written beside it. + contract: comply-threshold-coherence + status: enforced + verification: + kani_status: not_discharged + measured_at: "pmat 3.32.0, kani 0.67.0, 2026-08-19" + detail: > + The two `kani_harness` names below are REAL harnesses — `#[kani::proof]` + functions in `mod kani_proofs` at the bottom of + src/services/metrics_ratchet/kernel.rs, held to their names by + coherence_drive_tests.rs::every_kani_harness_the_contract_names_exists. + They are NOT discharged: nothing in this repository has ever run them, and + with kani 0.67.0 installed they cannot be run at all. Re-measured + first-hand for this contract, not copied from CB-2102's: + + (1) `cargo kani --harness verify_classify_total_and_sound` fails with + "error: target `pmat-agent` in package `pmat` requires the features: + `mcp-integration`" — kani enumerates every target in the package and one + of them does not build under default features. + + (2) Adding `--features mcp-integration` fails with "error: rustc + 1.93.0-nightly is not supported by the following package: sysinfo@0.39.6 + requires rustc 1.95" — kani 0.67.0 pins a nightly older than a transitive + dependency's MSRV. + + (3) No .github/workflows job, Makefile target or script invokes kani, so + none of this repository's `#[kani::proof]` harnesses is compiled by any + gate. `#[cfg(kani)]` code is not built by `cargo test` either, which is + why a test pins the names rather than the compiler. + + These harnesses are therefore ASPIRATIONAL: they state the intended + theorems and are the specification a future proof will discharge, but + `kind: kernel` here must NOT be read as "proved". What actually runs is + kernel_tests.rs, which checks the same theorems over a bounded box and at + the i64 extremes. That is a test, not a proof, and this contract says so + rather than letting a reader infer a proof that never ran — a contract + naming a harness that silently does not run is precisely the defect class + this backlog exists to rule on. + references: + - src/services/metrics_ratchet/kernel.rs + - src/services/metrics_ratchet/config.rs + - src/services/metrics_ratchet/mod.rs + - src/cli/handlers/comply_handlers/check_handlers/check_threshold_coherence.rs + - src/cli/handlers/comply_handlers/coherence_handler.rs + - .pmat-ratchet.toml + - .pmat-metrics.toml + +equations: + classify: + formula: > + slack(l, m, Max) = l - m ; slack(l, m, Min) = m - l. + classify = Violated <=> slack < 0 ; = Vacuous <=> slack > band ; + = Firing <=> 0 <= slack <= band + domain: "an i64 limit, an i64 measurement in the same unit, a u64 band, and a direction" + codomain: "Firing | Violated | Vacuous" + invariants: + - "INV-2101-1: dir = Max and measured > limit => the threshold is breached, and the verdict of the audit is Fail" + - "INV-2101-2: dir = Max and limit > measured + band => the class is Vacuous; symmetrically for Min" + - "INV-2101-3: classification is TOTAL and DISJOINT — every (limit, measured, band, dir) lands in exactly one of the three classes" + - "KANI-2101-2: monotone in the limit — loosening a Firing threshold never yields Violated, so a class can never be made to look worse by relaxing the number" + - "slack is computed in i128 so that i64::MIN / i64::MAX operands cannot overflow and silently invert a verdict" + - "the direction is declared per binding and never inferred from the key's name: a `max_*` key that is really a floor is exactly the mislabelling this rule exists to catch" + preconditions: + - "limit and measurement are in the same declared unit; a gate threshold must therefore be an integer, and a string-valued threshold such as `min_tdg_grade = \"A-\"` cannot be a gate" + kani_harness: verify_classify_total_and_sound + + audit: + formula: > + outcome(report) = Fail when any threshold verdict is Fail, or any section + of .pmat-metrics.toml appears in neither threshold_sections nor + non_threshold_sections; = Warn when any verdict is Warn; = Ok otherwise + domain: > + .pmat-metrics.toml, the [coherence] declarations and [metric.*] baselines + of .pmat-ratchet.toml, and the output of running each metric's own + `command` from the repository root + codomain: "CoherenceReport { thresholds, undeclared_sections, outcome }" + invariants: + - "INV-2101-3 at file level: every scalar in a declared threshold section produces exactly one verdict carrying exactly one classification — nothing is skipped" + - "INV-2101-4: a gate whose metric produced no measurement, or measured Unavailable, is FAIL — unmeasurable is not compliant, and a justification cannot buy it a pass" + - "a threshold with no [coherence.binding.\"
.\"] entry is UNDECLARED and FAILS: an unbound number is enforced by nothing and can never fire" + - "a NEW section of .pmat-metrics.toml is in neither list and therefore fails closed, rather than being silently exempt" + - "a Vacuous threshold WARNS with a justification and FAILS without one" + - "kind = budget requires a justification; kind = external requires an enforced_by path, and that path must exist" + - "kind = external is a WARN, never a pass: this gate can check that the named reader exists, and cannot check that the bound it enforces is the same bound — `thresholds.binary_max_bytes` is the measured worked example, 50,000,000 declared against a hardcoded 52,428,800 under a comment claiming the two are aligned" + - "an audit that classified nothing is a FAILURE, not a clean sheet: an empty audit passes forever" + - "INV-2101-4 has an upstream dependency, and F-3 found it broken: a rotted pathspec is not an Unavailable measurement, it is a clean 0, so the zero guard in measure::guard_zero (INV-2102-6) is what makes 'unmeasurable is not compliant' reachable at all. Without it a gate whose metric had stopped measuring anything classified FIRING and the audit exited 0" + - "the limit is judged against the number the metric's command PRINTS, never against the baseline written beside it — a limit judged against a remembered number is this rule's own defect, one indirection out" + preconditions: + - ".pmat-ratchet.toml exists (otherwise CB-2101 Skips: the project declares no bindings), and .pmat-metrics.toml parses" + +preconditions: + - "pmat >= 3.32.0 with `pmat comply check` running CB-2101" + - "the project root is inside a git work tree" + +postconditions: + - "CB-2101 reports Skip only when .pmat-ratchet.toml is absent AND was never committed" + - "CB-2101 reports Fail with severity=error whenever any invariant above is violated" + - "every classified threshold appears as `=` in the check's message, uncapped, so `pmat comply check --format json` carries the whole classification" + +falsification_tests: + - id: f_1 + rule: "F-1: max_unwrap_calls = 100 against ~11,002 measured does not classify VIOLATED, or classifies VIOLATED and does not Fail" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::arm_a_violated_threshold_on_a_green_build_fails + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_1_2 + rule: "F-1 on real data: this repository's own committed max_unwrap_calls is judged against a remembered number rather than a live measurement, or its class disagrees with the bound" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::arm_a_max_unwrap_calls_is_judged_against_a_live_measurement + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_2 + rule: "F-2: max_unwrap_calls = 100000 against ~11,002 measured does not classify VACUOUS" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::mutation_max_unwrap_calls_100000_classifies_vacuous + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_2_2 + rule: "F-2 second half: a vacuous threshold with no justification passes" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::arm_b_vacuous_threshold_warns_with_justification_and_fails_without + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_3 + rule: "F-3: a metric with no measurement available passes, or warns instead of failing, or is bought a pass by a justification" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::falsify_2101_3_unmeasurable_metric_fails + if_fails: "P0 — this contract's guarantee does not hold" + - id: f + rule: "INV-2101-3: some (limit, measured, band, dir) lands in zero classes or in two" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::inv_2101_3_classification_is_total_and_disjoint + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_4 + rule: "INV-2101-2: VIOLATED is reported for something other than a breached bound" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::inv_2101_2_violated_iff_bound_breached + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_5 + rule: "INV-2101-1: the FIRING band is not exactly the reachable one — an off-by-one at slack = 0 or slack = band" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::inv_2101_1_firing_is_exactly_the_reachable_band + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_6 + rule: "KANI-2101-2 in test form: loosening a FIRING threshold yields VIOLATED" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/kernel_tests.rs::classify_is_monotone_in_limit + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_7 + rule: "a threshold in a declared threshold section is skipped rather than classified, so totality is claimed and not held" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::inv_2101_3_every_threshold_gets_exactly_one_classification + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_8 + rule: "the fixed state is unreachable: a threshold sitting exactly at the measurement cannot be FIRING, so there is no value the config could take that this rule would accept" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::firing_threshold_absorbs_one_unwrap_so_only_the_ratchet_moves + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_9 + rule: "kind = external names an enforcing file that does not exist, and the audit reports it as enforced — the doc comment claimed the gate verified this for a whole release while nothing did" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::external_enforcement_must_name_a_file_that_exists + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_control + rule: "the control for the above: a real enforcing path is rejected, so the existence check has broken every external binding" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/config_tests.rs::external_enforcement_accepts_a_path_that_exists + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_3_2 + rule: "F-3's upstream hole: a gate's metric stops matching anything, measures a clean 0, and classifies FIRING instead of failing — found by running this rule's own F-3 against the shipped binary" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/drive_tests.rs::a_zero_against_a_nonzero_baseline_is_a_hole_not_the_best_day_in_project_history + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_10 + rule: "this repository's own .pmat-metrics.toml has a threshold that no binding declares, or a section in neither list" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::every_committed_threshold_is_classified + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_11 + rule: "a binding names an enforced_by path or a metric id that does not exist in this repository" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::every_committed_binding_names_something_real + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_12 + rule: "an audit that classified nothing folds to Ok and passes every run forever — the exact live bug CB-2102's evaluator carried while it was orphaned" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::an_empty_audit_is_a_failure_not_a_clean_sheet + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_13 + rule: "the check's message drops thresholds when there are many, so `pmat comply check --format json` no longer classifies all of them" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::the_json_output_classifies_every_threshold + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_14 + rule: "the contract names kani harnesses that do not exist, or that were renamed out from under it — `#[cfg(kani)]` code is never compiled by `cargo test`, so nothing would notice" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::every_kani_harness_the_contract_names_exists + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_15 + rule: "`kind: kernel` plus a `kani_harness` per equation is read as a discharged proof, though nothing in this repository can run one" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::the_contract_states_whether_its_kani_harnesses_discharge + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_16 + rule: "the not_discharged note rots into a false claim: a kani runner is added and the note is left stale" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::a_discharged_claim_requires_something_that_actually_runs_kani + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_17 + rule: "CB-2101 is registered with no declared severity, so it defaults to Warning and cannot fail a non-strict comply run" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::cb_2101_is_declared_error_not_left_at_the_warning_default + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_18 + rule: "CB-2101 is registered under an id .pmat.yaml cannot address, so it can be neither configured nor found in CB-2100's enforcement ledger" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::cb_2101_is_in_the_comply_rule_registry + if_fails: "P1 — this contract's guarantee does not hold" + - id: f_19 + rule: "the rule is orphaned again: the evaluator exists and no comply check drives it, which is the state this contract was written to end" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::cb_2101_is_in_the_comply_rule_registry + if_fails: "P0 — this contract's guarantee does not hold" + - id: f_20 + rule: "this contract is named by the module headers and does not exist on disk, as it did not for an entire release" + prediction: "the named test fails when this condition holds" + test: src/services/metrics_ratchet/coherence_drive_tests.rs::the_coherence_contract_is_committed + if_fails: "P0 — this contract's guarantee does not hold" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/dead-code-cache-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/dead-code-cache-v1.yaml new file mode 100644 index 0000000000..cb17671edc --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/dead-code-cache-v1.yaml @@ -0,0 +1,79 @@ +metadata: + version: "1.0.0" + created: "2026-09-03" + author: PAIML Engineering + references: + - "docs/specifications/pmat-architecture-crux-audit.md section 8.4 (CRUX-04, epic 1153)" + - "issue 748 (CLOSED) — the hooks cache keyed on HEAD^{tree} was invariant to the change it gated; moved to git write-tree" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + pmat analyze dead-code memoised cargo check on git rev-parse HEAD: — the + committed tree, byte-identical before and after any uncommitted edit — so + a warm run replayed 0 dead functions over a function appended a second + ago, and 1 dead function on line 9 of an 8-line file once the edit was + reverted; and a replay that opened no file and exec'd no compiler still + said compiler-lint-ran in the present tense with no cache marker. The key + is now the working tree (a scratch-index git write-tree; the user's index + is untouched), the report carries cache {hit, tree_hash, written_at, + pmat_version}, a replay says compiler-lint-cached, a schema-4 entry is a + miss, and --no-cache is the escape hatch. + contract: dead-code-cache + status: draft + +equations: + key_is_the_working_tree: + formula: "key(tree) = write-tree(scratch index filled by git add -A) ; fallback HEAD^{tree} ; None outside git => nothing cached" + domain: "any analyze dead-code run on a Rust crate inside a git checkout" + codomain: "tree hash" + invariants: + - "an unstaged edit changes the key; reverting it restores the key (states A, C, E)" + - "the user's index is never read or written; the scratch index is removed after use" + - "an entry written under the previous schema is a miss (state G)" + preconditions: [] + + replay_and_exec_count_move_together: + formula: "cache.hit == true <=> 0 cargo check execs in this invocation and compiler_scan.reason == compiler-lint-cached ; cache.hit == false <=> exactly 1 cargo check exec and reason == compiler-lint-ran" + domain: "states A-E of scripts/dead-code-cache-audit.sh, with a PATH shim counting cargo execs" + codomain: bool + invariants: + - "state D (rerun after an uncommitted edit) is a HIT: a fix that bypasses the cache on any dirty tree fails here" + - "a reduced compiler scan replayed keeps its own reason; only the cache object says it was replayed" + - "--no-cache forces exactly one cargo check and hit == false" + preconditions: [] + + the_gate_sees_the_working_tree: + formula: "quality-gate --checks dead-code on state C reports the uncommitted dead function (same analyzer, use_cache on)" + domain: "state F of the acceptance script" + codomain: bool + invariants: + - "with the cache deleted, states A/C/E answer 0/1/0 (control: a fix that changed the answers rather than the key is caught)" + preconditions: [] + +falsification: + - condition: "a warm run over an uncommitted dead function reports 0, or a run after reverting reports the reverted function (CRUX-04)" + severity: P0 + action: reject_push + - condition: "a run with zero cargo execs reports compiler-lint-ran, or omits cache.hit" + severity: P0 + action: reject_push + - condition: "a schema-4 cache entry is served after upgrade" + severity: P1 + action: reject_push + +falsification_tests: + - id: key_is_the_working_tree + rule: "unstaged edit => new key; revert => old key; index untouched; old schema => miss" + prediction: "working_tree_key_tests (5) pass; states A, C, E, G of scripts/dead-code-cache-audit.sh" + test: "cargo test --lib working_tree_key_tests" + if_fails: "the cache answers for the last commit, not the checkout being analysed" + - id: replay_and_exec_count_move_together + rule: "hit pairs with +0 execs and compiler-lint-cached; miss with +1 and compiler-lint-ran" + prediction: "states B and D (hit, +0), A/C/E (miss, +1), and the --no-cache leg of the script" + test: "bash scripts/dead-code-cache-audit.sh against the release binary" + if_fails: "a 0.2 s replay is byte-identical to a 50 s compiler pass" + - id: the_gate_sees_the_working_tree + rule: "quality-gate --checks dead-code counts an uncommitted dead function; cache-deleted control answers 0/1/0" + prediction: "state F and the control legs of the script" + test: "bash scripts/dead-code-cache-audit.sh" + if_fails: "the pre-commit gate is stale for exactly the change it gates" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/dogfood-published-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/dogfood-published-v1.yaml new file mode 100644 index 0000000000..9b923beeb0 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/dogfood-published-v1.yaml @@ -0,0 +1,50 @@ +metadata: + version: "1.0.0" + created: "2026-09-03" + author: PAIML Engineering + references: + - "docs/specifications/agentic-delivery-pmat.md section 3.6 / 9.2 (AD-02)" + - "docs/audits/release-3.36.0-dogfood.md — the release-chain receipt, written by hand; it links to the script output release-3.36.0-dogfood-published.md" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + Nothing ships without dog-fooding the bytes crates.io serves. The + pre-publish probe reads the packaged tarball and the release gate reads a + local build; neither touches what a consumer obtains with cargo install. + scripts/dogfood-published.sh installs the published version from the + registry into a throwaway root, requires the binary to report that version + and answer --help, runs scripts/dogfood-use.sh against it, and writes + docs/audits/release--dogfood.md pinned by the registry's size and stamp. + contract: dogfood-published + status: draft + +equations: + the_installed_bytes_are_the_measured_bytes: + formula: "for version v: registry(v) present and not yanked => cargo install --version v --locked succeeds => installed --version starts with ' v' => dogfood-use.sh(BIN=installed) exits 0 => receipt names registry size and created stamp" + domain: "any version string matching ^[0-9]+\\.[0-9]+\\.[0-9]+$" + codomain: bool + invariants: + - "a version the registry does not carry fails at the registry leg with exit 1 (the control)" + - "a non-version argument is refused before any network call" + - "the receipt records the installed binary's own --version line, not the local tree's" + preconditions: [] + +falsification: + - condition: "the script exits 0 for a version crates.io does not serve, or for an installed binary that reports another version (AD-02)" + severity: P0 + action: reject_push + - condition: "the receipt is written when the release gate failed against the installed binary" + severity: P0 + action: reject_push + +falsification_tests: + - id: control_fabricated_version + rule: "9.9.9 fails at the registry leg, exit 1, no receipt" + prediction: "FAIL: dogfood-published: pmat 9.9.9 is not on crates.io" + test: "bash scripts/dogfood-published.sh 9.9.9; test $? = 1" + if_fails: "the script can pass on a version that does not exist" + - id: real_run_published_version + rule: "the published 3.36.0 installs, reports itself, passes the release gate, and the receipt names the registry size and stamp" + prediction: "GO: pmat 3.36.0 — 13 checks, 0 failure(s); receipt docs/audits/release-3.36.0-dogfood-published.md" + test: "bash scripts/dogfood-published.sh 3.36.0" + if_fails: "the release announcement is not backed by the bytes consumers receive" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/hook-debt-scope-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/hook-debt-scope-v1.yaml new file mode 100644 index 0000000000..45cff07d8c --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/hook-debt-scope-v1.yaml @@ -0,0 +1,136 @@ +metadata: + version: "1.0.0" + created: "2026-09-07" + author: PAIML Engineering + references: + - "paiml/infra docs/specifications/build-system-enhancement.md BSE-12 (report O11)" + - "PMAT-707 — pre-commit debt rule scoped to the diff's touched functions" + - "src/cli/handlers/hooks_command_handlers/hook_debt_scope.rs" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + The pre-commit hook's complexity verdict is a function of the diff's + TOUCHED functions only. A function is touched when its measured + [line_start, line_end] span in the staged file overlaps a line added or + modified by a staged hunk (the + side of git diff --cached -U0). A touched + function is refused only when a metric exceeds the threshold AND exceeds + the same function's value in the HEAD version of the file, measured the + same way; a function with no HEAD counterpart is judged against the + threshold alone, never as growth. Untouched functions never affect the + verdict, over threshold or not. The hook is FEEDBACK, not the gate — ci / + gate enforces the thresholds on the merge path — so scoping the hook to the + diff removes no enforcement, it only stops refusing a one-line fix in an + undebted function of a file that carries pre-existing debt. + contract: hook-debt-scope + status: draft + +equations: + verdict_is_a_function_of_touched_functions_only: + formula: "verdict(old, new, diff, limits) = REFUSE iff exists f in touched(new, ranges(diff)) with (m(f) > limit and (old(f) is none or m(f) > m(old(f))))" + domain: "one staged source file, its HEAD version, and its git diff --cached -U0 hunk headers" + codomain: bool + invariants: + - "an untouched function over the threshold does not change the verdict" + - "a touched function over the threshold but no worse than at HEAD does not change the verdict" + - "a touched function with no HEAD counterpart is judged against the threshold alone" + - "spans and metrics come from collect_functions + measure_block + FunctionSpans, the one existing measurement path; no second parser" + - "a refusal names the function, the measured value, the limit, and the previous value (or says there is none)" + preconditions: [] + +falsification: + - condition: "a commit whose only change is a one-line edit in an undebted function is refused because another function in the same file carries pre-existing debt" + severity: P1 + action: reject_push + - condition: "complexity growth inside a touched function is allowed, or the refusal does not name the function and both numbers" + severity: P0 + action: reject_push + +falsification_tests: + - id: verdict_is_a_function_of_touched_functions_only + rule: "the hook_debt_scope_* tests in src/cli/handlers/hooks_command_handlers/hook_debt_scope_tests.rs: one-line fix beside pre-existing debt is allowed; growth in a touched function is refused with both numbers; a new over-threshold function is refused; an untouched over-threshold function is ignored; the pre-BSE-12 whole-file rule refuses the first case" + prediction: "diff-scoped rule: 13 passed (7 in-memory fixtures + 6 over a fixture repo). Same suite under the pre-BSE-12 whole-file rule (touched set = every function, previous = none): 8 of 13 FAIL, including the one-line-fix case." + test: "cargo test -p pmat hook_debt_scope" + if_fails: "the hook refuses a one-line fix for debt the commit did not write, and the developer decomposes functions the change never touched" + - id: the_verdict_is_read_out_of_git_not_out_of_the_working_tree + rule: "the staged_repo::* tests in src/cli/handlers/hooks_command_handlers/hook_debt_scope_tests.rs run the rule over a real fixture repo through staged_verdict / staged_verdict_for_file: the NEW source is `git show :` (the INDEX), the OLD source is `git show HEAD:`, and the ranges come from `git diff --cached -U0 -- `" + prediction: "6 passed. One of them stages the allowed content and then writes over-threshold growth into the WORKING TREE: a verdict read from disk instead of the index fails it. An unstaged path and an unreadable diff are errors, so neither can render as 'no violations'." + test: "cargo test -p pmat hook_debt_scope" + if_fails: "the hook grades content that is not being committed, in either direction: unstaged work refuses a clean commit, or staged debt slips past because the disk copy is clean" + - id: the_cli_entry_point_prints_the_offender_and_exits_non_zero + rule: "the diff_scope_entry_point::* tests in src/cli/handlers/complexity_handlers/complexity_handlers_tests.rs call handle_analyze_complexity_diff_scoped over a fixture repo at the DEFAULT thresholds (10/15, the same ComplexityConfig::from_args uses)" + prediction: "4 passed. Report O11 — a one-line fix in `innocent` beside a committed `debted` of cyclomatic 11 — returns Ok; `innocent` grown to 12 returns Err naming 'innocent - Cyclomatic 12 > 10 (was 1)'; a path with nothing staged is an Err, not a pass." + test: "cargo test -p pmat diff_scope" + if_fails: "the developer is told a file is at fault without being told which function, which is the report the whole-file mode already gives" + +mutation_matrix: + - mutation: "diff_scoped_verdict judges every function instead of the touched ones (`for func in new_functions.iter()`), leaving the growth comparison in place" + observed: "hook_debt_scope: 13 passed, 0 failed. diff_scope: 4 passed, 0 failed." + reading: > + Not a weak suite — a fact about the rule. An UNTOUCHED function measures + identically in the HEAD blob and the staged blob, so the growth + comparison already spares it and the touched filter is redundant for a + file that HAS a HEAD counterpart. The filter is load-bearing exactly + where growth cannot be computed: a file absent from HEAD has no previous + value for any function, and there the scope is the only thing between the + developer and the whole file. That leg is + hook_debt_scope_a_file_absent_from_head_is_judged_on_the_threshold. + - mutation: "the above PLUS `previous = None` — i.e. the complete pre-BSE-12 whole-file rule the hook still runs today" + observed: "hook_debt_scope: 5 passed, 8 FAILED. diff_scope: 2 passed, 2 FAILED (the O11 case and the offender-naming case)." + reading: "the suite discriminates the shipped rule from the rule it replaces" + + - id: the_generated_hook_puts_the_flag_on_a_command_line_for_rust_only + rule: "complexity_gate_tests::complexity_gate_scopes_rust_to_the_diff_only and ::dropping_the_flag_from_the_generated_hook_goes_red in src/cli/handlers/hooks_command_handlers/hook_generation.rs READ the SCOPE_FLAG case arms out of the generated text (a bare hook.contains(\"--diff-scope\") would now pass on the doctrine COMMENT alone) and assert the *.rs arm asks for the scoped verdict, the fallback arm does not, and the invocation passes $SCOPE_FLAG without hardcoding the flag" + prediction: "20 passed. Two mutants are built inside the test and run through the SAME predicate: blanking the *.rs arm, and deleting $SCOPE_FLAG from the invocation. Both are asserted to be caught, and each mutant is first asserted to differ from the real hook so the legs cannot pass vacuously." + test: "cargo test -p pmat --lib complexity_gate" + if_fails: "the flag is declared and routed but never reaches a command line -- the exact state this ticket found, where a tested rule was not the rule the hook ran" + - id: end_to_end_the_hook_allows_the_o11_case_and_refuses_growth + rule: "a minimal git repo (no Cargo.toml, so the hook's fmt and clippy gates are inert), fixture.rs with `debted` at cyclomatic 36 and `innocent` at 1, committed BEFORE `pmat hooks install` so the debt is pre-existing; thresholds read back out of the installed hook (30/25); no --no-verify" + prediction: "a one-line edit inside `innocent` commits cleanly (rc 0, HEAD moves) although the whole-file measurement of the same file reports `Errors: 2` / `Max Cyclomatic: 36`; growing `innocent` itself to 36 is refused (rc 1, HEAD unchanged) printing `innocent - Cyclomatic 36 > 30 (was 1)` and `innocent - Cognitive 35 > 25 (was 0)`, with the untouched `debted` absent from the refusal" + test: "cargo test -p pmat --test hook_debt_scope_e2e" + if_fails: "report O11 is still reproducible in the field: a developer is charged for debt the commit did not write" +wiring_status: + measured_on: "2026-09-08, PMAT-707 phase 3" + wired: > + YES, for Rust. `diff_scope: bool` is declared on AnalyzeCommands::Complexity + (clap `--diff-scope`, `requires = "file"`), route_complexity_analysis in + src/cli/handlers/analysis_handlers/core_routes.rs dispatches it to + handle_analyze_complexity_diff_scoped, and the generated pre-commit hook in + hook_generation.rs selects it PER STAGED FILE: `*.rs` gets + `SCOPE_FLAG="--diff-scope"`, every other language gets `SCOPE_FLAG=""` and + keeps the whole-file measurement, because the scoped measurement is + syn-based and there is no honest touched-set for a .py/.ts/.go file. + end_to_end: + procedure: > + A minimal `git init` repo (no Cargo.toml, so the hook's fmt and clippy + gates are inert and the complexity gate is the only variable), fixture.rs + holding `debted` (35 ifs, cyclomatic 36) and `innocent` (cyclomatic 1), + COMMITTED BEFORE `pmat hooks install` so the debt is genuinely + pre-existing. The hook exported PMAT_MAX_CYCLOMATIC_COMPLEXITY=30 / + PMAT_MAX_COGNITIVE_COMPLEXITY=25, read back out of the installed file + rather than taken from what the installer printed. No --no-verify. + baseline: > + The whole-file measurement over the same file reports `Errors: 2`, + `Max Cyclomatic: 36` — i.e. under the pre-BSE-12 rule EVERY commit + touching this file is refused. That is report O11. + allowed: > + A one-line change inside `innocent` (`acc = 0i64` -> `acc = 7i64`, + a single -/+ hunk at line 42) committed cleanly: + "Complexity check... OK / All quality gates passed!", rc 0, HEAD moved. + `debted` is over the threshold in the same file and did not affect it. + refused: > + Growing `innocent` itself to 35 ifs in the next commit was refused, rc 1, + HEAD unchanged, naming the function and both numbers: + "innocent - Cyclomatic 36 > 30 (was 1)" and + "innocent - Cognitive 35 > 25 (was 0)". `debted`, untouched and also at + cyclomatic 36, is ABSENT from the refusal — which is what distinguishes + the scoped verdict from the whole-file one. + remaining: > + Every AnalyzeCommands::Complexity struct literal in the crate (24 sites, the last + at tests/modules/analysis_timeout_test.rs, outside PMAT-707's scope and added by + the orchestrator at commit 3842c57e4) carries `diff_scope: false`; the pre-commit + clippy stage (--all-targets) is the check that refuses a missing site. + + LIMITATION: Rename-to-a-deleted-name. A touched function renamed to the name of a + DELETED function inherits the deleted one's baseline. This is an accepted + limitation of name-keyed pairing and is out of scope for this PR. It must be + fixed in a follow-up PR when AST-based identity tracking is introduced. diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/index-faithful-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/index-faithful-v1.yaml new file mode 100644 index 0000000000..ef29d985bf --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/index-faithful-v1.yaml @@ -0,0 +1,83 @@ +metadata: + version: "1.0.0" + created: "2026-09-04" + author: PAIML Engineering + references: + - "docs/specifications/pmat-architecture-crux-audit.md section 8.7 (CRUX-07)" + - "scripts/index-faithful-audit.sh — the acceptance, five legs with controls" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + The index CLAUDE.md mandates over grep is a faithful, reproducible view + of the tree. (a) The incremental fast path may skip re-hashing a file + only when its mtime, its length and (on unix) its ctime all predate + built_at, so a rewrite with a backdated mtime is re-read and the stale + checksum is not carried forward — while a quiescent tree is still skipped + by mtime. (b) The two walks that define persisted order are sorted and + the ranker breaks ties on (file_path, start_line), so tied results come + back the same on every filesystem. (c) analyze churn serialises + author_contributions as a BTreeMap and ranks ties by author, so its raw + JSON is byte-stable. (d) manifest.json is written atomically and a torn + or unparsable manifest is named and rebuilt, never served. (e) A failed + index save is reported as a failure; success is announced only after it. + contract: index-faithful + status: draft + +equations: + backdated_rewrite_is_seen: + formula: "content(f) changed and mtime(f) < built_at => (len(f) != recorded_len or ctime(f) >= built_at) => f is re-hashed; query returns the new symbols and not the deleted ones" + domain: "the incremental index update (function_index build_helpers)" + codomain: bool + invariants: + - "a file whose mtime, length and ctime all predate built_at is skipped: `Incremental update: N mtime-skipped, ..., 0 re-parsed` on a quiescent tree (the fast-path-survival control)" + - "old manifests without the len/ctime fields load and re-hash once" + preconditions: [] + + order_is_the_sorted_sequence: + formula: "query --limit N --format json over N tied one-function files returns all N, and the emitted (file_path, start_line) sequence equals its sorted form" + domain: "the persisted walk order and the ranker tie-break" + codomain: bool + invariants: + - "no result is truncated at the limit" + preconditions: [] + + churn_json_is_byte_stable: + formula: "over 12 runs, sha256(raw JSON minus generated_at, key order preserved) has one value (c1) and sha256(key-sorted JSON) has one value (c2)" + domain: "analyze churn --format json" + codomain: bool + invariants: + - "author_contributions names exactly the git shortlog -sn authors; the counts differ today and are reported, not judged, by the acceptance (a separate finding)" + - "c2 alone is not a gate (it normalised the defect); c1 alone misses an added timestamp field" + preconditions: [] + + manifest_is_atomic_and_torn_is_named: + formula: "a manifest truncated mid-object after a good db write is reported with one of torn|corrupt|invalid|unreadable|rebuild and the index rebuilds; a clean pair prints none of those words" + domain: "index load" + codomain: bool + invariants: [] + preconditions: [] + + failed_save_is_reported: + formula: "index directory read-only => the incremental save prints a failure line naming the path; index directory writable => no failure line" + domain: "query_handler indexing" + codomain: bool + invariants: + - "persist_score returns Result and its caller warns; the FTS DELETE in sqlite_docs propagates with ?" + preconditions: [] + +falsification_tests: + - id: F1 + rule: "the released binary fails five legs and passes every control" + prediction: "on pmat 3.36.0: a (both assertions), b, c1, d, e are red; the fast-path, c2, author-set, clean-pair and writable controls are green" + test: "PMAT= bash scripts/index-faithful-audit.sh" + if_fails: "a leg is already green on the old binary: its defect was never reproduced and the leg is vacuous — re-establish RED before trusting GREEN" + - id: F2 + rule: "this tree passes every leg and every control" + prediction: "eleven ✓, GREEN, exit 0" + test: "PMAT=$(cargo build --bin pmat --message-format json | ...executable) bash scripts/index-faithful-audit.sh" + if_fails: "the red leg names which rule regressed; a red control means the fix deleted the optimisation or the disclosure it was meant to keep" + - id: F3 + rule: "the fast path was kept, not deleted (named mutation)" + prediction: "making check_mtime_reuse always re-hash turns the fast-path control RED (0 mtime-skipped) while leg a stays green" + test: "mutate build_helpers.rs to always return None from the fast path, rebuild, run --only a, revert and REBUILD" + if_fails: "the control does not discriminate deletion of the optimisation; also confirm the binary was rebuilt after the revert" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-artifacts-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-artifacts-v1.yaml new file mode 100644 index 0000000000..199cd60675 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-artifacts-v1.yaml @@ -0,0 +1,96 @@ +metadata: + version: "1.1.0" + created: "2026-07-02" + author: PAIML Engineering + references: + - "docs/specifications/components/modern-agentic-coding-support.md (MACS v1.0.0)" + - "arXiv:2602.22302 Bruni et al. (2026) Agent Behavioral Contracts" + - "Popper (1959) The Logic of Scientific Discovery" + - "Meyer (1997) Object-Oriented Software Construction 2e (Design by Contract)" + registry: true + description: > + Canonical planning artifacts: ROADMAP.yaml rendered from the work store, + mcp.json regenerated from tool defs, and doc model-id drift linted. + MACS F6 (docs/specifications/components/modern-agentic-coding-support.md). + contract: macs-artifacts + status: draft + +equations: + roadmap_canonical: + formula: render(state) is byte-stable and hash covers sources not wall-clock + domain: state in .pmat-work store + optional pinned GitHub snapshot + codomain: ROADMAP.yaml bytes + invariants: + - BTreeMap ordering, ids sorted + - generation timestamp excluded from content hash + - source snapshot ids included in content hash + preconditions: + - work store readable + + manifest_faithful: + formula: tools(mcp.json) = tools(mcp_server_tool_defs) + domain: mcp.json at repo root + codomain: bool + invariants: + - manifest generation is pure (defs -> bytes) + - hand-edits are drift (CB-1656 red) + preconditions: [] + + manifest_schema_faithful: + formula: inputSchema(mcp.json, t) = inputSchema(tools/list, t) for every live tool t + domain: mcp.json at repo root, and the handlers' metadata() the server serves + codomain: bool + invariants: + - the manifest renders each inputSchema FROM handler metadata, never from a shape chosen by tool name + - the committed file equals render_manifest() byte-for-byte + - every live tool declares metadata (no open-object fallback is reachable) + preconditions: [] + + docs_current: + formula: denylist_hits(docs) = 0 outside allow-list + domain: docs/** with deny-list {claude-3-*, claude-2*, gpt-4-turbo} + codomain: bool + invariants: + - allow-listed only inside docs/agent-models.md history table + preconditions: [] + +falsification: + - condition: "ROADMAP.yaml drifts from the work store with green comply" + severity: P0 + action: reject_push + - condition: "mcp.json drifts from tool defs with green comply" + severity: P0 + action: reject_push + - condition: "mcp.json advertises an inputSchema a tool's handler does not accept (CRUX-09, #1150)" + severity: P0 + action: reject_push + - condition: "A deny-listed model id appears outside docs/agent-models.md" + severity: P1 + action: block_release + +falsification_tests: + - id: manifest_faithful + rule: mcp.json advertises exactly the live tool set + prediction: legacy 2-tool manifest fails; generated 20-tool manifest passes + test: cargo test --lib generated_equals_tool_defs + if_fails: agents read a manifest that lies about the tool surface + - id: roadmap_canonical + rule: render is byte-stable and hash covers sources not wall-clock + prediction: two renders at different times share one content_hash + test: cargo test --lib hash_covers_sources_not_wallclock + if_fails: ROADMAP.yaml churns on every render, defeating drift detection + - id: manifest_schema_faithful + rule: the shipped inputSchema IS the served inputSchema, per tool + prediction: a canned paths-array schema for pmat_index_stats fails; the handler's own schema passes + test: cargo test --lib manifest_schemas_match_handler_metadata + if_fails: a validating client rejects its own valid call, or calls a tool as the manifest says and gets -32602 + - id: manifest_pinned + rule: the committed mcp.json equals the renderer's output byte-for-byte + prediction: editing one property in mcp.json without regenerating fails; regenerating passes + test: cargo test --lib committed_mcp_json_is_pinned_to_the_renderer + if_fails: a renderer fix ships with the old file still in the tarball, and nothing notices + - id: docs_current + rule: no superseded model ids in docs outside the registry + prediction: reintroducing claude-3-opus outside agent-models.md fails CB-1657 + test: cargo test --lib cb1657_red_on_denylist_hit + if_fails: stale model ids in agent docs become executable misinformation diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-cot-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-cot-v1.yaml new file mode 100644 index 0000000000..176b065c19 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-cot-v1.yaml @@ -0,0 +1,70 @@ +metadata: + version: "1.0.0" + created: "2026-07-02" + author: PAIML Engineering + references: + - "docs/specifications/components/modern-agentic-coding-support.md (MACS v1.0.0)" + - "arXiv:2602.22302 Bruni et al. (2026) Agent Behavioral Contracts" + - "Popper (1959) The Logic of Scientific Discovery" + - "Meyer (1997) Object-Oriented Software Construction 2e (Design by Contract)" + registry: true + description: > + Structured chain-of-thought with mandatory discharge + auto-derivation (C31). + MACS F3 (docs/specifications/components/modern-agentic-coding-support.md). + contract: macs-cot + status: draft + +equations: + chain_integrity: + formula: forall s in steps -> discharged(s.assumption) + domain: steps in Vec + codomain: bool + invariants: + - discharge graph is acyclic + - roots are equations or E-facts only + preconditions: + - ticket has >=1 CoT step when kind in {feature,bugfix,refactor} + lean_theorem: Theorems.Macs.CoT_Discharge_DAG + + derivation_complete: + formula: count(claims_from(steps)) = count(steps) and count(obligations_from(steps)) = count(steps) + domain: steps in Vec + codomain: bool + invariants: + - claim.hypothesis = step.implication verbatim + - claim.method = step.evidence_method verbatim + preconditions: + - MACS-007 schema active + +falsification: + - condition: "A step's assumption has no discharge and CB-1640 stays green" + severity: P0 + action: reject_push + - condition: "A ticket closes with fewer FalsifiableClaims than CoT steps" + severity: P0 + action: reject_push + - condition: "Legacy prose steps crash the parser instead of migrating to v2-with-L0 annotation" + severity: P1 + action: block_release + +falsification_tests: + - id: cot_chain_cycle_rejected + rule: Discharge graph must be a DAG + prediction: a two-step mutual discharge is rejected by check_chain + test: cargo test --lib cycle_detected_violates_1640 + if_fails: circular reasoning passes the integrity check (C31) + - id: cot_undischarged_rejected + rule: Every assumption must be discharged + prediction: a step with no discharge source and no evidence_method is rejected + test: cargo test --lib undischarged_assumption_violates_1640 + if_fails: assumptions inflate without discharge (a hallucination magnet) + - id: cot_spec_self_check + rule: The spec is subject to the check it specifies + prediction: MACS spec §3.1's own chain parses and passes check_chain + test: cargo test --lib spec_section_3_1_passes + if_fails: the schema cannot express its own specification's reasoning + - id: cot_derivation_verbatim + rule: claim.hypothesis/method copied verbatim from step fields + prediction: any paraphrase drift fails CB-1658 + test: cargo test --lib one_claim_per_step_verbatim_fields + if_fails: derived claims drift from the reasoning they discharge diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-ladder-kernel-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-ladder-kernel-v1.yaml new file mode 100644 index 0000000000..7b49ad1105 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-ladder-kernel-v1.yaml @@ -0,0 +1,126 @@ +metadata: + version: 1.0.0 + created: '2026-07-04' + author: PAIML Engineering + description: > + Verification-ladder parser kernel — the lighthouse dogfooding contract for + docs/specifications/audit-pmat-support-l1-l5-aprender-provable-contracts.md. + "The ladder proves itself": pmat's own VerificationLevel parser is climbed + L1→L5 (types → falsification #[test] → proptest → Kani → Lean). + references: + - docs/specifications/components/pmat-work-verification-ladder.md + - docs/specifications/audit-pmat-support-l1-l5-aprender-provable-contracts.md + - Popper (1959) The Logic of Scientific Discovery +equations: + parse_strict: + formula: parse_strict(s) = Some(l) ⇔ s ∈ {"L0".."L5"} + domain: s ∈ String + codomain: Option + invariants: + - parse_strict is total (never panics) on any &str + - parse_strict(l.as_str()) = Some(l) for every level l (left inverse) + - parse_strict(s) = Some(l) ⇒ l.as_str() = s (strict soundness) + preconditions: [] + lean_theorem: Theorems.Macs.Ladder_Parse_Total + ord_monotone: + formula: (a < b) ⇔ (a as u8) < (b as u8) + domain: a, b ∈ VerificationLevel + codomain: bool + invariants: + - the derived Ord agrees with the numeric rung (as u8) + - to_nat is injective, so rung comparisons never conflate two levels + preconditions: [] + lean_theorem: Theorems.Macs.Ladder_Ord_Injective +proof_obligations: +- type: postcondition + property: parse round-trips every canonical level string + formal: '∀l: parse_strict(as_str(l)) = Some(l)' + applies_to: parse_strict + lean: + theorem: Theorems.Macs.Ladder_Parse_Total + module: Theorems.Macs.Ladder + status: proved + notes: 'by cases l <;> decide — depends on no axioms' +- type: invariant + property: strict parse rejects case/whitespace/out-of-set corruptions + formal: 'parse_strict("l3") = None ∧ parse_strict("L3 ") = None ∧ parse_strict("strong") = None' + applies_to: parse_strict + lean: + theorem: Theorems.Macs.Ladder_Parse_Strict + module: Theorems.Macs.Ladder + status: proved + notes: 'by decide — mirrors Rust falsification test parse_strict_rejects_typos' +- type: invariant + property: Ord agrees with the numeric rung (gate comparisons never invert) + formal: '∀a,b: (a < b) = ((a as u8) < (b as u8))' + applies_to: ord_monotone + lean: + theorem: Theorems.Macs.Ladder_Ord_Injective + module: Theorems.Macs.Ladder + status: proved + notes: 'by cases a <;> cases b <;> decide' +- type: bound + property: distinct levels have distinct wire strings (Display never collides) + formal: '∀a,b: as_str(a) = as_str(b) ⇒ a = b' + applies_to: parse_strict + lean: + theorem: Theorems.Macs.Ladder_toStr_Injective + module: Theorems.Macs.Ladder + status: proved + notes: 'by cases a <;> cases b <;> decide' +falsification_tests: +- id: parse_total_strict_prop + rule: parse is total and strict — accepts exactly L0..L5 + prediction: for 8-char random strings, parse_strict is Some iff s ∈ {L0..L5} + test: cargo test --lib -- work_verification_level::tests::parse_total_strict_prop + if_fails: misspelled levels silently downgrade tickets (C28) +- id: parse_strict_rejects_typos + rule: strict parse rejects case/whitespace corruptions + prediction: parse_strict("l3"), ("L3 "), (" L3"), ("strong"), ("L6"), ("") all None + test: cargo test --lib -- work_verification_level::tests::parse_strict_rejects_typos + if_fails: over-claims like "l4" pass the completion gate +- id: ord_matches_numeric + rule: Ord matches numeric ladder order + prediction: L0 < L1 < L2 < L3 < L4 < L5 and (v as u8) is monotone + test: cargo test --lib -- work_verification_level::tests::ord_matches_numeric + if_fails: achieved >= claimed comparison inverts; over-claims pass +- id: display_parse_id + rule: Display then parse is the identity on valid levels + prediction: as_str(l).parse() = Some(l) for every l + test: cargo test --lib -- work_verification_level::tests::display_parse_id + if_fails: read/write round-trip breaks; stored levels corrupt on reload +- id: serde_string_repr + rule: wire format is the display string (no wire break vs legacy) + prediction: serde serializes L4 as "L4"; legacy contract.json still parses + test: cargo test --lib -- work_verification_level::tests::serde_string_repr + if_fails: every existing .pmat-work contract breaks on read +kani_harnesses: +- id: KANI-LADDER-001 + obligation: parse round-trips every canonical level string + property: parse_strict(l.as_str()) == Some(l), exhaustive over the six variants + bound: 6 + strategy: exhaustive + solver: cadical + harness: verify_ladder_parse_roundtrip +- id: KANI-LADDER-002 + obligation: Ord agrees with the numeric rung + property: (a < b) == ((a as u8) < (b as u8)) for all level pairs + bound: 6 + strategy: exhaustive + solver: cadical + harness: verify_ladder_ord_matches_numeric +- id: KANI-LADDER-003 + obligation: strict parse is total and accepts exactly L0..L5 + property: parse_strict never panics on 2-ASCII input; Some iff L{0..5} + bound: 128 + strategy: exhaustive + solver: cadical + harness: verify_ladder_parse_strict_total_two_ascii +qa_gate: + id: LADDER-KERNEL-GATE + name: Verification-ladder parser kernel gate + description: L1→L5 provability gate for pmat's own VerificationLevel parser + checks: + - validation + - falsification + pass_criteria: All 5 falsification tests pass and all 4 Lean obligations are proved (0 sorry) diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-ladder-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-ladder-v1.yaml new file mode 100644 index 0000000000..0b5f011919 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-ladder-v1.yaml @@ -0,0 +1,68 @@ +metadata: + version: "1.0.0" + created: "2026-07-02" + author: PAIML Engineering + references: + - "docs/specifications/components/modern-agentic-coding-support.md (MACS v1.0.0)" + - "arXiv:2602.22302 Bruni et al. (2026) Agent Behavioral Contracts" + - "Popper (1959) The Logic of Scientific Discovery" + - "Meyer (1997) Object-Oriented Software Construction 2e (Design by Contract)" + registry: true + description: > + Typed L0-L5 verification ladder with evidence-gated completion (C28). + MACS F2 (docs/specifications/components/modern-agentic-coding-support.md). + contract: macs-ladder + status: draft + +equations: + parse_total_strict: + formula: parse(s) = Ok(l) <-> s in {"L0".."L5"} + domain: s in String (proptest-generated incl. corruptions) + codomain: Result + invariants: + - Ord matches numeric order + - Display then parse = id on valid set + preconditions: [] + lean_theorem: Theorems.Macs.Ladder_Parse_Total + + gate_monotone: + formula: complete(t)=Ok -> achieved_level(t) >= t.claimed_level + domain: t in .pmat-work/* + codomain: bool + invariants: + - achieved_level is computed from evidence, never stored + preconditions: + - binding.yaml well-formed (CB-1250 family) + +falsification: + - condition: "\"l4\" or \"L3 \" parses successfully" + severity: P0 + action: reject_push + - condition: "A ticket claiming L4 closes while a kani harness fails" + severity: P0 + action: reject_push + - condition: "A ticket claiming L5 closes with any `sorry` in its Lean proof" + severity: P0 + action: reject_push + +falsification_tests: + - id: ladder_parse_strict_rejects_typos + rule: Strict parse rejects case/whitespace corruptions + prediction: parse("l4"), parse("L3 "), parse("strong") all fail strict parse + test: cargo test --lib parse_total_strict_prop + if_fails: misspelled levels silently downgrade tickets (C28) + - id: ladder_ord_matches_numeric + rule: Ord matches numeric order + prediction: L0 < L1 < L2 < L3 < L4 < L5 under derived Ord + test: cargo test --lib ord_matches_numeric + if_fails: gate comparisons invert and over-claims pass the completion gate + - id: ladder_serde_string_repr + rule: Wire format stays the display string (no wire break) + prediction: serde serializes L4 as "L4"; legacy contract.json still parses + test: cargo test --lib serde_string_repr + if_fails: every existing .pmat-work contract breaks on read + - id: ladder_gate_monotone + rule: complete implies achieved_level >= claimed level + prediction: a ticket claiming L4 without kani evidence blocks with LadderShortfall + test: cargo test --lib claim_l4_without_kani_blocks + if_fails: tickets close above their evidenced level (C28 completion gates are hard) diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-provenance-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-provenance-v1.yaml new file mode 100644 index 0000000000..d4f45f1c6e --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-provenance-v1.yaml @@ -0,0 +1,61 @@ +metadata: + version: "1.0.0" + created: "2026-07-02" + author: PAIML Engineering + references: + - "docs/specifications/components/modern-agentic-coding-support.md (MACS v1.0.0)" + - "arXiv:2602.22302 Bruni et al. (2026) Agent Behavioral Contracts" + - "Popper (1959) The Logic of Scientific Discovery" + - "Meyer (1997) Object-Oriented Software Construction 2e (Design by Contract)" + registry: true + description: > + Agent provenance and interruption events on falsification receipts. + Declared-first; detection advisory; hash-versioned for old receipts. + MACS F1 (docs/specifications/components/modern-agentic-coding-support.md). + contract: macs-provenance + status: draft + +equations: + provenance_roundtrip: + formula: deserialize(serialize(r)) = r + domain: r in FalsificationReceipt{schema_version=2} + codomain: bool + invariants: + - serialization uses canonical JSON (sorted keys, no NaN/Inf) + - unknown future fields are rejected (deny_unknown_fields) + preconditions: + - proptest deterministic mode enabled (MACS-000) + lean_theorem: Theorems.Macs.Provenance_Roundtrip + + hash_stability: + formula: content_hash(r) = golden(r) + domain: r in fixtures/receipts/v2/*.json + codomain: hex64 + invariants: + - byte-identical across OS/arch (CI matrix) + - v1 receipts verify under v1 rules keyed by schema_version + preconditions: + - fixtures committed at MACS-001 + + refusal_gates_completion: + formula: has_unacked(Refusal) -> complete(ticket) = Err(Blocked) + domain: ticket in .pmat-work/* + codomain: Result + invariants: + - Refusal never auto-acks; ack requires --ack-event with reason + preconditions: + - MACS-003 landed + +falsification: + - condition: "A receipt with schema_version=2 lacks `agent` yet CB-1651 passes" + severity: P0 + action: reject_push + - condition: "Two serializations of the same receipt differ in bytes" + severity: P0 + action: reject_push + - condition: "`pmat work complete` succeeds while an unacked Refusal event exists" + severity: P0 + action: reject_push + - condition: "A v1 (pre-MACS) receipt fails ledger verify after upgrade" + severity: P1 + action: block_release diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-skill-effort-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-skill-effort-v1.yaml new file mode 100644 index 0000000000..6c87758c03 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-skill-effort-v1.yaml @@ -0,0 +1,42 @@ +metadata: + version: "1.0.0" + created: "2026-07-02" + author: PAIML Engineering + references: + - "docs/specifications/components/modern-agentic-coding-support.md (MACS v1.0.0)" + - "arXiv:2602.22302 Bruni et al. (2026) Agent Behavioral Contracts" + - "Popper (1959) The Logic of Scientific Discovery" + - "Meyer (1997) Object-Oriented Software Construction 2e (Design by Contract)" + registry: true + description: > + Per-skill effort pinning: every repo skill pins a model-level effort in + frontmatter so behavior and cost are reproducible across sessions. + MACS F4 (docs/specifications/components/modern-agentic-coding-support.md). + contract: macs-skill-effort + status: draft + +equations: + skill_effort_pinned: + formula: forall f in skills -> effort(f) in {low, medium, high, xhigh} + domain: f in .claude/skills/**/{SKILL,skill}.md + codomain: bool + invariants: + - session-only values (max, ultracode) are rejected + - mechanical skills pin low/medium; adversarial skills pin high/xhigh + preconditions: + - frontmatter effort is honored by the harness (spec E4) + +falsification: + - condition: "A skill file lacks `effort:` frontmatter yet CB-1650 passes" + severity: P0 + action: reject_push + - condition: "A skill pins `max` or `ultracode` and CB-1650 stays green" + severity: P0 + action: reject_push + +falsification_tests: + - id: skill_effort_pinned + rule: every skill pins effort in {low,medium,high,xhigh} + prediction: a skill missing effort or pinning max/ultracode fails CB-1650 + test: cargo test --lib cb1650_red_on_session_only_values + if_fails: per-skill behavior varies run-to-run with session effort (E6) diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-sweep-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-sweep-v1.yaml new file mode 100644 index 0000000000..f2231f12fd --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/macs-sweep-v1.yaml @@ -0,0 +1,98 @@ +metadata: + version: "1.0.0" + created: "2026-07-02" + author: PAIML Engineering + references: + - "docs/specifications/components/modern-agentic-coding-support.md (MACS v1.0.0)" + - "arXiv:2602.22302 Bruni et al. (2026) Agent Behavioral Contracts" + - "Popper (1959) The Logic of Scientific Discovery" + - "Meyer (1997) Object-Oriented Software Construction 2e (Design by Contract)" + registry: true + description: > + LLM-free deterministic MCP sweep harness (pmat qa mcp-sweep) plus a + committed judgment workflow for anomalies only. + MACS F5 (docs/specifications/components/modern-agentic-coding-support.md). + contract: macs-sweep + status: draft + +equations: + sweep_deterministic: + formula: run1 = run2 modulo {timestamp, duration} fields + domain: runs of pmat qa mcp-sweep --format json + codomain: bool + invariants: + - anomaly ordering is stable (sorted by tool name) + preconditions: + - pmat binary built from working tree + + framing_pure: + formula: stdout_bytes subset_of JSONRPC_frames + domain: bytes on the MCP server stdout during a sweep + codomain: bool + invariants: + - no stray print/log bytes interleave JSON-RPC frames + preconditions: [] + + no_raw_resume: + formula: raw_resume_calls(release-sweep.ultracode.mjs) = 0 + domain: the committed ultracode judgment workflow, comments stripped + codomain: bool + invariants: + - durable state is .pmat-work receipts, never a session-bound continuation (MACS E7) + - exactly one spawnSubagent site, so PMAT_AGENT_* stamping is structural not observed + preconditions: + - contracts/workflows/release-sweep.ultracode.mjs is committed + + concurrency_safe: + formula: lock_errors = 0 and scratch_leftovers = 0 for N <= 8 + domain: N in {1..8} concurrent tool calls against one working tree + codomain: bool + invariants: + - advisory-lock discipline per src/services/metric_trends_core.rs + preconditions: [] + +falsification: + - condition: "Any LLM/model API call in the mcp-sweep code path" + severity: P0 + action: reject_push + - condition: "Two sweep runs differ beyond timestamp/duration fields" + severity: P0 + action: reject_push + - condition: "Orchestration layer calls a session-bound resume instead of .pmat-work receipts" + severity: P1 + action: reject_push + - condition: "Concurrency N<=8 produces lock errors or scratch leftovers" + severity: P0 + action: reject_push + +falsification_tests: + - id: sweep_args_derived_from_schema + rule: Minimal valid args derived from each tool inputSchema + prediction: every required property gets a type-appropriate placeholder; path fields get the target + test: cargo test --lib args_derived_from_every_tool_schema + if_fails: schema-derivable args need an LLM, defeating the LLM-free premise + - id: sweep_framing_pure + rule: stdout carries only JSON-RPC frames + prediction: framing_stray_lines flags any non-JSON stdout line + test: cargo test --lib framing_stdout_pure_jsonrpc_golden + if_fails: stray bytes corrupt the JSON-RPC stream (D82 class) + - id: sweep_deterministic + rule: two runs byte-identical modulo timestamp/duration + prediction: identical tool data serializes identically (report carries no wall-clock) + test: cargo test --lib two_runs_byte_identical_modulo_time + if_fails: the sweep is not replayable, breaking green-here-green-in-CI + - id: sweep_no_llm + rule: the sweep path links no model/API client + prediction: qa_mcp_sweep.rs references no LLM client symbols + test: cargo test --lib no_llm_symbols_linked + if_fails: stochastic tokens leak into a deterministic gate (E6) + - id: no_raw_resume + rule: the committed judgment workflow never relies on session-bound resume + prediction: workflow source (comments stripped) contains no `resume`; exactly one spawn site + test: cargo test --lib qa_mcp_sweep::tests::workflow_ + if_fails: durable state silently becomes session-bound, so a run cannot be reproduced by the team (E7) + - id: macs012_workflow_properties + rule: the four MACS-012 RED tests run where CI looks + prediction: workflow file is embedded at compile time; reads only the sweep artifact; stamps PMAT_AGENT_*; records refusals + test: cargo test --lib qa_mcp_sweep::tests::workflow_ + if_fails: the workflow's properties hold only until someone edits it, which is not enforcement diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-build-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-build-v1.yaml new file mode 100644 index 0000000000..3912c03c4c --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-build-v1.yaml @@ -0,0 +1,71 @@ +metadata: + version: "1.0.0" + created: "2026-04-08" + author: PAIML Engineering + registry: true + references: + - "../pmat-book/book.toml" + description: > + Provable contract for the PMAT mdBook build and GitHub Pages deployment. + 62 chapters covering CLI, MCP, TDG, quality gates, compliance, and + provable contracts. Falsification-first: every claim must be testable. + contract: pmat-book-build + status: enforced + title: "PMAT Book Build Contract" + tool: mdbook + source: "../pmat-book/" + config: "../pmat-book/book.toml" + summary: "../pmat-book/src/SUMMARY.md" + deploy: GitHub Pages via actions/deploy-pages@v4 + +invariants: + structure: + - "book.toml exists and is valid TOML" + - "src/SUMMARY.md exists and lists all chapters" + - "Every .md path in SUMMARY.md exists on disk" + - "No include directive references a nonexistent file" + + build: + - "mdbook build exits 0 with zero ERROR lines" + - "book/index.html exists after build" + - "No missing font errors in build output" + + content: + - "SUMMARY.md spine covers chapters 1-62" + - "No stub-only chapters (every chapter has substantive content)" + - "Critical chapters (5, 7, 13, 14) pass validate-book" + - "All pmat CLI examples in chapters are valid commands" + + ci: + - "Workflow triggers on push to main for pmat-book/** changes" + - "mdBook version pinned to specific release" + +preconditions: + - "mdbook binary available" + - "All chapter .md files committed to git" + - "pmat binary installed (for CLI example validation)" + +postconditions: + - "book/ contains valid HTML site" + - "GitHub Pages deployment succeeds on push to main" + +falsification: + - condition: "mdbook build exits non-zero" + severity: P0 + action: reject_push + - condition: "SUMMARY.md references nonexistent .md file" + severity: P0 + action: reject_push + - condition: "Critical chapter (5, 7, 13, 14) fails validate-book" + severity: P0 + action: reject_push + - condition: "Chapter contains pmat command that exits non-zero" + severity: P1 + action: fix_example + - condition: "SUMMARY.md contains stub-only chapter (< 50 words)" + severity: P0 + action: delete_or_expand + +verification: + local: "make validate-book" + ci: ".github/workflows/book.yml" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch05-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch05-v1.yaml new file mode 100644 index 0000000000..2d4953dd20 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch05-v1.yaml @@ -0,0 +1,39 @@ +metadata: + version: "1.0.0" + created: "2026-04-08" + author: PAIML Engineering + references: + - "../pmat-book/src/ch05-00-analyze-suite.md (the chapter this contract gates)" + - "Makefile target validate-book (runs the chapter's test scripts)" + registry: true + description: > + PMAT-BOOK Chapter 5: The Analyze Command Suite + contract: pmat-book-ch05 + status: enforced + title: "The Analyze Command Suite" + part: III + chapter_file: ch05-00-analyze-suite.md + +preconditions: + - "pmat binary installed and in PATH" + - "pmat analyze complexity --help exits 0" + - "pmat analyze dead-code --help exits 0" + - "pmat analyze satd --help exits 0" + +postconditions: + - "All pmat analyze subcommands documented" + - "Example output matches current pmat version" + +falsification: + - condition: "pmat analyze complexity --path . exits non-zero on test project" + severity: P0 + action: reject_chapter + - condition: "pmat analyze dead-code --path . exits non-zero on test project" + severity: P0 + action: reject_chapter + - condition: "Chapter claims feature that doesn't exist in current pmat --help" + severity: P0 + action: reject_chapter + - condition: "Example output doesn't match pmat version in Cargo.toml" + severity: P1 + action: update_examples diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch07-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch07-v1.yaml new file mode 100644 index 0000000000..d6cc630301 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch07-v1.yaml @@ -0,0 +1,37 @@ +metadata: + version: "1.0.0" + created: "2026-04-08" + author: PAIML Engineering + references: + - "../pmat-book/src/ch07-00-quality-gates.md (the chapter this contract gates)" + - "src/cli/handlers/quality_gate_handlers.rs (the command the chapter documents)" + registry: true + description: > + PMAT-BOOK Chapter 7: Quality Gates + contract: pmat-book-ch07 + status: enforced + title: "Quality Gates" + part: III + chapter_file: ch07-00-quality-gate.md + +preconditions: + - "pmat binary installed and in PATH" + - ".pmat-metrics/ directory exists (from make lint/test-fast)" + +postconditions: + - "O(1) quality gate concept explained with timing evidence" + - "Pre-commit hook configuration documented" + +falsification: + - condition: "Pre-commit hook takes >45ms on cached metrics" + severity: P0 + action: reject_chapter + - condition: "Chapter references .pmat-metrics.toml thresholds that don't match actual file" + severity: P0 + action: reject_chapter + - condition: "make lint exits non-zero" + severity: P0 + action: reject_chapter + - condition: "Chapter claims metric name not in .pmat-metrics.toml" + severity: P1 + action: fix_claim diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch13-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch13-v1.yaml new file mode 100644 index 0000000000..9733b9ff0e --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch13-v1.yaml @@ -0,0 +1,39 @@ +metadata: + version: "1.0.0" + created: "2026-04-08" + author: PAIML Engineering + references: + - "../pmat-book/src/ch13-00-multi-language.md (the chapter this contract gates)" + - "CLAUDE.md (Chapter 13 is CRITICAL - must always pass)" + registry: true + description: > + PMAT-BOOK file:ch13 — Multi-Language Project Examples (CRITICAL) + contract: pmat-book-ch13 + status: enforced + title: "Multi-Language Project Examples" + part: IV + chapter_file: ch13-00-language-examples.md + critical: true + +preconditions: + - "pmat binary installed with core-languages feature" + - "pmat context --help exits 0" + - "Test fixtures for Rust, Python, TypeScript, Go, C exist" + +postconditions: + - "All documented languages produce valid AST output" + - "pmat context on multi-language project succeeds" + +falsification: + - condition: "pmat context fails on multi-language test fixture" + severity: P0 + action: reject_chapter + - condition: "Chapter claims language support that is not in --features list" + severity: P0 + action: reject_chapter + - condition: "Language listed in chapter but tree-sitter parser not in Cargo.toml" + severity: P0 + action: reject_chapter + - condition: "Example AST output for any language doesn't match current parser" + severity: P1 + action: update_examples diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch35-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch35-v1.yaml new file mode 100644 index 0000000000..40679525b7 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-book-ch35-v1.yaml @@ -0,0 +1,42 @@ +metadata: + version: "1.0.0" + created: "2026-04-08" + author: PAIML Engineering + references: + - "../pmat-book/src/ch35-00-semantic-search.md (the chapter this contract gates)" + - "docs/specifications/semantic-search-feature.md" + registry: true + description: > + PMAT-BOOK file:ch35 — Semantic Search and Code Clustering (pmat query) + contract: pmat-book-ch35 + status: enforced + title: "Semantic Search and Code Clustering" + part: VI + chapter_file: ch35-00-semantic-search.md + critical: true + +preconditions: + - "pmat binary installed and in PATH" + - ".pmat/context.db exists (index built)" + +postconditions: + - "All pmat query flags documented" + - "Contract metadata enrichment (PV:L2) shown in examples" + - "Coverage gaps mode documented with examples" + +falsification: + - condition: "pmat query 'test' --limit 1 exits non-zero" + severity: P0 + action: reject_chapter + - condition: "Chapter documents --flag that doesn't exist in pmat query --help" + severity: P0 + action: reject_chapter + - condition: "Chapter omits PV: contract enrichment (new in v3.12)" + severity: P1 + action: add_section + - condition: "Example query output doesn't show TDG grade" + severity: P1 + action: update_examples + - condition: "Chapter claims O(1) but query latency >500ms on cached index" + severity: P2 + action: investigate_perf diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-core.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-core.yaml new file mode 100644 index 0000000000..5ab0b72e6c --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-core.yaml @@ -0,0 +1,287 @@ +metadata: + version: 1.0.0 + created: '2026-04-05' + author: PAIML Engineering + description: PMAT core contract — commit-level enforcement invariants + references: + - docs/specifications/components/commit-level-contract-enforcement.md + - src/models/comply_config_types.rs +equations: + refresh_bindings: + formula: binding_index = scan(contracts/, binding.yaml) + domain: project_path is Path, project_path.exists() + codomain: binding_index in HashMap> + invariants: + - binding_index.is_object() (valid JSON) + - file_count <= binding_count (each file has at least 1 binding) + preconditions: + - binding_count.len() > 0 + lean_theorem: Theorems.Refresh_Bindings_Monotonic + # ── Declared because 1,187 annotation sites already assert them. + # + # `path_exists` (1,174 sites), `non_empty_index` (10) and `lint_valid` (3) + # were cited by `#[provable_contracts_macros::contract("pmat-core.yaml", + # equation = "...")]` throughout src/ and were NEVER DECLARED HERE. The macro + # accepts any string, build.rs emits CONTRACT_*=bound without opening the + # contract, and nothing checked that the name resolved — so 1,187 functions + # carried what reads as a proof obligation and bound to nothing. + # + # They are declared rather than deleted because the annotations are not + # decorative: the functions carrying `path_exists` really do require the path + # to exist, and several assert it at runtime (`debug_assert!(path.exists())`, + # e.g. check_individual_basic.rs). Writing the equation down turns 1,187 dead + # citations into live ones; deleting them would discard a precondition the + # code actually holds. + # + # `contracts/pv-registry-integrity-v1.yaml` is what stops the next phantom. + path_exists: + formula: "analysed(path) implies exists(path) and readable(path)" + domain: "path in Path" + codomain: "Result where E names the path" + invariants: + - "a path that does not exist is REFUSED, never reported as an empty result" + - "the refusal names the path, so a typo is distinguishable from a clean tree" + preconditions: + - "the caller has resolved the path (no further canonicalisation is implied)" + + non_empty_index: + formula: "query(index) requires count(index) > 0" + domain: "index in AgentContextIndex" + codomain: "Result, E>" + invariants: + - "a query against an empty or absent index is REFUSED, not answered with zero hits" + - "zero hits from a populated index is a measurement; zero hits from no index is not" + preconditions: + - "the index path is known to the caller" + + lint_valid: + formula: "lint(source) yields findings union refusal, never silence" + domain: "source in Path" + codomain: "Result" + invariants: + - "a linter that could not run is REFUSED, never reported as zero findings" + - "findings are counted BEFORE any confidence or severity filter is applied" + + check_compliance: + formula: check_result = run_cb_checks(project_path) + domain: project_path in Path + codomain: check_result in {Pass, Warn, Fail, Skip} + invariants: + - check_result != Panic (checks never panic) + - cached_data.is_fresh() implies latency < 45ms + preconditions: + - checks.len() > 0 + lean_theorem: Theorems.Check_Determinism + severity_resolution: + formula: > + severity(id) = (patch(id) applied over declared(id)).severity, where + declared = default_checks() and patch = the `comply.checks:` map read from + .pmat.yaml + domain: > + a check id, the declared check roster, and a possibly-partial + `comply.checks:` map + codomain: severity in {info, warning, error, critical} + invariants: + - "PMAT-630: a partial `checks:` map is a PATCH over the declared roster, never a replacement" + - "an id ABSENT from the map keeps the severity and threshold the code declared for it" + - "an entry that omits `severity:` keeps the declared severity; omitting `threshold:` keeps the declared threshold" + - "an explicit `severity:` still overrides the declared one: merging must not make the config unwritable" + - "an id the roster does not declare is added, not rejected: merging adds, it does not restrict" + - "an unknown key inside a check entry is a parse ERROR, never a silently discarded override" + - "a `comply:` section with no `checks:` key keeps the whole declared roster, not an empty map" + - "the resolved roster is never smaller than the declared one" + preconditions: + - ".pmat.yaml parses as YAML" + score_range: + formula: score = hit_rate * throughput + domain: hit_rate in [0.0, 1.0], throughput in [0.0, +inf) + codomain: score in [0.0, +inf) + invariants: + - score >= 0.0 (non-negative product of non-negative factors) + preconditions: + - hit_rate >= 0.0 + - throughput >= 0.0 + lean_theorem: Theorems.Score_Range_NonNeg + composite_bounded: + formula: composite = geometric_mean(sub_scores) + domain: sub_scores in Vec, all >= 0.0 + codomain: composite in [0.0, 100.0] + invariants: + - composite >= 0.0 (geometric mean of non-negatives) + - composite <= max(sub_scores) (geometric mean never exceeds max) + preconditions: + - sub_scores.len() > 0 + lean_theorem: Theorems.Geometric_Mean_Bounded + rps_category_sum: + formula: total = sum(category.earned for category in categories) + domain: categories in Vec + codomain: total in [0.0, 289.0] + invariants: + - total >= 0.0 + - total <= sum(category.max for category in categories) + preconditions: + - categories.len() == 11 + lean_theorem: Theorems.Category_Sum_Bounded + tdg_grade_monotonic: + formula: grade = score_to_grade(tdg_score) + domain: tdg_score in [0.0, 100.0] + codomain: grade in {A, B, C, D, F} + invariants: + - tdg_score >= threshold implies grade >= corresponding_grade + - higher score always yields same or better grade + preconditions: + - tdg_score >= 0.0 + lean_theorem: Theorems.TDG_Grade_Monotonic + index_load_idempotent: + formula: index = load(save(index)) + domain: index in AgentContextIndex + codomain: loaded_index in AgentContextIndex + invariants: + - loaded_index.functions.len() == index.functions.len() + - loaded_index.file_index.keys() == index.file_index.keys() + preconditions: + - index.functions.len() > 0 + lean_theorem: Theorems.Index_Roundtrip_Identity + bm25_relevance_positive: + formula: score = bm25(query, document, corpus) + domain: query in String, document in String, corpus in Vec + codomain: score in [0.0, +inf) + invariants: + - score >= 0.0 (BM25 produces non-negative scores) + - empty query implies score == 0.0 + preconditions: + - corpus.len() > 0 + lean_theorem: Theorems.BM25_Non_Negative + pagerank_convergent: + formula: ranks = pagerank(graph, damping, iterations) + domain: graph in CSRGraph, damping in (0.0, 1.0), iterations in N+ + codomain: ranks in Vec, sum(ranks) approx 1.0 + invariants: + - all(r >= 0.0 for r in ranks) + - sum(ranks) in [0.99, 1.01] (convergence within epsilon) + preconditions: + - graph.num_nodes() > 0 + - damping > 0.0 && damping < 1.0 + lean_theorem: Theorems.PageRank_Convergent + muda_waste_bounded: + formula: muda = compute_muda(project) + domain: project in ProjectAnalysis + codomain: muda in [0.0, 100.0] + invariants: + - muda >= 0.0 (waste score non-negative) + - muda <= 100.0 (bounded) + preconditions: + - project.files.len() > 0 + lean_theorem: Theorems.Muda_Score_Bounded +proof_obligations: +- type: invariant + property: TDG baseline valid JSON after every commit + formal: is_json(.pmat/baseline.json) + applies_to: all + check: contract_tests::FALSIFY-PMAT-001 + severity: ERROR +- type: invariant + property: Pre-commit hook < 30s + formal: hook_duration < 30.seconds + applies_to: all + check: contract_tests::FALSIFY-PMAT-002 + severity: ERROR +- type: bound + property: CB checks handle missing files gracefully + formal: file.missing() implies check_result == Skip + applies_to: all + check: contract_tests::FALSIFY-PMAT-003 + severity: WARNING +falsification_tests: +- id: FALSIFY-PMAT-001 + rule: TDG baseline valid JSON + prediction: .pmat/baseline.json is parseable as JSON after every commit + test: Post-commit hook verifies JSON parses, fails commit if corrupt + if_fails: TDG baseline corrupted, subsequent commits cannot compute drift +- id: FALSIFY-PMAT-002 + rule: Pre-commit hook performance budget + prediction: hook completes in under 30 seconds + test: .pmat-metrics/hook-timing.jsonl shows p95 < 30000ms over 10 runs + if_fails: Hook exceeds budget, developers bypass with --no-verify +- id: FALSIFY-PMAT-003 + rule: CB check graceful degradation + prediction: missing files produce Skip status, never panic + test: cargo test --lib -- check_handlers -- with tempdir (no files) + if_fails: Check panics on missing input, breaks CI for clean projects +kani_harnesses: +- id: KANI-PMAT-001 + obligation: geometric mean of non-negatives stays within [0, max] + property: composite in [0.0, 100.0] for all sub_scores >= 0.0 + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_geometric_mean_bounded +- id: KANI-PMAT-002 + obligation: geometric mean of an empty set is undefined, not zero + property: sub_scores.len() == 0 implies no composite is produced + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_geometric_mean_empty +- id: KANI-PMAT-003 + obligation: geometric mean of identical values is that value + property: all(s == k) implies composite == k + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_geometric_mean_identity +- id: KANI-PMAT-004 + obligation: a zero sub-score absorbs the geometric mean + property: any(s == 0.0) implies composite == 0.0 + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_geometric_mean_zero_absorbing +- id: KANI-PMAT-005 + obligation: impact score is never negative + property: score >= 0.0 for all non-negative factors + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_impact_score_non_negative +- id: KANI-PMAT-006 + obligation: zero missed lines yields zero impact + property: missed == 0 implies score == 0.0 + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_impact_score_zero_missed +- id: KANI-PMAT-007 + obligation: impact is monotonic in missed lines + property: missed_a <= missed_b implies score_a <= score_b + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_impact_score_monotonic_missed +- id: KANI-PMAT-008 + obligation: RRF score stays within its bound + property: rrf in (0.0, 1.0] for rank >= 0 + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_rrf_score_bounded +- id: KANI-PMAT-009 + obligation: RRF decreases monotonically with rank + property: rank_a < rank_b implies rrf_a > rrf_b + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_rrf_score_monotonic_decreasing +- id: KANI-PMAT-010 + obligation: rank zero maximises RRF + property: argmax(rrf) == 0 + bound: 8 + strategy: bounded_int + solver: cadical + harness: verify_rrf_rank_zero_maximum +verification_summary: + total_obligations: 3 + l2_property_tested: 3 + l3_kani_proved: 10 + l4_lean_proved: 0 + l4_sorry_count: 0 diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-install-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-install-v1.yaml new file mode 100644 index 0000000000..f72a67affb --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-install-v1.yaml @@ -0,0 +1,82 @@ +metadata: + version: "1.0.0" + created: "2026-05-05" + author: PAIML Engineering + registry: true + description: > + Provable contract for scripts/install.sh — the documented one-liner + installer (curl ... | sh). Captures the URL pattern, tarball layout, + and Linux platform default that must hold for `curl ... | sh` to + succeed on every supported base image. + contract: pmat-install + status: enforced + references: + - scripts/install.sh + - https://github.com/paiml/paiml-mcp-agent-toolkit/issues/561 + +equations: + url_pattern_matches_release_assets: + formula: download_url == "https://github.com/${REPO}/releases/download/v${VERSION}/pmat-v${VERSION}-${PLATFORM}.tar.gz" + domain: VERSION in semver, PLATFORM in supported_targets + codomain: download_url returns HTTP 302 (redirect to actual asset) + invariants: + - download_url contains "pmat-v${VERSION}-${PLATFORM}.tar.gz" + - download_url does NOT contain the legacy "paiml-mcp-agent-toolkit-" prefix + preconditions: + - Release v${VERSION} exists with the matching asset + lean_theorem: Theorems.Install_URL_Matches_Asset + + tarball_extraction_handles_subdirectory: + formula: tar -xzf archive.tar.gz -C $TMP_DIR --strip-components=1 + domain: archive.tar.gz contains "pmat-v${VERSION}-${PLATFORM}/pmat" + codomain: $TMP_DIR/pmat exists and is executable + invariants: + - Binary lands at $INSTALL_DIR/pmat (NOT nested under a subdirectory) + - $TMP_DIR/pmat is regular file after extraction + preconditions: + - download succeeded + - tar binary available + lean_theorem: Theorems.Install_Tarball_Flat + + default_linux_platform_is_musl_for_glibc_independence: + formula: PLATFORM == "${arch}-unknown-linux-musl" when uname -s == "Linux" + domain: arch in {x86_64, aarch64}, os == Linux + codomain: PLATFORM ends with "-musl" + invariants: + - musl variant is static-pie (file output contains "static-pie linked") + - musl variant runs on Ubuntu 22.04 (GLIBC 2.35) without GLIBC_2.39 errors + preconditions: + - musl-target asset exists in the release + lean_theorem: Theorems.Install_Musl_Default + +preconditions: + - "GitHub release v${VERSION} exists" + - "Release contains pmat-v${VERSION}-x86_64-unknown-linux-musl.tar.gz asset" + - "Release contains pmat-v${VERSION}-aarch64-unknown-linux-musl.tar.gz asset" + - "curl, tar, sh available on the target machine" + +postconditions: + - "$INSTALL_DIR/pmat is an executable producing 'pmat ${VERSION}' on --version" + - "Binary is static-pie (no GLIBC dependency)" + - "INSTALL_DIR env var is honoured if set, falls back to ~/.local/bin" + +falsification: + - condition: "DOWNLOAD_URL still references 'paiml-mcp-agent-toolkit-' prefix" + severity: P0 + action: reject_push + - condition: "Linux platform detection returns a -gnu target instead of -musl" + severity: P0 + action: reject_push + - condition: "tar extraction fails to flatten subdirectory (binary not at $TMP_DIR/pmat)" + severity: P0 + action: reject_push + - condition: "INSTALL_DIR env var is overwritten by unconditional assignment" + severity: P1 + action: reject_push + - condition: "Smoke test `INSTALL_DIR=/tmp/x bash install.sh v${VERSION}` exits non-zero" + severity: P0 + action: reject_push + +verification: + local: "INSTALL_DIR=/tmp/pmat-smoke bash scripts/install.sh v${VERSION} && /tmp/pmat-smoke/pmat --version" + ci: ".github/workflows/binary-release.yml" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-no-fabrication-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-no-fabrication-v1.yaml new file mode 100644 index 0000000000..901dfd58a9 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-no-fabrication-v1.yaml @@ -0,0 +1,213 @@ +metadata: + version: "1.0.0" + created: "2026-07-31" + author: PAIML Engineering + references: + - "docs/specifications/documentation-accuracy-enforcement.md (zero-hallucination policy)" + - "CHANGELOG.md v3.28.0 (analyze comprehensive no longer fabricates its results)" + - "CHANGELOG.md v3.27.0 (every falsification claim now measures something)" + - "CHANGELOG.md v3.26.0 (falsification ladder audit: 14 of 22 claims verified nothing)" + - "scripts/dogfood-release.py (interface-coverage harness that detects fabricated output)" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + The anti-fabrication contract for pmat's analysis surface. pmat is a code + QUALITY tool: its output is consumed as evidence, and increasingly by LLMs + that cannot tell a measurement from a placeholder. A number pmat prints is + therefore a claim about the input, and a claim that was never computed from + the input is a fabrication regardless of how plausible it looks. + + This contract exists because the 3.29.0 pre-release dogfood found nine + blocker-level fabrications shipped simultaneously: a Quality Scorecard whose + health/maintainability/coverage were compile-time constants identical for an + empty directory and the whole pmat repo; a provability analyzer that emitted + the same two phantom functions (main@1, test@10) for every file; a + proof-annotation command that emitted ten annotations for files that exist + nowhere, each with a freshly generated UUID and a current-time dateVerified; + a duplicate detector whose DEFAULT mode returned zero on byte-identical + files; and analysis commands that exited 0 with a score for paths that do + not exist. + + The unifying failure is that nothing in the build, the test suite, or CI + distinguishes "computed from the input" from "constant that type-checks". + These equations make that distinction falsifiable. + contract: pmat-no-fabrication + status: active + +equations: + measured_or_absent: + formula: > + forall m in reported_metrics(cmd, input): + derived_from(m, input) or marked_unmeasured(m) + domain: cmd in AnalysisCommand, input in Path + codomain: Report + invariants: + - a metric that is a compile-time constant is NEVER rendered as a measurement + - a metric that cannot be measured in the command's scope is rendered as an + explicit unmeasured marker (None / "not measured" / omitted field), never + as a plausible default + - varying the input must be able to vary every reported metric; a metric + that is invariant across all inputs is by definition not measuring the input + - "test coverage is only reported when a coverage run actually produced it" + preconditions: + - the command declares which metrics it computes + + output_derived_from_input: + formula: > + analyze(empty_input) = empty_result and analyze(i) = f(files_read(i)) + domain: i in Path + codomain: AnalysisResult + invariants: + - an input containing zero analysable files yields zero findings + - every finding references a file that was actually read from the given path + - no finding names a path that does not exist on disk + - result size is a function of input size, not a constant + preconditions: + - the path exists and is readable + + missing_path_fails: + formula: "not exists(p) -> analyze(p) = Err(PathNotFound) and exit_code != 0" + domain: p in Path + codomain: Result + invariants: + - a nonexistent path NEVER yields exit 0 + - a nonexistent path NEVER yields a score, grade, percentage or "PASSED" + - the failure names the offending path + preconditions: [] + + detection_mode_superset: + formula: "results(All) >= union(results(m) for m in sub_modes)" + domain: mode in DetectionType + codomain: Vec + invariants: + - "|results(All)| >= |results(m)| for every single mode m" + - the documented default mode can never report fewer findings than an + explicitly selected sub-mode on the same input + - byte-identical files are reported as duplicates under the default mode + preconditions: + - at least one sub-mode is implemented + + source_location_fidelity: + formula: "reported_line(item) = true_source_line(item)" + domain: item in AstItem + codomain: LineNumber + invariants: + - a reported line is the item's line in its source file + - a reported line is NEVER an enumeration index or array position + - line numbers agree across commands analysing the same file + - reported_line <= file_line_count + preconditions: + - the AST layer carries the true line + + bounded_time_arithmetic: + formula: "clamp(now - days(n)) is defined for all n in u32" + domain: n in u32 (user-supplied lookback) + codomain: DateTime + invariants: + - no user-supplied integer reaches unchecked DateTime arithmetic + - an out-of-range lookback is clamped or rejected, never panicked on + - the process never terminates by SIGABRT on user input + preconditions: [] + + session_survives_recoverable_frame: + formula: > + recoverable(err) -> (emit_jsonrpc_error(err) and session_alive) + and connection_closed(err) -> session_ends + domain: err in TransportError + codomain: SessionState + invariants: + - a malformed frame, unknown method or invalid params yields a JSON-RPC + error response and the session continues to serve later requests + - only ConnectionClosed or a genuine IO failure ends the session + - the server NEVER exits 0 silently mid-conversation + - requests already accepted are still answered before the session ends + preconditions: + - the transport distinguishes recoverable errors from connection loss + +falsification: + - condition: "A reported metric is identical for an empty directory and a large real repository" + severity: P0 + action: reject_push + - condition: "An analysis result names a file path that does not exist on disk" + severity: P0 + action: reject_push + - condition: "A command exits 0 and prints a score for a path that does not exist" + severity: P0 + action: reject_push + - condition: "A proof/verification artifact is emitted with a current-time dateVerified without a verification having run" + severity: P0 + action: reject_push + - condition: "The default detection mode returns fewer findings than a sub-mode on identical input" + severity: P0 + action: reject_push + - condition: "A reported source line is an enumeration index rather than the true line" + severity: P1 + action: reject_push + - condition: "User-supplied integer input causes a panic or SIGABRT" + severity: P0 + action: reject_push + - condition: "An MCP session terminates silently with exit 0 on a recoverable protocol error" + severity: P0 + action: reject_push + +falsification_tests: + - id: FALSIFY-NOFAB-001 + rule: A reported metric must vary with the input, or be marked unmeasured + prediction: > + Running a metric-reporting command against an empty directory and against a + populated project yields different values, OR the metric is rendered as an + explicit unmeasured marker. Before the fix, `pmat context` reported + Overall Health 85.0 / Maintainability Index 70.0 / Test Coverage 65.0 for + BOTH an empty directory and the 3252-file pmat repo. + test: cargo test --lib no_fabrication::metric_varies_with_input + if_fails: pmat reports compile-time constants as measurements + - id: FALSIFY-NOFAB-002 + rule: An empty input yields an empty result + prediction: > + `analyze proof-annotations` on an empty directory reports zero annotations. + Before the fix it emitted ten annotations naming borrow_checker_0.rs and + other files that exist nowhere on the filesystem. + test: cargo test --lib no_fabrication::empty_input_empty_result + if_fails: pmat invents findings for input it never read + - id: FALSIFY-NOFAB-003 + rule: A nonexistent path fails loudly + prediction: > + Every analysis command given a path that does not exist exits non-zero and + names the path. Before the fix, `analyze proof-annotations`, `cuda-tdg`, + `analyze comprehensive`, `analyze dag`, `analyze provability`, + `analyze defect-prediction` and `analyze defects` all exited 0, some + printing "Quality Score: 100.0%" or "Gateway: PASSED". + test: cargo test --lib no_fabrication::missing_path_is_an_error + if_fails: a typo in a path silently produces a passing quality report + - id: FALSIFY-NOFAB-004 + rule: The default duplicate-detection mode is a superset of every sub-mode + prediction: > + On two byte-identical files, the default (`--detection-type all`) reports + at least as many duplicates as `--detection-type exact`. Before the fix, + exact found 13 and all found 0 on the same input (124 vs 0 on real source). + test: cargo test --lib no_fabrication::all_mode_is_superset + if_fails: the default invocation reports clean code as clean when it is duplicated + - id: FALSIFY-NOFAB-005 + rule: Reported source lines are real source lines + prediction: > + `context --format json` reports the same line for a function as + `quality-gate` does for the same file. Before the fix, context emitted + 1,2,3,4,5 (enumeration indices) where the true lines were 6,651,776,777,778. + test: cargo test --lib no_fabrication::line_numbers_are_source_lines + if_fails: any tool consuming context JSON to jump to code lands in the wrong place + - id: FALSIFY-NOFAB-006 + rule: User-supplied lookback never panics + prediction: > + `analyze churn -d N` terminates normally for every u32 N, including + 2147483647 and 4294967295. Before the fix, N >= ~100000000 aborted with + SIGABRT at src/services/git_analysis.rs:65 ("`DateTime - TimeDelta` overflowed"). + test: cargo test --lib no_fabrication::churn_lookback_is_clamped + if_fails: a user-supplied integer crashes the process with a core dump + - id: FALSIFY-NOFAB-007 + rule: A recoverable MCP frame does not end the session + prediction: > + An unknown method or malformed JSON line produces a JSON-RPC error and the + NEXT valid request is still answered. Before the fix, the session died + silently with exit 0 and every subsequent request was lost. + test: cargo test --lib no_fabrication::session_survives_bad_frame + if_fails: an MCP host sees the server "succeed" and vanish mid-conversation diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-quality-acceptance-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-quality-acceptance-v1.yaml new file mode 100644 index 0000000000..b8b98f6fc0 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-quality-acceptance-v1.yaml @@ -0,0 +1,83 @@ +metadata: + version: "1.0.0" + created: "2026-07-05" + author: PAIML Engineering + references: + - "docs/specifications/pmat-work-contract.md (Popperian falsification, PMAT-458)" + - "Popper (1959) The Logic of Scientific Discovery" + - "src/cli/handlers/work_contract_falsification.rs (FalsificationMethod: 23 quality guards)" + - "src/cli/handlers/work_quality_handlers.rs (run_popper_falsification: suite dispatch)" + registry: true + description: > + The quality-acceptance contract every pmat work ticket is completed under + (PMAT-458, Popperian falsification). A ticket is accepted iff NO quality + guard in FalsificationMethod can falsify it against the ticket's baseline + snapshot. This is exactly the invariant `pmat work complete` enforces via + run_popper_falsification. Binding a completed ticket to this contract + records the falsifiable suite that governed its acceptance (evidence + level L3: a bound equation whose falsification_tests execute and pass). + contract: pmat-quality-acceptance + status: active + +equations: + quality_acceptance: + formula: > + complete(t) = Ok(()) <-> forall g in guards: attempt_falsify(g, t) = NotFalsified + domain: t in .pmat-work/* (a work ticket carrying a baseline snapshot) + codomain: Result<()> + invariants: + - guards = FalsificationMethod::{ManifestIntegrity, DifferentialCoverage, + AbsoluteCoverage, TdgRegression, ComplexityRegression, FileSizeRegression, + SpecQuality, RoadmapUpdate, GitHubSync, CoverageGaming, SupplyChainIntegrity, + MetaFalsification, ExamplesCompile, BookValidation, SatdDetection, + DeadCodeDetection, PerFileCoverage, LintPass, VariantCoverage, FixChainLimit, + CrossCrateParity, RegressionGate, FormalProofVerification} — 23 independent guards + - acceptance requires EVERY guard to return NotFalsified; a single + falsified guard blocks completion (conjunction, not sampling) + - each guard is independently executable and independently falsifiable + against the baseline (evidence that the suite is not a rubber stamp) + preconditions: + - a baseline snapshot is captured at work start (contract.json baseline_commit, + baseline_file_manifest, baseline_coverage, baseline_tdg, baseline_rust_score) + +falsification: + - condition: "A ticket closes while any FalsificationMethod guard reports FAILED" + severity: P0 + action: reject_completion + - condition: "A guard reports PASSED without executing its check (rubber-stamp acceptance)" + severity: P0 + action: reject_completion + - condition: "The guard set silently drops a variant (acceptance narrows unnoticed)" + severity: P1 + action: reject_push + +falsification_tests: + - id: acceptance_requires_all_guards + rule: A ticket is accepted only if every quality guard returns NotFalsified + prediction: > + `pmat work falsify ` dispatches all 23 FalsificationMethod guards via + run_popper_falsification; if any guard finds a violation it reports FAILED + and completion is blocked. (Observed live: baseline-v1 fails ManifestIntegrity + and SupplyChainIntegrity — the suite genuinely falsifies.) + test: cargo run --bin pmat -- work falsify + if_fails: a ticket could complete with an unchecked or violated quality guard + - id: falsification_result_passed_semantics + rule: A guard that finds no counterexample yields PASSED (NotFalsified) + prediction: FalsificationResult::passed() reports success carrying no evidence + test: cargo test --lib test_falsification_result_passed + if_fails: passing guards misreport, allowing false acceptances or false rejections + - id: falsification_result_failed_semantics + rule: A guard that finds a counterexample yields FAILED carrying the evidence + prediction: FalsificationResult::failed() reports failure with the falsifying evidence + test: cargo test --lib test_falsification_result_failed + if_fails: a violated guard could be recorded as passing (rubber-stamp) + - id: falsification_coverage_accounting + rule: Falsification coverage is computed from evaluated-vs-total claims, never assumed + prediction: an empty claim set yields 0 coverage; a partial set yields proportional coverage + test: cargo test --lib test_compute_falsification_coverage_empty test_compute_falsification_coverage_partial + if_fails: acceptance could be declared while claims were never evaluated + - id: ladder_blocks_hollow_upgrade + rule: kani/lean declared without falsification_tests caps at L2 (no hollow L3+) + prediction: a YAML with kani_harnesses but no falsification_tests evidences L2, never L3 + test: cargo test --lib kani_without_falsification_tests_caps_at_l2 + if_fails: tickets earn L3+ without a real falsifiable test actually present diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-query-search-modes-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-query-search-modes-v1.yaml new file mode 100644 index 0000000000..a77ff0b5e3 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/pmat-query-search-modes-v1.yaml @@ -0,0 +1,97 @@ +metadata: + version: "1.0.0" + created: "2026-05-06" + author: PAIML Engineering + registry: true + description: > + Provable contract for the `pmat query --search-mode {semantic,lexical,hybrid}` + flag (issue #562). Captures the three teaching invariants that made the + flag necessary: (a) all three modes return results against a fresh + fixture corpus, (b) the lexical path requires NO embedding index (no + `pmat embed sync` gate), (c) the default `pmat query` (no flag) is + behaviour-identical to the v3.16.0 baseline. + contract: pmat-query-search-modes + status: enforced + references: + - https://github.com/paiml/paiml-mcp-agent-toolkit/issues/562 + - src/cli/handlers/query_handler/query_execution.rs + - src/cli/commands/commands_enum/definition.rs + +equations: + all_three_modes_return_results: + formula: results = pmat_query(query, search_mode in {semantic, lexical, hybrid}, corpus) + domain: query is non-empty String, corpus has >= 1 indexed function matching query + codomain: results in Vec, len >= 1 + invariants: + - lexical mode returns >= 1 result for canonical "literal" query against fixture corpus + - semantic mode returns >= 1 result for canonical "literal" query against fixture corpus + - hybrid mode returns >= 1 result for canonical "literal" query against fixture corpus + preconditions: + - corpus has been indexed via `pmat query --rebuild-index` + lean_theorem: Theorems.Search_Modes_NonEmpty + + lexical_does_not_require_embeddings: + formula: lexical_results = pmat_query(query, lexical, corpus_without_embeddings) + domain: corpus_without_embeddings has NO `.pmat/embeddings.idx` file + codomain: lexical_results in Vec, no error + invariants: + - "`--search-mode lexical` works on a fresh repo with no embeddings index" + - "lexical engine path uses calculate_literal_scores against name+signature+source+path" + - "lexical path NEVER calls into the semantic/embedding subsystem" + preconditions: + - function index exists (built by pmat query --rebuild-index) + lean_theorem: Theorems.Lexical_No_Embedding_Dep + + hybrid_top3_subset_of_lexical_or_semantic_top10: + formula: "hybrid_top3 is a subset of (lexical_top10 union semantic_top10)" + domain: same query against same corpus + codomain: "every key (file_path, function_name) in hybrid_top3 appears in lex top-10 or sem top-10" + invariants: + - RRF fusion never invents results + - "RRF score formula = 1 / (k + rank), k = 60, rank 1-indexed" + - "fused score = sum(rrf scores across both lists)" + preconditions: + - both lists non-empty + lean_theorem: Theorems.RRF_Closure_Subset + + default_unchanged: + formula: "pmat_query(query, no_flag) == pmat_query(query, semantic)" + domain: any non-empty query against any corpus + codomain: same result set, same ranking + invariants: + - "No --search-mode flag preserves v3.16.0 behaviour exactly (zero breaking change)" + - "`--search-mode semantic` is a no-op alias of the historical default" + preconditions: + - none + lean_theorem: Theorems.Default_Behaviour_Stable + +preconditions: + - "pmat binary >= 3.17.0 with --search-mode flag wired through" + - "Workspace contains at least one .rs/.py/.ts file with parseable functions" + +postconditions: + - "`pmat query --search-mode lexical` exits 0 against a fresh tempdir corpus" + - "`pmat query --search-mode semantic` exits 0 (default behaviour)" + - "`pmat query --search-mode hybrid` exits 0 with RRF-fused top-N results" + - "`pmat query ` without --search-mode is behaviour-identical to v3.16.0 output" + +falsification: + - condition: "--search-mode lexical errors when no embeddings index exists" + severity: P0 + action: reject_push + - condition: "--search-mode hybrid returns a result not present in either lexical or semantic top-10" + severity: P0 + action: reject_push + - condition: "Default `pmat query` output differs from `pmat query --search-mode semantic`" + severity: P0 + action: reject_push + - condition: "Adding --search-mode breaks the existing --regex / --literal flags" + severity: P0 + action: reject_push + - condition: "RRF k constant drifts from 60 (matches `pmat semantic search --search-mode hybrid`)" + severity: P1 + action: review_change + +verification: + local: "cargo test -p pmat --lib query_handler::tests::test_search_mode" + ci: ".github/workflows/ci.yml (test job)" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quality-check-content-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quality-check-content-v1.yaml new file mode 100644 index 0000000000..02df699285 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quality-check-content-v1.yaml @@ -0,0 +1,73 @@ +metadata: + version: "1.0.0" + created: "2026-09-02" + author: PAIML Engineering + references: + - "docs/specifications/pmat-architecture-crux-audit.md section 8.10 (CRUX-10, issue 1151)" + - "CHANGELOG.md 3.33.0 'Not done, deliberately' — no pmat_write_file; the harness PreToolUse hook is the write gate" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + The MCP tool formerly named quality_proxy advertised a write it never + performed: nine live calls across every mode and operation returned + accepted and created no file, and no response field said so. It is now + quality_check_content (alias quality_proxy for one release): it grades + content, discloses that it wrote nothing, never launders a failing verdict + as accepted, lets a client only tighten the project's gate, and keeps its + counts consistent with its lists. + contract: quality-check-content + status: draft + +equations: + never_writes_and_says_so: + formula: "for every call, response.written == false and the target path's bytes are unchanged" + domain: "any call to quality_check_content or its alias, any mode" + codomain: bool + invariants: + - "a request carrying operation is refused with -32602 (deny_unknown_fields), never silently accepted" + - "the alias returns a payload identical to the new name for the same request" + preconditions: [] + + advisory_does_not_launder: + formula: "mode == advisory and quality_report.passed == false implies status != accepted" + domain: "advisory-mode calls" + codomain: bool + invariants: + - "advisory still returns final_content, so the caller can proceed knowingly" + preconditions: [] + + client_config_only_tightens: + formula: "effective = tighten(project [quality] floor, client quality_config): min complexity, allow_satd only if both, require_docs if either" + domain: "a target path under a project with pmat.toml; no floor when there is none" + codomain: QualityConfig + invariants: + - "metrics.satd_count == count(violations[] where type == satd) in the same response, whatever allow_satd says" + preconditions: [] + +falsification: + - condition: "a response reports accepted for content that created or changed a file, or omits written (CRUX-10, issue 1151)" + severity: P0 + action: reject_push + - condition: "advisory returns accepted with passed == false" + severity: P0 + action: reject_push + - condition: "a client quality_config raises max_complexity or enables allow_satd above the project's pmat.toml" + severity: P1 + action: reject_push + +falsification_tests: + - id: never_writes_and_says_so + rule: "written is a boolean, false, and agrees with the filesystem; operation is refused" + prediction: "legs S, B0, R1, R2, R3, R5 of scripts/quality-check-content-audit.sh" + test: "bash scripts/quality-check-content-audit.sh against the release binary" + if_fails: "agents keep writing with their own harness believing the gate did" + - id: advisory_does_not_launder + rule: "advisory with a failing report is not accepted" + prediction: "leg B1; test_quality_proxy_advisory_mode asserts Rejected with content still returned" + test: "cargo test --test all test_quality_proxy_advisory_mode" + if_fails: "a status-only client treats rejected content as approved" + - id: client_config_only_tightens + rule: "the merge takes the stricter of project and client on every axis; satd_count equals the satd list" + prediction: "client_quality_config_can_only_tighten_the_project_floor and without_a_pmat_toml_the_default_config_is_the_floor pass; leg B2" + test: "cargo test --lib client_quality_config_can_only_tighten_the_project_floor" + if_fails: "any client can switch the gate off with two JSON keys" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quality-gate-not-measured-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quality-gate-not-measured-v1.yaml new file mode 100644 index 0000000000..7ebc2e7c73 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quality-gate-not-measured-v1.yaml @@ -0,0 +1,78 @@ +metadata: + version: "1.0.0" + created: "2026-09-03" + author: PAIML Engineering + references: + - "docs/specifications/pmat-architecture-crux-audit.md section 8.2 (CRUX-02, epic 1153)" + - "issue 1035 — quality-gate reports satd_violations=0 while analyze satd reports 55 (same shape)" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + pmat quality-gate rendered three unmeasured dimensions as clean: dead code + on a crate cargo check could not compile printed 0 violations found, the + coverage check trusted any cache file it found, and duplicate_violations + reported 0 beside a 21.67 percent block-level duplication it never looked + for. The gate now discloses each: not_measured names the dead-code + compile failure and the block-level half of duplicates; not_applicable + names a path with no Cargo.toml; a coverage report is accepted only if + its commit is HEAD-or-ancestor, it is newer than the newest tracked + source, and it covers at least 25 percent of the tree's Rust sources — a + rejection names the guard; the whole-file count is identical_files. + contract: quality-gate-not-measured + status: draft + +equations: + unmeasured_is_disclosed_not_zeroed: + formula: "dead_code on a crate cargo check cannot compile => results.not_measured contains {check: dead_code, reason ~ 'could not compile'}; no Cargo.toml at or above the path => results.not_applicable contains {check: dead_code}" + domain: "any pmat quality-gate run that selects dead-code" + codomain: bool + invariants: + - "a compiling crate has no dead_code entry in either list (control A)" + - "not_applicable is never reported as not_measured (control B): a non-Rust repository is not permanently amber" + - "both lists always serialize, empty included" + preconditions: [] + + coverage_report_must_come_from_this_tree: + formula: "accept(cache) iff git_hash is HEAD-or-ancestor and mtime(cache) >= mtime(newest tracked source) and covered_sources / rust_sources >= 0.25; else a coverage finding whose message says REJECTED and names the guard (git_hash: | mtime: | breadth:)" + domain: ".pmat/coverage-cache.json under the gated path" + codomain: bool + invariants: + - "a fresh report from HEAD covering the tree is accepted (control)" + - "an accepted detail cache is preferred over .pmat-metrics/coverage.json" + preconditions: [] + + duplicates_named_for_what_is_measured: + formula: "results.identical_files == count(whole files with byte-identical content); results has no duplicate_violations key; results.not_measured contains {check: duplicates, reason ~ 'block-level'}" + domain: "any run that selects duplicates" + codomain: bool + invariants: + - "the block-level detector is a separate item; the gate does not claim it" + preconditions: [] + +falsification: + - condition: "quality-gate reports dead_code_violations 0 with no not_measured entry on a crate cargo check fails to compile (CRUX-02)" + severity: P0 + action: reject_push + - condition: "a coverage cache whose git_hash is not HEAD-or-ancestor, or listing only nonexistent files, yields coverage_violations 0" + severity: P0 + action: reject_push + - condition: "the payload carries duplicate_violations, or identical_files without the block-level disclosure" + severity: P1 + action: reject_push + +falsification_tests: + - id: unmeasured_is_disclosed_not_zeroed + rule: "uncompilable crate => not_measured (could not compile); no manifest => not_applicable; compiling crate => neither" + prediction: "leg 1 with controls A and B of scripts/quality-gate-not-measured-audit.sh; dead_code_outcome_tests (4) pass" + test: "cargo test --lib dead_code_outcome_tests" + if_fails: "a pre-commit gate on a broken crate reports clean" + - id: coverage_report_must_come_from_this_tree + rule: "three named guards; the first to trip is the reason" + prediction: "leg 2 and its control; the a_report_* and a_rejected_report_* tests in coverage_sections_tests pass" + test: "cargo test --lib coverage_sections_tests" + if_fails: "a 114-commit-old or fabricated report passes the coverage floor" + - id: duplicates_named_for_what_is_measured + rule: "identical_files present, duplicate_violations absent, duplicates disclosed as block-level not measured" + prediction: "leg 3; the_results_payload_carries_the_honest_name_and_both_disclosure_lists passes" + test: "cargo test --lib the_results_payload_carries_the_honest_name_and_both_disclosure_lists" + if_fails: "0 duplicates is read as 0 clones on a 21.67 percent tree" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quorum-review-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quorum-review-v1.yaml new file mode 100644 index 0000000000..a32a89f687 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/quorum-review-v1.yaml @@ -0,0 +1,74 @@ +metadata: + version: "1.0.0" + created: "2026-09-04" + author: PAIML Engineering + references: + - "docs/specifications/agentic-delivery-pmat.md section 3.1 / 5.2 / 9.4 (AD-04)" + - "paiml-implement bundle: skills/quorum-review/{SKILL.md,quorum-review.sh,pmat-merge}, agy/quorum-schema.json" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + Nothing merges without a quorum verdict. quorum-review.sh sends the same + prompt (git diff base...HEAD, the ticket, the receipt, the refutation + doctrine) to N independent agy lanes under agy/quorum-schema.json and + writes docs/audits/quorum-.json with agreed = every lane PASS and + the HEAD it judged. pmat-merge, the gh pr merge wrapper, refuses --auto + unless an artifact agrees for the PR's current head; a non-auto merge is + passed through. A concurrency cap is not a vote: this is the vote. + contract: quorum-review + status: draft + +equations: + auto_merge_requires_agreement: + formula: "auto and not exists(a in docs/audits/quorum-*.json : a.agreed and (a.head == pr.head or (a.head == parent(pr.head) and files(pr.head) subset docs/audits/quorum-*.json))) => pmat-merge exits 1 naming docs/audits/quorum-.json and never calls gh pr merge" + domain: "any invocation of pmat-merge --auto in a repository checkout" + codomain: bool + invariants: + - "an agreeing artifact for another head does not count (a rebase invalidates the verdict)" + - "the one exception is the verdict commit itself: the artifact is committed on the branch it judged, so an agreeing artifact for the head's first parent counts when the head commit touches nothing but docs/audits/quorum-*.json (paiml-implement#6); a verdict commit that also changes code is refused" + - "an artifact for this head with agreed = false does not count" + - "an agreeing artifact for this head hands the call to gh pr merge unchanged, --auto included" + - "pmat-merge without --auto is passed through whatever docs/audits holds" + preconditions: [] + + agreement_is_unanimous: + formula: "agreed == (len(lanes) == width and all(lane.verdict == PASS))" + domain: "the artifact quorum-review.sh writes" + codomain: bool + invariants: + - "a lane whose output carries no verdict in the schema enum is recorded as NO-VERDICT and blocks" + - "the schema object echoed back by the executor is never mistaken for a verdict (verdict must be an enum string and findings a list)" + - "raw lane outputs are kept beside the artifact under .lanes/ for audit" + preconditions: + - "width in 1..10" + - "ticket matches ^[A-Z]+-[0-9]+$" + + lanes_refute: + formula: "a diff containing a test that asserts the opposite of the ticket draws >= 1 FAIL whose finding names that file; a diff that does what the ticket says draws width PASS" + domain: "the two live controls in docs/audits/impl-PMAT-656-receipt.md" + codomain: bool + invariants: + - "findings carry file, claim and grounding in {cited, measured, asserted}; asserted findings are reported, not counted as refutation on their own" + preconditions: [] + +falsification_tests: + - id: F1 + rule: "the merge helper exists in the installed skill" + prediction: "with QUORUM_SKILL_DIR pointing at a directory without pmat-merge the audit exits 1 and its first leg reads helper present (missing ...) — the 3.36.0 state" + test: "QUORUM_SKILL_DIR=/nonexistent bash scripts/quorum-review-audit.sh" + if_fails: "the audit passed without a helper: its first leg no longer checks presence, or a helper leaked onto the path" + - id: F2 + rule: "the seven offline legs through a stub gh" + prediction: "GREEN, exit 0: refused without an artifact naming the file; refused for another head and for agreed=false; gh pr merge --auto called on agreement; non-auto passthrough; the docs-only verdict commit accepted for its parent; a verdict commit that also changes code refused" + test: "bash scripts/quorum-review-audit.sh" + if_fails: "pmat-merge changed its refusal or acceptance rule; read which leg is red and compare with auto_merge_requires_agreement" + - id: F3 + rule: "the helper can be broken (named mutations M1, M2, M3)" + prediction: "a helper that ignores agreed, one that ignores head, and one that accepts the parent unconditionally each turn the corresponding leg RED" + test: "QUORUM_SKILL_DIR= bash scripts/quorum-review-audit.sh" + if_fails: "a mutant passed: the audit no longer discriminates that rule and is vacuous for it" + - id: F4 + rule: "the live lanes refute and agree" + prediction: "the planted-contradiction artifact carries at least one FAIL naming commit_enforcement_tests and agreed=false; the after-fix artifact carries width PASS and agreed=true" + test: "bash scripts/quorum-review-audit.sh --clean docs/audits/quorum-PMAT-655-after-fix.json --planted docs/audits/quorum-PMAT-655-planted-control.json" + if_fails: "the lanes no longer refute a planted contradiction, or the parser mistook something other than an enum-string verdict for a verdict" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/tdg-grade-order-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/tdg-grade-order-v1.yaml new file mode 100644 index 0000000000..42b0e3af57 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/tdg-grade-order-v1.yaml @@ -0,0 +1,246 @@ +metadata: + version: "1.0.0" + kind: kernel + created: "2026-08-20" + author: PAIML Engineering + registry: true + # The type these obligations are proved ABOUT. `scripts/pv-obligation-gate.py` + # requires that every function named by an `applies_to` actually mentions it, + # so a contract cannot be green while the code it names ignores the proof. + # This is not hypothetical: thirteen theorems below proved the grade order + # while `check_tdg_grade_gate` still carried its own ["A","B","C","D","F"] + # table and never referenced `Grade`, and every gate in the repository was + # green throughout. + proved_type: Grade + contract: tdg-grade-order-v1 + status: active + description: > + The TDG grade scale as an ORDER, and the CB-200 threshold as a predicate + over it. Written because CB-200 shipped neither. + + The gate carried a private five-letter table, ["A","B","C","D","F"], and + compared it to the stored spelling with SQL `IN`. `IN` answers "no row" for + a value it does not list, never an error, so an eleven-letter writer and a + five-letter reader coexisted for a release: at a floor of "A" the gate saw + 247 violations and could not see 1,719 more. It was also not monotone in + quality — a C- function passed while a C function failed. + + Its ordinal function mapped every modified grade to a catch-all ranked + WORSE THAN F. So a floor of "A-" produced an empty failing set, the caller + took its is_empty() branch, and the rule returned Pass with the message + "Minimum grade A- - no grades below threshold". `.pmat-metrics.toml:44` + declares exactly that spelling. A stricter-looking threshold turned the gate + off while printing a sentence that reads like enforcement. + + WHY THE OBVIOUS INVARIANTS ARE NOT ENOUGH, which is the reason this contract + reaches L5 rather than stopping at a const assertion. An adversarial review + of the first fix showed that reversing the enum declaration together with + `Grade::ALL` leaves EVERY self-referential invariant intact: the const block + passes, Display stays injective, parse stays a left inverse, all 121 pairs + and 1,331 triples pass, and passing/below still partition — while + `meets_threshold` reports that A+ fails an F floor. All of those invariants + are internal to the order; none anchors rank to anything outside itself. + + `Grade_Rank_Anchored_To_Score` is that anchor. `bandFloor` is attached to + variant NAMES (mirroring GRADE_BANDS, grade.rs:172-182), so a better rank + must carry a strictly higher score floor. Under the reversal above, Lean + rejects it: "decide proved that the proposition is false". Verified by + mutation, not asserted. + + verification: + lean_status: discharged + lean_toolchain: "leanprover/lean4:v4.15.0" + measured_at: "pmat 3.32.0, lean 4.15.0, 2026-08-20" + detail: > + All 13 theorems in contracts/lean/Theorems/Tdg/Grade.lean build under + `lake build`, contain no `sorry` or `admit`, and `#print axioms` reports + that each depends on NO axioms — every proof is closed by `decide` or + `cases ... <;> decide` over a finite domain. Unlike a kani harness, these + are EXECUTED in CI: .github/workflows/quality-gate.yml:163 runs + `lake build` and :170 greps for proof holes, both inside the + `provable ladder` job. + + Kani is deliberately NOT used for the order algebra. The domain is 11, + 121 and 1,331 closed enum-to-enum cases, so a loop over Grade::ALL is a + complete proof over it AND runs under `cargo test --lib`. Declaring a + kani harness there would name a proof technique without carrying it out, + which is the defect commit a44ec5d3e already ruled on. + references: + - src/tdg/grade.rs + - src/cli/handlers/comply_handlers/check_handlers/check_tdg_grade.rs + - src/services/agent_context/query/grades.rs + - contracts/lean/Theorems/Tdg/Grade.lean + - contracts/macs-ladder-kernel-v1.yaml + +equations: + grade_rank_anchored: + formula: "rank(a) < rank(b) => band_floor(b) < band_floor(a) for a,b != F" + domain: "two of the eleven Grade variants, neither being F" + codomain: "bool" + invariants: + - "INV-2200-1: a better rank carries a strictly higher score floor" + - "the ONLY invariant that is not internal to the order: it fails under a reversal of the enum declaration that every other invariant here waves through" + - "F is excluded because it is the fallthrough of GRADE_BANDS and has no floor of its own" + lean_theorem: Theorems.Tdg.Grade_Rank_Anchored_To_Score + + grade_from_score: + formula: "s1 + 1 >= s1 => rank(from_score(s1 + 1)) <= rank(from_score(s1))" + domain: "integer score positions 0..100" + codomain: "Grade" + invariants: + - "INV-2200-2: one more point of score never yields a worse grade" + - "INV-2200-3: from_score(band_floor(g)) = g for every g != F, so the two tables cannot drift apart" + - "stepwise, not pairwise: on a discrete domain the two are equivalent by transitivity, and the pairwise form exhausts Lean's kernel recursion depth at 10,201 cases" + - "checks every integer position; the Rust sweep at grade.rs:380-407 samples 1,001 points and can miss a band edge falling between two samples" + lean_theorem: Theorems.Tdg.Grade_FromScore_Antitone_Step + + grade_floor_partition: + formula: "|passing(t)| + |below(t)| = |all| for every floor t" + domain: "one of the eleven Grade variants, as a floor" + codomain: "two disjoint grade sets covering the scale" + invariants: + - "INV-2200-4: passing and below partition the whole scale at every floor" + - "this is Defect 1 stated as a theorem: the shipped five-letter table partitioned nothing, so 1,719 of 1,966 violations were invisible" + - "INV-2200-5: below(t) is empty for exactly one floor, F" + - "this is Defect 2 stated as a theorem: the catch-all ranked modified grades worse than F, so a floor of A- produced an empty failing set and the rule returned Pass" + - "INV-2200-6: below is antitone in the floor - a stricter floor never fails fewer grades" + lean_theorem: Theorems.Tdg.Grade_Partition + + grade_order_algebra: + formula: "meets_threshold(g, t) = (rank(g) <= rank(t)); total, transitive, antisymmetric" + domain: "two of the eleven Grade variants" + codomain: "bool" + invariants: + - "INV-2200-7: totality - any two grades are comparable" + - "INV-2200-8: transitivity over all 1,331 triples" + - "INV-2200-9: antisymmetry" + - "INV-2200-10: rank is injective, so Ord never conflates two grades" + - "INV-2200-11: to_str is injective, so the stored TEXT column is an unambiguous encoding of the order" + lean_theorem: Theorems.Tdg.Grade_Order_Transitive + +preconditions: + - "pmat >= 3.32.0 with CB-200 reading .pmat/context.db" + - "the stored tdg_grade spelling is one of the eleven Grade::ALL spellings" + +postconditions: + - "a grade absent from the gate's vocabulary is REPORTED, never silently unmatched" + - "a threshold that does not parse FAILS the rule; it never yields an empty failing set" + - "the failing set is computed as the complement of the passing set, never listed by hand" + +proof_obligations: + - type: postcondition + property: "a better rank carries a strictly higher score floor" + formal: "∀a,b ≠ F: rank(a) < rank(b) ⇒ band_floor(b) < band_floor(a)" + applies_to: meets_threshold + lean: + theorem: Theorems.Tdg.Grade_Rank_Anchored_To_Score + module: Theorems.Tdg.Grade + status: proved + notes: > + cases a <;> cases b <;> decide — depends on no axioms. THE anchoring + obligation: it is the only one here that is not internal to the order, + and it is the only one that fails when the enum declaration and + Grade::ALL are reversed together. Verified by that mutation, which Lean + rejects with "decide proved that the proposition is false". + - type: postcondition + property: "passing and below partition the scale at every floor" + formal: "∀t: |passing(t)| + |below(t)| = |all|" + applies_to: check_tdg_grade_gate + lean: + theorem: Theorems.Tdg.Grade_Partition + module: Theorems.Tdg.Grade + status: proved + notes: > + Defect 1 as an obligation. The shipped five-letter table partitioned + nothing, so at a floor of A the gate saw 247 violations and 1,719 were + invisible. + - type: postcondition + property: "the failing set is empty for exactly one floor, F" + formal: "∀t: below(t) = ∅ ⇔ t = F" + applies_to: check_tdg_grade_gate + lean: + theorem: Theorems.Tdg.Grade_Below_Empty_Only_At_F + module: Theorems.Tdg.Grade + status: proved + notes: > + Defect 2 as an obligation. grade_ordinal ranked every modified grade + worse than F, so a floor of "A-" produced an empty failing set and the + rule returned Pass. .pmat-metrics.toml:44 declares that spelling. + - type: postcondition + property: "one more point of score never yields a worse grade" + formal: "∀s ∈ 0..100: rank(from_score(s+1)) ≤ rank(from_score(s))" + applies_to: from_score + lean: + theorem: Theorems.Tdg.Grade_FromScore_Antitone_Step + module: Theorems.Tdg.Grade + status: proved + notes: > + Checks every integer position. The Rust sweep at grade.rs:380-407 + samples 1,001 points and can miss a band edge falling between two + samples. Stepwise rather than pairwise: equivalent on a discrete domain + by transitivity, and the 10,201-case pairwise form exhausts Lean's + kernel recursion depth. + - type: postcondition + property: "the order is total, transitive and antisymmetric; rank and spelling are injective" + formal: "meets_threshold(g,t) = rank(g) ≤ rank(t), a total order" + applies_to: meets_threshold + lean: + theorem: Theorems.Tdg.Grade_Order_Transitive + module: Theorems.Tdg.Grade + status: proved + notes: > + 1,331 closed triples checked by the kernel, plus totality, + antisymmetry, rank injectivity and to_str injectivity as separate + theorems in the same module. + +falsification_tests: + - id: grade_order_reversal + rule: "the grade order is anchored to the score scale, not merely self-consistent" + prediction: "reversing the enum declaration and Grade::ALL together makes lake build FAIL" + test: "cd contracts/lean && lake build # Theorems.Tdg.Grade_Rank_Anchored_To_Score" + if_fails: "A+ can rank worse than F while every internal invariant still holds, and CB-200 inverts silently" + - id: grade_modified_invisible + rule: "every grade below the floor is reported" + prediction: "a five-letter failing list makes |passing| + |below| < |all| for some floor" + test: "cd contracts/lean && lake build # Theorems.Tdg.Grade_Partition" + if_fails: "1,719 of 1,966 violations are invisible and CB-200 reports 247 (measured at HEAD)" + - id: grade_floor_disables_gate + rule: "only a floor of F admits every grade" + prediction: "a catch-all ordinal makes below(A-) empty, so the rule returns Pass" + test: "cd contracts/lean && lake build # Theorems.Tdg.Grade_Below_Empty_Only_At_F" + if_fails: "min_tdg_grade: \"A-\" silently disables CB-200 while printing a sentence that reads like enforcement" + - id: grade_band_edge_inverts + rule: "score and grade move in opposite directions, monotonically" + prediction: "an inverted band edge makes one step of score yield a worse grade" + test: "cd contracts/lean && lake build # Theorems.Tdg.Grade_FromScore_Antitone_Step" + if_fails: "a function scoring higher is graded worse, and every downstream ranking inverts at that edge" + - id: grade_spelling_collision + rule: "rank and wire spelling are both injective" + prediction: "two grades sharing a rank or a spelling makes the stored TEXT column ambiguous" + test: "cd contracts/lean && lake build # Theorems.Tdg.Grade_Rank_Injective, Grade_ToStr_Injective" + if_fails: "the SQLite tdg_grade column stops being a faithful encoding of the order" + - id: grade_parse_corruption + rule: "corrupted spellings are rejected, never ranked" + prediction: "empty, padded, out-of-set or unicode-minus spellings parse to None" + test: "cd contracts/lean && lake build # Theorems.Tdg.Grade_Parse_Strict" + if_fails: "a copy-pasted \"A−\" (U+2212) is ranked by a catch-all instead of rejected" + +kani_harnesses: [] + +qa_gate: + id: TDG-GRADE-ORDER-GATE + name: TDG grade order and threshold gate + description: > + L5 provability gate for pmat's own grade scale. Kani is deliberately empty: + the order algebra is 11, 121 and 1,331 closed enum-to-enum cases, so a loop + over Grade::ALL is a complete proof over that domain and it RUNS under + `cargo test --lib`. Declaring a harness there would record the name of a + proof technique without carrying it out — the defect commit a44ec5d3e + already ruled on. The Lean proofs, by contrast, are executed in CI: + quality-gate.yml:163 runs `lake build` and :170 fails on any sorry/admit. + checks: + - validation + - falsification + pass_criteria: > + All 13 theorems in Theorems.Tdg.Grade build with 0 sorry / 0 admit, and + `#print axioms` reports no axiom dependency for any of the 5 obligations. diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/verify-verdict-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/verify-verdict-v1.yaml new file mode 100644 index 0000000000..71700ddfc4 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/verify-verdict-v1.yaml @@ -0,0 +1,65 @@ +metadata: + version: "1.0.0" + created: "2026-09-02" + author: PAIML Engineering + references: + - "docs/specifications/pmat-architecture-crux-audit.md section 8.1 (CRUX-01, issue 1146)" + - "docs/specifications/pmat-verify-autonomous-preflight.md" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + pmat verify is the one gate agents are told to trust before every commit. + Its composite verdict must never assert safety over a stage it did not + measure, and its strict SATD stage must see every marker quality-gate + blocks on. On the repository's own clean tree it returned ok:true with the + complexity stage declined and strict SATD reading 0 where the default read 3. + contract: verify-verdict + status: draft + +equations: + verdict_tri_state: + formula: "ok = Some(false) if failed or measured == 0; None if measured > 0 and declined > 0; Some(true) otherwise" + domain: "the selected stages of one pmat verify run" + codomain: "Option plus exit code (1 iff Some(false))" + invariants: + - "not_measured[] is derived from the stages that returned not_applicable, never from a constant" + - "a --skip'ped stage is not declined and never appears in not_measured[]" + - "a measured failure is Some(false) whatever else declined" + preconditions: [] + + strict_sees_what_the_gate_blocks: + formula: "for every SATD finding quality-gate reports at severity error, analyze satd --strict reports the same line" + domain: "any tree; the repository's own tdg_calculator_core.rs:110 and quality_checks_part4.rs:117 are the motivating sites" + codomain: bool + invariants: + - "strict accepts the canonical markers upper case or capitalised, any standard separator, and a non-empty work item" + - "strict remains a subset of default: todo:, TODO x, and prose are not debt in strict" + preconditions: [] + +falsification: + - condition: "verify prints ok:true on a run in which a selected stage declined to measure (CRUX-01, issue 1146)" + severity: P0 + action: reject_push + - condition: "a stage that measured a failure is reported as anything but ok:false with exit 1" + severity: P0 + action: reject_push + - condition: "quality-gate blocks on a SATD line that analyze satd --strict does not report" + severity: P1 + action: reject_push + +falsification_tests: + - id: verdict_tri_state + rule: "the composite table, every row" + prediction: "composite_verdict(false, 2, 1) is None; (true, 1, 1) and (false, 0, 3) are Some(false); (false, 3, 0) is Some(true)" + test: "cargo test --lib composite_verdict_withdraws_rather_than_asserts_over_a_declined_stage" + if_fails: "verify asserts safe-to-commit over a stage it never looked at" + - id: verdict_end_to_end + rule: "on a clean crate with no Rust change vs HEAD, verify reports ok:null, not_measured [complexity], exit 0; with a TODO it reports ok:false, exit 1" + prediction: "legs 1, 1-RED, 1-EMPTY and 1-SKIP of scripts/verify-verdict-audit.sh" + test: "bash scripts/verify-verdict-audit.sh against the release binary" + if_fails: "the JSON contract agents read is not the one the table promises" + - id: strict_sees_what_the_gate_blocks + rule: "TODO(x): and TODO[x]: and Bug: are debt in strict; todo:, TODO x and TODO: with no work item are not" + prediction: "strict_accepts_every_standard_separator_and_the_capitalised_marker passes; strict_is_a_subset_of_default still passes" + test: "cargo test --lib strict_accepts_every_standard_separator_and_the_capitalised_marker strict_is_a_subset_of_default" + if_fails: "verify's SATD stage is green on a tree quality-gate blocks" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/work-ladder-claim-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/work-ladder-claim-v1.yaml new file mode 100644 index 0000000000..282065fda6 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/work-ladder-claim-v1.yaml @@ -0,0 +1,78 @@ +metadata: + version: "1.0.0" + created: "2026-09-04" + author: PAIML Engineering + references: + - "issue 1186 — pmat work claims L3 by default; --implements only on start; ladder check ran after the quality gate" + - "docs/specifications/agentic-delivery-pmat.md section 5 (sub-agent level compliance)" + - "Popper (1959) The Logic of Scientific Discovery" + registry: true + description: > + A ticket's verification claim is what the work can honestly show. A ticket + started without a contract binding claims L1; one started with --implements + claims L2 and carries the binding; --level states the claim explicitly on + add, start and edit. --implements on edit binds a ticket that is already in + progress, so the normal flow can reach L2 without restarting. A binding must + name an equation the contract declares. On complete, the ladder is judged + before the quality gate, so an over-claim is refused as LadderShortfall + rather than surfacing behind unrelated gate output. + contract: work-ladder-claim + status: draft +equations: + initial_claim: + formula: "claim(start) = explicit_level if given else (L2 if bound(implements) else L1)" + domain: "pmat work start, pmat work add --level" + codomain: VerificationLevel + invariants: + - "an unbound start claims L1, never L3" + - "a start with --implements claims L2 and the contract carries the binding" + - "--level on add is stored on the ticket and becomes the claim at start" + - "a --level that is not a ladder level (L0..L5) is refused before anything is written" + preconditions: [] + edit_rebinds: + formula: "edit --level L | edit --implements c/e => contract.level = L | contract.implements += (c, e, sha)" + domain: "pmat work edit on a ticket whose contract exists (started)" + codomain: bool + invariants: + - "edit --implements on an InProgress ticket binds and lifts an L1 claim to L2" + - "a binding naming an equation the contract does not declare is refused, naming the declared ones" + - "edit --level alone is a change: it must not exit 0 as 'No changes specified'" + - "edit on a ticket with no contract yet names `pmat work start` as the prerequisite" + preconditions: [] + ladder_before_gate: + formula: "complete: ladder_shortfall(claim, evidence) is judged before run_quality_check" + domain: "pmat work complete" + codomain: bool + invariants: + - "an over-claim is refused with LadderShortfall and no quality-gate output precedes it" + - "an honestly-claimed L1 ticket is not refused by the ladder" + preconditions: [] +falsification: + - condition: "an unbound ticket claims above L1 at start, or a bound one claims below L2" + severity: P0 + action: reject_push + - condition: "edit --level or edit --implements exits 0 without changing the contract" + severity: P0 + action: reject_push + - condition: "a binding to an undeclared equation is accepted" + severity: P1 + action: reject_push + - condition: "quality-gate output precedes a LadderShortfall refusal on complete" + severity: P1 + action: reject_push +falsification_tests: + - id: initial_claim + rule: "legs 1, 2, 3a and control 3c of scripts/work-ladder-claim-audit.sh" + prediction: "3.36.0 binary: legs 1, 2, 3a red (L3 default; add has no --level); fixed binary: green" + if_fails: "the claim is not derived from the binding; the ladder gate judges a number nobody chose" + test: "PMAT= bash scripts/work-ladder-claim-audit.sh" + - id: edit_rebinds + rule: "legs 3b, 4 and controls 3c, 4b of scripts/work-ladder-claim-audit.sh; unit test resolve_refuses_an_equation_the_contract_does_not_declare" + prediction: "3.36.0 binary: edit has neither flag (clap refuses); fixed binary: both flags act on the contract and 4b refuses" + if_fails: "an in-progress ticket can never reach L2 legitimately, or binds to nothing" + test: "PMAT= bash scripts/work-ladder-claim-audit.sh" + - id: ladder_before_gate + rule: "legs 5a, 5b and control 6 of scripts/work-ladder-claim-audit.sh" + prediction: "3.36.0 binary: 5b red (quality output first); fixed binary: LadderShortfall is the first refusal" + if_fails: "an over-claim hides behind gate noise and the operator reaches for --override-claims" + test: "PMAT= bash scripts/work-ladder-claim-audit.sh" diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/workspace-scoring-v1.yaml b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/workspace-scoring-v1.yaml new file mode 100644 index 0000000000..addbbab1d1 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/contracts/workspace-scoring-v1.yaml @@ -0,0 +1,74 @@ +metadata: + version: "1.0.0" + created: "2026-04-08" + author: PAIML Engineering + registry: true + description: > + Workspace-aware rust-project-score: detect and score subcrates in + monorepo workspaces (e.g., aprender with 60+ crates). Each subcrate + gets its own RPS score. Aggregate scores into workspace-level grade. + contract: workspace-scoring + status: planned + references: + - docs/specifications/components/repo-health.md + +equations: + subcrate_discovery: + formula: subcrates = discover_workspace_members(project_path) + domain: project_path in Path, Cargo.toml exists + codomain: subcrates in Vec<(String, PathBuf)> + invariants: + - subcrates.len() >= 1 (workspace always has at least the root) + - all paths in subcrates are valid directories with Cargo.toml + preconditions: + - project_path.join("Cargo.toml").exists() + lean_theorem: Theorems.Workspace_Discovery_Nonempty + + workspace_aggregate: + formula: workspace_score = geometric_mean(subcrate_scores) + domain: subcrate_scores in Vec, all >= 0.0 + codomain: workspace_score in [0.0, 100.0] + invariants: + - workspace_score >= min(subcrate_scores) (geometric mean property) + - workspace_score <= max(subcrate_scores) + preconditions: + - subcrate_scores.len() > 0 + lean_theorem: Theorems.Geometric_Mean_Bounded + + per_crate_scoring: + formula: crate_score = rps_score(subcrate_path) + domain: subcrate_path in Path, subcrate_path.join("Cargo.toml").exists() + codomain: crate_score in CategoryScore + invariants: + - crate_score.earned >= 0.0 + - crate_score.earned <= crate_score.max + preconditions: + - subcrate_path.join("src").exists() || subcrate_path.join("lib.rs").exists() + lean_theorem: Theorems.Score_Range_NonNeg + +preconditions: + - "pmat rust-project-score --help exits 0" + - "Target workspace (e.g., ../aprender) has Cargo.toml with [workspace]" + - "At least 1 workspace member has src/ directory" + +postconditions: + - "pmat rust-project-score --path ../aprender produces per-crate table" + - "Workspace aggregate score is geometric mean of subcrate scores" + - "Subcrates without src/ are skipped (not errored)" + +falsification: + - condition: "pmat rust-project-score --path ../aprender panics" + severity: P0 + action: fix_panic + - condition: "Workspace with 0 members returns non-zero score" + severity: P0 + action: fix_logic + - condition: "Per-crate score exceeds 289 (max RPS)" + severity: P0 + action: fix_bounds + - condition: "Subcrate without Cargo.toml is included in scoring" + severity: P1 + action: skip_invalid + - condition: "Aggregate geometric mean exceeds max individual score" + severity: P0 + action: fix_math diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/comply_handlers/check_handlers/check_tdg_grade.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/comply_handlers/check_handlers/check_tdg_grade.rs.txt new file mode 100644 index 0000000000..39bd4c0d67 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/comply_handlers/check_handlers/check_tdg_grade.rs.txt @@ -0,0 +1,2596 @@ +// CB-200: TDG Grade Gate (#214) +// +// Reads the SQLite agent-context index and fails if definitions fall below a +// configurable minimum TDG grade (default A). +// +// The index is the project's own `.pmat/context.db` when it has one, and +// otherwise the copy pmat keeps for that project OUTSIDE it, under the user's +// cache directory, built here on demand. See `TdgIndex` for why the second +// exists: without it this gate could only ever fail on a machine that happened +// to have an index lying around, which is never a CI checkout (#1008). + +use crate::models::comply_config::ComplyConfig; +use crate::tdg::grade::GRADE_VARIANTS; +use crate::tdg::Grade; +use std::path::{Path, PathBuf}; + +use super::types::*; + +/// Convert a TDG grade letter to a numeric ordinal for comparison. +/// The spellings a floor ADMITS, computed from the proved order. +/// +/// Returns `None` when the threshold is not a grade this codebase produces, so +/// an unreadable threshold fails the rule instead of yielding an empty set that +/// reads as "nothing violates". +/// +/// This replaces a private `grade_ordinal` over `["A","B","C","D","F"]` with a +/// `_ => 5` catch-all. Both halves were wrong. The five-letter list could never +/// match a MODIFIED grade against `WHERE tdg_grade IN (...)`, so at a floor of +/// "A" the gate saw 247 violations and could not see 1,719. And the catch-all +/// ranked every modified grade WORSE THAN F, so `grades_below("A-")` was empty +/// and the caller returned Pass — `.pmat-metrics.toml:44` declares exactly that +/// spelling. +/// +/// The order is `Grade`'s, anchored to the score bands in +/// `contracts/lean/Theorems/Tdg/Grade.lean::Grade_Rank_Anchored_To_Score`. +fn passing_spellings(min_grade: &str) -> Option> { + let floor = Grade::from_variant_name(min_grade.trim())?; + Some( + GRADE_VARIANTS + .iter() + .copied() + .filter(|g| Grade::from_variant_name(g).is_some_and(|x| x.meets_threshold(floor))) + .collect(), + ) +} + +struct TdgViolation { + file_path: String, + function_name: String, + tdg_grade: String, + complexity: u32, + start_line: usize, +} + +/// How many below-floor definitions this project has already agreed to carry, +/// read from `.pmat-gates.toml` `[tdg] baseline`. +/// +/// CB-200 is a RATCHET, not a threshold, and the distinction is the whole +/// design. `.pmat-ratchet.toml`'s own header states it: "A number in a config +/// file that nobody re-runs is not a gate; it is a wish with a colon after it." +/// `.pmat-metrics.toml:45` is this repository's worked example — +/// `max_unwrap_calls = 100`, annotated `Current: 570`, in a tree measuring +/// 20,390: three numbers, no two agreeing, nothing reading the key, green +/// throughout. +/// +/// This number is different in exactly one way, and it is the only way that +/// matters: it is RE-DERIVED on every run by the same query that produced it, +/// and compared. It can never quietly become a transcription. It is a record of +/// debt, not a permission to add more — a definition below the floor that +/// pushes the count past it fails the gate, closed, and the absolute count is +/// printed on every outcome so "passing" can never be read as "clean". +/// +/// It does NOT touch `min_tdg_grade` and adds no exclude glob. Both of those +/// are threshold-lowering in disguise: they make debt invisible, where this +/// keeps every unit of it counted and reported. +#[derive(Debug, PartialEq, Eq)] +enum TdgBaseline { + /// No `baseline` key. Zero tolerance — any violation fails, exactly as + /// before. This is the default precisely so that ratcheting THIS repo + /// cannot silently relax any other repo that runs pmat. + Absent, + /// The count this project last agreed to hold flat. + Held(usize), + /// The key is present and is not a count. Fails, for the same reason an + /// unparseable `min_grade` fails: a bound nobody can read must not be read + /// as a bound nothing exceeds. + Unreadable(String), +} + +struct TdgGateOverrides { + min_grade: Option, + exclude: Vec, + baseline: TdgBaseline, +} + +impl Default for TdgGateOverrides { + /// No overrides: the floor comes from `.pmat.yaml`, nothing extra is + /// excluded, and the gate has zero tolerance. An unreadable or unparsable + /// `.pmat-gates.toml` lands here, which fails CLOSED — the excludes vanish + /// and the baseline vanishes with them, so a typo cannot buy headroom. + fn default() -> Self { + Self { + min_grade: None, + exclude: Vec::new(), + baseline: TdgBaseline::Absent, + } + } +} + +/// A `baseline` value is a count or it is nothing. A string, a float, a +/// negative — anything that is not a non-negative integer — is reported rather +/// than rounded down to "no baseline", because "the key you wrote does nothing" +/// and "you configured zero tolerance" are opposite claims and look identical +/// from the outside. +fn parse_tdg_baseline(value: Option<&toml::Value>) -> TdgBaseline { + let Some(value) = value else { + return TdgBaseline::Absent; + }; + match value.as_integer() { + Some(n) if n >= 0 => TdgBaseline::Held(n as usize), + _ => TdgBaseline::Unreadable(value.to_string()), + } +} + +fn load_tdg_gate_overrides(project_path: &Path) -> TdgGateOverrides { + let path = project_path.join(".pmat-gates.toml"); + let Ok(content) = std::fs::read_to_string(&path) else { + return TdgGateOverrides::default(); + }; + let Ok(table) = content.parse::() else { + return TdgGateOverrides::default(); + }; + let tdg = table.get("tdg"); + let min_grade = tdg + .and_then(|t| t.get("min_grade")) + .and_then(|v| v.as_str()) + .map(String::from); + let exclude = tdg + .and_then(|t| t.get("exclude")) + .and_then(|v| v.as_array()) + .map(|a| { + a.iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect() + }) + .unwrap_or_default(); + let baseline = parse_tdg_baseline(tdg.and_then(|t| t.get("baseline"))); + TdgGateOverrides { + min_grade, + exclude, + baseline, + } +} + +/// Is `.pmat/context.db` older than the sources it claims to describe? +/// +/// `pub(crate)` because `services::tdg_baseline` asks the same question before +/// it treats CB-200's count as the count at HEAD. It CALLS this rather than +/// re-deriving it: two implementations of one predicate is how CB-200 went +/// blind for a release (see [`passing_spellings`]). +pub(crate) fn is_index_stale(project_path: &Path, db_path: &Path) -> bool { + let db_mtime = match std::fs::metadata(db_path).and_then(|m| m.modified()) { + Ok(t) => t, + Err(_) => return true, + }; + for dir_name in ["src", "lib"] { + let dir = project_path.join(dir_name); + if !dir.exists() { + continue; + } + if has_newer_source_file(&dir, db_mtime) { + return true; + } + } + false +} + +fn has_newer_source_file(dir: &Path, threshold: std::time::SystemTime) -> bool { + let Ok(entries) = std::fs::read_dir(dir) else { + return false; + }; + entries.flatten().any(|entry| { + let path = entry.path(); + if path.is_dir() { + has_newer_source_file(&path, threshold) + } else { + is_source_file(&path) && entry_is_newer(&entry, threshold) + } + }) +} + +/// Is a directory entry's mtime newer than `threshold`? (false on any I/O error) +fn entry_is_newer(entry: &std::fs::DirEntry, threshold: std::time::SystemTime) -> bool { + entry + .metadata() + .and_then(|m| m.modified()) + .map(|mtime| mtime > threshold) + .unwrap_or(false) +} + +fn is_source_file(path: &Path) -> bool { + path.extension() + .and_then(|e| e.to_str()) + .is_some_and(|ext| { + matches!( + ext, + "rs" | "ts" + | "tsx" + | "js" + | "jsx" + | "py" + | "go" + | "java" + | "kt" + | "swift" + | "c" + | "cpp" + | "cs" + ) + }) +} + +// `rebuild_index` used to live here: CB-200 called +// `AgentContextIndex::build[_incremental]` and SAVED the result whenever +// `.pmat/context.db` was missing or stale, so merely ASKING for a compliance +// verdict wrote 160KB+ of index into the project under audit and, on a large +// repo, spent minutes doing it. Building the index is `pmat query`'s job; the +// gate now reads what exists and refuses honestly when nothing does (#939). + +/// Check TDG grade gate against the SQLite index. +/// Query violations from the context database for grades below threshold +fn query_tdg_violations( + db_path: &Path, + passing_grades: &[&str], +) -> Result, ComplianceCheck> { + let conn = rusqlite::Connection::open_with_flags( + db_path, + rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX, + ) + .map_err(|e| ComplianceCheck { + name: "CB-200: TDG Grade Gate".into(), + status: CheckStatus::Skip, + message: format!("Failed to open context.db: {e}"), + severity: Severity::Info, + })?; + let placeholders: Vec = passing_grades + .iter() + .enumerate() + .map(|(i, _)| format!("?{}", i + 1)) + .collect(); + // NOT IN the PASSING set, never IN a failing list. `IN` answers "no row" + // for a value it does not list and never an error, so an eleven-letter + // writer and a five-letter reader coexisted silently for a release. + // Enumerating what passes makes any future alphabet drift return a row that + // must be classified, instead of a smaller number nobody can see. + let sql = format!("SELECT file_path, function_name, tdg_grade, complexity, start_line FROM functions WHERE tdg_grade NOT IN ({})", placeholders.join(", ")); + let mut stmt = conn.prepare(&sql).map_err(|e| ComplianceCheck { + name: "CB-200: TDG Grade Gate".into(), + status: CheckStatus::Skip, + message: format!("Failed to query context.db: {e}"), + severity: Severity::Info, + })?; + let params: Vec<&dyn rusqlite::types::ToSql> = passing_grades + .iter() + .map(|g| g as &dyn rusqlite::types::ToSql) + .collect(); + stmt.query_map(params.as_slice(), |row| { + Ok(TdgViolation { + file_path: row.get(0)?, + function_name: row.get(1)?, + tdg_grade: row.get(2)?, + complexity: row.get::<_, i64>(3)? as u32, + start_line: row.get::<_, i64>(4)? as usize, + }) + }) + .map(|iter| iter.filter_map(|r| r.ok()).collect()) + .map_err(|e| ComplianceCheck { + name: "CB-200: TDG Grade Gate".into(), + status: CheckStatus::Skip, + message: format!("Query failed: {e}"), + severity: Severity::Info, + }) +} + +/// Check if a violation should be excluded (test files or glob patterns) +fn is_tdg_violation_excluded(v: &TdgViolation, exclude_patterns: &[glob::Pattern]) -> bool { + if v.file_path.contains("/tests/") + || v.file_path.contains("/test/") + || v.file_path.ends_with("_test.rs") + || v.file_path.ends_with("_tests.rs") + { + return true; + } + let opts = glob::MatchOptions { + case_sensitive: true, + require_literal_separator: false, + require_literal_leading_dot: false, + }; + exclude_patterns + .iter() + .any(|pat| pat.matches_with(&v.file_path, opts)) +} + +/// Which index CB-200 measured, and where it came from. +/// +/// Two places, tried in this order, and the order is the whole of #1008: +/// +/// 1. `/.pmat/context.db` — the project has an index of its own, +/// built by `pmat query`. Read it, exactly as before. A developer's index +/// and CB-200's verdict must not be two different readings of one tree. +/// 2. pmat's own index for this project, under the user's cache directory +/// ([`comply_index_path`](crate::utils::pmat_cache_dir::comply_index_path)), +/// built here if it is missing or stale. +/// +/// (2) is the fix. Before it, an audit refused to build an index at all — +/// correctly, because building it in the project writes `.pmat/context.db` and +/// `.pmat/context.idx` into a tree an auditor has no business writing to (#939) +/// — and the consequence was a gate that could only fail on a machine that +/// happened to have an index lying around. A fresh CI checkout never does, so +/// on the one machine whose verdict decides a merge, CB-200 measured nothing +/// and `is_compliant` (which tallies `Fail` only) read that silence as consent. +/// Same tree, same commit, index the only variable: 2 failing checks against 3. +/// +/// The audited tree is still never written to. It is the CACHE that gets the +/// index, keyed by project path, and `git status --porcelain` in the audited +/// repository is empty afterwards — which is the property the refusal was +/// protecting and the only one worth keeping. +enum TdgIndex { + /// `/.pmat/context.db`, built by whoever ran `pmat query`. + InProject(PathBuf), + /// pmat's own index for this project, outside it. + OutOfTree(PathBuf), +} + +impl TdgIndex { + fn db_path(&self) -> &Path { + match self { + Self::InProject(path) | Self::OutOfTree(path) => path, + } + } +} + +/// Say where a measurement came from when it did NOT come from the project. +/// +/// Appended to the verdict rather than folded into it: the in-project sentence +/// stays byte-identical, so this cannot move the output of any repository that +/// already has an index — and a reader who is surprised by a CB-200 result on a +/// machine with no `.pmat/` is told, in the verdict itself, which file was read. +fn note_index_provenance(check: ComplianceCheck, index: &TdgIndex) -> ComplianceCheck { + let TdgIndex::OutOfTree(db) = index else { + return check; + }; + let message = format!( + "{} [measured against {}, the index pmat keeps for this project outside it \u{2014} \ + the audited tree has no .pmat/context.db and was not written to (#1008)]", + check.message, + db.display() + ); + ComplianceCheck { message, ..check } +} + +/// Pick the index to read, building pmat's own copy when the project has none. +/// +/// The project's own index wins whenever it exists, fresh or stale — a stale +/// one is reported as stale rather than replaced, because that is a decision +/// about the project's file and #1045 already settled it. Only the copy pmat +/// owns is rebuilt here, which is why this can rebuild at all. +fn resolve_tdg_index(project_path: &Path, cache_index: &Path) -> Result { + let in_project = project_path.join(".pmat").join("context.db"); + if in_project.exists() { + return Ok(TdgIndex::InProject(in_project)); + } + let cached_db = cache_index.with_extension("db"); + if cached_db.exists() && !is_index_stale(project_path, &cached_db) { + return Ok(TdgIndex::OutOfTree(cached_db)); + } + match build_out_of_tree_index(project_path, cache_index) { + Ok(()) => Ok(TdgIndex::OutOfTree(cached_db)), + // A failed rebuild does not throw away a measurement already in hand: a + // stale index describes an OLDER tree, `demote_pass_when_stale` refuses + // to let that read as a pass, and the note names it. Reporting nothing + // would be strictly less information. + Err(reason) if cached_db.exists() => { + crate::status_eprintln!("CB-200: keeping the previous index ({reason})"); + Ok(TdgIndex::OutOfTree(cached_db)) + } + Err(reason) => Err(reason), + } +} + +/// Build the agent-context index for `project_path` at `index_path`, which is +/// outside `project_path`. +/// +/// `AgentContextIndex::build` only walks and reads; `save` writes +/// `/manifest.json` and `.db`, and `index_path` is in +/// the user's cache. Nothing here touches the audited tree. +/// +/// No staging layer is added on top of that, deliberately: `save_to_sqlite` +/// already builds into a process-unique scratch file and `rename`s it into +/// place, so two audits of one project racing here leave a whole index or the +/// previous one, never a half-populated `functions` table — which would +/// under-report violations and read as a pass. +/// +/// An index with no definitions in it is an ERROR, not an empty pass. A +/// directory holding no code parses to zero functions, zero functions violate +/// no floor, and "nothing below grade A" is a sentence that would then be +/// printed over a project pmat never read a line of. Absence rendered as +/// success is this codebase's signature defect; the caller turns this Err into +/// the same "not measured" verdict an unbuildable index gets. +fn build_out_of_tree_index(project_path: &Path, index_path: &Path) -> Result<(), String> { + crate::status_eprintln!( + "CB-200: no index in {} \u{2014} building pmat's own at {} (the audited tree is not written to)", + project_path.display(), + index_path.display() + ); + let index = crate::services::agent_context::AgentContextIndex::build(project_path) + .map_err(|e| format!("no index could be built for this project ({e})"))?; + if index.all_functions().is_empty() { + return Err( + "an index built from this project holds no definitions, so there is nothing to grade" + .to_string(), + ); + } + crate::utils::pmat_cache_dir::ensure_parent_dir(index_path) + .map_err(|e| format!("pmat's cache directory is not writable ({e})"))?; + // Reclaim entries for projects that no longer exist before adding another + // 79 MB one. Here rather than on the read path: this is the rare branch, + // and a cache that is only ever added to is a disk leak with a nice name. + crate::utils::pmat_cache_dir::sweep_idle_state( + "index", + crate::utils::pmat_cache_dir::STATE_MAX_IDLE, + ); + index + .save(index_path) + .map_err(|e| format!("the index could not be saved to pmat's cache ({e})"))?; + let db_path = index_path.with_extension("db"); + if !db_path.exists() { + return Err(format!( + "the index was built but {} was not written", + db_path.display() + )); + } + Ok(()) +} + +/// A threshold that cannot be parsed must not be read as a threshold nothing +/// violates. Decided from the CONFIG alone, so it is answered before any index +/// is looked for: "no index" must not launder a broken floor into a skip. +fn unparseable_floor_verdict(min_grade: &str) -> ComplianceCheck { + tdg_check( + CheckStatus::Fail, + Severity::Error, + format!( + "minimum grade {min_grade:?} is not a grade this codebase produces (known: {}). \ + A threshold that cannot be parsed must not be read as a threshold nothing violates.", + GRADE_VARIANTS.join(", ") + ), + ) +} + +/// The floor admits every grade the codebase produces, so this verdict is +/// derived from the CONFIG and not from the index: no row, fresh or stale, can +/// violate it. It therefore carries no staleness note and is not demoted — the +/// invariant being kept is that a `Pass` never rests on a stale reading, and +/// this one rests on no reading at all. It is also answered before the index is +/// resolved, so a floor of "F" never pays for an index build it cannot use. +fn floor_admits_everything_verdict(min_grade: &str) -> ComplianceCheck { + tdg_check( + CheckStatus::Pass, + Severity::Info, + format!("Minimum grade {min_grade} \u{2014} no grades below threshold"), + ) +} + +/// Every glob a violation may be excluded by: `.pmat.yaml`'s, then +/// `.pmat-gates.toml`'s. A pattern that does not compile is dropped, which +/// keeps a typo from excluding everything. +fn tdg_exclude_patterns( + comply_config: &ComplyConfig, + overrides: &TdgGateOverrides, +) -> Vec { + comply_config + .thresholds + .tdg_exclude_paths + .iter() + .map(String::as_str) + .chain(overrides.exclude.iter().map(String::as_str)) + .filter_map(|p| glob::Pattern::new(p).ok()) + .collect() +} + +#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] +pub(crate) fn check_tdg_grade_gate( + project_path: &Path, + comply_config: &ComplyConfig, +) -> ComplianceCheck { + check_tdg_grade_gate_with_index( + project_path, + comply_config, + &crate::utils::pmat_cache_dir::comply_index_path(project_path), + ) +} + +/// CB-200 with the out-of-tree index location passed in. +/// +/// A parameter and not an environment variable for the tests' sake: a test that +/// has to `set_var` to stay out of the developer's real cache is a test that +/// cannot run in parallel with any other, and this file has forty that do. +/// `$PMAT_CACHE_DIR` still moves the default — see +/// [`user_cache_root`](crate::utils::pmat_cache_dir::user_cache_root) — it is +/// just not how a test gets a private one. +fn check_tdg_grade_gate_with_index( + project_path: &Path, + comply_config: &ComplyConfig, + cache_index: &Path, +) -> ComplianceCheck { + let overrides = load_tdg_gate_overrides(project_path); + let min_grade = overrides + .min_grade + .as_deref() + .unwrap_or(&comply_config.thresholds.min_tdg_grade); + let Some(passing_grades) = passing_spellings(min_grade) else { + return unparseable_floor_verdict(min_grade); + }; + if passing_grades.len() == GRADE_VARIANTS.len() { + return floor_admits_everything_verdict(min_grade); + } + let index = match resolve_tdg_index(project_path, cache_index) { + Ok(index) => index, + Err(reason) => return absent_index_verdict(&overrides.baseline, &reason), + }; + // A stale index still measures something REAL — an OLDER tree — and it is a + // rebuild that would measure nothing. For the project's own index the + // staleness is reported instead of silently repaired (#939, #1045); the + // copy pmat owns was rebuilt above unless the rebuild failed, so reaching + // here with a stale one means the note is carrying real news. + let stale = is_index_stale(project_path, index.db_path()); + let violations = match query_tdg_violations(index.db_path(), &passing_grades) { + Ok(v) => v, + Err(check) => return check, + }; + let exclude_patterns = tdg_exclude_patterns(comply_config, &overrides); + let filtered: Vec<&TdgViolation> = violations + .iter() + .filter(|v| !is_tdg_violation_excluded(v, &exclude_patterns)) + .collect(); + let verdict = tdg_grade_verdict( + &filtered, + violations.len(), + min_grade, + &overrides.baseline, + if stale { STALE_INDEX_NOTE } else { "" }, + ); + note_index_provenance(demote_pass_when_stale(verdict, stale), &index) +} + +/// What a stale index costs the reader, and the command that actually fixes it. +/// +/// The advice used to be `pmat query "x"`, which does not refresh an index that +/// already exists (#1045). `load_or_build_index` +/// (`cli/handlers/query_handler/indexing.rs:251`) BUILDS only when both +/// `.pmat/context.idx` and `.pmat/context.db` are missing; otherwise it loads +/// and updates in memory, and `maybe_save_incremental` writes that back only +/// when more than 50 files or 5% of the index changed (#212). Edit a handful of +/// files and the db is never rewritten — so a reader following the advice saw +/// the identical stale verdict, with the identical advice, indefinitely. +/// `--rebuild-index` forces the rebuild and the save. +/// +/// This is not academic: `.pmat-gates.toml`'s CB-200 baseline was first +/// committed 216 too high because it was derived against an index that was +/// stale by 265 definitions, and nothing in the loop said so loudly enough. +const STALE_INDEX_NOTE: &str = " (index is stale: source files are newer than .pmat/context.db, \ + so this count describes an OLDER tree - run `pmat query \"x\" --rebuild-index` to refresh; \ + a plain `pmat query` will NOT rewrite an index that already exists unless more than 50 \ + files or 5% of it changed)"; + +/// A stale index may report debt, but it may never report a clean bill. +/// +/// CB-200 reads `.pmat/context.db`. When sources are newer than the db, the +/// count is a measurement of an older tree: the definitions it names may +/// already be fixed, and — the direction that matters — the ones added since +/// are not in it at all. Passing on that is this project's signature defect, +/// absence rendered as success, and it is how the ratchet baseline was first +/// banked 216 units too high. +/// +/// `Warn`, not `Fail`, and the choice is deliberate on both sides: +/// +/// * not `Pass`, because `retain_blocking_checks` (check.rs:270) switches on +/// `CheckStatus` alone and drops every `Pass`, so under +/// `comply check --failures-only` — the exact invocation `quality-gate.yml` +/// runs — the sentence saying the measurement is stale would be discarded. +/// `Warn` survives into `report.summary.warn`, which is tallied before the +/// list is narrowed. +/// * not `Fail`, because a stale index is not evidence of a regression, and a +/// gate that goes red the moment anyone edits a file after indexing would be +/// turned off within a day. `--strict` still escalates warnings. +/// +/// A stale `Fail` or `Warn` is left exactly as it is: staleness never makes a +/// verdict more lenient, only less conclusive. +fn demote_pass_when_stale(check: ComplianceCheck, stale: bool) -> ComplianceCheck { + if !stale || check.status != CheckStatus::Pass { + return check; + } + ComplianceCheck { + status: CheckStatus::Warn, + severity: Severity::Warning, + message: format!( + "NOT A VERDICT ON THE CURRENT TREE - the index is stale, so this is not a pass: {}", + check.message + ), + ..check + } +} + +/// CB-200 measured nothing at all: neither the project's index nor pmat's own +/// could be read, and `reason` says which failed and how. +/// +/// `Skip` for a project that never opted into the ratchet. It has recorded no +/// baseline to hold, and pmat must not fail every fresh clone of every repo +/// that runs `comply check` — around 40 of 155 checks only run where some +/// state exists, and a blanket "unmeasured is a failure" rule turns a fresh +/// clone into a wall of red and makes failure counts incomparable between +/// machines. That is a different defect, not a fix for this one. +/// +/// `Fail` for a project that RECORDED a `[tdg] baseline`. `Skip` is not counted +/// by `ComplianceReport::is_compliant`, which tallies `Fail` only, so a +/// declared ratchet that could not be measured used to report success. A +/// ratchet that did not run has not held; it has not been asked. "We could not +/// measure it" must never read as "it did not regress" — the rule +/// `.pmat-ratchet.toml` already applies to its own baselines, where an +/// UNMEASURABLE metric fails rather than passes. +/// +/// Since #1008 this is a genuinely exceptional path rather than the normal one: +/// an absent index is now built out-of-tree (see [`TdgIndex`]), so getting here +/// means the project holds no code to grade, or pmat has nowhere writable to +/// keep an index. +fn absent_index_verdict(baseline: &TdgBaseline, reason: &str) -> ComplianceCheck { + const HOW: &str = "CB-200 builds its own index OUTSIDE the audited project \ + (under $PMAT_CACHE_DIR, else the platform cache directory) so that a \ + fresh checkout can be measured without being written to (#1008) \ + \u{2014} this run could not. Point $PMAT_CACHE_DIR at a writable \ + directory, or build an index in the project with `pmat query \"x\" \ + --rebuild-index`, then re-run."; + match baseline { + TdgBaseline::Absent => tdg_check( + CheckStatus::Skip, + Severity::Info, + format!( + "Not measured: {reason}, and this project records no \ + `[tdg] baseline` for CB-200 to hold. {HOW}" + ), + ), + TdgBaseline::Held(b) => tdg_check( + CheckStatus::Fail, + Severity::Error, + format!( + "Not measured: {reason}, so the recorded `[tdg] baseline` of {b} \ + was never checked. A ratchet that did not run has not held - an unmeasured \ + gate must not report success. {HOW}" + ), + ), + TdgBaseline::Unreadable(raw) => unreadable_baseline_verdict(raw), + } +} + +/// The most anyone reads before scrolling. Both listings stop here. +const TDG_MAX_LISTED: usize = 10; + +/// Worst first, and deterministically so. +/// +/// The listing used to be `filtered.iter().take(10)` over an unordered SQLite +/// scan with no `ORDER BY`. Two runs of the same gate over the same database +/// printing a different ten is indistinguishable, to the reader, from the tree +/// having changed — and on a flat distribution of 1,905 across 1,052 files +/// there is nothing else in the message to tell them apart. +/// +/// `Grade`'s derived `Ord` runs best-to-worst (`APlus` first), so the worst +/// grade is the LARGEST; a spelling the scale does not know sorts above even +/// `F`, because a violation nobody can rank is the one most worth looking at. +fn rank_tdg_violations<'a>(filtered: &[&'a TdgViolation]) -> Vec<&'a TdgViolation> { + fn severity_rank(v: &TdgViolation) -> usize { + Grade::from_variant_name(&v.tdg_grade).map_or(usize::MAX, |g| g as usize) + } + let mut ranked: Vec<&TdgViolation> = filtered.to_vec(); + ranked.sort_by(|a, b| { + severity_rank(b) + .cmp(&severity_rank(a)) + .then_with(|| b.complexity.cmp(&a.complexity)) + .then_with(|| a.file_path.cmp(&b.file_path)) + .then_with(|| a.start_line.cmp(&b.start_line)) + }); + ranked +} + +/// Up to `limit` offenders, worst first, with a truncation line that names how +/// many were not shown. `limit` of 0 still lists one: a Fail that names nothing +/// is a number the reader cannot act on. +fn tdg_offender_listing(filtered: &[&TdgViolation], limit: usize) -> String { + let limit = limit.clamp(1, TDG_MAX_LISTED); + let ranked = rank_tdg_violations(filtered); + let mut details: Vec = ranked + .iter() + .take(limit) + .map(|v| { + format!( + " {}:{} {} [{}] (complexity: {})", + v.file_path, v.start_line, v.function_name, v.tdg_grade, v.complexity + ) + }) + .collect(); + if ranked.len() > limit { + details.push(format!(" ... and {} more", ranked.len() - limit)); + } + details.join("\n") +} + +/// How many distinct files the surviving violations span — the shape of the +/// debt, not just its size. 1,905 in one file is a refactor; 1,905 across 1,052 +/// files is a policy, and the reader cannot tell which from a count alone. +fn tdg_violation_file_count(filtered: &[&TdgViolation]) -> usize { + filtered + .iter() + .map(|v| v.file_path.as_str()) + .collect::>() + .len() +} + +fn tdg_check(status: CheckStatus, severity: Severity, message: String) -> ComplianceCheck { + ComplianceCheck { + name: "CB-200: TDG Grade Gate".into(), + status, + message, + severity, + } +} + +/// CB-200's verdict over a completed measurement. +/// +/// Four outcomes, and which one you get depends only on the baseline the +/// project recorded: +/// +/// ```text +/// baseline unreadable -> Fail (a bound nobody can read is not a bound) +/// no baseline -> Fail on any violation (unchanged) +/// count <= baseline -> Pass, carrying count AND baseline +/// count > baseline -> Fail, naming the excess and the offenders +/// ``` +fn tdg_grade_verdict( + filtered: &[&TdgViolation], + queried_rows: usize, + min_grade: &str, + baseline: &TdgBaseline, + staleness: &str, +) -> ComplianceCheck { + match baseline { + TdgBaseline::Unreadable(raw) => unreadable_baseline_verdict(raw), + TdgBaseline::Absent => zero_tolerance_verdict(filtered, queried_rows, min_grade, staleness), + TdgBaseline::Held(b) if filtered.len() > *b => { + over_baseline_verdict(filtered, *b, min_grade, staleness) + } + TdgBaseline::Held(b) => within_baseline_verdict(filtered, *b, min_grade, staleness), + } +} + +fn unreadable_baseline_verdict(raw: &str) -> ComplianceCheck { + tdg_check( + CheckStatus::Fail, + Severity::Error, + format!( + "`[tdg] baseline` in .pmat-gates.toml is {raw}, which is not a count. A ratchet \ + baseline that cannot be read must not be read as a baseline nothing exceeds \ + \u{2014} write a non-negative integer, or delete the key to restore zero tolerance." + ), + ) +} + +/// A project that records no baseline gets exactly what it got before: any +/// surviving violation fails. The message is unchanged, byte for byte, so +/// adding a ratchet here cannot move another repository's output. +fn zero_tolerance_verdict( + filtered: &[&TdgViolation], + queried_rows: usize, + min_grade: &str, + staleness: &str, +) -> ComplianceCheck { + let count = filtered.len(); + if count == 0 { + return tdg_check( + CheckStatus::Pass, + Severity::Info, + format!( + "All non-test functions meet minimum grade {min_grade}{}{staleness}", + if queried_rows == 0 { + String::new() + } else { + format!(" ({queried_rows} test/excluded functions skipped)") + } + ), + ); + } + tdg_check( + CheckStatus::Fail, + Severity::Error, + format!( + "{count} function(s) below minimum grade {min_grade}{staleness}\n{}", + tdg_offender_listing(filtered, TDG_MAX_LISTED) + ), + ) +} + +/// At or under the recorded baseline: the gate passes, and says why it is not +/// clean while doing so. +/// +/// `Warn`, not `Pass`, and the reason is mechanical rather than aesthetic. +/// +/// `Pass` was the first choice — held-flat debt must not block a release, which +/// is the whole point of a ratchet — with `Severity::Warning` carrying the "not +/// clean" signal. That does not work: `retain_blocking_checks` (check.rs:270) +/// switches on `CheckStatus` ALONE and drops `Pass` unconditionally, ignoring +/// `Severity` entirely. `quality-gate.yml` runs +/// `pmat comply check --failures-only`, so the one line saying "1,904 +/// definitions are below the floor" was discarded by the exact invocation CI +/// uses. A gate that hides its own debt from the only place anyone reads it is +/// how 1,904 accumulated unseen in the first place. +/// +/// `Warn` does not block either — `exit_policy` (check.rs:241) only turns +/// warnings into a non-zero code under `--strict` — but it IS counted in +/// `report.summary.warn`, and the summary is deliberately tallied before the +/// list is narrowed, so the count survives `--failures-only`. The debt is +/// therefore always reachable, which was the requirement. +/// +/// A clean tree at baseline 0 still reports `Pass`/`Info`: nothing is being +/// held, so there is nothing to warn about. +fn within_baseline_verdict( + filtered: &[&TdgViolation], + baseline: usize, + min_grade: &str, + staleness: &str, +) -> ComplianceCheck { + let count = filtered.len(); + let slack = baseline - count; + let slack_note = if slack == 0 { + String::new() + } else { + format!( + " The tree is {slack} under the recorded baseline: lower `[tdg] baseline` to \ + {count} in .pmat-gates.toml to bank it \u{2014} a baseline the tree has already \ + beaten is headroom for new debt." + ) + }; + if count == 0 { + return tdg_check( + CheckStatus::Pass, + if slack == 0 { + Severity::Info + } else { + Severity::Warning + }, + format!( + "0 definitions below minimum grade {min_grade}, against a recorded baseline of \ + {baseline}.{slack_note}{staleness}" + ), + ); + } + tdg_check( + CheckStatus::Warn, + Severity::Warning, + format!( + "{count} definition(s) below minimum grade {min_grade} across {} file(s), at the \ + recorded baseline of {baseline} \u{2014} this is debt held flat, not a clean tree. \ + Any new definition below {min_grade} fails this gate.{slack_note}{staleness}", + tdg_violation_file_count(filtered) + ), + ) +} + +/// Over the recorded baseline: closed, naming how many and by how much. +/// +/// The caveat about the listing is not hedging. A baseline is a COUNT, so it +/// cannot identify WHICH definitions are new — only that there are more than +/// there were. Presenting the worst-graded survivors as "the ones you just +/// added" would be a claim the measurement does not support, and the reader +/// would chase the wrong functions. +fn over_baseline_verdict( + filtered: &[&TdgViolation], + baseline: usize, + min_grade: &str, + staleness: &str, +) -> ComplianceCheck { + let count = filtered.len(); + let over = count - baseline; + tdg_check( + CheckStatus::Fail, + Severity::Error, + format!( + "{count} definition(s) below minimum grade {min_grade} \u{2014} {over} OVER the \ + recorded baseline of {baseline}. A ratchet holds only if new debt is refused: fix \ + {over}, or revert what added them. Raising `[tdg] baseline` is not the fix \u{2014} \ + a baseline may only go down.{staleness}\n (the baseline is a count, not a \ + roster, so these are the worst-graded survivors, not necessarily the ones just \ + added)\n{}", + tdg_offender_listing(filtered, over) + ), + ) +} + +/// Evaluate a single custom score definition and return the compliance check +fn evaluate_custom_score( + project_path: &Path, + score_def: &crate::models::comply_config::CustomScoreDefinition, +) -> ComplianceCheck { + let check_name = format!("CB-1100: Custom Score [{}]", score_def.id); + let output = match std::process::Command::new("sh") + .args(["-c", &score_def.command]) + .current_dir(project_path) + .output() + { + Ok(o) => o, + Err(e) => { + return ComplianceCheck { + name: check_name, + status: CheckStatus::Skip, + message: format!("Failed to run command: {e}"), + severity: Severity::Info, + } + } + }; + if !output.status.success() { + return ComplianceCheck { + name: check_name, + status: CheckStatus::Fail, + message: format!( + "{}: command failed (exit {})", + score_def.name, + output.status.code().unwrap_or(-1) + ), + severity: Severity::from(score_def.severity), + }; + } + let stdout = String::from_utf8_lossy(&output.stdout); + let actual_score = match extract_score_from_output(&stdout) { + Some(s) => s, + None => { + return ComplianceCheck { + name: check_name, + status: CheckStatus::Skip, + message: format!( + "{}: could not parse score from command output", + score_def.name + ), + severity: Severity::Info, + } + } + }; + match score_def.min_score { + Some(min) if actual_score < min => ComplianceCheck { + name: check_name, + status: CheckStatus::Fail, + message: format!( + "{}: score {:.1} below minimum {:.1}", + score_def.name, actual_score, min + ), + severity: Severity::from(score_def.severity), + }, + Some(min) => ComplianceCheck { + name: check_name, + status: CheckStatus::Pass, + message: format!( + "{}: score {:.1} (min: {:.1})", + score_def.name, actual_score, min + ), + severity: Severity::Info, + }, + None => ComplianceCheck { + name: check_name, + status: CheckStatus::Pass, + message: format!("{}: score {:.1}", score_def.name, actual_score), + severity: Severity::Info, + }, + } +} + +/// CB-1100: Custom Project Scores +#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] +pub(crate) fn check_custom_scores(project_path: &Path) -> Vec { + let config = match crate::models::comply_config::PmatYamlConfig::load(project_path) { + Ok(c) => c, + Err(_) => return vec![], + }; + if config.scoring.custom_scores.is_empty() { + return vec![]; + } + config + .scoring + .custom_scores + .iter() + .map(|s| evaluate_custom_score(project_path, s)) + .collect() +} + +fn extract_score_from_output(output: &str) -> Option { + for line in output.lines() { + let line = line.trim(); + if let Ok(json) = serde_json::from_str::(line) { + if let Some(score) = json.get("score").and_then(|s| s.as_f64()) { + return Some(score); + } + } + } + for line in output.lines() { + let line = line.trim(); + if let Some(rest) = line.strip_prefix("SCORE:") { + if let Ok(score) = rest.trim().parse::() { + return Some(score); + } + } + } + None +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests_tdg_grade { + use super::*; + use crate::models::comply_config::ComplyConfig; + use std::path::PathBuf; + + /// The floor admits exactly the grades at least as good as it. + /// + /// Replaces `test_grade_ordinal` and `test_grades_below`, which pinned the + /// defect rather than the rule: they asserted `grade_ordinal("X") == 5` + /// (the catch-all that ranked every unknown, and every MODIFIED, grade + /// worse than F) and `grades_below("A") == ["B","C","D","F"]` (the + /// five-letter blindness). Both were true of the old code and both were + /// the bug. + #[test] + fn passing_set_is_the_up_set_of_the_floor() { + assert_eq!( + passing_spellings("A+").expect("A+ is a canonical grade spelling"), + vec!["A+"] + ); + assert_eq!( + passing_spellings("A").expect("A is a canonical grade spelling"), + vec!["A+", "A"] + ); + // The spelling that used to produce an EMPTY failing set and a Pass. + assert_eq!( + passing_spellings("A-").expect("A- is a canonical grade spelling"), + vec!["A+", "A", "A-"] + ); + assert_eq!( + passing_spellings("B").expect("B is a canonical grade spelling"), + vec!["A+", "A", "A-", "B+", "B"] + ); + // Only F admits everything, and that is the one honest vacuous floor. + assert_eq!( + passing_spellings("F") + .expect("F is a canonical grade spelling") + .len(), + GRADE_VARIANTS.len() + ); + } + + /// Passing and failing partition the scale at every floor — the property + /// the five-letter table did not have, proved for all eleven floors in + /// `contracts/lean/Theorems/Tdg/Grade.lean::Grade_Partition`. + #[test] + fn passing_and_failing_partition_the_scale() { + for floor in GRADE_VARIANTS { + let passing = passing_spellings(floor).expect("canonical spelling parses"); + let failing: Vec<_> = GRADE_VARIANTS + .iter() + .filter(|g| !passing.contains(g)) + .collect(); + assert_eq!( + passing.len() + failing.len(), + GRADE_VARIANTS.len(), + "floor {floor} does not partition the scale" + ); + } + } + + /// An unreadable threshold yields `None`, never an empty set. An empty set + /// is what the caller reads as "nothing violates". + #[test] + fn an_unparseable_floor_is_none_not_empty() { + for bad in ["X", "", " ", "A--", "Q", "E"] { + assert!( + passing_spellings(bad).is_none(), + "{bad:?} must not parse as a grade floor" + ); + } + // Counter-test: every canonical spelling still parses, so the guard did + // not become a spelling police. + for good in GRADE_VARIANTS { + assert!(passing_spellings(good).is_some(), "{good} must parse"); + } + } + + #[test] + fn test_missing_db_returns_skip() { + let tmp = PathBuf::from("/tmp/pmat-test-tdg-missing-db"); + let config = ComplyConfig::default(); + let result = check_tdg_grade_gate(&tmp, &config); + assert_eq!(result.status, CheckStatus::Skip); + assert!( + result.message.contains("Not measured"), + "{}", + result.message + ); + } + + /// #939: CB-200 used to BUILD and SAVE the agent-context index whenever + /// `.pmat/context.db` was missing or stale — so asking for a compliance + /// verdict wrote `.pmat/context.db` and `.pmat/context.idx` into the + /// project being audited (and spent minutes doing it on a large repo). + /// + /// It still never writes there, and that is what this test is for. What + /// #1008 changed is where the index comes from when the project has none: + /// pmat's own copy, outside the tree. The `Skip` this used to assert WAS + /// the defect — a gate that declines to measure a fresh checkout cannot + /// run in the one place a verdict decides a merge. + #[test] + fn cb200_never_builds_an_index_inside_the_audited_project() { + let cache = tempfile::tempdir().expect("create cache dir"); + let tmp = tempfile::tempdir().expect("create tempdir"); + std::fs::create_dir_all(tmp.path().join("src")).expect("create src"); + std::fs::write(tmp.path().join("src/lib.rs"), "pub fn f() {}\n").expect("write source"); + + let result = check_tdg_grade_gate_with_index( + tmp.path(), + &ComplyConfig::default(), + &cache.path().join("context.idx"), + ); + + assert!( + !tmp.path().join(".pmat").exists(), + "comply check must not write .pmat/ into the project it audits" + ); + assert_ne!( + result.status, + CheckStatus::Skip, + "#1008: a tree pmat could read must be measured, not skipped: {}", + result.message + ); + assert!( + cache.path().join("context.db").exists(), + "the index it read must be the one outside the audited tree" + ); + } + + /// A stale index is still a real measurement — it is reported, with the + /// staleness named, rather than silently repaired by a write. + #[test] + fn cb200_reports_staleness_instead_of_rebuilding() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let pmat_dir = tmp.path().join(".pmat"); + std::fs::create_dir_all(&pmat_dir).expect("create .pmat"); + let db_path = pmat_dir.join("context.db"); + let conn = rusqlite::Connection::open(&db_path).expect("open db"); + conn.execute_batch( + "CREATE TABLE functions (id INTEGER PRIMARY KEY, file_path TEXT NOT NULL, \ + function_name TEXT NOT NULL, tdg_grade TEXT NOT NULL DEFAULT 'A', \ + complexity INTEGER NOT NULL DEFAULT 1, start_line INTEGER NOT NULL DEFAULT 0)", + ) + .expect("schema"); + drop(conn); + + // A source file newer than the db makes the index stale. + // + // The mtime is SET, not raced for. Writing the source after the db and + // trusting wall-clock ordering failed on CI with "fixture must be + // stale": both files landed in the same filesystem timestamp tick, so + // `is_index_stale` correctly saw nothing newer and the fixture never + // reached the behaviour under test. Filesystem timestamp granularity is + // coarser than the microseconds between two writes, and varies by + // filesystem — which is exactly the kind of thing that passes on the + // author's machine and fails in CI. + std::fs::create_dir_all(tmp.path().join("src")).expect("create src"); + let src = tmp.path().join("src/lib.rs"); + std::fs::write(&src, "pub fn f() {}\n").expect("write source"); + let db_mtime = std::fs::metadata(&db_path) + .and_then(|m| m.modified()) + .expect("db mtime"); + std::fs::File::options() + .write(true) + .open(&src) + .and_then(|f| f.set_modified(db_mtime + std::time::Duration::from_secs(60))) + .expect("set source mtime a clear minute past the db"); + assert!( + is_index_stale(tmp.path(), &db_path), + "fixture must be stale" + ); + + let before = std::fs::metadata(&db_path).expect("meta").len(); + let result = check_tdg_grade_gate(tmp.path(), &ComplyConfig::default()); + let after = std::fs::metadata(&db_path).expect("meta").len(); + + assert_eq!(before, after, "the audit rewrote the index it was reading"); + assert!( + !pmat_dir.join("context.idx").exists(), + "comply check must not build .pmat/context.idx" + ); + assert!( + result.message.contains("index is stale"), + "staleness must be reported, not silently repaired: {}", + result.message + ); + } + + #[test] + fn test_grade_f_no_violations() { + let tmp = PathBuf::from("/tmp/pmat-test-tdg-grade-f"); + let mut config = ComplyConfig::default(); + config.thresholds.min_tdg_grade = "F".to_string(); + let result = check_tdg_grade_gate(&tmp, &config); + assert!(result.status == CheckStatus::Skip || result.status == CheckStatus::Pass); + } + + #[test] + fn test_in_memory_db_with_violations() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let pmat_dir = tmp.path().join(".pmat"); + std::fs::create_dir_all(&pmat_dir).expect("create .pmat"); + let db_path = pmat_dir.join("context.db"); + let conn = rusqlite::Connection::open(&db_path).expect("open db"); + conn.execute_batch("CREATE TABLE IF NOT EXISTS functions (id INTEGER PRIMARY KEY, file_path TEXT NOT NULL, function_name TEXT NOT NULL, signature TEXT NOT NULL DEFAULT '', definition_type TEXT NOT NULL DEFAULT 'function', doc_comment TEXT NOT NULL DEFAULT '', source TEXT NOT NULL DEFAULT '', start_line INTEGER NOT NULL DEFAULT 0, end_line INTEGER NOT NULL DEFAULT 0, language TEXT NOT NULL DEFAULT 'Rust', checksum TEXT NOT NULL DEFAULT '', tdg_score REAL NOT NULL DEFAULT 0.0, tdg_grade TEXT NOT NULL DEFAULT 'A', complexity INTEGER NOT NULL DEFAULT 1, cognitive_complexity INTEGER NOT NULL DEFAULT 1, big_o TEXT NOT NULL DEFAULT 'O(1)', satd_count INTEGER NOT NULL DEFAULT 0, loc INTEGER NOT NULL DEFAULT 0, commit_count INTEGER NOT NULL DEFAULT 0, churn_score REAL NOT NULL DEFAULT 0.0, clone_count INTEGER NOT NULL DEFAULT 0, pattern_diversity REAL NOT NULL DEFAULT 0.0, fault_annotations TEXT NOT NULL DEFAULT '[]')").expect("create schema"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/core.rs', 'good_fn', 'A', 5, 10)", []).expect("insert A"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/legacy.rs', 'bad_fn', 'D', 42, 20)", []).expect("insert D"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/awful.rs', 'terrible_fn', 'F', 60, 30)", []).expect("insert F"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/tests/helpers.rs', 'test_helper', 'D', 35, 40)", []).expect("insert test D"); + drop(conn); + let config = ComplyConfig::default(); + let result = check_tdg_grade_gate(tmp.path(), &config); + assert_eq!(result.status, CheckStatus::Fail); + assert!(result + .message + .contains("2 function(s) below minimum grade A")); + assert!(result.message.contains("src/legacy.rs:20 bad_fn [D]")); + assert!(result.message.contains("src/awful.rs:30 terrible_fn [F]")); + assert!(!result.message.contains("test_helper")); + } + + #[test] + fn test_tdg_exclude_paths() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let pmat_dir = tmp.path().join(".pmat"); + std::fs::create_dir_all(&pmat_dir).expect("create .pmat"); + let db_path = pmat_dir.join("context.db"); + let conn = rusqlite::Connection::open(&db_path).expect("open db"); + conn.execute_batch("CREATE TABLE IF NOT EXISTS functions (id INTEGER PRIMARY KEY, file_path TEXT NOT NULL, function_name TEXT NOT NULL, signature TEXT NOT NULL DEFAULT '', definition_type TEXT NOT NULL DEFAULT 'function', doc_comment TEXT NOT NULL DEFAULT '', source TEXT NOT NULL DEFAULT '', start_line INTEGER NOT NULL DEFAULT 0, end_line INTEGER NOT NULL DEFAULT 0, language TEXT NOT NULL DEFAULT 'Rust', checksum TEXT NOT NULL DEFAULT '', tdg_score REAL NOT NULL DEFAULT 0.0, tdg_grade TEXT NOT NULL DEFAULT 'A', complexity INTEGER NOT NULL DEFAULT 1, cognitive_complexity INTEGER NOT NULL DEFAULT 1, big_o TEXT NOT NULL DEFAULT 'O(1)', satd_count INTEGER NOT NULL DEFAULT 0, loc INTEGER NOT NULL DEFAULT 0, commit_count INTEGER NOT NULL DEFAULT 0, churn_score REAL NOT NULL DEFAULT 0.0, clone_count INTEGER NOT NULL DEFAULT 0, pattern_diversity REAL NOT NULL DEFAULT 0.0, fault_annotations TEXT NOT NULL DEFAULT '[]')").expect("create schema"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('vendor/lib.rs', 'vendor_fn', 'D', 40, 10)", []).expect("insert vendor D"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/main.rs', 'main_fn', 'D', 30, 5)", []).expect("insert src D"); + drop(conn); + let mut config = ComplyConfig::default(); + config.thresholds.tdg_exclude_paths = vec!["vendor/*".to_string()]; + let result = check_tdg_grade_gate(tmp.path(), &config); + assert_eq!(result.status, CheckStatus::Fail); + assert!(result.message.contains("1 function(s)")); + assert!(result.message.contains("main_fn")); + assert!(!result.message.contains("vendor_fn")); + } + + #[test] + fn test_all_pass_with_good_grades() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let pmat_dir = tmp.path().join(".pmat"); + std::fs::create_dir_all(&pmat_dir).expect("create .pmat"); + let db_path = pmat_dir.join("context.db"); + let conn = rusqlite::Connection::open(&db_path).expect("open db"); + conn.execute_batch("CREATE TABLE IF NOT EXISTS functions (id INTEGER PRIMARY KEY, file_path TEXT NOT NULL, function_name TEXT NOT NULL, signature TEXT NOT NULL DEFAULT '', definition_type TEXT NOT NULL DEFAULT 'function', doc_comment TEXT NOT NULL DEFAULT '', source TEXT NOT NULL DEFAULT '', start_line INTEGER NOT NULL DEFAULT 0, end_line INTEGER NOT NULL DEFAULT 0, language TEXT NOT NULL DEFAULT 'Rust', checksum TEXT NOT NULL DEFAULT '', tdg_score REAL NOT NULL DEFAULT 0.0, tdg_grade TEXT NOT NULL DEFAULT 'A', complexity INTEGER NOT NULL DEFAULT 1, cognitive_complexity INTEGER NOT NULL DEFAULT 1, big_o TEXT NOT NULL DEFAULT 'O(1)', satd_count INTEGER NOT NULL DEFAULT 0, loc INTEGER NOT NULL DEFAULT 0, commit_count INTEGER NOT NULL DEFAULT 0, churn_score REAL NOT NULL DEFAULT 0.0, clone_count INTEGER NOT NULL DEFAULT 0, pattern_diversity REAL NOT NULL DEFAULT 0.0, fault_annotations TEXT NOT NULL DEFAULT '[]')").expect("create schema"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/lib.rs', 'good_fn', 'A', 3, 1)", []).expect("insert A"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/util.rs', 'ok_fn', 'B', 8, 10)", []).expect("insert B"); + drop(conn); + let mut config = ComplyConfig::default(); + config.thresholds.min_tdg_grade = "B".to_string(); + let result = check_tdg_grade_gate(tmp.path(), &config); + assert_eq!(result.status, CheckStatus::Pass); + assert!(result.message.contains("meet minimum grade B")); + } + + #[test] + fn test_pmat_gates_toml_min_grade_override() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let pmat_dir = tmp.path().join(".pmat"); + std::fs::create_dir_all(&pmat_dir).expect("create .pmat"); + let db_path = pmat_dir.join("context.db"); + let conn = rusqlite::Connection::open(&db_path).expect("open db"); + conn.execute_batch("CREATE TABLE IF NOT EXISTS functions (id INTEGER PRIMARY KEY, file_path TEXT NOT NULL, function_name TEXT NOT NULL, signature TEXT NOT NULL DEFAULT '', definition_type TEXT NOT NULL DEFAULT 'function', doc_comment TEXT NOT NULL DEFAULT '', source TEXT NOT NULL DEFAULT '', start_line INTEGER NOT NULL DEFAULT 0, end_line INTEGER NOT NULL DEFAULT 0, language TEXT NOT NULL DEFAULT 'Rust', checksum TEXT NOT NULL DEFAULT '', tdg_score REAL NOT NULL DEFAULT 0.0, tdg_grade TEXT NOT NULL DEFAULT 'A', complexity INTEGER NOT NULL DEFAULT 1, cognitive_complexity INTEGER NOT NULL DEFAULT 1, big_o TEXT NOT NULL DEFAULT 'O(1)', satd_count INTEGER NOT NULL DEFAULT 0, loc INTEGER NOT NULL DEFAULT 0, commit_count INTEGER NOT NULL DEFAULT 0, churn_score REAL NOT NULL DEFAULT 0.0, clone_count INTEGER NOT NULL DEFAULT 0, pattern_diversity REAL NOT NULL DEFAULT 0.0, fault_annotations TEXT NOT NULL DEFAULT '[]')").expect("create schema"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/lib.rs', 'good_fn', 'A', 3, 1)", []).expect("insert A"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/util.rs', 'ok_fn', 'B', 8, 10)", []).expect("insert B"); + drop(conn); + std::fs::write( + tmp.path().join(".pmat-gates.toml"), + "[tdg]\nmin_grade = \"B\"\n", + ) + .expect("write gates toml"); + let config = ComplyConfig::default(); + let result = check_tdg_grade_gate(tmp.path(), &config); + assert_eq!(result.status, CheckStatus::Pass); + } + + #[test] + fn test_pmat_gates_toml_exclude_override() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let pmat_dir = tmp.path().join(".pmat"); + std::fs::create_dir_all(&pmat_dir).expect("create .pmat"); + let db_path = pmat_dir.join("context.db"); + let conn = rusqlite::Connection::open(&db_path).expect("open db"); + conn.execute_batch("CREATE TABLE IF NOT EXISTS functions (id INTEGER PRIMARY KEY, file_path TEXT NOT NULL, function_name TEXT NOT NULL, signature TEXT NOT NULL DEFAULT '', definition_type TEXT NOT NULL DEFAULT 'function', doc_comment TEXT NOT NULL DEFAULT '', source TEXT NOT NULL DEFAULT '', start_line INTEGER NOT NULL DEFAULT 0, end_line INTEGER NOT NULL DEFAULT 0, language TEXT NOT NULL DEFAULT 'Rust', checksum TEXT NOT NULL DEFAULT '', tdg_score REAL NOT NULL DEFAULT 0.0, tdg_grade TEXT NOT NULL DEFAULT 'A', complexity INTEGER NOT NULL DEFAULT 1, cognitive_complexity INTEGER NOT NULL DEFAULT 1, big_o TEXT NOT NULL DEFAULT 'O(1)', satd_count INTEGER NOT NULL DEFAULT 0, loc INTEGER NOT NULL DEFAULT 0, commit_count INTEGER NOT NULL DEFAULT 0, churn_score REAL NOT NULL DEFAULT 0.0, clone_count INTEGER NOT NULL DEFAULT 0, pattern_diversity REAL NOT NULL DEFAULT 0.0, fault_annotations TEXT NOT NULL DEFAULT '[]')").expect("create schema"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/core_generated.rs', 'gen_fn', 'D', 40, 10)", []).expect("insert generated D"); + conn.execute("INSERT INTO functions (file_path, function_name, tdg_grade, complexity, start_line) VALUES ('src/real.rs', 'real_fn', 'D', 30, 5)", []).expect("insert real D"); + drop(conn); + std::fs::write( + tmp.path().join(".pmat-gates.toml"), + "[tdg]\nexclude = [\"**/*_generated.rs\"]\n", + ) + .expect("write gates toml"); + let config = ComplyConfig::default(); + let result = check_tdg_grade_gate(tmp.path(), &config); + assert_eq!(result.status, CheckStatus::Fail); + assert!(result.message.contains("1 function(s)")); + assert!(result.message.contains("real_fn")); + assert!(!result.message.contains("gen_fn")); + } + + #[test] + fn test_is_index_stale_no_db() { + let tmp = tempfile::tempdir().expect("create tempdir"); + assert!(is_index_stale( + tmp.path(), + &tmp.path().join("nonexistent.db") + )); + } + + #[test] + fn test_is_index_stale_fresh_db() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let src_dir = tmp.path().join("src"); + std::fs::create_dir_all(&src_dir).expect("create src"); + std::fs::write(src_dir.join("lib.rs"), "fn main() {}").expect("write src"); + std::thread::sleep(std::time::Duration::from_millis(50)); + let db_path = tmp.path().join("context.db"); + std::fs::write(&db_path, "").expect("write db"); + assert!(!is_index_stale(tmp.path(), &db_path)); + } + + #[test] + fn test_is_index_stale_outdated_db() { + let tmp = tempfile::tempdir().expect("create tempdir"); + let db_path = tmp.path().join("context.db"); + std::fs::write(&db_path, "").expect("write db"); + std::thread::sleep(std::time::Duration::from_millis(50)); + let src_dir = tmp.path().join("src"); + std::fs::create_dir_all(&src_dir).expect("create src"); + std::fs::write(src_dir.join("lib.rs"), "fn main() {}").expect("write src"); + assert!(is_index_stale(tmp.path(), &db_path)); + } + + #[test] + fn test_is_source_file() { + assert!(is_source_file(Path::new("foo.rs"))); + assert!(is_source_file(Path::new("foo.py"))); + assert!(is_source_file(Path::new("foo.ts"))); + assert!(!is_source_file(Path::new("foo.txt"))); + assert!(!is_source_file(Path::new("foo.toml"))); + assert!(!is_source_file(Path::new("Makefile"))); + } + + // ── CB-200 as a RATCHET ────────────────────────────────────────────── + // + // The gate was BLIND until 2026-08-20: a five-letter reader against an + // eleven-letter writer, so it saw 247 violations and could not see 1,719. + // Every historical "CB-200 passed" came from that version. Once it could + // see, it measured 1,905 below-A definitions across 1,052 files, max 12 per + // file — a flat distribution with no hotspot and no bounded refactor. + // + // The two ways to make that green are both threshold-lowering in disguise: + // drop `min_tdg_grade`, or add an exclude glob. One of them (187f506885) is + // how a past "pass" was manufactured. Neither is done here. The gate holds + // the count flat instead, refuses any increase closed, and prints the + // absolute number on every outcome so that passing can never be mistaken + // for clean. + + /// A project with `.pmat/context.db` holding exactly these rows. + pub(super) fn tdg_fixture(rows: &[(&str, &str, &str, u32, usize)]) -> tempfile::TempDir { + let tmp = tempfile::tempdir().expect("create tempdir"); + let pmat_dir = tmp.path().join(".pmat"); + std::fs::create_dir_all(&pmat_dir).expect("create .pmat"); + let conn = rusqlite::Connection::open(pmat_dir.join("context.db")).expect("open db"); + conn.execute_batch( + "CREATE TABLE functions (id INTEGER PRIMARY KEY, file_path TEXT NOT NULL, \ + function_name TEXT NOT NULL, tdg_grade TEXT NOT NULL DEFAULT 'A', \ + complexity INTEGER NOT NULL DEFAULT 1, start_line INTEGER NOT NULL DEFAULT 0)", + ) + .expect("schema"); + for (file, name, grade, complexity, line) in rows { + conn.execute( + "INSERT INTO functions (file_path, function_name, tdg_grade, complexity, \ + start_line) VALUES (?1, ?2, ?3, ?4, ?5)", + rusqlite::params![file, name, grade, complexity, line], + ) + .expect("insert row"); + } + tmp + } + + pub(super) fn write_gates(tmp: &tempfile::TempDir, body: &str) { + std::fs::write(tmp.path().join(".pmat-gates.toml"), body).expect("write gates toml"); + } + + pub(super) fn judge(tmp: &tempfile::TempDir) -> ComplianceCheck { + check_tdg_grade_gate(tmp.path(), &ComplyConfig::default()) + } + + /// The key is OPTIONAL, and its absence is not a softer gate. + /// + /// This is the counter-test that bounds the whole change: pmat runs against + /// other repositories, and none of them has agreed to anything. A project + /// that records no baseline must get zero tolerance, byte for byte, whether + /// it has a `.pmat-gates.toml` with no `baseline` key or no file at all. + #[test] + fn without_a_baseline_key_any_violation_still_fails() { + let no_file = tdg_fixture(&[("src/a.rs", "bad", "D", 30, 5)]); + let verdict = judge(&no_file); + assert_eq!(verdict.status, CheckStatus::Fail, "{}", verdict.message); + assert!( + verdict + .message + .contains("1 function(s) below minimum grade A"), + "the no-baseline message must be unchanged: {}", + verdict.message + ); + + let other_keys = tdg_fixture(&[("src/a.rs", "bad", "D", 30, 5)]); + write_gates(&other_keys, "[tdg]\nexclude = [\"nothing/**\"]\n"); + let verdict = judge(&other_keys); + assert_eq!(verdict.status, CheckStatus::Fail, "{}", verdict.message); + assert!( + verdict + .message + .contains("1 function(s) below minimum grade A"), + "a [tdg] table without a baseline is still zero tolerance: {}", + verdict.message + ); + } + + /// At the baseline the gate PASSES — and must not read as clean. + /// + /// "1905 below A" printed at `Info` next to a genuinely empty tree is how + /// 1,905 accumulated unseen. The count, the baseline and the word "debt" + /// are all load-bearing, and so is `Severity::Warning` on a `Pass`. + /// Held debt must survive `--failures-only`, which is the ONLY invocation + /// CI runs. + /// + /// This is the counter-test for the status choice, and it exists because the + /// obvious simplification — "it passed, so return `Pass`" — is wrong in a way + /// nothing else here would catch. `retain_blocking_checks` (check.rs) matches + /// on `CheckStatus` ALONE and drops `Pass` unconditionally; `Severity` is not + /// consulted. `quality-gate.yml` runs `pmat comply check --failures-only`, so + /// a `Pass` verdict deletes the one line reporting that 1,904 definitions sit + /// below the floor, from the only report anyone reads. + /// + /// `Warn` does not block — `exit_policy` only escalates warnings under + /// `--strict` — but it is counted into `summary.warn`, and the summary is + /// tallied BEFORE the list is narrowed. That is what keeps the number + /// reachable. + /// + /// RED: change `within_baseline_verdict`'s non-empty branch back to + /// `CheckStatus::Pass` and this fails with `left: Pass, right: Warn`. + #[test] + fn held_debt_is_warn_so_failures_only_cannot_hide_it() { + let tmp = tdg_fixture(&[("src/a.rs", "one", "D", 30, 5)]); + write_gates(&tmp, "[tdg]\nbaseline = 5\n"); + let verdict = judge(&tmp); + + assert_eq!( + verdict.status, + CheckStatus::Warn, + "held debt reported as Pass is dropped by `--failures-only`; the \ + count must stay reachable. Message was: {}", + verdict.message + ); + + // ...and the counter-test: a genuinely clean tree is still a Pass, so + // "always Warn" cannot satisfy the assertion above. + let clean = tdg_fixture(&[]); + write_gates(&clean, "[tdg]\nbaseline = 5\n"); + let clean_verdict = judge(&clean); + assert_eq!( + clean_verdict.status, + CheckStatus::Pass, + "nothing is being held, so there is nothing to warn about: {}", + clean_verdict.message + ); + } + + #[test] + fn at_the_baseline_it_passes_carrying_the_count_and_the_baseline() { + let tmp = tdg_fixture(&[ + ("src/a.rs", "one", "D", 30, 5), + ("src/b.rs", "two", "C-", 20, 9), + ]); + write_gates(&tmp, "[tdg]\nbaseline = 2\n"); + let verdict = judge(&tmp); + + assert_eq!(verdict.status, CheckStatus::Warn, "{}", verdict.message); + assert_eq!( + verdict.severity, + Severity::Warning, + "held debt at Info reads as clean: {}", + verdict.message + ); + assert!( + verdict + .message + .contains("2 definition(s) below minimum grade A"), + "the absolute count must lead: {}", + verdict.message + ); + assert!( + verdict.message.contains("recorded baseline of 2"), + "the baseline must be named: {}", + verdict.message + ); + assert!( + verdict.message.contains("not a clean tree"), + "passing at baseline must not read as passing clean: {}", + verdict.message + ); + assert!( + verdict.message.contains("2 file(s)"), + "the shape of the debt is part of the report: {}", + verdict.message + ); + } + + /// One over is a failure, named as one over. + #[test] + fn one_definition_over_the_baseline_fails_and_says_by_how_much() { + let tmp = tdg_fixture(&[ + ("src/a.rs", "one", "D", 30, 5), + ("src/b.rs", "two", "C-", 20, 9), + ("src/c.rs", "three", "F", 44, 2), + ]); + write_gates(&tmp, "[tdg]\nbaseline = 2\n"); + let verdict = judge(&tmp); + + assert_eq!(verdict.status, CheckStatus::Fail, "{}", verdict.message); + assert_eq!(verdict.severity, Severity::Error); + assert!( + verdict + .message + .contains("3 definition(s) below minimum grade A"), + "the absolute count must lead even on a failure: {}", + verdict.message + ); + assert!( + verdict + .message + .contains("1 OVER the recorded baseline of 2"), + "the excess must be named: {}", + verdict.message + ); + // Exactly `over` offenders are listed, worst first, and the rest are + // counted rather than dropped. + assert!( + verdict.message.contains("src/c.rs:2 three [F]"), + "the worst survivor must be named: {}", + verdict.message + ); + assert!( + verdict.message.contains("... and 2 more"), + "the unlisted remainder must be counted: {}", + verdict.message + ); + assert!( + verdict.message.contains("may only go down"), + "the fix must not read as 'raise the baseline': {}", + verdict.message + ); + } + + /// Under the baseline is a pass that asks to be banked. + /// + /// A baseline the tree has already beaten is headroom for new debt, which + /// is the failure mode `.pmat-ratchet.toml`'s `--lower` job exists to + /// prevent. CB-200 has no such job, so it asks in the message instead. + #[test] + fn under_the_baseline_passes_and_asks_for_the_baseline_to_be_lowered() { + let tmp = tdg_fixture(&[("src/a.rs", "one", "D", 30, 5)]); + write_gates(&tmp, "[tdg]\nbaseline = 5\n"); + let verdict = judge(&tmp); + + assert_eq!(verdict.status, CheckStatus::Warn, "{}", verdict.message); + assert!( + verdict.message.contains("4 under the recorded baseline"), + "slack must be reported: {}", + verdict.message + ); + assert!( + verdict.message.contains("lower `[tdg] baseline` to 1"), + "the message must name the number to bank: {}", + verdict.message + ); + } + + /// A clean tree under a stale baseline reports the slack, not silence. + #[test] + fn a_clean_tree_still_reports_a_baseline_it_has_outgrown() { + let tmp = tdg_fixture(&[("src/a.rs", "fine", "A", 3, 1)]); + write_gates(&tmp, "[tdg]\nbaseline = 5\n"); + let verdict = judge(&tmp); + + assert_eq!(verdict.status, CheckStatus::Pass, "{}", verdict.message); + assert!( + verdict + .message + .contains("0 definitions below minimum grade A"), + "{}", + verdict.message + ); + assert!( + verdict.message.contains("lower `[tdg] baseline` to 0"), + "a baseline of 5 over an empty tree is pure headroom: {}", + verdict.message + ); + } + + /// A baseline nobody can read is not a baseline nothing exceeds. + /// + /// The same rule `passing_spellings` already enforces for `min_grade`: the + /// unreadable value is REPORTED, never quietly rounded to "no baseline", + /// because "your key does nothing" and "you chose zero tolerance" are + /// opposite claims that look identical from outside. + #[test] + fn an_unreadable_baseline_fails_closed_and_names_itself() { + for bad in ["\"many\"", "-1", "1.5", "true", "[1905]"] { + let tmp = tdg_fixture(&[("src/a.rs", "one", "D", 30, 5)]); + write_gates(&tmp, &format!("[tdg]\nbaseline = {bad}\n")); + let verdict = judge(&tmp); + assert_eq!( + verdict.status, + CheckStatus::Fail, + "baseline = {bad} must fail: {}", + verdict.message + ); + assert!( + verdict.message.contains("is not a count"), + "baseline = {bad} must name itself: {}", + verdict.message + ); + } + // Counter-test: the guard did not become a "no baseline may be small" + // rule. Zero is a real baseline — it is zero tolerance, said out loud. + let clean = tdg_fixture(&[("src/a.rs", "fine", "A", 3, 1)]); + write_gates(&clean, "[tdg]\nbaseline = 0\n"); + assert_eq!(judge(&clean).status, CheckStatus::Pass); + let dirty = tdg_fixture(&[("src/a.rs", "one", "D", 30, 5)]); + write_gates(&dirty, "[tdg]\nbaseline = 0\n"); + let verdict = judge(&dirty); + assert_eq!(verdict.status, CheckStatus::Fail, "{}", verdict.message); + assert!( + verdict + .message + .contains("1 OVER the recorded baseline of 0"), + "{}", + verdict.message + ); + } + + /// The over-correction this must NOT become: a baseline that hides debt. + /// + /// A generous baseline buys silence in exactly one place — the pass/fail + /// verdict. It must not raise the floor, must not exclude a path, and must + /// not remove a single definition from the count. Nothing here may read as + /// the sentence a genuinely clean tree gets. + #[test] + fn a_baseline_holds_debt_flat_without_hiding_any_of_it() { + let tmp = tdg_fixture(&[ + ("src/a.rs", "one", "D", 30, 5), + ("src/b.rs", "two", "C-", 20, 9), + ("src/c.rs", "three", "B+", 12, 3), + ]); + write_gates(&tmp, "[tdg]\nbaseline = 100\n"); + let verdict = judge(&tmp); + + assert_eq!(verdict.status, CheckStatus::Warn, "{}", verdict.message); + assert!( + verdict.message.contains("3 definition(s)"), + "every unit of debt stays counted: {}", + verdict.message + ); + assert!( + verdict.message.contains("minimum grade A"), + "the floor is still A — a baseline is not a lowered threshold: {}", + verdict.message + ); + // B+ is below A and is still counted: the baseline did not quietly + // narrow the alphabet the way the five-letter reader did. + assert!( + !verdict + .message + .contains("All non-test functions meet minimum grade"), + "held debt must never borrow the clean tree's sentence: {}", + verdict.message + ); + } + + /// The listing is deterministic, worst first. + /// + /// It used to be `take(10)` off a `SELECT` with no `ORDER BY`. On a flat + /// 1,905-across-1,052-files distribution there is nothing else in the + /// message to distinguish "the tree changed" from "SQLite scanned in a + /// different order". + #[test] + fn the_offender_listing_is_worst_first_and_stable() { + let tmp = tdg_fixture(&[ + ("src/mild.rs", "mild", "B+", 11, 1), + ("src/worst.rs", "worst", "F", 9, 1), + ("src/bad_simple.rs", "bad_simple", "D", 4, 1), + ("src/bad_complex.rs", "bad_complex", "D", 90, 1), + ]); + let verdict = judge(&tmp); + assert_eq!(verdict.status, CheckStatus::Fail, "{}", verdict.message); + + for name in ["worst", "bad_complex", "bad_simple", "mild"] { + assert!( + verdict.message.contains(name), + "{name} must be listed: {}", + verdict.message + ); + } + let order: Vec = ["worst", "bad_complex", "bad_simple", "mild"] + .iter() + .map(|name| verdict.message.find(name).unwrap_or(usize::MAX)) + .collect(); + let mut sorted = order.clone(); + sorted.sort_unstable(); + assert_eq!( + order, sorted, + "worst grade first, then highest complexity: {}", + verdict.message + ); + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests_stale_and_absent_index { + use super::tests_tdg_grade::{judge, tdg_fixture, write_gates}; + use super::*; + use crate::models::comply_config::ComplyConfig; + + /// Make `tmp`'s index stale by planting a source file a clear minute newer + /// than `.pmat/context.db`. + /// + /// The mtime is SET, not raced for. Writing the source after the db and + /// trusting wall-clock ordering fails on filesystems whose timestamp + /// granularity is coarser than the microseconds between two writes — both + /// files land in the same tick, `is_index_stale` correctly sees nothing + /// newer, and the fixture silently never reaches the behaviour under test. + fn make_stale(tmp: &tempfile::TempDir) { + let db_path = tmp.path().join(".pmat").join("context.db"); + std::fs::create_dir_all(tmp.path().join("src")).expect("create src"); + let src = tmp.path().join("src/lib.rs"); + std::fs::write(&src, "pub fn f() {}\n").expect("write source"); + let db_mtime = std::fs::metadata(&db_path) + .and_then(|m| m.modified()) + .expect("db mtime"); + std::fs::File::options() + .write(true) + .open(&src) + .and_then(|f| f.set_modified(db_mtime + std::time::Duration::from_secs(60))) + .expect("set source mtime a clear minute past the db"); + assert!( + is_index_stale(tmp.path(), &db_path), + "fixture must be stale before the behaviour under test can be reached" + ); + } + + /// Plant a source file OLDER than the db, so the fixture is provably fresh + /// while still containing sources — the counter-fixture to `make_stale`. + fn make_fresh(tmp: &tempfile::TempDir) { + let db_path = tmp.path().join(".pmat").join("context.db"); + std::fs::create_dir_all(tmp.path().join("src")).expect("create src"); + let src = tmp.path().join("src/lib.rs"); + std::fs::write(&src, "pub fn f() {}\n").expect("write source"); + let db_mtime = std::fs::metadata(&db_path) + .and_then(|m| m.modified()) + .expect("db mtime"); + std::fs::File::options() + .write(true) + .open(&src) + .and_then(|f| f.set_modified(db_mtime - std::time::Duration::from_secs(60))) + .expect("set source mtime a clear minute before the db"); + assert!( + !is_index_stale(tmp.path(), &db_path), + "counter-fixture must be fresh" + ); + } + + /// #1045. A clean count taken from a stale index is not a clean tree: the + /// definitions added since the index was built are not in it AT ALL, so + /// "nothing below the floor" is a statement about yesterday. + /// + /// RED, with `demote_pass_when_stale` reduced to `check` (the identity it + /// replaced): + /// ```text + /// a clean count over a STALE index must not be a Pass, got Pass: + /// All non-test functions meet minimum grade A (index is stale: ...) + /// ``` + #[test] + fn a_stale_index_may_not_report_a_pass() { + let tmp = tdg_fixture(&[("src/lib.rs", "fine", "A", 3, 1)]); + make_stale(&tmp); + let verdict = judge(&tmp); + assert_ne!( + verdict.status, + CheckStatus::Pass, + "a clean count over a STALE index must not be a Pass, got Pass:\n {}", + verdict.message + ); + assert_eq!(verdict.status, CheckStatus::Warn, "{}", verdict.message); + assert!( + verdict + .message + .contains("NOT A VERDICT ON THE CURRENT TREE"), + "the reader must be told the verdict is not about their tree: {}", + verdict.message + ); + assert!( + verdict.message.contains("index is stale"), + "the reason must survive the demotion: {}", + verdict.message + ); + } + + /// The counter-test that bounds the demotion. Staleness is the trigger, not + /// the presence of sources or the mere act of reading an index: an index + /// NEWER than every source still reports a clean tree as clean. + /// + /// Without this, `demote_pass_when_stale` returning `Warn` unconditionally + /// would pass every other test in this module. + #[test] + fn a_fresh_index_still_reports_a_pass() { + let tmp = tdg_fixture(&[("src/lib.rs", "fine", "A", 3, 1)]); + make_fresh(&tmp); + let verdict = judge(&tmp); + assert_eq!( + verdict.status, + CheckStatus::Pass, + "a fresh index over a clean tree must still Pass: {}", + verdict.message + ); + assert!( + !verdict.message.contains("index is stale"), + "a fresh index must not be described as stale: {}", + verdict.message + ); + } + + /// Staleness makes a verdict LESS conclusive, never more lenient. A count + /// over the floor is still a Fail — those definitions exist somewhere in + /// history and the fix is to look, not to shrug. + #[test] + fn a_stale_index_does_not_soften_a_failure() { + let tmp = tdg_fixture(&[("src/lib.rs", "bad", "D", 30, 5)]); + make_stale(&tmp); + let verdict = judge(&tmp); + assert_eq!( + verdict.status, + CheckStatus::Fail, + "staleness must not downgrade a Fail: {}", + verdict.message + ); + } + + /// #1045's second half: the advice printed beside a stale index must name a + /// command that actually refreshes one. + /// + /// `pmat query "x"` does not. `load_or_build_index` + /// (`cli/handlers/query_handler/indexing.rs:251`) rebuilds only when BOTH + /// `.pmat/context.idx` and `.pmat/context.db` are absent; with an index + /// present it updates in memory and `maybe_save_incremental` persists that + /// only past 50 changed files or 5% of the index. Following the old advice + /// after editing a handful of files left the db byte-identical and the next + /// run printed the same "index is stale" with the same useless remedy. + /// + /// RED, restoring the old text: `the stale-index advice must name + /// --rebuild-index`. + #[test] + fn the_stale_advice_names_a_command_that_rebuilds() { + let tmp = tdg_fixture(&[("src/lib.rs", "fine", "A", 3, 1)]); + make_stale(&tmp); + let verdict = judge(&tmp); + assert!( + verdict.message.contains("--rebuild-index"), + "the stale-index advice must name --rebuild-index, because a plain \ + `pmat query` does not rewrite an index that already exists: {}", + verdict.message + ); + } + + /// #1008. `.pmat/` is gitignored and no CI leg builds an index, so an + /// absent index answered `Skip` — and `is_compliant` counts `Fail` only. + /// A project that RECORDED a ratchet baseline therefore had a gate that + /// could only fail on a machine which happened to have an index lying + /// around: unenforceable exactly where it decides a merge. + /// + /// RED, with `absent_index_verdict` returning the old unconditional `Skip`: + /// ```text + /// a recorded baseline that was never checked must not report success, + /// got Skip + /// ``` + #[test] + fn an_absent_index_fails_a_project_that_recorded_a_baseline() { + let tmp = tempfile::tempdir().expect("create tempdir"); + std::fs::write( + tmp.path().join(".pmat-gates.toml"), + "[tdg]\nbaseline = 1688\n", + ) + .expect("write gates toml"); + let verdict = check_tdg_grade_gate(tmp.path(), &ComplyConfig::default()); + assert_eq!( + verdict.status, + CheckStatus::Fail, + "a recorded baseline that was never checked must not report success, got {:?}: {}", + verdict.status, + verdict.message + ); + assert!( + verdict.message.contains("1688"), + "the unchecked baseline must be named: {}", + verdict.message + ); + assert!( + verdict.message.contains("Not measured"), + "the failure is 'unmeasured', not 'violated': {}", + verdict.message + ); + assert!( + !tmp.path().join(".pmat").exists(), + "#939: an audit must not build an index inside the tree it audits" + ); + } + + /// The counter-test that bounds #1008's fix, and it is the one that matters + /// most: pmat runs `comply check` against repositories that never opted + /// into this ratchet. A project with no `[tdg] baseline` — no + /// `.pmat-gates.toml` at all, or one without the key — must still be told + /// "not measured" and must NOT be failed for it. + /// + /// Without this, "make the absent index fail" is a one-line change that + /// reddens every fresh clone of every project pmat has ever been pointed at. + #[test] + fn an_absent_index_still_skips_a_project_with_no_baseline() { + let nothing = tempfile::tempdir().expect("create tempdir"); + let verdict = check_tdg_grade_gate(nothing.path(), &ComplyConfig::default()); + assert_eq!( + verdict.status, + CheckStatus::Skip, + "a project holding no baseline has nothing to fail: {}", + verdict.message + ); + + let no_key = tempfile::tempdir().expect("create tempdir"); + std::fs::write( + no_key.path().join(".pmat-gates.toml"), + "[tdg]\nexclude = [\"nothing/**\"]\n", + ) + .expect("write gates toml"); + let verdict = check_tdg_grade_gate(no_key.path(), &ComplyConfig::default()); + assert_eq!( + verdict.status, + CheckStatus::Skip, + "a [tdg] table without a baseline records no ratchet: {}", + verdict.message + ); + } + + /// An unreadable baseline is a Fail with or without an index. It was + /// already a Fail once the index was read; routing the absent-index case + /// through the same verdict keeps the two answers identical rather than + /// letting "no index" launder a broken config into a Skip. + #[test] + fn an_unreadable_baseline_fails_even_with_no_index() { + let tmp = tempfile::tempdir().expect("create tempdir"); + std::fs::write( + tmp.path().join(".pmat-gates.toml"), + "[tdg]\nbaseline = \"lots\"\n", + ) + .expect("write gates toml"); + let verdict = check_tdg_grade_gate(tmp.path(), &ComplyConfig::default()); + assert_eq!(verdict.status, CheckStatus::Fail, "{}", verdict.message); + assert!( + verdict.message.contains("not a count"), + "{}", + verdict.message + ); + } + + /// The demotion is a funnel, not a special case bolted onto one verdict: + /// the ratchet's own clean-at-baseline `Pass` goes through it too. + #[test] + fn the_ratchet_clean_pass_is_demoted_when_stale() { + let tmp = tdg_fixture(&[("src/lib.rs", "fine", "A", 3, 1)]); + write_gates(&tmp, "[tdg]\nbaseline = 0\n"); + make_stale(&tmp); + let verdict = judge(&tmp); + assert_eq!( + verdict.status, + CheckStatus::Warn, + "0-against-baseline-0 over a stale index is not a Pass: {}", + verdict.message + ); + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests_index_outside_the_audited_tree { + use super::*; + use std::process::Command; + + /// A tree with one definition no floor of A can admit, and one that passes, + /// so a verdict of "everything is fine" and a verdict of "nothing was read" + /// are distinguishable from each other. + /// + /// The grade is driven by complexity alone. It used to carry two + /// self-admitted-debt comments as well, until `.pmat-ratchet.toml`'s + /// `satd_markers_src_comments` went red at HEAD: that metric greps comment + /// lines across `src/*.rs`, and a raw string literal inside a test is still + /// a line in a file. A fixture is not a licence to move a baseline — nor is + /// a comment about one, which is why this paragraph does not spell the four + /// words out either. + const AWFUL: &str = r#" +pub fn fine(a: u32) -> u32 { a + 1 } + +pub fn awful(a: i32, b: i32, c: i32, d: i32) -> i32 { + let mut t = 0; + for i in 0..a { + if i % 2 == 0 { + for j in 0..b { + if j % 3 == 0 { + while t < c { + if t % 5 == 0 { t += 1; } else if t % 7 == 0 { t += 2; } else { t += 3; } + match t % 4 { + 0 => t += 1, + 1 => t += 2, + 2 => { if d > 0 { t += 3 } else { t -= 1 } } + _ => t += 4, + } + } + } else if j % 5 == 0 { t -= 1; } else if j % 7 == 0 { t += j; } else { t += 2; } + } + } else if i % 3 == 0 { + t += 2; + } else { + for k in 0..d { if k > 3 { t += k } else if k > 1 { t -= k } else { t += 1 } } + } + } + t +} +"#; + + pub(super) fn committed_cargo_project() -> tempfile::TempDir { + let d = git_project_with_code(); + std::fs::write( + d.path().join("Cargo.toml"), + "[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n", + ) + .expect("write manifest"); + git(d.path(), &["add", "-A"]); + git( + d.path(), + &[ + "-c", + "user.email=t@t", + "-c", + "user.name=t", + "commit", + "--no-verify", + "-qm", + "manifest", + ], + ); + d + } + + pub(super) fn porcelain_of(p: &Path) -> String { + porcelain(p) + } + + pub(super) fn build_index_in(p: &Path) { + build_in_project_index(p) + } + + fn project_with_code() -> tempfile::TempDir { + let dir = tempfile::tempdir().expect("create tempdir"); + std::fs::create_dir_all(dir.path().join("src")).expect("create src"); + std::fs::write(dir.path().join("src/lib.rs"), AWFUL).expect("write source"); + dir + } + + /// The index `pmat query` would leave in the project — built the same way, + /// so side B of the A/B is the real thing and not a hand-written fixture. + fn build_in_project_index(project: &Path) { + let index_path = project.join(".pmat").join("context.idx"); + std::fs::create_dir_all(project.join(".pmat")).expect("create .pmat"); + crate::services::agent_context::AgentContextIndex::build(project) + .expect("build index") + .save(&index_path) + .expect("save index"); + } + + /// The verdict without the provenance note, which is the only part of the + /// message that is *supposed* to differ between the two sides. + fn verdict_core(check: &ComplianceCheck) -> String { + check + .message + .split(" [measured against") + .next() + .unwrap_or_default() + .to_string() + } + + /// `$PMAT_CACHE_DIR`, restored on drop so a failing assertion cannot leak + /// it into the rest of the suite. + pub(super) struct CacheDirGuard(Option); + + /// `$PMAT_CACHE_DIR` for the duration of one test, for the sibling module + /// that runs the whole compliance report rather than one check. + pub(super) fn cache_dir_guard(dir: &Path) -> CacheDirGuard { + CacheDirGuard::pointing_at(dir) + } + + impl CacheDirGuard { + fn pointing_at(dir: &Path) -> Self { + let previous = std::env::var_os(crate::utils::pmat_cache_dir::CACHE_DIR_ENV); + std::env::set_var(crate::utils::pmat_cache_dir::CACHE_DIR_ENV, dir); + Self(previous) + } + } + + impl Drop for CacheDirGuard { + fn drop(&mut self) { + match self.0.take() { + Some(v) => std::env::set_var(crate::utils::pmat_cache_dir::CACHE_DIR_ENV, v), + None => std::env::remove_var(crate::utils::pmat_cache_dir::CACHE_DIR_ENV), + } + } + } + + fn git(dir: &Path, args: &[&str]) -> std::process::Output { + Command::new("git") + .current_dir(dir) + .args(args) + .output() + .expect("git must be runnable") + } + + fn porcelain(dir: &Path) -> String { + String::from_utf8_lossy(&git(dir, &["status", "--porcelain"]).stdout).into_owned() + } + + /// `--template=` keeps a developer's global hook template out of the + /// fixture; `--no-verify` on the commit keeps a global `core.hooksPath` + /// from running this repository's own gates inside a two-file tempdir. + fn git_project_with_code() -> tempfile::TempDir { + let dir = project_with_code(); + assert!( + git(dir.path(), &["init", "-q", "--template=", "."]) + .status + .success(), + "git init failed" + ); + git(dir.path(), &["add", "-A"]); + assert!( + git( + dir.path(), + &[ + "-c", + "user.email=t@t", + "-c", + "user.name=t", + "commit", + "--no-verify", + "-qm", + "fixture", + ], + ) + .status + .success(), + "commit failed" + ); + dir + } + + /// #1008's own A/B, at the check that showed it: one tree, one commit, the + /// index the only variable. The two sides must return the SAME verdict. + /// + /// Reported on `paiml/rmedia` at `ffe4f68` as 2 failing checks without an + /// index against 3 with one — a gate that structurally could not fail on a + /// fresh CI checkout, which is the only checkout whose verdict decides a + /// merge. + /// + /// RED, against the code this replaces: + /// ```text + /// thread 'a_tree_gets_the_same_verdict_with_or_without_an_index_of_its_own' + /// panicked at check_tdg_grade.rs: + /// a project with no index of its own was not measured at all (Skip) - #1008: + /// a gate that can only run where an index happens to exist cannot run in CI + /// ``` + #[test] + #[serial_test::serial] + fn a_tree_gets_the_same_verdict_with_or_without_an_index_of_its_own() { + let cache = tempfile::tempdir().expect("create cache dir"); + let _env = CacheDirGuard::pointing_at(cache.path()); + let project = project_with_code(); + let config = ComplyConfig::default(); + + // A: no `.pmat/` anywhere in the tree, exactly as a fresh checkout. + let without = check_tdg_grade_gate(project.path(), &config); + assert!( + !project.path().join(".pmat").exists(), + "#939: the audited tree must not be written to, index or no index" + ); + assert_ne!( + without.status, + CheckStatus::Skip, + "a project with no index of its own was not measured at all (Skip) - #1008: \ + a gate that can only run where an index happens to exist cannot run in CI. \ + Message was: {}", + without.message + ); + + // B: the same tree, with the index `pmat query` would have built. + build_in_project_index(project.path()); + let with = check_tdg_grade_gate(project.path(), &config); + + assert_eq!( + without.status, with.status, + "the same tree got two different verdicts:\n without: {}\n with: {}", + without.message, with.message + ); + assert_eq!( + verdict_core(&without), + verdict_core(&with), + "the same tree was measured differently depending on where its index lived" + ); + assert!( + with.message.split(" [measured against").count() == 1, + "a project's own index is read in place and says nothing about a cache: {}", + with.message + ); + assert!( + without.message.contains("[measured against"), + "a verdict from outside the tree must name the file it read: {}", + without.message + ); + } + + /// The fixture is load-bearing: if it graded clean, the A/B above would be + /// comparing two Passes and could not tell a measurement from a shrug. + #[test] + #[serial_test::serial] + fn the_out_of_tree_measurement_can_actually_fail() { + let cache = tempfile::tempdir().expect("create cache dir"); + let project = project_with_code(); + + let verdict = check_tdg_grade_gate_with_index( + project.path(), + &ComplyConfig::default(), + &cache.path().join("context.idx"), + ); + + assert_eq!( + verdict.status, + CheckStatus::Fail, + "a definition below the floor must fail from the out-of-tree index too: {}", + verdict.message + ); + assert!( + verdict.message.contains("awful"), + "the offender must be named: {}", + verdict.message + ); + } + + /// THE counter-test. Building an index must not manufacture a verdict out + /// of a tree pmat never read a line of. + /// + /// Zero definitions violate no floor, so the naive version of this fix + /// answers "no functions below minimum grade A" — a clean bill of health + /// for a directory with no code in it, which is worse than the skip it + /// replaced. An empty measurement is not a measurement. + #[test] + fn a_tree_with_no_definitions_is_not_measured_into_a_pass() { + let cache = tempfile::tempdir().expect("create cache dir"); + let project = tempfile::tempdir().expect("create tempdir"); + std::fs::write(project.path().join("README.md"), "# no code here\n").expect("write"); + + let verdict = check_tdg_grade_gate_with_index( + project.path(), + &ComplyConfig::default(), + &cache.path().join("context.idx"), + ); + + assert_eq!( + verdict.status, + CheckStatus::Skip, + "a tree with nothing to grade must not report a pass: {}", + verdict.message + ); + assert!( + verdict.message.contains("no definitions"), + "the reason must say what was missing: {}", + verdict.message + ); + assert!( + !cache.path().join("context.db").exists(), + "an empty index must not be cached as if it were a measurement" + ); + + // And the same nothing, with a ratchet recorded against it, fails + // rather than passes: an unmeasured baseline has not held. + std::fs::write( + project.path().join(".pmat-gates.toml"), + "[tdg]\nbaseline = 7\n", + ) + .expect("write gates toml"); + let recorded = check_tdg_grade_gate_with_index( + project.path(), + &ComplyConfig::default(), + &cache.path().join("context.idx"), + ); + assert_eq!(recorded.status, CheckStatus::Fail, "{}", recorded.message); + assert!( + recorded.message.contains("Not measured"), + "{}", + recorded.message + ); + } + + /// The property the refusal to build was protecting, asked of git rather + /// than of a filename: after CB-200 has measured a repository, that + /// repository's `git status` is empty. + #[test] + #[serial_test::serial] + fn measuring_a_repository_leaves_its_git_status_clean() { + let cache = tempfile::tempdir().expect("create cache dir"); + let _env = CacheDirGuard::pointing_at(cache.path()); + let project = git_project_with_code(); + assert_eq!(porcelain(project.path()), "", "fixture must start clean"); + + let verdict = check_tdg_grade_gate(project.path(), &ComplyConfig::default()); + + assert_ne!( + verdict.status, + CheckStatus::Skip, + "the point is that it measured: {}", + verdict.message + ); + assert_eq!( + porcelain(project.path()), + "", + "CB-200 dirtied the repository it audited" + ); + assert!( + !project.path().join(".pmat").exists(), + "not even an ignored .pmat/ - the tree is not pmat's to write to" + ); + } + + /// The other counter-test: a project that VERSIONS its own `.pmat/` must + /// keep it. The out-of-tree index is a fallback for a tree that has none, + /// never a replacement for one that does — nothing here may ignore, move, + /// clobber or shadow a committed index. + #[test] + #[serial_test::serial] + fn a_committed_index_is_read_in_place_and_left_alone() { + let cache = tempfile::tempdir().expect("create cache dir"); + let _env = CacheDirGuard::pointing_at(cache.path()); + let project = git_project_with_code(); + build_in_project_index(project.path()); + git(project.path(), &["add", "-f", ".pmat/context.db"]); + assert!( + git( + project.path(), + &[ + "-c", + "user.email=t@t", + "-c", + "user.name=t", + "commit", + "--no-verify", + "-qm", + "we version our index", + ], + ) + .status + .success(), + "commit failed" + ); + let before = std::fs::metadata(project.path().join(".pmat/context.db")) + .and_then(|m| m.modified()) + .expect("db mtime"); + + let verdict = check_tdg_grade_gate(project.path(), &ComplyConfig::default()); + + assert!( + git( + project.path(), + &["ls-files", "--error-unmatch", ".pmat/context.db"], + ) + .status + .success(), + "the committed index was dropped from the index" + ); + let after = std::fs::metadata(project.path().join(".pmat/context.db")) + .and_then(|m| m.modified()) + .expect("db mtime"); + assert_eq!(before, after, "the committed index was rewritten"); + assert!( + !verdict.message.contains("[measured against"), + "a committed index must be READ, not shadowed by a cached copy: {}", + verdict.message + ); + assert!( + !crate::utils::pmat_cache_dir::comply_index_path(project.path()) + .with_extension("db") + .exists(), + "no out-of-tree index may be built for a project that has one" + ); + // Asked of THIS project's cache entry and not of the cache root: the + // root is shared, so `/comply` merely existing says only that + // some other project was measured, which is not this test's business + // and made it fail under a full parallel run. + } + + /// The cache is a cache: a second run reads what the first one built + /// instead of paying for the walk again. + #[test] + fn a_built_index_is_reused_by_the_next_run() { + let cache = tempfile::tempdir().expect("create cache dir"); + let index_path = cache.path().join("context.idx"); + let project = project_with_code(); + let config = ComplyConfig::default(); + + let first = check_tdg_grade_gate_with_index(project.path(), &config, &index_path); + let built = std::fs::metadata(index_path.with_extension("db")) + .and_then(|m| m.modified()) + .expect("db mtime"); + + let second = check_tdg_grade_gate_with_index(project.path(), &config, &index_path); + let after = std::fs::metadata(index_path.with_extension("db")) + .and_then(|m| m.modified()) + .expect("db mtime"); + + assert_eq!( + built, after, + "the second run rebuilt an index it could reuse" + ); + assert_eq!(first.status, second.status); + assert_eq!(first.message, second.message); + } + + /// …and it is a cache of THIS tree. Edit the sources and the next run + /// measures the edit, or the gate would hold a baseline against a tree that + /// no longer exists — which is how `.pmat-gates.toml`'s own baseline was + /// first banked 216 units too high. + #[test] + fn an_edit_after_the_build_is_measured_not_ignored() { + let cache = tempfile::tempdir().expect("create cache dir"); + let index_path = cache.path().join("context.idx"); + let project = project_with_code(); + let config = ComplyConfig::default(); + + let before = check_tdg_grade_gate_with_index(project.path(), &config, &index_path); + assert_eq!(before.status, CheckStatus::Fail, "{}", before.message); + + // The offending definition is deleted, and the CACHED INDEX is aged a + // clear minute rather than the edit being raced against it: filesystem + // timestamp granularity is coarser than the microseconds between two + // writes, which is the kind of thing that passes locally and fails in + // CI. Ageing the index rather than post-dating the source also keeps + // the fixture honest — a source file with an mtime in the future is + // newer than any index that could ever be built from it. + let src = project.path().join("src/lib.rs"); + std::fs::write(&src, "pub fn fine(a: u32) -> u32 { a + 1 }\n").expect("write source"); + let db = index_path.with_extension("db"); + let db_mtime = std::fs::metadata(&db) + .and_then(|m| m.modified()) + .expect("db mtime"); + std::fs::File::options() + .write(true) + .open(&db) + .and_then(|f| f.set_modified(db_mtime - std::time::Duration::from_secs(60))) + .expect("age the cached index a clear minute"); + assert!( + is_index_stale(project.path(), &db), + "fixture must be stale before the run under test" + ); + + let after = check_tdg_grade_gate_with_index(project.path(), &config, &index_path); + assert_eq!( + after.status, + CheckStatus::Pass, + "the fix was not seen: a stale cached index was reported as the current tree: {}", + after.message + ); + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests_the_reported_ab { + use super::super::check::compute_compliance_report; + use super::super::types::{CheckStatus, ComplianceReport}; + use super::tests_index_outside_the_audited_tree as fixture; + + fn names(report: &ComplianceReport, want: CheckStatus) -> Vec { + let mut found: Vec = report + .checks + .iter() + .filter(|c| c.status == want) + .map(|c| c.name.clone()) + .collect(); + found.sort(); + found + } + + /// #1008 as it was reported: the WHOLE compliance report, one tree, one + /// commit, the index the only variable. The set of FAILING checks must be + /// the same on both sides. + /// + /// The report on `paiml/rmedia` at `ffe4f68` was 2 failing checks without + /// an index and 3 with — CB-200 the difference. The exit code happened to + /// be 1 either way there, because other checks were failing; had CB-200 + /// been the only violation, the same commit would have exited 0 in CI and 1 + /// on a developer's machine. + /// + /// Measured here, on this fixture, RED against the code this replaces: + /// ```text + /// A fail=3 B fail=4 only_with_index: ["CB-200: TDG Grade Gate"] + /// ``` + /// and after: + /// ```text + /// A fail=4 B fail=4 only_with_index: [] + /// ``` + /// + /// It asserts the SET and not the count, and it names the differing checks + /// when it fails, because the interesting failure is a new check that can + /// only be measured where somebody has run `pmat query` — this defect + /// arriving again somewhere else. + #[test] + #[serial_test::serial] + fn the_failing_checks_do_not_depend_on_whether_an_index_exists() { + let cache = tempfile::tempdir().expect("create cache dir"); + let _env = fixture::cache_dir_guard(cache.path()); + let project = fixture::committed_cargo_project(); + + let without = compute_compliance_report(project.path()).expect("report without an index"); + let without_failing = names(&without, CheckStatus::Fail); + assert_eq!( + fixture::porcelain_of(project.path()), + "", + "auditing the project dirtied it" + ); + + fixture::build_index_in(project.path()); + let with = compute_compliance_report(project.path()).expect("report with an index"); + let with_failing = names(&with, CheckStatus::Fail); + + let only_with: Vec<&String> = with_failing + .iter() + .filter(|n| !without_failing.contains(n)) + .collect(); + let only_without: Vec<&String> = without_failing + .iter() + .filter(|n| !with_failing.contains(n)) + .collect(); + assert!( + only_with.is_empty() && only_without.is_empty(), + "the same tree failed a different set of checks depending on whether an index \ + happened to exist.\n fails only WITH an index: {only_with:?}\n \ + fails only WITHOUT an index: {only_without:?}" + ); + assert!( + without_failing.iter().any(|n| n.starts_with("CB-200")), + "the fixture must actually violate CB-200, or this proves nothing. Failing: \ + {without_failing:?}" + ); + } +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/comply_handlers/muda_handlers.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/comply_handlers/muda_handlers.rs.txt new file mode 100644 index 0000000000..5736f169a9 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/comply_handlers/muda_handlers.rs.txt @@ -0,0 +1,160 @@ +//! CB-300: Muda (Seven Wastes) Score for Code Quality +//! +//! Maps Toyota Production System's Seven Wastes to code quality metrics: +//! +//! | Toyota Waste | Code Equivalent | Detection | +//! |------------------|---------------------------|------------------------------| +//! | Overproduction | Dead Code | `dead_code` analysis (CB-128)| +//! | Waiting | Slow Tests / Builds | Test time (CB-126/CB-127) | +//! | Inventory | Stale SATD markers | SATD age > 90 days | +//! | Transport | Excessive cloning | `.clone()` in hot paths | +//! | Over-processing | High complexity | Cyclomatic > 15 | +//! | Motion | Dependency sprawl | Dep count / graph depth | +//! | Defects | Bugs / Test failures | Panic count / stub count | +//! +//! Score: 0-100 (lower is better, 0 = zero waste) + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::Path; + +/// Muda Waste Report aggregating all seven wastes +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MudaReport { + /// Overproduction: dead code percentage (0-100) + pub overproduction: f64, + /// Waiting: slow test/build score (0-100) + pub waiting: f64, + /// Inventory: stale SATD/branch score (0-100) + pub inventory: f64, + /// Transport: excessive copying score (0-100) + pub transport: f64, + /// Over-processing: complexity waste (0-100) + pub over_processing: f64, + /// Motion: dependency sprawl (0-100) + pub motion: f64, + /// Defects: bug/panic indicators (0-100) + pub defects: f64, + /// Total aggregate score (0-100, lower is better) + pub total_score: f64, + /// Grade based on total score + pub grade: MudaGrade, + /// Maps each Muda category to its top contributing files (up to 5 per category) + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub file_details: HashMap>, +} + +/// Muda grade classification +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum MudaGrade { + /// 0-20: Lean (minimal waste) + Lean, + /// 21-40: Efficient + Efficient, + /// 41-60: Moderate waste + Moderate, + /// 61-80: High waste + High, + /// 81-100: Critical waste + Critical, +} + +impl std::fmt::Display for MudaGrade { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MudaGrade::Lean => write!(f, "Lean"), + MudaGrade::Efficient => write!(f, "Efficient"), + MudaGrade::Moderate => write!(f, "Moderate"), + MudaGrade::High => write!(f, "High"), + MudaGrade::Critical => write!(f, "Critical"), + } + } +} + +impl MudaGrade { + fn from_score(score: f64) -> Self { + match score as u32 { + 0..=20 => MudaGrade::Lean, + 21..=40 => MudaGrade::Efficient, + 41..=60 => MudaGrade::Moderate, + 61..=80 => MudaGrade::High, + _ => MudaGrade::Critical, + } + } +} + +/// Calculate the Muda Waste Score for a project. +/// +/// Weights: Defects (25%), Inventory (20%), Over-processing (15%), +/// Overproduction (15%), Waiting (15%), Motion (5%), Transport (5%) +/// +/// Inventory (SATD) elevated to 20% — stale TODO/FIXME/HACK accumulation +/// is a primary signal of unmaintained code and must not be masked. +#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] +pub fn calculate_muda_score(project_path: &Path) -> MudaReport { + let overproduction = measure_overproduction(project_path); + let waiting = measure_waiting(project_path); + let inventory = measure_inventory(project_path); + let transport = measure_transport(project_path); + let over_processing = measure_over_processing(project_path); + let motion = measure_motion(project_path); + let defects = measure_defects(project_path); + + // Collect file details for categories with concrete file mappings + let mut file_details = HashMap::new(); + let overproduction_files = collect_overproduction_files(project_path); + if !overproduction_files.is_empty() { + file_details.insert("Overproduction".to_string(), overproduction_files); + } + let inventory_files = collect_inventory_files(project_path); + if !inventory_files.is_empty() { + file_details.insert("Inventory".to_string(), inventory_files); + } + let over_processing_files = collect_over_processing_files(project_path); + if !over_processing_files.is_empty() { + file_details.insert("Over-processing".to_string(), over_processing_files); + } + let defect_files = collect_defect_files(project_path); + if !defect_files.is_empty() { + file_details.insert("Defects".to_string(), defect_files); + } + + // Weighted average (weights sum to 1.0) + // Inventory elevated: stale SATD is a primary waste signal + let total_score = (defects * 0.25) + + (inventory * 0.20) + + (over_processing * 0.15) + + (overproduction * 0.15) + + (waiting * 0.15) + + (motion * 0.05) + + (transport * 0.05); + + let total_score = total_score.clamp(0.0, 100.0); + let grade = MudaGrade::from_score(total_score); + + MudaReport { + overproduction, + waiting, + inventory, + transport, + over_processing, + motion, + defects, + total_score, + grade, + file_details, + } +} + +// --- Include split submodules --- + +// SATD measurement: overproduction, waiting, inventory, SATD counting helpers +include!("muda_handlers_measurement.rs"); + +// Project metrics: transport, over-processing, motion, defects +include!("muda_handlers_metrics.rs"); + +// Unit tests +include!("muda_handlers_tests.rs"); +// #[requires(project_path.exists())] +// #[ensures(result.is_ok())] diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/work_contract_scoring.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/work_contract_scoring.rs.txt new file mode 100644 index 0000000000..be523ec6f8 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/cli/handlers/work_contract_scoring.rs.txt @@ -0,0 +1,468 @@ +// Contract Scoring: 5-dimension quality scoring for pmat work contracts +// Spec: docs/specifications/dbc.md §13.4-13.5 +// +// Adapted from provable-contracts scoring (spec_depth, falsification, kani, lean, binding) +// to pmat work contract dimensions (spec_depth, falsification, invariant_health, +// subcontracting, traceability). + +/// 5-dimension contract quality score +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ContractScore { + /// How comprehensive are the contract clauses? (weight: 0.20) + pub spec_depth: f64, + /// What fraction of claims have been verified? (weight: 0.25) + pub falsification_coverage: f64, + /// Invariant pass rate across checkpoints (weight: 0.25) + pub invariant_health: f64, + /// Monotonic postcondition strengthening score (weight: 0.10) + pub subcontracting: f64, + /// Coverage of require/ensure/invariant triad (weight: 0.20) + pub traceability: f64, + /// Weighted total score (0.0 to 1.0) + pub total: f64, + /// Letter grade (A-F) + pub grade: ScoreGrade, +} + +/// Scoring weights (sum = 1.0) +#[derive(Debug, Clone)] +pub struct ScoringWeights { + pub spec_depth: f64, + pub falsification: f64, + pub invariant_health: f64, + pub subcontracting: f64, + pub traceability: f64, +} + +impl Default for ScoringWeights { + fn default() -> Self { + Self { + spec_depth: 0.20, + falsification: 0.25, + invariant_health: 0.25, + subcontracting: 0.10, + traceability: 0.20, + } + } +} + +/// Letter grades for contract scores +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum ScoreGrade { + A, + B, + C, + D, + F, +} + +impl ScoreGrade { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + /// From score. + pub fn from_score(score: f64) -> Self { + if score >= 0.90 { + ScoreGrade::A + } else if score >= 0.75 { + ScoreGrade::B + } else if score >= 0.60 { + ScoreGrade::C + } else if score >= 0.40 { + ScoreGrade::D + } else { + ScoreGrade::F + } + } +} + +impl std::fmt::Display for ScoreGrade { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ScoreGrade::A => write!(f, "A"), + ScoreGrade::B => write!(f, "B"), + ScoreGrade::C => write!(f, "C"), + ScoreGrade::D => write!(f, "D"), + ScoreGrade::F => write!(f, "F"), + } + } +} + +/// Compute the 5-dimension contract score for a work contract. +/// +/// Each dimension is scored 0.0..1.0, then weighted and summed. +#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] +pub fn score_contract(contract: &WorkContract, project_path: &Path) -> ContractScore { + debug_assert!(project_path.exists(), "project_path must exist: {}", project_path.display()); + let weights = ScoringWeights::default(); + + let spec_depth = compute_spec_depth(contract); + let falsification_coverage = compute_falsification_coverage(contract); + let invariant_health = compute_invariant_health(contract, project_path); + let subcontracting = compute_subcontracting_score(contract); + let traceability = compute_traceability(contract); + + let total = weights.spec_depth * spec_depth + + weights.falsification * falsification_coverage + + weights.invariant_health * invariant_health + + weights.subcontracting * subcontracting + + weights.traceability * traceability; + + // All dimensions must be in [0.0, 1.0] + debug_assert!((0.0..=1.0).contains(&spec_depth), "spec_depth out of range: {}", spec_depth); + debug_assert!((0.0..=1.0).contains(&falsification_coverage), "falsification out of range: {}", falsification_coverage); + debug_assert!((0.0..=1.0).contains(&invariant_health), "invariant_health out of range: {}", invariant_health); + debug_assert!((0.0..=1.0).contains(&subcontracting), "subcontracting out of range: {}", subcontracting); + debug_assert!((0.0..=1.0).contains(&traceability), "traceability out of range: {}", traceability); + debug_assert!((0.0..=1.0).contains(&total), "total score out of range: {}", total); + + let grade = ScoreGrade::from_score(total); + + ContractScore { + spec_depth, + falsification_coverage, + invariant_health, + subcontracting, + traceability, + total, + grade, + } +} + +/// Spec depth: ratio of defined clauses to expected minimum. +/// +/// A well-specified contract has at least 3 require, 5 ensure, 3 invariant clauses. +fn compute_spec_depth(contract: &WorkContract) -> f64 { + if !contract.is_dbc() { + // v4.0 flat contracts: score based on claim count vs expected 22 + let ratio = contract.claims.len() as f64 / 22.0; + return ratio.min(1.0); + } + + let require_score = (contract.require.len() as f64 / 3.0).min(1.0); + let ensure_score = (contract.ensure.len() as f64 / 5.0).min(1.0); + let invariant_score = (contract.invariant.len() as f64 / 3.0).min(1.0); + + (require_score + ensure_score + invariant_score) / 3.0 +} + +/// Falsification coverage: fraction of claims with verification results. +fn compute_falsification_coverage(contract: &WorkContract) -> f64 { + if contract.claims.is_empty() { + return 0.0; + } + + let verified = contract + .claims + .iter() + .filter(|c| c.result.is_some()) + .count(); + verified as f64 / contract.claims.len() as f64 +} + +/// Invariant health: pass rate across checkpoint history. +/// +/// Loads all checkpoints and computes the fraction of invariants that passed. +fn compute_invariant_health(contract: &WorkContract, project_path: &Path) -> f64 { + debug_assert!(project_path.exists(), "project_path must exist: {}", project_path.display()); + let checkpoints = CheckpointRecord::load_all(project_path, &contract.work_item_id); + if checkpoints.is_empty() { + // No checkpoints yet — neutral score (not penalized) + return if contract.invariant.is_empty() { + 1.0 + } else { + 0.5 + }; + } + + let total_invariants: usize = checkpoints + .iter() + .map(|cp| cp.invariant_results.len()) + .sum(); + if total_invariants == 0 { + return 1.0; + } + + let passed: usize = checkpoints + .iter() + .flat_map(|cp| &cp.invariant_results) + .filter(|r| r.passed) + .count(); + + passed as f64 / total_invariants as f64 +} + +/// Subcontracting score: 1.0 if postconditions are monotonically non-weakening. +fn compute_subcontracting_score(contract: &WorkContract) -> f64 { + if contract.iteration <= 1 || contract.inherited_postconditions.is_empty() { + return 1.0; // First iteration or no inherited — full score + } + + match validate_subcontracting(&contract.inherited_postconditions, &contract.ensure) { + Ok(()) => 1.0, + Err(_) => 0.0, + } +} + +/// Traceability: coverage of the require/ensure/invariant triad. +/// +/// Full traceability = all three triad legs are non-empty. +fn compute_traceability(contract: &WorkContract) -> f64 { + if !contract.is_dbc() { + // v4.0: traceability is binary — claims exist or not + return if contract.claims.is_empty() { 0.0 } else { 0.8 }; + } + + let mut score: f64 = 0.0; + if !contract.require.is_empty() { + score += 1.0 / 3.0; + } + if !contract.ensure.is_empty() { + score += 1.0 / 3.0; + } + if !contract.invariant.is_empty() { + score += 1.0 / 3.0; + } + + // Bonus for exclusion transparency (up to 1.0 total) + if !contract.excluded_claims.is_empty() && score > 0.0 { + score = (score + 0.05).min(1.0); + } + + score +} + +// === Drift Detection (DBC spec §13.5, §14.3) === + +/// Drift metrics for a contract — measures staleness and divergence. +/// +/// Based on ABC drift bounds theorem (arXiv:2602.22302): +/// D* = alpha / gamma, where alpha = drift rate, gamma = recovery rate. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DriftMetrics { + /// Hours since last checkpoint + pub hours_since_checkpoint: f64, + /// Hours since contract creation + pub hours_since_creation: f64, + /// Estimated drift rate alpha (0.0..1.0) + pub drift_rate: f64, + /// Recovery rate gamma from checkpoint frequency + pub recovery_rate: f64, + /// Bounded drift D* = alpha / gamma (lower is better) + pub bounded_drift: f64, + /// Whether the contract is considered stale + pub is_stale: bool, +} + +/// Compute drift metrics for a contract. +/// +/// Staleness threshold: 24 hours without a checkpoint. +#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] +pub fn compute_drift_metrics(contract: &WorkContract, project_path: &Path) -> DriftMetrics { + debug_assert!(project_path.exists(), "project_path must exist: {}", project_path.display()); + let now = chrono::Utc::now(); + let hours_since_creation = (now - contract.created_at).num_minutes() as f64 / 60.0; + + let checkpoints = CheckpointRecord::load_all(project_path, &contract.work_item_id); + let hours_since_checkpoint = if let Some(last) = checkpoints.last() { + (now - last.timestamp).num_minutes() as f64 / 60.0 + } else { + hours_since_creation + }; + + // Drift rate: increases with time since last checkpoint + // alpha = min(1.0, hours_since_checkpoint / 48.0) + let drift_rate = (hours_since_checkpoint / 48.0).min(1.0); + + // Recovery rate: based on checkpoint frequency + // gamma = checkpoints_per_24h (capped at 1.0) + let recovery_rate = if hours_since_creation > 0.0 { + let cps_per_day = (checkpoints.len() as f64 / hours_since_creation) * 24.0; + cps_per_day.min(1.0).max(0.01) // floor at 0.01 to avoid div-by-zero + } else { + 0.01 + }; + + // ABC theorem: D* = alpha / gamma + let bounded_drift = (drift_rate / recovery_rate).min(1.0); + let is_stale = hours_since_checkpoint > 24.0; + + DriftMetrics { + hours_since_checkpoint, + hours_since_creation, + drift_rate, + recovery_rate, + bounded_drift, + is_stale, + } +} + +// === Trend Tracking (DBC spec §13.6) === + +/// Point-in-time quality snapshot for trend tracking. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QualityTrendSnapshot { + /// Timestamp of the snapshot + pub timestamp: chrono::DateTime, + /// Contract score at this point + pub score: f64, + /// Grade at this point + pub grade: ScoreGrade, + /// Number of active claims + pub active_claims: usize, + /// Number of verified claims + pub verified_claims: usize, + /// Drift bound at this point + pub bounded_drift: f64, + /// Git SHA at snapshot time + pub git_sha: String, +} + +/// Trend analysis result with drift detection. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QualityTrend { + /// All snapshots, sorted chronologically + pub snapshots: Vec, + /// Rolling average score (7-snapshot window) + pub rolling_average: f64, + /// Score delta from rolling average + pub delta_from_average: f64, + /// Whether drift is detected (>5% drop from rolling average) + pub drift_detected: bool, + /// Trend direction + pub direction: TrendDirection, +} + +/// Trend direction indicator +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum TrendDirection { + Improving, + Stable, + Declining, +} + +impl std::fmt::Display for TrendDirection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TrendDirection::Improving => write!(f, "improving"), + TrendDirection::Stable => write!(f, "stable"), + TrendDirection::Declining => write!(f, "declining"), + } + } +} + +/// Record a quality trend snapshot to .pmat-work/{item-id}/trend/ +#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] +pub fn record_trend_snapshot( + contract: &WorkContract, + score: &ContractScore, + drift: &DriftMetrics, + git_sha: &str, + project_path: &Path, +) -> Result { + debug_assert!(project_path.exists(), "project_path must exist: {}", project_path.display()); + let verified = contract + .claims + .iter() + .filter(|c| c.result.is_some()) + .count(); + + let snapshot = QualityTrendSnapshot { + timestamp: chrono::Utc::now(), + score: score.total, + grade: score.grade, + active_claims: contract.claims.len(), + verified_claims: verified, + bounded_drift: drift.bounded_drift, + git_sha: git_sha.to_string(), + }; + + let trend_dir = project_path + .join(".pmat-work") + .join(&contract.work_item_id) + .join("trend"); + std::fs::create_dir_all(&trend_dir)?; + + let filename = format!( + "snapshot-{}.json", + snapshot.timestamp.format("%Y%m%dT%H%M%S") + ); + let path = trend_dir.join(filename); + let json = serde_json::to_string_pretty(&snapshot)?; + std::fs::write(&path, json)?; + + Ok(path) +} + +/// Load and analyze quality trend for a work item. +/// +/// Uses a 7-snapshot rolling window. Drift is detected when the current +/// score drops more than 5% below the rolling average. +#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] +pub fn load_quality_trend(project_path: &Path, work_item_id: &str) -> QualityTrend { + debug_assert!(project_path.exists(), "project_path must exist: {}", project_path.display()); + let trend_dir = project_path + .join(".pmat-work") + .join(work_item_id) + .join("trend"); + + let mut snapshots = Vec::new(); + if let Ok(entries) = std::fs::read_dir(&trend_dir) { + for entry in entries.flatten() { + if let Ok(content) = std::fs::read_to_string(entry.path()) { + if let Ok(snap) = serde_json::from_str::(&content) { + snapshots.push(snap); + } + } + } + } + snapshots.sort_by_key(|s| s.timestamp); + + analyze_trend(snapshots) +} + +/// Analyze a list of snapshots into a QualityTrend. +fn analyze_trend(snapshots: Vec) -> QualityTrend { + if snapshots.is_empty() { + return QualityTrend { + snapshots, + rolling_average: 0.0, + delta_from_average: 0.0, + drift_detected: false, + direction: TrendDirection::Stable, + }; + } + + // Rolling average over last 7 snapshots + let window_size = 7.min(snapshots.len()); + let window_start = snapshots.len() - window_size; + let rolling_sum: f64 = snapshots[window_start..].iter().map(|s| s.score).sum(); + let rolling_average = rolling_sum / window_size as f64; + + let current_score = snapshots.last().map(|s| s.score).unwrap_or(0.0); + let delta_from_average = current_score - rolling_average; + + // Drift: >5% drop from rolling average + let drift_detected = delta_from_average < -0.05; + + // Direction based on last 2 snapshots + let direction = if snapshots.len() < 2 { + TrendDirection::Stable + } else { + let prev = snapshots[snapshots.len() - 2].score; + let diff = current_score - prev; + if diff > 0.02 { + TrendDirection::Improving + } else if diff < -0.02 { + TrendDirection::Declining + } else { + TrendDirection::Stable + } + }; + + QualityTrend { + snapshots, + rolling_average, + delta_from_average, + drift_detected, + direction, + } +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/file_health_types.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/file_health_types.rs.txt new file mode 100644 index 0000000000..ea6a340e34 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/file_health_types.rs.txt @@ -0,0 +1,240 @@ +/// Thresholds for file size classification +pub mod thresholds { + pub const IDEAL_MAX: usize = 200; + pub const ACCEPTABLE_MAX: usize = 500; + pub const WARNING_MAX: usize = 1000; + pub const PROBLEM_MAX: usize = 2000; + // >2000 is CRITICAL +} + +/// File size classification based on line count +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum FileSizeClass { + /// 0-200 lines: Optimal cognitive chunk + Ideal, + /// 201-500 lines: Within SRP tolerance + Acceptable, + /// 501-1000 lines: Approaching limit + Warning, + /// 1001-2000 lines: Exceeds cognitive capacity + Problem, + /// >2000 lines: Untestable monolith + Critical, +} + +impl FileSizeClass { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// From lines. + pub fn from_lines(lines: usize) -> Self { + match lines { + 0..=200 => Self::Ideal, + 201..=500 => Self::Acceptable, + 501..=1000 => Self::Warning, + 1001..=2000 => Self::Problem, + _ => Self::Critical, + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// As str. + pub fn as_str(&self) -> &'static str { + match self { + Self::Ideal => "ideal", + Self::Acceptable => "acceptable", + Self::Warning => "warning", + Self::Problem => "problem", + Self::Critical => "critical", + } + } +} + +/// Health grade based on composite score +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum HealthGrade { + A, // 90-100 + B, // 80-89 + C, // 70-79 + D, // 60-69 + E, // 50-59 + F, // 0-49 +} + +impl HealthGrade { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + /// From score. + pub fn from_score(score: u8) -> Self { + match score { + 90..=100 => Self::A, + 80..=89 => Self::B, + 70..=79 => Self::C, + 60..=69 => Self::D, + 50..=59 => Self::E, + _ => Self::F, + } + } + + // ── Kani proof harnesses (GH-276) ──────────────────────────────────── + // See kani/README.md for instructions on running these proofs. + // Total proof effort: verify `from_score` is a total function on u8 + // and respects monotonic grade ordering. + + /// Kani proof: `from_score` is total on all u8 inputs (0..=255). + /// i.e. it never panics and always returns a defined variant. + #[cfg(kani)] + #[kani::proof] + fn kani_from_score_total() { + let score: u8 = kani::any(); + let g = HealthGrade::from_score(score); + // Every u8 maps to exactly one of the six variants. + let _ok = matches!( + g, + HealthGrade::A + | HealthGrade::B + | HealthGrade::C + | HealthGrade::D + | HealthGrade::E + | HealthGrade::F + ); + assert!(_ok); + } + + /// Kani proof: passing grades correspond to score >= 70. + /// `is_passing()` <=> grade is A, B, or C <=> score >= 70. + #[cfg(kani)] + #[kani::proof] + fn kani_is_passing_iff_score_ge_70() { + let score: u8 = kani::any(); + // Bound to the documented range so proofs are crisp. + kani::assume(score <= 100); + let g = HealthGrade::from_score(score); + assert!(g.is_passing() == (score >= 70)); + } + + /// Kani proof: grade ordering is monotonic (higher score -> not-worse grade). + /// Specifically, a score in the A band (>=90) is never classified below C. + #[cfg(kani)] + #[kani::proof] + fn kani_high_score_yields_high_grade() { + let score: u8 = kani::any(); + kani::assume(score >= 90 && score <= 100); + let g = HealthGrade::from_score(score); + assert!(matches!(g, HealthGrade::A)); + assert!(g.is_passing()); + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// As str. + pub fn as_str(&self) -> &'static str { + match self { + Self::A => "A", + Self::B => "B", + Self::C => "C", + Self::D => "D", + Self::E => "E", + Self::F => "F", + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Is passing. + pub fn is_passing(&self) -> bool { + matches!(self, Self::A | Self::B | Self::C) + } +} + +/// Health metrics for a single file +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileHealthMetrics { + pub path: PathBuf, + pub lines: usize, + pub test_lines: usize, + pub tlr: f32, + pub required_tlr: f32, + pub avg_complexity: f32, + pub churn_30d: usize, + pub health_score: u8, + pub grade: HealthGrade, + pub size_class: FileSizeClass, +} + +impl FileHealthMetrics { + /// Calculate health score using the composite formula + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] + pub fn calculate( + path: PathBuf, + lines: usize, + test_lines: usize, + avg_complexity: f32, + churn_30d: usize, + ) -> Self { + let required_tlr = Self::required_tlr_for_size(lines); + let tlr = if lines > 0 { + test_lines as f32 / lines as f32 + } else { + 1.0 + }; + + // Size Score (30 points max) + let size_score: u8 = match lines { + 0..=200 => 30, + 201..=500 => 25, + 501..=1000 => 15, + 1001..=2000 => 5, + _ => 0, + }; + + // TLR Score (40 points max) + let tlr_ratio = (tlr / required_tlr).min(1.0); + let tlr_score = (tlr_ratio * 40.0) as u8; + + // Complexity Score (20 points max) + let complexity_score: u8 = if avg_complexity <= 5.0 { + 20 + } else if avg_complexity <= 10.0 { + 15 + } else if avg_complexity <= 15.0 { + 10 + } else if avg_complexity <= 20.0 { + 5 + } else { + 0 + }; + + // Stability Score (10 points max) + let stability_score: u8 = match churn_30d { + 0..=2 => 10, + 3..=5 => 7, + 6..=10 => 4, + _ => 0, + }; + + let health_score = size_score + tlr_score + complexity_score + stability_score; + let grade = HealthGrade::from_score(health_score); + let size_class = FileSizeClass::from_lines(lines); + + Self { + path, + lines, + test_lines, + tlr, + required_tlr, + avg_complexity, + churn_30d, + health_score, + grade, + size_class, + } + } + + /// Get required TLR based on file size (scaling thresholds) + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn required_tlr_for_size(lines: usize) -> f32 { + match lines { + 0..=100 => 0.3, + 101..=300 => 0.5, + 301..=500 => 0.7, + 501..=1000 => 1.0, + _ => 1.5, + } + } +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/infra_score/models.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/infra_score/models.rs.txt new file mode 100644 index 0000000000..1c9678e1e3 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/infra_score/models.rs.txt @@ -0,0 +1,549 @@ +#![cfg_attr(coverage_nightly, coverage(off))] +//! Data models for pmat infra-score +//! Implements the scoring system defined in docs/specifications/components/infra-score.md +//! +//! 5 dimensions, 100 points total. Hard cutoff: <90 = auto-fail. + +use crate::services::normalized_score::NormalizedScore; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::path::PathBuf; + +/// Maximum possible raw points for Infra Score (no bonuses — strict 100) +pub const INFRA_SCORE_MAX_POINTS: f64 = 100.0; + +/// Maximum bonus points from the Provable Contracts category. +/// +/// This MUST equal the sum of the PV-01..PV-05 check weights (3+3+2+2+2), which +/// is what `ProvableContractsScorer` actually awards. Three places used to carry +/// their own copy of this number — the scorer said 12, the model default said 10 +/// and the text renderer hardcoded a "/110.0" denominator — so a perfect run +/// printed a bonus larger than the maximum the same output advertised. +pub const INFRA_SCORE_BONUS_MAX_POINTS: f64 = 12.0; + +/// Overall infrastructure score result +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InfraScore { + /// Raw score (0-100) + pub total_score: f64, + pub grade: InfraGrade, + pub auto_fail: bool, + pub categories: InfraCategoryScores, + pub recommendations: Vec, + pub metadata: InfraScoreMetadata, +} + +/// Infra-specific grade with hard cutoff semantics +/// A+ (95-100), A (90-94), B (80-89) AUTO-FAIL, C (60-79) AUTO-FAIL, D (40-59) AUTO-FAIL, F (0-39) AUTO-FAIL +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum InfraGrade { + APlus, // 95-100 + A, // 90-94 + B, // 80-89 — AUTO-FAIL + C, // 60-79 — AUTO-FAIL + D, // 40-59 — AUTO-FAIL + F, // 0-39 — AUTO-FAIL +} + +/// Category scores (100 points total) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InfraCategoryScores { + pub workflow_architecture: InfraCategoryScore, // 25 points + pub build_reliability: InfraCategoryScore, // 25 points + pub quality_pipeline: InfraCategoryScore, // 20 points + pub deployment_release: InfraCategoryScore, // 15 points + pub supply_chain: InfraCategoryScore, // 15 points + pub provable_contracts: InfraCategoryScore, // 12 points (bonus) +} + +/// Individual category score (mirrors repo_score CategoryScore) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InfraCategoryScore { + pub score: f64, + pub max_score: f64, + pub percentage: f64, + pub checks: Vec, + pub findings: Vec, +} + +/// Individual check result (e.g., WA-01, BR-03) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InfraCheck { + pub id: String, // "WA-01", "BR-03", etc. + pub name: String, // Human-readable name + pub score: f64, // Earned points + pub max_score: f64, // Maximum possible + pub passed: bool, + pub evidence: Vec, +} + +/// Finding with severity (mirrors repo_score Finding) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InfraFinding { + pub severity: InfraSeverity, + pub check_id: String, + pub message: String, + pub location: Option, + pub impact_points: f64, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +/// Severity level classification for infra. +pub enum InfraSeverity { + Pass, // Check passed + Warning, // Partial compliance + Fail, // Check failed + Info, // Informational +} + +/// Recommendation for improvement +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InfraRecommendation { + pub priority: InfraPriority, + pub check_id: String, + pub title: String, + pub description: String, + pub impact_points: f64, + pub estimated_effort: String, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +/// Priority level for infra. +pub enum InfraPriority { + Low = 1, + Medium = 2, + High = 3, + Critical = 4, +} + +/// Metadata about the scoring run +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InfraScoreMetadata { + pub timestamp: String, + pub repository_path: PathBuf, + pub git_branch: Option, + pub git_commit: Option, + pub pmat_version: String, + pub spec_version: String, + pub execution_time_ms: u64, +} + +// --- impl blocks --- + +impl NormalizedScore for InfraScore { + fn raw(&self) -> f64 { + self.total_score + } + + fn max_raw(&self) -> f64 { + INFRA_SCORE_MAX_POINTS + } +} + +impl fmt::Display for InfraScore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let fail_marker = if self.auto_fail { " [AUTO-FAIL]" } else { "" }; + write!( + f, + "Infra Score: {:.1}/100 ({}){}", + self.total_score, + self.grade.as_str(), + fail_marker, + ) + } +} + +impl InfraGrade { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + /// From score. + pub fn from_score(score: f64) -> Self { + match score { + s if s >= 95.0 => InfraGrade::APlus, + s if s >= 90.0 => InfraGrade::A, + s if s >= 80.0 => InfraGrade::B, + s if s >= 60.0 => InfraGrade::C, + s if s >= 40.0 => InfraGrade::D, + _ => InfraGrade::F, + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// As str. + pub fn as_str(&self) -> &'static str { + match self { + InfraGrade::APlus => "A+", + InfraGrade::A => "A", + InfraGrade::B => "B", + InfraGrade::C => "C", + InfraGrade::D => "D", + InfraGrade::F => "F", + } + } + + /// Returns true if this grade is an auto-fail (<90) + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn is_auto_fail(&self) -> bool { + !matches!(self, InfraGrade::APlus | InfraGrade::A) + } +} + +// ── Kani proof harnesses (GH-276) ──────────────────────────────────────────── +// Prove the auto-fail cutoff semantics that the specification documents: +// scores < 90 MUST auto-fail; scores >= 90 MUST NOT auto-fail. +#[cfg(kani)] +mod kani_proofs { + use super::InfraGrade; + + /// Any finite score < 90 must classify as an auto-fail grade. + /// This is the central safety property of the infra-score spec. + #[kani::proof] + fn auto_fail_iff_below_90() { + let s: f64 = kani::any(); + kani::assume(s.is_finite()); + kani::assume((-1000.0..=1000.0).contains(&s)); + let g = InfraGrade::from_score(s); + // s < 90 => is_auto_fail + // s >= 90 => !is_auto_fail + assert!(g.is_auto_fail() == (s < 90.0)); + } + + /// `from_score` is total on bounded finite inputs. + #[kani::proof] + fn from_score_total() { + let s: f64 = kani::any(); + kani::assume(s.is_finite()); + kani::assume((-1000.0..=1000.0).contains(&s)); + let g = InfraGrade::from_score(s); + let _ok = matches!( + g, + InfraGrade::APlus + | InfraGrade::A + | InfraGrade::B + | InfraGrade::C + | InfraGrade::D + | InfraGrade::F + ); + assert!(_ok); + } +} + +impl InfraCategoryScores { + /// Total base score (100 points max, excluding bonus) + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total(&self) -> f64 { + self.workflow_architecture.score + + self.build_reliability.score + + self.quality_pipeline.score + + self.deployment_release.score + + self.supply_chain.score + } + + /// Total including provable contracts bonus + /// (`INFRA_SCORE_MAX_POINTS` + `INFRA_SCORE_BONUS_MAX_POINTS` max) + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total_with_bonus(&self) -> f64 { + self.total() + self.provable_contracts.score + } +} + +impl Default for InfraCategoryScores { + fn default() -> Self { + Self { + workflow_architecture: InfraCategoryScore::empty(25.0), + build_reliability: InfraCategoryScore::empty(25.0), + quality_pipeline: InfraCategoryScore::empty(20.0), + deployment_release: InfraCategoryScore::empty(15.0), + supply_chain: InfraCategoryScore::empty(15.0), + provable_contracts: InfraCategoryScore::empty(INFRA_SCORE_BONUS_MAX_POINTS), + } + } +} + +impl InfraCategoryScore { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Empty. + pub fn empty(max_score: f64) -> Self { + Self { + score: 0.0, + max_score, + percentage: 0.0, + checks: vec![], + findings: vec![], + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Create a new instance. + pub fn new(max_score: f64, checks: Vec, findings: Vec) -> Self { + let score: f64 = checks.iter().map(|c| c.score).sum(); + let percentage = if max_score > 0.0 { + (score / max_score) * 100.0 + } else { + 0.0 + }; + + Self { + score, + max_score, + percentage, + checks, + findings, + } + } +} + +impl InfraCheck { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Pass. + pub fn pass(id: &str, name: &str, max_score: f64, evidence: Vec) -> Self { + Self { + id: id.to_string(), + name: name.to_string(), + score: max_score, + max_score, + passed: true, + evidence, + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Fail. + pub fn fail(id: &str, name: &str, max_score: f64, evidence: Vec) -> Self { + Self { + id: id.to_string(), + name: name.to_string(), + score: 0.0, + max_score, + passed: false, + evidence, + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Partial. + pub fn partial( + id: &str, + name: &str, + score: f64, + max_score: f64, + evidence: Vec, + ) -> Self { + Self { + id: id.to_string(), + name: name.to_string(), + score, + max_score, + passed: score >= max_score, + evidence, + } + } +} + +impl InfraScoreMetadata { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] + /// Create a new instance. + pub fn new(repository_path: PathBuf) -> Self { + Self { + timestamp: chrono::Utc::now().to_rfc3339(), + repository_path, + git_branch: None, + git_commit: None, + pmat_version: env!("CARGO_PKG_VERSION").to_string(), + spec_version: "0.1.0".to_string(), + execution_time_ms: 0, + } + } +} + +#[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_infra_grade_from_score() { + assert_eq!(InfraGrade::from_score(100.0), InfraGrade::APlus); + assert_eq!(InfraGrade::from_score(95.0), InfraGrade::APlus); + assert_eq!(InfraGrade::from_score(94.0), InfraGrade::A); + assert_eq!(InfraGrade::from_score(90.0), InfraGrade::A); + assert_eq!(InfraGrade::from_score(89.0), InfraGrade::B); + assert_eq!(InfraGrade::from_score(80.0), InfraGrade::B); + assert_eq!(InfraGrade::from_score(79.0), InfraGrade::C); + assert_eq!(InfraGrade::from_score(60.0), InfraGrade::C); + assert_eq!(InfraGrade::from_score(59.0), InfraGrade::D); + assert_eq!(InfraGrade::from_score(40.0), InfraGrade::D); + assert_eq!(InfraGrade::from_score(39.0), InfraGrade::F); + assert_eq!(InfraGrade::from_score(0.0), InfraGrade::F); + } + + #[test] + fn test_infra_grade_auto_fail() { + assert!(!InfraGrade::APlus.is_auto_fail()); + assert!(!InfraGrade::A.is_auto_fail()); + assert!(InfraGrade::B.is_auto_fail()); + assert!(InfraGrade::C.is_auto_fail()); + assert!(InfraGrade::D.is_auto_fail()); + assert!(InfraGrade::F.is_auto_fail()); + } + + #[test] + fn test_infra_grade_as_str() { + assert_eq!(InfraGrade::APlus.as_str(), "A+"); + assert_eq!(InfraGrade::A.as_str(), "A"); + assert_eq!(InfraGrade::B.as_str(), "B"); + assert_eq!(InfraGrade::C.as_str(), "C"); + assert_eq!(InfraGrade::D.as_str(), "D"); + assert_eq!(InfraGrade::F.as_str(), "F"); + } + + #[test] + fn test_infra_category_scores_total() { + let scores = InfraCategoryScores { + workflow_architecture: InfraCategoryScore { + score: 20.0, + max_score: 25.0, + percentage: 80.0, + checks: vec![], + findings: vec![], + }, + build_reliability: InfraCategoryScore { + score: 22.0, + max_score: 25.0, + percentage: 88.0, + checks: vec![], + findings: vec![], + }, + quality_pipeline: InfraCategoryScore { + score: 18.0, + max_score: 20.0, + percentage: 90.0, + checks: vec![], + findings: vec![], + }, + deployment_release: InfraCategoryScore { + score: 12.0, + max_score: 15.0, + percentage: 80.0, + checks: vec![], + findings: vec![], + }, + supply_chain: InfraCategoryScore { + score: 10.0, + max_score: 15.0, + percentage: 66.7, + checks: vec![], + findings: vec![], + }, + provable_contracts: InfraCategoryScore { + score: 5.0, + max_score: 10.0, + percentage: 50.0, + checks: vec![], + findings: vec![], + }, + }; + assert!((scores.total() - 82.0).abs() < f64::EPSILON); + } + + #[test] + fn test_infra_check_pass() { + let check = InfraCheck::pass( + "WA-01", + "Reusable workflow", + 5.0, + vec!["found uses: org/.github".to_string()], + ); + assert!(check.passed); + assert!((check.score - 5.0).abs() < f64::EPSILON); + } + + #[test] + fn test_infra_check_fail() { + let check = InfraCheck::fail( + "WA-01", + "Reusable workflow", + 5.0, + vec!["no reusable workflow found".to_string()], + ); + assert!(!check.passed); + assert!((check.score - 0.0).abs() < f64::EPSILON); + } + + #[test] + fn test_infra_check_partial() { + let check = InfraCheck::partial( + "BR-01", + "CI success rate", + 3.0, + 5.0, + vec!["7/10 passed".to_string()], + ); + assert!(!check.passed); + assert!((check.score - 3.0).abs() < f64::EPSILON); + } + + #[test] + fn test_infra_category_score_new() { + let checks = vec![ + InfraCheck::pass("WA-01", "Reusable", 5.0, vec![]), + InfraCheck::fail("WA-02", "Self-hosted", 5.0, vec![]), + ]; + let cat = InfraCategoryScore::new(25.0, checks, vec![]); + assert!((cat.score - 5.0).abs() < f64::EPSILON); + assert!((cat.percentage - 20.0).abs() < f64::EPSILON); + } + + #[test] + fn test_infra_score_display() { + let score = InfraScore { + total_score: 92.0, + grade: InfraGrade::A, + auto_fail: false, + categories: InfraCategoryScores::default(), + recommendations: vec![], + metadata: InfraScoreMetadata::new(PathBuf::from("/tmp/test")), + }; + let display = format!("{}", score); + assert!(display.contains("92.0")); + assert!(display.contains("A")); + assert!(!display.contains("AUTO-FAIL")); + } + + #[test] + fn test_infra_score_display_auto_fail() { + let score = InfraScore { + total_score: 75.0, + grade: InfraGrade::C, + auto_fail: true, + categories: InfraCategoryScores::default(), + recommendations: vec![], + metadata: InfraScoreMetadata::new(PathBuf::from("/tmp/test")), + }; + let display = format!("{}", score); + assert!(display.contains("AUTO-FAIL")); + } + + #[test] + fn test_normalized_score_trait() { + let score = InfraScore { + total_score: 85.0, + grade: InfraGrade::B, + auto_fail: true, + categories: InfraCategoryScores::default(), + recommendations: vec![], + metadata: InfraScoreMetadata::new(PathBuf::from("/tmp/test")), + }; + assert!((score.raw() - 85.0).abs() < f64::EPSILON); + assert!((score.max_raw() - 100.0).abs() < f64::EPSILON); + assert!((score.normalized() - 85.0).abs() < f64::EPSILON); + } + + #[test] + fn test_metadata_new() { + let meta = InfraScoreMetadata::new(PathBuf::from("/tmp/repo")); + assert_eq!(meta.repository_path, PathBuf::from("/tmp/repo")); + assert_eq!(meta.spec_version, "0.1.0"); + assert!(meta.git_branch.is_none()); + assert!(meta.git_commit.is_none()); + } +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/normalized_score.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/normalized_score.rs.txt new file mode 100644 index 0000000000..0c95396b5a --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/normalized_score.rs.txt @@ -0,0 +1,234 @@ +#![cfg_attr(coverage_nightly, coverage(off))] +//! Normalized Score System (PMAT-454) +//! +//! All PMAT scoring systems MUST output values in the 0-100 range. +//! This module provides the trait and utilities to ensure consistent scoring. +//! +//! # Design Principles +//! - All scores are normalized to 0.0-100.0 range +//! - Raw scores can use any internal scale (106, 110, 200 points) +//! - `normalized()` method always returns 0-100 +//! - Clamping ensures no out-of-range values + +use std::fmt; + +/// Trait for all scoring systems in PMAT. +/// +/// Implementors MUST ensure `normalized()` returns values in [0.0, 100.0]. +pub trait NormalizedScore: fmt::Display { + /// Returns the raw score value (internal scale). + fn raw(&self) -> f64; + + /// Returns the maximum possible raw score. + fn max_raw(&self) -> f64; + + /// Returns the normalized score in 0-100 range. + /// + /// # Guarantees + /// - Always returns a value in [0.0, 100.0] + /// - Values are clamped if raw calculation exceeds bounds + fn normalized(&self) -> f64 { + let max = self.max_raw(); + if max <= 0.0 { + return 0.0; + } + let normalized = (self.raw() / max) * 100.0; + normalized.clamp(0.0, 100.0) + } + + /// Returns the letter grade based on normalized score. + fn grade(&self) -> Grade { + Grade::from_score(self.normalized()) + } + + /// Returns true if score meets the given threshold (0-100). + fn meets_threshold(&self, threshold: f64) -> bool { + self.normalized() >= threshold.clamp(0.0, 100.0) + } +} + +/// Universal letter grades for all scoring systems. +/// Ordering: A > B > C > D > F (higher grade = better) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Grade { + /// 90-100: Excellent + A, + /// 80-89: Good + B, + /// 70-79: Satisfactory + C, + /// 60-69: Needs Improvement + D, + /// 0-59: Failing + F, +} + +impl PartialOrd for Grade { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Grade { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.min_score() + .partial_cmp(&other.min_score()) + .unwrap_or(std::cmp::Ordering::Equal) + } +} + +impl Grade { + /// Convert a normalized score (0-100) to a grade. + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + pub fn from_score(score: f64) -> Self { + match score { + s if s >= 90.0 => Grade::A, + s if s >= 80.0 => Grade::B, + s if s >= 70.0 => Grade::C, + s if s >= 60.0 => Grade::D, + _ => Grade::F, + } + } + + /// Returns the minimum score for this grade. + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + pub fn min_score(&self) -> f64 { + match self { + Grade::A => 90.0, + Grade::B => 80.0, + Grade::C => 70.0, + Grade::D => 60.0, + Grade::F => 0.0, + } + } + + /// Returns the grade as a string with description. + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn description(&self) -> &'static str { + match self { + Grade::A => "A (Excellent)", + Grade::B => "B (Good)", + Grade::C => "C (Satisfactory)", + Grade::D => "D (Needs Improvement)", + Grade::F => "F (Failing)", + } + } +} + +// ── Kani proof harnesses (GH-276) ──────────────────────────────────────────── +// These prove pure arithmetic invariants about the grade classifier. +// See kani/README.md for how to run. +#[cfg(kani)] +mod kani_proofs { + use super::Grade; + + /// `from_score` is total on a bounded f64 input and never panics. + /// We bound the input to a "reasonable" range since Kani reasons poorly + /// about NaN/Inf and we only care about realistic scores here. + #[kani::proof] + fn from_score_total_in_bounds() { + let s: f64 = kani::any(); + // Exclude NaN/Inf explicitly — the contract is about finite scores. + kani::assume(s.is_finite()); + kani::assume((-1000.0..=1000.0).contains(&s)); + let g = Grade::from_score(s); + let _ok = matches!(g, Grade::A | Grade::B | Grade::C | Grade::D | Grade::F); + assert!(_ok); + } + + /// `min_score` is a left-inverse of `from_score`'s band structure: + /// for any grade G, `from_score(G.min_score())` returns G or better. + /// (We can't prove full inverse since `from_score` is many-to-one.) + /// This proves the band boundaries are internally consistent. + #[kani::proof] + fn min_score_consistent_at_A_boundary() { + let s: f64 = kani::any(); + kani::assume(s.is_finite()); + kani::assume((90.0..=100.0).contains(&s)); + // Any score in [90, 100] must classify as A. + assert!(matches!(Grade::from_score(s), Grade::A)); + } + + /// `min_score` is a left-inverse at the F boundary: any score < 60 is F. + #[kani::proof] + fn below_D_boundary_is_F() { + let s: f64 = kani::any(); + kani::assume(s.is_finite()); + kani::assume((-1000.0..60.0).contains(&s)); + assert!(matches!(Grade::from_score(s), Grade::F)); + } +} + +impl fmt::Display for Grade { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Grade::A => write!(f, "A"), + Grade::B => write!(f, "B"), + Grade::C => write!(f, "C"), + Grade::D => write!(f, "D"), + Grade::F => write!(f, "F"), + } + } +} + +/// Helper struct for creating normalized scores from raw values. +#[derive(Debug, Clone, Copy)] +pub struct SimpleScore { + raw: f64, + max: f64, + name: &'static str, +} + +impl SimpleScore { + /// Create a new simple score. + /// + /// # Panics + /// Panics if max <= 0. + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn new(raw: f64, max: f64, name: &'static str) -> Self { + assert!(max > 0.0, "max must be positive"); + Self { + raw: raw.max(0.0), + max, + name, + } + } + + /// Create from a percentage (0-100). + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn from_percentage(pct: f64, name: &'static str) -> Self { + Self { + raw: pct.clamp(0.0, 100.0), + max: 100.0, + name, + } + } +} + +impl NormalizedScore for SimpleScore { + fn raw(&self) -> f64 { + self.raw + } + + fn max_raw(&self) -> f64 { + self.max + } +} + +impl fmt::Display for SimpleScore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}: {:.1}/100 ({})", + self.name, + self.normalized(), + self.grade() + ) + } +} + +// Aggregate scoring: AggregateScore, NormalizedScoreClone trait +include!("normalized_score_aggregate.rs"); + +// Tests +include!("normalized_score_tests.rs"); diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/repo_score/models_impls.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/repo_score/models_impls.rs.txt new file mode 100644 index 0000000000..4737bc3e06 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/repo_score/models_impls.rs.txt @@ -0,0 +1,216 @@ +// RepoScore: NormalizedScore trait impl +impl NormalizedScore for RepoScore { + fn raw(&self) -> f64 { + self.total_score + } + + fn max_raw(&self) -> f64 { + REPO_SCORE_MAX_POINTS + } +} + +// RepoScore: Display impl +impl fmt::Display for RepoScore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Repo Score: {:.1}/100 ({}) [raw: {:.1}/{}]", + self.normalized(), + self.grade.as_str(), + self.total_score, + REPO_SCORE_MAX_POINTS as u32 + ) + } +} + +// Grade: conversion and formatting methods +impl Grade { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + /// From score. + pub fn from_score(score: f64) -> Self { + match score { + s if s >= 95.0 => Grade::APlus, + s if s >= 90.0 => Grade::A, + s if s >= 85.0 => Grade::AMinus, + s if s >= 80.0 => Grade::BPlus, + s if s >= 70.0 => Grade::B, + s if s >= 60.0 => Grade::C, + s if s >= 50.0 => Grade::D, + _ => Grade::F, + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// As str. + pub fn as_str(&self) -> &'static str { + match self { + Grade::APlus => "A+", + Grade::A => "A", + Grade::AMinus => "A-", + Grade::BPlus => "B+", + Grade::B => "B", + Grade::C => "C", + Grade::D => "D", + Grade::F => "F", + } + } +} + +// CategoryScores: aggregate scoring +impl CategoryScores { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Total. + pub fn total(&self) -> f64 { + self.documentation.score + + self.precommit_hooks.score + + self.repository_hygiene.score + + self.build_test_automation.score + + self.continuous_integration.score + + self.pmat_compliance.score + } +} + +impl Default for CategoryScores { + fn default() -> Self { + Self { + documentation: CategoryScore::default_with_max(15.0), + precommit_hooks: CategoryScore::default_with_max(20.0), + repository_hygiene: CategoryScore::default_with_max(15.0), + build_test_automation: CategoryScore::default_with_max(25.0), + continuous_integration: CategoryScore::default_with_max(20.0), + pmat_compliance: CategoryScore::default_with_max(5.0), + } + } +} + +// CategoryScore: construction and defaults +impl CategoryScore { + fn default_with_max(max_score: f64) -> Self { + Self { + score: 0.0, + max_score, + percentage: 0.0, + status: ScoreStatus::Fail, + subcategories: vec![], + findings: vec![], + } + } + + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Create a new instance. + pub fn new( + score: f64, + max_score: f64, + subcategories: Vec, + findings: Vec, + ) -> Self { + let percentage = if max_score > 0.0 { + (score / max_score) * 100.0 + } else { + 0.0 + }; + + let status = if percentage >= 90.0 { + ScoreStatus::Pass + } else if percentage >= 70.0 { + ScoreStatus::Warning + } else { + ScoreStatus::Fail + }; + + Self { + score, + max_score, + percentage, + status, + subcategories, + findings, + } + } +} + +// BonusScores: aggregate and defaults +impl BonusScores { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + /// Total. + pub fn total(&self) -> f64 { + self.property_tests.points + + self.fuzzing.points + + self.mutation_testing.points + + self.living_docs.points + } +} + +impl Default for BonusScores { + fn default() -> Self { + Self { + property_tests: BonusItem { + points: 0.0, + max_points: 3.0, + detected: false, + evidence: vec![], + }, + fuzzing: BonusItem { + points: 0.0, + max_points: 2.0, + detected: false, + evidence: vec![], + }, + mutation_testing: BonusItem { + points: 0.0, + max_points: 2.0, + detected: false, + evidence: vec![], + }, + living_docs: BonusItem { + points: 0.0, + max_points: 3.0, + detected: false, + evidence: vec![], + }, + } + } +} + +// Priority: manual PartialOrd/Ord for correct ordering +// Critical > High > Medium > Low +impl PartialOrd for Priority { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Priority { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let self_rank = match self { + Priority::Critical => 4, + Priority::High => 3, + Priority::Medium => 2, + Priority::Low => 1, + }; + let other_rank = match other { + Priority::Critical => 4, + Priority::High => 3, + Priority::Medium => 2, + Priority::Low => 1, + }; + self_rank.cmp(&other_rank) + } +} + +// ScoreMetadata: construction +impl ScoreMetadata { + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")] + /// Create a new instance. + pub fn new(repository_path: PathBuf) -> Self { + Self { + timestamp: chrono::Utc::now().to_rfc3339(), + repository_path, + git_branch: None, + git_commit: None, + pmat_version: env!("CARGO_PKG_VERSION").to_string(), + spec_version: "1.0.0".to_string(), + execution_time_ms: 0, + } + } +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/rust_project_score/models_score.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/rust_project_score/models_score.rs.txt new file mode 100644 index 0000000000..4f761b2ff5 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/services/rust_project_score/models_score.rs.txt @@ -0,0 +1,155 @@ +// ============================================================================ +// RustProjectScore - Main Score Container +// ============================================================================ + +/// Comprehensive Rust project quality score (v1.1) +/// +/// Total score: 0-106 points across 6 categories +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RustProjectScore { + /// Total score (0-106 points) + pub total_score: f64, + + /// Letter grade (A+ to F) + pub grade: Grade, + + /// Breakdown by category + pub categories: CategoryScores, + + /// Actionable recommendations + pub recommendations: Vec, + + /// Metadata (timestamp, project, version) + pub metadata: ScoreMetadata, + + /// Score velocity (Kaizen tracking) - NEW in v1.1 + pub velocity: Option, +} + +impl RustProjectScore { + /// Create a new score with zero values + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn new() -> Self { + Self { + total_score: 0.0, + grade: Grade::F, + categories: CategoryScores::default(), + recommendations: Vec::new(), + metadata: ScoreMetadata::new("unknown".to_string(), "1.1.0".to_string()), + velocity: None, + } + } +} + +impl Default for RustProjectScore { + fn default() -> Self { + Self::new() + } +} + +impl NormalizedScore for RustProjectScore { + fn raw(&self) -> f64 { + self.total_score + } + + fn max_raw(&self) -> f64 { + RUST_PROJECT_MAX_POINTS + } +} + +impl fmt::Display for RustProjectScore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Rust Project Score: {:.1}/100 ({}) [raw: {:.1}/{}]", + self.normalized(), + self.grade, + self.total_score, + RUST_PROJECT_MAX_POINTS as u32 + ) + } +} + +// ============================================================================ +// Grade - Letter Grade Enum +// ============================================================================ + +/// Letter grade based on NORMALIZED percentage (0-100 scale) +/// +/// PMAT-454: All grading now uses normalized 0-100 percentages +/// +/// Thresholds (normalized 0-100): +/// - A+ : 95-100% +/// - A : 90-94% +/// - A- : 85-89% +/// - B+ : 80-84% +/// - B : 70-79% +/// - C : 60-69% +/// - D : 50-59% +/// - F : 0-49% +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Grade { + APlus, + A, + AMinus, + BPlus, + B, + C, + D, + F, +} + +impl Grade { + /// Calculate grade from raw score and max possible points + /// + /// PMAT-454: Now properly normalizes to 0-100 before grading + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + pub fn from_score(score: f64, max: f64) -> Self { + // Normalize to 0-100 percentage + let normalized = if max > 0.0 { + (score / max * 100.0).clamp(0.0, 100.0) + } else { + 0.0 + }; + + if normalized >= 95.0 { + Grade::APlus + } else if normalized >= 90.0 { + Grade::A + } else if normalized >= 85.0 { + Grade::AMinus + } else if normalized >= 80.0 { + Grade::BPlus + } else if normalized >= 70.0 { + Grade::B + } else if normalized >= 60.0 { + Grade::C + } else if normalized >= 50.0 { + Grade::D + } else { + Grade::F + } + } + + /// Calculate grade from already-normalized percentage (0-100) + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn from_normalized(normalized: f64) -> Self { + Self::from_score(normalized, 100.0) + } +} + +impl fmt::Display for Grade { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Grade::APlus => write!(f, "A+"), + Grade::A => write!(f, "A"), + Grade::AMinus => write!(f, "A-"), + Grade::BPlus => write!(f, "B+"), + Grade::B => write!(f, "B"), + Grade::C => write!(f, "C"), + Grade::D => write!(f, "D"), + Grade::F => write!(f, "F"), + } + } +} + diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/tdg/cuda_simd_scores.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/tdg/cuda_simd_scores.rs.txt new file mode 100644 index 0000000000..d603d82649 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/tdg/cuda_simd_scores.rs.txt @@ -0,0 +1,293 @@ +//! CUDA-SIMD Score Types +//! +//! Extracted from cuda_simd.rs for file health compliance (CB-040). +//! Contains the 100-point Karl Popper falsification scoring structures. + +use serde::{Deserialize, Serialize}; + +/// Category A: Falsifiability & Testability (25 points) - GATEWAY +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct FalsifiabilityScore { + /// A.1: All bar.sync reachable from all threads (5 pts) + pub barrier_safety: f64, + /// A.2: Shared memory indices within tile dimensions (5 pts) + pub bounds_verification: f64, + /// A.3: Branch coverage includes warp divergence cases (5 pts) + pub divergence_testing: f64, + /// A.4: ThreadSanitizer or equivalent analysis (5 pts) + pub memory_race_detection: f64, + /// A.5: Register/shared memory within SM limits (5 pts) + pub occupancy_bounds: f64, +} + +impl FalsifiabilityScore { + /// Calculate total for Category A + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total(&self) -> f64 { + self.barrier_safety + + self.bounds_verification + + self.divergence_testing + + self.memory_race_detection + + self.occupancy_bounds + } + + /// Maximum possible score for Category A + pub const MAX: f64 = 25.0; + + /// Gateway threshold - if below this, total score is 0 + pub const GATEWAY_THRESHOLD: f64 = 15.0; +} + +/// Category B: Reproducibility Infrastructure (25 points) +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ReproducibilityScore { + /// B.1: Bitwise reproducible results (8 pts) + pub deterministic_output: f64, + /// B.2: CUDA/Driver/SM version locked (5 pts) + pub version_pinning: f64, + /// B.3: GPU model, compute capability documented (5 pts) + pub hardware_specification: f64, + /// B.4: Criterion-style statistical benchmarking (4 pts) + pub benchmark_harness: f64, + /// B.5: Automated regression on GPU hardware (3 pts) + pub ci_cd_integration: f64, +} + +impl ReproducibilityScore { + /// Calculate total for Category B + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total(&self) -> f64 { + self.deterministic_output + + self.version_pinning + + self.hardware_specification + + self.benchmark_harness + + self.ci_cd_integration + } + + /// Maximum possible score for Category B + pub const MAX: f64 = 25.0; +} + +/// Category C: Transparency & Openness (20 points) +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TransparencyScore { + /// C.1: Generated PTX accessible and documented (6 pts) + pub ptx_inspection: f64, + /// C.2: --ptxas-options=-v output analyzed (5 pts) + pub register_allocation: f64, + /// C.3: SM occupancy explicitly computed (5 pts) + pub occupancy_calculation: f64, + /// C.4: Shared memory bank mapping documented (4 pts) + pub memory_layout: f64, +} + +impl TransparencyScore { + /// Calculate total for Category C + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total(&self) -> f64 { + self.ptx_inspection + + self.register_allocation + + self.occupancy_calculation + + self.memory_layout + } + + /// Maximum possible score for Category C + pub const MAX: f64 = 20.0; +} + +/// Category D: Statistical Rigor (15 points) +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct StatisticalRigorScore { + /// D.1: ≥3s warmup before measurement (4 pts) + pub warmup_iterations: f64, + /// D.2: ≥100 samples for statistical significance (4 pts) + pub sample_count: f64, + /// D.3: IQR-based outlier detection reported (4 pts) + pub outlier_analysis: f64, + /// D.4: 95% CI on throughput metrics (3 pts) + pub confidence_intervals: f64, +} + +impl StatisticalRigorScore { + /// Calculate total for Category D + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total(&self) -> f64 { + self.warmup_iterations + + self.sample_count + + self.outlier_analysis + + self.confidence_intervals + } + + /// Maximum possible score for Category D + pub const MAX: f64 = 15.0; +} + +/// Category E: Historical Integrity (10 points) +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct HistoricalIntegrityScore { + /// E.1: PARITY/PAR ticket references (4 pts) + pub fault_lineage: f64, + /// E.2: Tests derived from historical bugs (3 pts) + pub regression_tests: f64, + /// E.3: 5-Why analysis for each P0 defect (3 pts) + pub root_cause_documentation: f64, +} + +impl HistoricalIntegrityScore { + /// Calculate total for Category E + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total(&self) -> f64 { + self.fault_lineage + self.regression_tests + self.root_cause_documentation + } + + /// Maximum possible score for Category E + pub const MAX: f64 = 10.0; +} + +/// Category F: GPU/SIMD Specific (5 points) +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct GpuSimdSpecificScore { + /// F.1: Active threads / warp size ratio (2 pts) + pub warp_efficiency: f64, + /// F.2: Achieved vs theoretical bandwidth (2 pts) + pub memory_throughput: f64, + /// F.3: FMA/memory instruction ratio (1 pt) + pub instruction_mix: f64, +} + +impl GpuSimdSpecificScore { + /// Calculate total for Category F + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn total(&self) -> f64 { + self.warp_efficiency + self.memory_throughput + self.instruction_mix + } + + /// Maximum possible score for Category F + pub const MAX: f64 = 5.0; +} + +/// Complete 100-point Popper Falsification Score +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct PopperScore { + /// Category A: Falsifiability & Testability (25 pts) - GATEWAY + pub falsifiability: FalsifiabilityScore, + /// Category B: Reproducibility Infrastructure (25 pts) + pub reproducibility: ReproducibilityScore, + /// Category C: Transparency & Openness (20 pts) + pub transparency: TransparencyScore, + /// Category D: Statistical Rigor (15 pts) + pub statistical_rigor: StatisticalRigorScore, + /// Category E: Historical Integrity (10 pts) + pub historical_integrity: HistoricalIntegrityScore, + /// Category F: GPU/SIMD Specific (5 pts) + pub gpu_simd_specific: GpuSimdSpecificScore, + /// Total score (0-100, or 0 if gateway fails) + pub total: f64, + /// Whether the gateway (Category A ≥ 15) passed + pub gateway_passed: bool, + /// Grade interpretation + pub grade: CudaTdgGrade, +} + +impl PopperScore { + /// Calculate total score with gateway rule + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")] + pub fn calculate( + falsifiability: FalsifiabilityScore, + reproducibility: ReproducibilityScore, + transparency: TransparencyScore, + statistical_rigor: StatisticalRigorScore, + historical_integrity: HistoricalIntegrityScore, + gpu_simd_specific: GpuSimdSpecificScore, + ) -> Self { + let category_a = falsifiability.total(); + let gateway_passed = category_a >= FalsifiabilityScore::GATEWAY_THRESHOLD; + + let raw_total = category_a + + reproducibility.total() + + transparency.total() + + statistical_rigor.total() + + historical_integrity.total() + + gpu_simd_specific.total(); + + // Gateway rule: if Category A < 15, total = 0 + let total = if gateway_passed { raw_total } else { 0.0 }; + let grade = CudaTdgGrade::from_score(total, gateway_passed); + + Self { + falsifiability, + reproducibility, + transparency, + statistical_rigor, + historical_integrity, + gpu_simd_specific, + total, + gateway_passed, + grade, + } + } +} + +/// Grade interpretation for CUDA-SIMD TDG scores +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +pub enum CudaTdgGrade { + /// 90-100: Production-ready, minimal debt + /// + /// Spelled with a capital L up to and including v3.29.0. Nothing persists + /// this enum (it is only ever rendered into a report), so the rename needs + /// no compatibility alias. + APlus, + /// 80-89: Production-ready with monitoring + A, + /// 70-79: Acceptable, prioritize improvements + B, + /// 60-69: Technical debt accumulating + C, + /// 50-59: Significant remediation needed + D, + /// 0-49: Not production-ready + #[default] + F, + /// Gateway failure: Falsifiability requirements not met + GatewayFail, +} + +impl CudaTdgGrade { + /// Convert score to grade + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + pub fn from_score(score: f64, gateway_passed: bool) -> Self { + if !gateway_passed { + return Self::GatewayFail; + } + match score { + s if s >= 90.0 => Self::APlus, + s if s >= 80.0 => Self::A, + s if s >= 70.0 => Self::B, + s if s >= 60.0 => Self::C, + s if s >= 50.0 => Self::D, + _ => Self::F, + } + } +} + +impl std::fmt::Display for CudaTdgGrade { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::APlus => write!(f, "A+"), + Self::A => write!(f, "A"), + Self::B => write!(f, "B"), + Self::C => write!(f, "C"), + Self::D => write!(f, "D"), + Self::F => write!(f, "F"), + Self::GatewayFail => write!(f, "FAIL (Gateway)"), + } + } +} diff --git a/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/tdg/grade.rs.txt b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/tdg/grade.rs.txt new file mode 100644 index 0000000000..c9c8654b93 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/fixtures/pvl/obligations/pmat/src/tdg/grade.rs.txt @@ -0,0 +1,502 @@ +#![cfg_attr(coverage_nightly, coverage(off))] + +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +/// Grade. +/// +/// One serialization, everywhere: the symbolic form (`"A+"`, `"B-"`), which is +/// what `Display`, SARIF, `pmat tdg --format json` and the `--min-grade` +/// argument parser already used. `Serialize` was left DERIVED, so the Rust +/// variant names leaked onto every serde-rendered surface and the SAME binary +/// reported the SAME score as `"grade": "A+"` from `pmat tdg --format json` +/// and `"grade": "APlus"` from `pmat analyze tdg --format json` and from the +/// MCP `quality_gate` tool — no machine consumer could match on one string +/// (GH #703, #669). `Serialize` is therefore written by hand below and emits +/// exactly what `Display` does. The old variant-name spellings are kept as +/// deserialization aliases so stored baselines still load. +// `Serialize`/`Deserialize` are both implemented by hand below (deserialization +// accepts the wire spelling AND the historical variant names), so neither may +// also be derived -- two fix agents each solved this and the merge kept both, +// which is a conflicting-impl compile error. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Grade { + APlus, + A, + AMinus, + BPlus, + B, + BMinus, + CPlus, + #[default] + C, + CMinus, + D, + F, +} + +/// Grade spellings as `Serialize` emits them, for error messages. +/// CANONICAL, and the only list of grade spellings anything may enumerate. +/// Ordered worst-last, matching `Grade`'s own `Ord`. CB-200 kept a private +/// `["A","B","C","D","F"]` and was blind to every modified grade for a release. +pub(crate) const GRADE_VARIANTS: &[&str] = + &["A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"]; + +impl Serialize for Grade { + /// Emits the symbolic form -- byte-identical to `Display`. See the note on + /// `Grade`: a derived `Serialize` here is what made one binary print two + /// spellings of one grade. + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for Grade { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let name = String::deserialize(deserializer)?; + Grade::from_variant_name(&name) + .ok_or_else(|| serde::de::Error::unknown_variant(&name, GRADE_VARIANTS)) + } +} + +impl Grade { + /// Parse a serialized variant name. Case-insensitive on purpose: see the + /// note on `Grade::APlus`. + pub(crate) fn from_variant_name(name: &str) -> Option { + // Accepts BOTH spellings. `Serialize` emits the variant name (what + // every stored baseline contains), but symbolic forms reach this from + // user input and from output written while the two round-3 fixes + // disagreed about the wire format. One serialization, two accepted + // inputs -- which is what #669's "two spellings" finding asked for. + Some(match name.to_ascii_lowercase().as_str() { + "aplus" | "a+" => Grade::APlus, + "a" => Grade::A, + "aminus" | "a-" => Grade::AMinus, + "bplus" | "b+" => Grade::BPlus, + "b" => Grade::B, + "bminus" | "b-" => Grade::BMinus, + "cplus" | "c+" => Grade::CPlus, + "c" => Grade::C, + "cminus" | "c-" => Grade::CMinus, + "d" => Grade::D, + "f" => Grade::F, + _ => return None, + }) + } + + /// Returns `true` if this grade is at least as good as `threshold`. + /// + /// `Grade`'s derived `Ord` follows declaration order (`APlus` first, + /// `F` last), so BETTER grades compare as SMALLER. Threshold checks + /// must use this helper instead of a raw `>=`/`<=` so call sites never + /// have to reason about that inversion (see v3.18.2 quality_gate fix). + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "tdg_grade_monotonic")] + pub fn meets_threshold(self, threshold: Grade) -> bool { + // Smaller discriminant == better grade, so "at least as good" is `<=`. + self <= threshold + } + + /// The ONE score → grade mapping. + /// + /// Every grade pmat prints — per file, per project, in every renderer — + /// must come through here, and it must depend on nothing but the score. + /// + /// v3.29.0 had a second, stricter mapping layered on top of this one: the + /// CB-1400 "no provable contracts ⇒ cap at A-" override in + /// `TdgScore::calculate_total`. Because contract coverage is unmeasured for + /// any project without a `contracts/binding.yaml` — the flag simply keeps + /// its `false` default — that override fired unconditionally, and after + /// #680 unified the aggregate path onto it the entire top of the scale went + /// dead: a fixture scoring a perfect **100.0 reported `AMinus`**, and + /// `pmat tdg` printed the self-contradicting line + /// `Overall Score: 100.0/100 (A-)`. `APlus` and `A` were unreachable at any + /// score. The override is gone; contract coverage is still reported on + /// `TdgScore::has_contract_coverage` and still enforced by + /// `pmat comply` (CB-1400), where an unmeasured signal cannot silently + /// rewrite a measurement. + #[must_use] + #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")] + pub fn from_score(score: f32) -> Self { + GRADE_BANDS + .iter() + .find(|(floor, _)| score >= *floor) + .map_or(Grade::F, |(_, grade)| *grade) + } + + /// The half-open score band this grade covers, `[floor, ceiling)` — the top + /// grade's band is closed at 100.0. + /// + /// Exists so nothing has to restate the bands in prose. `pmat explain` + /// used to carry a hand-written five-grade table (A = "Score 85-94") that + /// contradicted this one (A = 90..95, and A-/B+/B-/C+/C-/D were not listed + /// at all), so `explain TDG-A-` answered "No checks matching 'TDG-A-'" for a + /// grade `pmat tdg` prints routinely. + #[must_use] + pub fn score_band(self) -> (f32, f32) { + match GRADE_BANDS.iter().position(|(_, grade)| *grade == self) { + // `F` is everything below the last floor. + None => (0.0, GRADE_BANDS[GRADE_BANDS.len() - 1].0), + Some(0) => (GRADE_BANDS[0].0, 100.0), + Some(i) => (GRADE_BANDS[i].0, GRADE_BANDS[i - 1].0), + } + } + + /// Every grade, best first — the population `pmat explain` must document. + #[must_use] + pub fn all() -> [Grade; 11] { + [ + Grade::APlus, + Grade::A, + Grade::AMinus, + Grade::BPlus, + Grade::B, + Grade::BMinus, + Grade::CPlus, + Grade::C, + Grade::CMinus, + Grade::D, + Grade::F, + ] + } +} + +/// Score floors, best grade first. `F` is everything below the last floor. +/// +/// A table rather than a chain of guard arms: the bands are then contiguous +/// and monotonic by construction (each band starts where the next-worse one +/// ends), and the eleven guards no longer push `from_score` to a cognitive +/// complexity of 32 against a ceiling of 25. +const GRADE_BANDS: [(f32, Grade); 10] = [ + (95.0, Grade::APlus), + (90.0, Grade::A), + (85.0, Grade::AMinus), + (80.0, Grade::BPlus), + (75.0, Grade::B), + (70.0, Grade::BMinus), + (65.0, Grade::CPlus), + (60.0, Grade::C), + (55.0, Grade::CMinus), + (50.0, Grade::D), +]; + +impl std::fmt::Display for Grade { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Grade::APlus => write!(f, "A+"), + Grade::A => write!(f, "A"), + Grade::AMinus => write!(f, "A-"), + Grade::BPlus => write!(f, "B+"), + Grade::B => write!(f, "B"), + Grade::BMinus => write!(f, "B-"), + Grade::CPlus => write!(f, "C+"), + Grade::C => write!(f, "C"), + Grade::CMinus => write!(f, "C-"), + Grade::D => write!(f, "D"), + Grade::F => write!(f, "F"), + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +/// Category classification for metric. +pub enum MetricCategory { + StructuralComplexity, + SemanticComplexity, + Duplication, + Coupling, + Documentation, + Consistency, + /// Not a scored component — the source of the critical-defect penalty + /// `TdgScore::calculate_total` applies on top of the components. + /// + /// The penalty was the single largest term in the score (up to ~91 points) + /// and appeared in `penalties_applied` nowhere at all: a file could read + /// `total: 25.16, grade: F, critical_defects_count: 3` with + /// `penalties_applied: ["Duplication"]` worth 9 points, so any consumer + /// reconstructing the grade from components-minus-penalties was wrong by the + /// whole difference and concluded the defects had cost nothing. A penalty + /// that moves the score must be attributable like every other penalty. + CriticalDefect, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +/// Penalty attribution. +pub struct PenaltyAttribution { + pub source_metric: MetricCategory, + pub amount: f32, + pub applied_to: HashSet, + pub issue: String, +} + +#[cfg(test)] +mod tests { + use super::*; + + /// All grades from best to worst (declaration order). + const BEST_TO_WORST: [Grade; 11] = [ + Grade::APlus, + Grade::A, + Grade::AMinus, + Grade::BPlus, + Grade::B, + Grade::BMinus, + Grade::CPlus, + Grade::C, + Grade::CMinus, + Grade::D, + Grade::F, + ]; + + #[test] + fn test_derived_ord_makes_better_grades_smaller() { + // Documents the inversion that meets_threshold exists to hide: + // declaration order means APlus < F under the derived Ord. + assert!(Grade::APlus < Grade::F); + assert!(Grade::AMinus < Grade::BPlus); + } + + #[test] + fn test_meets_threshold_better_grade_passes() { + // A- is better than B+, so it must meet a B+ threshold + assert!(Grade::AMinus.meets_threshold(Grade::BPlus)); + // Best grade meets every threshold + for threshold in BEST_TO_WORST { + assert!(Grade::APlus.meets_threshold(threshold)); + } + } + + #[test] + fn test_meets_threshold_worse_grade_fails() { + // C is worse than B+, so it must NOT meet a B+ threshold + assert!(!Grade::C.meets_threshold(Grade::BPlus)); + // Worst grade only meets the F threshold + for threshold in &BEST_TO_WORST[..BEST_TO_WORST.len() - 1] { + assert!(!Grade::F.meets_threshold(*threshold)); + } + assert!(Grade::F.meets_threshold(Grade::F)); + } + + /// Regression (GH #703, #669): ONE serialization for one grade. + /// `pmat tdg --format json` and `pmat analyze tdg --format json` disagreed + /// ("A+" vs "APlus") on the identical score, so no machine consumer could + /// match on a string. + /// + /// The wire form is the SYMBOLIC one -- what `Display`, SARIF, + /// `pmat tdg --format json` and `--min-grade` all already used. Only the + /// derived `Serialize` spoke variant names. `Deserialize` still accepts + /// both, so every baseline already on disk keeps its meaning. + #[test] + fn test_grade_has_exactly_one_json_form_and_accepts_both() { + for grade in BEST_TO_WORST { + let json = serde_json::to_string(&grade).expect("serialize"); + assert_eq!( + json, + format!("\"{grade}\""), + "JSON form must be the symbolic form Display prints" + ); + + let back: Grade = serde_json::from_str(&json).expect("round trip"); + assert_eq!(back, grade); + + // The historical variant-name form must still parse. + let variant = format!("\"{grade:?}\""); + let from_variant: Grade = + serde_json::from_str(&variant).expect("variant-name form must load"); + assert_eq!(from_variant, grade); + } + } + + /// Baselines written with the old variant names must still load. + #[test] + fn test_grade_deserializes_legacy_variant_names() { + let legacy = [ + ("\"APLus\"", Grade::APlus), + ("\"APlus\"", Grade::APlus), + ("\"AMinus\"", Grade::AMinus), + ("\"BPlus\"", Grade::BPlus), + ("\"BMinus\"", Grade::BMinus), + ("\"CPlus\"", Grade::CPlus), + ("\"CMinus\"", Grade::CMinus), + ]; + for (json, expected) in legacy { + let parsed: Grade = serde_json::from_str(json).expect("legacy grade must deserialize"); + assert_eq!(parsed, expected, "legacy form {json}"); + } + // ... and so must the new symbolic form, including as a map key. + let map: std::collections::BTreeMap = + serde_json::from_str("{\"A+\":2,\"B-\":1,\"APLus\":3}").expect("map of grades"); + assert_eq!(map.get(&Grade::APlus), Some(&3)); + assert_eq!(map.get(&Grade::BMinus), Some(&1)); + } + + #[test] + fn test_meets_threshold_exhaustive_direction() { + // For every pair: grade meets threshold iff it sits at or before + // the threshold in best-to-worst order. + for (gi, grade) in BEST_TO_WORST.iter().enumerate() { + for (ti, threshold) in BEST_TO_WORST.iter().enumerate() { + assert_eq!( + grade.meets_threshold(*threshold), + gi <= ti, + "{grade} vs threshold {threshold}" + ); + } + } + } + + /// GH #680 (second round): every grade must be reachable from some score. + /// + /// The v3.29.0 binary could not print `APlus` or `A` at all — the CB-1400 + /// cap rewrote both to `AMinus`, so the observed grade for a perfect 100.0 + /// was `AMinus`. A sweep of the whole scale must hit all 11 bands. + #[test] + fn test_every_grade_is_reachable_from_some_score() { + let mut seen = std::collections::HashSet::new(); + for tenth in 0..=1000 { + #[allow(clippy::cast_precision_loss)] + seen.insert(Grade::from_score(tenth as f32 / 10.0)); + } + for grade in BEST_TO_WORST { + assert!( + seen.contains(&grade), + "no score in 0.0..=100.0 maps to {grade}; the scale has a dead band" + ); + } + } + + /// A perfect score must take the top grade, not a capped one. + #[test] + fn test_perfect_score_is_the_top_grade() { + assert_eq!(Grade::from_score(100.0), Grade::APlus); + assert_eq!(Grade::from_score(95.0), Grade::APlus); + } + + /// Bands are contiguous and monotonic: sweeping the score upward may only + /// ever improve the grade, and never skips a band. + #[test] + fn test_bands_are_contiguous_and_monotonic() { + let mut previous = Grade::from_score(0.0); + assert_eq!(previous, Grade::F); + let mut transitions = 0; + for tenth in 0..=1000 { + #[allow(clippy::cast_precision_loss)] + let grade = Grade::from_score(tenth as f32 / 10.0); + if grade != previous { + // Better grades have smaller discriminants, so an improving + // score must step to exactly the next-better variant. + let prev_idx = BEST_TO_WORST.iter().position(|g| *g == previous).unwrap(); + let idx = BEST_TO_WORST.iter().position(|g| *g == grade).unwrap(); + assert_eq!( + idx + 1, + prev_idx, + "score {} jumped from {previous} to {grade}", + f64::from(tenth) / 10.0 + ); + transitions += 1; + previous = grade; + } + } + assert_eq!( + transitions, + BEST_TO_WORST.len() - 1, + "expected one transition per band boundary" + ); + } + + /// GH #680 (second round): the enum was misspelled `APlus`. What pmat + /// emits from now on is `"A+"`; what it accepts still includes the variant + /// names, so baselines written by <= v3.29.0 keep loading. + #[test] + fn test_a_plus_spelling_serialises_correctly_and_accepts_the_old_typo() { + assert_eq!(serde_json::to_string(&Grade::APlus).unwrap(), "\"A+\""); + assert_eq!(format!("{:?}", Grade::APlus), "APlus"); + // The old spelling is written out of two `char`s so this test file + // does not reintroduce the literal the fix removed. + let old = format!("\"AP{}us\"", 'L'); + let from_old: Grade = serde_json::from_str(&old).expect("old spelling must load"); + assert_eq!(from_old, Grade::APlus); + let from_new: Grade = serde_json::from_str("\"APlus\"").expect("new spelling must load"); + assert_eq!(from_new, Grade::APlus); + } + + /// Round-trip every variant, including as a `HashMap` key — `ProjectScore` + /// serialises `grade_distribution: HashMap`, and a hand-written + /// `Deserialize` has to keep working in map-key position. + #[test] + fn test_grade_round_trips_as_value_and_as_map_key() { + for grade in BEST_TO_WORST { + let json = serde_json::to_string(&grade).unwrap(); + let back: Grade = serde_json::from_str(&json).unwrap(); + assert_eq!(back, grade); + + let map: std::collections::HashMap = + std::collections::HashMap::from([(grade, 3)]); + let encoded = serde_json::to_string(&map).unwrap(); + let decoded: std::collections::HashMap = + serde_json::from_str(&encoded).unwrap(); + assert_eq!(decoded.get(&grade), Some(&3)); + } + } + + /// `score_band` must agree with `from_score` at every boundary, or the + /// documentation generated from it would restate the same contradiction + /// `pmat explain` used to carry. + #[test] + fn test_score_band_agrees_with_from_score() { + for grade in Grade::all() { + let (floor, ceiling) = grade.score_band(); + assert_eq!( + Grade::from_score(floor), + grade, + "{grade}'s floor {floor} does not map back to {grade}" + ); + // Just under the ceiling is still this grade; at the ceiling it is + // the next better one (except for the top band, closed at 100). + assert_eq!(Grade::from_score(ceiling - 0.1), grade, "{grade} ceiling"); + if grade != Grade::APlus { + assert_ne!(Grade::from_score(ceiling), grade, "{grade} ceiling is open"); + } + } + assert_eq!(Grade::APlus.score_band(), (95.0, 100.0)); + assert_eq!(Grade::F.score_band(), (0.0, 50.0)); + } + + #[test] + fn test_unknown_variant_name_is_rejected() { + let err = serde_json::from_str::("\"Z\"").unwrap_err(); + assert!(err.to_string().contains("unknown variant"), "{err}"); + } +} + +/// Every canonical spelling parses, and `GRADE_VARIANTS` is in `Ord` order. +/// +/// A TEST, not a `const` block: `from_variant_name` lowercases, and +/// `str::to_ascii_lowercase` is not a `const fn`, so this cannot run in a const +/// context. It runs under `cargo test --lib`, which is the rung this repository +/// actually executes. The anchoring property — that rank tracks the score band +/// — is proved in `contracts/lean/Theorems/Tdg/Grade.lean`, because no +/// self-referential assertion can catch a scale reversed together with its own +/// array. +#[test] +fn grade_order_is_parseable() { + let parsed: Vec = GRADE_VARIANTS + .iter() + .map(|s| Grade::from_variant_name(s).expect("every canonical spelling must parse")) + .collect(); + let mut sorted = parsed.clone(); + sorted.sort(); + assert_eq!(parsed, sorted, "GRADE_VARIANTS is not in Ord order"); + assert_eq!( + parsed.len(), + Grade::all().len(), + "GRADE_VARIANTS and Grade::all() disagree" + ); +} +const GRADE_ORDER_IS_PARSEABLE: () = (); diff --git a/crates/aprender-contracts-cli/tests/includes/dispatch_tests.rs b/crates/aprender-contracts-cli/tests/includes/dispatch_tests.rs index 88b0289f1d..9d9074635a 100644 --- a/crates/aprender-contracts-cli/tests/includes/dispatch_tests.rs +++ b/crates/aprender-contracts-cli/tests/includes/dispatch_tests.rs @@ -141,7 +141,13 @@ fn dispatch_proof_status_with_binding() { kind: None, verify_bindings: None, }); - assert!(result.is_ok()); + // PVL-001 EV-2: `--binding` RESOLVES every binding. This registry names five + // functions that exist nowhere in the tree (swap_axes, validate_element_count, + // map_tensor_name, bidirectional_attention, mint_test_token; #4094), so the + // honest answer is a reject. This test asserted `is_ok()` while proof-status + // counted entries without resolving them: it encoded the defect. + let err = result.expect_err("a registry holding ghost bindings must be rejected"); + assert!(err.to_string().contains("ghost binding"), "{err}"); } #[test] diff --git a/crates/aprender-contracts-cli/tests/ont2c_owl_tbox.rs b/crates/aprender-contracts-cli/tests/ont2c_owl_tbox.rs new file mode 100644 index 0000000000..0d55ff9955 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont2c_owl_tbox.rs @@ -0,0 +1,196 @@ +//! ONT-2c (PMAT-4071, aprender#4071) — `pv ontology export --owl`, `pv ontology tbox`, `pv lint --gate tbox`, +//! driven through the BUILT binary. The lib tests prove the writer and the told-closure. This file proves the +//! CLI surface the row's probe calls, and the one mapping the row names at the dispatch layer: the gate maps +//! the advisory report to `Unknown{Advisory}` (`decline: Advisory`, exit 2) and has NO arm that exits 0 (R-7). +//! +//! DISCRIMINATION: a fresh corpus declines with exit 2, and a stale or missing artifact is exit 3. A build +//! that mapped Advisory to a pass (exit 0) fails `the_repo_corpus_is_advisory_never_zero`. One that stopped +//! checking freshness fails `a_stale_report_is_refused_with_exit_3`. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn pv_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_pv")) +} + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn pv(args: &[&str]) -> Run { + let scratch = tempfile::tempdir().expect("scratch cwd is creatable"); + let out = Command::new(pv_bin()) + .current_dir(scratch.path()) + .args(args) + .output() + .expect("failed to spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn repo(rel: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join(rel) +} + +fn s(p: &Path) -> String { + p.to_str().expect("utf-8 path").to_string() +} + +/// A corpus directory holding the fixture Σ plus the two artifacts `pv ontology … --write` produces. +fn fresh_fixture_corpus() -> tempfile::TempDir { + let d = tempfile::tempdir().expect("tempdir"); + std::fs::copy( + repo("tests/fixtures/ont/owl/ontology.yaml"), + d.path().join("ontology.yaml"), + ) + .expect("copy Σ"); + // `pv lint` declines a corpus with 0 contracts before any gate runs; one minimal contract makes it a corpus. + std::fs::copy( + repo("tests/fixtures/ont/sigma-ok/fixture-kernel-v1.yaml"), + d.path().join("fixture-kernel-v1.yaml"), + ) + .expect("copy contract"); + let sigma = s(&d.path().join("ontology.yaml")); + let r = pv(&["ontology", "export", "--owl", "--write", &sigma]); + assert_eq!(r.code, 0, "{}", show(&r)); + let r = pv(&["ontology", "tbox", "--write", &sigma]); + assert_eq!(r.code, 0, "{}", show(&r)); + d +} + +#[test] +fn export_prints_exactly_the_committed_fixture_axiom_set() { + let r = pv(&[ + "ontology", + "export", + "--owl", + &s(&repo("tests/fixtures/ont/owl/ontology.yaml")), + ]); + assert_eq!(r.code, 0, "{}", show(&r)); + let want = + std::fs::read_to_string(repo("tests/fixtures/ont/owl/expected.ofn")).expect("expected.ofn"); + assert_eq!(r.stdout, want); +} + +#[test] +fn export_without_a_format_is_refused() { + let r = pv(&[ + "ontology", + "export", + &s(&repo("tests/fixtures/ont/owl/ontology.yaml")), + ]); + assert_eq!(r.code, 2, "{}", show(&r)); +} + +#[test] +fn the_repo_export_equals_the_tracked_ofn() { + // The row's probe: `"$PV" ontology export --owl contracts/ontology.yaml | cmp - contracts/ontology.ofn`. + let r = pv(&[ + "ontology", + "export", + "--owl", + &s(&repo("contracts/ontology.yaml")), + ]); + assert_eq!(r.code, 0, "{}", show(&r)); + let tracked = + std::fs::read_to_string(repo("contracts/ontology.ofn")).expect("contracts/ontology.ofn"); + assert_eq!( + r.stdout, tracked, + "contracts/ontology.ofn is not what the writer produces" + ); +} + +#[test] +fn write_writes_both_artifacts_next_to_sigma() { + let d = fresh_fixture_corpus(); + let ofn = + std::fs::read_to_string(d.path().join("ontology.ofn")).expect("--write wrote ontology.ofn"); + assert!(ofn.contains("SymmetricObjectProperty("), "{ofn}"); + let rep: serde_json::Value = serde_json::from_str( + &std::fs::read_to_string(d.path().join("tbox-report.json")).expect("report"), + ) + .expect("report is JSON"); + assert_eq!(rep["advisory"], true); + assert_eq!(rep["method"], "told-closure"); + assert_eq!(rep["consistent"], true); +} + +#[test] +fn the_repo_corpus_is_advisory_never_zero() { + let r = pv(&["lint", &s(&repo("contracts")), "--gate", "tbox"]); + assert_eq!( + r.code, + 2, + "the tbox gate must DECLINE (Unknown{{Advisory}}), never pass: {}", + show(&r) + ); + assert!( + r.stderr.contains("decline: Advisory") || r.stdout.contains("decline: Advisory"), + "{}", + show(&r) + ); +} + +#[test] +fn a_fresh_fixture_corpus_is_advisory() { + let d = fresh_fixture_corpus(); + let r = pv(&["lint", &s(d.path()), "--gate", "tbox"]); + assert_eq!(r.code, 2, "{}", show(&r)); + assert!( + format!("{}{}", r.stdout, r.stderr).contains("Advisory"), + "{}", + show(&r) + ); +} + +#[test] +fn a_stale_report_is_refused_with_exit_3() { + let d = fresh_fixture_corpus(); + let p = d.path().join("tbox-report.json"); + let body = std::fs::read_to_string(&p).expect("report"); + std::fs::write(&p, body.replace("\"classes\": 3", "\"classes\": 9")).expect("tamper"); + let r = pv(&["lint", &s(d.path()), "--gate", "tbox"]); + assert_eq!(r.code, 3, "{}", show(&r)); + assert!(r.stderr.contains("tbox-report.json"), "{}", show(&r)); +} + +#[test] +fn a_missing_ofn_is_refused_with_exit_3() { + let d = fresh_fixture_corpus(); + std::fs::remove_file(d.path().join("ontology.ofn")).expect("rm"); + let r = pv(&["lint", &s(d.path()), "--gate", "tbox"]); + assert_eq!(r.code, 3, "{}", show(&r)); + assert!(r.stderr.contains("ontology.ofn"), "{}", show(&r)); +} + +#[test] +fn an_undeclared_unexpressed_key_is_exit_3() { + let d = tempfile::tempdir().expect("tempdir"); + let sigma = std::fs::read_to_string(repo("tests/fixtures/ont/owl/ontology.yaml")).expect("Σ"); + let broken = sigma.replace(" - {key: symbols, reader: ontology/owl.rs}\n", ""); + assert_ne!(broken, sigma, "the mutation must change Σ"); + std::fs::write(d.path().join("ontology.yaml"), broken).expect("write"); + let r = pv(&[ + "ontology", + "export", + "--owl", + &s(&d.path().join("ontology.yaml")), + ]); + assert_eq!(r.code, 3, "{}", show(&r)); + assert!(r.stderr.contains("symbols"), "{}", show(&r)); +} diff --git a/crates/aprender-contracts-cli/tests/ont4b_shapes_gate.rs b/crates/aprender-contracts-cli/tests/ont4b_shapes_gate.rs index 1057520206..891dfee446 100644 --- a/crates/aprender-contracts-cli/tests/ont4b_shapes_gate.rs +++ b/crates/aprender-contracts-cli/tests/ont4b_shapes_gate.rs @@ -88,11 +88,13 @@ fn the_repo_corpus_passes_with_the_plant_fired_and_the_whole_corpus_as_focus_nod ); assert_eq!(v["extra"]["pc_shape"], "fired", "{}", show(&r)); // ONT-4b: exactly one, from ont:id minCount. ONT-4c1 plants a bare model:Model too, which draws - // ladder-measured's two minCounts: three on this corpus, and never zero. + // ladder-measured's two minCounts: three on this corpus, and never zero. ONT-4f (#4330) arms the four GitHub + // shapes, and the plant draws every minCount they declare: github-repo 4 + github-issue 6 + + // github-pull-request 7 + github-milestone 6 = 23 more, so 26. assert_eq!( v["extra"]["plant_violations"], - 3, - "ont:id minCount + ladder-measured's two (ONT-4c1)\n{}", + 26, + "ont:id minCount + ladder-measured's two (ONT-4c1) + the four GitHub shapes' 23 (ONT-4f)\n{}", show(&r) ); } @@ -230,11 +232,12 @@ fn the_tracked_repo_graph_is_fresh() { // that is the ratchet working, not a conflict to route around. It did: the // 0.69 batch folded #3600 in and the count went 6 -> 9 with its three shapes. #3715 added the nine-shape // `release-readiness-v1` family (shapes_n=18, triples=15863, measured on its branch); it contributes no focus - // node to a PR's graph — the release evidence is extracted only under `--release-*`. + // node to a PR's graph — the release evidence is extracted only under `--release-*`. ONT-4f (#4330) adds + // `github-entities-v1`'s four armed shapes: 22. #3560 R1 adds `examples-well-formed` (reported, not armed): 23. R4 adds `examples-model-current` (reported): 24. assert_eq!( v["shapes_n"], - 18, - "ont-shapes-v1 + ladder-measured + ladder-green (ONT-4c1) + bound-symbols-resolve + lean-statements-grounded (ONT-4b2) + refusal-receipt-v1 (#3605) + parity-receipt-complete + parity-comparator-self + parity-comparator-oracle (parity-receipt-v2, #3600) + release-readiness-v1{{,.release,.host,.context,.model,.coverage,.tokenizer,.kernel,.refusal}} (#3715)\n{}", + 24, + "ont-shapes-v1 + ladder-measured + ladder-green (ONT-4c1) + bound-symbols-resolve + lean-statements-grounded (ONT-4b2) + refusal-receipt-v1 (#3605) + parity-receipt-complete + parity-comparator-self + parity-comparator-oracle (parity-receipt-v2, #3600) + release-readiness-v1{{,.release,.host,.context,.model,.coverage,.tokenizer,.kernel,.refusal}} (#3715) + github-{{repo,issue,pull-request,milestone}} (#4330) + examples-well-formed (#3560 R1) + examples-model-current (#3560 R4)\n{}", show(&r) ); } diff --git a/crates/aprender-contracts-cli/tests/ont4d_subsumption.rs b/crates/aprender-contracts-cli/tests/ont4d_subsumption.rs new file mode 100644 index 0000000000..116135b7f2 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont4d_subsumption.rs @@ -0,0 +1,230 @@ +//! ONT-4d (PMAT-4070, aprender#4070): subsumption in Σ; shapes inherit down the hierarchy. Every RED clause of +//! the row, driven through the BUILT `pv`. +//! +//! - cycle → exit 3 `error: subsumes cycle ` `subsumption-cycle/` +//! - a shape on `Code` rejects a `Kernel` instance; focus_nodes_n == instances of Code ∪ all sub-concepts +//! `subsumption-inherit/` +//! - a sub-concept shape removing a super constraint → exit 1 `reject: weakens .` +//! `subsumption-weaken/` +//! - rdf:type closure present in contracts.nt for every super-concept (tracked corpus, and a fixture extract) +//! - export byte-identical across two runs +//! +//! DISCRIMINATION: `subsumption-ok/` (the same hierarchy, a valid kernel) is Pass at exit 0, so a build that +//! rejects every inherited instance fails this file. The row's MUTATION (drop the closure materialization) turns +//! `a_code_shape_rejects_a_kernel_instance_through_the_closure` RED: with no closure, the Code shape has no +//! focus node, and the gate declines instead of rejecting. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn pv_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_pv")) +} + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn pv(args: &[&str]) -> Run { + let scratch = tempfile::tempdir().expect("scratch cwd is creatable"); + let out = Command::new(pv_bin()) + .current_dir(scratch.path()) + .args(args) + .output() + .expect("failed to spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn repo(rel: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join(rel) +} + +fn fixture(name: &str) -> String { + s(&repo(&format!("tests/fixtures/ont/{name}"))) +} + +fn s(p: &Path) -> String { + p.to_str().expect("utf-8 path").to_string() +} + +fn shapes_json(dir: &str) -> (Run, serde_json::Value) { + let r = pv(&["lint", dir, "--gate", "shapes", "--format", "json"]); + let v = serde_json::from_str(&r.stdout) + .unwrap_or_else(|e| panic!("stdout is JSON: {e}\n{}", show(&r))); + (r, v) +} + +#[test] +fn a_subsumption_cycle_is_exit_3_naming_the_path() { + let r = pv(&["lint", &fixture("subsumption-cycle"), "--gate", "sigma"]); + assert_eq!(r.code, 3, "{}", show(&r)); + assert!(r.stderr.contains("error: subsumes cycle "), "{}", show(&r)); + for c in ["Kernel", "Code", "Contract"] { + assert!( + r.stderr.contains(c), + "the cycle path names {c}: {}", + show(&r) + ); + } +} + +#[test] +fn a_code_shape_rejects_a_kernel_instance_through_the_closure() { + let (r, v) = shapes_json(&fixture("subsumption-inherit")); + assert_eq!(r.code, 1, "{}", show(&r)); + assert_eq!(v["verdict"], "Fail", "{}", show(&r)); + // focus_nodes_n == instances of Code ∪ all sub-concepts: no contract is typed Code directly; the one kernel + // contract reaches the Code shape ONLY through Kernel ⊑ Code. + assert_eq!(v["focus_nodes_n"], 1, "{}", show(&r)); + assert_eq!( + v["by_shape"], + serde_json::Value::from(vec!["code-shape=1"]), + "{}", + show(&r) + ); + assert_eq!(v["inherited_shapes_applied"], 1, "{}", show(&r)); + assert_eq!(v["violations"], 1, "{}", show(&r)); +} + +#[test] +fn the_same_hierarchy_with_a_valid_kernel_passes() { + let (r, v) = shapes_json(&fixture("subsumption-ok")); + assert_eq!(r.code, 0, "{}", show(&r)); + assert_eq!(v["verdict"], "Pass", "{}", show(&r)); + assert_eq!(v["inherited_shapes_applied"], 1, "{}", show(&r)); + assert_eq!( + v["inherited_by_shape"], + serde_json::Value::from(vec!["code-shape <- Kernel=1"]), + "{}", + show(&r) + ); +} + +#[test] +fn a_sub_shape_that_weakens_a_super_shape_is_rejected() { + let (r, v) = shapes_json(&fixture("subsumption-weaken")); + assert_eq!(r.code, 1, "{}", show(&r)); + let msgs: Vec = v["findings"] + .as_array() + .expect("findings") + .iter() + .map(|f| f["message"].as_str().unwrap_or_default().to_string()) + .collect(); + assert!( + msgs.iter() + .any(|m| m.starts_with("reject: kernel-shape weakens contract-shape.name.minCount")), + "{msgs:?}\n{}", + show(&r) + ); + // The kernel instance satisfies BOTH shapes, so the weakening is the ONLY violation. + assert_eq!(v["violations"], 1, "{}", show(&r)); +} + +#[test] +fn the_repo_corpus_passes_with_shapes_applied_down_the_hierarchy() { + // The row's probe. + let sigma = std::fs::read_to_string(repo("contracts/ontology.yaml")).expect("Σ"); + assert!( + sigma.lines().any(|l| l.starts_with("subsumes:")), + "Σ declares subsumes" + ); + let (r, v) = shapes_json(&s(&repo("contracts"))); + assert_eq!(v["verdict"], "Pass", "{}", show(&r)); + assert!( + v["inherited_shapes_applied"].as_u64().unwrap_or(0) > 0, + "{}", + show(&r) + ); + let ofn = std::fs::read_to_string(repo("contracts/ontology.ofn")).expect("ontology.ofn"); + assert!(ofn.contains("SubClassOf( )")); + assert!(ofn.contains( + "SubClassOf( )" + )); +} + +/// Subjects typed `class` in an N-Triples text. +fn typed(nt: &str, class: &str) -> std::collections::BTreeSet { + let needle = format!("> ."); + nt.lines() + .filter(|l| l.ends_with(&needle)) + .map(|l| l.split(' ').next().unwrap_or_default().to_string()) + .collect() +} + +#[test] +fn the_tracked_contracts_nt_carries_the_type_closure() { + let nt = std::fs::read_to_string(repo("contracts/contracts.nt")).expect("contracts.nt"); + let kernels = typed(&nt, "Kernel"); + let symbols = typed(&nt, "Symbol"); + assert!( + !kernels.is_empty() && !symbols.is_empty(), + "both sub-concepts have instances" + ); + assert!( + kernels.is_subset(&typed(&nt, "Contract")), + "every Kernel is typed Contract" + ); + assert!( + symbols.is_subset(&typed(&nt, "Code")), + "every Symbol is typed Code (closure-only: no extractor types Code)" + ); +} + +#[test] +fn a_fixture_extract_carries_the_closure_and_is_byte_identical_twice() { + let d = tempfile::tempdir().expect("tempdir"); + for f in ["ontology.yaml", "code-shape.yaml", "kernel-noname.yaml"] { + std::fs::copy( + repo(&format!("tests/fixtures/ont/subsumption-inherit/{f}")), + d.path().join(f), + ) + .expect("copy"); + } + let dir = s(d.path()); + let r = pv(&["extract", &dir]); + assert_eq!(r.code, 0, "{}", show(&r)); + let first = std::fs::read(d.path().join("contracts.nt")).expect("contracts.nt written"); + let r = pv(&["extract", &dir]); + assert_eq!(r.code, 0, "{}", show(&r)); + let second = std::fs::read(d.path().join("contracts.nt")).expect("contracts.nt written"); + assert_eq!(first, second, "export byte-identical across two runs"); + let nt = String::from_utf8(first).expect("utf-8"); + let k = typed(&nt, "Kernel"); + assert_eq!(k.len(), 1, "{nt}"); + assert!( + k.is_subset(&typed(&nt, "Code")) && k.is_subset(&typed(&nt, "Contract")), + "closure over two levels: {nt}" + ); +} + +#[test] +fn census_reports_by_concept_over_the_closure() { + let r = pv(&["census", &s(&repo("contracts")), "--format", "json"]); + assert_eq!(r.code, 0, "{}", show(&r)); + let v: serde_json::Value = serde_json::from_str(&r.stdout).expect("census JSON"); + let n = |c: &str| v["by_concept"][c].as_u64().unwrap_or(0); + assert!(n("Kernel") > 0 && n("Symbol") > 0, "{}", show(&r)); + assert!( + n("Contract") >= n("Kernel"), + "Contract counts its Kernel sub-instances" + ); + assert!( + n("Code") >= n("Symbol"), + "Code counts its Symbol sub-instances" + ); +} diff --git a/crates/aprender-contracts-cli/tests/ont4e_refines_gate.rs b/crates/aprender-contracts-cli/tests/ont4e_refines_gate.rs new file mode 100644 index 0000000000..994c546c78 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont4e_refines_gate.rs @@ -0,0 +1,240 @@ +//! ONT-4e (PMAT-4075) — `refines` is Liskov or it is rejected (R-20), on the CLI. +//! +//! ONT-001 §5 ONT-4e probe, verbatim in `the_spec_probe_holds_on_the_repo_corpus`. Around it, each RED line of the +//! row: a strengthened precondition and a weakened postcondition are `reject:` lines naming the clause, a dropped +//! invariant is exit 1, a `prose` clause is `Unknown{Prose}` naming the clause, a legacy `invariants[]`-only pair is +//! `Pass` with `liskov_pairs_checked: 0` on the verdict line. Every witness is written by the real `pv-sat` binary, so +//! the reasoner and the checker are tested against each other; a tampered witness is PV-ONT-025, never a verdict. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn run(bin: &str, args: &[&str]) -> Run { + let out = Command::new(bin).args(args).output().expect("spawn"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn s(p: &Path) -> &str { + p.to_str().expect("utf-8 path") +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn repo() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn pv_sat(dir: &Path) -> Run { + run(env!("CARGO_BIN_EXE_pv-sat"), &[s(dir)]) +} + +fn gate(dir: &Path) -> Run { + run( + env!("CARGO_BIN_EXE_pv"), + &["lint", s(dir), "--gate", "refines", "--format", "json"], + ) +} + +fn json_of(r: &Run) -> serde_json::Value { + serde_json::from_str(&r.stdout).unwrap_or_else(|e| panic!("stdout is JSON: {e}\n{}", show(r))) +} + +/// A copy of `tests/fixtures/ont/`. +fn fixture(name: &str) -> tempfile::TempDir { + let dir = tempfile::tempdir().expect("tempdir"); + for entry in std::fs::read_dir(repo().join("tests/fixtures/ont").join(name)).expect("fixture") { + let entry = entry.expect("entry"); + std::fs::copy(entry.path(), dir.path().join(entry.file_name())).expect("copy"); + } + dir +} + +/// The fixture, with the witness the real pv-sat wrote for it, gated. +fn reasoned(name: &str) -> (tempfile::TempDir, Run) { + let dir = fixture(name); + let sat = pv_sat(dir.path()); + assert_eq!(sat.code, 0, "pv-sat on {name}: {}", show(&sat)); + let r = gate(dir.path()); + (dir, r) +} + +fn liskov_witnesses(dir: &Path) -> Vec { + std::fs::read_dir(dir.join("witness/liskov")) + .map(|rd| rd.flatten().map(|e| e.path()).collect()) + .unwrap_or_default() +} + +#[test] +fn the_spec_probe_holds_on_the_repo_corpus() { + // ONT-001 §5 ONT-4e probe: `"$PV" lint contracts/ --gate refines --format json >"$TMP/lk.json" + // && json_object "$TMP/lk.json" && jq -e '.liskov_pairs_checked>0 and .pc_checker=="fired" and .verdict=="Pass"'` + let r = gate(&repo().join("contracts")); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + assert!(v.is_object(), "json_object: {v}"); + assert!( + v["liskov_pairs_checked"].as_u64().unwrap_or(0) > 0, + "liskov_pairs_checked>0: {v}" + ); + assert_eq!(v["pc_checker"], "fired", "{v}"); + assert_eq!(v["verdict"], "Pass", "{v}"); + assert_eq!(v["witness"]["pc_reasoner"], "fired", "{v}"); +} + +#[test] +fn a_liskov_pair_passes_and_the_witness_is_a_chain() { + let (dir, r) = reasoned("refines-ok"); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["verdict"], "Pass", "{v}"); + assert_eq!(v["liskov_pairs_checked"], 1, "{v}"); + assert_eq!(v["violations"], 0, "{v}"); + let w = liskov_witnesses(dir.path()); + assert_eq!(w.len(), 1, "one Liskov witness: {w:?}"); + let text = std::fs::read_to_string(&w[0]).expect("witness"); + assert!(text.contains("\"chain\""), "{text}"); + assert!(!text.contains("counter_model"), "{text}"); + + // Byte-stable: a second pv-sat run confirms, it does not rewrite. + let again = pv_sat(dir.path()); + assert_eq!(again.code, 0, "{}", show(&again)); + assert_eq!( + std::fs::read_to_string(&w[0]).expect("witness"), + text, + "rerun rewrote the witness" + ); +} + +#[test] +fn a_strengthened_precondition_is_rejected_naming_the_clause() { + let (_dir, r) = reasoned("refines-pre-strengthened"); + assert_eq!(r.code, 1, "{}", show(&r)); + assert!( + r.stderr + .contains("reject: a refines b: precondition strengthened (PRE-1)"), + "{}", + show(&r) + ); + let v = json_of(&r); + assert_eq!(v["verdict"], "Fail", "{v}"); + assert_eq!(v["findings"][0]["rule_id"], "PV-ONT-024", "{v}"); +} + +#[test] +fn a_weakened_postcondition_is_rejected_naming_the_clause() { + let (_dir, r) = reasoned("refines-post-weakened"); + assert_eq!(r.code, 1, "{}", show(&r)); + assert!( + r.stderr + .contains("reject: a refines b: postcondition weakened (POST-1)"), + "{}", + show(&r) + ); +} + +#[test] +fn a_dropped_invariant_is_rejected() { + let (_dir, r) = reasoned("refines-inv-dropped"); + assert_eq!(r.code, 1, "{}", show(&r)); + assert!( + r.stderr + .contains("reject: a refines b: invariant dropped (INV-1)"), + "{}", + show(&r) + ); +} + +#[test] +fn a_prose_clause_is_unknown_prose_naming_the_clause() { + let (_dir, r) = reasoned("refines-prose"); + assert_eq!(r.code, 2, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["verdict"], "Unknown(Prose)", "{v}"); + assert_eq!(v["liskov_pairs_checked"], 0, "{v}"); + assert_eq!(v["liskov_prose"], 1, "{v}"); + assert_eq!(v["prose_clauses"][0], "a.requires PRE-1", "{v}"); + assert!(r.stderr.contains("a.requires PRE-1"), "{}", show(&r)); + assert!(r.stderr.contains("decline: Prose"), "{}", show(&r)); +} + +#[test] +fn a_legacy_pair_passes_and_says_nothing_was_checked() { + // No witness needed: nothing to check, and the verdict line says so. + let dir = fixture("refines-legacy"); + let r = gate(dir.path()); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["verdict"], "Pass", "{v}"); + assert_eq!(v["liskov_pairs_checked"], 0, "{v}"); + assert_eq!(v["liskov_pairs_legacy"], 1, "{v}"); + assert_eq!(v["pc_checker"], "fired", "{v}"); +} + +#[test] +fn no_witness_is_a_stale_decline_naming_make_contracts() { + let dir = fixture("refines-ok"); + let r = gate(dir.path()); + assert_eq!(r.code, 2, "{}", show(&r)); + assert!(r.stderr.contains("make contracts"), "{}", show(&r)); + assert!(r.stderr.contains("decline: WitnessStale"), "{}", show(&r)); +} + +#[test] +fn a_tampered_witness_is_refused_not_believed() { + // pv-sat proves the pre-strengthened pair violates PRE-1; a hand edit turns the counter-model into a chain + // that derives A's PRE-1 from nothing B requires. The checker must refuse it (PV-ONT-025), not pass it. + let dir = fixture("refines-pre-strengthened"); + assert_eq!(pv_sat(dir.path()).code, 0); + let w = liskov_witnesses(dir.path()); + assert_eq!(w.len(), 1, "{w:?}"); + let mut doc: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&w[0]).expect("witness")).expect("json"); + let obligations = doc["pairs"][0]["obligations"] + .as_array_mut() + .expect("obligations"); + for ob in obligations.iter_mut() { + if ob["kind"] == "pre" { + *ob = serde_json::from_str( + r#"{"kind": "pre", "chain": [{"clause": "PRE-1", "from": "PRE-1"}]}"#, + ) + .expect("obligation"); + } + } + std::fs::write(&w[0], serde_json::to_string_pretty(&doc).expect("ser")).expect("write"); + let r = gate(dir.path()); + assert_eq!(r.code, 1, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["findings"][0]["rule_id"], "PV-ONT-025", "{v}"); + assert!( + !r.stderr.contains("precondition strengthened"), + "a refused witness names no verdict: {}", + show(&r) + ); +} + +#[test] +fn pv_sat_self_test_fires_the_liskov_controls() { + let r = run(env!("CARGO_BIN_EXE_pv-sat"), &["--self-test"]); + assert_eq!(r.code, 0, "{}", show(&r)); + assert!( + r.stdout.contains("pc_liskov_reasoner fired"), + "{}", + show(&r) + ); + assert!(r.stdout.contains("pc_liskov_checker fired"), "{}", show(&r)); +} diff --git a/crates/aprender-contracts-cli/tests/ont4f_github_entities.rs b/crates/aprender-contracts-cli/tests/ont4f_github_entities.rs new file mode 100644 index 0000000000..bebc5af3fd --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont4f_github_entities.rs @@ -0,0 +1,167 @@ +//! ONT-4f (aprender#4330) — GitHub repos, issues, pull requests and milestones as focus nodes, on the CLI. +//! +//! Each fixture is the real Σ, the real `contracts/github-entities-v1.yaml`, and committed snapshots under +//! `evidence/github//`: +//! +//! | fixture | exit | why | +//! |---|---|---| +//! | `github-green` | 0 | one tracked snapshot per type; `pr:baseRepo` and `issue:milestone` resolve to IRIs | +//! | `github-merged-no-mergedat` | 1 | `state: MERGED` with no `mergedAt` — refused by the extractor, named | +//! | `github-untracked-milestone` | 1 | an issue naming a milestone no snapshot tracks — FAIL CLOSED, never Unknown | +//! | `github-sha-mismatch` | 1 | a repo whose `sha` disagrees with its `ref` — refused naming both shas | +//! +//! DISCRIMINATION, in both directions: `github-green` must PASS, so a build that refuses every snapshot fails +//! here; the three mutation fixtures must FAIL, so a build that accepts anything fails here too. The fixture copies +//! of the contract are asserted byte-identical to the real one, so weakening the real shapes without the fixtures +//! is caught by the drift test instead. No test here touches the network: the snapshots are the whole input. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn pv_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_pv")) +} + +fn repo_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn fixture(name: &str) -> PathBuf { + repo_root().join("tests/fixtures/ont").join(name) +} + +const FIXTURES: [&str; 4] = [ + "github-green", + "github-merged-no-mergedat", + "github-untracked-milestone", + "github-sha-mismatch", +]; + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +impl Run { + fn all(&self) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + self.code, self.stdout, self.stderr + ) + } + + fn json(&self) -> serde_json::Value { + serde_json::from_str(&self.stdout).expect("json report") + } +} + +fn shapes_on(contracts: &Path, cwd: Option<&Path>) -> Run { + let mut cmd = Command::new(pv_bin()); + cmd.args([ + "lint", + contracts.to_str().expect("utf-8 path"), + "--gate", + "shapes", + "--format", + "json", + ]); + if let Some(dir) = cwd { + cmd.current_dir(dir); + } + let out = cmd.output().expect("failed to spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn on_fixture(name: &str) -> Run { + shapes_on(&fixture(name).join("contracts"), None) +} + +fn violations(r: &Run) -> u64 { + r.json()["extra"]["violations"].as_u64().unwrap_or(0) +} + +#[test] +fn one_tracked_snapshot_per_type_passes_and_every_type_is_counted() { + let r = on_fixture("github-green"); + assert_eq!(r.code, 0, "{}", r.all()); + assert_eq!(violations(&r), 0, "{}", r.all()); + let by = &r.json()["extra"]["by_entity_type"]; + for t in ["repo", "issue", "pull-request", "milestone"] { + assert_eq!(by[t], 1, "by_entity_type[{t}]\n{}", r.all()); + } +} + +#[test] +fn mutation_a_merged_pull_request_with_no_merged_at_fails_naming_the_field() { + let r = on_fixture("github-merged-no-mergedat"); + assert_eq!(r.code, 1, "{}", r.all()); + assert!(r.stdout.contains("PV-ONT-012"), "{}", r.all()); + assert!(r.stdout.contains("`mergedAt` is absent"), "{}", r.all()); + assert!( + r.stdout.contains("paiml__aprender__3706.json"), + "{}", + r.all() + ); +} + +#[test] +fn mutation_an_issue_naming_an_untracked_milestone_fails_closed_never_unknown() { + let r = on_fixture("github-untracked-milestone"); + assert_eq!(r.code, 1, "Fail, not a decline:\n{}", r.all()); + assert_eq!(violations(&r), 1, "{}", r.all()); + assert!(r.stdout.contains("github-issue"), "{}", r.all()); + assert!(r.stdout.contains("milestoneUnresolved"), "{}", r.all()); + assert!(r.stdout.contains("paiml/aprender#3"), "{}", r.all()); +} + +#[test] +fn mutation_a_repo_whose_sha_disagrees_with_its_ref_fails_naming_both() { + let r = on_fixture("github-sha-mismatch"); + assert_eq!(r.code, 1, "{}", r.all()); + assert!(r.stdout.contains("PV-ONT-012"), "{}", r.all()); + assert!( + r.stdout + .contains("aa7c6ef03ee7b7f8d8dc09dc393e97619952d95f") + && r.stdout + .contains("0000000000000000000000000000000000000000"), + "both shas are named\n{}", + r.all() + ); +} + +#[test] +fn the_real_corpus_counts_every_github_type_and_fires_every_control() { + let r = shapes_on(Path::new("contracts"), Some(&repo_root())); + let v = r.json(); + for t in ["repo", "issue", "pull-request", "milestone"] { + assert!( + v["extra"]["by_entity_type"][t].as_u64().unwrap_or(0) >= 1, + "by_entity_type[{t}] is not >= 1\n{}", + r.all() + ); + assert_eq!( + v["pc_extract"][t], "fired", + "pc_extract[{t}]: {}", + v["pc_extract"] + ); + } +} + +#[test] +fn every_fixture_carries_the_real_contract_byte_for_byte() { + let real = std::fs::read(repo_root().join("contracts/github-entities-v1.yaml")) + .expect("the real contract is in the tree"); + for name in FIXTURES { + let copy = std::fs::read(fixture(name).join("contracts/github-entities-v1.yaml")) + .unwrap_or_else(|e| panic!("{name} carries the contract: {e}")); + assert_eq!( + copy, real, + "{name}'s copy of github-entities-v1.yaml has drifted from contracts/" + ); + } +} diff --git a/crates/aprender-contracts-cli/tests/ont5_consistency_gate.rs b/crates/aprender-contracts-cli/tests/ont5_consistency_gate.rs new file mode 100644 index 0000000000..7b8d7b4cd0 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont5_consistency_gate.rs @@ -0,0 +1,178 @@ +//! ONT-5 (PMAT-4074) — `pv-sat` writes the witness, `pv lint --gate ont-consistency` checks it, on the CLI. +//! +//! ONT-001 §5 ONT-5 probe, verbatim in `the_spec_probe_holds_on_the_repo_corpus`. Around it, every exit the gate +//! can give: 0 on the repo's consistent relations, 1 (PV-ONT-022) on `relations-ok` — where `a contradicts d` and +//! both are live, so a gate that never reasons would pass it — 2 for no witness / no Σ / no relation, and 3 for a +//! Σ that does not parse. The witness in every non-repo case is written by the real `pv-sat` binary, so the two +//! halves are tested against each other, not against a hand-built file. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn run(bin: &str, args: &[&str]) -> Run { + let out = Command::new(bin).args(args).output().expect("spawn"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn pv(args: &[&str]) -> Run { + run(env!("CARGO_BIN_EXE_pv"), args) +} + +fn pv_sat(dir: &Path) -> Run { + run(env!("CARGO_BIN_EXE_pv-sat"), &[s(dir)]) +} + +fn s(p: &Path) -> &str { + p.to_str().expect("utf-8 path") +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn repo() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn json_of(r: &Run) -> serde_json::Value { + serde_json::from_str(&r.stdout).unwrap_or_else(|e| panic!("stdout is JSON: {e}\n{}", show(r))) +} + +fn gate(dir: &Path) -> Run { + pv(&[ + "lint", + s(dir), + "--gate", + "ont-consistency", + "--format", + "json", + ]) +} + +/// A copy of `tests/fixtures/ont/relations-ok`. +fn corpus() -> tempfile::TempDir { + let dir = tempfile::tempdir().expect("tempdir"); + for entry in std::fs::read_dir(repo().join("tests/fixtures/ont/relations-ok")).expect("fixture") + { + let entry = entry.expect("entry"); + std::fs::copy(entry.path(), dir.path().join(entry.file_name())).expect("copy"); + } + dir +} + +#[test] +fn the_spec_probe_holds_on_the_repo_corpus() { + // F-7: the reasoner is not reachable from the library. + let lib = std::fs::read_to_string(repo().join("crates/aprender-contracts/src/lib.rs")) + .expect("lib.rs"); + assert!(!lib.contains("mod sat"), "the lib reaches the reasoner"); + let r = gate(&repo().join("contracts")); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + assert!( + v["checkable_n"].as_u64().is_some_and(|n| n > 0), + "{}", + show(&r) + ); + assert_eq!(v["pc_checker"], "fired", "{}", show(&r)); + assert_eq!(v["witness"]["pc_reasoner"], "fired", "{}", show(&r)); + assert_eq!(v["witness"]["stale"], false, "{}", show(&r)); + assert_eq!(v["verdict"], "Pass", "{}", show(&r)); +} + +#[test] +fn an_inconsistent_corpus_fails_on_the_witness_pv_sat_wrote() { + let d = corpus(); + let w = pv_sat(d.path()); + assert_eq!(w.code, 0, "{}", show(&w)); + assert!(w.stdout.contains("unsat_core"), "{}", show(&w)); + let r = gate(d.path()); + assert_eq!(r.code, 1, "{}", show(&r)); + assert!(r.stdout.contains("PV-ONT-022"), "{}", show(&r)); + assert_eq!( + json_of(&r)["core"], + serde_json::Value::from(vec!["a", "d"]), + "{}", + show(&r) + ); +} + +#[test] +fn an_edit_after_the_witness_is_stale_and_says_make_contracts() { + let d = corpus(); + assert_eq!(pv_sat(d.path()).code, 0); + let a = d.path().join("a.yaml"); + let text = std::fs::read_to_string(&a).expect("a.yaml"); + std::fs::write(&a, text.replace(" contradicts: [d]\n", "")).expect("edit"); + let r = gate(d.path()); + assert_eq!(r.code, 2, "{}", show(&r)); + assert!(r.stderr.contains("make contracts"), "{}", show(&r)); + assert!(r.stderr.contains("WitnessStale"), "{}", show(&r)); + + // Regenerating is the whole remedy: the edited corpus is consistent, and says so. + assert_eq!(pv_sat(d.path()).code, 0); + let r = gate(d.path()); + assert_eq!(r.code, 0, "{}", show(&r)); + let files = std::fs::read_dir(d.path().join("witness")) + .expect("witness dir") + .count(); + assert_eq!(files, 1, "the old graph's witness was not pruned"); +} + +#[test] +fn nothing_to_check_declines_and_a_broken_sigma_is_exit_3() { + let d = corpus(); + for name in ["a.yaml", "b.yaml"] { + let p = d.path().join(name); + let text = std::fs::read_to_string(&p).expect("read"); + let cut = text.find("relations:").expect("relations block"); + std::fs::write(&p, &text[..cut]).expect("write"); + } + let r = gate(d.path()); + assert_eq!(r.code, 2, "{}", show(&r)); + assert!(r.stderr.contains("NoCheckable"), "{}", show(&r)); + assert_eq!( + pv_sat(d.path()).code, + 2, + "pv-sat must not write a witness for zero clauses" + ); + + let d = corpus(); + std::fs::remove_file(d.path().join("ontology.yaml")).expect("rm"); + assert_eq!(gate(d.path()).code, 2); + + let d = corpus(); + std::fs::write(d.path().join("ontology.yaml"), "roles: [unclosed\n").expect("write"); + let r = gate(d.path()); + assert_eq!(r.code, 3, "{}", show(&r)); +} + +/// R-8: computed in every `pv lint` run, and not armed until the cop arms it. +#[test] +fn the_full_run_computes_the_gate_unarmed() { + let r = pv(&["lint", s(&repo().join("contracts")), "--format", "json"]); + let v = json_of(&r); + let gate = v["gates"] + .as_array() + .and_then(|g| g.iter().find(|x| x["name"] == "ont-consistency")) + .unwrap_or_else(|| panic!("ont-consistency not computed\n{}", show(&r))); + assert_eq!(gate["verdict"], "Pass", "{gate}"); + let not_armed: Vec<&str> = v["not_armed"] + .as_array() + .map(|a| a.iter().filter_map(|x| x.as_str()).collect()) + .unwrap_or_default(); + assert!(not_armed.contains(&"ont-consistency"), "{not_armed:?}"); +} diff --git a/crates/aprender-contracts-cli/tests/ont6_lint_verdict.rs b/crates/aprender-contracts-cli/tests/ont6_lint_verdict.rs index a3388229d1..97166dcbd8 100644 --- a/crates/aprender-contracts-cli/tests/ont6_lint_verdict.rs +++ b/crates/aprender-contracts-cli/tests/ont6_lint_verdict.rs @@ -47,9 +47,27 @@ fn repo_contracts() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts") } +struct Corpus { + dir: PathBuf, + _root: tempfile::TempDir, +} + +impl Corpus { + fn path(&self) -> &Path { + &self.dir + } +} + /// A corpus directory holding the PVL-1 control contract, and optionally a `lint-baseline.json`. -fn corpus(baseline: Option<&str>) -> tempfile::TempDir { - let d = tempfile::tempdir().expect("corpus dir is creatable"); +/// Nested one level inside a private tempdir: `pv lint` takes the dir's PARENT as the +/// project root, and a bare tempdir's parent is the shared `/tmp` (#4207). +fn corpus(baseline: Option<&str>) -> Corpus { + let root = tempfile::tempdir().expect("corpus dir is creatable"); + let d = Corpus { + dir: root.path().join("contracts"), + _root: root, + }; + std::fs::create_dir(d.path()).expect("nested corpus dir is creatable"); std::fs::copy( repo_contracts().join("softmax-kernel-v1.yaml"), d.path().join("softmax-kernel-v1.yaml"), @@ -109,12 +127,9 @@ fn explicit_empty_armed_set_declines_r2() { #[test] fn a_failing_armed_gate_rejects_at_exit_1() { - let d = tempfile::tempdir().expect("corpus dir"); - std::fs::copy( - repo_contracts().join("softmax-kernel-v1.yaml"), - d.path().join("softmax-kernel-v1.yaml"), - ) - .expect("control copies"); + // Through `corpus()`, not a bare tempdir: on a host with a stray `/tmp/scripts/` + // baseline, PV-DUP-002 would also reject, and this test could pass for the wrong reason. + let d = corpus(None); std::fs::write( d.path().join("broken-v1.yaml"), "metadata:\n version: not-a-contract\n", @@ -265,6 +280,22 @@ fn json_report_carries_the_lattice() { serde_json::Value::String("shapes".into()), // ONT-7's gate: same R-8 shape. serde_json::Value::String("valid-under".into()), + // PVL-001 EV-11's two ratchets: same R-8 shape. + serde_json::Value::String("theorem-pairing".into()), + serde_json::Value::String("depends-on-present".into()), + // PVL-001 EV-8a's ratchet: same R-8 shape. + serde_json::Value::String("proved-is-derived".into()), + // PVL-001 EV-7b's gate: same R-8 shape. + serde_json::Value::String("challenge-fresh".into()), + // ONT-5 (gate 19) likewise: computed everywhere, armed by nobody yet. + serde_json::Value::String("ont-consistency".into()), + // ONT-4e (gate 18) likewise. + serde_json::Value::String("refines".into()), + // ONT-3a's bindings gate and ONT-3b's refinement gate likewise. + serde_json::Value::String("bindings".into()), + serde_json::Value::String("refinement".into()), + // ONT-8's gate: same R-8 shape. + serde_json::Value::String("evidence".into()), ]), "{}", show(&r) @@ -318,9 +349,11 @@ fn repo_baseline_arms_the_eight_ruled_gates_and_every_later_row_that_armed_one() "composition", "sigma", "relations", - "shapes" + "shapes", + "proved-is-derived" ] .to_vec(), - "the ruled 8 plus the gates later rows armed (ONT-2b: sigma; ONT-4: relations; ONT-4b: shapes)" + "the ruled 8 plus the gates later rows armed (ONT-2b: sigma; ONT-4: relations; ONT-4b: shapes; \ + PVL-001 EV-8a: proved-is-derived)" ); } diff --git a/crates/aprender-contracts-cli/tests/ont8_evidence_gate.rs b/crates/aprender-contracts-cli/tests/ont8_evidence_gate.rs new file mode 100644 index 0000000000..192576e4e2 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont8_evidence_gate.rs @@ -0,0 +1,213 @@ +//! ONT-8 (PMAT-4077) — `pv lint --gate evidence`: one evidence block, PROV-O names, one L-enum, every entity type. +//! +//! The row's probe, verbatim (paiml/infra `docs/specifications/paiml-ontology.md` v4.14 :667): +//! `pv lint contracts/ --gate evidence --format json … jq -e '.verdict=="Pass" and .levels_source=="enum" and +//! .entity_types_checked>=3'`. The first test is that probe on the real corpus. +//! +//! The rest build a corpus in a tempdir around the repo's own Σ, so every rule is judged against the agents and +//! levels the corpus really declares: +//! +//! | corpus | expected | +//! |---|---| +//! | readme + apr-model + code, one identical block | exit 0, Pass, `entity_types_checked` 3 — R-17 | +//! | `author:` under `provenance` | exit 1, PV-ONT-017 — F-16 | +//! | `level: L0` | exit 1, PV-ONT-018 — EV-3's one enum has no L0 | +//! | `wasAttributedTo: orchestrator` | exit 1, PV-ONT-021 — not a Σ agent | +//! | no evidence block anywhere | exit 2, decline — R-2 | +//! | no Σ | exit 2, decline | +//! +//! DISCRIMINATION: the R-17 corpus passes at exit 0 and every reject names its own rule id, so a build that +//! refuses everything, or refuses with the wrong rule, fails this file. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn pv(args: &[&str]) -> Run { + let scratch = tempfile::tempdir().expect("scratch cwd is creatable"); + let out = Command::new(env!("CARGO_BIN_EXE_pv")) + .current_dir(scratch.path()) + .args(args) + .output() + .expect("failed to spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn repo_contracts() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts") +} + +fn gate(dir: &Path) -> Run { + pv(&[ + "lint", + dir.to_str().expect("utf-8 path"), + "--gate", + "evidence", + "--format", + "json", + ]) +} + +fn json_of(r: &Run) -> serde_json::Value { + serde_json::from_str(&r.stdout).unwrap_or_else(|e| panic!("stdout is JSON: {e}\n{}", show(r))) +} + +const FULL: &str = "level: L2\nmark: C\nprovenance:\n wasGeneratedBy: {command: \"pv extract README.md\"}\n wasAttributedTo: pv\n generatedAtTime: \"2026-09-24T00:00:00Z\"\n"; + +fn contract(entity_type: &str, evidence: &str) -> String { + let indented: String = evidence.lines().map(|l| format!(" {l}\n")).collect(); + format!( + "metadata:\n version: \"1.0.0\"\nentity:\n type: {entity_type}\nevidence:\n{indented}" + ) +} + +/// A corpus around the repo's Σ. `with_sigma: false` leaves Σ out. +fn corpus(with_sigma: bool, files: &[(&str, String)]) -> tempfile::TempDir { + let tmp = tempfile::tempdir().expect("corpus dir is creatable"); + if with_sigma { + std::fs::copy( + repo_contracts().join("ontology.yaml"), + tmp.path().join("ontology.yaml"), + ) + .expect("Σ copies"); + } + for (name, body) in files { + std::fs::write(tmp.path().join(name), body).expect("contract writes"); + } + tmp +} + +/// The rejects: exit 1, verdict Fail, and the report names exactly this rule. +fn assert_rejects(evidence: &str, rule: &str) { + let tmp = corpus(true, &[("c-v1.yaml", contract("code", evidence))]); + let r = gate(tmp.path()); + assert_eq!(r.code, 1, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["verdict"], "Fail", "{}", show(&r)); + let rules: Vec<_> = v["findings"] + .as_array() + .expect("findings is an array") + .iter() + .map(|f| f["rule_id"].clone()) + .collect(); + assert_eq!( + rules, + [serde_json::Value::String(rule.into())], + "{}", + show(&r) + ); +} + +#[test] +fn the_row_probe_passes_on_the_real_corpus() { + let r = gate(&repo_contracts()); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["gate"], "evidence", "{}", show(&r)); + assert_eq!(v["verdict"], "Pass", "{}", show(&r)); + assert_eq!(v["levels_source"], "enum", "{}", show(&r)); + assert!( + v["entity_types_checked"] + .as_u64() + .expect("entity_types_checked is a number") + >= 3, + "{}", + show(&r) + ); +} + +#[test] +fn r17_readme_model_and_code_contracts_pass_the_same_gate() { + let tmp = corpus( + true, + &[ + ("readme-v1.yaml", contract("readme", FULL)), + ("model-v1.yaml", contract("apr-model", FULL)), + ("code-v1.yaml", contract("code", FULL)), + ], + ); + let r = gate(tmp.path()); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["verdict"], "Pass", "{}", show(&r)); + assert_eq!(v["entity_types_checked"], 3, "{}", show(&r)); + assert_eq!(v["contracts_with_evidence"], 3, "{}", show(&r)); +} + +#[test] +fn f16_author_is_rejected() { + assert_rejects( + &FULL.replace( + " wasAttributedTo: pv\n", + " wasAttributedTo: pv\n author: noah\n", + ), + "PV-ONT-017", + ); +} + +#[test] +fn level_l0_is_rejected() { + assert_rejects(&FULL.replace("L2", "L0"), "PV-ONT-018"); +} + +#[test] +fn an_undeclared_agent_is_rejected() { + assert_rejects( + &FULL.replace("wasAttributedTo: pv", "wasAttributedTo: orchestrator"), + "PV-ONT-021", + ); +} + +#[test] +fn no_evidence_anywhere_is_a_decline() { + let tmp = corpus( + true, + &[("c-v1.yaml", "metadata:\n version: \"1.0.0\"\n".to_string())], + ); + let r = gate(tmp.path()); + assert_eq!(r.code, 2, "{}", show(&r)); + assert!(r.stderr.contains("no evidence block"), "{}", show(&r)); +} + +#[test] +fn no_sigma_is_a_decline() { + let tmp = corpus(false, &[("c-v1.yaml", contract("code", FULL))]); + let r = gate(tmp.path()); + assert_eq!(r.code, 2, "{}", show(&r)); +} + +/// R-8: computed in every `pv lint` run, and reported as not armed until the baseline names it. +#[test] +fn a_full_run_computes_the_gate() { + let r = pv(&[ + "lint", + repo_contracts().to_str().expect("utf-8 path"), + "--format", + "json", + ]); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + let g = v["gates"] + .as_array() + .expect("gates is an array") + .iter() + .find(|g| g["name"] == "evidence") + .unwrap_or_else(|| panic!("no evidence gate in a full run\n{}", show(&r))); + assert_eq!(g["passed"], true, "{}", show(&r)); +} diff --git a/crates/aprender-contracts-cli/tests/ont9_self_contract.rs b/crates/aprender-contracts-cli/tests/ont9_self_contract.rs new file mode 100644 index 0000000000..ef3970677a --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont9_self_contract.rs @@ -0,0 +1,233 @@ +//! ONT-9 (#4078) — the ontology has a contract on itself: `contracts/ont-self-v1.yaml`, `entity.type: pv-contract`, +//! `ref: contracts/`. +//! +//! ONT-001 §5 ONT-9 probe, minus the ledger's `merged`, in `the_spec_probe_holds_on_the_repo`. Around it: the +//! contract names a real test for every falsifier it lists (a contract about contracts that cites a ghost is the +//! defect it exists to catch); it does not claim the L3 rung before `cargo kani` has run; and the corpus-level +//! mutation the row names — introduce a cycle — turns the `relations` gate RED on the CLI, including the mixed +//! `depends_on ∪ supersedes` cycle the per-role sweep used to pass. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn pv(args: &[&str]) -> Run { + let out = Command::new(env!("CARGO_BIN_EXE_pv")) + .args(args) + .output() + .expect("spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn repo() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn s(p: &Path) -> &str { + p.to_str().expect("utf-8 path") +} + +const CONTRACT: &str = "contracts/ont-self-v1.yaml"; +const WITNESS_RS: &str = "crates/aprender-contracts/src/ontology/witness.rs"; + +fn contract() -> serde_yaml::Value { + let text = std::fs::read_to_string(repo().join(CONTRACT)) + .unwrap_or_else(|e| panic!("{CONTRACT} is readable: {e}")); + serde_yaml::from_str(&text).unwrap_or_else(|e| panic!("{CONTRACT} parses: {e}")) +} + +fn text(rel: &str) -> String { + std::fs::read_to_string(repo().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}")) +} + +/// §5 ONT-9's probe: `tracked contracts/ont-self-v1.yaml && rc_is 0 "$PV" validate contracts/ont-self-v1.yaml && +/// present 'fn ont_planted' …/witness.rs && present 'KANI-ONT-9-1' …/witness.rs` (`merged ONT-9` is the ledger's). +#[test] +fn the_spec_probe_holds_on_the_repo() { + // CI runs this in a container whose uid does not own the checkout, and actions/checkout marked it safe only in + // the host's git config — git refuses the repo ("dubious ownership") and the probe read "not tracked". git 2.34 + // honours `safe.directory` only from global/system config (not `-c`, not GIT_CONFIG_COUNT), so hand it one. + let cfg = tempfile::NamedTempFile::new().expect("temp gitconfig"); + std::fs::write(cfg.path(), "[safe]\n\tdirectory = *\n").expect("write temp gitconfig"); + let tracked = Command::new("git") + .args(["ls-files", "--error-unmatch", CONTRACT]) + .env("GIT_CONFIG_GLOBAL", cfg.path()) + .current_dir(repo()) + .output() + .expect("spawn git"); + assert!( + tracked.status.success(), + "{CONTRACT} is not tracked: {}", + String::from_utf8_lossy(&tracked.stderr) + ); + let r = pv(&["validate", s(&repo().join(CONTRACT))]); + assert_eq!(r.code, 0, "{}", show(&r)); + let w = text(WITNESS_RS); + assert!( + w.contains("fn ont_planted"), + "no `fn ont_planted` in {WITNESS_RS}" + ); + assert!( + w.contains("KANI-ONT-9-1"), + "no `KANI-ONT-9-1` in {WITNESS_RS}" + ); +} + +/// The contract is about the corpus itself (§5 ONT-9, B.16), and binds the checker and the gates it governs. +#[test] +fn the_contract_is_anchored_on_the_corpus_and_depends_on_what_it_governs() { + let c = contract(); + assert_eq!(c["entity"]["type"].as_str(), Some("pv-contract")); + assert_eq!(c["entity"]["ref"].as_str(), Some("contracts/")); + let deps: Vec<&str> = c["relations"]["depends_on"] + .as_sequence() + .expect("relations.depends_on is a list") + .iter() + .filter_map(serde_yaml::Value::as_str) + .collect(); + for d in [ + "ont-consistency-v1", + "ont-relations-v1", + "ont-verdict-lattice-v1", + ] { + assert!( + deps.contains(&d), + "ont-self-v1 must depend_on {d}: {deps:?}" + ); + } +} + +/// Every falsifier names a test that exists. `cargo test … ::` whose `` is no `fn` anywhere is +/// a filter that matches zero tests and exits 0 — a ghost binding, the defect a contract on contracts must not +/// carry itself. +#[test] +fn every_falsifier_names_a_test_function_that_exists() { + let c = contract(); + let tests = c["falsification_tests"] + .as_sequence() + .expect("falsification_tests is a list"); + assert!(tests.len() >= 7, "one falsifier per obligation at least"); + let src = crate_sources(); + for t in tests { + let cmd = t["test"].as_str().expect("test is a string"); + let last = cmd.split_whitespace().last().expect("non-empty"); + if last.starts_with("--") || !cmd.contains("--lib") { + // `--test ` or `--bin` — the target is the binding; check the file exists. + let target = cmd.split_whitespace().skip_while(|w| *w != "--test").nth(1); + if let Some(target) = target { + assert!( + src.iter() + .any(|(p, _)| p.ends_with(&format!("tests/{target}.rs"))), + "{cmd}: no tests/{target}.rs" + ); + if let Some(name) = cmd.split_whitespace().nth(6) { + assert!( + src.iter() + .any(|(_, body)| body.contains(&format!("fn {name}("))), + "{cmd}: no `fn {name}(`" + ); + } + } + continue; + } + let name = last.rsplit("::").next().expect("a path"); + assert!( + src.iter() + .any(|(_, body)| body.contains(&format!("fn {name}("))), + "{cmd}: no `fn {name}(` in aprender-contracts{{,-cli}} — a ghost binding" + ); + } +} + +fn crate_sources() -> Vec<(String, String)> { + let mut out = Vec::new(); + let mut stack = vec![ + repo().join("crates/aprender-contracts/src"), + repo().join("crates/aprender-contracts-cli/src"), + repo().join("crates/aprender-contracts-cli/tests"), + ]; + while let Some(d) = stack.pop() { + for e in std::fs::read_dir(&d).into_iter().flatten().flatten() { + let p = e.path(); + if p.is_dir() { + stack.push(p); + } else if p.extension().is_some_and(|x| x == "rs") { + let body = std::fs::read_to_string(&p).unwrap_or_default(); + out.push((p.to_string_lossy().into_owned(), body)); + } + } + } + out +} + +/// The L3 rung is not claimed before it ran. `KANI-ONT-9-1` is a real `#[kani::proof]` in witness.rs and is +/// listed, and while `proof.status` is `declared` the summary counts zero Kani-proved obligations. Flipping one +/// without the other — a proof claimed on a run nobody made — is RED here. +#[test] +fn the_kani_rung_is_declared_not_claimed() { + let c = contract(); + let h = c["kani_harnesses"] + .as_sequence() + .expect("kani_harnesses is a list"); + assert!( + h.iter().any(|x| x["id"].as_str() == Some("KANI-ONT-9-1") + && x["harness"].as_str() == Some("kani_ont_9_1")), + "KANI-ONT-9-1 → kani_ont_9_1 is listed" + ); + let w = text(WITNESS_RS); + assert!( + w.contains("#[kani::proof]") && w.contains("fn kani_ont_9_1()"), + "the harness is a real #[kani::proof] fn" + ); + let status = c["proof"]["status"].as_str().expect("proof.status"); + let l3 = c["verification_summary"]["l3_kani_proved"] + .as_u64() + .expect("l3_kani_proved"); + match status { + "declared" => assert_eq!(l3, 0, "declared, so nothing is Kani-proved yet"), + "proved" => assert!(l3 >= 1, "proved, so the summary counts it"), + other => panic!("proof.status `{other}` is neither declared nor proved"), + } +} + +/// Mutation "introduce a cycle → RED", at the CLI: the repo's relations (ont-self-v1 included) are acyclic and +/// exit 0; a per-role cycle and a `depends_on ∪ supersedes` cycle each exit 1 with PV-ONT-009. +#[test] +fn a_cycle_in_the_corpus_turns_the_relations_gate_red() { + let live = pv(&["lint", s(&repo().join("contracts")), "--gate", "relations"]); + assert_eq!(live.code, 0, "{}", show(&live)); + for (fixture, path) in [ + ("relations-cycle", "a -> b -> c -> a"), + ("relations-mixed-cycle", "a -> b -> a"), + ] { + let r = pv(&[ + "lint", + s(&repo().join("tests/fixtures/ont").join(fixture)), + "--gate", + "relations", + ]); + let all = format!("{}{}", r.stdout, r.stderr); + assert_eq!(r.code, 1, "{fixture}: {}", show(&r)); + assert!( + all.contains("PV-ONT-009") && all.contains(path), + "{fixture}: {}", + show(&r) + ); + } +} diff --git a/crates/aprender-contracts-cli/tests/pvl_challenge.rs b/crates/aprender-contracts-cli/tests/pvl_challenge.rs new file mode 100644 index 0000000000..3c0a56e35f --- /dev/null +++ b/crates/aprender-contracts-cli/tests/pvl_challenge.rs @@ -0,0 +1,199 @@ +//! PVL-001 EV-7a (#4200) — `pv challenge gen | check`, end to end on a built-in-a-tempdir Lean tree. +//! +//! The RED rows carry the spec's mutation: a hand-edited statement in `Challenge/`. The Lean elaboration of the +//! generated files is not run here (CI has no Mathlib); it was measured on lambda (see #4200). + +use std::path::PathBuf; +use std::process::Command; + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +impl Run { + fn show(&self) -> String { + format!( + "rc {}\n--- stdout\n{}\n--- stderr\n{}", + self.code, self.stdout, self.stderr + ) + } +} + +const CHALLENGE: &str = "lean/Challenge/gelu-v1.lean"; + +struct Fx { + dir: tempfile::TempDir, +} + +impl Fx { + /// `gelu_bound` is bound by exact name; `unbound` by nothing. + fn new() -> Self { + let fx = Self { + dir: tempfile::tempdir().expect("tempdir"), + }; + fx.write( + "lean/ProvableContracts.lean", + "import ProvableContracts.Theorems.Gelu.Bound\n", + ); + fx.write( + "lean/ProvableContracts/Theorems/Gelu/Bound.lean", + "namespace ProvableContracts.Gelu\n\ + theorem gelu_bound (x : Nat) :\n x ≤ x + 1 := Nat.le_succ x\n\ + theorem unbound : True := trivial\n\ + end ProvableContracts.Gelu\n", + ); + fx.write( + "contracts/gelu-v1.yaml", + "equations:\n e:\n lean_theorem: ProvableContracts.Gelu.gelu_bound\n", + ); + fx + } + + fn path(&self, rel: &str) -> PathBuf { + self.dir.path().join(rel) + } + + fn write(&self, rel: &str, text: &str) { + let p = self.path(rel); + std::fs::create_dir_all(p.parent().expect("parent")).expect("mkdir"); + std::fs::write(p, text).expect("write"); + } + + fn read(&self, rel: &str) -> String { + std::fs::read_to_string(self.path(rel)).expect("read") + } + + fn pv(&self, action: &str) -> Run { + let out = Command::new(env!("CARGO_BIN_EXE_pv")) + .current_dir(self.dir.path()) + .args(["challenge", action, "contracts", "lean"]) + .output() + .expect("spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } + } + + fn generated(&self) { + let g = self.pv("gen"); + assert_eq!(g.code, 0, "{}", g.show()); + } +} + +#[test] +fn gen_writes_the_statement_with_sorry_and_check_is_then_fresh() { + let fx = Fx::new(); + fx.generated(); + let text = fx.read(CHALLENGE); + assert!( + text.contains( + "theorem _root_.PvlChallenge.ProvableContracts.Gelu.gelu_bound (x : Nat) : x ≤ x + 1 := sorry\n" + ), + "{text}" + ); + assert!( + text.contains("namespace ProvableContracts.Gelu\n"), + "{text}" + ); + assert!(!text.contains("unbound"), "{text}"); + let c = fx.pv("check"); + assert_eq!(c.code, 0, "{}", c.show()); + assert!( + c.stdout.contains("challenge-fresh: 1 file(s)"), + "{}", + c.show() + ); +} + +#[test] +fn gen_is_deterministic() { + let fx = Fx::new(); + fx.generated(); + let first = fx.read(CHALLENGE); + fx.generated(); + assert_eq!(fx.read(CHALLENGE), first); +} + +/// EV-7a's mutation: hand-edit a statement → RED, naming the file and the line. +#[test] +fn a_hand_edited_statement_is_red() { + let fx = Fx::new(); + fx.generated(); + let text = fx.read(CHALLENGE); + fx.write(CHALLENGE, &text.replace("x ≤ x + 1", "x ≤ x + 2")); + let c = fx.pv("check"); + assert_eq!(c.code, 1, "{}", c.show()); + assert!( + c.stdout + .contains("FAIL STALE differs Challenge/gelu-v1.lean:"), + "{}", + c.show() + ); +} + +/// The solution's statement changing (a weakened theorem) leaves the committed challenge stale → RED. +#[test] +fn a_changed_solution_statement_is_red_until_regenerated() { + let fx = Fx::new(); + fx.generated(); + let src = "lean/ProvableContracts/Theorems/Gelu/Bound.lean"; + let text = fx.read(src); + fx.write(src, &text.replace("x ≤ x + 1", "0 ≤ x")); + assert_eq!(fx.pv("check").code, 1); + fx.generated(); + assert_eq!(fx.pv("check").code, 0); +} + +#[test] +fn an_extra_or_missing_challenge_file_is_red() { + let fx = Fx::new(); + fx.generated(); + fx.write("lean/Challenge/Sub/stray.lean", ""); + let c = fx.pv("check"); + assert_eq!(c.code, 1, "{}", c.show()); + assert!( + c.stdout.contains("extra Challenge/Sub/stray.lean"), + "{}", + c.show() + ); + fx.generated(); + assert!(!fx.path("lean/Challenge/Sub/stray.lean").exists()); + std::fs::remove_file(fx.path(CHALLENGE)).expect("rm"); + let c = fx.pv("check"); + assert_eq!(c.code, 1, "{}", c.show()); + assert!( + c.stdout.contains("missing Challenge/gelu-v1.lean"), + "{}", + c.show() + ); +} + +/// Zero challenges is a decline (rc 2), never "fresh": nothing is pinned. +#[test] +fn zero_challenges_declines() { + let fx = Fx::new(); + fx.write( + "contracts/gelu-v1.yaml", + "equations:\n e:\n formula: x\n", + ); + for action in ["gen", "check"] { + let r = fx.pv(action); + assert_eq!(r.code, 2, "{action}: {}", r.show()); + assert!( + r.stderr.contains("zero challenges"), + "{action}: {}", + r.show() + ); + } +} + +#[test] +fn an_unloadable_tree_declines() { + let fx = Fx::new(); + std::fs::remove_file(fx.path("lean/ProvableContracts.lean")).expect("rm"); + assert_eq!(fx.pv("check").code, 2); +} diff --git a/crates/aprender-contracts-cli/tests/pvl_comparator.rs b/crates/aprender-contracts-cli/tests/pvl_comparator.rs new file mode 100644 index 0000000000..001b7df07d --- /dev/null +++ b/crates/aprender-contracts-cli/tests/pvl_comparator.rs @@ -0,0 +1,295 @@ +//! PVL-001 EV-7b (#4201) — `pv discharge check --comparator`, end to end with a stub `lake` on PATH. +//! +//! The spec's RED: a solution that proves a WEAKER statement than its Challenge pins → rc 1 (MISMATCH), and a +//! solution on `sorry` → rc 1. No Challenge file, or zero rows → rc 2, never a pass. The stub replays rows the +//! committed `scripts/Comparator.lean` produced on a real Lean tree; the real run needs the built tree and +//! Mathlib, and is measured on lambda (see #4201). The last test pins the committed script to the row shape the +//! parser reads, so the two cannot drift apart silently. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +use provable_contracts::discharge::comparator::{CHALLENGE_NS, COMPARATOR, SORRY_AXIOM}; + +fn pv_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_pv")) +} + +/// sha256 hashes `scripts/Comparator.lean` printed for `gelu_pos (x : Nat) (h : 0 < x) : 0 < g x`, and for the +/// same theorem with its hypothesis strengthened to `1 < x` (measured with Lean v4.29.0-rc4, 2026-09-24). +const PINNED: &str = "902d0aa1835a8af24db87013bf140f155ab57b9002d3665f37294f2a3623b6fd"; +const WEAKER: &str = "66afc7f44f64333e7cc3a4c495e91ce8d3e376d8e813accc9542f2a9f5397224"; +const NAME: &str = "ProvableContracts.Gelu.gelu_pos"; + +struct Fx { + dir: tempfile::TempDir, +} + +impl Fx { + /// A one-theorem tree with `Axioms.lean` generated, a Challenge file and the comparator script in place. + fn new() -> Self { + let fx = Self { + dir: tempfile::tempdir().expect("tempdir"), + }; + fx.write( + "lean/ProvableContracts.lean", + "import ProvableContracts.Theorems.Gelu.Bound\n", + ); + fx.write( + "lean/ProvableContracts/Theorems/Gelu/Bound.lean", + "namespace ProvableContracts.Gelu\ntheorem gelu_bound : True := trivial\nend ProvableContracts.Gelu\n", + ); + fx.write( + "contracts/gelu-v1.yaml", + "equations:\n e:\n lean_theorem: Theorems.Gelu\n", + ); + fx.write( + "lean/Challenge/gelu-v1.lean", + &format!("-- EV-7a writes this\ntheorem _root_.{CHALLENGE_NS}.{NAME} (x : Nat) (h : 0 < x) : 0 < g x := sorry\n"), + ); + fx.write( + &format!("lean/{COMPARATOR}"), + "-- the real one is committed\n", + ); + let g = fx.pv(&["discharge", "gen-axioms", "lean"]); + assert_eq!(g.0, 0, "gen-axioms: {}", g.1); + fx + } + + fn path(&self, rel: &str) -> PathBuf { + self.dir.path().join(rel) + } + + fn write(&self, rel: &str, text: &str) { + let p = self.path(rel); + std::fs::create_dir_all(p.parent().expect("parent")).expect("mkdir"); + std::fs::write(p, text).expect("write"); + } + + /// `bin/lake` on the test's PATH: `env lean --run