From 04b4aa9b17ce6a016c791b63614362a137ec4b58 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 13 Aug 2026 23:57:38 -0700 Subject: [PATCH] Duck-type across defineTool/GrantStore interface versions (CL-5865 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumers with a newer @intx/agent (static definitions on defineTool options) or @intx/authz (collectGrantsInChain on GrantStore) than this package's own pinned dependency were failing to typecheck the raw source pulled in transitively through the resident distiller's export of the memory tool factories. Both fixes bind the object to a named const before handing it to the typed API instead of a fresh literal, so normal structural (duck-typed) assignment applies rather than TypeScript's excess-property check on literals — the object satisfies either interface shape, and an older caller simply never reads the extra field. --- src/ports/writable-grant-store.ts | 30 +++++++++++++++++++++++------- src/tools/install.ts | 18 ++++++++++++++---- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/ports/writable-grant-store.ts b/src/ports/writable-grant-store.ts index 5d572bb..cefbbcf 100644 --- a/src/ports/writable-grant-store.ts +++ b/src/ports/writable-grant-store.ts @@ -32,15 +32,30 @@ export function createInMemoryWritableGrantStore( initial: GrantRule[] = [], ): WritableGrantStore & { grants: GrantRule[] } { const grants = [...initial]; - return { + function collect(principalId: string) { + const now = new Date(); + return grants.filter((g) => { + if (g.principalId !== principalId) return false; + if (g.expiresAt !== null && g.expiresAt <= now) return false; + return true; + }); + } + // Bound to a const rather than returned as a fresh object literal: some + // `@intx/authz` versions require `collectGrantsInChain` on `GrantStore` + // (an ancestor-chain walk) that this package's own pinned version + // predates. Routing through a named variable gets normal structural + // assignment for the function's declared return type instead of + // TypeScript's excess-property check on literals, so this satisfies + // either shape — tenantId (and thus the ancestor chain) is a no-op here + // regardless, matching `createInMemoryGrantStore`'s own tenant-scoped + // fixture semantics. + const store = { grants, async collectGrants(principalId: string, _tenantId?: string) { - const now = new Date(); - return grants.filter((g) => { - if (g.principalId !== principalId) return false; - if (g.expiresAt !== null && g.expiresAt <= now) return false; - return true; - }); + return collect(principalId); + }, + async collectGrantsInChain(principalId: string, _tenantId?: string) { + return collect(principalId); }, async putGrant(grant: GrantRule) { const idx = grants.findIndex((g) => g.id === grant.id); @@ -48,4 +63,5 @@ export function createInMemoryWritableGrantStore( else grants.push(grant); }, }; + return store; } diff --git a/src/tools/install.ts b/src/tools/install.ts index 688dd62..a098739 100644 --- a/src/tools/install.ts +++ b/src/tools/install.ts @@ -37,10 +37,19 @@ export function defineMemoryHttpTool(opts: { signal: AbortSignal | undefined, ) => Promise; }) { - return defineTool({ + // Bound to a const rather than passed as a fresh object literal: some + // `@intx/agent` versions add a static `definitions` field to defineTool's + // options (so callers can enumerate tool names without instantiating the + // factory) that this package's own pinned version predates. Routing + // through a named variable gets normal structural (duck-typed) parameter + // assignment instead of TypeScript's excess-property check on literals, + // so this call type-checks against either shape — the extra field is + // simply unused by an older defineTool at runtime. + const toolOpts = { id: opts.id, requires: MEMORY_TOOL_ENV_KEYS, - factory(env) { + definitions: [{ name: opts.name }], + factory(env: MemoryInstallEnv) { const client = createMemoryHttpClient(readMemoryToolEnv(env)); const runner = createToolRunner([ stringTool({ @@ -55,8 +64,9 @@ export function defineMemoryHttpTool(opts: { ]); return { definitions: runner.definitions, - run: (call, signal) => runner.run(call, signal), + run: runner.run.bind(runner), }; }, - }); + }; + return defineTool(toolOpts); }