From bc6833f716ffa404fda89a5106d3ae5cd8c34775 Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 24 Sep 2026 12:01:20 -0700 Subject: [PATCH] fix(grok): synchronize launcher admission and numeric expiry refresh Handle supported numeric credential expiries and verify launcher digests across the TypeScript, Rust, server, and provider-pack boundaries. Co-Authored-By: Paperclip --- packages/grok-acp/launcher.cjs | 7 +++- .../runner-core/src/acpx_provider_backend.rs | 2 +- .../scripts/build-provider-pack.mjs | 2 +- .../acpx/installation-integrity.test.ts | 32 +++++++++++++++++-- .../src/drivers/acpx/qualified-profiles.ts | 2 +- .../native-session-executor.test.ts | 2 +- .../native-runtime/native-session-executor.ts | 2 +- 7 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/grok-acp/launcher.cjs b/packages/grok-acp/launcher.cjs index a193ab505ca..a6b7339c7ae 100755 --- a/packages/grok-acp/launcher.cjs +++ b/packages/grok-acp/launcher.cjs @@ -38,7 +38,12 @@ if (!environment.XAI_API_KEY && environment.GROK_HOME && isAbsolute(environment. if (error.code !== "ENOENT") throw new Error("Grok subscription credential is invalid"); } const entries = auth && typeof auth === "object" && !Array.isArray(auth) ? Object.values(auth) : []; - const expiry = entries.length === 1 ? Date.parse(entries[0]?.expires_at) : NaN; + const rawExpiry = entries.length === 1 ? entries[0]?.expires_at : undefined; + // Match the managed Grok auth contract: ISO-8601, epoch seconds, or milliseconds. + const isoExpiry = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/; + const expiry = typeof rawExpiry === "number" && Number.isFinite(rawExpiry) + ? rawExpiry < 1e12 ? rawExpiry * 1000 : rawExpiry + : typeof rawExpiry === "string" && isoExpiry.test(rawExpiry) ? Date.parse(rawExpiry) : NaN; if (Number.isFinite(expiry) && expiry <= Date.now() + 60_000) { // Descriptor-backed executables must retain their exact descriptor in the // child. Other descriptors, including ACP stdin/stdout, are not forwarded. diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index 6cf64fbb5e0..22581e7d2a5 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -168,7 +168,7 @@ impl AcpxProviderDescriptor { "1.0.13", Some("@paperclipai/grok-acp"), Some("1.0.13"), - "sha256:42fe296ec6fc0715c3509cec9671451bcd5bfdc7f185041101c8aac1e9ac8718", + "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55", ), "pi" => return Err(DurableRunnerError::invalid( "ACPX agent pi is not executable through the verified runnerd provider boundary", diff --git a/packages/paperclip-runner/scripts/build-provider-pack.mjs b/packages/paperclip-runner/scripts/build-provider-pack.mjs index c88362ccb11..03e5ede50ad 100644 --- a/packages/paperclip-runner/scripts/build-provider-pack.mjs +++ b/packages/paperclip-runner/scripts/build-provider-pack.mjs @@ -314,7 +314,7 @@ try { .update(distDigest) .digest("hex")}`, acpxProfileDigests: { - grok: "sha256:42fe296ec6fc0715c3509cec9671451bcd5bfdc7f185041101c8aac1e9ac8718", + grok: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55", claude: "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", codex: diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts index c908b348a8b..963255639aa 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts @@ -2118,15 +2118,29 @@ async function installationFixture() { describe("Grok launcher subscription refresh", () => { + it("keeps the Grok launcher digest synchronized across TypeScript, Rust, server, and provider pack", async () => { + const launcher = await readFile(new URL("../../../../grok-acp/launcher.cjs", import.meta.url)); + const digest = `sha256:${createHash("sha256").update(launcher).digest("hex")}`; + expect(resolveQualifiedAcpxProfile("grok", "grok-4.7").commandDigest).toBe(digest); + const [server, pack, rust] = await Promise.all([ + readFile(new URL("../../../../../server/src/services/native-runtime/native-session-executor.ts", import.meta.url), "utf8"), + readFile(new URL("../../../scripts/build-provider-pack.mjs", import.meta.url), "utf8"), + readFile(new URL("../../../runner/crates/runner-core/src/acpx_provider_backend.rs", import.meta.url), "utf8"), + ]); + expect(server).toContain(`grok: "${digest}"`); + expect(pack).toContain(`grok: "${digest}"`); + expect(rust.split('"grok" =>')[1]?.slice(0, 600)).toContain(`"${digest}"`); + }); + async function fixture(input: { - expiry?: number; apiKey?: string; executable?: string; + expiry?: number; rawExpiry?: unknown; apiKey?: string; executable?: string; refresh?: { status: number | null; signal?: string | null; error?: Error }; mode?: number; fileError?: string; growingFile?: boolean; } = {}) { const script = await readFile(new URL("../../../../grok-acp/launcher.cjs", import.meta.url), "utf8"); const payload = Buffer.from(JSON.stringify({ account: { key: "PRIVATE-CREDENTIAL-SENTINEL", refresh_token: "PRIVATE-REFRESH-SENTINEL", - expires_at: new Date(input.expiry ?? Date.now() - 60_000).toISOString(), + expires_at: "rawExpiry" in input ? input.rawExpiry : new Date(input.expiry ?? Date.now() - 60_000).toISOString(), } })); const fs = { constants, @@ -2166,6 +2180,20 @@ describe("Grok launcher subscription refresh", () => { const test = await fixture({ expiry: Date.now() + 600_000 }); test.run(); expect(test.spawnSync).not.toHaveBeenCalled(); expect(test.execve).toHaveBeenCalledOnce(); }); + it.each(["seconds", "milliseconds"])("refreshes expired and near-expiry numeric %s but skips fresh credentials", async encoding => { + for (const [offset, expected] of [[-60_000, true], [30_000, true], [600_000, false]] as const) { + const ms = Date.now() + offset; + const test = await fixture({ rawExpiry: encoding === "seconds" ? ms / 1000 : ms }); + test.run(); + expect(test.spawnSync).toHaveBeenCalledTimes(expected ? 1 : 0); + expect(test.execve).toHaveBeenCalledOnce(); + } + }); + it.each([null, "1760000000", "2020-01-01", true, {}, []])("does not coerce unsupported expiry encodings: %j", async rawExpiry => { + const test = await fixture({ rawExpiry }); test.run(); + expect(test.spawnSync).not.toHaveBeenCalled(); + expect(test.execve).toHaveBeenCalledOnce(); + }); it("never discovers subscription credentials for an explicit API key", async () => { const test = await fixture({ apiKey: "explicit-api-key" }); test.run(); expect(test.fs.openSync).not.toHaveBeenCalled(); expect(test.spawnSync).not.toHaveBeenCalled(); expect(test.execve).toHaveBeenCalledOnce(); diff --git a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts index bf1da3e0f8f..0d33811be69 100644 --- a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts @@ -36,7 +36,7 @@ export const QUALIFIED_ACPX_PROFILES: Readonly< acpxVersion: QUALIFIED_ACPX_VERSION, agent: "grok", agentProfileVersion: 1, agentServerPackage: "@paperclipai/grok-acp", agentServerVersion: "1.0.13", agentRuntimePackage: "@paperclipai/grok-acp", agentRuntimeVersion: "1.0.13", - commandDigest: "sha256:387d6a186753307fdf6f6d8898fa543525239312e31e2c5d5c4baa6b6eec6671", + commandDigest: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55", qualificationModel: "grok-4.7", reportedModelId: "grok-4.7", permissionPolicy: "interactive", }, pi: { diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 74f66087fe2..55947d459da 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -1074,7 +1074,7 @@ describe("remote provider pack manifest", () => { distDigest: sha256DirectoryTree(join(root, "dist")), bridgeDigest: "", acpxProfileDigests: { - grok: "sha256:42fe296ec6fc0715c3509cec9671451bcd5bfdc7f185041101c8aac1e9ac8718", + grok: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55", claude: "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", codex: diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 826bccd7aae..fca22e24a57 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -9237,7 +9237,7 @@ const REMOTE_PROVIDER_PACK_PINS = { grok: "1.0.13", } as const; const REMOTE_PROVIDER_PACK_PROFILE_DIGESTS = { - grok: "sha256:42fe296ec6fc0715c3509cec9671451bcd5bfdc7f185041101c8aac1e9ac8718", + grok: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55", claude: "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", codex: