Skip to content

feat(perf): persist release report artifacts for rerendering - #206

Merged
acgetchell merged 2 commits into
mainfrom
feat/205-performance-artifacts
Aug 5, 2026
Merged

feat(perf): persist release report artifacts for rerendering#206
acgetchell merged 2 commits into
mainfrom
feat/205-performance-artifacts

Conversation

@acgetchell

@acgetchell acgetchell commented Aug 4, 2026

Copy link
Copy Markdown
Owner
  • Store deterministic comparison CSV with an adjacent schema-versioned provenance sidecar before temporary worktrees are removed.
  • Render and promote reports only from validated artifact reloads, with fail-closed path checks and transactional rollback.
  • Add performance-rerender and document artifact retention, GitHub assets, and the release workflow.
  • Refresh Rust, Python, contributor-tool, and GitHub Action pins, and separate Dependabot security update groups.

Closes #205

Summary by CodeRabbit

  • New Features

    • Added reproducible performance artifacts in CSV and JSON formats, including provenance and metadata.
    • Added performance-rerender to regenerate release reports without rerunning benchmarks.
    • Improved report validation, promotion, rollback, and retained-input support.
  • Documentation

    • Expanded benchmarking and release guidance for artifact retention, validation, rerendering, and reproducibility.
  • Chores

    • Updated development tools, dependencies, and automated workflow actions.

- Store deterministic comparison CSV with an adjacent schema-versioned provenance sidecar before temporary worktrees are removed.
- Render and promote reports only from validated artifact reloads, with fail-closed path checks and transactional rollback.
- Add performance-rerender and document artifact retention, GitHub assets, and the release workflow.
- Refresh Rust, Python, contributor-tool, and GitHub Action pins, and separate Dependabot security update groups.

Closes #205
@acgetchell acgetchell self-assigned this Aug 4, 2026
@acgetchell
acgetchell enabled auto-merge August 4, 2026 22:16
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d6e8255c-87a2-42ff-97fb-69f92a3de25c

📥 Commits

Reviewing files that changed from the base of the PR and between 600445c and 8b8537b.

📒 Files selected for processing (6)
  • docs/RELEASING.md
  • scripts/archive_performance.py
  • scripts/bench_compare.py
  • scripts/tests/test_archive_performance.py
  • scripts/tests/test_bench_compare.py
  • scripts/tests/test_performance_artifacts.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/RELEASING.md
  • scripts/tests/test_bench_compare.py
  • scripts/tests/test_archive_performance.py

📝 Walkthrough

Walkthrough

The release performance pipeline now persists schema-versioned CSV and provenance artifacts, validates and publishes them transactionally, renders reports from reloaded artifacts, and supports rerendering without benchmarks or temporary worktrees. CI pins, tooling versions, documentation, and tests were updated.

Changes

Release performance reporting

Layer / File(s) Summary
Tooling and workflow updates
.github/*, Cargo.toml, CONTRIBUTING.md, README.md, pyproject.toml, justfile
Updated action pins, Dependabot groups, tool versions, the optional num-bigint dependency, and packaging metadata.
Artifact schema and transactional storage
scripts/performance_artifacts.py, scripts/tests/test_performance_artifacts.py
Added validated artifact models, deterministic CSV and JSON serialization, strict loading, atomic publication, rollback handling, and validation tests.
Artifact-backed comparison rendering
scripts/bench_compare.py, scripts/tests/test_bench_compare.py
Added artifact bundle generation, suite and scope filtering, provenance handling, artifact reload rendering, CLI options, and path validation.
Archive promotion and rerender workflow
scripts/archive_performance.py, scripts/tests/test_archive_performance.py
Integrated artifact publication into benchmark generation and added rerender-only promotion, collision checks, snapshots, rollback handling, and end-to-end coverage.
Rerender workflow documentation and recipes
justfile, docs/*, scripts/README.md
Added the performance-rerender recipe and documented artifact retention, report reproduction, promotion, cleanup, and Criterion archive ownership.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BenchCompare
  participant PerformanceArtifacts
  participant ArchivePerformance
  participant ReleaseReport

  BenchCompare->>PerformanceArtifacts: Build and validate CSV/provenance bundle
  PerformanceArtifacts->>PerformanceArtifacts: Serialize and publish artifact pair
  ArchivePerformance->>PerformanceArtifacts: Reload published artifacts
  PerformanceArtifacts-->>ArchivePerformance: Return validated bundle
  ArchivePerformance->>ReleaseReport: Render Markdown from retained bundle
  ReleaseReport-->>ArchivePerformance: Return validated report
  ArchivePerformance->>ArchivePerformance: Promote report and archive with rollback protection
Loading

Possibly related issues

  • acgetchell/delaunay issue 550 — Describes the same retained benchmark artifacts, provenance, and rerender workflow.
  • acgetchell/causal-triangulations issue 184 — Covers the same release-benchmark artifact and rerender implementation.
  • acgetchell/mcmc-learned-proposals issue 7 — Uses the same shared release performance reporting workflow.

Possibly related PRs

  • acgetchell/la-stack#70 — Earlier benchmark comparison workflow changes in the same scripts, tests, documentation, and justfile.
  • acgetchell/la-stack#143 — Earlier benchmark comparison workflow extended by artifact-backed rendering.
  • acgetchell/la-stack#150 — Earlier performance archiving workflow extended with durable artifacts and rerendering.

Suggested labels: dependencies, github-actions

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request includes unrelated dependency, tool-version, GitHub Action, and Dependabot configuration updates outside issue #205. Move dependency, tool-version, GitHub Action, and Dependabot updates to a separate pull request.
Docstring Coverage ⚠️ Warning Docstring coverage is 38.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: persisting performance report artifacts for rerendering.
Linked Issues check ✅ Passed The implementation covers issue #205 through validated CSV and JSON artifacts, rerendering, documentation, transactional publication, and comprehensive tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/205-performance-artifacts

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.86%. Comparing base (5e05170) to head (8b8537b).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #206      +/-   ##
==========================================
+ Coverage   97.84%   97.86%   +0.02%     
==========================================
  Files           8        8              
  Lines        4969     4969              
==========================================
+ Hits         4862     4863       +1     
+ Misses        107      106       -1     
Flag Coverage Δ
unittests 97.86% <ø> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
scripts/bench_compare.py (2)

1368-1394: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Share the suite and scope selection rules with the comparison collector.

_unavailable_artifact_rows re-implements three selection rules that _collect_comparisons already owns: the suite membership test, the release-signal exact-group filter, and the release-signal vs_linalg bench filter. Line 1382 also re-derives the suite from the exact_ group-name prefix instead of the shared suite mapping.

If any selection rule changes in one place, the retained coverage rows and the comparison rows disagree, and the artifact then misreports which baselines were excluded. Extract the row-selection predicate into one helper and call it from both paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/bench_compare.py` around lines 1368 - 1394, The selection logic in
_unavailable_artifact_rows must be shared with _collect_comparisons instead of
duplicated. Extract a common row-selection predicate that uses the existing
suite mapping and encapsulates suite membership, release-signal exact-group
filtering, and release-signal vs_linalg bench filtering; then call it from both
paths, removing the local row_suite derivation and checks.

1953-1976: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the artifact path resolution and publication from main.

main already suppresses C901, PLR0911, PLR0912, and PLR0915. This change adds two more early-return groups and repeats the same paired-option test three times, at line 1953, line 1961, and line 2066. Line 1953 already proves the two options agree, so the later tests only serve to narrow the types.

Extract two helpers, for example _resolve_artifact_paths(args, root, output_path) -> ArtifactPaths | None and _write_and_render_artifacts(...) -> str. Then main tests artifact_paths is not None once, and the invariant AssertionError at line 2068 becomes unnecessary.

Also applies to: 2066-2085

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/bench_compare.py` around lines 1953 - 1976, Extract artifact path
resolution from main into a helper such as _resolve_artifact_paths, preserving
paired-option validation, path normalization, distinct-path checks, and existing
error returns. Extract artifact writing and rendering around the later block
into a helper such as _write_and_render_artifacts, then have main branch once on
artifact_paths is not None and remove the redundant paired-option narrowing and
invariant AssertionError.
scripts/tests/test_performance_artifacts.py (1)

146-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the CSV digest and row-count mismatch guards.

_replace_csv_and_digest recomputes csv.sha256 for every malformed-CSV fixture. As a result, no test reaches the two fail-closed branches in load_bundle_bytes: the digest mismatch and the row-count mismatch. Both branches detect tampered or partially written pairs, which the artifact contract treats as a hard failure.

Add two focused tests that mutate the CSV without repairing the sidecar, and that change csv.row_count.

💚 Proposed tests for the mismatch guards
def test_artifact_loader_rejects_csv_digest_mismatch() -> None:
    csv_payload, provenance_payload = serialize_bundle(_bundle())
    tampered = csv_payload.replace(b"comparable", b"comparable", 1) + b"\n"

    with pytest.raises(ValueError, match="CSV digest mismatch"):
        load_bundle_bytes(tampered, provenance_payload, source="digest mismatch fixture")


def test_artifact_loader_rejects_csv_row_count_mismatch() -> None:
    csv_payload, provenance_payload = serialize_bundle(_bundle())
    provenance = json.loads(provenance_payload)
    provenance["csv"]["row_count"] = 99

    with pytest.raises(ValueError, match="CSV row count mismatch"):
        load_bundle_bytes(
            csv_payload,
            (json.dumps(provenance, indent=2, sort_keys=True) + "\n").encode(),
            source="row count mismatch fixture",
        )

Also applies to: 205-211

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/tests/test_performance_artifacts.py` around lines 146 - 149, The
performance artifact tests lack coverage for the fail-closed CSV integrity
guards. Add focused tests near the existing artifact-loader tests that mutate
the CSV without updating its sidecar and assert load_bundle_bytes raises
ValueError for a CSV digest mismatch, then alter provenance["csv"]["row_count"]
and assert the corresponding row-count mismatch error, while leaving
_replace_csv_and_digest unchanged for fixtures that intentionally repair the
digest.
scripts/archive_performance.py (1)

390-402: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the "How to Update" block with bench_compare.

This PR edits the same instruction block twice: here and in scripts/bench_compare.py _generate_markdown (the just performance-rerender lines and the retained-artifacts sentence). The two copies must stay identical, but nothing enforces that. _normalize_how_to_update silently rewrites the section during promotion, so a drift produces a local report that differs from the promoted report without any test failure.

archive_performance already imports from bench_compare. Move the block into one shared constant or helper and use it in both modules.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/archive_performance.py` around lines 390 - 402, Extract the
duplicated “How to Update” instruction block into a shared constant or helper in
the existing bench_compare import path, including the rerender command and
retained-artifacts sentence. Update archive_performance and
bench_compare._generate_markdown to consume that shared source, and ensure
_normalize_how_to_update preserves the same content during promotion.
scripts/tests/test_bench_compare.py (1)

1038-1043: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the two new artifact CLI guards.

The parameterized test covers invalid path combinations. Two other new fail-closed guards in main have no test: the paired-option check for --csv-output and --provenance-output, and the median-baseline restriction that rejects --snapshot or a non-median --stat. Both return exit code 2 with distinct messages.

Extend the parameterization or add two short tests so a regression in either guard fails the suite.

💚 Proposed tests for the CLI guards
`@pytest.mark.parametrize`(
    ("extra_args", "expected"),
    [
        (["--csv-output", "performance.csv"], "must be provided together"),
        (
            ["--csv-output", "performance.csv", "--provenance-output", "performance.provenance.json", "--snapshot"],
            "require a median baseline comparison",
        ),
    ],
)
def test_main_rejects_incomplete_artifact_selection(
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
    extra_args: list[str],
    expected: str,
) -> None:
    rc = bench_compare.main(["v0.4.3", "--repo-root", str(tmp_path), *extra_args])

    assert rc == 2
    assert expected in capsys.readouterr().err
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/tests/test_bench_compare.py` around lines 1038 - 1043, Extend the CLI
guard tests around test_main_rejects_invalid_artifact_paths_without_writing, or
add a focused parameterized test, to cover incomplete
--csv-output/--provenance-output pairing and artifact selection with --snapshot
or a non-median --stat. Invoke bench_compare.main with each invalid argument set
and assert exit code 2 plus the corresponding distinct error message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/RELEASING.md`:
- Around line 155-157: Update the performance comparison wording in the release
documentation to say “stored GitHub Release assets” instead of “stored GitHub
Actions release assets,” preserving the existing command guidance and
terminology distinction elsewhere in the file.

---

Nitpick comments:
In `@scripts/archive_performance.py`:
- Around line 390-402: Extract the duplicated “How to Update” instruction block
into a shared constant or helper in the existing bench_compare import path,
including the rerender command and retained-artifacts sentence. Update
archive_performance and bench_compare._generate_markdown to consume that shared
source, and ensure _normalize_how_to_update preserves the same content during
promotion.

In `@scripts/bench_compare.py`:
- Around line 1368-1394: The selection logic in _unavailable_artifact_rows must
be shared with _collect_comparisons instead of duplicated. Extract a common
row-selection predicate that uses the existing suite mapping and encapsulates
suite membership, release-signal exact-group filtering, and release-signal
vs_linalg bench filtering; then call it from both paths, removing the local
row_suite derivation and checks.
- Around line 1953-1976: Extract artifact path resolution from main into a
helper such as _resolve_artifact_paths, preserving paired-option validation,
path normalization, distinct-path checks, and existing error returns. Extract
artifact writing and rendering around the later block into a helper such as
_write_and_render_artifacts, then have main branch once on artifact_paths is not
None and remove the redundant paired-option narrowing and invariant
AssertionError.

In `@scripts/tests/test_bench_compare.py`:
- Around line 1038-1043: Extend the CLI guard tests around
test_main_rejects_invalid_artifact_paths_without_writing, or add a focused
parameterized test, to cover incomplete --csv-output/--provenance-output pairing
and artifact selection with --snapshot or a non-median --stat. Invoke
bench_compare.main with each invalid argument set and assert exit code 2 plus
the corresponding distinct error message.

In `@scripts/tests/test_performance_artifacts.py`:
- Around line 146-149: The performance artifact tests lack coverage for the
fail-closed CSV integrity guards. Add focused tests near the existing
artifact-loader tests that mutate the CSV without updating its sidecar and
assert load_bundle_bytes raises ValueError for a CSV digest mismatch, then alter
provenance["csv"]["row_count"] and assert the corresponding row-count mismatch
error, while leaving _replace_csv_and_digest unchanged for fixtures that
intentionally repair the digest.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c9fb96a9-cd88-43dc-b5aa-a5eca1bca002

📥 Commits

Reviewing files that changed from the base of the PR and between 5e05170 and 600445c.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • .github/actions/setup-just/action.yml
  • .github/dependabot.yml
  • .github/workflows/audit.yml
  • .github/workflows/benchmarks.yml
  • .github/workflows/ci.yml
  • .github/workflows/codecov.yml
  • .github/workflows/codeql.yml
  • .github/workflows/release-benchmarks.yml
  • .github/workflows/rust-clippy.yml
  • .github/workflows/semgrep-sarif.yml
  • CONTRIBUTING.md
  • Cargo.toml
  • README.md
  • docs/BENCHMARKING.md
  • docs/RELEASING.md
  • justfile
  • pyproject.toml
  • scripts/README.md
  • scripts/archive_performance.py
  • scripts/bench_compare.py
  • scripts/performance_artifacts.py
  • scripts/tests/test_archive_performance.py
  • scripts/tests/test_bench_compare.py
  • scripts/tests/test_performance_artifacts.py

Comment thread docs/RELEASING.md Outdated
- Treat CRLF and LF archive reports as equivalent while preserving stored bytes.
- Share comparison selection and report-update guidance across generation paths.
- Isolate artifact path resolution and retained-artifact rendering.
- Clarify that durable benchmark downloads are GitHub Release assets.
@acgetchell
acgetchell merged commit 5c004ad into main Aug 5, 2026
21 checks passed
@acgetchell
acgetchell deleted the feat/205-performance-artifacts branch August 5, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persist CSV inputs for release performance rerendering

1 participant