@poppykit/server 0.3.1 → 0.5.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.
Files changed (57) hide show
  1. package/API.md +11 -3
  2. package/AUTH.md +120 -95
  3. package/CHANGELOG.md +18 -0
  4. package/CONFORMANCE.md +73 -0
  5. package/CONTRIBUTING.md +13 -10
  6. package/DELIVERY.md +21 -1
  7. package/INTEGRATIONS.md +72 -69
  8. package/OPERATIONS.md +142 -0
  9. package/README.md +14 -17
  10. package/RELEASING.md +2 -0
  11. package/SECURITY.md +2 -0
  12. package/STORAGE.md +57 -2
  13. package/dist/adapter-contract.d.ts +18 -0
  14. package/dist/adapter-contract.js +90 -11
  15. package/dist/auth/authenticator.d.ts +3 -1
  16. package/dist/auth/authenticator.js +34 -29
  17. package/dist/auth/authority.d.ts +8 -2
  18. package/dist/auth/authority.js +7 -2
  19. package/dist/auth/session.d.ts +47 -0
  20. package/dist/auth/session.js +94 -0
  21. package/dist/browser.d.ts +15 -13
  22. package/dist/browser.js +80 -46
  23. package/dist/conversation/service.d.ts +18 -8
  24. package/dist/conversation/service.js +60 -28
  25. package/dist/feedback.d.ts +1 -1
  26. package/dist/feedback.js +5 -5
  27. package/dist/index.d.ts +6 -1
  28. package/dist/index.js +3 -0
  29. package/dist/mcp.d.ts +1 -1
  30. package/dist/mcp.js +8 -4
  31. package/dist/network/discovery.d.ts +51 -22
  32. package/dist/network/discovery.js +159 -69
  33. package/dist/network/redirect.d.ts +3 -0
  34. package/dist/network/redirect.js +38 -0
  35. package/dist/network/tasks.d.ts +23 -0
  36. package/dist/network/tasks.js +45 -0
  37. package/dist/operations.d.ts +115 -0
  38. package/dist/operations.js +212 -0
  39. package/dist/postgres.d.ts +6 -0
  40. package/dist/postgres.js +92 -6
  41. package/dist/protocol/operations.d.ts +51 -0
  42. package/dist/protocol/operations.js +98 -0
  43. package/dist/protocol/session.d.ts +28 -0
  44. package/dist/protocol/session.js +93 -0
  45. package/dist/protocol/sse.d.ts +1 -0
  46. package/dist/protocol/sse.js +1 -1
  47. package/dist/protocol/wire.d.ts +20 -15
  48. package/dist/protocol/wire.js +70 -22
  49. package/dist/reference-auth/index.d.ts +17 -12
  50. package/dist/reference-auth/index.js +252 -386
  51. package/dist/reference-auth/store.d.ts +2 -1
  52. package/dist/sqlite.d.ts +11 -0
  53. package/dist/sqlite.js +104 -6
  54. package/examples/operations.mjs +18 -0
  55. package/examples/quickstart.mjs +10 -6
  56. package/migrations/RETIREMENT.md +77 -0
  57. package/package.json +5 -2
@@ -0,0 +1,28 @@
1
+ import type { MetadataResolver } from '../network/discovery.js';
2
+ export interface SessionClaims {
3
+ iss: string;
4
+ aud: string;
5
+ sid: string;
6
+ sub: string;
7
+ agent_id?: string;
8
+ htm: string;
9
+ htu: string;
10
+ iat: number;
11
+ jti: string;
12
+ ath?: string;
13
+ }
14
+ export interface VerifiedSession extends SessionClaims {
15
+ delegationExpiresAt: number;
16
+ }
17
+ export declare function sessionRequestUrl(value: string): string;
18
+ export declare function parseSessionClaims(value: unknown): SessionClaims;
19
+ /** Verifies identity only. The resource must separately admit/continue the client_id + sid under its store fence. */
20
+ export declare function verifySessionJwt(jwt: string | null, expected: {
21
+ companyId: string;
22
+ method: string;
23
+ url: string;
24
+ nowMs: number;
25
+ metadata: MetadataResolver;
26
+ accessToken?: string;
27
+ tokenClientId?: string;
28
+ }): Promise<VerifiedSession>;
@@ -0,0 +1,93 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { calculateJwkThumbprint, compactVerify, createLocalJWKSet, decodeProtectedHeader, errors, importJWK } from 'jose';
3
+ import { id, object, parseJson, string, ValidationError } from './wire.js';
4
+ import { validateClientId } from '../network/discovery.js';
5
+ import { secureUrl } from '../network/transport.js';
6
+ const COMPACT = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
7
+ const PRIVATE = ['d', 'p', 'q', 'dp', 'dq', 'qi', 'oth', 'k'];
8
+ const hash = (value) => createHash('sha256').update(value).digest('base64url');
9
+ function unique(jwt) {
10
+ if (!COMPACT.test(jwt))
11
+ throw new ValidationError('session.jwt');
12
+ for (const segment of jwt.split('.').slice(0, 2))
13
+ parseJson(Buffer.from(segment, 'base64url').toString('utf8'));
14
+ }
15
+ function timestamp(value) {
16
+ if (!Number.isSafeInteger(value) || value < 0)
17
+ throw new ValidationError('session.time');
18
+ return value;
19
+ }
20
+ function bounded(value, field) {
21
+ const result = string(value, field);
22
+ if (result.length > 256)
23
+ throw new ValidationError(field);
24
+ return result;
25
+ }
26
+ export function sessionRequestUrl(value) {
27
+ const url = secureUrl(value);
28
+ return `${url.origin}${url.pathname}`;
29
+ }
30
+ export function parseSessionClaims(value) {
31
+ const claims = object(value, 'session');
32
+ validateClientId(string(claims.iss, 'session.iss'));
33
+ secureUrl(string(claims.aud, 'session.aud'));
34
+ id(claims.sid, 'session.sid');
35
+ bounded(claims.sub, 'session.sub');
36
+ if ('agent_id' in claims)
37
+ bounded(claims.agent_id, 'session.agent_id');
38
+ string(claims.htm, 'session.htm');
39
+ const htu = string(claims.htu, 'session.htu');
40
+ if (htu.includes('?') || htu.includes('#'))
41
+ throw new ValidationError('session.htu');
42
+ secureUrl(htu);
43
+ timestamp(claims.iat);
44
+ const jti = bounded(claims.jti, 'session.jti');
45
+ if (jti.length < 16)
46
+ throw new ValidationError('session.jti');
47
+ if ('ath' in claims)
48
+ string(claims.ath, 'session.ath');
49
+ return claims;
50
+ }
51
+ /** Verifies identity only. The resource must separately admit/continue the client_id + sid under its store fence. */
52
+ export async function verifySessionJwt(jwt, expected) {
53
+ if (!jwt || jwt.length > 32768)
54
+ throw new ValidationError('session.jwt');
55
+ unique(jwt);
56
+ const header = decodeProtectedHeader(jwt);
57
+ if (header.typ !== 'poppy-session+jwt' || header.alg !== 'ES256' || typeof header.kid !== 'string' || typeof header.pap_dlg !== 'string')
58
+ throw new ValidationError('session.header');
59
+ const claims = parseSessionClaims(parseJson(Buffer.from(jwt.split('.')[1], 'base64url').toString('utf8')));
60
+ const delegation = header.pap_dlg;
61
+ unique(delegation);
62
+ const delegatedHeader = decodeProtectedHeader(delegation);
63
+ if (delegatedHeader.typ !== 'pap-delegation+jwt' || typeof delegatedHeader.alg !== 'string' || !['ES256', 'PS384'].includes(delegatedHeader.alg) || typeof delegatedHeader.kid !== 'string')
64
+ throw new ValidationError('delegation.header');
65
+ const client = await expected.metadata.resolveClient(claims.iss);
66
+ let jwks = await expected.metadata.resolveJwks(client.jwks_uri);
67
+ let verified;
68
+ try {
69
+ verified = await compactVerify(delegation, createLocalJWKSet(jwks), { algorithms: ['ES256', 'PS384'] });
70
+ }
71
+ catch (error) {
72
+ if (!(error instanceof errors.JWKSNoMatchingKey))
73
+ throw error;
74
+ jwks = await expected.metadata.resolveJwks(client.jwks_uri, { refresh: true });
75
+ verified = await compactVerify(delegation, createLocalJWKSet(jwks), { algorithms: ['ES256', 'PS384'] });
76
+ }
77
+ const delegated = object(parseJson(new TextDecoder().decode(verified.payload)), 'delegation');
78
+ const issued = timestamp(delegated.iat), expires = timestamp(delegated.exp), at = expected.nowMs / 1000;
79
+ bounded(delegated.jti, 'delegation.jti');
80
+ if (delegated.jti.length < 16 || delegated.iss !== claims.iss || expires <= issued || expires - issued > 86400 || expires <= at || issued > at + 30)
81
+ throw new ValidationError('delegation.claims');
82
+ const key = object(object(delegated.cnf, 'delegation.cnf').jwk, 'delegation.key');
83
+ if (key.kty !== 'EC' || key.crv !== 'P-256' || PRIVATE.some(name => name in key) || typeof key.x !== 'string' || typeof key.y !== 'string')
84
+ throw new ValidationError('delegation.key');
85
+ if (header.kid !== await calculateJwkThumbprint(key) || claims.iat > expires)
86
+ throw new ValidationError('session.key');
87
+ await compactVerify(jwt, await importJWK(key, 'ES256'), { algorithms: ['ES256'] });
88
+ if (claims.aud !== expected.companyId || claims.htm !== expected.method || sessionRequestUrl(claims.htu) !== sessionRequestUrl(expected.url) || claims.iat < at - 60 || claims.iat > at + 30)
89
+ throw new ValidationError('session.binding');
90
+ if (expected.accessToken === undefined ? 'ath' in claims : claims.ath !== hash(expected.accessToken) || expected.tokenClientId !== claims.iss)
91
+ throw new ValidationError('session.token');
92
+ return { ...claims, delegationExpiresAt: expires * 1000 };
93
+ }
@@ -12,4 +12,5 @@ export declare function parseEventStream(body: ReadableStream<Uint8Array>, optio
12
12
  maxFrameBytes?: number;
13
13
  maxStreamBytes?: number;
14
14
  signal?: AbortSignal;
15
+ operations?: boolean;
15
16
  }): AsyncGenerator<StreamFrame>;
@@ -18,7 +18,7 @@ export async function* parseEventStream(body, options = {}) {
18
18
  const delta = object(value);
19
19
  return { kind: 'delta', messageId: id(delta.message_id, 'message_id'), text: string(delta.text, 'text', false) };
20
20
  }
21
- const event = parseEvent(value);
21
+ const event = parseEvent(value, options.operations);
22
22
  if (eventId !== event.id)
23
23
  throw new ValidationError('sse.id');
24
24
  return { kind: 'event', event };
@@ -1,17 +1,27 @@
1
+ import type { BusinessOperation, OperationApproval } from './operations.js';
1
2
  export type Json = null | boolean | number | string | Json[] | {
2
3
  [key: string]: Json;
3
4
  };
4
5
  export type JsonObject = {
5
6
  [key: string]: Json;
6
7
  };
7
- export type Sender = 'agent' | 'human';
8
+ export type AuthorType = 'agent' | 'human';
9
+ export interface Author {
10
+ type: AuthorType;
11
+ id?: string;
12
+ name?: string;
13
+ image?: string;
14
+ [key: string]: unknown;
15
+ }
8
16
  export type Status = 'working' | 'idle' | 'queued' | 'closed';
9
17
  export interface Message {
10
18
  id: string;
11
- sender: Sender;
19
+ author: Author;
12
20
  text?: string;
13
21
  data?: JsonObject;
14
22
  context?: JsonObject;
23
+ operation?: BusinessOperation;
24
+ approval?: OperationApproval;
15
25
  [key: string]: unknown;
16
26
  }
17
27
  export interface RecordedMessage extends Message {
@@ -25,7 +35,7 @@ export interface ConversationRequest {
25
35
  export interface ConversationState {
26
36
  conversation_id: string;
27
37
  status: Status;
28
- responder: Sender;
38
+ responder: AuthorType;
29
39
  [key: string]: unknown;
30
40
  }
31
41
  export interface ConversationEvent {
@@ -40,11 +50,7 @@ export type KnownEvent = (ConversationEvent & {
40
50
  }) | (ConversationEvent & {
41
51
  type: 'state';
42
52
  status: Status;
43
- responder: Sender;
44
- }) | (ConversationEvent & {
45
- type: 'authorization';
46
- error: 'sign_in_required';
47
- scope?: string;
53
+ responder: AuthorType;
48
54
  }) | (ConversationEvent & {
49
55
  type: 'authorization';
50
56
  error: 'insufficient_scope';
@@ -64,10 +70,8 @@ export interface EventPage extends ConversationState {
64
70
  export interface TokenResponse {
65
71
  access_token: string;
66
72
  token_type: 'DPoP' | 'Bearer';
67
- expires_in: number;
68
- scope: string;
69
- session_id: string;
70
- signed_in: boolean;
73
+ expires_in?: number;
74
+ scope?: string;
71
75
  refresh_token?: string;
72
76
  refresh_token_expires_in?: number;
73
77
  [key: string]: unknown;
@@ -92,11 +96,12 @@ export declare function id(value: unknown, field: string): string;
92
96
  export declare function parseMessage(value: unknown, options?: {
93
97
  recorded?: boolean;
94
98
  company?: boolean;
99
+ operations?: boolean;
95
100
  }): Message | RecordedMessage;
96
- export declare function parseConversationRequest(value: unknown): ConversationRequest;
101
+ export declare function parseConversationRequest(value: unknown, operations?: boolean): ConversationRequest;
97
102
  export declare function parseConversationState(value: unknown): ConversationState;
98
- export declare function parseEvent(value: unknown): ConversationEvent;
99
- export declare function parseEventPage(value: unknown, requestCursor?: string): EventPage;
103
+ export declare function parseEvent(value: unknown, operations?: boolean): ConversationEvent;
104
+ export declare function parseEventPage(value: unknown, requestCursor?: string, operations?: boolean): EventPage;
100
105
  export declare function parseTokenResponse(value: unknown): TokenResponse;
101
106
  export declare function parseProtocolError(value: unknown): ProtocolErrorBody;
102
107
  export declare function parseReadOptions(url: URL, maxWaitSeconds: number): {
@@ -1,3 +1,4 @@
1
+ import { parseBusinessOperation, parseOperationApproval } from './operations.js';
1
2
  export class ValidationError extends Error {
2
3
  field;
3
4
  constructor(field) {
@@ -50,8 +51,38 @@ function positive(value, field) {
50
51
  export function parseMessage(value, options = {}) {
51
52
  const message = { ...object(value, 'message') };
52
53
  id(message.id, 'message.id');
53
- member(message.sender, ['agent', 'human'], 'message.sender');
54
- if (!['text', 'data', 'context'].some(key => key in message))
54
+ const author = object(message.author, 'message.author');
55
+ member(author.type, ['agent', 'human'], 'message.author.type');
56
+ if ('sender' in message)
57
+ throw new ValidationError('message.sender');
58
+ for (const key of ['id', 'name', 'image'])
59
+ if (key in author) {
60
+ if (!options.company && !(options.recorded && message.role === 'company'))
61
+ throw new ValidationError('message.author.' + key);
62
+ string(author[key], 'message.author.' + key);
63
+ }
64
+ if ('image' in author) {
65
+ try {
66
+ const url = new URL(author.image);
67
+ if (url.protocol !== 'https:' || url.username || url.password)
68
+ throw 0;
69
+ }
70
+ catch {
71
+ throw new ValidationError('message.author.image');
72
+ }
73
+ }
74
+ const company = options.company || (options.recorded && message.role === 'company');
75
+ if ('operation' in message) {
76
+ if (!company || !options.operations)
77
+ throw new ValidationError('message.operation');
78
+ message.operation = parseBusinessOperation(message.operation);
79
+ }
80
+ if ('approval' in message) {
81
+ if (company || !options.operations)
82
+ throw new ValidationError('message.approval');
83
+ message.approval = parseOperationApproval(message.approval);
84
+ }
85
+ if (!['text', 'data', 'context', ...(options.operations ? [company ? 'operation' : 'approval'] : [])].some(key => key in message))
55
86
  throw new ValidationError('message.content');
56
87
  if ('text' in message)
57
88
  string(message.text, 'message.text', false);
@@ -90,20 +121,24 @@ export function parseMessage(value, options = {}) {
90
121
  }
91
122
  return message;
92
123
  }
93
- export function parseConversationRequest(value) {
124
+ export function parseConversationRequest(value, operations = false) {
94
125
  const request = object(value);
95
126
  if ('parent_conversation_id' in request)
96
127
  id(request.parent_conversation_id, 'parent_conversation_id');
97
- return { ...request, message: parseMessage(request.message) };
128
+ return { ...request, message: parseMessage(request.message, { operations }) };
98
129
  }
99
130
  export function parseConversationState(value) {
100
131
  const state = object(value);
101
132
  id(state.conversation_id, 'conversation_id');
102
- member(state.status, ['working', 'idle', 'queued', 'closed'], 'status');
103
- member(state.responder, ['agent', 'human'], 'responder');
133
+ string(state.status, 'status');
134
+ string(state.responder, 'responder');
135
+ if (!['working', 'idle', 'queued', 'closed'].includes(state.status))
136
+ state.status = 'working';
137
+ if (!['agent', 'human'].includes(state.responder))
138
+ state.responder = 'human';
104
139
  return state;
105
140
  }
106
- export function parseEvent(value) {
141
+ export function parseEvent(value, operations = false) {
107
142
  const event = object(value, 'event');
108
143
  id(event.id, 'event.id');
109
144
  string(event.type, 'event.type');
@@ -119,16 +154,18 @@ export function parseEvent(value) {
119
154
  throw new ValidationError('event.created_at');
120
155
  switch (event.type) {
121
156
  case 'message':
122
- parseMessage(event.message, { recorded: true });
157
+ parseMessage(event.message, { recorded: true, operations });
123
158
  break;
124
- case 'state':
125
- member(event.status, ['working', 'idle', 'queued', 'closed'], 'event.status');
126
- member(event.responder, ['agent', 'human'], 'event.responder');
159
+ case 'state': {
160
+ const state = parseConversationState({ conversation_id: 'state', status: event.status, responder: event.responder });
161
+ event.status = state.status;
162
+ event.responder = state.responder;
127
163
  break;
164
+ }
128
165
  case 'authorization':
129
- member(event.error, ['sign_in_required', 'insufficient_scope'], 'event.error');
130
- if (event.error === 'insufficient_scope' || 'scope' in event)
131
- string(event.scope, 'event.scope', false);
166
+ member(event.error, ['insufficient_scope'], 'event.error');
167
+ if (!/^[\x21\x23-\x5B\x5D-\x7E]+(?: [\x21\x23-\x5B\x5D-\x7E]+)*$/.test(string(event.scope, 'event.scope')))
168
+ throw new ValidationError('event.scope');
132
169
  break;
133
170
  case 'user_requested':
134
171
  string(event.reason, 'event.reason', false);
@@ -140,11 +177,11 @@ export function parseEvent(value) {
140
177
  }
141
178
  return event;
142
179
  }
143
- export function parseEventPage(value, requestCursor) {
180
+ export function parseEventPage(value, requestCursor, operations = false) {
144
181
  const page = parseConversationState(value);
145
182
  if (!Array.isArray(page.events))
146
183
  throw new ValidationError('events');
147
- page.events.forEach(parseEvent);
184
+ page.events.forEach(event => parseEvent(event, operations));
148
185
  member(page.has_more, [true, false], 'has_more');
149
186
  if (page.cursor === null) {
150
187
  if (requestCursor !== undefined || page.events.length || page.has_more)
@@ -153,7 +190,7 @@ export function parseEventPage(value, requestCursor) {
153
190
  else {
154
191
  string(page.cursor, 'cursor');
155
192
  const expected = page.events.length ? page.events.at(-1).id : requestCursor;
156
- if (expected !== undefined && page.cursor !== expected)
193
+ if (expected === undefined || page.cursor !== expected)
157
194
  throw new ValidationError('cursor');
158
195
  }
159
196
  return page;
@@ -161,11 +198,17 @@ export function parseEventPage(value, requestCursor) {
161
198
  export function parseTokenResponse(value) {
162
199
  const token = object(value);
163
200
  string(token.access_token, 'access_token');
164
- string(token.session_id, 'session_id');
165
- string(token.scope, 'scope', false);
166
- member(token.token_type, ['DPoP', 'Bearer'], 'token_type');
167
- member(token.signed_in, [true, false], 'signed_in');
168
- positive(token.expires_in, 'expires_in');
201
+ if ('scope' in token) {
202
+ const scope = string(token.scope, 'scope', false);
203
+ if (scope !== '' && (!/^[\x21\x23-\x5B\x5D-\x7E]+(?: [\x21\x23-\x5B\x5D-\x7E]+)*$/.test(scope) || new Set(scope.split(' ')).size !== scope.split(' ').length))
204
+ throw new ValidationError('scope');
205
+ }
206
+ const type = string(token.token_type, 'token_type').toLowerCase();
207
+ if (type !== 'bearer' && type !== 'dpop')
208
+ throw new ValidationError('token_type');
209
+ token.token_type = type === 'bearer' ? 'Bearer' : 'DPoP';
210
+ if ('expires_in' in token && (!Number.isSafeInteger(token.expires_in) || token.expires_in < 0))
211
+ throw new ValidationError('expires_in');
169
212
  if ('refresh_token' in token)
170
213
  string(token.refresh_token, 'refresh_token');
171
214
  if ('refresh_token_expires_in' in token)
@@ -215,6 +258,11 @@ export function parseAssertionClaims(value) {
215
258
  const claims = object(value, 'claims');
216
259
  for (const key of ['iss', 'sub', 'aud', 'jti'])
217
260
  string(claims[key], key);
261
+ if ('agent_id' in claims) {
262
+ string(claims.agent_id, 'agent_id');
263
+ if (claims.agent_id.length > 256)
264
+ throw new ValidationError('agent_id');
265
+ }
218
266
  // 128 random bits is at least 16 bytes; cap length since jti lands in the replay store. Encoding is not constrained.
219
267
  if (claims.jti.length < 16 || claims.jti.length > 256)
220
268
  throw new ValidationError('jti');
@@ -1,13 +1,13 @@
1
1
  import type { MetadataResolver } from '../network/discovery.js';
2
2
  import type { IssuerStore, IssuerSelection } from './store.js';
3
3
  import type { AuthorityCoordinator, ExternalAuthorityAdapter } from '../auth/authority.js';
4
- import type { BrowserSessionAuthority } from '../browser.js';
5
4
  export type { IssuerStore, IssuerSelection };
6
5
  export { createMetadataResolver } from '../network/discovery.js';
7
6
  export { createNodeTransport } from '../network/transport.js';
8
7
  interface SessionRecord {
9
8
  clientId: string;
10
9
  userId: string;
10
+ agentId?: string;
11
11
  version: number;
12
12
  createdAt: number;
13
13
  expiresAt: number;
@@ -25,6 +25,8 @@ interface GrantRecord {
25
25
  previousHash: string;
26
26
  }
27
27
  interface AccessTokenRecord {
28
+ clientId?: string;
29
+ accountId?: string;
28
30
  sessionId: string;
29
31
  grantId: string | null;
30
32
  scopes: string[];
@@ -33,6 +35,7 @@ interface AccessTokenRecord {
33
35
  expiresAt: number;
34
36
  }
35
37
  interface AuthorizationRecord {
38
+ sessionKey: string | null;
36
39
  clientId: string;
37
40
  redirectUri: string;
38
41
  state: string;
@@ -47,6 +50,7 @@ interface AuthorizationRecord {
47
50
  };
48
51
  }
49
52
  interface CodeRecord {
53
+ sessionKey: string | null;
50
54
  clientId: string;
51
55
  redirectUri: string;
52
56
  codeChallenge: string;
@@ -58,6 +62,7 @@ interface CodeRecord {
58
62
  }
59
63
  export type SignInRecord = {
60
64
  kind: 'device';
65
+ sessionKey: string | null;
61
66
  clientId: string;
62
67
  scopes: string[];
63
68
  expiresAt: number;
@@ -70,9 +75,11 @@ export type SignInRecord = {
70
75
  accountId?: string;
71
76
  } | {
72
77
  kind: 'mediated';
78
+ resource?: string | null;
79
+ grantId?: string;
73
80
  clientId: string;
74
- userId: string;
75
- sessionId: string;
81
+ userId?: string;
82
+ sessionId?: string;
76
83
  scopes: string[];
77
84
  expiresAt: number;
78
85
  status: 'verifying' | 'pending' | 'failed' | 'expired' | 'complete';
@@ -97,12 +104,10 @@ export type SignInRecord = {
97
104
  };
98
105
  export interface MediatedContext {
99
106
  clientId: string;
100
- userId: string;
101
- sessionId: string;
102
107
  issuer: string;
103
108
  }
104
109
  export interface MediatedOptions {
105
- endpoint: string;
110
+ endpoint?: string;
106
111
  scopes: string[];
107
112
  fields: {
108
113
  name: string;
@@ -132,9 +137,10 @@ export interface IssuerState {
132
137
  pending: number;
133
138
  pendingForClient: number;
134
139
  };
135
- version: 3;
140
+ version: 3 | 4 | 5;
136
141
  generation: string;
137
142
  sessions: Record<string, SessionRecord>;
143
+ sessionKeys: Record<string, string>;
138
144
  grants: Record<string, GrantRecord>;
139
145
  refreshTokens: Record<string, string>;
140
146
  authorizations: Record<string, AuthorizationRecord>;
@@ -152,8 +158,8 @@ export interface ReferenceIssuerOptions {
152
158
  tokenEndpoint: string;
153
159
  revocationEndpoint: string;
154
160
  authorizationEndpoint?: string;
155
- poppyDomains: string[];
156
- directScopes: string[];
161
+ protectedResources: string[];
162
+ supportedScopes: string[];
157
163
  resources?: string[];
158
164
  mcpResources?: {
159
165
  url: string;
@@ -177,7 +183,6 @@ export interface ReferenceIssuerOptions {
177
183
  device?: {
178
184
  endpoint: string;
179
185
  verificationUri: string;
180
- scopes: string[];
181
186
  intervalSeconds?: number;
182
187
  };
183
188
  mediated?: MediatedOptions;
@@ -196,7 +201,6 @@ export type AuthorizationDecision = {
196
201
  denied: true;
197
202
  };
198
203
  export interface ReferenceIssuer {
199
- browserSessions: BrowserSessionAuthority;
200
204
  token(request: Request): Promise<Response>;
201
205
  revoke(request: Request): Promise<Response>;
202
206
  beginAuthorization(request: Request, browserBinding: string): Promise<AuthorizationView>;
@@ -204,7 +208,6 @@ export interface ReferenceIssuer {
204
208
  deviceAuthorization(request: Request): Promise<Response>;
205
209
  beginDeviceAuthorization(userCode: string, browserBinding: string): Promise<AuthorizationView>;
206
210
  completeDeviceAuthorization(userCode: string, browserBinding: string, decision: AuthorizationDecision): Promise<void>;
207
- mediatedSignIn(request: Request): Promise<Response>;
208
211
  authorizationMetadata(): Record<string, unknown>;
209
212
  authority: ExternalAuthorityAdapter;
210
213
  disconnect(clientId: string, accountId: string): Promise<void>;
@@ -214,5 +217,7 @@ export declare class AuthorizationError extends Error {
214
217
  readonly redirectTo?: URL | undefined;
215
218
  constructor(code: string, redirectTo?: URL | undefined);
216
219
  }
220
+ /** Run once over the complete namespace, under the adapter's writer lock. */
221
+ export declare function upgradeIssuerState(state: IssuerState): void;
217
222
  export declare function initialIssuerState(): IssuerState;
218
223
  export declare function createReferenceIssuer(options: ReferenceIssuerOptions): ReferenceIssuer;