mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-17 10:06:42 +02:00
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:
1 parent
0690801f13
commit
a5b16834c8
3 files changed
+134
-2
No files matched your search
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user