From 49051ccae2acac7c3cff8f625e7978e78fcf5927 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 8 Nov 2026 21:54:34 -0600 Subject: [PATCH] fix(evals): retain SDK declarations under sealer-admitted names With the SDK public proof fixed, the six plugin-SDK cells got past startup, then every attempt failed at finalization with "Unsupported packaged evidence carrier". copyRetainedSourceSdk copies the built SDK output (.d.ts and .map files among it) into the attempt upload directory, and the private result sealer admits only standard evidence extensions, so it rejected the whole results tree and no evidence was uploaded. Retain any SDK file whose name the sealer would refuse under a .txt suffix. Bytes, hashes and the receipt are unchanged; the receipt records the new retained path. Add a test that seals a copied SDK upload with the real sealer. Co-Authored-By: Claude Opus 5.5 --- tests/runner-e2e/source-sdk-evidence.test.ts | 16 +++++++++++++++- tests/runner-e2e/source-sdk-evidence.ts | 10 +++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/runner-e2e/source-sdk-evidence.test.ts b/tests/runner-e2e/source-sdk-evidence.test.ts index 307e8a6b4c5..a678ce90a0a 100644 --- a/tests/runner-e2e/source-sdk-evidence.test.ts +++ b/tests/runner-e2e/source-sdk-evidence.test.ts @@ -10,6 +10,8 @@ import { prepareSourceServerDependencies } from "./source-startup.js"; import { copyRetainedSourceSdk, retainPreparedSourceSdk, SOURCE_SDK_DIAGNOSTICS } from "./source-sdk-evidence.js"; import { packageEvidence } from "./evidence.js"; import { isHistoricalBundlePathAllowed } from "./history-publish.js"; +import { sealManualResultEvidence, type ManualPrivateHandoff } from "./manual-private-handoff.js"; +import { MANUAL_PRIVATE_EXECUTIONS, MANUAL_PRIVATE_PROTOCOL } from "./manual-private-retention.js"; const roots: string[] = [], executionId = "chat-features.runner-opencode.local.plugins.install-plugin", sourceRevision = "a".repeat(40); const hash = (bytes: Buffer | string) => createHash("sha256").update(bytes).digest("hex"); @@ -23,7 +25,7 @@ async function fixture(secrets: string[] = []) { for (const directory of ["packages/shared", "packages/plugins/sdk"]) { await put(`${directory}/src/index.ts`, "export const syntheticFixture = true;\n"); await put(`${directory}/tsconfig.json`, "{}"); } - await put("node_modules/typescript/bin/tsc", `const fs=require('node:fs'),path=require('node:path');if(process.env.FAIL_COMPILER==='1')process.exit(7);const dir=path.join(path.dirname(process.argv[3]),'dist');fs.mkdirSync(path.join(dir,'nested'),{recursive:true});fs.writeFileSync(path.join(dir,'index.js'),process.env.SYNTHETIC_OUTPUT_CONTENT||'export const syntheticFixture = true;\\n');fs.writeFileSync(path.join(dir,'nested/child.js'),'export {};\\n');`); + await put("node_modules/typescript/bin/tsc", `const fs=require('node:fs'),path=require('node:path');if(process.env.FAIL_COMPILER==='1')process.exit(7);const dir=path.join(path.dirname(process.argv[3]),'dist');fs.mkdirSync(path.join(dir,'nested'),{recursive:true});fs.writeFileSync(path.join(dir,'index.js'),process.env.SYNTHETIC_OUTPUT_CONTENT||'export const syntheticFixture = true;\\n');fs.writeFileSync(path.join(dir,'nested/child.js'),'export {};\\n');fs.writeFileSync(path.join(dir,'index.d.ts'),'export declare const syntheticFixture: boolean;\\n');fs.writeFileSync(path.join(dir,'index.js.map'),'{"version":3}\\n');`); await mkdir(path.join(temporaryRoot, "artifacts-private"), { recursive: true, mode: 0o700 }); await mkdir(path.join(temporaryRoot, "selected-private-inputs"), { recursive: true, mode: 0o700 }); stagePrivateDiagnostics(temporaryRoot, [executionId], secrets); @@ -95,3 +97,15 @@ it("ordinary/multi-cell/installed lanes do not retain or require SDK evidence", await expect(copyRetainedSourceSdk(f.temporaryRoot, "unselected", null, f.uploadDir, [])).resolves.toBeUndefined(); await expect(stat(path.join(f.temporaryRoot, "artifacts-private/source-sdk-build"))).rejects.toThrow(); }); +it("retains declarations and source maps under names the private result sealer admits", async () => { + const f = await fixture(); await f.prepare(); + const receipt = JSON.parse(await readFile(path.join(f.temporaryRoot, "artifacts-private/source-sdk-build/receipt.json"), "utf8")); + expect(receipt.files.map((row: { retainedPath: string }) => row.retainedPath)).toEqual(expect.arrayContaining(["packages/shared/dist/index.d.ts.txt", "packages/shared/dist/index.js.map.txt", "packages/shared/dist/index.js"])); + // Built SDK output always contains .d.ts and .map files. Sealing the results + // tree used to reject them as unsupported carriers and lose the whole attempt. + const context = { root: f.root, protocol: MANUAL_PRIVATE_PROTOCOL, private: true, workflowSha: "a".repeat(40), targetSha: "b".repeat(40), runId: "123", attempt: "1", executionIds: [MANUAL_PRIVATE_EXECUTIONS[0]!] } as ManualPrivateHandoff; + const results = path.join(f.root, "results"), uploadDir = path.join(results, "campaign", "attempt-1"); + await copyRetainedSourceSdk(f.temporaryRoot, executionId, sourceRevision, uploadDir, []); + for (const file of receipt.files) expect(hash(await readFile(path.join(uploadDir, SOURCE_SDK_DIAGNOSTICS, file.retainedPath)))).toBe(file.sha256); + await expect(sealManualResultEvidence(context, [], results)).resolves.not.toThrow(); +}); diff --git a/tests/runner-e2e/source-sdk-evidence.ts b/tests/runner-e2e/source-sdk-evidence.ts index 7a33a224cc8..05baf496a45 100644 --- a/tests/runner-e2e/source-sdk-evidence.ts +++ b/tests/runner-e2e/source-sdk-evidence.ts @@ -6,6 +6,7 @@ import { chmod, lstat, mkdir, open, readFile, readdir, realpath, rm, writeFile } import path from "node:path"; import { diagnosticSecrets, readPrivateDiagnosticSecrets } from "./private-diagnostics.js"; import { findSecretLeak } from "./redaction.js"; +import { isStandardEvidenceFile } from "./evidence.js"; const selectedIds = new Set(["plugins.configure-plugin", "plugins.disable-plugin", "plugins.install-plugin", "plugins.use-plugin-tool", "routines.plugin-managed-routine", "secrets.least-scope-delivery"].map(id => `chat-features.runner-opencode.local.${id}`)); export const SOURCE_SDK_DIAGNOSTICS = "diagnostics/source-plugin-sdk"; @@ -14,7 +15,14 @@ const hash = (bytes: string | Buffer) => createHash("sha256").update(bytes).dige const hex = (value: unknown): value is string => typeof value === "string" && /^[a-f0-9]{64}$/.test(value); const staging = (root: string) => path.join(root, "artifacts-private", "source-sdk-build"); interface BuiltFile { file: string; size: number; sha256: string } -const retainedPath = (file: string) => file.replace(/\/\.paperclip-build-complete$/, "/build-completion-receipt.json"); +// Retained bytes must use a name the private result sealer admits as a +// standard carrier: declarations (.d.ts) and source maps (.map) are retained +// byte-for-byte under a .txt suffix instead of failing the whole attempt. +export const retainedSourceSdkPath = (file: string) => { + const named = file.replace(/\/\.paperclip-build-complete$/, "/build-completion-receipt.json"); + return isStandardEvidenceFile(`html-report/${SOURCE_SDK_DIAGNOSTICS}/${named}`) ? named : `${named}.txt`; +}; +const retainedPath = retainedSourceSdkPath; interface RetainedBuild { schema: "paperclip.source-plugin-sdk/v1" | "paperclip.source-plugin-sdk/v2"; sourceRootSha256?: string; scanAudit?: Awaited["audit"]>>; executionId: string; sourceRevision: string; retainedAt: string; buildHelperSha256: string; lockSha256: string; files: Array;