Skip to content

Latest commit

 

History

History
101 lines (83 loc) · 7.7 KB

File metadata and controls

101 lines (83 loc) · 7.7 KB

Repository Guidelines

Overview

preloop reimplements the GitHub Actions control plane and official actions/runner in Rust. The unmodified runner registers, polls, executes, and reports against it. Also contains runner-watch for protocol conformance testing.

Crates

Crate Role
preloop-runner-server HTTP control plane: /_apis/… (runner protocol) + /api/v1/… (native REST) + /broker/…
preloop-gha-parser Workflow YAML → typed model → job DAG/matrix expansion
preloop-gha-expressions ${{ }} parser/evaluator
preloop-gha-protocol Wire DTOs, session crypto, secret wrappers, NDJSON events
preloop-runner Rust runner: Listener + Worker (faithful to actions/runner v2.335.1 wire protocol; official runner pin 2.336.0 in versions.toml)
preloop-runner-client CLI for submitting workflows
preloop-cache / preloop-artifacts File-backed protocol storage
preloop-dap Debug Adapter Protocol bridge
preloop-conformance / runner-watch Conformance harnesses and protocol-diff tooling
preloop-cli The preloop binary (run/serve/debug/push CLI)
preloop-orchestrator Golden/env provisioning and the runner pool
preloop-vm VmProvider backends (SmolVM/libkrun, AgentENV)
preloop-observability Logging/metrics/OTel export
preloop-socket-activation systemd socket activation

Commands

just test-ci    # fmt-check + clippy + zizmor + test + conform replay (the full gate)
just serve      # cargo run --release -p preloop-runner-server -- serve --listen 127.0.0.1:9090
just dogfood    # E2E with real runner

Key Conventions

  • Toolchain: Rust 1.97, cargo fmt, cargo clippy --workspace --all-targets.
  • Error handling: anyhow at top-level; ApiError in HTTP handlers; thiserror enums in libraries.
  • State: in-memory behind Arc<Mutex<…>> + Notify/broadcast. Secrets use SecretString — call expose() only at protocol boundaries.
  • Wire compatibility: /_apis/… is the source of truth. Validate protocol changes against the official runner, not only unit tests.
  • Broker path only: all work targets the modern broker + Twirp results-service protocol (v2.329.0+).
  • VM substrates: two backends behind preloop_vm::VmProvider — SmolVM (libkrun; the default everywhere) and AgentENV (Firecracker over /dev/kvm; opt-in via PRELOOP_VM_BACKEND=agentenv). Selected by PRELOOP_VM_BACKEND; capability differences are declared by VmProvider::capabilities() and the orchestrator branches on them. See docs/vm-substrates.md before touching pool or provider code.
  • Store backends: the Store trait (store.rs, async, object-safe) is the only surface the server sees; backends are SQLite (store.rs, default, <state_dir>/preloop.db) and Postgres (store_pg.rs), selected via PRELOOP_STORE_URL (sqlite://<path> / bare path / postgres://…). Both are single-writer: one connection behind a mutex. Two servers on the same SQLite file (or same PG database) still diverge in-memory — the DB is a restart source, not a shared bus.
  • Store is best-effort: in-memory state is the source of truth, the database is a restart source. Store failures are logged; the affected event is still broadcast (see state.rs::emit). Per-backend MIGRATIONS is the schema source of truth (SQLite: PRAGMA user_version; PG: schema_migrations version table).
  • Encryption-at-rest is obfuscation, not security: <state_dir>/hmac-key.bin and preloop.db sit in the same directory; the store key is HKDF-derived from the JWT HMAC key with domain separation. It stops a stolen DB file, not a compromised state dir. Key loss = unbootable state. The envelope is backend-independent (sealed blobs), so it applies to Postgres rows too — for remote PG, rely on TLS + DB auth instead.

Important Files

  • docs/architecture.md — crate map + module map
  • docs/fidelity-gap.md — protocol gaps and conformance status
  • CONTRIBUTING.md — dev workflow and compatibility checklist
  • fixtures/workflows/dogfood.yml — local self-hosted validation workflow
  • .runner-watch/golden/v2.335.1/ — protocol golden captures (prior baseline)
  • versions.toml — pinned official runner (2.336.0)
  • Official runner binary cache: ~/.cache/actions-runner/current (osx-arm64)
  • Official runner source checkout: /tmp/runner-v2.336.0 (commit 98aabcd)

Agent Preferences

  • Be critical. Push back with evidence when a plan hides risk or a claim is wrong.
  • Composability is the goal. Any runner should work with any server. Never introduce protocol divergences.
  • Local CI is mandatory. After every large chunk of work or task, run just test-ci to validate the changes and dogfood the workflow.
  • Drop-in workflows. Users should be able to run their workflows in local CI unmodified.
  • Supply-chain policy isolation. A PR must not mix policy files (Cargo.lock, deny.toml, .cargo/audit.toml, supply-chain/, .github/workflows/supply-chain.yml) with non-policy changes — the supply-chain audit job fails the PR. The one sanctioned mixed shape is a dedicated dependency PR: exactly Cargo.lock plus the root/crate Cargo.toml manifests that declare the dependency; the guard accepts that shape and rejects anything else riding along (source, tests, docs, other policy files). Adding a dependency (even a dev-dependency) touches Cargo.lock; use that dedicated PR or vendor the need away.

Debugging Dogfood (mandatory)

When investigating CI behavior — queue stalls, job failures, check-run state, runner churn — default to preloop's own debug features before log-diving. Dogfood them; any divergence from docs/debug-sessions.md is a bug to report.

  • Sessions: failed jobs pause into debug sessions (state machine in docs/debug-sessions.md §2). List: preloop debug or GET /api/v1/debug/sessions (native bearer). Attach: preloop debug [<ref>] — in-VM verbs :retry, :retry --sync, :retry --from <step>, :sync, :export. Non-interactive: preloop debug --verdict retry|continue|abort.
  • Agent API (native bearer): lease POST /api/v1/agent/debug/sessions/<id>/lease, stream GET .../events, drive POST .../operations, audit GET .../audit.
  • DAP: attach over WS GET /api/v1/runs/<run_id>/debug (native bearer); preloop-dap bridges DAP to the session state machine.
  • Hold VMs: preloop run --preserve-on-failure keeps the VM for a later preloop shell/attach when nothing can answer interactively (piped/detached/CI).
  • Leaked sessions (terminal states abandoned, aborted, or sessions that never close) count as findings, not noise.