Runbook — Live-stack CI lane operations (analyzer-coverage, airgap-federation-e2e)

Applies to: the heavy CI lanes that stand up the full ~40-service stack from a bare checkout (.gitea/workflows/e2e-analyzer-coverage.yml, airgap-federation-e2e.yml; runs-on: apparmor-host). These reproduce the whole deploy from clean, so they surface drift no other lane catches. Companion: docs/runbooks/ci-registry-pull-through-cache.md, docs/agent-playbooks/live-deploy-operator.md, docs/agent-playbooks/orchestrator.md. Verify against src/ and the workflow YAML — code wins.

Meta-lesson first (it cost ~3 CI rounds): when a CI run’s behavior contradicts what the file shows, run git blame / git diff HEAD before theorizing about YAML/compose internals. Uncommitted working-tree edits to a concurrent file make the runner’s HEAD differ from what you’re reading.

What these lanes need from a bare checkout (gitignored dev assets)

A fresh runner has none of the operator’s local secrets/certs/env. Provision in this order — each gap only surfaces after the previous is fixed:

  1. .env(devops/compose/.env, gitignored) — image pins + ~15 generated secrets + the postgres password embedded literally in the STELLAOPS_POSTGRES_* connection strings. Materialize with devops/compose/scripts/init-local-env.sh --force (renders env/stellaops.env.local-dev.example). Do NOT pin POSTGRES_PASSWORD in workflow env — it desyncs from the connection-string literal. Symptom: invalid empty volume spec / STELLAOPS_POSTGRES_CONNECTION not set.
  2. Dev TLS pfxdevops/etc/authority/keys/kestrel-dev.pfx is gitignored (only the .crt is committed). Generate with devops/compose/scripts/ensure-dev-certs.sh (self-signed; SAN stella-ops.local,*.stella-ops.local,127.1.0.1,127.0.0.1). Symptom: Authority FileNotFoundException: …/kestrel-dev.pfx.
  3. Cert perms — generated files are root-owned 0600; the Authority container runs non-root (UID 10001) over a read-only mount → UnauthorizedAccessException. chmod 0644 the key/crt/pfx.
  4. Scanner surface — append devops/compose/docker-compose.scanner-surface-fix.override.yml (named volume + chown init): scanner surface validation rejects the dev HTTP S3 endpoint, and uid 10001 can’t write the surface tmpfs at mode:1777 on apparmor-host.
  5. Vault — registering an SCM integration stores its credential via Vault; wire VAULT_ADDR/VAULT_TOKEN (dev root token) in the e2e override or integrations-web 500s.

The recurring failure classes (durable — these recur across lanes and real deploys)

Concurrency

Base-stack lanes use fixed container names (stellaops-valkey, …), so analyzer-coverage and airgap-federation-e2e cannot run concurrently (container name already in use). Keep their crons de-staggered or project-scope the names; add a CI concurrency group.

Diagnostics that pay off

Artifacts caveat

The Gitea runner reports as GHES: actions/upload-artifact@v4 hard-fails the job at upload even when tests passed. Use @v3 with continue-on-error so artifact plumbing never sets the verdict.

Verify (live forcing-function)

A lane is green only when it reaches and passes its real harness (analyzer corpus / the full federation export→download→verify→import→notifier→Mailpit flow), not when the stack merely boots.