Bind Daytona candidate image identity to selected provider assets

Refresh the reviewed resolved dependency digest and fix ACP selector typing.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-28 11:59:18 -05:00
1 parent 588dd00af1
commit a3a71ebdb1
5 files changed
+37 -3

No files matched your search

+1 -1
View File
@@ -28,7 +28,7 @@ COPY cli/package.json ./cli/package.json
# The complete resolved lock (including transitive integrity hashes) is reviewed.
# Reject registry-time drift BEFORE installing packages or running lifecycle code.
# Refresh this digest together with source/provider dependency changes.
ARG PAPERCLIP_RUNNER_LOCK_SHA256=d7d96cf0d98cf0946f6195e29ba173b03711a947a1c38f312b67cda56c254c22
ARG PAPERCLIP_RUNNER_LOCK_SHA256=3b2842ff53812112cf2eb59efa0664e10630d5f7a7586813fdd07741131486f9
RUN pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile \
&& printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \
&& sha256sum -c /tmp/provider-lock.sha256 \
+4
View File
@@ -90,6 +90,10 @@ A pack with candidate assets does not qualify or enable that provider.
For an isolated Linux x64 Daytona qualification image, pass
`--build-arg PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS=cursor` with the normal build arguments.
Compute its content ID with the same selection:
`pnpm --silent test:e2e:runner:image-id --candidate-providers=cursor`.
Candidate assets and the default pack have distinct image identities. Never reuse
the default image's content ID for a candidate build.
Use each provider branch's recorded version and explicit model. Keep credentials
out of images. Paid qualification requires bound provider and Daytona credentials,
inspectable spend, and the shared $100 ceiling recorded in the capability report.
+22 -1
View File
@@ -34,6 +34,7 @@ export const DAYTONA_IMAGE_INPUT_PATHS = [
"packages/paperclip-runner/runner/crates",
"packages/paperclip-runner/scripts/acpx-sidecar-contract.mjs",
"packages/paperclip-runner/scripts/build-provider-pack.mjs",
"packages/paperclip-runner/scripts/candidate-provider-pack.mjs",
"packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs",
"packages/paperclip-runner/scripts/generate-acpx-sidecar-contract.mjs",
"packages/paperclip-runner/scripts/generate-protocol-schema-module.mjs",
@@ -65,6 +66,17 @@ export interface DaytonaImageContentOptions {
platform?: string;
baseImages?: readonly string[];
frontendDigest?: string;
/** Must match the Docker PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS build argument. */
candidateProviders?: readonly string[];
}
export function normalizedDaytonaCandidateProviders(values: readonly string[]): string[] {
const selected = [...values].sort();
if (selected.some(value => !["cursor", "copilot", "pi"].includes(value))
|| new Set(selected).size !== selected.length) {
throw new Error("Daytona candidate providers must be distinct known ACP profiles");
}
return selected;
}
function compareNames(left: string, right: string): number {
@@ -262,6 +274,9 @@ export async function computeDaytonaImageContentId(
for (const baseImage of await resolveBaseImages(root, options.baseImages)) {
updateRecord(hash, "base-image", baseImage);
}
for (const candidate of normalizedDaytonaCandidateProviders(options.candidateProviders ?? [])) {
updateRecord(hash, "candidate-provider", candidate);
}
for (const inputPath of inputPaths) {
await hashEntry(hash, root, inputPath);
}
@@ -270,7 +285,13 @@ export async function computeDaytonaImageContentId(
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null;
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
computeDaytonaImageContentId()
const arguments_ = process.argv.slice(2);
const candidateFlag = arguments_[0];
if (arguments_.length > 1 || (candidateFlag !== undefined && !candidateFlag.startsWith("--candidate-providers="))) {
throw new Error("Expected only --candidate-providers=cursor,copilot,pi");
}
const candidateProviders = candidateFlag?.slice("--candidate-providers=".length).split(",").filter(Boolean) ?? [];
computeDaytonaImageContentId({ candidateProviders })
.then((contentId) => process.stdout.write(`${contentId}\n`))
.catch((error: unknown) => {
process.stderr.write(
+9
View File
@@ -166,6 +166,7 @@ describe("runner E2E Daytona image contract", () => {
"patches",
"packages/paperclip-eval-kernel/src",
"packages/paperclip-runner/package.json",
"packages/paperclip-runner/scripts/candidate-provider-pack.mjs",
"packages/paperclip-runner/runner/crates",
"packages/paperclip-runner/src",
]) {
@@ -242,6 +243,14 @@ describe("runner E2E Daytona image contract", () => {
'pub const VERSION: &str = "one";\n',
);
const baseline = await computeDaytonaImageContentId(options);
const candidate = await computeDaytonaImageContentId({ ...options, candidateProviders: ["pi"] });
expect(candidate).not.toBe(baseline);
expect(await computeDaytonaImageContentId({ ...options, candidateProviders: ["copilot", "pi"] }))
.toBe(await computeDaytonaImageContentId({ ...options, candidateProviders: ["pi", "copilot"] }));
await expect(computeDaytonaImageContentId({ ...options, candidateProviders: ["pi", "pi"] }))
.rejects.toThrow("distinct known");
await expect(computeDaytonaImageContentId({ ...options, candidateProviders: ["unknown"] }))
.rejects.toThrow("distinct known");
expect(
await computeDaytonaImageContentId({
...options,
@@ -240,7 +240,7 @@ export function CodexLocalConfigFields({
{runnerManaged && runnerProvider === "acpx" && (
<Field configSection="adapter" label="ACP agent" hint="Cursor, GitHub Copilot, and Pi are awaiting local and Daytona qualification.">
<select className={inputClass}
value={isCreate ? values!.adapterSchemaValues?.acpxAgent ?? "claude" : eff("adapterConfig", "acpxAgent", config.acpxAgent ?? "claude")}
value={String(isCreate ? values!.adapterSchemaValues?.acpxAgent ?? "claude" : eff("adapterConfig", "acpxAgent", config.acpxAgent ?? "claude"))}
onChange={(event) => {
const profile = PAPERCLIP_RUNNER_ACPX_PROFILES.find(entry => entry.value === event.target.value);
if (!profile?.qualified) return;