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 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 committed 2026-11-08 22:23:44 -06:00
1 parent fb41c8ce49
commit 49051ccae2
2 files changed
+24 -2

No files matched your search

+15 -1
View File
@@ -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();
});
+9 -1
View File
@@ -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<ReturnType<ReturnType<typeof createSdkArtifactScanner>["audit"]>>; executionId: string; sourceRevision: string; retainedAt: string;
buildHelperSha256: string; lockSha256: string; files: Array<BuiltFile & { retainedPath: string; retainedSha256: string }>;