| name | manage-ci |
| description | Use this skill as the mandatory starting point whenever inspecting, running, debugging, defining, editing, reviewing, or documenting MeshLLM CI/CD. It governs GitHub Actions workflows and local actions, triggers and routing, runners, caches, artifacts, permissions, releases, deployments, and CI infrastructure. |
Manage CI
This is the normative CI rule source for MeshLLM. Read it completely before
every CI task.
Read these companion documents before changing CI:
references/current-inventory.md for the checked-in workflow, runner,
variable, secret-name, and environment inventory.
ci/ci.md for the current topology and artifact flow.
.omo/specs/pr-ci-optimization.md when changing PR/main composition,
routing, fan-out, or runner-provider policy.
The specification records design, status and acceptance criteria. When
implementation and documentation disagree, inspect the implementation, fix the
owning source, and update the inventory and topology in the same change.
Required procedure
- Inspect
git status, applicable AGENTS.md files, the complete workflows,
reusable workflows, local actions, scripts, and manifests in scope.
- Classify the change as entrypoint, planner/routing, reusable slice, runner
policy, cache, artifact producer/consumer, smoke, main, release, deployment,
maintenance, or infrastructure.
- Identify the event and trust context, every producer and consumer, required
permissions and secrets, runner class, cache authority, required check, and
cancellation behavior.
- Inspect live GitHub configuration only when the task depends on it. Treat
repository variables as strings and report inaccessible organization state
as unverified, never absent.
- Make the smallest coherent change. Update this skill first if a normative
rule changes; update the inventory and
ci/ci.md when facts or topology
change.
- Validate the changed contracts and report repository changes separately
from external GitHub, Depot, runner, secret, or deployment state changes.
CI architecture contract
Entrypoints and reusable slices
- Event entrypoints own triggers, concurrency, and top-level permissions.
Pull requests use separate Quality, Website, Linux, macOS, and Windows
workflows. Each entry computes the canonical plan, then calls only its
matching reusable lane so jobs remain attached to a small topic/platform
run. PR callers use the protected default-branch lane definition; main
callers use the same-commit local lane definition. Explicit manual-full
diagnostics alone may use the protected default-branch dispatch controller.
No path owns duplicated build implementations.
- New PR and main behavior must be implemented in a typed reusable workflow or
local action consumed by both entrypoints. Do not copy a job between PR and
main and do not add a third implementation to release.
- A PR-selected slice must run the same commands, build profile, artifact
contract, and verification as that row on main. PRs reduce work by selecting
fewer main-representative slices, not by changing what a selected slice
means.
- Reusable workflow inputs must be typed, bounded, documented, and validated.
Accept semantic inputs such as platform, architecture, backend, profile, or
smoke kind. Never accept arbitrary shell, arbitrary workflow path, raw
runs-on JSON, a secret name, or an independent cache-permission flag.
- Pass secrets by name only to the workflow and step that consumes them. Never
use
secrets: inherit in CI slice calls.
- Keep reusable-workflow nesting shallow and the slice catalog small. Separate
topic/platform graphs may be dispatched with native, bounded inputs from the
protected controller. Each lane owns a platform-local static superset of
typed calls gated by the checked plan; a platform lane must not call a
cross-platform reusable workflow with empty placeholder matrices. Do not
generate workflow YAML at run time.
- Prefer local composite actions for repeated steps inside a job and reusable
workflows for repeated jobs or job graphs.
PR workflow visibility and split invariant
- PR validation has exactly five event entrypoints:
pr_quality.yml, pr_website.yml, pr_linux.yml, pr_macos.yml, and
pr_windows.yml. Keep this topic/platform split unless a maintainer
explicitly changes the architecture contract.
- Each PR entrypoint may call only its matching protected default-branch lane.
It owns an independent concurrency group and stable
PR / <lane> result.
Compose additional work inside that lane's typed reusable graph; do not add
cross-lane jobs to an entrypoint.
- Native PR visibility is an operability requirement. Reviewers must be able
to open the PR checks view, select Quality, Website, Linux, macOS, or Windows,
and drill directly into that lane's jobs and logs. A custom
dispatched
placeholder or a separate workflow-dispatch run does not satisfy this
requirement for PRs.
- Never introduce a monolithic PR workflow or reusable PR composer that calls
all five lanes in one run. Do not replace the five entrypoints with one
matrix, one giant dependency graph, or one aggregate workflow whose only
visible PR result is a dispatch/controller job.
- A reusable-only, no-op filename shim is temporarily allowed when the
protected pre-migration runner contract requires a deleted entrypoint to be
present while the migration PR is open. It must have no event trigger, call
no lane or producer, be documented as removable after merge, and must never
regain the behavior implied by its legacy filename.
- Do not add
paths filters to the five PR entrypoints. All five stable results
must be created for every relevant PR synchronization; the checked planner
makes an unselected lane skip its expensive work and lets its stable result
succeed. This prevents required checks from remaining absent or pending.
Main workflow visibility and split invariant
- Routine main validation has exactly five push entrypoints:
main_quality.yml, main_website.yml, main_linux.yml, main_macos.yml,
and main_windows.yml. Each calls only its matching same-commit reusable
lane and owns one stable Main / <lane> result.
- Native main visibility is an operability requirement. A maintainer must be
able to open a main push run for one topic/platform and drill directly into
its jobs and logs. Do not route routine main pushes through a dispatch-only
controller, synthetic check graph, monolithic matrix, or all-lanes composer.
- The five main workflows must not use path filters or cancel one another or
older main revisions. Main is exhaustive evidence; supersession cancellation
remains PR-only. Planner-owned skips and matrix fail-fast behavior must not
make the top-level main result disappear.
ci-control.yml is reserved for an explicit default-branch manual-full run.
It may dispatch the five lanes with correlated synthetic checks because the
operator deliberately chose the detached diagnostic surface. It must not be
triggered by push, pull_request, or workflow_run.
Planning and routing
- Compute changed files and the CI plan once per entry graph. Each independent
PR topic/platform workflow projects only its own lane and calls that protected
default-branch lane natively, so every nested job and log stays visible from
the matching PR run without a monolithic matrix. Each main topic/platform
workflow computes the same exhaustive plan and calls its same-commit lane
natively. Only manual-full passes digest-bound lane projections through
native workflow-dispatch inputs. Do not use an artifact as dispatch state.
Fork PRs use the same
no-secret reusable path after fetching the immutable head SHA through the
base repository. All lane conditions and summaries consume the same plan or
its bounded projection.
- Keep direct semantic ownership separate from Cargo reverse dependencies:
direct paths/crates select product, SDK, backend, platform, protocol, model,
UI, website, and infrastructure slices; affected crates select Rust compile,
Clippy, and tests.
- Store path/domain ownership and slice dependencies in checked-in manifests.
The planner must emit a versioned machine-readable plan containing the
profile, source identity, direct domains, affected crates, required slice
IDs, bounded matrices, dependency reasons, and fan-out budgets.
- Supported profiles are semantic and closed: draft PR, ready PR, exhaustive
main, and explicit full/manual validation. Do not add ad hoc boolean inputs
when a profile or ownership rule expresses the decision.
- CI control-plane changes fail open to the affected slices and their
consumers, except paths that map only to documentation plus
ci-control;
those retain limited documentation routing. Unknown paths, unknown plan
fields, duplicate slice IDs, invalid matrices, or an ownership/schema
mismatch fail planning.
- Pull requests test affected crates plus reverse dependents. Main tests every
workspace member exactly once. Workspace discovery belongs to Cargo metadata,
not a workflow-maintained allowlist.
- Use measured workload data to rebalance deterministic shards, but keep the
checked-in algorithm reproducible. Use one Cargo invocation per shard unless
a documented package-isolation check requires otherwise.
Dependencies, summaries, and cancellation
- If a consumer downloads an artifact, its producer must be reachable under
the same plan and declared through
needs. Consumers never rebuild missing
producer inputs.
- Every lane has one unique, stable, non-matrix summary. Each PR/main entry has
one stable native result; the manual-only controller owns one stable
synthetic aggregate check.
- Each lane summary directly needs every top-level slice call in that lane,
runs with
if: ${{ !cancelled() }}, and validates results against its
digest-bound plan projection. The aggregate completes only after every
correlated lane check is terminal.
A skipped slice is valid only when absent from the plan. Reject required
skips, failures, cancellations, unknown results, duplicate plan entries, and
planned IDs outside the static needs graph.
- Cancel superseded PR runs. Do not cancel releases, deployments, cache
warming, cleanup, or publishing unless their rollback semantics explicitly
permit it.
- Treat each focused PR workflow as an independent failure domain. A Quality
or Website failure must not cancel platform compilation, tests, products, or
smoke jobs, and a platform failure must not cancel another focused PR
workflow.
- Within a PR platform lane, compilation, functional-test, product, and smoke
matrices fail fast so a required row failure cancels their queued and
in-progress sibling rows. Keep this profile-derived: exhaustive main/manual
runs continue sibling rows to retain complete diagnostics, and Quality
matrices continue all rows because they are independent diagnostics rather
than producer/consumer gates.
- Prefer declared
needs edges to suppress impossible consumers after a
producer failure. Do not cancel an entire workflow run through the Actions
API on first failure: that prevents the stable lane summary from reporting a
terminal failure and discards unrelated failure-domain evidence.
- Control fan-out in the graph. Use bounded matrices and
max-parallel after
eliminating irrelevant work; do not use additional runner capacity as a
substitute for routing and composition.
Trust, runners, and providers
- Use
pull_request, never pull_request_target, to build or execute PR
content. Metadata-only and positively matched cleanup workflows may use
pull_request_target but must never execute PR-controlled content.
- Treat GitHub and Depot as placement providers, not different build graphs.
Slices request a semantic runner role; a centralized, event-derived policy
maps that role to a provider label and cache mode.
- A caller may not choose a privileged runner label or independently grant
remote-cache access. The workflow that owns
runs-on must derive both from
repository, event, ref, trust profile, architecture, and a bounded size.
- Ordinary PR jobs remain GitHub-hosted unless the bounded same-repository
Depot risk exception below is active; an explicitly approved ephemeral,
uncredentialed hardware runner may own a documented GPU smoke exception.
Eligible trusted main jobs may use
Depot only through the exact-string
DEPOT_RUNNERS_ENABLED gate and retain a
GitHub-hosted fallback. Tags, feature refs, external callers, credentialed
smokes, macOS, Windows, and hardware-qualified GPU work stay on their
explicitly approved provider until separately migrated.
- Never route untrusted code to a persistent self-hosted runner. Public-repo
self-hosted execution requires ephemeral runners, restricted credentials and
network access, and a runner group limited to the repository and exact
protected default-branch reusable-workflow refs.
- A checked-out selector is defense in depth, not a PR trust boundary. PRs can
modify local workflows and actions. Any future PR-on-Depot path must use a
default-branch-pinned runner-owning workflow and a GitHub runner-group
selected-workflow restriction.
- Provider changes must not alter source checkout, commands, profile,
artifacts, tests, required checks, or plan membership.
Bounded Depot PR cache-risk exception
Depot Cache currently scopes entries by repository but not by branch and
automatically connects GitHub-cache API consumers on Depot runners. Cache-key
prefixes, job-local sccache, and a trusted caller do not prevent checked-out PR
code from using injected cache authority directly.
The provider-isolation requirements below remain the desired end state:
- Automatic Depot Cache connectivity is disabled for the PR runner context,
or Depot supplies a comparably strong per-PR namespace and read/write policy.
- A fork PR receives no Depot cache token, WebDAV credential, transparent
Actions-cache write authority, registry credential, or repository secret.
- A hostile PR can neither read a trusted sentinel cache entry nor publish an
entry later restored by trusted main/release jobs. Cache-key conventions are
not proof.
- The ephemeral Depot runner group is restricted to this repository and exact
protected default-branch runner-owning workflows.
- CI/workflow changes force the GitHub-hosted validation path so a PR cannot
replace the protected executor that grants Depot placement.
- GitHub-hosted rollback remains one checked provider-policy change and does
not change the plan or artifact graph.
Until the provider supplies that end state, maintainers may deliberately accept
the documented repository-wide cache risk for one exact same-repository PR
head under all of these controls:
- the exception has a checked-in UTC expiry and fails hosted after it;
DEPOT_PR_RUNNERS_ENABLED is exactly true;
DEPOT_PR_APPROVED_REF exactly matches refs/pull/<number>/merge and
DEPOT_PR_APPROVED_SHA exactly matches the current PR head SHA;
- the head repository is exactly
Mesh-LLM/mesh-llm; forks remain hosted;
- CI-control, workflow, runner-policy and cache-policy changes force hosted;
- the protected default-branch runner-owning workflows remain the executor;
- PR jobs receive no repository secrets or registry credentials; and
- rollback is deletion/false of the PR gate or either exact approval value.
This is an explicit speed-versus-isolation decision, not evidence that Depot
cache is isolated. While active, GitHub Actions cache API consumers on selected
Depot PR and trusted-main jobs may share Depot's repository-wide namespace.
Treat that namespace as attacker-controlled and keep release, publishing,
deployment and credential-bearing jobs off it. Record the rationale, known
failure modes, owner, start, expiry and rollback in
ci/DEPOT_PR_RISK_EXCEPTION.md.
GitHub's all_external_contributors workflow-approval policy does not cover a
same-repository branch pushed by a collaborator. Do not describe that setting
as the approval boundary for this exception. The exact maintainer-controlled
ref and head-SHA variables are the checked-in per-PR approval boundary and must
be refreshed after every PR synchronization.
Cache contract
- Declare cache mode per slice: no remote cache, PR restore-only/isolated, or
trusted read-write. Derive it from the same policy as runner placement.
- GitHub-hosted PR writes remain isolated from trusted main: normal protected
same-repository and fork lanes explicitly force restore-only cache mode even
though their workflow ref is the default branch. The bounded Depot exception
is the documented cross-branch deviation. Trusted
main/release/warmers own shared publication. Do not save large shared
Rust/native caches from PRs.
- GitHub scopes caches created by
pull_request runs to that PR's merge ref.
Small exact caches may publish in that isolated scope when their key covers
every compatibility boundary and their contents are verified before use;
this is the approved mechanism for accelerating reruns of the same PR.
Never use a restore prefix for these PR-produced native caches.
- Keep multi-gigabyte Cargo target caches restore-only on PRs. Per-PR copies
multiply by matrix row, consume repository cache quota, and can evict the
trusted main caches that every PR can restore. Prefer small exact native ABI
caches and package-manager download stores with lockfile-exact keys.
- Assign one publisher for a shared package-manager key. Other focused PR
workflows may restore it but must not race to upload the same cache entry.
- Keep PR sccache job-local unless a provider proves safe isolation. A cache
miss must remain a correctness-preserving miss, never a reason to rebuild a
producer secretly in a consumer.
- Cache keys include every compatibility boundary: provider where necessary,
OS, architecture, backend/toolchain, profile, image/toolchain epoch,
lockfiles, recipe inputs, and
.github/cache-version.txt. Do not use broad
restore prefixes across trust or ABI boundaries.
- Treat restored cache contents as untrusted. Verify native build stamps,
manifests, checksums, target/backend identity, and required link closure
before reuse. Never store credentials or private endpoints in a cache.
- Artifacts move outputs within a run; caches accelerate regeneration across
runs. Do not use a cache as the producer/consumer correctness contract.
- PR and smoke-only artifacts default to one-day retention. Release evidence
follows the release policy.
Product and artifact contract
- Model every executable product as a backend-neutral host, one separately
packaged native runtime per OS/architecture/backend, and a composition-only
product. A backend matrix belongs to runtime/product rows, never host rows.
- Build prepared UI assets once per selected platform lane and feed that
immutable artifact to every host producer in the lane. Host producers must
not rerun UI tests. Cross-workflow artifact sharing is an explicit timing
tradeoff, not a correctness dependency.
- Host artifacts include the executable checksum and import-policy report.
Runtime artifacts include manifest, checksums, ABI/version, platform, and
backend identity. Product manifests record exact host and runtime digests.
- Use the owning preparation and composition actions. A composer verifies and
copies exact producer bytes; it never compiles, links, re-stamps, or silently
substitutes an input.
- No-driver
--version, runtime discovery, and noninteractive client readiness
are mandatory for every composed alias. Hardware serving qualification is
additional coverage, not a replacement.
- Restore smoke inputs through the shared restore action and reusable smoke
workflows. Every CI invocation of
mesh-llm includes --log-format json.
- Release and packaging wrap the same producers with signing, attestation,
publication, and retention. They do not fork the underlying build commands.
Workflow definition and dependencies
- Declare least-privilege
permissions explicitly. Default to
contents: read; grant write or OIDC permissions only to the owning job.
- Set
persist-credentials: false on checkout unless a narrowly scoped job
must push. Never expose secrets to fork PRs or interpolate untrusted values
into shell commands.
- Pin new third-party actions to a full commit SHA with a release comment.
- Use stable job/step IDs, explicit shells,
set -euo pipefail for nontrivial
Bash, PowerShell-native error handling on Windows, and realistic timeouts.
- Project dependencies belong in checked-in manifests/lockfiles. Shared Linux
tools belong in
mesh-llm-runner-images. Do not add one-off apt, pip,
global npm, cargo install, downloaded binaries, or curl | sh to a job.
- Use digest-pinned multi-architecture runner images. A backend image provides
a toolchain, not GPU hardware. GPU execution requires a restricted matching
device runner.
Operational safety
- Inspection, log reads, syntax validation, and dry-run planning are read-only.
Dispatching, rerunning, cancelling, approving, deleting, changing variables
or secrets, editing runner groups, changing Depot settings, publishing,
deploying, or resetting caches are external mutations and require scope from
the user.
- Never print or persist secret values. Report names, scope, and presence only.
- Diagnose failures as product, dependency, runner image, capacity, cache,
artifact, permission, secret/variable, external service, or workflow logic.
Fix the owning source; do not weaken a gate or add retries to hide a
deterministic failure.
- Validate with the narrowest safe workflow. A run is not successful until all
required jobs reach a terminal successful conclusion; state expected skips.
Validation contract
For every workflow or local-action edit:
just ci-validate
Also run what applies:
just ci-shellcheck <changed-script>... for changed shell scripts or
substantial embedded Bash.
just ci-crate-lists for routing,
workspace, Clippy, or test-plan changes.
just check-release for release/product target changes.
just publish-crates for publishing.
- Owning planner/action tests covering selected, skipped, invalid, PR, main,
GitHub, and Depot-denied branches as applicable.
- A non-required dual-run or authorized dispatch before migrating required
checks or external runner policy.
Finish with changed files, validation results, expected skips, unresolved
risks, external state changes, and live configuration that could not be
verified.