From 59bba2819295407d8e76bc70bf0221ca1384aa1a Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 29 Sep 2026 10:04:53 -0500 Subject: [PATCH] Follow current Copilot profile identity in native installation admission Co-Authored-By: Paperclip --- .../src/drivers/acpx/copilot-installation.test.ts | 13 ++++++++++++- .../src/drivers/acpx/copilot-installation.ts | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts index 7eed92a785a..66cf3b83501 100644 --- a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts @@ -10,6 +10,17 @@ import { QUALIFIED_ACPX_PROFILES } from "./qualified-profiles.js"; vi.mock("./installation-integrity.js", () => ({ verifyNativeAcpxInstallation: vi.fn() })); describe("Copilot build-owned installation", () => { + it("admits the current v3 declaration and binds its source policy/patch hashes", () => { + const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v3-identity.json", import.meta.url), "utf8")); + expect(QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion).toBe(identity.declaration.agentProfileVersion); + expect(QUALIFIED_ACPX_PROFILES.copilot.commandDigest).toBe(identity.commandDigest); + const sorted = Object.fromEntries(Object.entries(identity.declaration).sort(([a], [b]) => a.localeCompare(b))); + expect(`sha256:${createHash("sha256").update(JSON.stringify(sorted)).digest("hex")}`).toBe(identity.commandDigest); + for (const [relative, field] of [["copilot-policy.ts", "policySha256"], ["../../../../../patches/acpx@0.13.1.patch", "acpxPatchSha256"]]) { + expect(createHash("sha256").update(readFileSync(new URL(relative!, import.meta.url))).digest("hex")).toBe(identity.declaration[field!]); + } + expect(readFileSync(new URL("../../../scripts/build-copilot-distribution.mjs", import.meta.url), "utf8")).toContain(identity.commandDigest); + }); it("pins the materializer's complete executable closure on every target platform", () => { const script = readFileSync(new URL("../../../scripts/materialize-copilot-binary.mjs", import.meta.url), "utf8"); const records = [...script.matchAll(/"(darwin-arm64|darwin-x64|linux-x64)": Object\.freeze\(\{ packageName: "[^"]+", executableDigest: "([a-f0-9]+)", size: (\d+)/g)]; @@ -38,7 +49,7 @@ describe("Copilot build-owned installation", () => { }); it("rejects changed profile identities before native file access", async () => { vi.mocked(verifyNativeAcpxInstallation).mockClear(); - for (const override of [{ agentServerVersion: "latest" }, { commandDigest: "sha256:untrusted" }, { agent: "cursor" }, { agentProfileVersion: 1 }]) { + for (const override of [{ agentServerVersion: "latest" }, { commandDigest: "sha256:untrusted" }, { agent: "cursor" }, { agentProfileVersion: 1 }, { agentProfileVersion: 2 }, { agentProfileVersion: 4 }]) { await expect(verifyCopilotInstallation({ ...QUALIFIED_ACPX_PROFILES.copilot, ...override } as never)).rejects.toThrow("exact pinned"); } expect(verifyNativeAcpxInstallation).not.toHaveBeenCalled(); diff --git a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.ts b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.ts index d3347bcf73d..c57994a2f47 100644 --- a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.ts +++ b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.ts @@ -13,7 +13,7 @@ export const COPILOT_CLOSURE_SHA256 = Object.freeze({ /** Admission primitive only. Pending candidates remain gated by qualification. */ export async function verifyCopilotInstallation(profile: QualifiedAcpxProfile): Promise { const expected = QUALIFIED_ACPX_PROFILES.copilot; - if (profile.agent !== "copilot" || profile.agentProfileVersion !== 2 + if (profile.agent !== "copilot" || profile.agentProfileVersion !== expected.agentProfileVersion || profile.acpxVersion !== expected.acpxVersion || profile.agentServerPackage !== "@github/copilot" || profile.agentServerVersion !== COPILOT_VERSION || profile.commandDigest !== expected.commandDigest || profile.agentRuntimePackage !== null || profile.agentRuntimeVersion !== null) {