Skip to content

Skip benchmark-ratchet on non-performance pull requests (4.4.3.) - #289

Open
leynos wants to merge 6 commits into
mainfrom
ci/path-gate-benchmark-ratchet
Open

Skip benchmark-ratchet on non-performance pull requests (4.4.3.)#289
leynos wants to merge 6 commits into
mainfrom
ci/path-gate-benchmark-ratchet

Conversation

@leynos

@leynos leynos commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

This branch stops the benchmark-ratchet job — the only paid Ubicloud job
in this repository — from running on pull requests that cannot affect
performance. A July 2026 Ubicloud usage audit found the job firing on every
one of 420 CI runs that month, including docs-only edits and Dependabot
github-actions batches, making cuprum the estate's dominant premium-4
consumer.

A new changes job (GitHub-hosted, completes in seconds) classifies the
diff with dorny/paths-filter; benchmark-ratchet now runs on pull
requests only when performance-relevant paths change. Pushes to main are
never gated, because the main run refreshes the
benchmark-ratchet-main-baseline artifact that pull-request runs compare
against. Dependency bumps that touch uv.lock or rust/ still benchmark
by design, since a dependency change can legitimately alter throughput.

A companion commit adds
.github/actionlint.yaml
declaring the ubicloud-standard-4-ubuntu-2404 label, matching the
convention already present in lille, wildside, nile-valley, and chutoro,
so that actionlint passes on this repository's workflows.

Review walkthrough

  • Start with the changes job and the gated needs/if on
    benchmark-ratchet in
    .github/workflows/ci.yml
    — the comments explain why changes runs on every event (a skipped
    needs dependency would otherwise skip the main-branch baseline run).
  • Then .github/actionlint.yaml
    for the runner-label declaration.

Validation

  • actionlint .github/workflows/ci.yml: clean (with the new label
    declaration).
  • uv run pytest cuprum/unittests/test_extension_ci_contract.py cuprum/unittests/test_fetch_main_benchmark_baseline.py -q: 15 passed.

Notes

  • dorny/paths-filter is pinned to the v3.0.2 commit
    (de90cc6f…), consistent with the repository's SHA-pinning policy; the
    grouped github-actions Dependabot ecosystem will keep it updated.
  • If benchmark-ratchet is a required status check, a skipped run counts
    as satisfied, so gated pull requests still merge normally.
  • .github/actionlint.yaml now lists CODESCENE_CLI_SHA256 rather than
    disabling the configuration-variable check, so a typo in a vars.*
    reference is an actionlint error rather than an empty string at run time.
  • One commit here is unrelated housekeeping: the estate-wide typos
    dictionary stopped exempting inline code spans, so regenerating
    typos.toml broke the spelling gate on three pre-existing identifiers.
    They are exempted individually in typos.local.toml rather than by
    reinstating the blanket rule.

Review feedback addressed

  • Tests. Both styles the repository requires now cover the gate, reading
    one ci.yml through one model in tests/helpers/workflow.py.
    cuprum/unittests/test_benchmark_gate_ci_contract.py parses
    ci.yml and asserts the gate: the bench output wiring, the needs
    edge, the gate expression verbatim, the exact filter path set, the
    GitHub-hosted runner for changes, the summary step, and the
    concurrency policy. Property tests (Hypothesis) over sampled changed-path
    sets check the rule those parts encode — any watched path benchmarks
    however it is mixed with docs, a diff touching nothing watched skips, and
    a non-pull-request event always benchmarks. A companion test fails if a
    filter pattern outside the two modelled forms is added, so the path model
    cannot silently stop describing the filter. Verified non-vacuous by
    inverting the gate and deleting a filter path: both mutations fail.
    tests/behaviour/test_benchmark_path_gate_behaviour.py, with
    tests/features/benchmark_path_gate.feature, states the decision for
    recognizable pull requests: docs-only, a Rust change, a dependency bump, a
    mixed diff, an empty diff, and a push to main. A further test pins the
    absence of a status function in the gate — GitHub inserts an implicit
    success() unless the expression names one, so a failed detector skips the
    paid job, and if: always() && (…) would be the single edit that turns a
    broken detector into an unconditional paid run.
  • Observability. The changes job appends the decision — event, filter
    verdict, and whether the benchmark ran or was skipped — to
    $GITHUB_STEP_SUMMARY on every run. All three fields are closed sets, so
    the summaries stay countable; a skipped job and a broken gate are
    otherwise indistinguishable in the run list.
  • Concurrency and state. The workflow now declares
    concurrency: ci-${{ github.ref }} with cancel-in-progress true only
    for pull requests. Superseded pull-request runs are cancelled; runs on
    main are not, because a cancelled run abandons the baseline upload, and
    queueing them keeps two quick merges from publishing that artifact out of
    commit order.
  • Documentation. "Gating the paid benchmark job" in
    docs/developers-guide.md records the path list, the runner label, the
    main-push behaviour, and why changes is ungated; docs/users-guide.md
    and docs/cuprum-design.md state the gate where they describe the
    ratchet, and ExecPlan 4.4.3 carries an amendment note.

Summary by Sourcery

Reduce paid benchmark usage by running benchmark-ratchet only for performance-relevant pull requests while preserving main-branch baseline updates.

Enhancements:

  • Gate the paid benchmark-ratchet job on performance-relevant pull-request changes while continuing to benchmark all non-pull-request events for baseline refreshes.
  • Record benchmark gate decisions in workflow summaries and cancel superseded pull-request runs without cancelling main-branch runs.
  • Add shared workflow contract and behavioural coverage for path filtering, gate decisions, summaries, and concurrency.

CI:

  • Add actionlint configuration for the Ubicloud runner label and workflow configuration variable validation.

Documentation:

  • Document the benchmark gate, watched paths, baseline behaviour, observability, and concurrency policy in the developer, user, and design guides.

Tests:

  • Add declarative, property-based, behavioural, and script-execution tests covering the benchmark path gate and its summary output.

Chores:

  • Update typo configuration to replace the blanket inline-code exemption with targeted identifier exceptions and centralize workflow parsing helpers.

References

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Important

Approval pending

CodeRabbit has no unresolved comments, but it has not reviewed the latest commit.

Use the checkbox below to review the latest commit. CodeRabbit will approve the changes if it finds no blocking issues.

  • 🔍 Trigger review

Summary

  • Add a changes job with dorny/paths-filter to detect performance-relevant changes.
  • Run benchmark-ratchet for all pushes to main.
  • Run benchmark-ratchet on pull requests only when relevant paths change.
  • Add workflow concurrency and run-summary reporting for gate decisions.
  • Add .github/actionlint.yaml with the ubicloud-standard-4-ubuntu-2404 runner label.
  • Add contract, property-based, and behavioural tests for the gate.
  • Document the gate in the design, developer, user, and performance execplan documents.
  • Validate the workflow with clean actionlint output and 15 passing tests.

Walkthrough

Configure actionlint and spell-check exceptions. Add a changes job that detects performance-relevant paths, records its decision, and gates benchmark-ratchet for pull requests. Keep pushes to main ungated. Add contract, behavioural, and documentation coverage.

Changes

CI benchmark workflow

Layer / File(s) Summary
Gate benchmark execution
.github/workflows/ci.yml
Add workflow concurrency, detect performance-relevant changes, publish the bench output, record the gate decision, and apply event-specific benchmark execution.
Validate workflow contracts
tests/helpers/workflow.py, cuprum/unittests/test_benchmark_gate_ci_contract.py, cuprum/unittests/__snapshots__/test_maturin_build.ambr
Add typed workflow helpers, path matching, execution modelling, contract checks, property-based tests, and the packaged test snapshot entry.
Cover gate behaviour and documentation
tests/features/benchmark_path_gate.feature, tests/behaviour/test_benchmark_path_gate_behaviour.py, docs/cuprum-design.md, docs/developers-guide.md, docs/execplans/4-4-3-ratchet-rust-performance.md, docs/users-guide.md
Define benchmark gate scenarios and document watched paths, event handling, concurrency, summary reporting, and validation coverage.
Configure validation tools
.github/actionlint.yaml, typos.local.toml, typos.toml
Configure the self-hosted runner label, allowlist CODESCENE_CLI_SHA256, and add targeted spelling exceptions.

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant changes
  participant benchmark-ratchet
  GitHubActions->>changes: Detect changed paths
  changes-->>GitHubActions: Publish bench output and run summary
  GitHubActions->>benchmark-ratchet: Run for non-PR events or relevant PRs
Loading

Possibly related PRs

Suggested reviewers: codescene-access

Poem

Set the lint rules in place,
Trace changed paths through the gate,
Run benchmarks when paths align,
Keep main baselines on time,
Let contracts guard the workflow.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 4 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error The gate tests are substantive, but the summary test only searches for tokens; a no-op or always-wrong summary script containing those strings would still pass. Add a behavioural test that executes or parses the summary script for relevant, irrelevant, and non-pull-request inputs, and asserts the emitted event, verdict, and run/skip decision.
Testing (Unit And Behavioural) ⚠️ Warning The BDD scenarios only call test helpers that reimplement the filter and gate; no test executes the GitHub workflow, dorny/paths-filter action, or actual job boundary. Replace the model-only BDD coverage with a workflow-boundary integration test using a workflow runner or equivalent action/expression harness, and retain the YAML contract tests for structure.
Observability ⚠️ Warning The gate summary records event, filter result, and run/skip, but no bounded resource-use metric exists and the summary step is skipped when paths-filter fails. Add a bounded run/skip/detector-failure metric and an always-run summary that records detector status and the resulting benchmark decision, including failure cases.
Concurrency And State ⚠️ Warning Fail: ci.yml uses cancel-in-progress: false for main, but pending runs can be replaced and GitHub does not guarantee FIFO; the sole test checks YAML values, not interleavings. Replace the claimed FIFO scheme with commit-aware, monotonic baseline publication, or remove the claim, and test rapid pushes, pending replacement, cancellation, and out-of-order completion.
Architectural Complexity And Maintainability ⚠️ Warning The PR adds a second ci.yml parser in tests/helpers/workflow.py while test_extension_ci_contract.py retains an equivalent parser; WorkflowContractError also has no consumer. Consolidate ci.yml parsing and shape validation into one shared helper, migrate test_extension_ci_contract.py, and remove unused abstraction or add a real consumer.
Unit Architecture ❓ Inconclusive Initial inspection found declarative CI changes and test-only helpers; I need to verify whether the new file/parsing APIs violate the check or follow existing test architecture. Inspect repository conventions and the complete changed-file diff before deciding.
✅ Passed checks (14 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
User-Facing Documentation ✅ Passed Document the gate in docs/users-guide.md: it lists all watched paths, skips documentation-only pull requests, always runs on main pushes, and records the workflow summary decision.
Developer Documentation ✅ Passed Documentation covers the CI boundary and tooling change: the guide names dorny/paths-filter and actionlint configuration, while design and ExecPlan record the gating rationale and progress.
Module-Level Documentation ✅ Passed Accept this check: each of the three added Python modules has a module docstring that states its purpose, utility, and relationship to the related CI tests or workflow.
Testing (Property / Proof) ✅ Passed Accept the check: Hypothesis properties cover mixed relevant paths, irrelevant and empty pull requests, and non-pull-request events; the model parses ci.yml and guards its pattern assumptions.
Testing (Compile-Time / Ui) ✅ Passed No Rust or TypeScript files changed, so no new compile-time test is required. The PR adds focused workflow contract/behaviour tests and a normalised, meaningful wheel snapshot.
Domain Architecture ✅ Passed The PR changes CI workflow configuration, documentation, and test helpers only; no domain model or production business logic now depends on transport, persistence, framework, or vendor concerns.
Security And Privacy ✅ Passed Keep the change: added code contains no secret values, uses pinned actions, grants changes only read permissions, and writes only closed-set gate values to the summary.
Performance And Resource Use ✅ Passed No performance or resource regression is evident: the gate adds one bounded, cached YAML parse and constant-pattern path matching, while the workflow uses a cheap hosted detector.
Rust Compiler Lint Integrity ✅ Passed The full PR diff from origin/main changes no Rust files and adds no Rust lint suppressions, artificial references, or clone operations; this Rust-specific check is not applicable.
Title check ✅ Passed The title accurately summarises the main CI change and references roadmap item 4.4.3.
Description check ✅ Passed The description directly explains the benchmark gate, workflow changes, tests, documentation, and validation.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/path-gate-benchmark-ratchet

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

codescene-access[bot]

This comment was marked as outdated.

@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds a lightweight changes classification job to CI and gates the paid benchmark-ratchet workflow on performance-relevant file changes, plus configures actionlint to recognize the Ubicloud runner label.

Flow diagram for gating benchmark-ratchet on performance-relevant changes

flowchart LR
  GitHubEvent[GitHub event]
  ChangesJob[changes job]
  PathsFilter[dorny/paths-filter]
  BenchOutput{needs.changes.outputs.bench}
  BenchmarkRatchet[benchmark-ratchet job]

  GitHubEvent --> ChangesJob
  ChangesJob --> PathsFilter
  PathsFilter --> BenchOutput

  BenchOutput -- pull_request and bench == 'true' --> BenchmarkRatchet
  GitHubEvent -- event_name != 'pull_request' --> BenchmarkRatchet
Loading

File-Level Changes

Change Details Files
Introduce a changes classification job to detect performance-relevant diffs and expose an output flag for downstream jobs.
  • Add a changes job running on ubuntu-latest with minimal read permissions for contents and pull-requests.
  • Use dorny/paths-filter (pinned by commit SHA) to define a bench filter matching performance-relevant paths such as cuprum/, rust/, benchmarks/, core config files, and CI workflow YAML.
  • Expose the bench result via job outputs for consumption by other jobs.
.github/workflows/ci.yml
Gate benchmark-ratchet execution on the changes job output so benchmarks only run when needed, while preserving always-on behavior for main branch pushes.
  • Add changes as a needs dependency of benchmark-ratchet to ensure the classification step runs before benchmarks.
  • Add an if condition combining event type and changes.bench output so benchmark-ratchet runs on all non-pull_request events and only on pull requests with performance-relevant changes.
  • Document the cost rationale and behavior of the gating logic directly in the workflow comments.
.github/workflows/ci.yml
Configure actionlint to understand the Ubicloud self-hosted runner label and disable configuration variable checks.
  • Add .github/actionlint.yaml with a self-hosted-runner.labels entry for ubicloud-standard-4-ubuntu-2404.
  • Set config-variables to null to disable config variable checking in actionlint.
.github/actionlint.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@leynos
leynos marked this pull request as ready for review August 5, 2026 20:54
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@sourcery-ai sourcery-ai 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.

Sorry @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

codescene-access[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 @.github/actionlint.yaml:
- Line 9: Update the config-variables setting in actionlint.yaml from null to an
allow-list containing only CODESCENE_CLI_SHA256, matching the sole vars.*
workflow variable.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 16a12af4-b20a-4299-906c-ee4719a4b012

📥 Commits

Reviewing files that changed from the base of the PR and between b1e6452 and 8871cdc.

📒 Files selected for processing (2)
  • .github/actionlint.yaml
  • .github/workflows/ci.yml
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/shared-actions (auto-detected)
  • leynos/pylint-pypy-shim (auto-detected)
  • leynos/whitaker (auto-detected)

Comment thread .github/actionlint.yaml Outdated
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

leynos and others added 4 commits August 8, 2026 02:01
`actionlint` rejects `ubicloud-standard-4-ubuntu-2404` as an unknown
runner label without a `self-hosted-runner` declaration. The other
Ubicloud-using repositories in the estate (lille, wildside,
nile-valley, chutoro) already carry `.github/actionlint.yaml` for this
reason; bring cuprum in line so workflow changes can be linted.
`benchmark-ratchet` is the only paid Ubicloud job in this workflow,
and a July 2026 usage audit found it running on every one of 420 CI
runs — including docs edits and Dependabot github-actions batches that
cannot change pipeline throughput.

Add a `changes` job (GitHub-hosted, seconds) that classifies the diff
with `dorny/paths-filter`, and gate `benchmark-ratchet` on it for pull
requests. Performance-relevant paths — `cuprum/`, `rust/`,
`benchmarks/`, `conftest.py`, the `Makefile`, `pyproject.toml`,
`uv.lock`, and the workflow itself — still trigger the ratchet, so
dependency bumps that could alter performance are still measured.

Pushes to main are never gated: the ratchet must run there to refresh
the `benchmark-ratchet-main-baseline` artifact that pull-request runs
compare against. The `changes` job itself runs on every event so the
`needs` edge cannot leave `benchmark-ratchet` skipped on main.
The estate-wide typos base no longer exempts inline code spans wholesale,
so regenerating typos.toml drops the rule the committed copy still carried
and three real identifiers start failing the spelling gate: a helper name,
a third-party command-line flag, and the style guide's own example of a
US-spelled API name.

Exempt them one at a time in typos.local.toml rather than reinstating the
blanket rule; prose inside backticks should still be spell-checked. Commit
the regenerated typos.toml so the gate stops re-deriving the drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gate in front of benchmark-ratchet lives entirely in declarative
configuration, and every part of it fails silently in the direction that
costs money or hides a regression: invert the condition and a genuine
performance change merges unbenchmarked, drop the event clause and pushes
to main stop refreshing the baseline that pull-request runs compare
against. No ordinary test notices, so parse ci.yml and assert the contract
— the bench output wiring, the needs edge, the gate expression verbatim,
the exact filter path set, and the runner the detector uses. Property
tests over sampled changed-path sets then check the rule those parts
encode, against a path model a companion test stops from drifting away
from the filter it claims to describe.

Record the decision — event, filter verdict, ran or skipped — in the run
summary of every run, because a skipped job and a broken gate look
identical in the run list, and this gate exists to be audited against
paid-runner spend.

Add a per-ref concurrency policy while here. Superseded pull-request runs
are cancelled, since a stale run only spends benchmark minutes on a diff
nobody will merge; runs on main are not, because cancelling one abandons
the baseline upload, and queueing them keeps two quick merges from
publishing that artifact out of commit order.

Narrow actionlint's config-variables to the one variable the workflows
read, so a typo in a vars.* reference is an error rather than an empty
string at run time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lodyai
lodyai Bot force-pushed the ci/path-gate-benchmark-ratchet branch from 22ac97a to d80b30d Compare August 8, 2026 00:06
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Two red checks on this branch are pre-existing and unrelated to the change; recording the evidence.

benchmark-ratchet — the current main baseline contains an outlier.
After rebasing, this branch's runtime code is identical to main, yet the ratchet reports a 0.46 regression. Three of the four scenarios agree closely with the baseline; one does not:

scenario baseline ratio candidate ratio regression
medium-single-cb 0.916 1.047 +0.143
medium-single-nocb 0.760 1.110 +0.461
small-single-cb 1.105 1.088 −0.016
small-single-nocb 1.166 1.076 −0.077

That 0.760 comes from the main run at 7f6ec92, which measured medium-single-nocb at 0.760 against its own baseline of 1.013 — a −25% swing it recorded as a pass and then published as the new baseline. The candidate's 1.110 is in line with its three siblings; the baseline entry is the outlier. Every pull request will fail this scenario until a main push republishes a representative baseline. Worth a follow-up on the ratchet's noise handling rather than a change here.

Python 3.15a fails four test_non_positive_timeout_at_public_boundary cases on a CPython 3.15 alpha change to TimeoutExpired.output. That row is continue-on-error, and #293 is addressing it.

All local commit gates pass on the rebased branch: make check-fmt, make lint, make typecheck, make test, make markdownlint, make nixie.

@leynos

leynos commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat o11y, code safety, documentation and validation coverage as in scope).

❌ Failed checks (1 error, 5 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error The PR adds no tests; existing tests cover benchmark helpers and build steps, not the new paths-filter paths, output, needs edge, or event gating. Add workflow contract tests that parse ci.yml and fail for missing or inverted gating, wrong paths, missing changes dependency, and incorrect push/pull_request conditions.
Developer Documentation ⚠️ Warning The PR adds changes/dorny/paths-filter gating and .github/actionlint.yaml, but docs/developers-guide.md has no documentation for them and no docs files changed. Update docs/developers-guide.md, the benchmark design/user guidance, and ExecPlan 4.4.3 to record the path gate, path list, runner label, and main-push behaviour.
Testing (Unit And Behavioural) ⚠️ Warning The PR adds CI gating in .github/workflows/ci.yml but no tests; the reported tests cover existing benchmark code, while actionlint checks syntax only. Add workflow-boundary contract tests for main pushes, relevant and irrelevant pull-request paths, the filter list, and the benchmark job condition.
Testing (Property / Proof) ⚠️ Warning Flag the missing property-testing recommendation: the new gate varies over event types and arbitrary changed-path sets, but the PR adds no property/model-based test or recommendation. Add a Hypothesis or bounded-model contract test for path-set classification and event gating, covering matching, non-matching, mixed diffs, and main pushes.
Observability ⚠️ Warning Fail this check: the new changes/benchmark-ratchet gate changes paid-runner consumption, but only exposes an internal bench output and adds no bounded metric or explicit decision summary. Add a low-cardinality CI gate signal that records event type, bench result, and benchmark invocation or skip, then document where maintainers can query it.
Concurrency And State ⚠️ Warning benchmark-ratchet now depends on changes and writes the shared main baseline, but ci.yml has no concurrency policy and no tests cover skipped PRs or out-of-order baseline runs. Add an explicit baseline ordering or concurrency policy, and add workflow contract tests for PR gating, main refresh, dependency failure, and out-of-order main runs.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== PR files and checks =='
gh pr view 289 --repo leynos/cuprum --json headRefName,baseRefName,files,statusCheckRollup,url \
  --jq '{url, headRefName, baseRefName, files: [.files[].path], checks: [.statusCheckRollup[] | {name, conclusion, status}]}'

printf '%s\n' '== Relevant repository files =='
fd -H -t f -a '^(ci\.yml|actionlint\.yaml|developers-guide\.md|.*benchmark.*|.*workflow.*|.*execplan.*)$' . \
  | sort

printf '%s\n' '== Workflow outline and relevant content =='
ast-grep outline .github/workflows/ci.yml --items all || true
sed -n '1,280p' .github/workflows/ci.yml
printf '%s\n' '== actionlint configuration =='
cat .github/actionlint.yaml

printf '%s\n' '== Workflow/paths-filter/benchmark test and documentation references =='
rg -n -i -C 3 \
  '(paths-filter|benchmark-ratchet|workflow contract|workflow.*test|benchmark.*gate|ubic[l]?oud|concurrency|ExecPlan)' \
  -g '!uv.lock' .

printf '%s\n' '== Candidate follow-up issues =='
gh issue list --repo leynos/cuprum --state open --limit 100 \
  --search 'benchmark ratchet OR paths-filter OR workflow gating OR concurrency OR observability' \
  --json number,title,url,body \
  --jq '.[] | {number,title,url,body}'

Length of output: 186921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Changes and benchmark job =='
rg -n -A 105 -B 8 '^[[:space:]]{2}changes:|^[[:space:]]{2}benchmark-ratchet:' .github/workflows/ci.yml

printf '%s\n' '== Gate contract test outline =='
ast-grep outline cuprum/unittests/test_benchmark_gate_ci_contract.py --items all
printf '%s\n' '== Gate contract test =='
sed -n '1,430p' cuprum/unittests/test_benchmark_gate_ci_contract.py

printf '%s\n' '== Documentation and ExecPlan gate sections =='
sed -n '2200,2265p' docs/developers-guide.md
sed -n '195,214p' docs/execplans/4-4-3-ratchet-rust-performance.md

printf '%s\n' '== Remaining failure issues =='
gh issue view 219 --repo leynos/cuprum --json number,state,title,url --jq '{number,state,title,url}'
gh issue view 293 --repo leynos/cuprum --json number,state,title,url --jq '{number,state,title,url}'

Length of output: 30718


@leynos: Treat all six reported findings as resolved.

Reported check Resolution verified
Testing (Overall) cuprum/unittests/test_benchmark_gate_ci_contract.py parses ci.yml and asserts the bench output, exact path list, needs: changes, and exact benchmark condition.
Developer Documentation docs/developers-guide.md and docs/execplans/4-4-3-ratchet-rust-performance.md document the path gate, runner label, filter paths, main baseline refresh, decision summary, tests, and concurrency policy.
Testing (Unit And Behavioural) The workflow contract test covers the detector job, output wiring, dependency edge, filter paths, and pull-request versus non-pull-request gate.
Testing (Property / Proof) Hypothesis tests cover relevant-only, irrelevant-only, mixed path sets, and non-pull-request events. The test also rejects filter patterns outside its bounded model.
Observability The changes job writes the event, bench verdict, and run or skip decision to $GITHUB_STEP_SUMMARY. These fields have bounded values.
Concurrency And State ci.yml uses concurrency.group: ci-${{ github.ref }}. It cancels superseded pull-request runs and queues main runs. The contract test asserts both settings.

Keep the two remaining red checks out of this PR’s scope:

  • Track the benchmark-ratchet false positive in existing issue #219.
  • Track the Python 3.15a timeout-capture failure in existing PR #293.

Do not create a new issue. Do not add further implementation work to this PR. No AI agent prompt is required.

You are interacting with an AI system.

The contract tests pin what ci.yml declares; they do not say what those
declarations decide for a pull request someone would recognize. Add the
behavioural half — docs-only, a Rust change, a dependency bump, a mixed
diff, an empty diff, and a push to main — so the gate is covered in both
of the styles this repository requires.

Move the workflow parsing and the path model into tests/helpers/workflow.py
so the two suites read one ci.yml through one model, rather than agreeing
by coincidence.

Pin the absence of a status function in the gate as well. GitHub inserts an
implicit success() unless the expression names one, so a failed detector
currently skips the paid job; `if: always() && (...)` reads as a harmless
robustness tweak and is the single edit that would turn a broken detector
into an unconditional paid run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No quality gates enabled for this code.

@leynos

leynos commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Update: benchmark-ratchet is green on the latest run, but the baseline outlier has not gone away — the run simply landed inside the threshold this time.

scenario baseline ratio candidate ratio regression
medium-single-cb 0.916 1.071 +0.170
medium-single-nocb 0.760 0.924 +0.217
small-single-cb 1.105 0.983 −0.111
small-single-nocb 1.166 1.158 −0.007

The baseline for medium-single-nocb is still the 0.760 published by 7f6ec929; this run measured 0.924 against it rather than the 1.110 measured earlier, so the same scenario moved from +0.461 to +0.217 with no code change on either side. The follow-up on the ratchet's noise handling still stands.

The only remaining red check is the Python 3.15a row, which is continue-on-error and is being addressed in #293.

@leynos

leynos commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Both of the red checks discussed above are now tracked in their existing threads, so they need no further action on this pull request:

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 @.github/workflows/ci.yml:
- Around line 12-21: Separate main-branch baseline publication from pull-request
cancellation in the workflow’s concurrency configuration: choose and explicitly
implement a main-only queue that retains required runs, including the
queue-limit behavior, or document latest-run semantics if that is the intended
policy. Update cuprum/unittests/test_benchmark_gate_ci_contract.py at lines
297-318 to assert the selected policy, and revise the concurrency and
baseline-publication claims in docs/cuprum-design.md lines 2469-2482,
docs/developers-guide.md lines 2246-2250, and docs/users-guide.md lines
1633-1638 to match it.

In `@docs/execplans/4-4-3-ratchet-rust-performance.md`:
- Around line 200-219: Update the earlier completion criteria and retrospective
in the execplan to match the current ratchet policy: pull-request benchmarks run
only for performance-relevant path changes, and regressions fail only when above
0.30. Remove or revise claims requiring every pull request and a 10% threshold,
while preserving the later amendment and its documented gating behavior.

In `@tests/helpers/workflow.py`:
- Line 71: Update _require so condition is keyword-only and remove the FBT001
suppression; if positional Boolean usage is required, retain the narrow
suppression only with an inline justification explaining that necessity.
- Around line 39-180: Document the public interfaces with comprehensive
NumPy-style docstrings: in tests/helpers/workflow.py (lines 39-180), add
Parameters, Returns, and Raises sections where applicable for Step, Job,
Workflow, WorkflowContractError, and public helpers; keep private helpers
concise. In cuprum/unittests/test_benchmark_gate_ci_contract.py (lines 98-318),
document every public pytest test function using the required sections where
applicable. In tests/behaviour/test_benchmark_path_gate_behaviour.py (lines
20-161), document Event, Decision, and all public scenario and step functions in
the same style; do not alter behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0701c131-6447-4b38-9cda-37d35e6bd966

📥 Commits

Reviewing files that changed from the base of the PR and between 7f6ec92 and 67aa40b.

📒 Files selected for processing (13)
  • .github/actionlint.yaml
  • .github/workflows/ci.yml
  • cuprum/unittests/__snapshots__/test_maturin_build.ambr
  • cuprum/unittests/test_benchmark_gate_ci_contract.py
  • docs/cuprum-design.md
  • docs/developers-guide.md
  • docs/execplans/4-4-3-ratchet-rust-performance.md
  • docs/users-guide.md
  • tests/behaviour/test_benchmark_path_gate_behaviour.py
  • tests/features/benchmark_path_gate.feature
  • tests/helpers/workflow.py
  • typos.local.toml
  • typos.toml
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/shared-actions (auto-detected)
  • leynos/pylint-pypy-shim (auto-detected)
  • leynos/whitaker (auto-detected)

Comment thread .github/workflows/ci.yml
Comment on lines +200 to +219
### Later amendment: gating the job on changed paths

A July 2026 audit of Ubicloud usage found this job — the repository's only
paid job — running on all 420 CI runs that month, including documentation
edits and Dependabot `github-actions` batches. A `changes` job now classifies
the diff with `dorny/paths-filter`, and `benchmark-ratchet` runs on pull
requests only when a performance-relevant path changed. The
`push`-to-`main` trigger stated above is deliberately left ungated: that run
publishes the baseline artefact every later comparison reads, so filtering it
would fail open rather than merely cost less. `.github/actionlint.yaml`
declares the Ubicloud runner label so `actionlint` can check the workflow.

Both test styles cover the gate, as this plan's constraints require:
`cuprum/unittests/test_benchmark_gate_ci_contract.py` pins the declarations
and property-tests the rule they encode, while
`tests/behaviour/test_benchmark_path_gate_behaviour.py` and
`tests/features/benchmark_path_gate.feature` state the decision for
recognizable pull requests. See "Gating the paid benchmark job" in
`docs/developers-guide.md`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Synchronize the completed plan with the current ratchet policy.

Update the earlier completion criteria and retrospective. They still require a
benchmark on every pull request and a 10% regression threshold. The workflow
now filters pull requests and fails only above 0.30. The later amendment does
not remove those contradictory claims.

Triage: [type:docstyle]

As per coding guidelines, “Keep design decisions, roadmap completion status, and
any execplan up to date.”

🧰 Tools
🪛 LanguageTool

[uncategorized] ~209-~209: The official name of this software platform is spelled with a capital “H”.
Context: ...fail open rather than merely cost less. .github/actionlint.yaml declares the Ubicloud ...

(GITHUB)

🤖 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 `@docs/execplans/4-4-3-ratchet-rust-performance.md` around lines 200 - 219,
Update the earlier completion criteria and retrospective in the execplan to
match the current ratchet policy: pull-request benchmarks run only for
performance-relevant path changes, and regressions fail only when above 0.30.
Remove or revise claims requiring every pull request and a 10% threshold, while
preserving the later amendment and its documented gating behavior.

Sources: Coding guidelines, Path instructions

Comment thread tests/helpers/workflow.py
Comment on lines +39 to +180
class Step(typ.TypedDict, total=False):
"""One step of a job, declaring only the keys these helpers read."""

id: object
uses: object
run: object


class Job(typ.TypedDict, total=False):
"""One job of a workflow, declaring only the keys these helpers read."""

needs: object
outputs: object
steps: list[Step]


class Workflow(typ.TypedDict, total=False):
"""A parsed workflow file, declaring only the keys these helpers read."""

concurrency: object
jobs: dict[str, Job]


class WorkflowContractError(AssertionError):
"""The workflow does not have the shape the contract tests require.

Raised rather than asserted so that a malformed workflow fails with the
same message whichever suite read it, and so that a suite can distinguish
"the file is not shaped like a workflow" from "the contract is not met".
"""


def _require(condition: bool, message: str) -> None: # noqa: FBT001
"""Raise `WorkflowContractError` when a shape requirement is unmet."""
if not condition:
raise WorkflowContractError(message)


def mapping(value: object, message: str) -> dict[str, object]:
"""Require that a value read from the workflow is a mapping, and type it.

`yaml.safe_load` produces mappings of unknown key type, which makes every
subsequent `.get("…")` a type error rather than a narrowing.
"""
_require(isinstance(value, dict), message)
return typ.cast("dict[str, object]", value)


@functools.cache
def workflow() -> Workflow:
"""Parse the CI workflow."""
parsed = yaml.safe_load((repo_root() / CI_WORKFLOW).read_text(encoding="utf-8"))
_require(isinstance(parsed, dict), f"{CI_WORKFLOW} must parse to a mapping")
return typ.cast("Workflow", parsed)


def job(job_name: str) -> dict[str, object]:
"""Return a named job, failing with the available names when absent."""
jobs = mapping(workflow().get("jobs"), f"{CI_WORKFLOW} must declare a jobs mapping")
return mapping(
jobs.get(job_name),
f"{CI_WORKFLOW} must declare a {job_name!r} job; found {sorted(jobs)}",
)


def steps(job_name: str) -> list[dict[str, object]]:
"""Return the steps of a named job."""
declared = job(job_name).get("steps")
_require(isinstance(declared, list), f"the {job_name!r} job must declare steps")
return typ.cast("list[dict[str, object]]", declared)


def step_with_id(job_name: str, step_id: str) -> dict[str, object]:
"""Return the step of a job carrying a given `id:`."""
found = next(
(step for step in steps(job_name) if step.get("id") == step_id),
None,
)
return mapping(
found, f"the {job_name!r} job must declare a step with id {step_id!r}"
)


def benchmark_gate() -> str:
"""Return the `if:` expression gating the benchmark job."""
condition = job(BENCHMARK_JOB).get("if")
_require(
isinstance(condition, str),
f"the {BENCHMARK_JOB!r} job must declare an `if:` condition",
)
return typ.cast("str", condition)


@functools.cache
def filter_paths() -> frozenset[str]:
"""Return the path patterns the `bench` filter declares."""
step = step_with_id(CHANGES_JOB, FILTER_STEP_ID)
inputs = mapping(
step.get("with"),
f"the {FILTER_STEP_ID!r} step must pass inputs to the filter action",
)
filters = mapping(
yaml.safe_load(str(inputs["filters"])),
"the `filters` input must parse to a mapping",
)
patterns = filters.get(FILTER_NAME)
_require(
isinstance(patterns, list),
f"the filter must declare a {FILTER_NAME!r} list; found {sorted(filters)}",
)
return frozenset(str(pattern) for pattern in typ.cast("list[object]", patterns))


def matches_filter(pattern: str, path: str) -> bool:
"""Return whether a changed `path` matches a declared filter `pattern`.

A bounded model of the two pattern forms the filter is allowed to use: a
literal path, and a `dir/**` prefix. A contract test fails when a pattern
outside those forms is declared, so the model cannot silently stop
describing the filter it stands in for.
"""
if pattern.endswith("/**"):
return path.startswith(pattern.removesuffix("**"))
return path == pattern


def bench_output(changed_paths: cabc.Collection[str]) -> bool:
"""Model the `bench` output the filter produces for a set of changes."""
return any(
matches_filter(pattern, path)
for pattern in filter_paths()
for path in changed_paths
)


def benchmark_runs(*, event_name: str, bench: bool) -> bool:
"""Model the gate, returning whether `benchmark-ratchet` runs.

Mirrors the `if:` expression a contract test pins verbatim; the pin is what
keeps this model and the workflow from drifting apart.
"""
return event_name != "pull_request" or bench

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add structured NumPy-style docstrings to the new public Python interfaces.

Document public classes and functions with Parameters, Returns, and
Raises sections where applicable. Keep private helpers concise.

  • tests/helpers/workflow.py#L39-L180: document exported TypedDict classes,
    WorkflowContractError, and shared workflow helper functions.
  • cuprum/unittests/test_benchmark_gate_ci_contract.py#L98-L318: document the
    public pytest test functions to the required format.
  • tests/behaviour/test_benchmark_path_gate_behaviour.py#L20-L161: document
    Event, Decision, and public scenario and step functions to the required
    format.

As per coding guidelines, “Public Python functions, classes, and methods must
have comprehensive NumPy-style docstrings.” As per path instructions,
“Docstrings must follow the numpy style guide.”

📍 Affects 3 files
  • tests/helpers/workflow.py#L39-L180 (this comment)
  • cuprum/unittests/test_benchmark_gate_ci_contract.py#L98-L318
  • tests/behaviour/test_benchmark_path_gate_behaviour.py#L20-L161
🤖 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 `@tests/helpers/workflow.py` around lines 39 - 180, Document the public
interfaces with comprehensive NumPy-style docstrings: in
tests/helpers/workflow.py (lines 39-180), add Parameters, Returns, and Raises
sections where applicable for Step, Job, Workflow, WorkflowContractError, and
public helpers; keep private helpers concise. In
cuprum/unittests/test_benchmark_gate_ci_contract.py (lines 98-318), document
every public pytest test function using the required sections where applicable.
In tests/behaviour/test_benchmark_path_gate_behaviour.py (lines 20-161),
document Event, Decision, and all public scenario and step functions in the same
style; do not alter behavior.

Sources: Coding guidelines, Path instructions

Comment thread tests/helpers/workflow.py Outdated
"""


def _require(condition: bool, message: str) -> None: # noqa: FBT001

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove or justify the FBT001 suppression.

Make condition keyword-only, or add an inline reason that explains why a
positional Boolean is necessary. The policy forbids an unexplained # noqa
suppression.

As per path instructions, “Only narrow in-line disables (# noqa: XYZ) are
permitted, must be accompanied by justification and used only as a last resort.”

🤖 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 `@tests/helpers/workflow.py` at line 71, Update _require so condition is
keyword-only and remove the FBT001 suppression; if positional Boolean usage is
required, retain the narrow suppression only with an inline justification
explaining that necessity.

Source: Path instructions

The contract test asserted the summary step mentioned the right words. A
script that emitted nothing, or the opposite verdict, contains the same
words: the check could not fail for the reason it existed. Extract the
step's script from ci.yml and run it under bash for each combination of
event and detector state, then read back the row it emitted. Verified by
inverting the decision in the script: five cases fail.

Record the detector's own status too, and run the step on !cancelled()
rather than the implicit success(). A failed detector is the case most
worth recording — the benchmark then skips for a reason unrelated to the
diff — and a summary that stops being written exactly when the gate
misbehaves documents only the runs that needed no explanation. An absent
verdict now reads `unknown` rather than `false`, which would have asserted
"no performance-relevant changes" on the strength of nothing.

Correct the concurrency claim while here. Queueing per ref does not order
anything: GitHub replaces a pending run when a newer one arrives and
promises nothing about completion order, so two merges in quick succession
may still publish baselines out of commit order. State what the policy
actually buys and where ordering would have to be enforced instead.

Fold the workflow parsing in test_extension_ci_contract.py into
tests/helpers/workflow.py so one parser reads ci.yml, and drop
WorkflowContractError, which no caller distinguished from AssertionError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@leynos leynos changed the title Skip benchmark-ratchet on non-performance pull requests Skip benchmark-ratchet on non-performance pull requests (4.4.3.) Aug 20, 2026
@leynos

leynos commented Aug 20, 2026

Copy link
Copy Markdown
Owner Author

Addressed, with one skipped. Verified each finding against head first.

Testing (Overall) — valid, fixed. The summary test only searched for tokens, so a script that emitted nothing or the opposite verdict would have passed it. tests/features/benchmark_gate_summary.feature and tests/behaviour/test_benchmark_gate_summary_behaviour.py now extract the step's run: block from ci.yml, execute it under bash with the environment Actions supplies, and read back the emitted row — for a relevant pull request, a docs-only one, a push, a workflow_dispatch, a failed detector, and a detector that never ran. Verified non-vacuous by inverting the decision in the script: five cases fail.

Observability — valid, fixed. The step now carries if: ${{ !cancelled() }} instead of inheriting the implicit success(), and records the detector's own status. benchmark-ratchet is reported as exactly one of run, skip, skip-detector-failed, so the metric stays bounded. An absent verdict reads unknown rather than false, which would have asserted "no performance-relevant changes" on the strength of nothing measured.

Concurrency And State — valid, claim removed. You are right that per-ref queueing orders nothing: GitHub replaces a pending run when a newer one arrives and promises nothing about completion order. The comment, the test's rationale and the developers' guide now state what the policy actually buys (superseded pull-request runs cancelled; main runs left to finish) and say explicitly that anything needing monotonic baselines must enforce it where the artefact is written. Commit-aware publication is exactly what the stacked #306 does, so it is fixed there rather than restated here.

Architectural Complexity — valid, fixed. test_extension_ci_contract.py now reads ci.yml through tests/helpers/workflow.py, which is the only parser, and WorkflowContractError is gone — no caller distinguished it from AssertionError.

Title check — skipped as stated, applied anyway. No merged pull request in this repository carries a (4.4.3.)-style roadmap reference (the convention in merged titles is an issue number, e.g. (#258)), and this change amends the CI job that ExecPlan 4.4.3 delivered rather than implementing that item. Since it costs nothing and the plan is the one this touches, the title now carries the reference.

All six make gates pass, actionlint is clean, and cs delta reports no issues. #306 has been rebased onto this and re-verified.

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.

2 participants