Skip to content

fix(*): name the missing credential when an agent cannot inherit #2534

fix(*): name the missing credential when an agent cannot inherit

fix(*): name the missing credential when an agent cannot inherit #2534

Workflow file for this run

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