Skip to content

Build caching for deploy actions (sccache) - #347

Draft
aram356 wants to merge 47 commits into
mainfrom
feature/build-app-cli-cache
Draft

aram356 wants to merge 47 commits into
mainfrom
feature/build-app-cli-cache

Conversation

@aram356

@aram356 aram356 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Establishes the reviewed trust-root baseline for the deploy-action build-caching rollout. This PR is gate candidate G; it intentionally stops before the source release request, image publication, cache action, provenance consumer, provider launchers, or app-repository adoption phases.

Tracks #346.

Design: docs/superpowers/specs/2026-08-20-edgezero-deploy-build-caching-design.md

Plans:

  • docs/superpowers/plans/2026-08-20-build-cache-container.md
  • docs/superpowers/plans/2026-08-20-build-cache-actions.md
  • docs/superpowers/plans/2026-08-20-build-cache-provenance.md
  • docs/superpowers/plans/2026-08-20-build-cache-launcher-providers.md
  • docs/superpowers/plans/2026-08-20-build-cache-consumer-adoption.md
  • docs/superpowers/plans/2026-09-05-build-cache-execution.md

Gate Baseline

  • Adds the canonical 108-path gate manifest, exact CODEOWNERS expansion, and closed 51-path image context.
  • Adds the reproducible build-app-cli Dockerfile, provenance protocol validator, hostile fixtures, and fail-closed image/toolchain verification.
  • Adds protected build-container CI, publication, and gate-rotation workflows with exact-version action references.
  • Adds strict change classification, workflow graph validation, API allowlists, approval-before-App-token ordering, and publication/rotation evidence contracts.
  • Adds repository-pinned actionlint and yq installers and compatibility validation.
  • Consolidates the deployment design, plans, and adoption guide under docs/superpowers/.

Scope

The net diff contains 105 gate-manifest paths and 23 Task 0/supporting-documentation paths. It contains no crates/ or examples/ changes. The previously bundled Fastly safety fixes were removed from this PR's net diff and remain available by their original commits for a later focused change.

This bootstrap PR must be merged through human review before G can be recorded and the repository/organization controls can be activated. The three build-container jobs are expected to skip until EDGEZERO_BUILD_CONTAINER_GATE_SHA is configured after merge.

Verification

  • Independent exact-HEAD security/scope review: no findings; READY as gate candidate G
  • .github/actions/deploy-core/tests/run.sh: 326 passed, 0 failed, 5 platform skips
  • cargo test --workspace --all-targets
  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo check --workspace --all-targets --features "fastly cloudflare spin"
  • cargo check -p edgezero-adapter-spin --target wasm32-wasip2 --features spin
  • Provenance validator: 76 unit tests and 21 CLI tests passed
  • Pinned actionlint 1.7.12, ShellCheck, action/doc pin policies, and zizmor --offline
  • Hosted CI: all applicable checks passed; protected build-container jobs skipped as designed before activation

First increment of the build-caching feature (sub-plan 1, Task 1) per
docs/specs/edgezero-deploy-build-caching.md (v6.14) and
docs/superpowers/plans/2026-08-20-build-cache-container.md. The pinned build
container's platform-id keys the whole feature on a sha256 manifest digest, so
check-image-pin.sh fails closed on a tag, missing digest, or malformed JSON.
Colocated unit test: 6 cases, all green; shellcheck clean.
@aram356 aram356 self-assigned this Aug 27, 2026
@aram356
aram356 marked this pull request as draft August 27, 2026 04:46
aram356 added 27 commits August 26, 2026 21:46
…tion)

The build-caching design spec was authored via the brainstorming flow, whose
specs live under docs/superpowers/specs alongside their plans (the container
sub-plan is already in docs/superpowers/plans). Relocate it there from docs/specs
and update the two references (the plan's Spec: link and the validator's comment).
Vitepress builds clean; the validator test stays green.
…nvention

Siblings in docs/superpowers/specs are dated YYYY-MM-DD-<topic>-design.md; rename
edgezero-deploy-build-caching.md to 2026-08-20-edgezero-deploy-build-caching-design.md
(its authoring/plan date) and update the plan link + validator comment.
…ion gap)

The validator used jq -r, which coerces a numeric field to a string, so a
{"repository": 123, "tag": 1} would pass despite the contract requiring strings.
Assert the JSON type is string for repository, tag, and digest before the value
checks, and add a wrong-type test case. 7/7 green, shellcheck clean.
Harden the sccache design toward plan-ready. env: add PATH and RUSTUP_HOME and an
absolute RUSTC_WRAPPER so rustc starts under env -i (rustup-image layout) (1). Narrow
the sccache correctness claim (it hashes dep-info/args/deps/env/cwd) and make the
undeclared-input proc-macro/build.rs risk an explicit cache opt-in (2). Bounded,
collision-free generation: run_id-run_attempt-artifact, SCCACHE_CACHE_SIZE 2G,
sccache --stop-server before save, aggregate bounded by GitHub's LRU (3). A complete
FIXED mount table with a constant /work/app cwd so sccache's cwd hash is stable across
host paths (4). Prove the writable /work/app is a faithful copy (content/modes/symlinks/
submodules, hardlinks broken) and state build/deploy use separate container instances (5).
Warm test via sccache --show-stats ONLINE (dependency sources are not cached, so the
network cannot be disabled for the fetch) (6). Public, anonymously-fetchable sources only;
private auth is out of scope (7). RFC 8785 (JCS) canonical JSON and ustar-only archive with
binary-size equality (8). Full 40-hex app-ref and length-framed hash encodings with golden
vectors (9). Hardened validator smoke: --cap-drop=ALL, no-new-privileges, memory/pids/
timeout (10). Plan: fix the first-publish deadlock (authenticated smoke in the workflow;
anonymous pull is the operator's post-make-public step) and drop the stale four-root-prune
language (11). Design only.
The validator accepted any non-empty repository, so a pin naming a foreign
repository could become platform-id. Require repository == the canonical
ghcr.io/stackpop/edgezero-build-app-cli and add a foreign-repository reject case
(8/8). A trusted digest is only trustworthy for the repository we publish.
…findings

Stable host cache path: actions/cache folds the on-disk path into the cache
version, so a per-run mktemp path forces permanent misses; use one fixed
${RUNNER_TEMP}/edgezero-sccache-v1, emptied before restore, mounted at
/work/sccache (1). Whole-repo /work/repo working copy with the compile cwd at the
relative working-directory, so a nested working-directory (apps/api under a parent
workspace) keeps its enclosing Cargo config and sibling path-deps; the flattened
/work/app is gone (2). Frozen source: git-ignored files excluded from the copy and
initialized submodules validated, and the SAME copy is reused across the separate
build/deploy container instances so build outputs reach deploy as derived state (3).
Storage restated as repository-global LRU that can evict unrelated caches and may be
billable, not family-local (4). Generation keyed on an app-cli-artifact unique across
every cache-writing invocation (fail-closed on a detectable collision), with concurrent
lineages forked, not merged (accepted) (5). PATH includes /usr/local/bin where Fastly
and sccache live; enumerated compile/validation/deploy env profiles listing EDGEZERO_*
by name, not the namespace (6). app-checkout-token assigned to the host-side app-repo-id
API check and barred from containers/copies/artifacts/caches (7). Exact byte contracts:
length-framed <len>:<bytes> hash encoding with normalized relative paths, normalized
ustar headers (zero mtime/uid/gid, fixed names), and abi as recomputed ELF metadata
(machine/interp=null-if-static/direct-DT_NEEDED; transitive resolved, dlopen out of
scope) (8). sccache undeclared-input risk stated as accepted (no proc-macro
input-declaration mechanism exists); fail-cold restore/audit/read failures; skip-save on
--stop-server failure (9). Plan: two-tier pin policy (major action tags per the repo's
own check-action-pins gate, image digests) resolving the apparent checkout@v7
inconsistency; validator canonical-repo requirement reflected; image.json rigor scoped
(the JCS/schema/dup-key provenance machinery is for produced artifacts, sub-plan 3) (10).
Design only.
…findings

Reusable workflow is BUILD-ONLY: no provider inputs, emits the artifact plus every
ExpectedIdentity field as outputs; the shared-copy build->deploy lifecycle moves to
the consumer's own deploy job, resolving the one-container-builds-and-deploys
contradiction (1). Undeclared-input staleness restated as may-pass-every-downstream-
check: a stale proc-macro result can be internally consistent and pass digest/ELF/--help,
so provenance/ABI is not a staleness safety net (2). A deploy-compile env/mount profile:
fastly compute deploy compiles the wasm, so it carries pinned Rustup/Cargo + fresh
target/cargo but NO RUSTC_WRAPPER, SCCACHE_DIR, or cache save, with the token (3). The
shared writable copy's tracked files/modes/symlinks/gitlinks are re-verified before the
token-bearing deploy-compile; derived state only in declared output paths, so a build.rs
that mutates tracked source fails closed (4). Per-operation mount profiles instead of one
common table: the unauthenticated validator --help smoke gets no writable repo/target/
cargo/sccache; only cached-compile mounts sccache (5). Cache holds compiled results incl.
replayed compiler stdout/stderr (warnings, paths, source excerpts), widening cross-repo
disclosure to build diagnostics (6). Cross-repo topology predicates split: deployer ref,
called-workflow SHA, and app-checkout SHA checked separately (deployer HEAD != app SHA is
fine); path deps permitted anywhere beneath git-root, only a git-root escape rejected (7).
Byte-exact path hashing: drop NFC (Linux/Git paths are byte strings; NFC vs NFD are
distinct files), define the '.' root, reject non-UTF-8; NFC/NFD golden vectors that must
differ (8). job.check_run_id generation, SCCACHE_IGNORE_SERVER_IO_ERROR=1 (per-object IO
error -> miss not cold reset), name reserved before save, compiler errors never retried;
full recursive non-sparse checkout with LFS/filter content materialized; wall-time is
telemetry (11,12 spec parts). Plan: baked project-owned validator (JCS/dup-key/schema/
ustar/ELF, smoke-tested at publish) since jq/tar cannot do it (10); SHA-pin the actions in
the write-privileged publish workflow (contents/packages/PRs write) while leaving the
repo-wide migration of low-privilege references as a separate decision (9); single-manifest
check rejects a one-entry OCI index (leaf manifest required) and the anonymous-pull check
reads the merged digest, not the placeholder (12 plan parts). Design only.
@aram356 aram356 changed the title Build caching for the deploy actions (sccache) — sub-plan 1: container digest-pin gate Build caching for deploy actions (sccache) Sep 13, 2026
@aram356
aram356 marked this pull request as ready for review September 15, 2026 02:13
@aram356
aram356 marked this pull request as draft September 15, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant