mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
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 <noreply@paperclip.ing>
This commit is contained in:
1 parent
d20e8a5c2e
commit
bc6833f716
7 files changed
+41
-8
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -314,7 +314,7 @@ try {
|
||||
.update(distDigest)
|
||||
.digest("hex")}`,
|
||||
acpxProfileDigests: {
|
||||
grok: "sha256:42fe296ec6fc0715c3509cec9671451bcd5bfdc7f185041101c8aac1e9ac8718",
|
||||
grok: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
claude:
|
||||
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
codex:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user