fix: pin ARM qualification helper to workflow source

Keep frozen product and reusable workflow tooling identities separate. Preserve clean-consumer and package integrity checks.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 13:26:56 -05:00
1 parent 0690801f13
commit a5b16834c8
3 files changed
+134 -2

No files matched your search

+40 -1
View File
@@ -308,10 +308,49 @@ jobs:
path: ${{ runner.temp }}/public-runner-packs
- name: Install only the existing verifier tooling
run: pnpm install --ignore-scripts --no-frozen-lockfile
- name: Checkout immutable ARM qualification tooling
if: ${{ matrix.target == 'linux-arm64' }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
# job.workflow_* identifies this reusable workflow, not its caller.
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
path: .qualification-tooling
sparse-checkout: scripts/prepare-linux-arm64-qualification.sh
sparse-checkout-cone-mode: false
persist-credentials: false
- name: Prepare Linux ARM sandbox prerequisites
if: ${{ matrix.target == 'linux-arm64' }}
timeout-minutes: 5
run: bash scripts/prepare-linux-arm64-qualification.sh
env:
SOURCE_SHA: ${{ inputs.qualification_source_sha }}
TOOLING_SHA: ${{ job.workflow_sha }}
TOOLING_REPOSITORY: ${{ job.workflow_repository }}
TOOLING_WORKFLOW_REF: ${{ job.workflow_ref }}
TOOLING_WORKFLOW_PATH: ${{ job.workflow_file_path }}
run: |
set -euo pipefail
[[ "$SOURCE_SHA" =~ ^[a-f0-9]{40}$ ]] || exit 1
[[ "$TOOLING_SHA" =~ ^[a-f0-9]{40}$ ]] || exit 1
[[ "$(git rev-parse HEAD)" == "$SOURCE_SHA" ]] || exit 1
[[ "$(git -C .qualification-tooling rev-parse HEAD)" == "$TOOLING_SHA" ]] || exit 1
node --input-type=module <<'NODE'
import { createHash } from 'node:crypto';
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const directory = join(process.env.RUNNER_TEMP, 'paperclip-arm-bwrap-prerequisite');
mkdirSync(directory, { recursive: true });
const helper = '.qualification-tooling/scripts/prepare-linux-arm64-qualification.sh';
writeFileSync(join(directory, 'qualification-identity.json'), JSON.stringify({
productSourceSha: process.env.SOURCE_SHA,
toolingSourceSha: process.env.TOOLING_SHA,
toolingRepository: process.env.TOOLING_REPOSITORY,
toolingWorkflowRef: process.env.TOOLING_WORKFLOW_REF,
toolingWorkflowPath: process.env.TOOLING_WORKFLOW_PATH,
helperSha256: createHash('sha256').update(readFileSync(helper)).digest('hex'),
}, null, 2) + '\n');
NODE
bash .qualification-tooling/scripts/prepare-linux-arm64-qualification.sh
- name: Verify host identity and actual clean npm consumer
env:
SOURCE_SHA: ${{ inputs.qualification_source_sha }}
@@ -2,6 +2,47 @@
Updated 2026-10-12.
## Latest checkpoint: ARM qualification tooling provenance
Production code remains frozen at `a200495cc7488f4d59da570407e516cf702e5af8`.
Subsequent changes are QA tooling and documentation. The verified 18-package
archive retains that literal source. All four hosted native producers, Linux x64
clean installation, and both Mac installed startup/cleanup consumers pass.
The qualified ARM native startup passes with stock scoped OS prerequisites;
the original unprepared-host failure remains recorded below.
At `0690801`, fresh review found one valid workflow compatibility defect: an
older frozen product source does not contain the new prerequisite helper. The
existing native qualification job now obtains only that helper through a sparse
checkout pinned to `job.workflow_repository` and `job.workflow_sha`. These
identify the workflow defining the current job, including reusable calls. The
product checkout, archive guards, and original native consumer remain unchanged.
Both checkout identities and the helper hash enter the existing diagnostic
artifact. No mutable branch or caller-source fallback is used.
Five focused controls pass, including execution with a product source that lacks
the helper and rejection of mismatched checkout identities. Syntax/YAML checks
pass. The full affected workflow suite passes all 70 controls with localhost
fixture access; it runs no native or Docker builds. Receipt:
`/private/tmp/codex-default-arm-immutable-tooling-fix-receipt.json`.
The [committed-helper consumer](https://github.com/paperclipai/paperclip/actions/runs/38290234153)
checks tooling `0690801` against literal `a200495` packages with no diagnostic
overlay, no native rebuild, and no provider turns. Final results and latest-head
CI/review remain required before sign-off.
The local preview serves `2026.1005.0+352.git.0690801f13` at
[3125](http://127.0.0.1:3125/COD/agents/all). All four complete agent configurations
and both historical native runs remain unchanged after restart. The downloaded
Apple Silicon daemon is verified; `needsBuild` and `sourceBuild` are false.
No local Dockerfile/native build or provider call ran.
Next action: freeze the workflow-only correction, prove its actual sparse
checkout on hosted ARM against the unchanged verified package archive, and
require fresh CI/review. Refresh the preview and PR evidence at that candidate.
Human code-owner approval, managed staging onboarding, and actual post-merge
Cloud composition remain outstanding. No merge or production deployment is
authorized. Historical unknown costs and cleanup holds remain reserved.
## Current amendment: remote build performance and Linux baseline
The user requires Cargo dependency caching and overlapping JavaScript/native
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { spawn, spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
@@ -967,13 +968,64 @@ test("native ARM qualification prepares its scoped distro sandbox before the exi
const preparation = native.split(" - name: Prepare Linux ARM sandbox prerequisites\n")[1].split("\n - ")[0];
assert.match(preparation, /if: \$\{\{ matrix\.target == 'linux-arm64' \}\}/);
assert.match(preparation, /timeout-minutes: 5/);
assert.match(preparation, /run: bash scripts\/prepare-linux-arm64-qualification\.sh/);
assert.match(preparation, /bash \.qualification-tooling\/scripts\/prepare-linux-arm64-qualification\.sh/);
const tooling = native.split(" - name: Checkout immutable ARM qualification tooling\n")[1].split("\n - ")[0];
assert.match(tooling, /if: \$\{\{ matrix\.target == 'linux-arm64' \}\}/);
assert.match(tooling, /repository: \$\{\{ job\.workflow_repository \}\}/);
assert.match(tooling, /ref: \$\{\{ job\.workflow_sha \}\}/);
assert.match(tooling, /sparse-checkout: scripts\/prepare-linux-arm64-qualification\.sh/);
assert.match(tooling, /sparse-checkout-cone-mode: false/);
assert.match(tooling, /persist-credentials: false/);
assert.doesNotMatch(tooling + preparation, /github\.workflow_sha|github\.sha|ref: (?:master|main)|ref: \$\{\{ inputs\.qualification_source_sha/);
assert.ok(native.indexOf("Checkout immutable ARM qualification tooling") < native.indexOf("Prepare Linux ARM sandbox prerequisites"));
assert.ok(native.indexOf("Prepare Linux ARM sandbox prerequisites") < native.indexOf("Verify host identity and actual clean npm consumer"));
assert.match(native, /path: \|\n\s+\$\{\{ runner\.temp \}\}\/native-public-install\.json\n\s+\$\{\{ runner\.temp \}\}\/paperclip-arm-bwrap-prerequisite\//);
assert.doesNotMatch(smoke.split(" qualification_daemons:\n")[0], /prepare-linux-arm64-qualification/,
"ordinary service and Docker smoke keep their existing setup");
});
test("ARM qualification binds separate product and reusable-workflow revisions even when the product lacks the helper", { skip: process.platform === "win32" }, () => {
const native = readWorkflow("release-smoke.yml").split(" qualification_native:\n")[1];
const step = native.split(" - name: Prepare Linux ARM sandbox prerequisites\n")[1].split("\n - ")[0];
const script = step.split(" run: |\n")[1].split("\n").map(line => line.startsWith(" ") ? line.slice(10) : line).join("\n");
const root = mkdtempSync(path.join(tmpdir(), "arm-separate-tooling-"));
try {
const tools = path.join(root, "tools"); mkdirSync(tools);
const tooling = path.join(root, ".qualification-tooling", "scripts"); mkdirSync(tooling, { recursive: true });
const helper = 'printf "%s\\n" "$TOOLING_SHA" > "$FIXTURE_PREPARED"\n';
writeFileSync(path.join(tooling, "prepare-linux-arm64-qualification.sh"), helper);
writeFileSync(path.join(tools, "git"), '#!/bin/bash\nif [[ "$1" == -C ]]; then printf "%s\\n" "$FIXTURE_TOOLING_HEAD"; else printf "%s\\n" "$FIXTURE_PRODUCT_HEAD"; fi\n', { mode: 0o755 });
const productSha = "a".repeat(40), toolingSha = "b".repeat(40);
for (const [productHead, toolingHead, expectedStatus] of [[productSha, toolingSha, 0], ["c".repeat(40), toolingSha, 1], [productSha, "c".repeat(40), 1], [productSha, "invalid", 1]]) {
const report = path.join(root, "report"); mkdirSync(report);
const prepared = path.join(root, "prepared");
const result = spawnSync("/bin/bash", ["-c", script], { cwd: root, encoding: "utf8", env: {
PATH: `${tools}:${path.dirname(process.execPath)}:/usr/bin:/bin`, RUNNER_TEMP: report,
SOURCE_SHA: productSha, TOOLING_SHA: toolingSha, TOOLING_REPOSITORY: "paperclipai/paperclip",
TOOLING_WORKFLOW_REF: "paperclipai/paperclip/.github/workflows/release-smoke.yml@refs/heads/qualification",
TOOLING_WORKFLOW_PATH: ".github/workflows/release-smoke.yml",
FIXTURE_PRODUCT_HEAD: productHead, FIXTURE_TOOLING_HEAD: toolingHead, FIXTURE_PREPARED: prepared,
// Reusable calls retain the caller context; it must not select tooling.
GITHUB_REPOSITORY: "different/caller", GITHUB_SHA: "d".repeat(40),
} });
assert.equal(result.status, expectedStatus, result.stderr);
assert.equal(existsSync(path.join(root, "scripts", "prepare-linux-arm64-qualification.sh")), false);
assert.equal(existsSync(prepared), expectedStatus === 0);
if (expectedStatus === 0) {
assert.equal(readFileSync(prepared, "utf8").trim(), toolingSha);
const identity = JSON.parse(readFileSync(path.join(report, "paperclip-arm-bwrap-prerequisite", "qualification-identity.json"), "utf8"));
assert.equal(identity.productSourceSha, productSha);
assert.equal(identity.toolingSourceSha, toolingSha);
assert.equal(identity.toolingRepository, "paperclipai/paperclip");
assert.equal(identity.toolingWorkflowPath, ".github/workflows/release-smoke.yml");
assert.equal(identity.helperSha256, createHash("sha256").update(helper).digest("hex"));
}
rmSync(report, { recursive: true, force: true });
rmSync(prepared, { force: true });
}
} finally { rmSync(root, { recursive: true, force: true }); }
});
test("Linux ARM prerequisite setup rejects other hosts before invoking tools and preserves scoped security", { skip: process.platform === "win32" }, () => {
const helper = path.join(repoRoot, "scripts/prepare-linux-arm64-qualification.sh");
const source = readFileSync(helper, "utf8");