Skip to main content

Module package

Module package 

Source
Expand description

The cached, checksum-verified GitHub runner package.

Two failure modes drive this module, and every decision below is one of them being closed:

  • A tampered package executes arbitrary code on the operator’s machine. Download metadata comes from GitHub and nowhere else, the published SHA-256 is verified before extraction, and an absent published checksum fails closed rather than degrading into “skip the check” (07-security.md, 05-infrastructure.md).
  • A stale package makes every job start failing for a reason nobody will guess. GitHub rejects runners more than 30 days behind the latest release (01-current-architecture.md, edge case 7), so the published version is re-checked on a bounded interval and a superseded cache entry is refreshed before a cold start.

§The shape of the cache

Under AppPaths::state_dir, which 05-infrastructure.md names as the home of “the retained runner package cache”:

state/
  packages/
    2.330.0/                     one immutable entry, never mutated after it lands
      .runner-package.json       its manifest, written before the entry lands
      run.sh, bin/, externals/   GitHub's archive, extracted
    .staging/                    transient; a partial install is only ever here
    .leases/                     <attempt-uuid>.lease, the prune guard's evidence
  tool-cache/                    approved tool caches, retained beside the binaries
runtime/                         per-attempt job workspaces — NOT under packages/

A cache entry is created by exactly one filesystem operation. Extraction writes into .staging/<uuid>/, the manifest is written inside that staging directory, and the directory is then renamed to packages/<version>/. The rename is the single commit point: an entry that exists is an entry that is complete, and a crash at any earlier moment leaves nothing but staging litter. That is also what makes “a second install of the same version is a no-op” true rather than aspirational — the entry is found and returned before anything is fetched.

§The seam e3 consumes, stated deliberately

e3 creates each JIT runtime as “a copy or link from that cache plus a unique workspace” (05-infrastructure.md). Three contracts hold that seam together, and they are stated here because an unstated port contract becomes a defect in the consumer:

  1. InstalledPackage::root is read-only to the caller. It is the shared, immutable source that every runtime is copied or linked from. Writing into it corrupts every future runtime, and nothing in this module can detect it after the fact.
  2. PackageCache::lease must be called for the version a runtime was created from, and PackageCache::release when that runtime is removed. The prune guard is only as truthful as those calls: a version with no lease is a version this module believes nothing references. The guard is fail-closed in the other direction — see PackageCache::prune — so a lease that is never released costs disk, while a lease that is never taken costs a running runner its binaries.
  3. Job workspaces live under AppPaths::runtime_dir and never under the package cache. PackageCache::lease enforces this rather than documenting it: a lease whose attempt’s runtime path resolves inside the cache root is refused.

§What is built here and not yet reachable in production

Two mechanisms in this module are complete and tested but have no live caller, and both are easier to find written down here than inferred from an absence:

  • PinnedDigests has no operator-facing surface. It is the entire remedy PackageError::ChecksumAbsent names, and today it can only be supplied by PackageCache::with_pins from Rust. Until a configuration or CLI path reaches it, an operator who hits that refusal has been given an instruction they cannot carry out. Wiring it is a g/f-group concern, not this module’s.
  • PackageError::VersionRejected cannot be produced through the real adapter. GatewayCatalog maps every InventoryError to the retryable PackageError::CatalogUnavailable, correctly — the downloads endpoint has no version-rejection response. The rejection lands at registration, which is e3’s path, and DownloadCatalog exists to carry it here when e3 reports it. So the no-retry behaviour is proven and nothing yet supplies its input.

Structs§

CachePorts
The ports PackageCache reaches the world through.
ExponentialBackoff
Bounded exponential backoff, as 03-control-flows.md requires.
Freshness
When a cached package stops being good enough.
GatewayCatalog
Adapts c3’s InventoryGateway to DownloadCatalog.
HttpFetcher
Streams the package with reqwest, one chunk at a time.
InstalledPackage
One immutable entry in the cache.
NoBackoff
No delay at all. For tests, and for a caller that supplies its own pacing.
PackageCache
The versioned, immutable, checksum-verified runner package cache.
PinnedDigests
Digests the operator has independently confirmed, keyed by version.
RunnerVersion
A runner package version, such as 2.330.0.
Sha256Hex
A SHA-256 digest, normalised to 64 lowercase hex characters.

Enums§

PackageError
Everything that can stop a package reaching the cache.
PublishedChecksum
Which unusable shape GitHub’s optional sha256_checksum arrived in.

Constants§

CHECK_INTERVAL_HOURS
How often the published version is re-checked. “A bounded interval” (05-infrastructure.md); six hours keeps the check far cheaper than the demand poll while still catching a release the same day it ships.
FRESHNESS_WINDOW_DAYS
How far behind the latest release a cached package may be before it is refreshed. GitHub rejects runners past this and plans to block them at registration (01-current-architecture.md, edge case 7).
RETRY_BUDGET
How many times a retryable failure is retried before the install is abandoned. A terminal failure consumes none of this budget.

Traits§

Backoff
How long to wait between retries of a retryable failure.
DownloadCatalog
Where the published runner-download metadata comes from.
PackageFetcher
Streams a published package onto disk.