Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ publishing — all backed by YAML provable contracts that fail CI on drift.
| Metric | Count | Source of truth |
|-------:|------:|---|
| Workspace crates | **81** workspace crates | `cargo metadata --no-deps` (NOT `ls crates/` — 4 are `exclude`d, 1 has no Cargo.toml) |
| Provable contracts | **<!-- CONTRACT_COUNT_START -->1985<!-- CONTRACT_COUNT_END -->** 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 | **<!-- CONTRACT_COUNT_START -->1987<!-- CONTRACT_COUNT_END -->** 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 | **113** CLI commands | `contracts/apr-cli-commands-v1.yaml` §`commands` (parse the list; `apr --help` also lists `help` itself, and `grep -c '^ - name:'` gives 119 — other same-indent `name:` keys exist in the file) |
| Book CLI chapters | **114** chapters | `ls book/src/cli/*.md` |
| Book lib chapters | **73** chapters | `ls book/src/lib/*.md` (parity with `pub mod`) |
Expand Down Expand Up @@ -403,7 +403,7 @@ falsification_tests:
prediction: apr validate bad-model.apr exits non-zero
```

The tree carries <!-- CONTRACT_COUNT_START -->1985<!-- CONTRACT_COUNT_END --> contracts across inference, training, quantization, attention, FFN,
The tree carries <!-- CONTRACT_COUNT_START -->1987<!-- CONTRACT_COUNT_END --> contracts across inference, training, quantization, attention, FFN,
tokenization, model formats, CLI safety — and this README itself.

## Migration from old crates
Expand Down
47 changes: 47 additions & 0 deletions contracts/cbtop-render-sparklines-v1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
metadata:
id: C-CBTOP-RENDER-SPARKLINES
version: 1.0.0
created: '2026-10-19'
author: PAIML Engineering
kind: pattern
status: ACTIVE_ALGORITHM_LEVEL
description: |
Pins CbtopApp::render_sparklines (#5595): the CPU history box needs
y < height - 8, the bricks box needs y < height - 5, empty data skips its
box, and y advances 4 rows past the CPU box and 2 past the bricks box.
references:
- 'crates/aprender-cbtop/src/app/render/panels.rs (render_sparklines)'

equations:
sparkline_layout_gates:
formula: |
cpu drawn iff cpu nonempty and y < height - 8, then y += 4
bricks drawn iff bricks nonempty and y < height - 5, then y += 2
domain: render_sparklines calls on a fresh cell buffer
invariants:
- 'a skipped box leaves y unchanged'

proof_obligations:
- id: CRS-INV-001
type: invariant
property: each sparkline box is drawn only inside its headroom gate and advances y by its fixed amount
formal: 'drawn ≡ gate'
applies_to: sparkline_layout_gates

falsification_tests:
- id: FALSIFY-CBTOP-RENDER-SPARKLINES-001
rule: render_sparklines gates and advances exactly
prediction: 'boundary starts 21/22 (cpu) and 20/21 (bricks) flip the box; empty inputs draw nothing; final y is 8 for start 2 with both boxes'
test: cargo test -p aprender-cbtop --lib sparklines_tests
if_fails: the cbtop dashboard overflows its pane or drops a history box
ship_blocking: false
mutation: 'shift either headroom constant, shorten the +2.0 advance, or invert the bricks emptiness check; each must turn RED'

qa_gate:
id: QA-CBTOP-RENDER-SPARKLINES-001
name: cbtop sparklines gate
description: sparkline box gates and y advance are exact
checks:
- sparkline_layout_gates
pass_criteria: FALSIFY-CBTOP-RENDER-SPARKLINES-001 passes
falsification: Any failure fails the gate
42 changes: 42 additions & 0 deletions contracts/cgp-doctor-collect-checks-v1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
metadata:
version: 1.0.0
created: '2026-10-19'
author: PAIML Engineering
description: "aprender-cgp doctor::collect_checks (#5614) — returns the eleven tool checks in fixed order; nvcc is reported as CUDA Runtime; CUPTI status tracks the library path found; a check is Ok iff it has a path."
kind: pattern
references:
- "crates/aprender-cgp/src/doctor.rs — collect_checks"
- "aprender#5614 — coverage ticket"

equations:
collect_checks_shape:
formula: "names(collect_checks()) = [nvidia-smi, CUDA Runtime, ncu, nsys, CUPTI, perf, valgrind, renacer, trueno-explain, GPU, CPU]; CUPTI.status = Ok iff CUPTI.path is Some"
domain: "any host (checks are host-dependent, shape is not)"
codomain: "Vec<ToolCheck>"
invariants:
- "no check is named nvcc"
- "for binary checks, status Ok iff path is Some"

proof_obligations:
- id: CGP-DOCTOR-INV-001
type: invariant
property: "Check list order and names are fixed"
formal: "∀ run: cargo test -p aprender-cgp --lib test_collect_checks_names_and_order exits 0"
applies_to: collect_checks_shape
- id: CGP-DOCTOR-INV-002
type: invariant
property: "CUPTI status tracks its path"
formal: "∀ run: cargo test -p aprender-cgp --lib test_collect_checks_cupti_status_tracks_path exits 0"
applies_to: collect_checks_shape

falsification_tests:
- id: FALSIFY-CGP-DOCTOR-001
rule: "CUDA Runtime rename dropped"
prediction: "deleting check.name = \"CUDA Runtime\" turns test_collect_checks_names_and_order RED"
test: "cargo test -p aprender-cgp --lib test_collect_checks_names_and_order"
if_fails: "check naming is unguarded"
- id: FALSIFY-CGP-DOCTOR-002
rule: "CUPTI status inverted"
prediction: "swapping Ok/Missing for CUPTI turns test_collect_checks_cupti_status_tracks_path RED"
test: "cargo test -p aprender-cgp --lib test_collect_checks_cupti_status_tracks_path"
if_fails: "CUPTI status is unguarded"
10 changes: 10 additions & 0 deletions contracts/contracts.nt
Original file line number Diff line number Diff line change
Expand Up @@ -9456,6 +9456,16 @@
<https://ont.paiml.dev/v1alpha1/contract/canary-score-gate-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://ont.paiml.dev/v1alpha1/Kernel> .
<https://ont.paiml.dev/v1alpha1/contract/canary-score-gate-v1> <https://ont.paiml.dev/v1alpha1/file> "contracts/entrenar/canary-score-gate-v1.yaml"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/canary-score-gate-v1> <https://ont.paiml.dev/v1alpha1/id> "canary-score-gate-v1"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/cbtop-render-sparklines-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/ns/prov#Entity> .
<https://ont.paiml.dev/v1alpha1/contract/cbtop-render-sparklines-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://ont.paiml.dev/v1alpha1/Contract> .
<https://ont.paiml.dev/v1alpha1/contract/cbtop-render-sparklines-v1> <https://ont.paiml.dev/v1alpha1/file> "contracts/cbtop-render-sparklines-v1.yaml"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/cbtop-render-sparklines-v1> <https://ont.paiml.dev/v1alpha1/id> "cbtop-render-sparklines-v1"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/cbtop-render-sparklines-v1> <https://ont.paiml.dev/v1alpha1/kind> "pattern"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-doctor-collect-checks-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/ns/prov#Entity> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-doctor-collect-checks-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://ont.paiml.dev/v1alpha1/Contract> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-doctor-collect-checks-v1> <https://ont.paiml.dev/v1alpha1/file> "contracts/cgp-doctor-collect-checks-v1.yaml"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-doctor-collect-checks-v1> <https://ont.paiml.dev/v1alpha1/id> "cgp-doctor-collect-checks-v1"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-doctor-collect-checks-v1> <https://ont.paiml.dev/v1alpha1/kind> "pattern"^^<http://www.w3.org/2001/XMLSchema#string> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-monorepo-build-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/ns/prov#Entity> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-monorepo-build-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://ont.paiml.dev/v1alpha1/Contract> .
<https://ont.paiml.dev/v1alpha1/contract/cgp-monorepo-build-v1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://ont.paiml.dev/v1alpha1/Kernel> .
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"census_id_set_sha256": "063d371c514532fccc38497ed2f0d85711be3c35061de05df561446f16e93d99",
"census_id_set_sha256": "2cd65471402c5d974cca226206c779350b3419dda8b5db7b61d0fc70d2fb2549",
"relations_sha256": "811c78184eb2609244c59534f015f0be2f90923120a3027c176ad56723d0d850",
"checkable_n": 87,
"result": {
Expand Down
3 changes: 3 additions & 0 deletions crates/aprender-cbtop/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,6 @@ impl CbtopApp {
Ok(())
}
}

#[cfg(test)]
mod sparklines_tests;
104 changes: 104 additions & 0 deletions crates/aprender-cbtop/src/app/sparklines_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Behavioural tests for `CbtopApp::render_sparklines` (ticket #5595).

use super::CbtopApp;
use presentar_core::TextStyle;
use presentar_terminal::direct::{CellBuffer, DirectTerminalCanvas};
use presentar_terminal::Theme;

const W: u16 = 80;
const H: u16 = 30;

/// Run `render_sparklines` on a fresh buffer; return the text of every row and the final y.
fn run(start_y: f32, height: u16, cpu: &[f64], bricks: &[f64]) -> (Vec<String>, f32) {
let mut buffer = CellBuffer::new(W, H);
let mut y = start_y;
{
let mut canvas = DirectTerminalCanvas::new(&mut buffer);
let style = TextStyle::default();
CbtopApp::render_sparklines(
&mut canvas,
&mut y,
height,
cpu,
bricks,
40,
43,
20,
&Theme::tokyo_night(),
&style,
&style,
);
}
let rows = (0..H)
.map(|r| {
(0..W)
.filter_map(|x| buffer.get(x, r).map(|c| c.symbol.to_string()))
.collect::<String>()
})
.collect();
(rows, y)
}

fn has(rows: &[String], needle: &str) -> bool {
rows.iter().any(|r| r.contains(needle))
}

#[test]
fn both_boxes_drawn_and_y_advances_to_end_of_second_box() {
let (rows, y) = run(2.0, H, &[10.0, 50.0, 90.0], &[1.0, 2.0, 3.0]);
assert!(rows[2].contains("┌─ CPU History (braille) "), "{}", rows[2]);
assert!(rows[3].starts_with(" │ "), "{}", rows[3]);
assert!(
rows[4].contains('└') && rows[4].contains('┘'),
"{}",
rows[4]
);
// cpu block advances 1 + 1 + 2 = 4 rows, so the bricks box starts at row 6
assert!(rows[6].contains("┌─ Bricks/sec (braille) "), "{}", rows[6]);
assert!(rows[8].contains('└'), "{}", rows[8]);
assert_eq!(y, 8.0, "second box advances 1 + 1 only");
}

#[test]
fn empty_cpu_data_skips_cpu_box_and_leaves_y() {
let (rows, y) = run(2.0, H, &[], &[1.0, 2.0]);
assert!(!has(&rows, "CPU History"));
assert!(rows[2].contains("┌─ Bricks/sec (braille) "), "{}", rows[2]);
assert_eq!(y, 4.0);
}

#[test]
fn empty_bricks_data_skips_bricks_box() {
let (rows, y) = run(2.0, H, &[5.0], &[]);
assert!(has(&rows, "CPU History"));
assert!(!has(&rows, "Bricks/sec"));
assert_eq!(y, 6.0);
}

#[test]
fn both_empty_draws_nothing() {
let (rows, y) = run(2.0, H, &[], &[]);
assert!(rows.iter().all(|r| r.trim().is_empty()));
assert_eq!(y, 2.0);
}

#[test]
fn cpu_box_needs_eight_rows_of_headroom() {
// y < height - 8 is strict: 22 < 22 is false, 21 < 22 is true
let (rows, y) = run(22.0, H, &[5.0], &[]);
assert!(!has(&rows, "CPU History"));
assert_eq!(y, 22.0);
let (rows, _) = run(21.0, H, &[5.0], &[]);
assert!(has(&rows, "CPU History"));
}

#[test]
fn bricks_box_needs_five_rows_of_headroom() {
// after the cpu box y = start + 4; bricks need y < height - 5 = 25
let (rows, y) = run(20.0, H, &[5.0], &[1.0]);
assert!(has(&rows, "Bricks/sec"), "start 20 -> y 24 < 25");
assert_eq!(y, 26.0);
let (rows, y) = run(21.0, H, &[5.0], &[1.0]);
assert!(!has(&rows, "Bricks/sec"), "start 21 -> y 25 is not < 25");
assert_eq!(y, 25.0);
}
71 changes: 71 additions & 0 deletions crates/aprender-cgp/src/doctor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,4 +407,75 @@ mod tests {
#[cfg(not(feature = "timing-gate"))]
let _ = elapsed;
}

// ===== collect_checks (FALSIFY-CGP-DOCTOR-001..002) =====

fn by_name<'a>(checks: &'a [ToolCheck], name: &str) -> &'a ToolCheck {
checks
.iter()
.find(|c| c.name == name)
.unwrap_or_else(|| panic!("missing check {name}"))
}

#[test]
fn test_collect_checks_names_and_order() {
let names: Vec<String> = collect_checks().into_iter().map(|c| c.name).collect();
let expected = [
"nvidia-smi",
"CUDA Runtime",
"ncu",
"nsys",
"CUPTI",
"perf",
"valgrind",
"renacer",
"trueno-explain",
"GPU",
"CPU",
];
assert_eq!(names, expected);
}

#[test]
fn test_collect_checks_cuda_runtime_renamed_from_nvcc() {
let checks = collect_checks();
assert!(checks.iter().all(|c| c.name != "nvcc"));
let cuda = by_name(&checks, "CUDA Runtime");
// Present iff nvcc is on PATH.
assert_eq!(cuda.status == ToolStatus::Ok, which::which("nvcc").is_ok());
}

#[test]
fn test_collect_checks_cupti_status_tracks_path() {
let checks = collect_checks();
let cupti = by_name(&checks, "CUPTI");
match &cupti.path {
Some(p) => {
assert_eq!(cupti.status, ToolStatus::Ok);
assert_eq!(cupti.version.as_deref(), Some(p.as_str()));
assert!(std::path::Path::new(p).exists());
}
None => {
assert_eq!(cupti.status, ToolStatus::Missing);
assert!(cupti.version.is_none());
}
}
}

#[test]
fn test_collect_checks_status_matches_path_presence() {
for c in collect_checks() {
if c.name == "GPU" || c.name == "CPU" {
continue;
}
assert_eq!(
c.status == ToolStatus::Ok,
c.path.is_some(),
"{}: status {:?} vs path {:?}",
c.name,
c.status,
c.path
);
}
}
}
Loading