Skip to content

Move ci.yml's inline shell into Cyclopts scripts under scripts/ci - #708

Draft
leynos wants to merge 4 commits into
mainfrom
ci-inline-shell-to-python
Draft

leynos wants to merge 4 commits into
mainfrom
ci-inline-shell-to-python

Conversation

@leynos

@leynos leynos commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Summary

This branch moves every substantive inline shell block in .github/workflows/ci.yml into Python scripts under scripts/ci, written to the repository's scripting standards. Each script is a Cyclopts application that reads its parameters from INPUT_* and ambient GitHub Actions environment variables, runs external programs through cuprum's allowlisted catalogue rather than a shell, and uses pathlib for the filesystem. The workflow invokes each one as uv run --script scripts/ci/<name>.py, so a step body is one line and every pin it needs is an env: entry beside it, where the workflow contract tests read it. The workflow drops from 400 lines, its file limit, to 329.

The five converted steps in ci.yml are the gawk staging for the test sandbox, the actionlint installer, the sccache statistics report, the instrumented-tree discard, and the Kani installer. The remaining run: lines are single make targets or version prints. The actionlint installer no longer runs the upstream downloader behind a curl override: the checksum-verified archive is the whole artefact, so the single actionlint member is extracted directly, and both installers refuse links and directories in archives and accept only https:// and file:// URLs.

Every script has behavioural tests with pytest and cmd-mox, which shim sudo, sccache, df, and cargo at the process boundary, and release fixtures are served over file:// so the real download, verification, and extraction path runs without a network. make test-ci-scripts runs the suite; CI runs it before make check-fmt.

No roadmap task or issue is attached, and no execplan exists for this change.

Review walkthrough

Validation

  • make test-ci-scripts: 54 passed
  • make test-workflow-contracts: 364 passed
  • make lint-python, make typecheck-python: pass
  • PATH="$HOME/go/bin:$PATH" make github-actions-lint: pass (yamllint and actionlint)
  • make check-fmt, make markdownlint, make nixie: pass
  • End-to-end smoke runs through uv run --script: discard_instrumented_tree.py against a scratch target directory; install_actionlint.py against the real v1.7.12 release with the pinned SHA-256 (cold install, then warm reuse reporting restored from the cache volume)

No Rust sources changed, so the Clippy, Whitaker, and cargo test gates were not run. The scripts have not yet run on a GitHub runner; the first CI run of this PR is the proof for stage_test_shell.py (needs sudo apt-get) and install_kani.py.

Notes

  • cuprum is pinned to leynos/cuprum@a2134c7a (the Makefile's CUPRUM_REF) rather than the PyPI 0.1.0 release, because the scripts use the RunOutputOptions runtime that 0.2.0 will ship. Bump CUPRUM_REF to the release pin once it is published. That revision has no Catalogue.from_programs either, so the scripts build a ProgramCatalogue from ProjectSettings; docs/scripting-standards.md still shows the older sketch and needs a separate correction.
  • The same sccache report block in ci-windows.yml, netsukefile-test.yml, and coverage-main.yml now runs report_sccache_stats.py too, and coverage-main.yml uses discard_instrumented_tree.py; lanes without uv gained a cache-less Setup uv step. The sccache contract requires the script on every compiling lane except release.yml's Windows smoke job, which only prints the JSON form and is recorded as the one exception.
  • The generated assertion messages in the new tests state the expectation being checked, as the df12 house lint requires a message on every assert.

References

Summary by Sourcery

Replace substantive inline CI shell with tested, secure Python helper scripts and update workflows, contracts, tooling, and documentation to use them.

New Features:

  • Add reusable Python/Cyclopts helpers for staging test dependencies, installing actionlint and Kani, reporting sccache statistics, and cleaning instrumented build trees.
  • Use the shared sccache reporting and cleanup scripts across the applicable CI workflows.

Bug Fixes:

  • Replace actionlint's installer-script download path with direct extraction from a checksum-verified release archive and reject unsafe archive members or unsupported download schemes.

Enhancements:

  • Move substantive CI shell logic into tested, allowlisted Python scripts invoked through uv, while keeping workflow steps concise and pins explicit in step environments.
  • Add cache-aware Kani and actionlint installation with version validation and GitHub Actions path publication.

Build:

  • Add the test-ci-scripts Make target and include CI script dependencies and paths in Python type checking.

CI:

  • Run CI helper script tests before formatting checks and add uv setup where workflows invoke the scripts.
  • Update workflow contract checks to validate script invocations and their pinned inputs instead of inline shell contents.

Documentation:

  • Document the CI helper scripts, their testing workflow, dependency pins, and revised actionlint setup in the developers' guide.

Tests:

  • Add behavioral pytest and cmd-mox coverage for all CI scripts, shared download and extraction helpers, cache reuse, failure handling, and metadata contracts.

The five substantive `run:` blocks in .github/workflows/ci.yml (staging
gawk for the sandbox, installing actionlint, reporting sccache statistics,
discarding the instrumented build trees, and installing Kani) become
Python scripts under scripts/ci, written to docs/scripting-standards.md:
each is a Cyclopts application reading its parameters from INPUT_* and
ambient GitHub Actions environment variables, running external programs
through cuprum's allowlisted catalogue rather than a shell, and using
pathlib for the filesystem. The workflow invokes each as
`uv run --script scripts/ci/<name>.py`, so every pin sits beside its step
as an `env:` entry the contract tests can read, and ci.yml drops from 400
lines to 329. Setup uv moves ahead of the first script in build-test and
is added to kani-smoke.

The actionlint installer no longer runs the upstream downloader script
behind a curl override: the checksum-verified archive is the whole
artefact, so the single `actionlint` member is extracted directly. Both
installers refuse links and directories in archives and accept only
https:// and file:// URLs.

scripts/ci/ci_support.py holds the shared catalogue, runner, GITHUB_PATH
publication, and verified download and extraction. scripts/tests/test_ci_*
cover every script's happy path and failure modes with pytest and cmd-mox
shims for sudo, sccache, df, and cargo, and serve release fixtures over
file:// so the real download, verification, and extraction path runs
without a network. `make test-ci-scripts` runs them; CI runs it before
Format. The scripts pin cyclopts and cuprum in their PEP 723 blocks at the
Makefile's CYCLOPTS_VERSION and CUPRUM_VERSION, held equal by a test, and
ty checks them alongside the other Python sources.

The workflow contracts for the retired inline blocks now assert the script
invocation and the pins each step passes; the behaviours they spelled out
line by line live in the script tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR moves substantive inline CI shell into five tested, environment-driven Cyclopts scripts using cuprum for allowlisted process execution and pathlib for filesystem work. The workflow now delegates to one-line uv run --script invocations with explicit step-local pins, while shared secure archive helpers, behavioral tests, workflow contracts, Makefile integration, and documentation preserve and validate the original CI behavior.

Sequence diagram for verified CI archive installation

sequenceDiagram
    participant CI as GitHub Actions
    participant Script as install_actionlint.py or install_kani.py
    participant Release as Release archive
    participant Support as ci_support.py
    participant Disk as Versioned install directory
    participant Tool as actionlint or cargo kani

    CI->>Script: Run via uv run --script with INPUT_* pins
    Script->>Disk: Check cached executable or bundle
    alt payload missing
        Script->>Support: download(url, destination)
        Support->>Release: Fetch HTTPS or file URL
        Release-->>Support: Archive bytes
        Script->>Support: verify_sha256(archive, pin)
        Script->>Support: extract_members(archive, destination, members)
        Support-->>Disk: Write regular executable files
        Script->>Tool: Run setup when verifier bundle requires it
    end
    Script-->>CI: Installed or restored pinned tool
Loading

Sequence diagram for CI report and cleanup scripts

sequenceDiagram
    participant CI as GitHub Actions
    participant Report as report_sccache_stats.py
    participant Cache as sccache
    participant Summary as GITHUB_STEP_SUMMARY
    participant Cleanup as discard_instrumented_tree.py
    participant Disk as Target filesystem

    CI->>Report: Run statistics step
    Report->>Cache: show_stats(--show-stats)
    Report->>Cache: show_stats(--stats-format=json)
    Report->>Summary: Write text summary and report files
    CI->>Cleanup: Run cleanup step
    Cleanup->>Disk: show_disk_usage()
    Cleanup->>Disk: discard(target_dir, subtrees)
    Cleanup->>Disk: show_disk_usage()
Loading

File-Level Changes

Change Details Files
Replaced five substantive CI shell blocks with standalone, environment-driven Cyclopts Python applications invoked via one-line uv run --script workflow steps.
  • Added helpers for cuprum allowlisting, command execution, GitHub path publication, secure downloads, checksum verification, and safe archive extraction.
  • Implemented scripts for GNU Awk staging, actionlint installation, sccache reporting, instrumented-tree cleanup, and Kani installation.
  • Moved workflow pins into step-local INPUT_* environment variables and added uv setup before script execution.
  • Retained remaining inline commands only for single make targets or version checks.
.github/workflows/ci.yml
scripts/ci/ci_support.py
scripts/ci/stage_test_shell.py
scripts/ci/install_actionlint.py
scripts/ci/report_sccache_stats.py
scripts/ci/discard_instrumented_tree.py
scripts/ci/install_kani.py
Added comprehensive behavioral and metadata coverage for the new CI scripts and updated workflow contracts to validate delegation and pins rather than shell implementation details.
  • Added pytest and cmd-mox tests covering success, failure, cache reuse, environment inputs, command boundaries, checksum validation, archive member safety, and file:// release fixtures.
  • Added metadata checks tying PEP 723 dependency pins to Makefile versions and enforcing the env-first Cyclopts entry-point shape.
  • Updated workflow contract tests to assert script paths, setup ordering, and exact step-local pin values.
Makefile
scripts/tests/ci_script_support.py
scripts/tests/test_ci_support.py
scripts/tests/test_ci_stage_test_shell.py
scripts/tests/test_ci_install_actionlint.py
scripts/tests/test_ci_report_sccache_stats.py
scripts/tests/test_ci_discard_instrumented_tree.py
scripts/tests/test_ci_install_kani.py
scripts/tests/test_ci_scripts_metadata.py
tests/workflow_contracts/actionlint_installer_contract.py
tests/workflow_contracts/ci_lint_test.py
tests/workflow_contracts/github_actions_validation_test.py
tests/workflow_contracts/kani_cache_test.py
tests/workflow_contracts/sccache_contract_test.py
Strengthened archive installation security and simplified actionlint provisioning by extracting verified release members directly.
  • Restricted downloads to HTTPS and test-only file URLs.
  • Verified SHA-256 before extraction or handing bundles to Kani.
  • Rejected archive directories and links, extracting only named regular files as executables.
  • Removed the upstream actionlint downloader and its curl override.
scripts/ci/ci_support.py
scripts/ci/install_actionlint.py
scripts/ci/install_kani.py
Integrated the scripts into repository tooling, static checks, and developer documentation.
  • Added make test-ci-scripts and included CI script dependencies in Python type checking.
  • Exempted Cyclopts entry points from argument-count lint limits.
  • Documented script responsibilities, dependency pin ownership, testing, and revised actionlint setup.
Makefile
pyproject.toml
docs/developers-guide.md

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

codescene-access[bot]

This comment was marked as outdated.

cuprum 0.1.0 on PyPI predates the `RunOutputOptions` runtime the
scripting standards describe; 0.2.0 is close but unreleased. The scripts
and the Makefile's CI_SCRIPT_DEPS now pin `cuprum @ git+https://github.com/
leynos/cuprum@<CUPRUM_REF>`, held equal by the metadata test, and the
shared runner passes `output=RunOutputOptions(capture=True, echo=...)`.
Bump CUPRUM_REF to the release pin once 0.2.0 ships.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

ci-windows.yml, netsukefile-test.yml, and coverage-main.yml carried the
same inline sccache report block as ci.yml; each now runs
scripts/ci/report_sccache_stats.py, and coverage-main.yml runs
scripts/ci/discard_instrumented_tree.py for its instrumented tree as
well. The lanes that lacked uv gain a cache-less Setup uv step after
their credential export. The sccache contract now requires the script on
every compiling lane; the release smoke job, which only prints the JSON
form and has no uv, is the one recorded exception.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

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

Gates Failed
Enforce advisory code health rules (3 files with Complex Method, Excess Number of Function Arguments, Large Method)

Our agent can fix these. Install it.

Gates Passed
5 Quality Gates Passed

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
install_kani.py 2 advisory rules 9.28 Suppress
ci_support.py 2 advisory rules 9.39 Suppress
install_actionlint.py 1 advisory rule 9.69 Suppress

See analysis details in CodeScene

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

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.

1 participant