ci(runner): bind build cache to native toolchain

This commit is contained in:
Dotta 2026-09-04 12:44:39 -05:00
parent cd8358d246
commit ab16c12d4f
4 changed files with 64 additions and 15 deletions

View File

@ -553,33 +553,48 @@ jobs:
-f "ref=$GITHUB_WORKFLOW_SHA" --jq .sha)"
[[ "$workflow_blob" =~ ^[0-9a-f]{40}$ ]]
package_set_manifest="$RUNNER_TEMP/runner-e2e-build-packages"
dpkg-query --show --showformat='${binary:Package}=${Version}\n' \
| LC_ALL=C sort > "$package_set_manifest"
test -s "$package_set_manifest"
package_set_id="$(sha256sum "$package_set_manifest" | cut -d ' ' -f 1)"
[[ "$package_set_id" =~ ^[0-9a-f]{64}$ ]]
toolchain_id="$({
printf '%s\n' 'paperclip-runner/e2e-build-toolchain/v1'
printf '%s\n' 'paperclip-runner/e2e-build-toolchain/v2'
printf 'runner=%s\n' "$TEST_RUNNER"
printf 'runner-os=%s\n' "$RUNNER_OS"
printf 'runner-arch=%s\n' "$RUNNER_ARCH"
printf 'runner-image-os=%s\n' "${ImageOS-}"
printf 'runner-image-version=%s\n' "${ImageVersion-}"
printf 'package-set=%s\n' "$package_set_id"
for variable in \
CC CFLAGS CI CARGO_BUILD_TARGET CARGO_ENCODED_RUSTFLAGS \
AR CC CFLAGS CI CMAKE CARGO_BUILD_TARGET CARGO_ENCODED_RUSTFLAGS \
CARGO_TARGET_DIR LANG LC_ALL LDFLAGS NODE_ENV NODE_OPTIONS \
RUSTC RUSTC_WRAPPER RUSTFLAGS SOURCE_DATE_EPOCH TZ
CXX PKG_CONFIG RANLIB RUSTC RUSTC_WRAPPER RUSTFLAGS \
SOURCE_DATE_EPOCH TZ
do
printf '%s=%s\n' "$variable" "${!variable-}"
done
uname -srm
sha256sum /etc/os-release "$(command -v cc)" "$(command -v ld)" "$(command -v ldd)"
sha256sum /etc/os-release
node --version
pnpm --version
(cd packages/paperclip-runner && rustc -vV)
(cd packages/paperclip-runner && cargo -Vv)
cc --version
ld --version
ldd --version
for tool in cc c++ ld ldd ar ranlib cmake pkg-config; do
tool_path="$(command -v "$tool")"
test -f "$tool_path"
printf 'tool=%s path=%s\n' "$tool" "$tool_path"
sha256sum "$tool_path"
"$tool" --version
done
} | sha256sum | cut -d ' ' -f 1)"
[[ "$toolchain_id" =~ ^[0-9a-f]{64}$ ]]
manifest="$RUNNER_TEMP/runner-e2e-build-inputs"
{
printf '%s\n' 'paperclip-runner/e2e-build-inputs/v1'
printf '%s\n' 'paperclip-runner/e2e-build-inputs/v2'
printf 'workflow=%s\n' "$workflow_blob"
printf 'lock=%s\n' "$EXPECTED_LOCK_SHA256"
printf 'toolchain=%s\n' "$toolchain_id"
@ -612,7 +627,7 @@ jobs:
{
echo "content_id=$content_id"
echo "toolchain_id=$toolchain_id"
echo "cache_key=runner-e2e-build-v1-$ref_scope-$content_id"
echo "cache_key=runner-e2e-build-v2-$ref_scope-$content_id"
} >> "$GITHUB_OUTPUT"
- name: Restore exact reusable build outputs

View File

@ -341,6 +341,14 @@ default of 32. Multi-turn steps are sequential inside their cell while
independent cells overlap. Artifacts and merged HTML/JUnit/normalized reports
are retained for 30 days.
The campaign builds shared TypeScript and native runner outputs once and then
fans that verified bundle out to selected cells. A later campaign on the same
target branch may reuse the bundle only when the trusted workflow, target
source closure and lockfile, requested output shape, runner image package set,
and native toolchain identity are exact matches. Changes to native build tools
such as CMake, pkg-config, the compiler, archiver, or ranlib deliberately force
a cold rebuild.
Restrict the RunsOn fleet to this repository and independently trusted
workflows. Do not let untrusted pull-request or fork-triggered workflows target
it, and require a fresh ephemeral instance for each job so one paid cell cannot

View File

@ -38,6 +38,15 @@ history publication explicitly use the trusted workflow commit and do not
consume the target lockfile. Never run the workflow definition from the target
branch.
Reusable executable build outputs are keyed by the trusted workflow blob, the
resolved target lockfile, a conservative source closure, requested output
shape, target branch scope, and the build environment. The environment identity
includes the literal runner selector, OS and architecture, the sorted installed
Debian package set, and the resolved path, digest, and version output of the C,
C++, linker, archiver, ranlib, CMake, and pkg-config tools. This is intentionally
conservative: a runner image or native build-package update must produce a cold
cache miss rather than reusing binaries from an under-specified toolchain.
The workflows verify both the original actor and triggering actor for every
scheduled or manual attempt, including human reruns. Every
secret-bearing job repeats this check as its first step so GitHub's partial-job

View File

@ -549,10 +549,10 @@ describe("public repository paid workflow security", () => {
);
expect(runnerBuildJob).not.toContain("restore-keys:");
expect(runnerBuildJob).toContain(
"cache_key=runner-e2e-build-v1-$ref_scope-$content_id",
"cache_key=runner-e2e-build-v2-$ref_scope-$content_id",
);
expect(runnerBuildJob).not.toContain(
"cache_key=runner-e2e-build-v1-$TARGET_SHA",
"cache_key=runner-e2e-build-v2-$TARGET_SHA",
);
expect(runnerBuildJob).toContain(
'"repos/$REPOSITORY/contents/.github/workflows/runner-full-stack-e2e.yml"',
@ -593,21 +593,38 @@ describe("public repository paid workflow security", () => {
"@paperclipai/paperclip-eval-kernel",
]);
for (const toolchainInput of [
"paperclip-runner/e2e-build-toolchain/v2",
"paperclip-runner/e2e-build-inputs/v2",
"AR CC CFLAGS",
"CARGO_ENCODED_RUSTFLAGS",
"CMAKE",
"CXX",
"NODE_OPTIONS",
"PKG_CONFIG",
"RANLIB",
"RUSTFLAGS",
"uname -srm",
'sha256sum /etc/os-release "$(command -v cc)"',
"runner-image-os=",
"runner-image-version=",
"dpkg-query --show --showformat=",
"package-set=%s",
'for tool in cc c++ ld ldd ar ranlib cmake pkg-config; do',
'tool_path="$(command -v "$tool")"',
'sha256sum "$tool_path"',
"node --version",
"pnpm --version",
"rustc -vV",
"cargo -Vv",
"cc --version",
"ld --version",
"ldd --version",
'"$tool" --version',
]) {
expect(runnerBuildJob).toContain(toolchainInput);
}
expect(runnerBuildJob).not.toContain(
"paperclip-runner/e2e-build-toolchain/v1",
);
expect(runnerBuildJob).not.toContain(
"paperclip-runner/e2e-build-inputs/v1",
);
expect(
runnerBuildJob.match(
/if: steps\.restore_build_cache\.outputs\.cache-hit != 'true'/gu,