diff --git a/.github/workflows/docker-runner-check.yml b/.github/workflows/docker-runner-check.yml index 7958f3b7a8..5fdc65ad62 100644 --- a/.github/workflows/docker-runner-check.yml +++ b/.github/workflows/docker-runner-check.yml @@ -234,7 +234,11 @@ jobs: test "$SOURCE_E2E_SUPPORT_ONLY" != true test "$DIAGNOSE_AJV" != true test "$IMAGE_MODE" != true + if [ "$PI_STARTUP_OVERLAP" = true ]; then test "$EXPECTED_SOURCE_SHA" = f5c5fde380f60937ef26cc5a92e1d6a043e9cddc + else + test "$EXPECTED_SOURCE_SHA" = bba63f51207de8513ad2d9593694f718fc60ef17 + fi test "$EXPECTED_LOCK_SHA256" = 38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba test "$(gh api "repos/$REPOSITORY" --jq '.visibility')" = public fi @@ -380,7 +384,7 @@ jobs: if-no-files-found: error manual_pi_startup_diagnostic: - name: Retained f5 profile13 native Intel startup timing diagnostic (not qualification) + name: Retained bba profile13 native Intel startup timing diagnostic (not qualification) if: github.event_name == 'workflow_dispatch' && inputs.diagnose_pi_startup && !inputs.pi_startup_overlap needs: authorize_manual runs-on: macos-15-intel @@ -401,16 +405,16 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - gh api repos/paperclipai/paperclip/actions/artifacts/11218057612/zip \ - > "$RUNNER_TEMP/pi-startup-36984851998.zip" - printf '%s %s\n' 5159dcb57b380648679d441a73b87d50334dcca969adc95150e8429a71cbdfeb \ - "$RUNNER_TEMP/pi-startup-36984851998.zip" | shasum -a 256 --check + gh api repos/paperclipai/paperclip/actions/artifacts/11226760000/zip \ + > "$RUNNER_TEMP/pi-startup-37004499595.zip" + printf '%s %s\n' 9bc17434eb15b1f38b29b42b8ef95a477520a097a060ac1af8dc916d10d14d92 \ + "$RUNNER_TEMP/pi-startup-37004499595.zip" | shasum -a 256 --check - name: Run one instrumented startup with original assertions and deadlines timeout-minutes: 18 run: | set -euo pipefail python3 -B trusted-ci/scripts/ci/pi-intel/startup_diagnostic.py \ - --archive "$RUNNER_TEMP/pi-startup-36984851998.zip" \ + --inputs bba --archive "$RUNNER_TEMP/pi-startup-37004499595.zip" \ --output "$GITHUB_WORKSPACE/pi-startup-diagnostic-evidence" - name: Admit complete diagnostic evidence within the storage bound if: always() diff --git a/scripts/ci/pi-intel/startup-diagnostic-bba-inputs.json b/scripts/ci/pi-intel/startup-diagnostic-bba-inputs.json new file mode 100644 index 0000000000..c88e48950e --- /dev/null +++ b/scripts/ci/pi-intel/startup-diagnostic-bba-inputs.json @@ -0,0 +1,243 @@ +{ + "schema": "paperclip.native-intel-startup-diagnostic-input/v1", + "sourceRevision": "bba63f51207de8513ad2d9593694f718fc60ef17", + "resolvedLockSha256": "38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba", + "artifactRunId": "37004499595", + "artifactId": 11226760000, + "artifactWorkflowRevision": "f07200505bebc3db4dd981184d10d383298e0bba", + "artifactZipBytes": 699302565, + "artifactZipSha256": "9bc17434eb15b1f38b29b42b8ef95a477520a097a060ac1af8dc916d10d14d92", + "selectedFiles": { + "receipt.json": { + "sha256": "52be6ef06049ea8ca999daea11bbef8e07fe3c23676c08dc7eec5c96594d7d9a", + "bytes": 26369 + }, + "source.tar": { + "sha256": "38ae0523faace6d7797f8cfd5c4b0a018260ce421677e250fb35fbcc0ec8c14e", + "bytes": 142090240 + }, + "source-input-inventory.json": { + "sha256": "0c89bf9990b4eacc5c7313bde5c1f8ff6895919add44e5cf72b10d9a0823b5f7", + "bytes": 1036377 + }, + "resolved-pnpm-lock.yaml": { + "sha256": "38338a6867358440c5ab5993eaeb85fb501bc7df65d24acf52fbd63c880ee4ba", + "bytes": 607788 + }, + "original-pnpm-lock.yaml": { + "sha256": "e11d69fa8702a906c293c1cb307c71df90d3b1f1617b3c23ebf17fa9a87fec79", + "bytes": 607788 + }, + "provider-pack.json": { + "sha256": "7fda16213dbad0ccad99f014ab4e2d5162f84cff4644c1931d16f93bd208f974", + "bytes": 2743 + }, + "pack-inventory.json": { + "sha256": "a6f17cf2ab692235f03427f9763bf48403d8940e86ad642dc3c91eb5e25dc656", + "bytes": 10353069 + }, + "paperclip-runnerd": { + "sha256": "8a65b041bff62f8324237b4a1eb9035865eb445cd24004c8d840c3ef82123dbb", + "bytes": 32072480 + }, + "provider-pack.tar.gz": { + "sha256": "f9ff172df22f02f15fd646b8b1f9f3fdb7c17c5ed398d3fe0788f07f98abf343", + "bytes": 648284591 + }, + "hardware.log": { + "sha256": "4404f16e6c781a34b3aa80daf339839b06cf53740452fb6951e8bb33c77efaeb", + "bytes": 119 + }, + "node.log": { + "sha256": "3f7282f09fc0b20115d70e05037efe8b47e44a8d612e433b1c34a131a72560fe", + "bytes": 28 + }, + "pnpm.log": { + "sha256": "aa4b6073295967fb7e7e4f8b415ae112cffbd0fe5aa20c6edb02e3b6c4935684", + "bytes": 7 + }, + "npm-version.log": { + "sha256": "005206ab803f8881d8227540b7980b37e723b69a8849edb31734a90b7e7964e2", + "bytes": 8 + }, + "install.log": { + "sha256": "6133fce1367f5aaed4f953bbb670dc618a6fec87ca26c768f0521cd567df11f4", + "bytes": 12506 + }, + "typescript.log": { + "sha256": "3f7138c4b56663d93ed15024f25e9daff02f8a61ec0232630c46d1026b37dbb1", + "bytes": 1178 + }, + "rust-install.log": { + "sha256": "ad4589c40b796b55d0b530528ea40efdc3dea2f8af0ab76014123b2d36733657", + "bytes": 445 + }, + "cargo-path.log": { + "sha256": "d02696ca67c6148d2f065093f5d978d996ac24f7cf7e2dbac6a62fbdc4dcb5c8", + "bytes": 86 + }, + "rustc-path.log": { + "sha256": "6f9d4203e3658d8739cc44d23313c3a57cea3637c3262bd519eabae57542c16f", + "bytes": 86 + }, + "rust-version-verbose.log": { + "sha256": "87c09aab5ad89ed5a2636cd4f577eb178a3afba7a9f71490b240845245725f04", + "bytes": 191 + }, + "cargo-version.log": { + "sha256": "1aedaa9cce116919d39b1eb303a58865771582bc24fc259aa8e26cb212e0f59e", + "bytes": 36 + }, + "sdk-path.log": { + "sha256": "e6006a0ded501fa927f656931e70c3690852ee98f775d137c30eab661cc8f99f", + "bytes": 100 + }, + "sdk-version.log": { + "sha256": "521fbd942d19d4f08ee3ac22fe5e1fee54d05fb48db0d8d179461b8195d210eb", + "bytes": 5 + }, + "clang-path.log": { + "sha256": "49d8f9f4284933d2d9de7c39957c598bf6ee3ff73475761fce9c8cdcd62a62b3", + "bytes": 98 + }, + "ld-path.log": { + "sha256": "e5433141a9dcd84e4f6d50914971357e48533fa22def24382800d81a2edcb6a8", + "bytes": 95 + }, + "daemon-build.log": { + "sha256": "5f2d3b993c11f3b2ef3761a282a31e502bf1b6977e3c44fa80f5b07ad49654a5", + "bytes": 18358 + }, + "daemon-sign.log": { + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "bytes": 0 + }, + "daemon-signature-verify.log": { + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "bytes": 0 + }, + "daemon-architecture.log": { + "sha256": "786645000d0cc7f1656e956bfa6e23f2932b6684893ffd3a35ee199f4c2b61df", + "bytes": 135 + }, + "daemon-metadata.log": { + "sha256": "a1b957a6ee5fad916252ba629b3c2ad85af8b54c1f006d5901f1614eb700ebfe", + "bytes": 452 + }, + "pack-build.log": { + "sha256": "a0aaacad24a26e218efeb05fed91a84279dbdb5c86dfdf16bf70eea1206fde80", + "bytes": 2790 + }, + "pack-verify.log": { + "sha256": "f75281a60aa9c9b8a8ea5c64fa3fc9dc18af8faabe1eff0e9c806ba71c312fff", + "bytes": 1825 + } + }, + "originalSidecarSha256": "c7c331492378017d474ab732648dc96c0ece08466983e11be795d60abb9ed99c", + "originalPackDigest": "sha256:96bd73cd7235cdfd082444db2522fe24f550b54f6cfae17edd0cf63f46ba68e6", + "daemonSha256": "8a65b041bff62f8324237b4a1eb9035865eb445cd24004c8d840c3ef82123dbb", + "nodeSha256": "7abcf39bd37ab251015337ff75304d7555f0d8e88c6e0fbf04bce8ce34636f49", + "testPath": "packages/paperclip-runner/test/pi-closed-startup.test.mjs", + "testSha256": "32bb057e1505082c0ed6b1d71cdd8cdb3271d920c2bea9cdc1ebddedf568c0c4", + "observerSha256": "1fcf353beb90722f90401a7792a5853bf0807d964a1065e3f55b76a63bc4b4cc", + "profileDigest": "sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177", + "closureDigest": "sha256:4728e5a4e7fc1ba602c3e219824fb84884b05a06cdd19dd3e521ee312e1b373a", + "model": "openrouter/deepseek/deepseek-v4-flash-0731", + "originalFailure": "Original build and pack passed; initial canonical test was interrupted by 2s process inspection. Exact retained follow-up 37012371475 then failed session.open at original30s, close failure secondary, SDK63 passed; full post-integrity and cleanup passed. No runtime retry or qualification claim from this timing-only diagnostic.", + "executionCount": 1, + "providerCalls": 0, + "timeoutChanges": false, + "archiveBytesMaximum": 268435456, + "retentionDays": 7, + "profileVersion": 13, + "phaseLimit": "Native get_state is internal to unchanged wrapper; ACP new/set spans include it but do not identify its internal duration.", + "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", + "cargoVersion": "cargo 1.97.1 (c980f4866 2026-06-30)\n", + "jobs": 2, + "sdk": { + "path": "/Applications/Xcode_16.4.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk", + "version": "15.5", + "settings": { + "SDKSettings.json": "58a133735f0a55a624a1703067059f6e78925e51725ec6e1f966072e142c9c42", + "SDKSettings.plist": "30bf3c8ef32f46d43f68eb91a85eded3e1763edc752a6fd71633eecac4909b28" + } + }, + "appleTools": { + "clang": { + "path": "/Applications/Xcode_16.4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang", + "sha256": "4c458256bcdf913774de1bb4a37768244d3b41f32ad55afb2dfec3432f46f4fe" + }, + "ld": { + "path": "/Applications/Xcode_16.4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld", + "sha256": "648a8aac7f6f829a42305d24dddd7a3cfa48ee0bd9d4e7ec1ded5c51edea372b" + } + }, + "target": "x86_64-apple-darwin", + "rustflags": "", + "targetDirectory": "/private/tmp/pc-intel-5dwslosd/target" + }, + "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" + ], + "originalVerifierSha256": "5cbfbb4f3009f2644eef3801777b52a1845b8ea7f42f838334b51b37c5f8efe1", + "testSourceClosure": { + "packages/paperclip-runner/test/pi-closed-startup.test.mjs": "32bb057e1505082c0ed6b1d71cdd8cdb3271d920c2bea9cdc1ebddedf568c0c4", + "packages/paperclip-runner/test/pi-native-package-contract.test.mjs": "cdeb1b9c5b9363e0cca4ed475f3622251bf514b42d16a3e200b4f6d6d671d24f", + "packages/paperclip-runner/src/drivers/acpx/pi-runtime-extension.ts": "f3446be10d984c368c52202e768aefe9b4a25968e09922e98e3f016c9dc0198e", + "packages/paperclip-runner/src/drivers/acpx/pi-acp-runtime.ts": "95f74eb44a2e498fbd9461d363fac5b4d5fdae05b9666dca1ac4a632229dfef4", + "packages/paperclip-runner/test/pi-acp-package-contract.test.mjs": "b4a7b7dd0887f3d1daa8cb339a73f47cb88ef01857c7a38e14d83ae069f093ba", + "packages/paperclip-runner/test-fixtures/pi-acp/fake-pi.mjs": "edc945b4636dea6c0ff67d3320a50fbdd3da67bdf53cd4d7f18b3b3573ac8bb2" + }, + "expectedTests": { + "closed-startup": 1, + "native-extension-contracts": 63 + }, + "packVerifierSha256": "12380ee6070908dda3c54c78db9dea4bf0768b50b7d369a51f9d4a913aa0de82", + "normalProviderSelection": { + "pi": { + "qualification": "qualified", + "profileVersion": 13, + "profileDigest": "sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177" + } + }, + "repairedObserver": true, + "compilerProvenanceValidated": true +} diff --git a/scripts/ci/pi-intel/startup_diagnostic.py b/scripts/ci/pi-intel/startup_diagnostic.py index cf1888a97b..4aa37fea4d 100644 --- a/scripts/ci/pi-intel/startup_diagnostic.py +++ b/scripts/ci/pi-intel/startup_diagnostic.py @@ -95,6 +95,8 @@ def finalize_diagnostic(proof,active,post_check,retain,remove): raise RuntimeError('; '.join(errors)) def execute(args): + global PIN + if getattr(args,'inputs','f5')=='bba':PIN=json.loads((HERE/'startup-diagnostic-bba-inputs.json').read_text()) out=args.output.resolve();out.mkdir(mode=0o700,parents=True,exist_ok=False) scratch=Path(tempfile.mkdtemp(prefix='pc-intel-diagnostic-',dir='/private/tmp'));scratch.chmod(0o700) scratch_id=(scratch.stat().st_dev,scratch.stat().st_ino,scratch.stat().st_uid) @@ -121,9 +123,14 @@ def execute(args): require('GenuineIntel' in hardware and 'x86_64' in hardware,'Intel hardware evidence missing') original=extract_selected(args.archive,scratch/'original-artifact',PIN) references=out/'original-reference';references.mkdir(mode=0o700) - for name in ['receipt.json','provider-pack.json','pack-inventory.json','source-input-inventory.json','resolved-pnpm-lock.yaml']:shutil.copy2(original/name,references/name) + for name in PIN['selectedFiles']: + if name not in {'provider-pack.tar.gz','paperclip-runnerd','source.tar'}:shutil.copy2(original/name,references/name) atomic_json(out/'original-input-pins.json',PIN) - receipt=json.loads((original/'receipt.json').read_text());require(receipt['sourceRevision']==PIN['sourceRevision'] and receipt['runId']==PIN['artifactRunId'] and receipt['trustedWorkflowRevision']==PIN['artifactWorkflowRevision'],'Original artifact provenance mismatch') + receipt=json.loads((original/'receipt.json').read_text()) + if PIN.get('compilerProvenanceValidated'): + from retained_qualification import validate_original + validate_original(original,PIN) + require(receipt['sourceRevision']==PIN['sourceRevision'] and receipt['runId']==PIN['artifactRunId'] and receipt['trustedWorkflowRevision']==PIN['artifactWorkflowRevision'],'Original artifact provenance mismatch') require(receipt['cleanupUncertain'] is False and receipt['status']=='failed_no_retry','Unexpected original qualification disposition') proof['originalFailure']={'status':receipt['status'],'testFailures':receipt.get('testFailures'),'packArchiveSha256':sha(original/'provider-pack.tar.gz'),'daemonSha256':sha(original/'paperclip-runnerd')} require(sha(original/'paperclip-runnerd')==PIN['daemonSha256'] and sha(original/'resolved-pnpm-lock.yaml')==PIN['resolvedLockSha256'],'Original daemon/lock mismatch') @@ -137,10 +144,10 @@ def execute(args): before_manifest=json.loads((pack/'provider-pack.json').read_text()) sink=scratch/'startup-timings.jsonl';fd=os.open(sink,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600);st=os.fstat(fd);os.close(fd) identity={k:str(getattr(st,'st_'+k)) for k in ['dev','ino','uid']};identity['path']=str(sink) - sidecar=pack/'dist/cli/acpx-runtime-sidecar.cjs';original_sidecar=sidecar.read_bytes();patched,patch=patch_sidecar(original_sidecar,identity) + sidecar=pack/'dist/cli/acpx-runtime-sidecar.cjs';original_sidecar=sidecar.read_bytes();patched,patch=patch_sidecar(original_sidecar,identity,PIN['originalSidecarSha256']) # Preserve original file mode; this one private copy now has an explicit diagnostic identity. sidecar.write_bytes(patched);atomic_json(out/'sidecar-patch.json',patch) - (out/'sidecar.diff').write_text(''.join(difflib.unified_diff(original_sidecar.decode().splitlines(True),patched.decode().splitlines(True),fromfile='original-f5-profile13-sidecar',tofile='diagnostic-sidecar'))) + (out/'sidecar.diff').write_text(''.join(difflib.unified_diff(original_sidecar.decode().splitlines(True),patched.decode().splitlines(True),fromfile='original-'+PIN['sourceRevision']+'-sidecar',tofile='diagnostic-sidecar'))) command('diagnostic-sidecar-syntax',[node,'--check',sidecar]) proof['diagnosticPackVerification']=json.loads(command('diagnostic-pack-rebind',[node,HERE/'rebind-diagnostic-pack.mjs',pack,PIN['originalSidecarSha256'],PIN['sourceRevision'],*(['profile13'] if PIN.get('profileVersion')==13 else [])],180)) after_manifest=json.loads((pack/'provider-pack.json').read_text());expected=copy.deepcopy(before_manifest) @@ -157,8 +164,13 @@ def execute(args): require(sha(test)==PIN['testSha256'] and sha(HERE/'retain-test-state.mjs')==PIN['observerSha256'],'Test/observer changed');test.chmod(0o444);shutil.copy2(HERE/'retain-test-state.mjs',out/'retain-test-state.mjs') retained=out/'retained-test-state';retained.mkdir(mode=0o700) tenv={**env,'PAPERCLIP_TEST_PI_STARTUP_PACKAGE_ROOT':str(pack),'PAPERCLIP_TEST_PI_STARTUP_RUNNER_BINARY':str(daemon),'PI_INTEL_OWNED_TMP':str(scratch),'PI_INTEL_RETAINED_STATE':str(retained)} - owner=OwnedProcesses(out/'closed-startup-processes.json',scratch,pack,sidecar);active=DiagnosticChild(owner) - inspection=DiagnosticInspection(owner,active);owner.table=inspection.table;owner.argv=inspection.argv + owner=OwnedProcesses(out/'closed-startup-processes.json',scratch,pack,sidecar) + if PIN.get('repairedObserver'): + from retained_qualification import RetainedChild + active=RetainedChild(owner) + else: + active=DiagnosticChild(owner) + inspection=DiagnosticInspection(owner,active);owner.table=inspection.table;owner.argv=inspection.argv proof['processInspection']={'activeCadenceSeconds':0.5,'maximumCallSeconds':5,'defaultSharedCallSeconds':2,'deadlineCap':'remaining active70s or existing cleanup100s','activeFailuresFatal':True,'activeInspectionRetries':0} row={'label':'original-closed-startup-test-with-diagnostic-sidecar','argv':[str(node),'--import',str(HERE/'retain-test-state.mjs'),'--test',str(test)],'outerDeadlineSeconds':70,'originalTestTimeoutMs':60000,'originalAdmissionLimitMs':30000,'startedAt':datetime.datetime.now(datetime.timezone.utc).isoformat(),'startedMonotonicNs':str(time.monotonic_ns())};proof['commands'].append(row);proof['status']='diagnostic_running';save() try: @@ -198,5 +210,5 @@ def execute(args): finally:save() if __name__=='__main__': - p=argparse.ArgumentParser(description=__doc__);p.add_argument('--archive',type=Path,required=True);p.add_argument('--output',type=Path,required=True) + p=argparse.ArgumentParser(description=__doc__);p.add_argument('--inputs',choices=['f5','bba'],default='f5');p.add_argument('--archive',type=Path,required=True);p.add_argument('--output',type=Path,required=True) raise SystemExit(execute(p.parse_args())) diff --git a/scripts/ci/pi-intel/startup_timing_patch.py b/scripts/ci/pi-intel/startup_timing_patch.py index 9119efb52c..511fc3801c 100644 --- a/scripts/ci/pi-intel/startup_timing_patch.py +++ b/scripts/ci/pi-intel/startup_timing_patch.py @@ -2,10 +2,11 @@ import hashlib,json from source_guard import require ORIGINAL_SHA='ce987993ffb92f449ef0432dee6cd7eb90ea19521ce3d7b51a185fed16ed8c47' +BBA_SHA='c7c331492378017d474ab732648dc96c0ece08466983e11be795d60abb9ed99c' PREFIX='__pcStartupDiagnostic' -def patch_sidecar(original,sink): - require(hashlib.sha256(original).hexdigest()==ORIGINAL_SHA,'Diagnostic sidecar original digest mismatch') +def patch_sidecar(original,sink,expected_sha=ORIGINAL_SHA): + require(expected_sha in {ORIGINAL_SHA,BBA_SHA} and hashlib.sha256(original).hexdigest()==expected_sha,'Diagnostic sidecar original digest mismatch') require(set(sink)=={'path','dev','ino','uid'} and sink['path'].startswith('/private/tmp/pc-intel-diagnostic-') and sink['path'].endswith('/startup-timings.jsonl'),'Unexpected timing sink binding') require(all(isinstance(sink[k],str) and sink[k].isdigit() for k in ['dev','ino','uid']),'Sink identity must use decimal strings') text=original.decode();insertions=[];labels=[] @@ -103,4 +104,4 @@ process.once("exit", () => __pcStartupDiagnosticMark("sidecar.exit")); for _,(pos,value) in ordered: result.append(text[at:pos]);result.append(value);patch.append({'offset':pos,'inserted':value});at=pos result.append(text[at:]);modified=''.join(result).encode() - return modified,{'schema':'paperclip.startup-timing-additive-patch/v1','originalSha256':ORIGINAL_SHA,'diagnosticSha256':hashlib.sha256(modified).hexdigest(),'sinkBinding':sink,'phases':labels+['sidecar.exit'],'insertions':patch,'onlyAdditions':True,'vendorClosureChanged':False} + return modified,{'schema':'paperclip.startup-timing-additive-patch/v1','originalSha256':expected_sha,'diagnosticSha256':hashlib.sha256(modified).hexdigest(),'sinkBinding':sink,'phases':labels+['sidecar.exit'],'insertions':patch,'onlyAdditions':True,'vendorClosureChanged':False} diff --git a/scripts/ci/pi-intel/test_startup_diagnostic.py b/scripts/ci/pi-intel/test_startup_diagnostic.py index a332cabc1b..c575535d4a 100644 --- a/scripts/ci/pi-intel/test_startup_diagnostic.py +++ b/scripts/ci/pi-intel/test_startup_diagnostic.py @@ -38,6 +38,33 @@ class DiagnosticTests(unittest.TestCase): shift-=len(change['inserted']);at=change['offset']+shift;text=text[:at]+text[at+len(change['inserted']):] self.assertEqual(text.encode(),self.original);self.assertEqual(hashlib.sha256(text.encode()).hexdigest(),ORIGINAL_SHA) self.assertEqual(len(proof['phases']),54) + def test_current_bba_patch_and_exact_retained_provenance(self): + from startup_timing_patch import BBA_SHA + from retained_qualification import validate_original,RetainedChild + current=json.loads((Path(__file__).parent/'startup-diagnostic-bba-inputs.json').read_text()) + original=Path(os.environ['PI_DIAGNOSTIC_BBA_SIDECAR']).read_bytes() + modified,proof=patch_sidecar(original,{'path':'/private/tmp/pc-intel-diagnostic-fixture/startup-timings.jsonl','dev':'1','ino':'2','uid':'501'},BBA_SHA) + self.assertEqual(proof['originalSha256'],BBA_SHA) + text=modified.decode();shift=sum(len(x['inserted']) for x in proof['insertions']) + for change in reversed(proof['insertions']): + shift-=len(change['inserted']);at=change['offset']+shift + self.assertEqual(text[at:at+len(change['inserted'])],change['inserted']) + text=text[:at]+text[at+len(change['inserted']):] + self.assertEqual(text.encode(),original);self.assertEqual(len(proof['phases']),54) + self.assertEqual(current['sourceRevision'],'bba63f51207de8513ad2d9593694f718fc60ef17') + self.assertEqual(current['artifactRunId'],'37004499595') + self.assertTrue(current['repairedObserver']);self.assertTrue(current['compilerProvenanceValidated']) + self.assertEqual(current['originalSidecarSha256'],BBA_SHA) + self.assertEqual(current['testSha256'],PIN['testSha256']) + validate_original(Path(os.environ['PI_DIAGNOSTIC_BBA_ORIGINAL_ARTIFACT']),current) + with self.assertRaisesRegex(RuntimeError,'original digest mismatch'): + patch_sidecar(original,{'path':'unused','dev':'1','ino':'2','uid':'3'}) + workflow=(Path(__file__).parents[3]/'.github/workflows/docker-runner-check.yml').read_text() + job=workflow.split(' manual_pi_startup_diagnostic:',1)[1].split(' manual_pi_startup_overlap:',1)[0] + self.assertIn('--inputs bba',job);self.assertIn('artifacts/11226760000/zip',job) + self.assertIn(current['artifactZipSha256'],job) + fallback=workflow.split(' manual_image:',1)[1].split('\n needs:',1)[0] + self.assertIn('!inputs.diagnose_pi_startup',fallback) def test_wrong_original_rejected(self): with self.assertRaises(RuntimeError):patch_sidecar(self.original+b' ',{'path':'unused','dev':'1','ino':'2','uid':'3'}) def test_retained_f5_pack_identity_and_original_test_contract(self):