mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
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:
1 parent
fb41c8ce49
commit
49051ccae2
2 files changed
+24
-2
No files matched your search
@@ -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();
|
||||
});
|
||||
@@ -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 }>;
|
||||
|
||||
Reference in new issue
Block a user