CI
Continuous integration on RunYard runners — GitHub stays the canonical repo host; RunYard verifies every change with SHA-pinned, trust-aware pipelines.
RunYard CI answers one question for every candidate change: "would this exact change, integrated with the current target branch, satisfy the repository's declared invariants?" GitHub remains the canonical repository, pull-request, tag, and release surface. RunYard is the external CI control plane: it receives verified GitHub events, compiles a trusted .runyard/ci.yml into pipelines, executes jobs on your own runners — consuming no GitHub Actions minutes — and reports the results back as GitHub Checks.
The loop:
GitHub event → signed + deduplicated delivery → pipeline (a canonical run)
→ DAG of job runs on RunYard runners → logs/artifacts/evidence
→ GitHub Check updates → merge decision back on GitHubOwnership boundary
| GitHub owns | RunYard owns |
|---|---|
| Repositories, branches, PRs, tags, releases, packages | Pipeline compilation and job execution |
| Merge button and branch protection decisions | The evidence behind the checks (runs, logs, artifacts) |
| Check display on commits/PRs | Check content: status, conclusion, annotations, deep links |
| Webhook delivery + redelivery | Delivery verification, dedupe, and the receipt ledger |
RunYard deliberately does not host Git, clone the full GitHub Actions syntax, or run a second scheduler. Pipelines and jobs are ordinary RunYard runs — the same lifecycle, events, artifacts, cancellation, and stall-reaping as everything else.
Pipelines and jobs are runs
An accepted trigger creates one pipeline — a parent run that holds provenance and the job roll-up — plus one job row per configured job. When a job's needs are satisfied it is dispatched as a child run with the internal ci-job workflow, claimed only by runners advertising the ci tag. Live status is never duplicated: the pipeline record stores immutable provenance (trigger, config SHA, tested semantics, check ids) while the canonical run rows own the live state. Cancel the parent run and every dispatched job's process group dies on its runner; a newer push on the same concurrency key cancel-supersedes the older pipeline.
Exact-source semantics
CI is SHA-pinned. Provenance distinguishes four things: the provider-reported head SHA, the base SHA (target branch at receipt time), the tested checkout, and the check target (always the head SHA). For pull requests the runner constructs the merge candidate deterministically — fetch the pinned base and head, merge head into base — so every job of a pipeline tests the identical tree, and CI honestly tests the integration result rather than the raw branch head. A conflicted merge is a first-class blocked conclusion (action_required on the check), never a silent fallback to testing the head.
Trust model and fork security
Repositories connect disabled and untrusted by default; an admin enables CI and sets the trust policy per repository.
- Config comes from the trusted base. For pull requests,
.runyard/ci.ymlis loaded from the base branch head — never the PR head — and pinned by SHA into the pipeline's provenance. A fork cannot change what CI runs by editing the config in its own branch. - Fork PRs are always untrusted, regardless of repository trust: they receive no repository secrets and cannot request native host execution. Their native jobs are policy-skipped with an explicit
action_requiredcheck. - Native execution is doubly gated: the repo's trust policy must allow it and the runner must opt in with
RUNYARD_RUNNER_CI_NATIVE=1. Without both, jobs run only through the Dagger executor or are blocked. - Installation tokens are minted just in time, scoped to one repository and the minimum permissions, cached only in memory, and never stored in the database, logs, events, or artifacts. The runner's git credential is minted per job over the authenticated runner protocol and expires within the hour.
RunYard dogfood invariant
RunYard's own .runyard/ci.yml is the stable-production-tests-candidate baseline. It must keep the trusted-base/fork/no-secrets/native-double-gate boundaries above while exercising production-shaped CI: PRs to main, pushes to main, v* tags, and manual dispatch; independent deterministic jobs for quality, tests, build/docs drift, sandbox smoke, and security/artifact hygiene; and no LLM-controlled pass/fail or automatic repair. During shadow rollout, GitHub Actions release.yml and images.yml remain the permanent lifeboat and are not replaced by RunYard CI.
Failure vocabulary
Job outcomes reuse the run failure classes, and the GitHub check mapping keeps code failure distinguishable from infrastructure failure:
| Run outcome | Check conclusion | Meaning |
|---|---|---|
succeeded | success | Commands exited 0 |
failed | failure | The code/tests failed (annotations extracted from file:line output) |
timed_out | timed_out | Exceeded the job's timeoutMinutes |
cancelled | cancelled | Operator cancel or cancel-superseded |
infra_unavailable | action_required | CI infrastructure problem (e.g. Dagger missing) — not a code failure |
blocked_by_preflight | action_required | Merge conflict, trust denial, or invalid input |
| skipped (dependency) | skipped | An upstream needs job did not succeed |
| skipped (policy) | action_required | Denied by trust policy (e.g. native on a fork) |
An invalid .runyard/ci.yml still reaches the PR as a failed runyard/ci check carrying the validation errors — configuration mistakes are visible where the author is looking, not swallowed by the hub.
See the CI setup guide for GitHub App registration, .runyard/ci.yml reference, runner setup, and troubleshooting.