@ractive-ch/tab-mcp 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/notopen.d.ts CHANGED
@@ -4,8 +4,11 @@ import { type RpcId } from "./exchange.js";
4
4
  * JSON-RPC message the AI app's model can act on, instead of an HTTP error
5
5
  * status that makes MCP clients re-probe the server's protocol era.
6
6
  */
7
- /** Why the page is not there: never came, or went away mid-request. */
8
- export type NotOpenPhase = "not_open" | "gone";
7
+ /**
8
+ * Why the page is not there: never came, went away mid-request, or is open
9
+ * but nobody is signed in to it (identity binding, DEC-077).
10
+ */
11
+ export type NotOpenPhase = "not_open" | "gone" | "signed_out";
9
12
  /** What the relay says about itself in a stand-in `server/discover` result. */
10
13
  export interface RelayInfo {
11
14
  name: string;
@@ -14,6 +17,8 @@ export interface RelayInfo {
14
17
  export declare function isRetryable(method: unknown): boolean;
15
18
  /** The text for "the page is not open" (tool results and JSON-RPC errors). */
16
19
  export declare function notOpenText(appName: string | undefined): string;
20
+ /** The text for a page that is open but not signed in (DEC-077). */
21
+ export declare function signedOutText(appName: string | undefined): string;
17
22
  /** The text for a tool call whose page went away while it ran. */
18
23
  export declare function goneDuringCallText(appName: string | undefined): string;
19
24
  /**
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The OIDC pairing method (DEC-075, DEC-079): at `/authorize` the user signs in
3
+ * at the configured OpenID Connect provider, and the relay pairs the AI app if
4
+ * that person is the page's user. The relay is an ordinary OIDC client there
5
+ * (authorization code flow, PKCE S256, `state`, `nonce`).
6
+ */
7
+ import type { OidcConfig } from "../config.js";
8
+ import type { Logger } from "../log.js";
9
+ import type { PairingContext, PairingMethod, PairingResult, PairingTarget } from "./pairing.js";
10
+ /** The provider's metadata the relay uses (OIDC Discovery §3). */
11
+ export interface ProviderMetadata {
12
+ issuer: string;
13
+ authorizationEndpoint: string;
14
+ tokenEndpoint: string;
15
+ jwksUri: string;
16
+ /** `client_secret_basic` (default) or `client_secret_post`. */
17
+ tokenAuth: "client_secret_basic" | "client_secret_post";
18
+ }
19
+ export declare class OidcError extends Error {
20
+ name: string;
21
+ readonly reason: string;
22
+ constructor(reason: string, message: string);
23
+ }
24
+ /** Discovery for the configured issuer, cached an hour (OIDC Discovery §4). */
25
+ export declare class ProviderDiscovery {
26
+ #private;
27
+ constructor(issuer: string, development: boolean);
28
+ /** The cached metadata, if loaded. */
29
+ get cached(): ProviderMetadata | undefined;
30
+ get(): Promise<ProviderMetadata>;
31
+ }
32
+ export interface OidcPairingOptions {
33
+ config: OidcConfig;
34
+ /** The relay's public URL (the callback is `<url>/authorize/callback`). */
35
+ publicUrl: () => string;
36
+ logger: Logger;
37
+ development: boolean;
38
+ }
39
+ /** The identity-binding pairing method. */
40
+ export declare class OidcPairing implements PairingMethod {
41
+ #private;
42
+ readonly kind: "oidc";
43
+ constructor(options: OidcPairingOptions);
44
+ get callbackUrl(): string;
45
+ /** Load the provider's metadata before the page is shown. */
46
+ prepare(): Promise<void>;
47
+ /** The provider's origins, for the page's CSP `form-action` (the redirect after the post). */
48
+ formTargets(): string[];
49
+ render(target: PairingTarget): string;
50
+ check(target: PairingTarget, _form: URLSearchParams, _admit: () => boolean, context?: PairingContext): Promise<PairingResult>;
51
+ /**
52
+ * The provider sent the browser back. Returns the pending request the
53
+ * sign-in belongs to (if the state is known) and the result.
54
+ */
55
+ callback(params: URLSearchParams, browserId: string, targetFor: (requestId: string) => PairingTarget | undefined): Promise<{
56
+ requestId?: string;
57
+ result: PairingResult;
58
+ }>;
59
+ }
@@ -2,17 +2,34 @@
2
2
  * Pairing (DEC-028, DEC-042): the step at `/authorize` that proves the browser
3
3
  * belongs to the tab's user. The authorization server only knows the
4
4
  * `PairingMethod` interface; which method a session uses comes from its site
5
- * (DEC-045). Today's only method is the 6-digit code shown in the tab; the
6
- * planned identity binding (kb/design/identity-binding.md) is the next.
5
+ * (DEC-045): the 6-digit code shown in the tab, or identity binding at an
6
+ * OpenID Connect provider (`oidc.ts`, DEC-079).
7
7
  */
8
8
  import type { PairingKind } from "../config.js";
9
9
  /** What a pairing method sees of a tab session. */
10
10
  export interface PairingTarget {
11
11
  /** The code state of the session (used by the code method). */
12
12
  readonly codes: PairingCodes;
13
+ /** The binding key of the page's user, if signed in (DEC-077). */
14
+ readonly identity: string | undefined;
15
+ /** Whether the page's "Connect your AI app" dialog is open. */
16
+ readonly pairingOpen: boolean;
13
17
  }
14
- export type PairingResult = {
18
+ /** The pending request a check belongs to (for methods that leave the page). */
19
+ export interface PairingContext {
20
+ requestId: string;
21
+ browserId: string;
22
+ }
23
+ export type PairingResult =
24
+ /** Paired; `subject` is recorded on the grant (identity binding, DEC-077). */
25
+ {
15
26
  ok: true;
27
+ subject?: string;
28
+ }
29
+ /** Continue elsewhere (the provider's sign-in); the answer comes to `callback`. */
30
+ | {
31
+ ok: "redirect";
32
+ location: string;
16
33
  } | {
17
34
  ok: false;
18
35
  /** Plain English for the page (plain text; the page escapes it). */
@@ -35,7 +52,16 @@ export interface PairingMethod {
35
52
  * with reason `rate_limited` without judging. Malformed input and a state
36
53
  * that cannot succeed are answered without calling it (DEC-061).
37
54
  */
38
- check(target: PairingTarget, form: URLSearchParams, admit: () => boolean): PairingResult | Promise<PairingResult>;
55
+ check(target: PairingTarget, form: URLSearchParams, admit: () => boolean, context?: PairingContext): PairingResult | Promise<PairingResult>;
56
+ /** Work before the page is shown (e.g. provider discovery); throws if unavailable. */
57
+ prepare?(): Promise<void>;
58
+ /** Extra origins the page's form may lead to (CSP `form-action`). */
59
+ formTargets?(): readonly string[];
60
+ /** The answer of a method that redirected (`/authorize/callback`). */
61
+ callback?(params: URLSearchParams, browserId: string, targetFor: (requestId: string) => PairingTarget | undefined): Promise<{
62
+ requestId?: string;
63
+ result: PairingResult;
64
+ }>;
39
65
  }
40
66
  /** Code lifetime and attempts (DEC-028). */
41
67
  export declare const PAIRING_CODE_TTL_MS = 120000;
@@ -68,5 +94,5 @@ export declare class PairingCodes {
68
94
  }
69
95
  /** The 6-digit code typed at `/authorize` (DEC-028). */
70
96
  export declare const codePairing: PairingMethod;
71
- /** The methods by kind. `oidc` joins with identity binding (DEC-042). */
97
+ /** The methods without settings; `oidc` is added by the server when configured. */
72
98
  export declare const PAIRING_METHODS: Partial<Record<PairingKind, PairingMethod>>;
package/oauth/server.d.ts CHANGED
@@ -59,6 +59,8 @@ export declare class AuthorizationServer {
59
59
  authorize(req: Request, ip: string): Promise<Response>;
60
60
  /** POST `/authorize`: the pairing form (connect, cancel, try again). */
61
61
  authorizeSubmit(req: Request, ip: string): Promise<Response>;
62
+ /** GET `/authorize/callback`: the provider sends the browser back (DEC-079). */
63
+ authorizeCallback(req: Request, ip: string): Promise<Response>;
62
64
  /** POST `/token`. */
63
65
  token(req: Request, ip: string): Promise<Response>;
64
66
  /** POST `/revoke` (RFC 7009). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ractive-ch/tab-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Relay that lets AI agents call the MCP tools of a web page open in a browser tab",
5
5
  "type": "module",
6
6
  "bin": {
package/protocol.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * One WebSocket per tab session; text frames with one JSON object each. The
5
5
  * normative description is `kb/design/protocol.md` (later `docs/protocol.md`).
6
6
  */
7
+ import { type PairingKind } from "./config.js";
7
8
  export declare const SUBPROTOCOL = "tab-mcp.v1";
8
9
  /** The only MCP revision the relay serves (DEC-033). */
9
10
  export declare const MCP_PROTOCOL_VERSION = "2026-07-28";
@@ -53,6 +54,8 @@ export interface SessionMessage {
53
54
  origin: string;
54
55
  /** The name `/authorize` shows: the site's configured name, else the page's. */
55
56
  appName?: string;
57
+ /** How AI apps pair with this page: `code` or `oidc` (DEC-079). */
58
+ pairing: PairingKind;
56
59
  expiresAt: string;
57
60
  limits: Limits;
58
61
  grants: GrantInfo[];
@@ -93,14 +96,22 @@ export interface GrantRemovedMessage {
93
96
  grantId: string;
94
97
  reason: "revoked" | "reuse_detected" | "expired";
95
98
  }
99
+ /** The page's identity assertion was accepted (DEC-076). */
100
+ export interface IdentityBoundMessage {
101
+ type: "identity.bound";
102
+ /** The `round` of the `identity` this answers, echoed when it had one. */
103
+ round?: number;
104
+ }
96
105
  export interface ErrorMessage {
97
106
  type: "error";
98
107
  code: ErrorCode;
99
108
  message: string;
100
109
  id?: string;
110
+ /** On `identity_refused`: the `round` of the `identity` it answers, if it had one. */
111
+ round?: number;
101
112
  }
102
- export type RelayMessage = SessionMessage | PingMessage | ByeMessage | HttpRequestMessage | HttpCancelMessage | PairingCodeMessage | GrantAddedMessage | GrantRemovedMessage | ErrorMessage;
103
- export type ErrorCode = "bad_message" | "unknown_type" | "unknown_id" | "too_large" | "rate_limited";
113
+ export type RelayMessage = SessionMessage | PingMessage | ByeMessage | HttpRequestMessage | HttpCancelMessage | PairingCodeMessage | GrantAddedMessage | GrantRemovedMessage | IdentityBoundMessage | ErrorMessage;
114
+ export type ErrorCode = "bad_message" | "unknown_type" | "unknown_id" | "too_large" | "rate_limited" | "identity_refused";
104
115
  export interface HelloMessage {
105
116
  type: "hello";
106
117
  library: {
@@ -161,7 +172,18 @@ export interface GrantRevokeMessage {
161
172
  export interface GrantsRevokeAllMessage {
162
173
  type: "grants.revokeAll";
163
174
  }
164
- export type TabMessage = HelloMessage | PairingStartMessage | PairingStopMessage | GrantRevokeMessage | GrantsRevokeAllMessage | PongMessage | ByeMessage | HttpResponseMessage | HttpResponseStartMessage | HttpResponseChunkMessage | HttpResponseEndMessage | TabErrorMessage;
175
+ /** The web app's signed statement of who the page's user is (DEC-076). */
176
+ export interface IdentityMessage {
177
+ type: "identity";
178
+ assertion: string;
179
+ /** The page's number for this assertion, echoed in the answer (a lost answer cannot be mistaken for a later one). */
180
+ round?: number;
181
+ }
182
+ /** The page's user signed out: end every AI connection (DEC-078). */
183
+ export interface IdentityLogoutMessage {
184
+ type: "identity.logout";
185
+ }
186
+ export type TabMessage = HelloMessage | PairingStartMessage | PairingStopMessage | GrantRevokeMessage | GrantsRevokeAllMessage | IdentityMessage | IdentityLogoutMessage | PongMessage | ByeMessage | HttpResponseMessage | HttpResponseStartMessage | HttpResponseChunkMessage | HttpResponseEndMessage | TabErrorMessage;
165
187
  /** Result of parsing one frame from the tab. */
166
188
  export type ParseResult = {
167
189
  ok: true;
package/relay.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type RelayConfig, type StoreKind } from "./config.js";
2
+ import { type KeySource } from "./jwt.js";
2
3
  import { type Logger } from "./log.js";
3
4
  import { type AuthorizationTestMode } from "./oauth/server.js";
4
5
  import type { TokenStore } from "./store/types.js";
@@ -32,6 +33,8 @@ export interface Relay {
32
33
  */
33
34
  export interface RelayTestHooks extends AuthorizationTestMode {
34
35
  pairingCodeTtlMs?: number;
36
+ /** Keys for tab assertions instead of fetching the JWKS URL. */
37
+ assertionKeys?: KeySource;
35
38
  }
36
39
  /** Start a relay. Returns once the server listens. */
37
40
  export declare function createRelay(options?: CreateRelayOptions): Relay;
package/session.d.ts CHANGED
@@ -27,6 +27,10 @@ export interface SessionHost {
27
27
  revokeGrant(session: TabSession, grantId: string): Promise<boolean>;
28
28
  /** The tab revokes every grant of its address ("New address"). */
29
29
  revokeAll(session: TabSession): Promise<void>;
30
+ /** The page proves who its user is (DEC-076); answers the tab itself. */
31
+ bindIdentity(session: TabSession, assertion: string, round?: number): Promise<void>;
32
+ /** The page's user signed out (DEC-078). */
33
+ signOut(session: TabSession): Promise<void>;
30
34
  /** Pairing code lifetime (tests shorten it). */
31
35
  readonly pairingCodeTtlMs?: number;
32
36
  }
@@ -60,9 +64,16 @@ export declare class TabSession {
60
64
  appName: string | undefined;
61
65
  /** Pairing codes while the tab's dialog is open (DEC-028). */
62
66
  readonly codes: PairingCodes;
67
+ /**
68
+ * The binding key of the page's user, once an assertion was accepted
69
+ * (DEC-077); `undefined` while nobody is signed in.
70
+ */
71
+ identity: string | undefined;
63
72
  readonly pending: Map<string, Exchange>;
64
73
  constructor(ws: SocketLike, ip: string, origin: string, site: Site, host: SessionHost);
65
74
  get ready(): boolean;
75
+ /** Whether the page's "Connect your AI app" dialog is open. */
76
+ get pairingOpen(): boolean;
66
77
  /** The limits of this session (its site's). */
67
78
  get limits(): SiteLimits;
68
79
  /** Send one message; false if Bun dropped it under backpressure (DEC-021). */
package/store/types.d.ts CHANGED
@@ -38,6 +38,12 @@ export interface TokenRecord {
38
38
  expiresAt: number;
39
39
  /** Refresh tokens: when it was exchanged for a new one. */
40
40
  usedAt?: number;
41
+ /**
42
+ * Identity binding (DEC-077): the hashed (provider, subject) the grant was
43
+ * made for. Must be stored and returned unchanged; a store that drops it
44
+ * makes every request of such a grant fail as a mismatch.
45
+ */
46
+ subject?: string;
41
47
  }
42
48
  /** One connected AI app of an address. */
43
49
  export interface GrantSummary {
@@ -46,6 +52,8 @@ export interface GrantSummary {
46
52
  clientName: string;
47
53
  scopes: string[];
48
54
  createdAt: number;
55
+ /** The grant's `subject`, if it has one (DEC-077). */
56
+ subject?: string;
49
57
  }
50
58
  export type RevokeSelector = {
51
59
  grantId: string;
package/testing.js CHANGED
@@ -59,6 +59,19 @@ function tokenStoreConformance(name, factory) {
59
59
  const got = await store.get(r.hash);
60
60
  expect(got !== undefined && "usedAt" in got).toBe(false);
61
61
  });
62
+ test("the subject of an identity-bound grant round-trips (DEC-077)", async () => {
63
+ const bound = record({ grantId: "g_bound", subject: "key-of-alice" });
64
+ const plain = record({ grantId: "g_plain" });
65
+ await store.put(bound);
66
+ await store.put(plain);
67
+ expect(await store.get(bound.hash)).toEqual(bound);
68
+ const got = await store.get(plain.hash);
69
+ expect(got !== undefined && "subject" in got).toBe(false);
70
+ const grants = await store.listGrants("addr-a", now);
71
+ expect(grants.find((g) => g.grantId === "g_bound")?.subject).toBe("key-of-alice");
72
+ const unbound = grants.find((g) => g.grantId === "g_plain");
73
+ expect(unbound !== undefined && "subject" in unbound).toBe(false);
74
+ });
62
75
  test("markUsed succeeds once, then fails", async () => {
63
76
  const r = record({ kind: "refresh" });
64
77
  await store.put(r);
@@ -200,5 +213,5 @@ export {
200
213
  tokenStoreConformance
201
214
  };
202
215
 
203
- //# debugId=894AE659D8460D9664756E2164756E21
216
+ //# debugId=533A6EA43546708864756E2164756E21
204
217
  //# sourceMappingURL=testing.js.map
package/testing.js.map CHANGED
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/store/conformance.ts"],
4
4
  "sourcesContent": [
5
- "import { afterEach, beforeEach, describe, expect, test } from \"bun:test\";\nimport type { TokenRecord, TokenStore } from \"./types.ts\";\n\n/**\n * The conformance suite every `TokenStore` must pass (DEC-041). Call it from a\n * `bun test` file with a factory that returns a fresh, empty store:\n *\n * ```ts\n * import { tokenStoreConformance } from \"@ractive-ch/tab-mcp/testing\";\n * tokenStoreConformance(\"RedisTokenStore\", async () => new RedisTokenStore(…));\n * ```\n */\nexport function tokenStoreConformance(\n\tname: string,\n\tfactory: () => TokenStore | Promise<TokenStore>,\n): void {\n\tdescribe(`TokenStore conformance: ${name}`, () => {\n\t\tlet store: TokenStore;\n\t\tconst now = 1_800_000_000_000;\n\t\tlet seq = 0;\n\n\t\tconst until = { now, keepUntil: now + 86_400_000 };\n\n\t\tconst record = (over: Partial<TokenRecord> = {}): TokenRecord => {\n\t\t\tseq++;\n\t\t\treturn {\n\t\t\t\thash: `hash-${seq}`,\n\t\t\t\tkind: \"access\",\n\t\t\t\tgrantId: \"g_1\",\n\t\t\t\taddressId: \"addr-a\",\n\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\tclientName: \"Client\",\n\t\t\t\tscopes: [\"tools\"],\n\t\t\t\tgrantCreatedAt: now - 1000,\n\t\t\t\tissuedAt: now,\n\t\t\t\texpiresAt: now + 3_600_000,\n\t\t\t\t...over,\n\t\t\t};\n\t\t};\n\n\t\tbeforeEach(async () => {\n\t\t\tstore = await factory();\n\t\t});\n\n\t\tafterEach(async () => {\n\t\t\tawait store.close?.();\n\t\t});\n\n\t\ttest(\"put then get returns an equal record\", async () => {\n\t\t\tconst r = record({ scopes: [\"tools\", \"extra\"] });\n\t\t\texpect(await store.put(r)).toBe(true);\n\t\t\texpect(await store.get(r.hash)).toEqual(r);\n\t\t});\n\n\t\ttest(\"get of an unknown hash is undefined\", async () => {\n\t\t\texpect(await store.get(\"nope\")).toBeUndefined();\n\t\t});\n\n\t\ttest(\"put replaces the record with the same hash\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tawait store.put({ ...r, clientName: \"Renamed\", expiresAt: now + 5 });\n\t\t\tconst got = await store.get(r.hash);\n\t\t\texpect(got?.clientName).toBe(\"Renamed\");\n\t\t\texpect(got?.expiresAt).toBe(now + 5);\n\t\t});\n\n\t\ttest(\"get returns expired and used records unchanged\", async () => {\n\t\t\tconst expired = record({ expiresAt: now - 1 });\n\t\t\tconst used = record({ kind: \"refresh\", usedAt: now - 10 });\n\t\t\tawait store.put(expired);\n\t\t\tawait store.put(used);\n\t\t\texpect(await store.get(expired.hash)).toEqual(expired);\n\t\t\texpect(await store.get(used.hash)).toEqual(used);\n\t\t});\n\n\t\ttest(\"a record without usedAt comes back without the key\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tconst got = await store.get(r.hash);\n\t\t\texpect(got !== undefined && \"usedAt\" in got).toBe(false);\n\t\t});\n\n\t\ttest(\"markUsed succeeds once, then fails\", async () => {\n\t\t\tconst r = record({ kind: \"refresh\" });\n\t\t\tawait store.put(r);\n\t\t\texpect(await store.markUsed(r.hash, now + 1)).toBe(true);\n\t\t\texpect(await store.markUsed(r.hash, now + 2)).toBe(false);\n\t\t\texpect((await store.get(r.hash))?.usedAt).toBe(now + 1);\n\t\t});\n\n\t\ttest(\"markUsed of an unknown hash fails\", async () => {\n\t\t\texpect(await store.markUsed(\"nope\", now)).toBe(false);\n\t\t});\n\n\t\ttest(\"concurrent markUsed calls: exactly one wins\", async () => {\n\t\t\tconst r = record({ kind: \"refresh\" });\n\t\t\tawait store.put(r);\n\t\t\tconst results = await Promise.all(\n\t\t\t\tArray.from({ length: 10 }, (_, i) => store.markUsed(r.hash, now + i)),\n\t\t\t);\n\t\t\texpect(results.filter(Boolean)).toHaveLength(1);\n\t\t});\n\n\t\ttest(\"revoke by grant deletes only that grant's tokens\", async () => {\n\t\t\tconst a = record({ grantId: \"g_a\" });\n\t\t\tconst a2 = record({ grantId: \"g_a\", kind: \"refresh\" });\n\t\t\tconst b = record({ grantId: \"g_b\" });\n\t\t\tfor (const r of [a, a2, b]) await store.put(r);\n\t\t\texpect(await store.revoke({ grantId: \"g_a\" }, until)).toBe(2);\n\t\t\texpect(await store.get(a.hash)).toBeUndefined();\n\t\t\texpect(await store.get(a2.hash)).toBeUndefined();\n\t\t\texpect(await store.get(b.hash)).toEqual(b);\n\t\t});\n\n\t\ttest(\"revoke by address deletes only that address's tokens\", async () => {\n\t\t\tconst a = record({ addressId: \"addr-a\", grantId: \"g_a\" });\n\t\t\tconst b = record({ addressId: \"addr-b\", grantId: \"g_b\" });\n\t\t\tawait store.put(a);\n\t\t\tawait store.put(b);\n\t\t\texpect(await store.revoke({ addressId: \"addr-a\" }, until)).toBe(1);\n\t\t\texpect(await store.get(a.hash)).toBeUndefined();\n\t\t\texpect(await store.get(b.hash)).toEqual(b);\n\t\t});\n\n\t\ttest(\"revoke of nothing returns 0\", async () => {\n\t\t\texpect(await store.revoke({ grantId: \"none\" }, until)).toBe(0);\n\t\t});\n\n\t\ttest(\"a revoked grant gets no new tokens until its tombstone expires\", async () => {\n\t\t\tawait store.revoke({ grantId: \"g_dead\" }, until);\n\t\t\tconst late = record({ grantId: \"g_dead\", kind: \"refresh\" });\n\t\t\texpect(await store.put(late)).toBe(false);\n\t\t\texpect(await store.get(late.hash)).toBeUndefined();\n\t\t\texpect(await store.put(record({ grantId: \"g_alive\" }))).toBe(true);\n\t\t\tawait store.deleteExpired(until.keepUntil);\n\t\t\texpect(await store.put(record({ grantId: \"g_dead\" }))).toBe(true);\n\t\t});\n\n\t\ttest(\"a revoked address refuses grants created up to the revocation, not later ones\", async () => {\n\t\t\tawait store.revoke({ addressId: \"addr-a\" }, until);\n\t\t\texpect(\n\t\t\t\tawait store.put(record({ grantId: \"g_before\", grantCreatedAt: now })),\n\t\t\t).toBe(false);\n\t\t\texpect(\n\t\t\t\tawait store.put(\n\t\t\t\t\trecord({ grantId: \"g_after\", grantCreatedAt: now + 1 }),\n\t\t\t\t),\n\t\t\t).toBe(true);\n\t\t\texpect(\n\t\t\t\tawait store.put(\n\t\t\t\t\trecord({\n\t\t\t\t\t\tgrantId: \"g_elsewhere\",\n\t\t\t\t\t\taddressId: \"addr-b\",\n\t\t\t\t\t\tgrantCreatedAt: now,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t).toBe(true);\n\t\t});\n\n\t\ttest(\"a put racing a revocation of its grant never survives it\", async () => {\n\t\t\tfor (let i = 0; i < 20; i++) {\n\t\t\t\tconst r = record({ grantId: `g_race_${i}`, kind: \"refresh\" });\n\t\t\t\tawait Promise.all([\n\t\t\t\t\tstore.put(r),\n\t\t\t\t\tstore.revoke({ grantId: r.grantId }, until),\n\t\t\t\t]);\n\t\t\t\texpect(await store.get(r.hash)).toBeUndefined();\n\t\t\t}\n\t\t});\n\n\t\ttest(\"deleteExpired removes records with expiresAt <= now only\", async () => {\n\t\t\tconst gone = record({ expiresAt: now - 1 });\n\t\t\tconst edge = record({ expiresAt: now });\n\t\t\tconst kept = record({ expiresAt: now + 1 });\n\t\t\tfor (const r of [gone, edge, kept]) await store.put(r);\n\t\t\texpect(await store.deleteExpired(now)).toBe(2);\n\t\t\texpect(await store.get(gone.hash)).toBeUndefined();\n\t\t\texpect(await store.get(edge.hash)).toBeUndefined();\n\t\t\texpect(await store.get(kept.hash)).toEqual(kept);\n\t\t});\n\n\t\ttest(\"listGrants groups live tokens of one address by grant\", async () => {\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_new\",\n\t\t\t\t\tgrantCreatedAt: now - 10,\n\t\t\t\t\tclientName: \"New\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tgrantCreatedAt: now - 500,\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tkind: \"refresh\",\n\t\t\t\t\tgrantCreatedAt: now - 500,\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(record({ grantId: \"g_other\", addressId: \"addr-b\" }));\n\t\t\texpect(await store.listGrants(\"addr-a\", now)).toEqual([\n\t\t\t\t{\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t\tcreatedAt: now - 500,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tgrantId: \"g_new\",\n\t\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\t\tclientName: \"New\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t\tcreatedAt: now - 10,\n\t\t\t\t},\n\t\t\t]);\n\t\t});\n\n\t\ttest(\"listGrants leaves out grants without a usable token\", async () => {\n\t\t\tawait store.put(record({ grantId: \"g_expired\", expiresAt: now }));\n\t\t\tawait store.put(\n\t\t\t\trecord({ grantId: \"g_used\", kind: \"refresh\", usedAt: now - 1 }),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({ grantId: \"g_live\", kind: \"refresh\", expiresAt: now + 1 }),\n\t\t\t);\n\t\t\tconst grants = await store.listGrants(\"addr-a\", now);\n\t\t\texpect(grants.map((g) => g.grantId)).toEqual([\"g_live\"]);\n\t\t});\n\n\t\ttest(\"listGrants of an unknown address is empty\", async () => {\n\t\t\texpect(await store.listGrants(\"nobody\", now)).toEqual([]);\n\t\t});\n\n\t\ttest(\"changing a returned record does not change the store\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tconst got = await store.get(r.hash);\n\t\t\tif (got === undefined) throw new Error(\"missing\");\n\t\t\tgot.scopes.push(\"mutated\");\n\t\t\tgot.expiresAt = 0;\n\t\t\texpect(await store.get(r.hash)).toEqual(r);\n\t\t});\n\t});\n}\n"
5
+ "import { afterEach, beforeEach, describe, expect, test } from \"bun:test\";\nimport type { TokenRecord, TokenStore } from \"./types.ts\";\n\n/**\n * The conformance suite every `TokenStore` must pass (DEC-041). Call it from a\n * `bun test` file with a factory that returns a fresh, empty store:\n *\n * ```ts\n * import { tokenStoreConformance } from \"@ractive-ch/tab-mcp/testing\";\n * tokenStoreConformance(\"RedisTokenStore\", async () => new RedisTokenStore(…));\n * ```\n */\nexport function tokenStoreConformance(\n\tname: string,\n\tfactory: () => TokenStore | Promise<TokenStore>,\n): void {\n\tdescribe(`TokenStore conformance: ${name}`, () => {\n\t\tlet store: TokenStore;\n\t\tconst now = 1_800_000_000_000;\n\t\tlet seq = 0;\n\n\t\tconst until = { now, keepUntil: now + 86_400_000 };\n\n\t\tconst record = (over: Partial<TokenRecord> = {}): TokenRecord => {\n\t\t\tseq++;\n\t\t\treturn {\n\t\t\t\thash: `hash-${seq}`,\n\t\t\t\tkind: \"access\",\n\t\t\t\tgrantId: \"g_1\",\n\t\t\t\taddressId: \"addr-a\",\n\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\tclientName: \"Client\",\n\t\t\t\tscopes: [\"tools\"],\n\t\t\t\tgrantCreatedAt: now - 1000,\n\t\t\t\tissuedAt: now,\n\t\t\t\texpiresAt: now + 3_600_000,\n\t\t\t\t...over,\n\t\t\t};\n\t\t};\n\n\t\tbeforeEach(async () => {\n\t\t\tstore = await factory();\n\t\t});\n\n\t\tafterEach(async () => {\n\t\t\tawait store.close?.();\n\t\t});\n\n\t\ttest(\"put then get returns an equal record\", async () => {\n\t\t\tconst r = record({ scopes: [\"tools\", \"extra\"] });\n\t\t\texpect(await store.put(r)).toBe(true);\n\t\t\texpect(await store.get(r.hash)).toEqual(r);\n\t\t});\n\n\t\ttest(\"get of an unknown hash is undefined\", async () => {\n\t\t\texpect(await store.get(\"nope\")).toBeUndefined();\n\t\t});\n\n\t\ttest(\"put replaces the record with the same hash\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tawait store.put({ ...r, clientName: \"Renamed\", expiresAt: now + 5 });\n\t\t\tconst got = await store.get(r.hash);\n\t\t\texpect(got?.clientName).toBe(\"Renamed\");\n\t\t\texpect(got?.expiresAt).toBe(now + 5);\n\t\t});\n\n\t\ttest(\"get returns expired and used records unchanged\", async () => {\n\t\t\tconst expired = record({ expiresAt: now - 1 });\n\t\t\tconst used = record({ kind: \"refresh\", usedAt: now - 10 });\n\t\t\tawait store.put(expired);\n\t\t\tawait store.put(used);\n\t\t\texpect(await store.get(expired.hash)).toEqual(expired);\n\t\t\texpect(await store.get(used.hash)).toEqual(used);\n\t\t});\n\n\t\ttest(\"a record without usedAt comes back without the key\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tconst got = await store.get(r.hash);\n\t\t\texpect(got !== undefined && \"usedAt\" in got).toBe(false);\n\t\t});\n\n\t\ttest(\"the subject of an identity-bound grant round-trips (DEC-077)\", async () => {\n\t\t\tconst bound = record({ grantId: \"g_bound\", subject: \"key-of-alice\" });\n\t\t\tconst plain = record({ grantId: \"g_plain\" });\n\t\t\tawait store.put(bound);\n\t\t\tawait store.put(plain);\n\t\t\texpect(await store.get(bound.hash)).toEqual(bound);\n\t\t\tconst got = await store.get(plain.hash);\n\t\t\texpect(got !== undefined && \"subject\" in got).toBe(false);\n\t\t\tconst grants = await store.listGrants(\"addr-a\", now);\n\t\t\texpect(grants.find((g) => g.grantId === \"g_bound\")?.subject).toBe(\n\t\t\t\t\"key-of-alice\",\n\t\t\t);\n\t\t\tconst unbound = grants.find((g) => g.grantId === \"g_plain\");\n\t\t\texpect(unbound !== undefined && \"subject\" in unbound).toBe(false);\n\t\t});\n\n\t\ttest(\"markUsed succeeds once, then fails\", async () => {\n\t\t\tconst r = record({ kind: \"refresh\" });\n\t\t\tawait store.put(r);\n\t\t\texpect(await store.markUsed(r.hash, now + 1)).toBe(true);\n\t\t\texpect(await store.markUsed(r.hash, now + 2)).toBe(false);\n\t\t\texpect((await store.get(r.hash))?.usedAt).toBe(now + 1);\n\t\t});\n\n\t\ttest(\"markUsed of an unknown hash fails\", async () => {\n\t\t\texpect(await store.markUsed(\"nope\", now)).toBe(false);\n\t\t});\n\n\t\ttest(\"concurrent markUsed calls: exactly one wins\", async () => {\n\t\t\tconst r = record({ kind: \"refresh\" });\n\t\t\tawait store.put(r);\n\t\t\tconst results = await Promise.all(\n\t\t\t\tArray.from({ length: 10 }, (_, i) => store.markUsed(r.hash, now + i)),\n\t\t\t);\n\t\t\texpect(results.filter(Boolean)).toHaveLength(1);\n\t\t});\n\n\t\ttest(\"revoke by grant deletes only that grant's tokens\", async () => {\n\t\t\tconst a = record({ grantId: \"g_a\" });\n\t\t\tconst a2 = record({ grantId: \"g_a\", kind: \"refresh\" });\n\t\t\tconst b = record({ grantId: \"g_b\" });\n\t\t\tfor (const r of [a, a2, b]) await store.put(r);\n\t\t\texpect(await store.revoke({ grantId: \"g_a\" }, until)).toBe(2);\n\t\t\texpect(await store.get(a.hash)).toBeUndefined();\n\t\t\texpect(await store.get(a2.hash)).toBeUndefined();\n\t\t\texpect(await store.get(b.hash)).toEqual(b);\n\t\t});\n\n\t\ttest(\"revoke by address deletes only that address's tokens\", async () => {\n\t\t\tconst a = record({ addressId: \"addr-a\", grantId: \"g_a\" });\n\t\t\tconst b = record({ addressId: \"addr-b\", grantId: \"g_b\" });\n\t\t\tawait store.put(a);\n\t\t\tawait store.put(b);\n\t\t\texpect(await store.revoke({ addressId: \"addr-a\" }, until)).toBe(1);\n\t\t\texpect(await store.get(a.hash)).toBeUndefined();\n\t\t\texpect(await store.get(b.hash)).toEqual(b);\n\t\t});\n\n\t\ttest(\"revoke of nothing returns 0\", async () => {\n\t\t\texpect(await store.revoke({ grantId: \"none\" }, until)).toBe(0);\n\t\t});\n\n\t\ttest(\"a revoked grant gets no new tokens until its tombstone expires\", async () => {\n\t\t\tawait store.revoke({ grantId: \"g_dead\" }, until);\n\t\t\tconst late = record({ grantId: \"g_dead\", kind: \"refresh\" });\n\t\t\texpect(await store.put(late)).toBe(false);\n\t\t\texpect(await store.get(late.hash)).toBeUndefined();\n\t\t\texpect(await store.put(record({ grantId: \"g_alive\" }))).toBe(true);\n\t\t\tawait store.deleteExpired(until.keepUntil);\n\t\t\texpect(await store.put(record({ grantId: \"g_dead\" }))).toBe(true);\n\t\t});\n\n\t\ttest(\"a revoked address refuses grants created up to the revocation, not later ones\", async () => {\n\t\t\tawait store.revoke({ addressId: \"addr-a\" }, until);\n\t\t\texpect(\n\t\t\t\tawait store.put(record({ grantId: \"g_before\", grantCreatedAt: now })),\n\t\t\t).toBe(false);\n\t\t\texpect(\n\t\t\t\tawait store.put(\n\t\t\t\t\trecord({ grantId: \"g_after\", grantCreatedAt: now + 1 }),\n\t\t\t\t),\n\t\t\t).toBe(true);\n\t\t\texpect(\n\t\t\t\tawait store.put(\n\t\t\t\t\trecord({\n\t\t\t\t\t\tgrantId: \"g_elsewhere\",\n\t\t\t\t\t\taddressId: \"addr-b\",\n\t\t\t\t\t\tgrantCreatedAt: now,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t).toBe(true);\n\t\t});\n\n\t\ttest(\"a put racing a revocation of its grant never survives it\", async () => {\n\t\t\tfor (let i = 0; i < 20; i++) {\n\t\t\t\tconst r = record({ grantId: `g_race_${i}`, kind: \"refresh\" });\n\t\t\t\tawait Promise.all([\n\t\t\t\t\tstore.put(r),\n\t\t\t\t\tstore.revoke({ grantId: r.grantId }, until),\n\t\t\t\t]);\n\t\t\t\texpect(await store.get(r.hash)).toBeUndefined();\n\t\t\t}\n\t\t});\n\n\t\ttest(\"deleteExpired removes records with expiresAt <= now only\", async () => {\n\t\t\tconst gone = record({ expiresAt: now - 1 });\n\t\t\tconst edge = record({ expiresAt: now });\n\t\t\tconst kept = record({ expiresAt: now + 1 });\n\t\t\tfor (const r of [gone, edge, kept]) await store.put(r);\n\t\t\texpect(await store.deleteExpired(now)).toBe(2);\n\t\t\texpect(await store.get(gone.hash)).toBeUndefined();\n\t\t\texpect(await store.get(edge.hash)).toBeUndefined();\n\t\t\texpect(await store.get(kept.hash)).toEqual(kept);\n\t\t});\n\n\t\ttest(\"listGrants groups live tokens of one address by grant\", async () => {\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_new\",\n\t\t\t\t\tgrantCreatedAt: now - 10,\n\t\t\t\t\tclientName: \"New\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tgrantCreatedAt: now - 500,\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tkind: \"refresh\",\n\t\t\t\t\tgrantCreatedAt: now - 500,\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait store.put(record({ grantId: \"g_other\", addressId: \"addr-b\" }));\n\t\t\texpect(await store.listGrants(\"addr-a\", now)).toEqual([\n\t\t\t\t{\n\t\t\t\t\tgrantId: \"g_old\",\n\t\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\t\tclientName: \"Old\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t\tcreatedAt: now - 500,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tgrantId: \"g_new\",\n\t\t\t\t\tclientId: \"https://client.example/cimd\",\n\t\t\t\t\tclientName: \"New\",\n\t\t\t\t\tscopes: [\"tools\"],\n\t\t\t\t\tcreatedAt: now - 10,\n\t\t\t\t},\n\t\t\t]);\n\t\t});\n\n\t\ttest(\"listGrants leaves out grants without a usable token\", async () => {\n\t\t\tawait store.put(record({ grantId: \"g_expired\", expiresAt: now }));\n\t\t\tawait store.put(\n\t\t\t\trecord({ grantId: \"g_used\", kind: \"refresh\", usedAt: now - 1 }),\n\t\t\t);\n\t\t\tawait store.put(\n\t\t\t\trecord({ grantId: \"g_live\", kind: \"refresh\", expiresAt: now + 1 }),\n\t\t\t);\n\t\t\tconst grants = await store.listGrants(\"addr-a\", now);\n\t\t\texpect(grants.map((g) => g.grantId)).toEqual([\"g_live\"]);\n\t\t});\n\n\t\ttest(\"listGrants of an unknown address is empty\", async () => {\n\t\t\texpect(await store.listGrants(\"nobody\", now)).toEqual([]);\n\t\t});\n\n\t\ttest(\"changing a returned record does not change the store\", async () => {\n\t\t\tconst r = record();\n\t\t\tawait store.put(r);\n\t\t\tconst got = await store.get(r.hash);\n\t\t\tif (got === undefined) throw new Error(\"missing\");\n\t\t\tgot.scopes.push(\"mutated\");\n\t\t\tgot.expiresAt = 0;\n\t\t\texpect(await store.get(r.hash)).toEqual(r);\n\t\t});\n\t});\n}\n"
6
6
  ],
7
- "mappings": ";;AAAA;AAYO,SAAS,qBAAqB,CACpC,MACA,SACO;AAAA,EACP,SAAS,2BAA2B,QAAQ,MAAM;AAAA,IACjD,IAAI;AAAA,IACJ,MAAM,MAAM;AAAA,IACZ,IAAI,MAAM;AAAA,IAEV,MAAM,QAAQ,EAAE,KAAK,WAAW,MAAM,SAAW;AAAA,IAEjD,MAAM,SAAS,CAAC,OAA6B,CAAC,MAAmB;AAAA,MAChE;AAAA,MACA,OAAO;AAAA,QACN,MAAM,QAAQ;AAAA,QACd,MAAM;AAAA,QACN,SAAS;AAAA,QACT,WAAW;AAAA,QACX,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,QAAQ,CAAC,OAAO;AAAA,QAChB,gBAAgB,MAAM;AAAA,QACtB,UAAU;AAAA,QACV,WAAW,MAAM;AAAA,WACd;AAAA,MACJ;AAAA;AAAA,IAGD,WAAW,YAAY;AAAA,MACtB,QAAQ,MAAM,QAAQ;AAAA,KACtB;AAAA,IAED,UAAU,YAAY;AAAA,MACrB,MAAM,MAAM,QAAQ;AAAA,KACpB;AAAA,IAED,KAAK,wCAAwC,YAAY;AAAA,MACxD,MAAM,IAAI,OAAO,EAAE,QAAQ,CAAC,SAAS,OAAO,EAAE,CAAC;AAAA,MAC/C,OAAO,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI;AAAA,MACpC,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,KACzC;AAAA,IAED,KAAK,uCAAuC,YAAY;AAAA,MACvD,OAAO,MAAM,MAAM,IAAI,MAAM,CAAC,EAAE,cAAc;AAAA,KAC9C;AAAA,IAED,KAAK,8CAA8C,YAAY;AAAA,MAC9D,MAAM,IAAI,OAAO;AAAA,MACjB,MAAM,MAAM,IAAI,CAAC;AAAA,MACjB,MAAM,MAAM,IAAI,KAAK,GAAG,YAAY,WAAW,WAAW,MAAM,EAAE,CAAC;AAAA,MACnE,MAAM,MAAM,MAAM,MAAM,IAAI,EAAE,IAAI;AAAA,MAClC,OAAO,KAAK,UAAU,EAAE,KAAK,SAAS;AAAA,MACtC,OAAO,KAAK,SAAS,EAAE,KAAK,MAAM,CAAC;AAAA,KACnC;AAAA,IAED,KAAK,kDAAkD,YAAY;AAAA,MAClE,MAAM,UAAU,OAAO,EAAE,WAAW,MAAM,EAAE,CAAC;AAAA,MAC7C,MAAM,OAAO,OAAO,EAAE,MAAM,WAAW,QAAQ,MAAM,GAAG,CAAC;AAAA,MACzD,MAAM,MAAM,IAAI,OAAO;AAAA,MACvB,MAAM,MAAM,IAAI,IAAI;AAAA,MACpB,OAAO,MAAM,MAAM,IAAI,QAAQ,IAAI,CAAC,EAAE,QAAQ,OAAO;AAAA,MACrD,OAAO,MAAM,MAAM,IAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,IAAI;AAAA,KAC/C;AAAA,IAED,KAAK,sDAAsD,YAAY;AAAA,MACtE,MAAM,IAAI,OAAO;AAAA,MACjB,MAAM,MAAM,IAAI,CAAC;AAAA,MACjB,MAAM,MAAM,MAAM,MAAM,IAAI,EAAE,IAAI;AAAA,MAClC,OAAO,QAAQ,aAAa,YAAY,GAAG,EAAE,KAAK,KAAK;AAAA,KACvD;AAAA,IAED,KAAK,sCAAsC,YAAY;AAAA,MACtD,MAAM,IAAI,OAAO,EAAE,MAAM,UAAU,CAAC;AAAA,MACpC,MAAM,MAAM,IAAI,CAAC;AAAA,MACjB,OAAO,MAAM,MAAM,SAAS,EAAE,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK,IAAI;AAAA,MACvD,OAAO,MAAM,MAAM,SAAS,EAAE,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK,KAAK;AAAA,MACxD,QAAQ,MAAM,MAAM,IAAI,EAAE,IAAI,IAAI,MAAM,EAAE,KAAK,MAAM,CAAC;AAAA,KACtD;AAAA,IAED,KAAK,qCAAqC,YAAY;AAAA,MACrD,OAAO,MAAM,MAAM,SAAS,QAAQ,GAAG,CAAC,EAAE,KAAK,KAAK;AAAA,KACpD;AAAA,IAED,KAAK,+CAA+C,YAAY;AAAA,MAC/D,MAAM,IAAI,OAAO,EAAE,MAAM,UAAU,CAAC;AAAA,MACpC,MAAM,MAAM,IAAI,CAAC;AAAA,MACjB,MAAM,UAAU,MAAM,QAAQ,IAC7B,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,MAAM,MAAM,SAAS,EAAE,MAAM,MAAM,CAAC,CAAC,CACrE;AAAA,MACA,OAAO,QAAQ,OAAO,OAAO,CAAC,EAAE,aAAa,CAAC;AAAA,KAC9C;AAAA,IAED,KAAK,oDAAoD,YAAY;AAAA,MACpE,MAAM,IAAI,OAAO,EAAE,SAAS,MAAM,CAAC;AAAA,MACnC,MAAM,KAAK,OAAO,EAAE,SAAS,OAAO,MAAM,UAAU,CAAC;AAAA,MACrD,MAAM,IAAI,OAAO,EAAE,SAAS,MAAM,CAAC;AAAA,MACnC,WAAW,KAAK,CAAC,GAAG,IAAI,CAAC;AAAA,QAAG,MAAM,MAAM,IAAI,CAAC;AAAA,MAC7C,OAAO,MAAM,MAAM,OAAO,EAAE,SAAS,MAAM,GAAG,KAAK,CAAC,EAAE,KAAK,CAAC;AAAA,MAC5D,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,cAAc;AAAA,MAC9C,OAAO,MAAM,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,cAAc;AAAA,MAC/C,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,KACzC;AAAA,IAED,KAAK,wDAAwD,YAAY;AAAA,MACxE,MAAM,IAAI,OAAO,EAAE,WAAW,UAAU,SAAS,MAAM,CAAC;AAAA,MACxD,MAAM,IAAI,OAAO,EAAE,WAAW,UAAU,SAAS,MAAM,CAAC;AAAA,MACxD,MAAM,MAAM,IAAI,CAAC;AAAA,MACjB,MAAM,MAAM,IAAI,CAAC;AAAA,MACjB,OAAO,MAAM,MAAM,OAAO,EAAE,WAAW,SAAS,GAAG,KAAK,CAAC,EAAE,KAAK,CAAC;AAAA,MACjE,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,cAAc;AAAA,MAC9C,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,KACzC;AAAA,IAED,KAAK,+BAA+B,YAAY;AAAA,MAC/C,OAAO,MAAM,MAAM,OAAO,EAAE,SAAS,OAAO,GAAG,KAAK,CAAC,EAAE,KAAK,CAAC;AAAA,KAC7D;AAAA,IAED,KAAK,kEAAkE,YAAY;AAAA,MAClF,MAAM,MAAM,OAAO,EAAE,SAAS,SAAS,GAAG,KAAK;AAAA,MAC/C,MAAM,OAAO,OAAO,EAAE,SAAS,UAAU,MAAM,UAAU,CAAC;AAAA,MAC1D,OAAO,MAAM,MAAM,IAAI,IAAI,CAAC,EAAE,KAAK,KAAK;AAAA,MACxC,OAAO,MAAM,MAAM,IAAI,KAAK,IAAI,CAAC,EAAE,cAAc;AAAA,MACjD,OAAO,MAAM,MAAM,IAAI,OAAO,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI;AAAA,MACjE,MAAM,MAAM,cAAc,MAAM,SAAS;AAAA,MACzC,OAAO,MAAM,MAAM,IAAI,OAAO,EAAE,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI;AAAA,KAChE;AAAA,IAED,KAAK,iFAAiF,YAAY;AAAA,MACjG,MAAM,MAAM,OAAO,EAAE,WAAW,SAAS,GAAG,KAAK;AAAA,MACjD,OACC,MAAM,MAAM,IAAI,OAAO,EAAE,SAAS,YAAY,gBAAgB,IAAI,CAAC,CAAC,CACrE,EAAE,KAAK,KAAK;AAAA,MACZ,OACC,MAAM,MAAM,IACX,OAAO,EAAE,SAAS,WAAW,gBAAgB,MAAM,EAAE,CAAC,CACvD,CACD,EAAE,KAAK,IAAI;AAAA,MACX,OACC,MAAM,MAAM,IACX,OAAO;AAAA,QACN,SAAS;AAAA,QACT,WAAW;AAAA,QACX,gBAAgB;AAAA,MACjB,CAAC,CACF,CACD,EAAE,KAAK,IAAI;AAAA,KACX;AAAA,IAED,KAAK,4DAA4D,YAAY;AAAA,MAC5E,SAAS,IAAI,EAAG,IAAI,IAAI,KAAK;AAAA,QAC5B,MAAM,IAAI,OAAO,EAAE,SAAS,UAAU,KAAK,MAAM,UAAU,CAAC;AAAA,QAC5D,MAAM,QAAQ,IAAI;AAAA,UACjB,MAAM,IAAI,CAAC;AAAA,UACX,MAAM,OAAO,EAAE,SAAS,EAAE,QAAQ,GAAG,KAAK;AAAA,QAC3C,CAAC;AAAA,QACD,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,cAAc;AAAA,MAC/C;AAAA,KACA;AAAA,IAED,KAAK,4DAA4D,YAAY;AAAA,MAC5E,MAAM,OAAO,OAAO,EAAE,WAAW,MAAM,EAAE,CAAC;AAAA,MAC1C,MAAM,OAAO,OAAO,EAAE,WAAW,IAAI,CAAC;AAAA,MACtC,MAAM,OAAO,OAAO,EAAE,WAAW,MAAM,EAAE,CAAC;AAAA,MAC1C,WAAW,KAAK,CAAC,MAAM,MAAM,IAAI;AAAA,QAAG,MAAM,MAAM,IAAI,CAAC;AAAA,MACrD,OAAO,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,KAAK,CAAC;AAAA,MAC7C,OAAO,MAAM,MAAM,IAAI,KAAK,IAAI,CAAC,EAAE,cAAc;AAAA,MACjD,OAAO,MAAM,MAAM,IAAI,KAAK,IAAI,CAAC,EAAE,cAAc;AAAA,MACjD,OAAO,MAAM,MAAM,IAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,IAAI;AAAA,KAC/C;AAAA,IAED,KAAK,yDAAyD,YAAY;AAAA,MACzE,MAAM,MAAM,IACX,OAAO;AAAA,QACN,SAAS;AAAA,QACT,gBAAgB,MAAM;AAAA,QACtB,YAAY;AAAA,MACb,CAAC,CACF;AAAA,MACA,MAAM,MAAM,IACX,OAAO;AAAA,QACN,SAAS;AAAA,QACT,gBAAgB,MAAM;AAAA,QACtB,YAAY;AAAA,QACZ,QAAQ,CAAC,OAAO;AAAA,MACjB,CAAC,CACF;AAAA,MACA,MAAM,MAAM,IACX,OAAO;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,QACN,gBAAgB,MAAM;AAAA,QACtB,YAAY;AAAA,MACb,CAAC,CACF;AAAA,MACA,MAAM,MAAM,IAAI,OAAO,EAAE,SAAS,WAAW,WAAW,SAAS,CAAC,CAAC;AAAA,MACnE,OAAO,MAAM,MAAM,WAAW,UAAU,GAAG,CAAC,EAAE,QAAQ;AAAA,QACrD;AAAA,UACC,SAAS;AAAA,UACT,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,QAAQ,CAAC,OAAO;AAAA,UAChB,WAAW,MAAM;AAAA,QAClB;AAAA,QACA;AAAA,UACC,SAAS;AAAA,UACT,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,QAAQ,CAAC,OAAO;AAAA,UAChB,WAAW,MAAM;AAAA,QAClB;AAAA,MACD,CAAC;AAAA,KACD;AAAA,IAED,KAAK,uDAAuD,YAAY;AAAA,MACvE,MAAM,MAAM,IAAI,OAAO,EAAE,SAAS,aAAa,WAAW,IAAI,CAAC,CAAC;AAAA,MAChE,MAAM,MAAM,IACX,OAAO,EAAE,SAAS,UAAU,MAAM,WAAW,QAAQ,MAAM,EAAE,CAAC,CAC/D;AAAA,MACA,MAAM,MAAM,IACX,OAAO,EAAE,SAAS,UAAU,MAAM,WAAW,WAAW,MAAM,EAAE,CAAC,CAClE;AAAA,MACA,MAAM,SAAS,MAAM,MAAM,WAAW,UAAU,GAAG;AAAA,MACnD,OAAO,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC;AAAA,KACvD;AAAA,IAED,KAAK,6CAA6C,YAAY;AAAA,MAC7D,OAAO,MAAM,MAAM,WAAW,UAAU,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,KACxD;AAAA,IAED,KAAK,wDAAwD,YAAY;AAAA,MACxE,MAAM,IAAI,OAAO;AAAA,MACjB,MAAM,MAAM,IAAI,CAAC;AAAA,MACjB,MAAM,MAAM,MAAM,MAAM,IAAI,EAAE,IAAI;AAAA,MAClC,IAAI,QAAQ;AAAA,QAAW,MAAM,IAAI,MAAM,SAAS;AAAA,MAChD,IAAI,OAAO,KAAK,SAAS;AAAA,MACzB,IAAI,YAAY;AAAA,MAChB,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,KACzC;AAAA,GACD;AAAA;",
8
- "debugId": "894AE659D8460D9664756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "533A6EA43546708864756E2164756E21",
9
9
  "names": []
10
10
  }
package/tokens.d.ts CHANGED
@@ -13,6 +13,8 @@ export interface GrantInput {
13
13
  clientName: string;
14
14
  scopes?: string[];
15
15
  grantCreatedAt?: number;
16
+ /** The hashed identity of an identity-bound grant (DEC-077). */
17
+ subject?: string;
16
18
  }
17
19
  /**
18
20
  * Issue an access token for a grant and store its hash, without OAuth. An
@@ -31,8 +33,9 @@ export declare function issueAccessToken(store: TokenStore, grant: GrantInput, o
31
33
  * beyond `grantEndsAt` (DEC-062). Returns `undefined` if the store refuses
32
34
  * them because the grant was revoked meanwhile (its tombstone, DEC-063).
33
35
  */
34
- export declare function issueTokenPair(store: TokenStore, grant: Required<Omit<GrantInput, "scopes">> & {
36
+ export declare function issueTokenPair(store: TokenStore, grant: Required<Omit<GrantInput, "scopes" | "subject">> & {
35
37
  scopes: string[];
38
+ subject?: string;
36
39
  }, options: {
37
40
  accessTtlMs: number;
38
41
  refreshTtlMs: number;