Skip to content

Assay-Runner CI Lane Contract

Internal Phase 2A reference. This page classifies when ordinary continuous CI is enough and when the delegated Linux/eBPF runner lane is required.

Assay-Runner has two proof classes:

  • Continuous CI: hosted or ordinary repository checks that must stay fast, broadly available, and safe for pull requests.
  • Delegated CI: the manual Runner Spike Delegated workflow on [self-hosted, linux, assay-bpf-runner], used when a change can only be proven on the dedicated Linux/eBPF host.

The delegated lane is intentionally workflow_dispatch only. Do not add a pull-request, push, or schedule trigger to make the lane automatic. The host is dedicated and destructive cleanup is part of the contract.

Decision Table

Change type Continuous CI Delegated CI Required before merge?
Docs-only change outside runner acceptance, runbook, artifact contracts, or CI lane docs yes no no
Docs-only change to runner acceptance, runbook, artifact contracts, fixture contract, or CI lane docs yes no no, unless it changes acceptance criteria
Non-runner crate change with no monitor, policy, SDK fixture, cgroup, workflow, or evidence artifact impact yes no no
Runner artifact schema, field set, note format, or artifact determinism semantics yes yes yes
Runner telemetry-versus-evidence filter behavior yes yes yes
assay-monitor ring-buffer reader, BPF event decode, drop accounting, or capture stats yes yes yes
crates/assay-monitor/** or crates/assay-ebpf/** yes yes yes
eBPF build image, build command, or loader/attach path yes yes yes
Runner cgroup placement, domain-root resolution, or process spawn discipline yes yes yes
Policy correlation, policy event normalization, or policy-to-kernel coherence rule yes yes yes
OpenAI Agents fixture dependency, SDK event normalization, SDK version assertion, or tool_call_id binding yes yes yes
Gemini Python google-genai fixture dependency, SDK event normalization, SDK version assertion, cassette content, or tool_call_id binding yes yes yes
Acceptance fixture behavior or control paths yes yes case-by-case; required when observed evidence can change
Delegated workflow, runner labels, cleanup, checkout, sudo environment, preflight, or scripts/ci/runner-spike-*.sh yes yes yes
@openai/agents, zod, or fixture package-lock.json bump yes yes yes
aya, aya-ebpf, aya-log-ebpf, or BPF/runtime dependency bump yes yes yes
Workspace dependency bump that can affect assay-runner-spike, assay-monitor, assay-ebpf, assay-cli, policy correlation, or runner fixtures yes yes yes
Delegated runbook wording that only clarifies existing behavior yes no no
Follow-up issue text, planning notes, or extraction-roadmap prose yes no no

Required Delegated Gate

When delegated CI is required, choose the narrowest gate that proves the changed layer:

Touched surface Minimum delegated gate
Kernel fixture control path or kernel-only acceptance assertion kernel-only
Policy capture or policy-to-kernel coherence kernel-policy
OpenAI Agents SDK fixture, SDK event schema, SDK version assertion, or tool_call_id binding openai-agents-kernel-policy
Gemini Python google-genai fixture, SDK event schema, SDK version assertion, cassette content, or tool_call_id binding all (no narrower Gemini-specific gate exists in v0; a narrower gate is a separate coordinated decision per #1307)
Delegated hidden-write semantic-gap expansion, proof-pack collector changes, crates/assay-monitor/**, crates/assay-ebpf/**, crates/assay-xtask/**, eBPF build/attach path, cgroup placement, telemetry filter, cross-layer archive, artifact schema, correlation report, workflow/security model, runner scripts, BPF/runtime dependency bump, or final release/acceptance proof all

If a change touches multiple surfaces, run the highest required gate. If the right gate is ambiguous, default to all.

Use build_ebpf=true for delegated proof unless the workflow has been explicitly extended to restore a deterministic prebuilt eBPF object after cleanup and checkout.

Merge Discipline

  1. Run ordinary continuous CI for every PR.
  2. For runner-impacting changes, dispatch the minimum delegated gate after the PR branch contains the final candidate commit.
  3. Record the workflow run URL, commit SHA, selected gate, and result in the PR body or a PR comment.
  4. Do not treat a delegated skip as success. In the delegated lane, exit 40 means the runner contract has drifted.
  5. Do not bypass required repository checks for runner-impacting changes.

If a runner-impacting PR cannot get a delegated run because the host is unavailable, leave the PR open. Merge only docs-only or clearly non-runner-impacting changes without delegated proof.

The Assay-Runner Lane Check / lane-check workflow provides the machine-visible path classification and delegated proof check for this contract. To make the contract hard-blocking, repository branch protection must mark that check as required.

The executable path mapping lives in scripts/ci/assay_runner_lane_check.py and must mirror the decision table on this page. Changes to this contract and changes to the classifier must land in the same PR; the helper's --self-test is the drift canary for the known runner-impacting surfaces.

Dependabot PRs follow the same proof rules, but need maintainer action because Dependabot cannot dispatch the delegated lane or record proof itself. See the Runner Dependabot lane flow for the required maintainer steps. Do not auto-dispatch the self-hosted delegated workflow from Dependabot PRs without a separate security design review.

Ring-buffer drop diagnostics remain a separate follow-up tracked in https://github.com/Rul1an/assay/issues/1271; that issue must not weaken the ringbuf_drops=0 delegated acceptance rule.

Boundary Rule

The delegated lane proves the Linux/eBPF runner boundary, not macOS, Windows, live LLM calls, production load, or a distributed runner fleet. Platform expansion requires a separate platform spike with its own kill criteria and CI lane contract.