diff --git a/ci/explicit-test-commands.d/601-aprender-contracts-cli-repo-tree-pv.cmd b/ci/explicit-test-commands.d/601-aprender-contracts-cli-repo-tree-pv.cmd new file mode 100644 index 0000000000..92252f5198 --- /dev/null +++ b/ci/explicit-test-commands.d/601-aprender-contracts-cli-repo-tree-pv.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test repo_tree_pv diff --git a/crates/aprender-contracts-cli/tests/ev11_lint_ratchets.rs b/crates/aprender-contracts-cli/tests/ev11_lint_ratchets.rs index 6d76110cf4..51225fe5e1 100644 --- a/crates/aprender-contracts-cli/tests/ev11_lint_ratchets.rs +++ b/crates/aprender-contracts-cli/tests/ev11_lint_ratchets.rs @@ -111,41 +111,6 @@ fn lint(root: &Path, gates: &[&str]) -> Run { 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(); diff --git a/crates/aprender-contracts-cli/tests/ont2b_sigma_gate.rs b/crates/aprender-contracts-cli/tests/ont2b_sigma_gate.rs index 4dc7d094c3..129582f28d 100644 --- a/crates/aprender-contracts-cli/tests/ont2b_sigma_gate.rs +++ b/crates/aprender-contracts-cli/tests/ont2b_sigma_gate.rs @@ -52,10 +52,6 @@ fn fixture(name: &str) -> PathBuf { .join(name) } -fn repo_contracts() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts") -} - fn s(p: &Path) -> String { p.to_str().expect("utf-8 path").to_string() } @@ -64,16 +60,6 @@ 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))) } -#[test] -fn the_repo_corpus_passes_the_sigma_gate() { - let dir = s(&repo_contracts()); - let r = pv(&["lint", &dir, "--gate", "sigma", "--format", "json"]); - assert_eq!(r.code, 0, "{}", show(&r)); - let v = json_of(&r); - assert_eq!(v["gate"], "sigma", "{}", show(&r)); - assert_eq!(v["verdict"], "Pass", "{}", show(&r)); -} - #[test] fn a_well_formed_fixture_corpus_passes() { let dir = s(&fixture("sigma-ok")); @@ -147,7 +133,7 @@ fn a_corpus_without_sigma_declines_at_exit_2() { #[test] fn an_unknown_gate_name_is_refused() { - let dir = s(&repo_contracts()); + let dir = s(&fixture("sigma-ok")); let r = pv(&["lint", &dir, "--gate", "no-such-gate", "--format", "json"]); assert_ne!(r.code, 0, "{}", show(&r)); assert!( @@ -160,7 +146,15 @@ fn an_unknown_gate_name_is_refused() { /// `--gate` selects ONE gate: the report is that gate's, not the whole run's. #[test] fn the_gate_mode_reports_only_the_named_gate() { - let dir = s(&repo_contracts()); + // One real contract in a tempdir (the fixtures are sigma corpora, not valid under `validate`), not the + // whole repo tree: #5914. + let tmp = tempfile::tempdir().expect("tempdir"); + std::fs::copy( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts/softmax-kernel-v1.yaml"), + tmp.path().join("softmax-kernel-v1.yaml"), + ) + .expect("control contract copied"); + let dir = s(tmp.path()); let r = pv(&["lint", &dir, "--gate", "validate", "--format", "json"]); assert_eq!(r.code, 0, "{}", show(&r)); let v = json_of(&r); diff --git a/crates/aprender-contracts-cli/tests/ont4_relations_gate.rs b/crates/aprender-contracts-cli/tests/ont4_relations_gate.rs index 1fc8f2b871..fb118f768a 100644 --- a/crates/aprender-contracts-cli/tests/ont4_relations_gate.rs +++ b/crates/aprender-contracts-cli/tests/ont4_relations_gate.rs @@ -51,10 +51,6 @@ fn fixture(name: &str) -> PathBuf { .join(name) } -fn repo_contracts() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts") -} - fn s(p: &Path) -> String { p.to_str().expect("utf-8 path").to_string() } @@ -67,27 +63,6 @@ fn gate(dir: &Path) -> Run { pv(&["lint", &s(dir), "--gate", "relations", "--format", "json"]) } -#[test] -fn the_repo_corpus_passes_the_relations_gate() { - let r = gate(&repo_contracts()); - assert_eq!(r.code, 0, "{}", show(&r)); - let v = json_of(&r); - assert_eq!(v["gate"], "relations", "{}", show(&r)); - assert_eq!(v["verdict"], "Pass", "{}", show(&r)); - // R-2 by construction: the real corpus must carry typed relations, or this would be a decline - assert!( - v["extra"]["relations_n"].as_u64().unwrap_or(0) > 0, - "the real corpus has typed relations\n{}", - show(&r) - ); - // the legacy field is measured, never rewritten: 188 contracts carry it at 3409b29d - assert!( - v["extra"]["legacy_depends_on"].as_u64().unwrap_or(0) > 100, - "metadata.depends_on is counted\n{}", - show(&r) - ); -} - #[test] fn four_relations_are_accepted_and_contradicts_is_read_both_ways() { let r = gate(&fixture("relations-ok")); @@ -174,24 +149,3 @@ fn the_legacy_ratchet_rejects_a_rise_and_passes_a_hold() { assert_eq!(rise.code, 1, "{}", show(&rise)); assert!(rise.stdout.contains("PV-ONT-010"), "{}", show(&rise)); } - -/// R-8: the gate is computed in every `pv lint` run, not only under `--gate`. -#[test] -fn the_full_run_computes_the_relations_gate() { - let dir = s(&repo_contracts()); - let r = pv(&["lint", &dir, "--format", "json"]); - let v = json_of(&r); - let names: Vec = v["gates"] - .as_array() - .map(|g| { - g.iter() - .filter_map(|x| x["name"].as_str().map(String::from)) - .collect() - }) - .unwrap_or_default(); - assert!( - names.iter().any(|n| n == "relations"), - "{names:?}\n{}", - 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 59014352b8..d07f8b14dc 100644 --- a/crates/aprender-contracts-cli/tests/ont4b_shapes_gate.rs +++ b/crates/aprender-contracts-cli/tests/ont4b_shapes_gate.rs @@ -53,10 +53,6 @@ fn fixture(name: &str) -> PathBuf { .join(name) } -fn repo_contracts() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts") -} - fn s(p: &Path) -> String { p.to_str().expect("utf-8 path").to_string() } @@ -69,37 +65,6 @@ fn gate(dir: &Path) -> Run { pv(&["lint", &s(dir), "--gate", "shapes", "--format", "json"]) } -#[test] -fn the_repo_corpus_passes_with_the_plant_fired_and_the_whole_corpus_as_focus_nodes() { - let r = gate(&repo_contracts()); - assert_eq!(r.code, 0, "{}", show(&r)); - let v = json_of(&r); - assert_eq!(v["gate"], "shapes"); - assert_eq!(v["verdict"], "Pass", "{}", show(&r)); - assert!( - v["extra"]["shapes_n"].as_u64().unwrap_or(0) >= 1, - "{}", - show(&r) - ); - assert!( - v["extra"]["focus_nodes_n"].as_u64().unwrap_or(0) > 1000, - "every contract's projection is a focus node\n{}", - show(&r) - ); - 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. 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. ONT-4c (#3847) arms readme-root, claude-md, - // model-setfit-slice and csv-train, whose plants add sixteen: 42. - assert_eq!( - v["extra"]["plant_violations"], - 42, - "ont:id minCount + ladder-measured's two (ONT-4c1) + the four GitHub shapes' 23 (ONT-4f) + the ONT-4c plants against readme-root, claude-md, model-setfit-slice, csv-train (16)\n{}", - show(&r) - ); -} - #[test] fn a_conforming_fixture_passes_at_exit_0() { let r = gate(&fixture("shapes-ok")); @@ -211,70 +176,3 @@ fn extract_is_deterministic_and_extract_check_sees_drift() { show(&drift) ); } - -/// The tracked graph in this repository IS a fresh extraction (R-18), and `shapes.ttl` is the export. -#[test] -fn the_tracked_repo_graph_is_fresh() { - let r = pv(&["extract", &s(&repo_contracts()), "--check"]); - assert_eq!(r.code, 0, "{}", show(&r)); - let v = json_of(&r); - assert!(v["triples"].as_u64().unwrap_or(0) > 5000, "{}", show(&r)); - // MEASURED on this branch: `pv extract contracts --check` reports - // shapes_n=9, triples=15851 (the 0.69 batch, #3669, pinned pv 0.68.2 built from - // the tree; was 6 / 15620 before #3600 folded in). The count is deliberately hardcoded rather - // than derived — a shape added without anyone noticing is the thing this - // assertion exists to prevent, so adding one is SUPPOSED to turn it red and - // make you name the new shape here. - // - // It did exactly that for #3605's `refusal-receipt-v1`, and a quorum lane - // caught it rather than I did. Note for whoever merges second: this counter - // is shared across branches, so a sibling PR that also adds a shape - // (#3600's parity-receipt-v2) will need the number raised again at merge — - // 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-*`. 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. - // ONT-4c (#3847) adds the four armed non-code shapes readme-root, claude-md, model-setfit-slice, csv-train: 28. - // ONT-4g/ONT-10 (#4502) add binary-target, surface-audit-ledger, binary-apr-http-mcp and fifteen - // binary--surface shapes (apr-cli-apr, apr-corpus-ingest, aprender-cbtop, aprender-compute-xtask, aprender-db, - // aprender-explain, aprender-orchestrate-aprender-orchestrate, aprender-profile, aprender-ptx-debug, aprender-test-cli, aprender-zram-generator, - // apr-qa-readme-sync, pv, score, trueno-rag): 46. Each new ONT-10 slice raises this — name it here. - // The 0.70.0 car (#4429) added, disjoint from these: #3745 S2 (#3777) six DERIVED cell-class shapes (effect, - // probe, model-cell, effect-cell, crux-verb, crux) and a8's `sampling`, ONT-4c5 (PMAT-3972) `capability-cells`, - // and ONT-4c4 (#4069) kernel-parity, kernel-timing and kernel-safety: 46 + 8 + 3 = 57. - // ONT-10 rest (#4588, PR-2) adds eight more binary--surface shapes (apr, apr-qa, aprender-data, - // aprender-present, aprender-ptop, aprender-simulate, aprender-train-lora, aprender-train-shell) and - // binary-aprender-apr-http-mcp: 57 + 9 = 66. - // #4455 adds `findings-ledger-v1` (not in `armed_shapes`; it grades zero focus nodes here): 67. - // KREG-001 (#4539) adds `kernel-registry-v1`'s one shape, a row per dispatchable kernel: 68. - // #3715 v2 adds `release-readiness-v2{.kernel,.model,.model-smoke,.sanitizer}` (like v1, a focus node only under - // `--v2-evidence`; the smoke is its own shape so no shape exports two `/node` subjects) and the - // per-forward `kernel-registry-v1.op`: 68 + 5 = 73. - assert_eq!( - v["shapes_n"], - 73, - "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) + readme-root + claude-md + model-setfit-slice + csv-train (ONT-4c, #3847) + binary-target + surface-audit-ledger + binary-apr-http-mcp + 15 binary-*-surface (ONT-10, #4502) + release-readiness-v1{{.effect,.probe,.model-cell,.effect-cell,.crux-verb,.crux,.sampling}} (#3745 S2) + capability-cells (ONT-4c5) + kernel-parity + kernel-timing + kernel-safety (ONT-4c4) + 8 binary-*-surface + binary-aprender-apr-http-mcp (ONT-10 rest, #4588) + findings-ledger-v1 (#4455) + kernel-registry-v1{{,.op}} (KREG-001 #4539, #3715 v2) + release-readiness-v2{{.kernel,.model,.model-smoke,.sanitizer}} (#3715 v2)\n{}", - show(&r) - ); -} - -/// R-8: the gate is computed in every `pv lint` run. -#[test] -fn the_full_run_computes_the_shapes_gate() { - let r = pv(&["lint", &s(&repo_contracts()), "--format", "json"]); - let v = json_of(&r); - let names: Vec = v["gates"] - .as_array() - .map(|g| { - g.iter() - .filter_map(|x| x["name"].as_str().map(String::from)) - .collect() - }) - .unwrap_or_default(); - assert!( - names.iter().any(|n| n == "shapes"), - "{names:?}\n{}", - show(&r) - ); -} diff --git a/crates/aprender-contracts-cli/tests/ont4e_refines_gate.rs b/crates/aprender-contracts-cli/tests/ont4e_refines_gate.rs index 994c546c78..a92b448fe9 100644 --- a/crates/aprender-contracts-cli/tests/ont4e_refines_gate.rs +++ b/crates/aprender-contracts-cli/tests/ont4e_refines_gate.rs @@ -79,23 +79,6 @@ fn liskov_witnesses(dir: &Path) -> Vec { .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"); diff --git a/crates/aprender-contracts-cli/tests/repo_tree_pv.rs b/crates/aprender-contracts-cli/tests/repo_tree_pv.rs new file mode 100644 index 0000000000..4774ef3220 --- /dev/null +++ b/crates/aprender-contracts-cli/tests/repo_tree_pv.rs @@ -0,0 +1,196 @@ +//! #5914 — the ONE debug-build `pv` pass over this repository's real `contracts/` tree. +//! +//! A debug `pv lint contracts` costs ~28 CPU-s, and seven test binaries used to pay it each (617% CPU on the +//! intel runners). Every other `pv` integration test lints a fixture corpus under `tests/fixtures/ont/`; the +//! repo tree is linted here, once, and each gate's verdict is read from that single report: +//! +//! | gate | was asserted in | asserted here | +//! |---|---|---| +//! | sigma | `ont2b_sigma_gate` | Pass | +//! | relations | `ont4_relations_gate` | Pass, typed relations present, legacy `depends_on` counted | +//! | shapes | `ont4b_shapes_gate` | Pass, plant fired, 73 shapes, 42 plant violations | +//! | refines | `ont4e_refines_gate` | Pass, a Liskov pair checked, `pc_checker` fired | +//! | theorem-pairing, depends-on-present | `ev11_lint_ratchets` | Pass, equal to the baseline file, which is not written | +//! +//! `pv extract contracts --check` (R-18, the tracked graph is fresh) is the one other repo-tree command. + +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::OnceLock; + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +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 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 dir_arg() -> String { + repo_contracts().to_str().expect("utf-8 path").to_string() +} + +/// The single `pv lint contracts --format json` run, plus the baseline bytes before and after it. +fn full_run() -> &'static (Run, Vec, Vec) { + static FULL: OnceLock<(Run, Vec, Vec)> = OnceLock::new(); + FULL.get_or_init(|| { + let baseline = repo_contracts().join("lint-baseline.json"); + let before = std::fs::read(&baseline).expect("baseline readable"); + let r = pv(&["lint", &dir_arg(), "--format", "json"]); + let after = std::fs::read(&baseline).expect("baseline readable"); + (r, before, after) + }) +} + +fn report() -> serde_json::Value { + let r = &full_run().0; + assert_eq!(r.code, 0, "{}", show(r)); + serde_json::from_str(&r.stdout).unwrap_or_else(|e| panic!("stdout is JSON: {e}\n{}", show(r))) +} + +/// One gate's entry in the full report; the full run computes every gate (R-8), so its absence is a failure. +fn gate(name: &str) -> serde_json::Value { + let v = report(); + let g = v["gates"] + .as_array() + .and_then(|gs| gs.iter().find(|g| g["name"] == name)) + .unwrap_or_else(|| panic!("the full run computes the {name} gate: {v}")) + .clone(); + assert_eq!(g["verdict"], "Pass", "{name}: {g}"); + g +} + +#[test] +fn the_repo_corpus_passes_the_sigma_gate() { + gate("sigma"); +} + +#[test] +fn the_repo_corpus_passes_the_relations_gate() { + let g = gate("relations"); + // R-2 by construction: the real corpus must carry typed relations, or this would be a decline + assert!( + g["extra"]["relations_n"].as_u64().unwrap_or(0) > 0, + "the real corpus has typed relations\n{g}" + ); + // the legacy field is measured, never rewritten: 188 contracts carry it at 3409b29d + assert!( + g["extra"]["legacy_depends_on"].as_u64().unwrap_or(0) > 100, + "metadata.depends_on is counted\n{g}" + ); +} + +#[test] +fn the_repo_corpus_passes_with_the_plant_fired_and_the_whole_corpus_as_focus_nodes() { + let g = gate("shapes"); + let x = &g["extra"]; + assert!( + x["focus_nodes_n"].as_u64().unwrap_or(0) > 1000, + "every contract's projection is a focus node\n{g}" + ); + assert_eq!(x["pc_shape"], "fired", "{g}"); + // ONT-4b: exactly one, from ont:id minCount; ONT-4c1, ONT-4f and ONT-4c add plants (see the history + // below): 42. + assert_eq!(x["plant_violations"], 42, "{g}"); + // The shape count is hardcoded on purpose, so adding a shape turns this red and makes you name it. + // MEASURED: `pv extract contracts --check` reported + // shapes_n=9, triples=15851 (the 0.69 batch, #3669, pinned pv 0.68.2 built from + // the tree; was 6 / 15620 before #3600 folded in). The count is deliberately hardcoded rather + // than derived — a shape added without anyone noticing is the thing this + // assertion exists to prevent, so adding one is SUPPOSED to turn it red and + // make you name the new shape here. + // + // It did exactly that for #3605's `refusal-receipt-v1`, and a quorum lane + // caught it rather than I did. Note for whoever merges second: this counter + // is shared across branches, so a sibling PR that also adds a shape + // (#3600's parity-receipt-v2) will need the number raised again at merge — + // 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-*`. 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. + // ONT-4c (#3847) adds the four armed non-code shapes readme-root, claude-md, model-setfit-slice, csv-train: 28. + // ONT-4g/ONT-10 (#4502) add binary-target, surface-audit-ledger, binary-apr-http-mcp and fifteen + // binary--surface shapes (apr-cli-apr, apr-corpus-ingest, aprender-cbtop, aprender-compute-xtask, aprender-db, + // aprender-explain, aprender-orchestrate-aprender-orchestrate, aprender-profile, aprender-ptx-debug, aprender-test-cli, aprender-zram-generator, + // apr-qa-readme-sync, pv, score, trueno-rag): 46. Each new ONT-10 slice raises this — name it here. + // The 0.70.0 car (#4429) added, disjoint from these: #3745 S2 (#3777) six DERIVED cell-class shapes (effect, + // probe, model-cell, effect-cell, crux-verb, crux) and a8's `sampling`, ONT-4c5 (PMAT-3972) `capability-cells`, + // and ONT-4c4 (#4069) kernel-parity, kernel-timing and kernel-safety: 46 + 8 + 3 = 57. + // ONT-10 rest (#4588, PR-2) adds eight more binary--surface shapes (apr, apr-qa, aprender-data, + // aprender-present, aprender-ptop, aprender-simulate, aprender-train-lora, aprender-train-shell) and + // binary-aprender-apr-http-mcp: 57 + 9 = 66. + // #4455 adds `findings-ledger-v1` (not in `armed_shapes`; it grades zero focus nodes here): 67. + // KREG-001 (#4539) adds `kernel-registry-v1`'s one shape, a row per dispatchable kernel: 68. + // #3715 v2 adds `release-readiness-v2{.kernel,.model,.model-smoke,.sanitizer}` (like v1, a focus node only under + // `--v2-evidence`; the smoke is its own shape so no shape exports two `/node` subjects) and the + // per-forward `kernel-registry-v1.op`: 68 + 5 = 73. + assert_eq!(x["shapes_n"], 73, "{g}"); +} + +#[test] +fn the_spec_probe_holds_on_the_repo_corpus() { + // ONT-001 §5 ONT-4e probe: `lint contracts/ --gate refines` reports liskov_pairs_checked>0, pc_checker fired, Pass. + let g = gate("refines"); + let x = &g["extra"]; + assert!( + x["liskov_pairs_checked"].as_u64().unwrap_or(0) > 0, + "liskov_pairs_checked>0: {g}" + ); + assert_eq!(x["pc_checker"], "fired", "{g}"); + assert_eq!(x["witness"]["pc_reasoner"], "fired", "{g}"); +} + +/// PVL-001 EV-11's probe on the real corpus: both ratchets Pass at the recorded baseline, and no gate writes it. +#[test] +fn the_ratchet_probe_passes_on_the_real_corpus_and_writes_nothing() { + let (_, before, after) = full_run(); + let doc: serde_json::Value = serde_json::from_slice(before).expect("baseline is JSON"); + 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 pairing = gate("theorem-pairing"); + let depends = gate("depends-on-present"); + assert_eq!( + pairing["extra"]["unpaired_theorem_modules"], doc["unpaired_theorem_modules"], + "the recorded baseline is the measured count (`make lint-ratchet` wrote it)" + ); + assert_eq!( + depends["extra"]["contracts_without_depends_on"], + doc["contracts_without_depends_on"] + ); + assert_eq!(after, before, "a gate wrote the baseline"); +} + +/// The tracked graph in this repository IS a fresh extraction (R-18), and `shapes.ttl` is the export. +#[test] +fn the_tracked_repo_graph_is_fresh() { + let r = pv(&["extract", &dir_arg(), "--check"]); + assert_eq!(r.code, 0, "{}", show(&r)); + let v: serde_json::Value = serde_json::from_str(&r.stdout).expect("stdout is JSON"); + assert!(v["triples"].as_u64().unwrap_or(0) > 5000, "{}", show(&r)); + assert_eq!(v["shapes_n"], 73, "{}", show(&r)); +} diff --git a/docs/roadmaps/entries/PMAT-5914.yaml b/docs/roadmaps/entries/PMAT-5914.yaml new file mode 100644 index 0000000000..52d6ad2d14 --- /dev/null +++ b/docs/roadmaps/entries/PMAT-5914.yaml @@ -0,0 +1,19 @@ +- id: PMAT-5914 + github_issue: 5914 + item_type: task + title: 'pv debug-build CPU: tests lint whole contracts tree with debug pv' + status: planned + priority: high + assigned_to: null + created: 2026-10-22T07:04:44Z + updated: 2026-10-22T07:04:44Z + spec: null + acceptance_criteria: + - See handoff/pv-debug-cpu-20261022.md; PV_BIN override + shared release pv + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index 277fbb376d..91a8ff9b14 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -25454,3 +25454,22 @@ Not done, pending the ruling: moving the 31 fixture files to an obviously synthe - kind:code notes: null epic: 5510 +- id: PMAT-5914 + github_issue: 5914 + item_type: task + title: 'pv debug-build CPU: tests lint whole contracts tree with debug pv' + status: planned + priority: high + assigned_to: null + created: 2026-10-22T07:04:44Z + updated: 2026-10-22T07:04:44Z + spec: null + acceptance_criteria: + - See handoff/pv-debug-cpu-20261022.md; PV_BIN override + shared release pv + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 diff --git a/scripts/tree_reader_tests.txt b/scripts/tree_reader_tests.txt index a560a1bca2..3f2ddcc979 100644 --- a/scripts/tree_reader_tests.txt +++ b/scripts/tree_reader_tests.txt @@ -132,6 +132,7 @@ aprender-contracts-cli --test pvl_discharge_leanchecker aprender-contracts-cli --test pvl_ghost_binding aprender-contracts-cli --test pvl_obligations_golden aprender-contracts-cli --test pvl_zero_contracts +aprender-contracts-cli --test repo_tree_pv aprender-contracts-cli --test shapes_vacuity aprender-core --lib format::metadata_bounds_contract_falsify aprender-core --lib format::model_family_loader