ci(runner): build fresh Pi12 Intel daemon from frozen source

Pin the final source and profile, restore fresh Rust compilation with SDK provenance, and keep manual immutable-source checks independent.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-01 22:20:22 -05:00
1 parent 2b3924f91d
commit 9541eb1878
5 files changed
+163 -175

No files matched your search

+5 -15
View File
@@ -68,8 +68,9 @@ on:
permissions: {}
concurrency:
# Publishing a newer image must not discard an earlier verification's evidence.
group: docker-runner-check-${{ github.event.pull_request.number || github.ref }}-${{ inputs.diagnose_pi_startup && 'pi-startup-diagnostic' || inputs.verify_pi_intel && 'pi-native-intel' || inputs.diagnose_ajv_pack && 'ajv-diagnostic' || inputs.verify_runner && 'runner-verification' || inputs.source_e2e_support_only && 'source-e2e-support' || inputs.source_root_tests_only && 'source-root-tests' || inputs.verify_source && 'verification' || inputs.verify_public_install && 'public-install' || inputs.build_eval_viewer && 'viewer' || 'image' }}
# Different immutable manual sources retain independent evidence; the same
# source/mode still supersedes itself. Ordinary PR grouping is unchanged.
group: docker-runner-check-${{ github.event.pull_request.number || github.ref }}-${{ inputs.diagnose_pi_startup && 'pi-startup-diagnostic' || inputs.verify_pi_intel && 'pi-native-intel' || inputs.diagnose_ajv_pack && 'ajv-diagnostic' || inputs.verify_runner && 'runner-verification' || inputs.source_e2e_support_only && 'source-e2e-support' || inputs.source_root_tests_only && 'source-root-tests' || inputs.verify_source && 'verification' || inputs.verify_public_install && 'public-install' || inputs.build_eval_viewer && 'viewer' || 'image' }}${{ github.event_name == 'workflow_dispatch' && inputs.expected_source_sha && format('-{0}', inputs.expected_source_sha) || '' }}
cancel-in-progress: true
jobs:
@@ -176,7 +177,7 @@ jobs:
test "$SOURCE_ROOT_TESTS_ONLY" != true
test "$DIAGNOSE_AJV" != true
test "$IMAGE_MODE" != true
test "$EXPECTED_SOURCE_SHA" = efe019a79f50440d7bd6c3bc6c75fb8f18953093
test "$EXPECTED_SOURCE_SHA" = b9e5d6ecdb05ab7244c90976e07c950f8d09b15b
test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba
# Standard hosted runner minutes are free for this public repository.
# Artifact storage and unrelated resource costs are not inferred here.
@@ -230,24 +231,13 @@ jobs:
with:
version: 9.15.4
package_json_file: candidate/package.json
- name: Download exact historical native daemon evidence
timeout-minutes: 5
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh api repos/paperclipai/paperclip/actions/artifacts/11197947432/zip \
> "$RUNNER_TEMP/pi-native-daemon-36933598154.zip"
printf '%s %s\n' 2f9892bfa65318e3a61fd39f09fbe26374098eff6cfc3126fa1cba8df31b85c1 \
"$RUNNER_TEMP/pi-native-daemon-36933598154.zip" | shasum -a 256 --check
- name: Build frozen pack and run unchanged no-provider contracts once
timeout-minutes: 55
run: |
set -euo pipefail
python3 -B trusted-ci/scripts/ci/pi-intel/verify.py \
--source "$GITHUB_WORKSPACE/candidate" \
--output "$GITHUB_WORKSPACE/pi-intel-evidence" \
--daemon-archive "$RUNNER_TEMP/pi-native-daemon-36933598154.zip"
--output "$GITHUB_WORKSPACE/pi-intel-evidence"
- name: Admit complete evidence within the reserved storage bound
if: always()
id: pi_intel_evidence
+46 -147
View File
@@ -13,10 +13,10 @@
"digest": "sha256:48cecd8dc77a5533240fcf2f29d19be05380da4a79f8e5061480f94241db75a8"
},
"pi": {
"path": "packages/paperclip-runner/test-fixtures/pi-acp/profile-v11-identity.json",
"sha256": "7b253e94b1672dff0e7a4f4a50f3bb2270038d14f1ef27857f8bef67b9800a7f",
"version": 11,
"digest": "sha256:5e276f48c8a87b3e6165369faac62d3925282c84b98934575b1b7b97ad50b309"
"path": "packages/paperclip-runner/test-fixtures/pi-acp/profile-v12-identity.json",
"sha256": "99a6dd48b3da1c555cfbec7f42fe0cb1c00afe437b65a78db92f6b14d7828168",
"version": 12,
"digest": "sha256:47306e6d2a9b59e8f9189f725ebb7a0a7f91826044d1739e1a35ab31f228ba1f"
}
},
"candidateClosureDigests": {
@@ -31,26 +31,30 @@
"linux-x64": "sha256:a3d8f4367cfa1694d79e3f8b7930b6fbfe42a229c272b375b11951d631db8d07"
},
"pi": {
"darwin-arm64": "sha256:a3e43c23f9603983a59fd256ca1dcf8bbd5cc2728dad095f7bcd3b3e62278597",
"darwin-x64": "sha256:8c6e7802d47da9410af56e367f6cf7af572fcbf3e361627cee026e8cffc9bdd3",
"linux-x64": "sha256:40e186c53db60d1ab5673ba34f33002e93cea518725a93395d241393b984caef"
"darwin-arm64": "sha256:e17be4d27c589b8f8b5de7d00686c39873fe6f902a3f133bdaf1a9f94dee00a2",
"darwin-x64": "sha256:03351f4a250a8db0e79411a9079b43a0ff05f72a2aff41fae17f1fc2de24bd41",
"linux-x64": "sha256:29dfc829700c57392f1d56373078dcfc80674cbd50f65674ec64fced81dffb68"
}
},
"additionalSourceFiles": {
"packages/paperclip-runner/test/fixtures/copilot-message-identity-distributions-1.0.88.json": "7e1534d668a3db7acae15377b67d86fa80cc21ce699d021709b0b52ad314cfb2",
"patches/pi-acp@0.0.33.patch": "429184a462f9acef5bef890f3537db3ef385ee7c06d9fe0c335158590005ab0a",
"patches/pi-acp@0.0.33.patch": "93254de82ae779587611748cfb963475c3439e033f1582f06dfdcc7f7b937546",
"packages/paperclip-runner/scripts/pi-distribution/package.json": "c14f4050ef27dca91d3650e7446bc9927296a926224dba50a0e02f5d9fd4d95b",
"packages/paperclip-runner/scripts/pi-distribution/package-lock.json": "f4b04d51c4d91a6529dc97af2f3f84c28a220d93a54b5555826c4d9fd30f0939",
"packages/paperclip-runner/scripts/materialize-pi-distribution.mjs": "39bd8cc48791f8e54a953c7803334b965b9998943b0402008438d565ea2230be",
"packages/paperclip-runner/scripts/materialize-pi-distribution.mjs": "315e1123abdad82006caca1823e15ba2da0cb1cfe7f0f29534c3830e8bf0682b",
"packages/paperclip-runner/scripts/build-node-startup-timeout.mjs": "545dbaf7062b1cd54176dafe3feb8877e5ccf19406f791172a3ad47dbfd73fc4",
"packages/paperclip-runner/src/drivers/acpx/pi-acp-runtime.ts": "a0ec66a03cd9fa32e9aaafc4ac6840115883333000fd079ccba230706b763ac0",
"packages/paperclip-runner/src/drivers/acpx/pi-acp-runtime.ts": "95f74eb44a2e498fbd9461d363fac5b4d5fdae05b9666dca1ac4a632229dfef4",
"packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.ts": "f3446be10d984c368c52202e768aefe9b4a25968e09922e98e3f016c9dc0198e",
"packages/paperclip-runner/src/drivers/acpx/pi-verified-runtime.ts": "dfb5f6604da0abc307c4bb514e3dd47354f4b867be28fdeb64273c3c055b68c1",
"packages/paperclip-runner/src/drivers/acpx/pi-node-pins.ts": "ffc822aff64db315e461cbe44f03d704695de4a4296f643af79386c3556a1d46",
"packages/paperclip-runner/src/drivers/acpx/pi-closure-pins.ts": "0941f56ea13b8eb309a345d2a1513e0ec53d201c81c755b356565505bc1df905",
"packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts": "20185223c77ce6d5b67b270abdc0debc26166c7d3dd412a1ecea83f980429027",
"packages/paperclip-runner/package.json": "a87426b0184e114e46c6f4ddbf3f4a9727cd591cf0a5ca900efde42198190fd1",
"packages/paperclip-runner/src/drivers/acpx/pi-installation.ts": "d32de12609133c16bb78c559b98ac878fa792a1f336159d09f2c1948f887125e"
"packages/paperclip-runner/src/drivers/acpx/pi-closure-pins.ts": "9fdfcd7d7b0bdcbb75fe7d8f7e9228642f32d83bead9813d494b3ee133c2fc5e",
"packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts": "9760d924815554cb0f438c444e1f23a3608fca4a245344b25d15199b743515e5",
"packages/paperclip-runner/package.json": "b5be67eb3190ddaa9baf20a455fae62fc7722b764f89f2cdbdd02a6d6a9dcad3",
"packages/paperclip-runner/src/drivers/acpx/pi-installation.ts": "a9c4e9c3f003eebd38b35f89a112291ad0620aed57894c878ff6806593911fd7",
"packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.ts": "943ba0fb2d27d08b9b0d36778f08c7c0dbdb78893c7564babd4e67f67cf065fe",
"packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs": "3c90ed419b96a3484756954f3c2a8af8c4c8fc4ec5aa2a7725b9df36167dcd72",
"packages/paperclip-runner/rust-toolchain.toml": "e4c6d27e48d71df0ae66d84f3bc2d5df8cbf5c3cbb3225475a7852d94361a0c5",
"packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs": "116d2641e844ba28cd7496d56b44fa405ea8def8940d9e50e9d6b30603602581"
},
"permissionPolicySources": {
"packages/paperclip-runner/src/drivers/acpx/copilot-permission-context.ts": {
@@ -174,8 +178,8 @@
}
},
"localComparison": {
"sourceRevision": "efe019a79f50440d7bd6c3bc6c75fb8f18953093",
"sourceArchiveSha256": "587c0f60a30f0ca4b43361830cb611fcaa4a83f492cb7a9fd66b7908b45f72be",
"sourceRevision": "b9e5d6ecdb05ab7244c90976e07c950f8d09b15b",
"sourceArchiveSha256": "b32d8e32639e6527804fe11bfa1aaefcefa87fca31e9155499e6b9a28f69dbd4",
"trackedLockSha256": "e11d69fa8702a906c293c1cb307c71df90d3b1f1617b3c23ebf17fa9a87fec79",
"resolvedLockSha256": "38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba",
"nodeSha256": "7abcf39bd37ab251015337ff75304d7555f0d8e88c6e0fbf04bce8ce34636f49",
@@ -183,7 +187,7 @@
"piRuntimeVersion": "1.0.0",
"piServerVersion": "0.0.33",
"acpxVersion": "0.13.1",
"note": "Fresh W TypeScript/sidecar/provider pack; exact retained native Intel daemon from5eba after complete declared-input equality. Historical local pack and S/V outputs are not current outputs. Original Apple SDK/linker details were not retained; no equivalence or fresh-compilation claim.",
"note": "Fresh exact-source TypeScript, sidecar, native Intel Rust daemon and Pi12 provider pack. Historical local outputs are not current outputs; no old daemon import or native input-equivalence claim.",
"historicalLocalOutputs": {
"localPackDigest": "sha256:a7ae16b680c6fcaa9438faa46562ba31519bb0fb42e21b0a4b00e8936c462d89",
"localDaemonSha256": "c0e9acdd461d5efe4a10f9fd751e8a67beceede4d43593951d7079edad657044",
@@ -191,138 +195,33 @@
"sourceRevision": "5eba61ece929dcfb2b0c1761825a2d9e557c2554"
}
},
"nativeDaemonReuse": {
"schema": "paperclip.native-daemon-reuse/v1",
"originalBuildSource": "5eba61ece929dcfb2b0c1761825a2d9e557c2554",
"nativeInputEquivalentTo": "efe019a79f50440d7bd6c3bc6c75fb8f18953093",
"artifactRunId": "36933598154",
"artifactId": "11197947432",
"artifactName": "pi-native-intel-36933598154",
"artifactWorkflowRevision": "b58907578ed590f11b049f794f01ce0368df2676",
"artifactZipSha256": "2f9892bfa65318e3a61fd39f09fbe26374098eff6cfc3126fa1cba8df31b85c1",
"artifactZipBytes": 1076265811,
"sourceArchiveSha256": "f751f86b19701757d30501e39d47fe7d20a102e32f9e4c4df585074393e85237",
"selectedFiles": {
"source.tar": {
"sha256": "f751f86b19701757d30501e39d47fe7d20a102e32f9e4c4df585074393e85237",
"bytes": 141516800
},
"source-input-inventory.json": {
"sha256": "f95181f36e02e84b309f3c7b21831bc9e6480d4431c0b57a31aa5764ec966695",
"bytes": 1032076
},
"receipt.json": {
"sha256": "900fdd6a89a190bd93501f650749c72c7417ea392febd0d0cb4645eae8381736",
"bytes": 26160
},
"paperclip-runnerd": {
"sha256": "a0c7ace7ad0be9ac5b5f9c58749d9c452cd3a1ef143a38d5120d8e4f2bf82d6a",
"bytes": 32035568
},
"daemon-build.log": {
"sha256": "eea04dc2986eb3f451a0b2eeb76942eab6affb8643a6cc90eace5274a1cd5bb2",
"bytes": 18358
},
"daemon-metadata.log": {
"sha256": "a1b957a6ee5fad916252ba629b3c2ad85af8b54c1f006d5901f1614eb700ebfe",
"bytes": 452
},
"daemon-sign.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"rust-version-verbose.log": {
"sha256": "87c09aab5ad89ed5a2636cd4f577eb178a3afba7a9f71490b240845245725f04",
"bytes": 191
}
},
"sourceDeltaAllowed": [
"doc/evals.md",
"packages/paperclip-runner/scripts/materialize-pi-distribution.test.mjs",
"packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.test.ts",
"packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.ts",
"packages/paperclip-runner/src/drivers/acpx/pi-installation.ts",
"packages/paperclip-runner/src/drivers/acpx/pi-native-admission.test.ts",
"packages/paperclip-runner/src/drivers/acpx/pi-verified-runtime.test.ts",
"packages/paperclip-runner/src/drivers/acpx/pi-verified-runtime.ts",
"tests/runner-e2e/FIXTURES.md",
"tests/runner-e2e/README.md",
"tests/runner-e2e/catalog.test.ts",
"tests/runner-e2e/catalog.ts",
"tests/runner-e2e/failure-classifier.ts",
"tests/runner-e2e/pi-controls-evidence.test.ts",
"tests/runner-e2e/pi-file-evidence.test.ts",
"tests/runner-e2e/pi-file-evidence.ts",
"tests/runner-e2e/pi-native-cases.test.ts",
"tests/runner-e2e/pi-native-cases.ts",
"tests/runner-e2e/pi-native-flow.ts",
"tests/runner-e2e/pi-native-provider-death-flow.test.ts",
"tests/runner-e2e/pi-native-provider-death-flow.ts",
"tests/runner-e2e/pi-native-restart-flow.test.ts",
"tests/runner-e2e/pi-native-restart-flow.ts",
"tests/runner-e2e/pi-provider-fault.py",
"tests/runner-e2e/pi-provider-fault.test.py",
"tests/runner-e2e/pi-provider-fault.test.ts",
"tests/runner-e2e/remote-native-fixtures.test.ts",
"tests/runner-e2e/remote-native-fixtures.ts",
"tests/runner-e2e/runner.spec.ts",
"tests/runner-e2e/server-stop.test.ts"
"expectedDaemonBuildMetadata": {
"binaryContractVersion": 2,
"binaryName": "paperclip-runnerd",
"durableSessionCapabilities": [
"unlimited_runtime",
"connection_lease_renewal"
],
"compiler": {
"rustcSha256": "a1fd557be213adbcfe224e0e05e2903aa3f4b8d9294bed51d97a9d2a0b201e55",
"cargoSha256": "07816976dbe29da10bf127b75edcb0f5cd8b8652051b9c79709f8c0898d125d2",
"versionVerbose": "rustc 1.97.1 (8bab26f4f 2026-07-14)\nbinary: rustc\ncommit-hash: 8bab26f4f68e0e26f0bb7960be334d5b520ea452\ncommit-date: 2026-07-14\nhost: x86_64-apple-darwin\nrelease: 1.97.1\nLLVM version: 22.1.6\n",
"jobs": 2
"harnessDriverVersion": 1,
"nativeExecutionVersion": 1,
"packageName": "@paperclipai/paperclip-runner",
"packageVersion": "0.0.0",
"prp": {
"maximumVersion": 2,
"minimumVersion": 1,
"name": "paperclip.runner"
},
"daemonBuildMetadata": {
"binaryContractVersion": 2,
"binaryName": "paperclip-runnerd",
"durableSessionCapabilities": [
"unlimited_runtime",
"connection_lease_renewal"
],
"harnessDriverVersion": 1,
"nativeExecutionVersion": 1,
"packageName": "@paperclipai/paperclip-runner",
"packageVersion": "0.0.0",
"prp": {
"maximumVersion": 2,
"minimumVersion": 1,
"name": "paperclip.runner"
},
"prpTransportModes": [
"dial_ws_loopback",
"dial_wss",
"listen_ws"
],
"schema": "paperclip-runner/runnerd-build-metadata/v1"
},
"buildCommandTail": [
"build",
"--release",
"--target",
"x86_64-apple-darwin",
"--manifest-path",
"packages/paperclip-runner/runner/Cargo.toml",
"--locked",
"-p",
"paperclip-runner-core",
"--bin",
"paperclip-runnerd",
"-j",
"2"
"prpTransportModes": [
"dial_ws_loopback",
"dial_wss",
"listen_ws"
],
"originalVerifierSha256": "7de772df5beabe6413e2d0d8dd287017ef2be8d3b3cdc76e8a23007ba547b509",
"missingOriginalToolchainEvidence": [
"Apple SDK version",
"Apple linker/compiler identity",
"full Rust compiler sysroot inventory"
],
"claims": {
"freshNativeCompilation": false,
"currentAppleToolchainEquivalent": false,
"completeDeclaredNativeSourceConfigProtocolEquality": true,
"originalCompilerProvenanceRetained": true
}
"schema": "paperclip-runner/runnerd-build-metadata/v1"
},
"freshNativeBuild": {
"toolchain": "1.97.1",
"target": "x86_64-apple-darwin",
"jobs": 2,
"deadlineSeconds": 1500
}
}
+63 -1
View File
@@ -6,13 +6,75 @@ import subprocess
import tarfile
import tempfile
import unittest
import json
import re
from unittest.mock import Mock, patch
from lifecycle import CleanupUncertain, Lifecycle, run_test_cases
from retain_pack import retain_pack, verify_archive
from closed_inventory import closed_tree
from source_guard import tree, sha, capture_authority, verify_authority, verify_source, ASSETS
from admit_evidence import admit_evidence, regular_bytes
from verify import tool_invocation, verify_tool_invocation
from verify import tool_invocation, verify_tool_invocation, build_native_daemon, INPUTS, SOURCE
class FreshNativeBuildRegression(unittest.TestCase):
"""Mock command boundaries only: no compiler, platform tool or child launch."""
def setUp(self):
self.temp=tempfile.TemporaryDirectory();self.addCleanup(self.temp.cleanup)
self.root=Path(self.temp.name).resolve();self.stage=self.root/'stage';self.stage.mkdir()
self.scratch=self.root/'scratch';self.scratch.mkdir();self.sdk=self.root/'sdk';self.sdk.mkdir()
(self.sdk/'SDKSettings.json').write_text('{"Version":"fixture"}')
self.tools={}
for name in ('rustup','cargo','rustc','clang','ld'):
path=self.root/name;path.write_text('fixture-only '+name);path.chmod(0o700);self.tools[name]=path
self.audit={'invocationPath':str(self.tools['rustup']),'resolvedTarget':str(self.tools['rustup']),'targetSha256':sha(self.tools['rustup'])}
self.env={'CARGO_TARGET_DIR':str(self.scratch/'target')};self.receipt={};self.calls=[];self.fail_label=None;self.bad_metadata=False
def run_command(self,command,label,timeout):
self.calls.append((label,list(map(str,command)),timeout))
if label==self.fail_label:raise RuntimeError('injected command failure')
values={'cargo-path':str(self.tools['cargo']),'rustc-path':str(self.tools['rustc']),
'rust-version-verbose':'rustc 1.97.1 fixture\nhost: x86_64-apple-darwin', 'cargo-version':'cargo 1.97.1 fixture',
'sdk-path':str(self.sdk),'sdk-version':'fixture','clang-path':str(self.tools['clang']),'ld-path':str(self.tools['ld']),
'daemon-architecture':'Mach-O 64-bit executable x86_64',
'daemon-metadata':json.dumps({} if self.bad_metadata else INPUTS['expectedDaemonBuildMetadata'])}
if label=='daemon-build':
p=self.scratch/'target/x86_64-apple-darwin/release/paperclip-runnerd';p.parent.mkdir(parents=True);p.write_bytes(b'newly-compiled-fixture');p.chmod(0o755)
return values.get(label,'')
def build(self):
with patch('verify.tool_invocation',return_value=(self.tools['rustup'],self.audit)):
return build_native_daemon(self.stage,self.scratch,self.env,self.run_command,self.receipt,lambda:None)
def test_fresh_compiler_recipe_and_output_provenance(self):
daemon=self.build();self.assertEqual(daemon.read_bytes(),b'newly-compiled-fixture')
self.assertTrue(self.receipt['nativeRecompiled']);self.assertFalse(self.receipt['nativeDaemonReuse']);self.assertEqual(self.receipt['nativeBuildSource'],SOURCE)
build=next(c for c in self.calls if c[0]=='daemon-build')
self.assertEqual(build[2],1500);self.assertEqual(build[1][1:],['build','--release','--target','x86_64-apple-darwin','--manifest-path','packages/paperclip-runner/runner/Cargo.toml','--locked','-p','paperclip-runner-core','--bin','paperclip-runnerd','-j','2'])
self.assertEqual(self.env['RUSTC'],str(self.tools['rustc']))
self.assertEqual(self.receipt['compiler']['sdk']['settings']['SDKSettings.json'],sha(self.sdk/'SDKSettings.json'))
labels=[c[0] for c in self.calls];self.assertLess(labels.index('daemon-sign'),labels.index('daemon-signature-verify'));self.assertLess(labels.index('daemon-signature-verify'),labels.index('daemon-metadata'))
def test_compiler_failure_cannot_produce_or_claim_fresh_daemon(self):
self.fail_label='daemon-build'
with self.assertRaisesRegex(RuntimeError,'injected'):self.build()
self.assertNotIn('nativeRecompiled',self.receipt);self.assertFalse((self.stage/'packages/paperclip-runner/dist/bin/paperclip-runnerd').exists())
self.assertNotIn('daemon-sign',[c[0] for c in self.calls])
def test_wrong_metadata_cannot_claim_fresh_success(self):
self.bad_metadata=True
with self.assertRaisesRegex(RuntimeError,'build metadata'):self.build()
self.assertNotIn('nativeRecompiled',self.receipt)
def test_missing_sdk_evidence_stops_before_build(self):
(self.sdk/'SDKSettings.json').unlink()
with self.assertRaisesRegex(RuntimeError,'SDK settings'):self.build()
self.assertNotIn('daemon-build',[c[0] for c in self.calls])
def test_unreviewed_recipe_stops_before_commands(self):
with patch.dict(INPUTS,{'freshNativeBuild':dict(INPUTS['freshNativeBuild'],jobs=8)}):
with self.assertRaisesRegex(RuntimeError,'recipe'):self.build()
self.assertEqual(self.calls,[])
def test_pack_verifier_and_source_profile_authorities_agree(self):
verifier=(Path(__file__).parent/'verify-pack.mjs').read_text()
block=re.search(r'const pins = \{([^}]+)\};',verifier)
self.assertIsNotNone(block)
pins=dict(re.findall(r"(\w+): '([a-f0-9]{64})'",block.group(1)))
self.assertEqual({agent:'sha256:'+digest for agent,digest in pins.items()},
{agent:profile['digest'] for agent,profile in INPUTS['profiles'].items()})
class CleanupRegression(unittest.TestCase):
def test_first_cleanup_failure_prevents_second_test_and_scratch_removal(self):
+1 -1
View File
@@ -47,7 +47,7 @@ export function verifyPack(pack, source, platform, architecture) {
requireTrue(manifest.digest === `sha256:${createHash('sha256').update(canonicalJson(p)).digest('hex')}`, 'Canonical manifest mismatch');
requireTrue(p.runnerSourceRevision === source, 'Wrong source');
requireTrue(p.target.platform === platform && p.target.architecture === architecture, 'Wrong target');
const pins = { cursor: '1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564', copilot: '48cecd8dc77a5533240fcf2f29d19be05380da4a79f8e5061480f94241db75a8', pi: '5e276f48c8a87b3e6165369faac62d3925282c84b98934575b1b7b97ad50b309' };
const pins = { cursor: '1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564', copilot: '48cecd8dc77a5533240fcf2f29d19be05380da4a79f8e5061480f94241db75a8', pi: '47306e6d2a9b59e8f9189f725ebb7a0a7f91826044d1739e1a35ab31f228ba1f' };
requireTrue(Object.keys(p.candidateProviders).sort().join(',') === Object.keys(pins).sort().join(','), 'Wrong candidate set');
const closurePins = JSON.parse(readFileSync(new URL('./profile-inputs.json', import.meta.url))).candidateClosureDigests;
const checks = [];
+48 -11
View File
@@ -7,7 +7,6 @@ from owned_processes import OwnedProcesses,atomic_json
import source_guard
from lifecycle import Lifecycle, run_test_cases
from retain_pack import retain_pack
from native_daemon_reuse import import_daemon
HERE=Path(__file__).resolve().parent
INPUTS=json.loads((HERE/'profile-inputs.json').read_text())
@@ -29,6 +28,51 @@ def verify_tool_invocation(invocation, audit):
require(str(invocation)==audit['invocationPath'] and str(invocation.resolve(strict=True))==audit['resolvedTarget'], 'Executable invocation or target changed')
require(sha(invocation.resolve(strict=True))==audit['targetSha256'],'Executable target bytes changed')
def build_native_daemon(stage, scratch, env, run, receipt, save):
"""Compile this source; historical artifact bytes are never an input."""
pin=INPUTS['freshNativeBuild']
require(pin=={'toolchain':'1.97.1','target':'x86_64-apple-darwin','jobs':2,'deadlineSeconds':1500},'Unexpected native build recipe')
rustup,audit=tool_invocation('rustup')
verify_tool_invocation(rustup,audit)
receipt['rustup']=audit;save()
run([rustup,'toolchain','install',pin['toolchain'],'--profile','minimal','--component','rustfmt'],'rust-install',600)
verify_tool_invocation(rustup,audit)
cargo=Path(run([rustup,'which','--toolchain',pin['toolchain'],'cargo'],'cargo-path',30).strip()).resolve(strict=True)
rustc=Path(run([rustup,'which','--toolchain',pin['toolchain'],'rustc'],'rustc-path',30).strip()).resolve(strict=True)
env['RUSTC']=str(rustc)
version=run([rustc,'--version','--verbose'],'rust-version-verbose',30)
require(version.startswith('rustc 1.97.1 '),'Wrong Rust')
cargo_version=run([cargo,'--version'],'cargo-version',30)
require(cargo_version.startswith('cargo 1.97.1 '),'Wrong Cargo')
sdk=Path(run(['/usr/bin/xcrun','--show-sdk-path'],'sdk-path',30).strip()).resolve(strict=True)
require(sdk.is_dir(),'Apple SDK missing')
sdk_version=run(['/usr/bin/xcrun','--show-sdk-version'],'sdk-version',30).strip()
require(sdk_version,'Apple SDK version missing')
env['SDKROOT']=str(sdk)
tools={}
for name in ('clang','ld'):
path=Path(run(['/usr/bin/xcrun','--find',name],name+'-path',30).strip()).resolve(strict=True)
require(path.is_file() and os.access(path,os.X_OK),'Apple tool missing: '+name)
tools[name]={'path':str(path),'sha256':sha(path)}
settings={p.name:sha(p) for p in (sdk/'SDKSettings.json',sdk/'SDKSettings.plist') if p.is_file()}
require(settings,'Apple SDK settings missing')
receipt['compiler']={'rustcSha256':sha(rustc),'cargoSha256':sha(cargo),'versionVerbose':version,'cargoVersion':cargo_version,'jobs':2,
'sdk':{'path':str(sdk),'version':sdk_version,'settings':settings},'appleTools':tools,
'target':pin['target'],'rustflags':env.get('RUSTFLAGS',''),'targetDirectory':env['CARGO_TARGET_DIR']}
save()
run([cargo,'build','--release','--target',pin['target'],'--manifest-path','packages/paperclip-runner/runner/Cargo.toml','--locked',
'-p','paperclip-runner-core','--bin','paperclip-runnerd','-j','2'],'daemon-build',pin['deadlineSeconds'])
daemon=stage/'packages/paperclip-runner/dist/bin/paperclip-runnerd';daemon.parent.mkdir(parents=True,exist_ok=True)
shutil.copy2(scratch/'target'/pin['target']/'release/paperclip-runnerd',daemon)
run(['/usr/bin/codesign','--force','--sign','-',daemon],'daemon-sign',30)
run(['/usr/bin/codesign','--verify','--strict',daemon],'daemon-signature-verify',30)
require('Mach-O 64-bit executable x86_64' in run(['/usr/bin/file',daemon],'daemon-architecture',30),'Wrong daemon architecture')
receipt['daemonBuildMetadata']=json.loads(run([daemon,'--build-metadata'],'daemon-metadata',30))
require(receipt['daemonBuildMetadata']==INPUTS['expectedDaemonBuildMetadata'],'Wrong daemon build metadata')
receipt.update(nativeRecompiled=True,nativeDaemonReuse=False,nativeBuildSource=SOURCE,daemonSha256=sha(daemon))
save()
return daemon
def execute(args):
stage=args.source.resolve(strict=True);out=args.output.resolve()
out.mkdir(mode=0o700,parents=True,exist_ok=False)
@@ -45,7 +89,6 @@ def execute(args):
inherited_path=os.environ['PATH']
node=Path(shutil.which('node')).resolve(strict=True)
pnpm=Path(shutil.which('pnpm')).resolve(strict=True)
# Reused daemon bytes retain their original compiler provenance; no new compiler is invoked.
env={'PATH':inherited_path,'HOME':str(scratch/'home'),'TMPDIR':str(scratch),'LANG':'en_US.UTF-8','CI':'true',
'PAPERCLIP_TELEMETRY_ENABLED':'false','PAPERCLIP_RUNNER_SOURCE_REVISION':SOURCE,
'CARGO_HOME':str(scratch/'cargo'),'RUSTUP_HOME':str(scratch/'rustup'),'CARGO_TARGET_DIR':str(scratch/'target'),
@@ -129,13 +172,7 @@ def execute(args):
run([pnpm,'install','--frozen-lockfile','--ignore-scripts','--package-import-method','copy'],'install',600)
source_guard.verify_source(stage,SOURCE,PIN['resolvedLockSha256'])
run([pnpm,'--filter','@paperclipai/paperclip-runner','build:typescript'],'typescript',600)
daemon=stage/'packages/paperclip-runner/dist/bin/paperclip-runnerd'
receipt['nativeDaemonReuse']=import_daemon(args.daemon_archive,out/'source.tar',out/'reused-native-evidence',daemon,INPUTS['nativeDaemonReuse']);save()
run(['/usr/bin/codesign','--verify','--strict',daemon],'daemon-signature-verify',30)
require(sha(daemon)==INPUTS['nativeDaemonReuse']['selectedFiles']['paperclip-runnerd']['sha256'],'Daemon changed during signature verification')
require('Mach-O 64-bit executable x86_64' in run(['/usr/bin/file',daemon],'daemon-architecture',30),'Wrong daemon architecture')
receipt['daemonBuildMetadata']=json.loads(run([daemon,'--build-metadata'],'daemon-metadata',30))
require(receipt['daemonBuildMetadata']==INPUTS['nativeDaemonReuse']['daemonBuildMetadata'],'Reused daemon metadata changed')
daemon=build_native_daemon(stage,scratch,env,run,receipt,save)
pack=scratch/'provider-pack'
run([node,'packages/paperclip-runner/scripts/build-provider-pack.mjs',pack,'--candidate-providers=pi,copilot,cursor'],'pack-build',600)
verified=json.loads(run([node,HERE/'verify-pack.mjs',pack,SOURCE,'darwin','x64'],'pack-verify',180))
@@ -143,7 +180,7 @@ def execute(args):
authority=source_guard.capture_authority(stage,pack)
source_guard.verify_source(stage,SOURCE,PIN['resolvedLockSha256'],pack,authority)
atomic_json(out/'pack-inventory.json',closed_tree(pack));shutil.copy2(pack/'provider-pack.json',out/'provider-pack.json');shutil.copy2(daemon,out/'paperclip-runnerd')
receipt.update(packVerification=verified,packManifestSha256=sha(out/'provider-pack.json'),packInventorySha256=sha(out/'pack-inventory.json'),daemonSha256=sha(daemon),nodeSha256=sha(node),declaredNativeInputsMatchOriginal=True,outputDigestsAssumedEqual=False)
receipt.update(packVerification=verified,packManifestSha256=sha(out/'provider-pack.json'),packInventorySha256=sha(out/'pack-inventory.json'),daemonSha256=sha(daemon),nodeSha256=sha(node),outputDigestsAssumedEqual=False)
receipt['providerPackArchive']=retain_pack(pack,out/'provider-pack.tar.gz',authority['pack'])
save()
test=stage/'packages/paperclip-runner/test/pi-closed-startup.test.mjs'
@@ -175,4 +212,4 @@ def execute(args):
receipt['finishedAt']=datetime.datetime.now(datetime.timezone.utc).isoformat();save()
if __name__=='__main__':
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--source',type=Path,required=True);parser.add_argument('--output',type=Path,required=True);parser.add_argument('--daemon-archive',type=Path,required=True);execute(parser.parse_args())
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--source',type=Path,required=True);parser.add_argument('--output',type=Path,required=True);execute(parser.parse_args())