CI pipeline overview
A deeply-technical-ELI5 map of what happens every time you push — what runs,
why each gate exists, and what "green" actually proves. The source of truth is
.github/workflows/ci.yml; this doc explains it.
This page describes the contract of a green pipeline. It is not itself a claim that today's branch is green. For buyer-facing or release-facing evidence, use the actual workflow artifacts for the exact commit; after the July 1, 2026 audit harness run, coverage, integration, isolation, and e2e proof remain current only when their named gates have rerun green on that commit.
When it runs
ci.yml triggers on push to main and on every pull request. A
concurrency group keyed on the ref cancels any in-progress run when you push
again, so a rapid second push abandons the first (no wasted runners). The
workflow starts with only contents: read permission — it can read the repo but
not write to it, so CI can never push to main.
The repo's other three workflows do not run on a normal push:
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
push to main + every PR |
the gate described here |
release.yml |
push of a v* tag |
build/sign/publish a release — require-green-ci rejects untested/red commits and completeness-release-gate rejects any capability-ledger gap before images or binaries are built |
nightly.yml |
daily 03:17 UTC (+ manual) | the slow stuff: e2e (black-box full stack against the real compose dependencies), ingest-bench (consumer hot-path benchmarks), and scale-gate-m (the M-profile SLO regression guard, make scale-gate-m, plus an M-tier full-stack run against real Kafka + Prometheus + ClickHouse) |
security-scan.yml |
weekly, Mondays 05:17 UTC (+ manual) | scheduled dependency-CVE scans that upload evidence artifacts and gate — govulncheck fails on any reachable Go vulnerability, npm-audit enforces Critical/no-unapproved-High policy, and trivy-fs fails on Critical/High findings — so a new CVE in an unchanged repo still goes red |
The shape: fan-out, then one umbrella
A push is like sending the change through a checkpoint with 42 specialist
inspectors, each examining one thing, all in parallel. A 43rd job —
verify-all — is the supervisor: it needs: 39 of them, runs with
if: always(), writes a receipt artifact, and fails loudly listing any
non-green gate. It is the one status you actually watch: green verify-all =
the whole pipeline passed.
Three jobs sit outside the roll-up and report as their own checks: commitlint
and dco run only on pull requests (on a push to main they'd register as
"skipped" and falsely redden the umbrella), and coverage-comment is a
write-capable best-effort PR reporting job. If you turn on branch protection,
docs/ops/branch-protection.md documents the exact
required-check set.
The guiding principle, visible throughout the repo: every rule in the Non-negotiables has a matching CI gate, and the gates execute the thing rather than just inspecting it — they boot real kernels, real databases, real Kafka, and drill real failovers. Verification you ran, not verification you described.
The jobs, by stage
1. Cheap gatekeeping — "is this change even well-formed?"
- action-pins — every GitHub Action is pinned to a commit SHA, not a moving
tag, so a hijacked upstream action can't slip in; a second pass
(
scripts/check_supply_pins.sh) rejects:latestimage tags and unpinned tool installs, and requires literal digests for scalar/nested job containers, service images, and matrix image values in the workflows (supply-chain). - secret-scan — gitleaks, a scanner that pattern-matches every reachable
git commit for anything shaped like a credential (tokens, private keys,
key=valuepasswords): nothing secret-shaped can be committed and then hidden by deleting it later. The deliberately fake secrets inside redaction tests and the exact historical OIDC fixture are allowlisted in.gitleaks.toml. - commitlint — commit messages follow Conventional Commits (
feat(...),fix(ci): ...). - dco — every commit carries a
Signed-off-by(the Developer Certificate of Origin; whatgit commit -sadds). - no-devauth-in-release — proves the dev-auth bypass is physically absent
from a release binary (symbol + literal absence + a boot-time refusal of
PROBECTL_AUTH_MODE=dev), and compiles a tagged build to confirm the check isn't vacuous.
2. Static checks — compile, lint, spec; no services yet
- lint-go / lint-python — gofmt +
go vet+ golangci-lint on Go, plus the repo's guard scripts (crypto primitives only viainternal/crypto, no swallowed errors, hardened HTTP clients, no string-built SQL, unified TLS config); ruff/black on the BGP analyzer. - editions-gate — enforces the editions boundary via
make editions-gate:scripts/check_editions_imports.sh(self-tested) proves core never importsee/, then the core-only build (-tags probectl_core) compiles and passes its tests with theee/tree linked out entirely. - fips-gate — the FIPS artifact (
-tags probectl_fips, validated Go Cryptographic Module viaGOFIPS140) builds, and its power-on self-test passes with the validated module active — the build is real, not just tagged. - proto —
buf lint, thenbuf breakingagainstmain(the schemas are the agent/bus wire contract, so incompatible changes block the merge — see CONTRIBUTING.md), then regenerates and asserts the committed*.pb.gomatch the.protofiles (no drift, no codegen poisoning). - openapi-gate — every registered core
/v1and provider/provider/v1route exactly matches its OpenAPI 3.1 spec, and every operation has a named CLI path or a reasoned none-by-design exception; no undocumented route or silent provider-CLI gap ships. - completeness-gate — validates that every capability in the release
catalog has concrete engine, binary, API, CLI, UI, docs, configuration,
telemetry, real-stack, and migration evidence (or an explicit reasoned
none_by_designcell, or an explicit not-donegapon a partial-evidence row). Gaps remain outside the reported spine-coverage count. Its planted negative self-test proves a missing cell turns the same production validator red, and CI retains deterministic HTML and JSON ledgers. See the capability completeness contract. - delivery-audit-gate — exercises the signed delivery-receipt policy,
exact-source/trusted-signer promotion rules, repository-only auditor boundary,
and a planted fixture-only negative. The Docker-backed human-path run remains
the explicit
make delivery-auditworkflow; this fast CI job proves its acceptance policy cannot silently accept test-only evidence. - docs-claims gate —
scripts/check_docs_claims.sh SELFTEST && scripts/check_docs_claims.sh(alsomake docs-claims-gate) enforces the claim register: every capability claim in a governed surface is declared indocs/claims/register.jsonand bound to the code path that implements it and the gate/test that proves it (scripts/claims_register_check.py, both directions — an undeclared claim-shaped line fails, and a stale code/proof/surface binding fails), plus the migrated DOCS-S honest-claim properties. SELFTEST first proves the good fixture passes, then runs one negative fixture per DOCS-S label AND per REG-* register failure shape, so the gate cannot go vacuously green in either direction. - migration-gate — DB migrations are additive / expand-only: it rejects
destructive or blocking changes (drop column, column-type change, rename,
adding
NOT NULL) so release N's schema still works with N-1's code during a rolling upgrade.
3. Tests + coverage
- test-go — the Go unit suite across every workspace module, run with
-race(Go's race detector: it catches two goroutines touching the same memory unsynchronized — the class of bug that stays invisible until production load), plus a fuzz smoke over the untrusted-input parsers (fuzzing = feeding a parser machine-mutated random inputs to find the crashes no human would think to type). The fuzz smoke discovers every GoFuzz*target underinternal/, andmake fuzz-policyproves the scheduled nightly job is matrix-sharded so each target gets its own long budget instead of all targets sharing one timeout. This job also runs the backup-encryption/erasure gate, an agent-overhead bench smoke, and cross-compile checks (linux amd64+arm64; the endpoint agent additionally builds for macOS and Windows). - test-python — the BGP analyzer's tests, installed from a hash-locked
requirements file (which is itself checked against
pyproject.tomlfor drift), with an 85% coverage floor. - coverage — per-package statement-coverage floors on the service-free
logic/parser/probe packages (
scripts/check_coverage.sh); retainscoverage.out+ a summary (the per-function tail ofgo tool cover -func) as a downloadable receipt artifact (seedocs/frontend-coverage.md). - web — the frontend: typecheck, lint, production build, surface coverage, jsdom axe, theme-token, and no-hardcoded-token gates.
- web-rendered-a11y — a real Chromium accessibility and interaction-performance gate in the pinned Playwright container. It renders every native route under light and dark at desktop and mobile reference widths, checks browser-computed contrast, keyboard focus, focus-obscured, and target size, then records five fresh-context LCP/INP runs for every J1–J6 landing route. CI recomputes p75 and retains the route/theme/viewport and raw-sample JSON receipts.
- browser-worker — the Playwright browser-synthetic worker, run inside the official Playwright image (a real scripted login, timings + failure screenshot).
4. eBPF — building and loading real BPF objects
- ebpf-kernel-matrix — boots real LTS kernels (5.15 and 6.6) in QEMU — a
machine emulator that runs a complete virtual machine, real kernel and all,
inside the CI runner (driven by
vimto) — and actually loads + attaches the BPF programs (tracepoint + uprobes, one flush cycle). Static analysis cannot prove a BPF program loads; only a kernel's verifier can. One matrix entry raises kernel lockdown to INTEGRITY inside the VM and proves load+attach still works on a hardened kernel. The matrix is x86_64 only and runs under KVM (hardware-accelerated virtualization) on the hosted Linux runners; missing/dev/kvmis a hard CI error, not a green skip. arm64 eBPF is compile-verified, not live-verified (decision D-13): vimto does not emulate cross-arch, so the arm64 row needed a self-hosted Linux/ARM64 runner with akvmlabel. Without one the job never leftqueued, and a job that never starts means the RUN never reachescompleted— which is what release.yml polls, so every tag timed out with all other jobs green. See docs/ebpf-agent.md for what arm64 does and does not prove, and how to restore the row. - ebpf-image-live — the shipped
probectl-ebpf-agentimage must carry the live CO-RE loader (CO-RE = Compile Once – Run Everywhere: the BPF object is relocated at load time to fit the running kernel's struct layouts), built fromDockerfile.ebpfand asserting-tags=ebpf— not the fixture replayer.
5. Real-infrastructure suites — spin up actual Postgres / Kafka / ClickHouse
- cross-tenant-isolation — the
tenant-isolation non-negotiable,
executed (
make test-isolation): row-level-security posture (RLS is the Postgres feature where the database itself filters every query by tenant, so even buggy handler code cannot return another tenant's rows) + cross-tenant injection against a real Postgres (over TLS,sslmode=verify-full— the client checks the server's certificate and hostname, like production) and a real ClickHouse (including its row-policy DDL). - integration — boots the control plane against real Postgres (TLS,
verify-full) plus a remote-write Prometheus; migrations are idempotent,/readyzpasses, the result pipeline round-trips — andinternal/storemust hold its 60% integration-coverage floor. It buildsweb/and embeds the real bundle (as the release does) and installs browser-worker's pinned Chromium, because real-stack receipts render the live control plane's UI (internal/testsupport.RenderUI); the embedded placeholder or a missing browser fails the job rather than skipping a rendered leg. It also starts Dex, a real OIDC IdP, under a per-run test CA (scripts/ci_dex.sh), so the SSO receipt signs people in through a real login. - perf-smoke — a cheap, repeatable latency/throughput baseline; the first place a pooled-cardinality or RLS-cost regression would surface.
- backup-drill — backup → wipe → restore actually runs; nonce-marked rows must survive a full database drop.
- failover-drill — kills the primary, promotes the streaming standby, and times RTO (first write on the new primary) / RPO (acked rows lost).
- load-smoke — synthetic agents → real Kafka → the production consumer (retry/DLQ — a dead-letter queue, where a message that repeatedly fails processing is parked for inspection instead of retried forever or silently dropped — plus cardinality caps) → Prometheus remote-write → tenant-scoped PromQL, end to end.
6. AI quality
- rca-eval — a blocking evaluation of the AI root-cause analysis against fixtures, with quality floors (0.85 / 0.85). If RCA quality regresses, the build fails — the AI is held to a measured bar, not just "it compiled."
7. Deploy / packaging config
- helm-gate — the Helm charts lint for every reference profile and uphold
the secure-by-default invariants (non-root/read-only pod,
NetworkPolicy/PDB/HPA, HTTPS Service/probes/ingress backend, required serving
certificate Secret, HSTS, and no default credentials); the manifests are
schema-validated with
kubeconform, the GitOps (ArgoCD/Flux) manifests are checked, and the shipped compose file must passdocker compose config. - terraform-gate — the Terraform module is
fmt-clean andterraform validates via an example root that consumes it.
8. Supply chain & artifacts
- dependency-scan — vulnerability scan of the dependency graph.
- build-images — builds the multi-arch container images.
- image-scan — scans those built images for known vulnerabilities.
- sbom — emits a software bill of materials for the build.
9. The umbrella
- verify-all — depends on the gates above, writes
verify-all-summary.jsonas a receipt, and goes red listing any gate that wasn't green. This is the single signal of overall pipeline health.
What a green pipeline proves
When verify-all is green for a commit, you've demonstrated — on that exact
commit — that the code:
- compiles and passes lint in every mode (normal, FIPS, eBPF);
- passes unit, real-Postgres, real-ClickHouse, and real-Kafka tests;
- keeps tenants isolated at the storage layer (RLS, enforced and tested);
- ships container images that scan clean, with an SBOM and SHA-pinned actions;
- didn't drift its API spec, protobufs, or migrations, and keeps zero-downtime upgrade compatibility;
- holds the AI's root-cause quality above its floor;
- survives a backup/restore and a primary failover.
That is a heavier pipeline than most projects run — closer to what a regulated enterprise expects — which matches where probectl is aimed (sovereign, multi-tenant, source-available).
What the pipeline cannot self-apply
Branch-protection enforcement (making GitHub refuse to merge a red PR) is a
server-side repository setting, not something a workflow in the tree can turn
on. Until an admin configures it, every check above is advisory at the merge
button — the recommended ruleset (require verify-all plus the two additional
required checks outside its roll-up, commitlint and dco) and the one-time
console steps are in
docs/ops/branch-protection.md. The release path
has its own independent backstop either way: release.yml refuses to publish a
tag whose commit lacks a green ci run or whose strict capability ledger is
below 100% spine coverage.