Files
AutopilotMonitor/.github/workflows/ci.yml
T
Oliver Kieselbach c26542776e CI: credential-free build jobs, trust jobs that never build
Every job that runs third-party code (restore, build, tests, npm ci, next build,
wix) now holds only a read-only token and hands its output on as one archive.
Signing, publishing and deploying happen in separate jobs on a fresh runner that
run only this repository's scripts and pinned actions, unpack artifacts outside
the checkout and take no build-job output into a script.

- build-agent: reserve -> build -> release
- build-msi: sign-loader -> build -> release
- deploy-backend: reserve -> build -> deploy (publish output as one tar.gz)
- deploy-web: build -> deploy (SWA token only, no id-token) -> record
- combine-rules: validate (read-only, PRs too) -> publish (push to main only)
- deploy-mcp: same shape; documents docker build as its boundary

Deploy-only jobs run in the production environment. The jobs that sign stay
without an environment for now: a job carries one OIDC subject, and the signing
identity still trusts the ref. oidc-login-test.yml is removed; combine-rules no
longer triggers on the dev branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:21:04 +02:00

294 lines
11 KiB
YAML

name: CI
# Tests, typechecks and drift-checks every push to main and every pull request:
# the .NET suites, the web and MCP suites, a guard that the generated rule
# files still match rules/guardrails.json, and the duplicate-code check.
#
# The deploy workflows build but never test, so this is the only place the suites
# actually run. It does not gate anything — main is deliberately unprotected, so a
# red run reports rather than blocks. Read the result and follow up.
#
# Path filters: commits that only bump a private submodule gitlink (internal/, infra/,
# .claude/commands), touch prose or edit another workflow cannot change anything the
# suites cover, so they are skipped instead of burning ~15 runner-minutes each. A gitlink bump reports the bare
# path ('internal', no trailing slash), which 'internal/**' does NOT match — both forms
# are listed. GitHub skips only when EVERY changed path matches; a commit that touches
# docs AND code still runs.
on:
push:
branches: [main]
paths-ignore:
- 'internal'
- 'internal/**'
- 'infra'
- 'infra/**'
- '.claude/**'
- '.gitmodules'
- 'zizmor.yml'
- '**/*.md'
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.github/FUNDING.yml'
- '.github/dependabot.yml'
- '.github/assets/**'
- '.github/codeql/**'
- '.github/scripts/**'
# Every other workflow: nothing here builds or tests anything CI covers. paths-ignore
# cannot negate, so they are listed one by one; ci.yml itself stays out of the list
# so a change to CI runs CI. A new workflow file is opt-in — add it here too.
- '.github/workflows/action-pins.yml'
- '.github/workflows/bootstrap-script-gates.yml'
- '.github/workflows/build-agent.yml'
- '.github/workflows/build-msi.yml'
- '.github/workflows/codeql.yml'
- '.github/workflows/combine-rules.yml'
- '.github/workflows/deploy-backend.yml'
- '.github/workflows/deploy-mcp.yml'
- '.github/workflows/deploy-web.yml'
- '.github/workflows/indexnow-notify.yml'
- '.github/workflows/pester.yml'
- '.github/workflows/publish-scripts.yml'
pull_request:
branches: [main]
paths-ignore:
- 'internal'
- 'internal/**'
- 'infra'
- 'infra/**'
- '.claude/**'
- '.gitmodules'
- 'zizmor.yml'
- '**/*.md'
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.github/FUNDING.yml'
- '.github/dependabot.yml'
- '.github/assets/**'
- '.github/codeql/**'
- '.github/scripts/**'
# Every other workflow: nothing here builds or tests anything CI covers. paths-ignore
# cannot negate, so they are listed one by one; ci.yml itself stays out of the list
# so a change to CI runs CI. A new workflow file is opt-in — add it here too.
- '.github/workflows/action-pins.yml'
- '.github/workflows/bootstrap-script-gates.yml'
- '.github/workflows/build-agent.yml'
- '.github/workflows/build-msi.yml'
- '.github/workflows/codeql.yml'
- '.github/workflows/combine-rules.yml'
- '.github/workflows/deploy-backend.yml'
- '.github/workflows/deploy-mcp.yml'
- '.github/workflows/deploy-web.yml'
- '.github/workflows/indexnow-notify.yml'
- '.github/workflows/pester.yml'
- '.github/workflows/publish-scripts.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
dotnet:
name: .NET tests (backend + agent)
# windows-latest is required: the agent test projects target net48.
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: '10.0.x'
- name: Restore
run: dotnet restore AutopilotMonitor.sln
- name: Build
run: dotnet build AutopilotMonitor.sln --configuration Release --no-restore
- name: Backend tests
run: dotnet test src/Backend/AutopilotMonitor.Functions.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal"
- name: Agent V2 Core tests
run: dotnet test src/Agent/AutopilotMonitor.Agent.V2.Core.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal"
- name: Decision Core tests
run: dotnet test src/Agent/AutopilotMonitor.DecisionCore.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal"
- name: Summary Dialog tests
run: dotnet test src/Agent/AutopilotMonitor.SummaryDialog.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal"
- name: Push tests
run: dotnet test src/Push/AutopilotMonitor.Push.Tests --configuration Release --no-build --verbosity quiet --logger "console;verbosity=minimal"
web:
name: Web (typecheck + tests)
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/Web/autopilot-monitor-web
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
cache-dependency-path: src/Web/autopilot-monitor-web/package-lock.json
- run: npm ci
# next-env.d.ts + .next type stubs are gitignored (Next-managed since
# v16); typegen generates them so tsc sees the same types as a build.
- name: Generate Next.js types
run: npx next typegen
- name: Typecheck
run: npx tsc --noEmit
# Errors fail outright, and so does ANY warning: the codebase is
# warning-clean and must stay that way — fix new findings, never
# raise the cap.
- name: Lint
run: npm run lint -- --max-warnings 0
# Unused files, exports and dependencies (knip.json). Its own script on
# purpose: chained into `lint`, the `-- --max-warnings 0` above would be
# handed to knip and ESLint would lose its cap.
- name: Unused code
run: npm run knip
- name: Tests
run: npm test
mcp:
name: MCP server tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/McpServer/autopilot-monitor-mcp
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
cache-dependency-path: src/McpServer/autopilot-monitor-mcp/package-lock.json
- run: npm ci
# tsconfig.json covers sources, tests and scripts; the build emits via
# tsconfig.build.json.
- name: Typecheck
run: npx tsc --noEmit
# Same rule as the web job: any warning fails — fix new findings, never
# raise the cap.
- name: Lint
run: npm run lint -- --max-warnings 0
- name: Unused code
run: npm run knip
# The vector-search tests tokenize with the real all-MiniLM-L6-v2 model
# (~23 MB tokenizer + ONNX weights). transformers.js caches under
# node_modules by default, which npm ci recreates — so every CI run
# re-downloaded from huggingface.co and rate-limits (429) failed the job
# (run 31252869254). Persist the model across runs instead; HF_CACHE_DIR
# is honored by vector-search-provider.ts.
- name: Cache embedding model
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.hf-cache
key: hf-model-Xenova-all-MiniLM-L6-v2
- name: Tests
run: npm test
env:
HF_CACHE_DIR: ${{ github.workspace }}/.hf-cache
guardrails-in-sync:
name: Generated guardrails match rules/guardrails.json
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
- name: Regenerate
run: node rules/scripts/combine.js
- name: Fail if the generated files drifted
run: |
if ! git diff --quiet; then
echo "::error::Generated files are out of date. Run 'node rules/scripts/combine.js' and commit the result."
git diff --stat
exit 1
fi
shared-manifests-in-sync:
name: Generated wire types match shared-manifests.json
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
# JSON ↔ C# parity is the dotnet job's SharedManifestParityTests; this job pins
# JSON ↔ generated TS (shared-manifests.generated.ts, wire-types.generated.ts and the
# MCP's wire-types + wire-vocabularies copies - the vocabularies are what the MCP
# tool input schemas derive their z.enum() from).
- name: Regenerate
run: node scripts/generate-shared-manifest-types.js
working-directory: src/Web/autopilot-monitor-web
- name: Fail if the generated files drifted
run: |
if ! git diff --quiet -- src/Web/autopilot-monitor-web/lib/generated/shared-manifests.generated.ts src/Web/autopilot-monitor-web/lib/generated/wire-types.generated.ts src/McpServer/autopilot-monitor-mcp/src/generated/wire-types.generated.ts src/McpServer/autopilot-monitor-mcp/src/generated/wire-vocabularies.generated.ts; then
echo "::error::Generated wire types are out of date. Run 'npm run generate:manifests' in src/Web/autopilot-monitor-web and commit the result."
git diff --stat
exit 1
fi
duplicates:
name: Duplicate code
runs-on: ubuntu-latest
defaults:
run:
working-directory: tools/quality
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
cache: npm
cache-dependency-path: tools/quality/package-lock.json
- run: npm ci --ignore-scripts
# jscpd over the C# and TypeScript sources (.jscpd.json). A clone missing from
# jscpd-baseline.json fails, and so does a baseline entry whose clone is gone,
# so the baseline only shrinks. Locally: npm run dup in tools/quality.
- name: Duplicate check
run: npm run dup