Repository navigation
fix(*): name the missing credential when an agent cannot inherit #2534
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| pre-commit: | |
| name: pre-commit diff | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Set up Python | |
| run: uv python install 3.12 | |
| - name: Install dependencies | |
| run: make install-deps | |
| - name: Run pre-commit on changed files | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE="${{ github.event.pull_request.base.sha }}" | |
| else | |
| BASE="${{ github.event.before }}" | |
| fi | |
| if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ]; then | |
| BASE="$(git rev-list --max-parents=0 HEAD)" | |
| fi | |
| uv run pre-commit run --from-ref "$BASE" --to-ref HEAD | |
| lint-python: | |
| name: python lint | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Set up Python | |
| run: uv python install 3.12 | |
| - name: Install dependencies | |
| run: make install-deps | |
| - name: Python lint | |
| run: make lint-python | |
| - name: Import contracts (layer boundaries) | |
| run: make lint-imports | |
| - name: Dependency hygiene (no direct import of a transitive dep) | |
| run: make lint-deps | |
| - name: Type check (call correctness) | |
| run: make lint-types | |
| # Four runners, each collecting and running a quarter of the test files | |
| # (`--shard`, dealt round-robin in tests/conftest.py), each writing coverage | |
| # data and no report; the `coverage` job below combines the four and runs | |
| # the gates once. Measured before the split: 12 minutes on one runner, of | |
| # which two were the four xdist workers importing every test module. | |
| unit: | |
| name: unit (py${{ matrix.python }} / ${{ matrix.os }} / ${{ matrix.shard }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 20 | |
| env: | |
| PYTHON_VERSION: ${{ matrix.python }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest] | |
| python: ["3.12"] | |
| # K/N in one place: the denominator is not derived from the matrix size, | |
| # which would silently drop files if another dimension were added. | |
| shard: ["1/4", "2/4", "3/4", "4/4"] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Set up Python | |
| run: uv python install ${{ matrix.python }} | |
| # tests/test_ui_css_token_gate.py shells out to the node gate, and it is | |
| # the ONLY thing anywhere that checks what that gate rejects -- the page | |
| # job below runs the gate against the current stylesheet, which proves it | |
| # runs and that the file passes, and nothing about refusal. Installed | |
| # here so this job really runs that suite: without it the runtime skip in | |
| # that file quietly drops all 43 cases. | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| # No bytecode precompilation, of site-packages here or of the checkout | |
| # before the run: measured over three runs of this workflow, neither paid | |
| # for itself. The note stays so the next reader does not spend an | |
| # afternoon finding that out again. | |
| - name: Install dependencies | |
| run: uv sync --locked --all-extras --dev --python ${{ matrix.python }} | |
| # Stdlib only. The dedicated gate is the `page` job below; this step | |
| # keeps the build honest inside the test matrix too, because | |
| # `ui-web/build.py` matches exact literal markers in the page sources and | |
| # hard-exits on a miss, so an edit that reflows a source or drops a | |
| # marker leaves CI green and blocks the release at `git push v1.2.3`. | |
| - name: Build served page | |
| run: | | |
| npm ci --prefix ui-web | |
| npm run --prefix ui-web build | |
| python3 ui-web/build.py | |
| test -f ui-web/dist/index.html | |
| - name: Unit tests, this shard, with line and branch coverage data | |
| # TERM=dumb: typer's rich_utils force-enables colour when GITHUB_ACTIONS is | |
| # set, and the highlighter then splits option names with escape codes, so | |
| # `"--flag" in result.stdout` assertions fail only on CI. | |
| # The idle ceiling fails the shard here and only here: a test that waits | |
| # out a production sleep waits the same seconds on every machine, so the | |
| # verdict is portable; the flag stays off addopts because the Windows | |
| # job runs pytest with --noconftest, where the option does not exist. | |
| run: make coverage-shard COVERAGE_SHARD=${{ matrix.shard }} PYTEST_ARGS=--idle-ceiling-strict | |
| - name: Name the data file after the shard | |
| run: mv .coverage .coverage.shard-${{ strategy.job-index }} | |
| - name: Upload coverage data | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-data-py${{ matrix.python }}-${{ matrix.os }}-${{ strategy.job-index }} | |
| path: .coverage.shard-* | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| retention-days: 1 | |
| coverage: | |
| name: coverage gates | |
| needs: unit | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Set up Python | |
| run: uv python install 3.12 | |
| - name: Install dependencies | |
| run: make install-deps | |
| - name: Download the shards' coverage data | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: coverage-data-* | |
| path: coverage-data | |
| merge-multiple: true | |
| - name: Combine the shards into one report | |
| run: make coverage-combine | |
| - name: Coverage summary and lowest-covered files | |
| if: ${{ !cancelled() && hashFiles('coverage.json') != '' }} | |
| run: make coverage-summary | |
| - name: Overall coverage ratchet | |
| if: ${{ !cancelled() && hashFiles('coverage.json') != '' }} | |
| run: make coverage-ratchet | |
| - name: Coverage baseline cannot decrease | |
| if: ${{ !cancelled() && github.event_name == 'pull_request' }} | |
| env: | |
| COVERAGE_BASE_REF: origin/${{ github.base_ref }} | |
| run: make coverage-baseline-check | |
| - name: PR diff coverage | |
| if: ${{ !cancelled() && github.event_name == 'pull_request' && hashFiles('coverage.json') != '' }} | |
| env: | |
| COVERAGE_BASE_REF: origin/${{ github.base_ref }} | |
| run: make coverage-diff | |
| - name: Explain skipped diff coverage | |
| if: ${{ !cancelled() && github.event_name != 'pull_request' }} | |
| run: echo "Diff coverage is a PR-only gate; push builds still enforce the overall ratchet." | |
| - name: Create auditable baseline candidate | |
| if: ${{ !cancelled() && hashFiles('coverage.json') != '' }} | |
| run: make coverage-baseline-candidate | |
| - name: Upload coverage reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-coverage-py3.12-ubuntu-latest | |
| path: | | |
| coverage.xml | |
| coverage.json | |
| coverage-baseline-candidate.json | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| trajectory: | |
| name: trajectory regressions | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Set up Python | |
| run: uv python install 3.12 | |
| - name: Install dependencies | |
| run: make install-deps | |
| - name: Replay committed regression cases | |
| env: | |
| RAVEN_REGRESSION_REPORT_DIR: ${{ runner.temp }}/trajectory-reports | |
| run: uv run pytest tests/test_trajectory_regressions.py -q | |
| - name: Validate case format, redaction review, and size budget | |
| if: ${{ !cancelled() }} | |
| run: uv run raven trajectory regression validate --all | |
| - name: Upload failure reports | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: trajectory-failure-reports | |
| path: ${{ runner.temp }}/trajectory-reports/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| installer: | |
| name: installer (${{ matrix.os }}) against the latest release | |
| # The two installers are served from main and install the latest published | |
| # release, so a script that reaches for something that release does not have | |
| # breaks every one-line install the moment it merges -- which is how an | |
| # ending on `raven web` shipped against a wheel that had no `web`. This job | |
| # runs exactly that combination, piped the way users run it so remote mode | |
| # is what gets exercised, and asks the installed raven to answer. Only PRs | |
| # that touch an installer pay for it; every push to main runs it. | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 25 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Decide whether an installer changed | |
| id: changed | |
| shell: bash | |
| run: | | |
| if [ "${{ github.event_name }}" != "pull_request" ]; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| elif git diff --name-only "origin/${{ github.base_ref }}...HEAD" -- install.sh install.ps1 | grep -q .; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "run=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Node 22 up front: the script would otherwise fetch a private runtime, | |
| # which is its business to get right but not what this job measures. | |
| - uses: actions/setup-node@v6 | |
| if: steps.changed.outputs.run == 'true' | |
| with: | |
| node-version: "22" | |
| - name: Install the latest release with install.sh, piped | |
| if: steps.changed.outputs.run == 'true' && runner.os != 'Windows' | |
| env: | |
| RAVEN_MINIMAL: "1" | |
| RAVEN_NO_LAUNCH: "1" | |
| UV_TOOL_DIR: ${{ runner.temp }}/tools | |
| UV_TOOL_BIN_DIR: ${{ runner.temp }}/bin | |
| UV_CACHE_DIR: ${{ runner.temp }}/cache | |
| RAVEN_HOME: ${{ runner.temp }}/home | |
| run: | | |
| cat install.sh | sh | |
| "$UV_TOOL_BIN_DIR/raven" --version | |
| - name: Install the latest release with install.ps1, piped | |
| if: steps.changed.outputs.run == 'true' && runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| RAVEN_MINIMAL: "1" | |
| RAVEN_NO_LAUNCH: "1" | |
| UV_TOOL_DIR: ${{ runner.temp }}\tools | |
| UV_TOOL_BIN_DIR: ${{ runner.temp }}\bin | |
| UV_CACHE_DIR: ${{ runner.temp }}\cache | |
| RAVEN_HOME: ${{ runner.temp }}\home | |
| run: | | |
| Get-Content install.ps1 -Raw | Invoke-Expression | |
| & "$env:UV_TOOL_BIN_DIR\raven.exe" --version | |
| windows-upgrade: | |
| name: Windows self-upgrade | |
| runs-on: windows-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| - name: Set up Python | |
| run: uv python install 3.12 | |
| - name: Test real uv self-upgrade | |
| # pytest-asyncio is needed even though this file has no async test: the | |
| # isolated interpreter still reads the project's pytest config, whose | |
| # --strict-config turns the then-unknown asyncio_mode into a hard error. | |
| # pytest-xdist for the same reason, one step further: addopts now carries | |
| # `-n 4`, and an unknown ARGUMENT is fatal whatever --strict-config says | |
| # ("unrecognized arguments: -n"). It runs two tests here, so the workers | |
| # buy nothing -- the plugin only has to exist. | |
| run: uvx --python 3.12 --from "pytest>=8,<10" --with pytest-asyncio --with pytest-xdist pytest --noconftest tests/integration/test_cli_upgrade_real_uv.py -q | |
| core-wheel: | |
| name: kernel wheel smoke | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Set up Python | |
| run: uv python install 3.12 | |
| - name: Install dependencies | |
| run: make install-deps | |
| # The closure guard in the unit suite proves the SOURCE kernel imports | |
| # nothing else; this proves the ARTIFACT does -- built from the contract | |
| # roster, installed into a clean venv with nothing but its own pins. | |
| # norecursedirs keeps it out of the default pytest scope, so this job is | |
| # what keeps the wheel honest on every push. | |
| - name: Build and smoke the raven-core wheel | |
| run: make check-core-wheel | |
| tui: | |
| name: TUI checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: ui-tui/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| working-directory: ui-tui | |
| - name: Lint, type-check, and test | |
| run: | | |
| npm run lint | |
| npm run lint:rpc | |
| npm run lint:i18n | |
| npm run type-check | |
| npm test | |
| npm run build | |
| working-directory: ui-tui | |
| ui-rpc: | |
| name: ui rpc contract | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: ui-web/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| working-directory: ui-web | |
| # generated.ts is committed; a schema edit without regeneration is the | |
| # drift this catches. Then the transport layer compiles against it and | |
| # the fixture transport's behaviour is pinned by vitest. | |
| - name: Check contract, lint, types, and tests | |
| run: | | |
| npm run gen:check | |
| npm run lint | |
| npm run type-check | |
| npm test | |
| working-directory: ui-web | |
| page: | |
| name: page checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| # The npm build makes the island bundle build.py inlines; the gate | |
| # scripts themselves still run on bare node, straight off the checkout. | |
| # build.py validates the part seams (IIFE opener first, closer last); | |
| # check-page.mjs validates the assembled artifact (one style, two | |
| # inline scripts, no leftover markers, scripts parse). | |
| - name: Build and check the served page | |
| run: | | |
| npm ci --prefix ui-web | |
| npm run --prefix ui-web build | |
| python3 ui-web/build.py | |
| node ui-web/scripts/check-page.mjs | |
| node ui-web/scripts/check-css.mjs | |
| node ui-web/scripts/check-class-namespace.mjs | |
| bridge: | |
| name: bridge checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: bridge/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| working-directory: bridge | |
| - name: Build bridge | |
| run: npm run build | |
| working-directory: bridge | |
| - name: Audit bridge dependencies | |
| run: npm audit --audit-level=critical | |
| working-directory: bridge |