v1.6.0: harden audit comment ingestion #2384
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Code Mower CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| jobs: | |
| package_matrix: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12", "3.13", "3.14"] | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install package | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install ruff | |
| python -m pip install -e '.[coworker]' | |
| python -m pip check | |
| python -c 'from code_mower import coworker_mcp; import keyring' | |
| - name: Ruff lint | |
| run: python -m ruff check . | |
| - name: Privacy scan | |
| run: python scripts/privacy_scan.py | |
| - name: Documentation lifecycle | |
| if: matrix.python-version == '3.12' | |
| run: python -m code_mower.docs_lifecycle --json | |
| - name: Current release documentation | |
| if: matrix.python-version == '3.12' | |
| run: python scripts/render_release.py --check | |
| - name: Unit tests | |
| run: python -m unittest discover -s tests | |
| - name: Compile sources | |
| run: python -m compileall src scripts | |
| - name: Guard package workflow templates | |
| run: python scripts/guard_package_workflows.py | |
| - name: Install actionlint | |
| if: matrix.python-version == '3.12' | |
| run: | | |
| go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 | |
| echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" | |
| - name: Actionlint generated workflows | |
| if: matrix.python-version == '3.12' | |
| run: python scripts/actionlint_generated_workflows.py --code-mower-bin code-mower | |
| - name: Release readiness | |
| if: matrix.python-version == '3.12' | |
| run: python -m code_mower.migration release-readiness --json | |
| - name: Verify the base install without optional dependencies | |
| if: matrix.python-version == '3.12' | |
| run: | | |
| python -m venv "$RUNNER_TEMP/code-mower-base" | |
| "$RUNNER_TEMP/code-mower-base/bin/python" -m pip install "$GITHUB_WORKSPACE" | |
| "$RUNNER_TEMP/code-mower-base/bin/python" -c 'import importlib.util; assert all(importlib.util.find_spec(name) is None for name in ("mcp", "keyring", "httpx2", "jwt"))' | |
| - name: Easy-mode smoke with the base install | |
| if: matrix.python-version == '3.12' | |
| run: python scripts/smoke_easy_mode.py --code-mower-bin "$RUNNER_TEMP/code-mower-base/bin/code-mower" --work-dir "$RUNNER_TEMP/code-mower-smoke" --json | |
| - name: Fresh-clone rehearsal | |
| if: matrix.python-version == '3.12' | |
| run: >- | |
| python scripts/fresh_clone_rehearsal.py | |
| --repo-url "$GITHUB_WORKSPACE" | |
| --ref "$GITHUB_SHA" | |
| --work-dir "$RUNNER_TEMP/code-mower-fresh-clone" | |
| --json | |
| - name: Package-install first-user rehearsal | |
| if: matrix.python-version == '3.12' | |
| run: >- | |
| python -m code_mower.migration package-install-rehearsal | |
| --package-spec "$GITHUB_WORKSPACE" | |
| --work-dir "$RUNNER_TEMP/code-mower-package-install" | |
| --python "$(command -v python)" | |
| --timeout 240 | |
| --json | |
| release_rehearsal: | |
| name: release wheel rehearsal | |
| runs-on: ubuntu-latest | |
| env: | |
| SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| steps: | |
| - name: Check out the exact rehearsal source | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| ref: ${{ env.SOURCE_SHA }} | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 | |
| with: | |
| python-version: "3.12" | |
| - name: Build a pre-merge rehearsal pair | |
| # No --release-pr: these bytes are explicitly ineligible for publication. | |
| run: | | |
| python -m pip install build twine PyYAML packaging | |
| python scripts/release_candidate.py build --source "$GITHUB_WORKSPACE" \ | |
| --source-sha "$SOURCE_SHA" --dist "$RUNNER_TEMP/rehearsal-dist" | |
| - name: Rehearse the exact installed wheel offline | |
| run: | | |
| python scripts/rehearse_release.py --dist "$RUNNER_TEMP/rehearsal-dist" \ | |
| --source-sha "$SOURCE_SHA" --work-dir "$RUNNER_TEMP/rehearsal" | |
| - name: Upload sanitized pre-merge evidence | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: release-rehearsal-evidence | |
| path: | | |
| ${{ runner.temp }}/rehearsal-dist/candidate.json | |
| ${{ runner.temp }}/rehearsal/rehearsal.json | |
| if-no-files-found: error | |
| graph_containment: | |
| # The local-graph boundary, against the real kernel mechanism rather than a | |
| # stand-in for one. The unit suite skips these when a host offers no | |
| # mechanism, which is right for a laptop and useless as coverage: this job | |
| # installs bubblewrap and sets CODE_MOWER_REQUIRE_CONTAINMENT, which turns | |
| # that skip into a failure. | |
| name: graph containment | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 | |
| with: | |
| python-version: "3.12" | |
| - name: Install bubblewrap | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y bubblewrap | |
| test -x /usr/bin/bwrap | |
| # Ubuntu 24.04 restricts unprivileged user namespaces by default, which | |
| # is what bubblewrap needs to build a mount namespace without being | |
| # setuid. Enabling it is a property of this runner, not of the product: | |
| # a host that refuses keeps refusing builds, which is the fail-closed | |
| # posture. Reported rather than asserted, so the suite below is what | |
| # decides the job. | |
| - name: Allow unprivileged user namespaces | |
| run: | | |
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true | |
| sudo sysctl -w kernel.unprivileged_userns_clone=1 || true | |
| bwrap --unshare-net --dev-bind / / /bin/true \ | |
| && echo "bwrap: namespaces available" \ | |
| || echo "bwrap: refused a namespace on this runner" | |
| - name: Install package | |
| run: python -m pip install -e . | |
| - name: Real containment tests | |
| env: | |
| CODE_MOWER_REQUIRE_CONTAINMENT: "1" | |
| run: python -m unittest discover -s tests -p test_context_graph_lifecycle.py -v | |
| graph_containment_macos: | |
| # The Seatbelt half of the same claim. The bubblewrap job above proves the | |
| # Linux boundary and nothing whatever about this one, and macOS is the | |
| # platform this tool is developed on: leaving the profile to be exercised | |
| # only by whoever happens to run the suite on a laptop is how it stayed | |
| # unexecuted. ``sandbox-exec`` ships with the OS, so there is nothing to | |
| # install -- the job is the evidence that the profile runs at all. | |
| # | |
| # Required, like the bubblewrap job. macOS containment is a behaviour claim | |
| # in ``docs/context-graph-lifecycle.md``, and a claim whose only check is | |
| # allowed to be red is not being checked. A red result still carries the | |
| # probe's own diagnosis of which candidate failed and what the launcher | |
| # said, which is what turned the equivalent bubblewrap failure into a | |
| # one-round fix. | |
| name: graph containment (macOS) | |
| runs-on: macos-latest | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 | |
| with: | |
| python-version: "3.12" | |
| - name: Confirm the system sandbox is present | |
| run: test -x /usr/bin/sandbox-exec | |
| - name: Install package | |
| run: python -m pip install -e . | |
| - name: Real containment tests | |
| env: | |
| CODE_MOWER_REQUIRE_CONTAINMENT: "1" | |
| run: python -m unittest discover -s tests -p test_context_graph_lifecycle.py -v | |
| board_qualification: | |
| name: Board qualification | |
| runs-on: ubuntu-latest | |
| env: | |
| QUALIFICATION_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 | |
| with: | |
| python-version: "3.12" | |
| - name: Install package and browser test tool | |
| run: | | |
| python -m pip install -e . | |
| npm install --prefix "$RUNNER_TEMP/board-browser" --no-audit --no-fund playwright@1.63.0 | |
| node "$RUNNER_TEMP/board-browser/node_modules/playwright/cli.js" install --with-deps chromium | |
| - name: Local contract scorecard | |
| run: >- | |
| python scripts/qualify_board.py --head-sha "$QUALIFICATION_HEAD_SHA" | |
| --output "$RUNNER_TEMP/board-qualification/scorecard.json" | |
| - name: Desktop and phone Board fixtures | |
| if: always() | |
| run: | | |
| python tests/board_qualification_fixtures.py "$RUNNER_TEMP/board-qualification" | |
| NODE_PATH="$RUNNER_TEMP/board-browser/node_modules" node tests/board_qualification_browser.cjs "$RUNNER_TEMP/board-qualification" | |
| - name: Upload allowlisted qualification metadata | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: board-qualification | |
| path: | | |
| ${{ runner.temp }}/board-qualification/scorecard.json | |
| ${{ runner.temp }}/board-qualification/browser-scorecard.json | |
| ${{ runner.temp }}/board-qualification/board-1440.png | |
| ${{ runner.temp }}/board-qualification/board-390.png | |
| package: | |
| name: package | |
| runs-on: ubuntu-latest | |
| needs: [package_matrix, board_qualification, release_rehearsal] | |
| if: always() | |
| steps: | |
| - name: Check matrix result | |
| run: | | |
| test "${{ needs.package_matrix.result }}" = "success" | |
| test "${{ needs.board_qualification.result }}" = "success" | |
| test "${{ needs.release_rehearsal.result }}" = "success" |