@inixiative/signet 0.4.3 → 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.
package/src/contracts.ts CHANGED
@@ -1,4 +1,17 @@
1
1
  import { z } from 'zod';
2
+ import {
3
+ foundryIdSchema,
4
+ narrowsThreadsLens,
5
+ type ThreadOperation,
6
+ threadFloorActionSchema,
7
+ threadIdSchema,
8
+ threadInputSchemas,
9
+ threadsLensSchema,
10
+ threadsResourceKind,
11
+ threadTextLimit,
12
+ } from './threads';
13
+
14
+ export * from './threads';
2
15
 
3
16
  const token43 = /^[A-Za-z0-9_-]{43}$/;
4
17
 
@@ -37,10 +50,50 @@ export const signetKeySchema = publicClientKeySchema.extend({
37
50
  export type PublicClientKey = z.infer<typeof publicClientKeySchema>;
38
51
  export type SignetKey = z.infer<typeof signetKeySchema>;
39
52
 
40
- export const signetLensSchema = z.strictObject({
53
+ /** The resource kind of a hosted Archive library: one per Archive integration. */
54
+ export const archiveLibraryResourceKind = 'archiveLibrary';
55
+ /** Which documents and fields of an `archiveLibrary` a grant reaches; `{}` reaches all of them. */
56
+ export const archiveLensSchema = z.strictObject({
41
57
  documentIds: z.array(archiveDocumentIdSchema).max(1000).optional(),
42
58
  fields: z.array(archiveFieldSchema).max(6).optional(),
43
59
  });
60
+ export type ArchiveLens = z.infer<typeof archiveLensSchema>;
61
+ /** Every other resource kind takes no lens. */
62
+ export const emptyLensSchema = z.strictObject({});
63
+
64
+ /**
65
+ * A grant's lens. `signetResourceSchema` names a resource by id with no `kind`, so the contract can
66
+ * only hold a union of the strict shapes; which shape a grant may carry depends on its resource's
67
+ * kind, and Kingdom checks that with `resourceLensSchema` against the kind it stores (review R9).
68
+ * `{}` parses as the empty Archive lens: "no lens", valid for every kind but `threads`.
69
+ */
70
+ export const signetLensSchema = z.union([archiveLensSchema, threadsLensSchema]);
71
+ export type SignetLens = z.infer<typeof signetLensSchema>;
72
+
73
+ /** The lens shape a resource of `kind` takes. */
74
+ export const resourceLensSchema = (kind: string) =>
75
+ kind === archiveLibraryResourceKind
76
+ ? archiveLensSchema
77
+ : kind === threadsResourceKind
78
+ ? threadsLensSchema
79
+ : emptyLensSchema;
80
+
81
+ const subset = <T>(granted: T[] | undefined, requested: T[] | undefined) =>
82
+ !requested || (granted !== undefined && granted.every((item) => requested.includes(item)));
83
+
84
+ /**
85
+ * `granted` reaches nothing `requested` doesn't, for lenses of the same resource. Approving someone
86
+ * else's request may only narrow; an owner revising their own grant may widen (review U3). A lens of
87
+ * another shape than the request's is refused.
88
+ */
89
+ export const narrowsSignetLens = (granted: SignetLens, requested: SignetLens): boolean => {
90
+ if ('threads' in granted || 'threads' in requested)
91
+ return 'threads' in granted && 'threads' in requested && narrowsThreadsLens(granted, requested);
92
+ return (
93
+ subset(granted.documentIds, requested.documentIds) && subset(granted.fields, requested.fields)
94
+ );
95
+ };
96
+
44
97
  export const signetResourceSchema = z.strictObject({
45
98
  resourceId: z.uuid(),
46
99
  operations: z.array(z.string().min(1).max(80)).min(1).max(100),
@@ -51,7 +104,6 @@ export const signetRecipientSchema = z.discriminatedUnion('recipientModel', [
51
104
  z.strictObject({ recipientModel: z.literal('Integration'), integrationId: z.uuid() }),
52
105
  ]);
53
106
  export const signetLifecycleSchema = z.enum(['request', 'task', 'ongoing']);
54
- export type SignetLens = z.infer<typeof signetLensSchema>;
55
107
  export type SignetRecipient = z.infer<typeof signetRecipientSchema>;
56
108
  export type SignetResource = z.infer<typeof signetResourceSchema>;
57
109
 
@@ -95,11 +147,25 @@ const signetTerms = {
95
147
  /** A local Foundry, Archive or Oracle known to Kingdom by its key. */
96
148
  export const installationKindSchema = z.enum(['foundry', 'archive', 'oracle']);
97
149
  export type InstallationKind = z.infer<typeof installationKindSchema>;
150
+ /**
151
+ * A resource asked for by kind, and optionally name, when the asker does not know its id: an
152
+ * Installation registering, or a party asking to join a Foundry's `threads` (one per Foundry, so the
153
+ * integration id names it). Kingdom resolves it to the resource's id before anyone reviews it.
154
+ */
155
+ export const requestedResourceKindSchema = z.strictObject({
156
+ resourceKind: z.string().min(1).max(80),
157
+ resourceName: z.string().trim().min(1).max(120).optional(),
158
+ operations: z.array(z.string().min(1).max(80)).min(1).max(100),
159
+ lens: signetLensSchema.default({}),
160
+ });
161
+ export const requestedResourceSchema = z.union([signetResourceSchema, requestedResourceKindSchema]);
162
+ /** What a party asks for on an integration: resources by id or by kind (review R8). */
98
163
  export const signetProposalSchema = z.strictObject({
99
164
  ...signetTerms,
100
165
  integrationId: z.uuid(),
101
- resources: z.array(signetResourceSchema).min(1).max(100),
166
+ resources: z.array(requestedResourceSchema).min(1).max(100),
102
167
  });
168
+ /** The terms as reviewed and issued: every resource named by id. */
103
169
  export const signetReviewProposalSchema = z.strictObject({
104
170
  ...signetTerms,
105
171
  resources: z.array(signetResourceSchema).max(100),
@@ -116,14 +182,6 @@ export const requestedOwnerSchema = z.strictObject({
116
182
  organization: z.string().trim().min(1).max(120).optional(),
117
183
  spaceId: z.uuid().optional(),
118
184
  });
119
- /** A resource asked for by kind, and optionally name, when the Installation does not know its id. */
120
- export const requestedResourceKindSchema = z.strictObject({
121
- resourceKind: z.string().min(1).max(80),
122
- resourceName: z.string().trim().min(1).max(120).optional(),
123
- operations: z.array(z.string().min(1).max(80)).min(1).max(100),
124
- lens: signetLensSchema.default({}),
125
- });
126
- export const requestedResourceSchema = z.union([signetResourceSchema, requestedResourceKindSchema]);
127
185
  /** What an Installation asks for: the owner it expects and the resources and operations it needs. */
128
186
  export const registrationRequestSchema = z.strictObject({
129
187
  ...signetTerms,
@@ -132,10 +190,7 @@ export const registrationRequestSchema = z.strictObject({
132
190
  resources: z.array(requestedResourceSchema).max(100),
133
191
  });
134
192
  /** A person asks for a Signet on an integration, to be presented with `publicKey`. */
135
- export const signetRequestSchema = z.strictObject({
136
- ...signetTerms,
137
- integrationId: z.uuid(),
138
- resources: z.array(signetResourceSchema).min(1).max(100),
193
+ export const signetRequestSchema = signetProposalSchema.extend({
139
194
  publicKey: publicClientKeySchema,
140
195
  });
141
196
  export const signetRequestResponseSchema = z.object({
@@ -164,14 +219,26 @@ export const registerInstallationSchema = z
164
219
  });
165
220
  export const registerInstallationResponseSchema = z.object({ installationId: z.uuid() });
166
221
  export const requestRegistrationSchema = z.strictObject({ terms: registrationRequestSchema });
222
+ /** A link into Kingdom's web app that callers open, so http(s) only. */
223
+ const kingdomLinkSchema = z.url({ protocol: /^https?$/ });
167
224
  export const requestRegistrationResponseSchema = z.object({
168
225
  reviewCode: z.string(),
169
226
  expiresAt: z.iso.datetime(),
227
+ /** Where a person reviews the registration in Kingdom's web app. */
228
+ reviewUrl: kingdomLinkSchema,
170
229
  });
230
+ const inquiryStatusSchema = z.enum([
231
+ 'draft',
232
+ 'sent',
233
+ 'changesRequested',
234
+ 'approved',
235
+ 'denied',
236
+ 'canceled',
237
+ ]);
171
238
  export const installationInquirySchema = z.object({
172
239
  id: z.uuid(),
173
240
  type: z.string(),
174
- status: z.enum(['draft', 'sent', 'changesRequested', 'approved', 'denied', 'canceled']),
241
+ status: inquiryStatusSchema,
175
242
  createdAt: z.iso.datetime(),
176
243
  expiresAt: z.iso.datetime().nullable(),
177
244
  owner: ownerRefSchema.nullable(),
@@ -180,23 +247,90 @@ export const installationInquirySchema = z.object({
180
247
  signetId: z.uuid().nullable(),
181
248
  deliverBefore: z.iso.datetime().nullable(),
182
249
  });
250
+ /**
251
+ * A pending `signetRequest` whose target is one of the Installation's integrations: a party asking
252
+ * to join a Foundry's threads. The owner reviews it in Kingdom at `reviewUrl`.
253
+ */
254
+ export const hostedSignetRequestSchema = z.object({
255
+ id: z.uuid(),
256
+ status: inquiryStatusSchema,
257
+ createdAt: z.iso.datetime(),
258
+ expiresAt: z.iso.datetime().nullable(),
259
+ /** The Installation's integration the request targets. */
260
+ integrationId: z.uuid(),
261
+ requester: z.object({ sourceModel: z.string(), id: z.uuid(), displayName: z.string() }),
262
+ terms: signetReviewProposalSchema,
263
+ reviewUrl: kingdomLinkSchema,
264
+ });
265
+ export type HostedSignetRequest = z.infer<typeof hostedSignetRequestSchema>;
183
266
  export const installationInquiriesResponseSchema = z.object({
184
267
  pending: z.object({ reviewCode: z.string(), expiresAt: z.iso.datetime() }).nullable(),
185
268
  declinedAt: z.iso.datetime().nullable(),
186
269
  inquiries: z.array(installationInquirySchema),
270
+ hostedSignetRequests: z.array(hostedSignetRequestSchema),
187
271
  });
188
272
  export const inquiryReferenceSchema = z.strictObject({ inquiryId: z.uuid() });
273
+
274
+ /** One resource a Signet reaches, with its stored kind, so its lens can be checked against it. */
275
+ export const signetGrantSchema = z.object({
276
+ resourceId: z.uuid(),
277
+ kind: z.string().min(1).max(80),
278
+ operations: z.array(z.string().min(1).max(80)),
279
+ lens: signetLensSchema,
280
+ });
281
+ export type SignetGrant = z.infer<typeof signetGrantSchema>;
282
+ const grantsSchema = z.array(signetGrantSchema);
283
+ /** Whether a grant's lens is the shape its resource's kind takes. */
284
+ export const grantLensFitsKind = (grant: Pick<SignetGrant, 'kind' | 'lens'>) =>
285
+ resourceLensSchema(grant.kind).safeParse(grant.lens).success;
286
+
287
+ /** Who a Signet is issued to. Recipients run broad (2026-10-18); Kingdom widens issuing in K0. */
288
+ export const signetRecipientModelSchema = z.enum([
289
+ 'User',
290
+ 'OrganizationUser',
291
+ 'Organization',
292
+ 'Space',
293
+ 'SpaceUser',
294
+ 'Integration',
295
+ ]);
296
+ export const installationSignetSchema = z.object({
297
+ signetId: z.uuid(),
298
+ integrationId: z.uuid(),
299
+ name: z.string(),
300
+ /** The enrollment that is current for this installation's key, if any. */
301
+ enrollmentId: z.uuid().nullable(),
302
+ owner: ownerRefSchema,
303
+ /** What the Signet reaches, so a holder knows, for example, which threads it may open. */
304
+ resources: grantsSchema,
305
+ });
306
+ /**
307
+ * A Signet issued on one of the Installation's integrations: a member of a Foundry's conversations.
308
+ * For display and for cancelling a member's queued work once it is gone, never as an admission
309
+ * gate: Kingdom admits every call.
310
+ */
311
+ export const hostedSignetSchema = z.object({
312
+ signetId: z.uuid(),
313
+ /** The Installation's integration the Signet is issued on. */
314
+ integrationId: z.uuid(),
315
+ name: z.string(),
316
+ /** The current revision; the host compares it with the `acceptedRevision` frames carry. */
317
+ revision: z.number().int().min(1),
318
+ lifecycle: signetLifecycleSchema,
319
+ expiresAt: z.iso.datetime().nullable(),
320
+ maxRequests: z.number().int().nullable(),
321
+ maxConcurrent: z.number().int(),
322
+ recipient: z.object({
323
+ recipientModel: signetRecipientModelSchema,
324
+ id: z.uuid(),
325
+ displayName: z.string(),
326
+ }),
327
+ resources: grantsSchema,
328
+ });
329
+ export type InstallationSignet = z.infer<typeof installationSignetSchema>;
330
+ export type HostedSignet = z.infer<typeof hostedSignetSchema>;
189
331
  export const installationSignetsResponseSchema = z.object({
190
- signets: z.array(
191
- z.object({
192
- signetId: z.uuid(),
193
- integrationId: z.uuid(),
194
- name: z.string(),
195
- /** The enrollment that is current for this installation's key, if any. */
196
- enrollmentId: z.uuid().nullable(),
197
- owner: ownerRefSchema,
198
- }),
199
- ),
332
+ signets: z.array(installationSignetSchema),
333
+ hostedSignets: z.array(hostedSignetSchema),
200
334
  });
201
335
  export const enrollInstallationSignetSchema = z.strictObject({ signetId: z.uuid() });
202
336
  export const rotateInstallationKeySchema = z.strictObject({
@@ -205,6 +339,13 @@ export const rotateInstallationKeySchema = z.strictObject({
205
339
  });
206
340
  export type InstallationInquiry = z.infer<typeof installationInquirySchema>;
207
341
 
342
+ export const signetDescriptionResourceSchema = z.object({
343
+ id: z.uuid(),
344
+ name: z.string(),
345
+ kind: z.string(),
346
+ integrationId: z.uuid(),
347
+ lens: signetLensSchema,
348
+ });
208
349
  export const signetDescriptionSchema = z.object({
209
350
  signetId: z.uuid(),
210
351
  integrationId: z.uuid(),
@@ -219,14 +360,36 @@ export const signetDescriptionSchema = z.object({
219
360
  z.object({
220
361
  key: z.string(),
221
362
  name: z.string(),
222
- resources: z.array(
223
- z.object({ id: z.uuid(), name: z.string(), kind: z.string(), integrationId: z.uuid() }),
224
- ),
363
+ resources: z.array(signetDescriptionResourceSchema),
225
364
  }),
226
365
  ),
227
366
  });
228
367
  export type SignetDescription = z.infer<typeof signetDescriptionSchema>;
229
368
 
369
+ const holderPersonSchema = z.object({
370
+ id: z.uuid(),
371
+ name: z.string().nullable(),
372
+ email: z.string(),
373
+ });
374
+ /**
375
+ * `access/describe`'s `holder` for an Integration-held Signet (an Installation such as a Foundry):
376
+ * the integration, its owner, who issued the Signet, and where to manage it in Kingdom.
377
+ */
378
+ export const signetHolderSchema = z.object({
379
+ integration: z.object({ id: z.uuid(), name: z.string(), provider: z.string().nullable() }),
380
+ owner: z.object({
381
+ ownerModel: ownerModelSchema,
382
+ name: z.string().nullable(),
383
+ organization: z.object({ id: z.uuid(), name: z.string(), slug: z.string() }).nullable(),
384
+ space: z.object({ id: z.uuid(), name: z.string() }).nullable(),
385
+ user: holderPersonSchema.nullable(),
386
+ }),
387
+ issuedBy: holderPersonSchema.nullable(),
388
+ webUrl: kingdomLinkSchema,
389
+ manageUrl: kingdomLinkSchema,
390
+ });
391
+ export type SignetHolder = z.infer<typeof signetHolderSchema>;
392
+
230
393
  /** Operation-specific fields; Kingdom validates each against the operation it names. */
231
394
  export const accessInputSchema = z.strictObject({
232
395
  resourceId: z.uuid(),
@@ -243,6 +406,14 @@ export const accessInputSchema = z.strictObject({
243
406
  .regex(/^[a-f0-9]{64}$/)
244
407
  .nullable()
245
408
  .optional(),
409
+ threadId: threadIdSchema.optional(),
410
+ postId: z.uuid().optional(),
411
+ text: z.string().min(1).max(threadTextLimit).optional(),
412
+ inReplyTo: foundryIdSchema.optional(),
413
+ afterSeq: z.number().int().min(0).optional(),
414
+ floor: threadFloorActionSchema.optional(),
415
+ promptId: foundryIdSchema.optional(),
416
+ answer: threadInputSchemas['prompt.resolve'].shape.answer.optional(),
246
417
  });
247
418
  export const accessRequestSchema = z.strictObject({
248
419
  requestId: z.uuid(),
@@ -255,12 +426,195 @@ export const accessRequestSchema = z.strictObject({
255
426
  });
256
427
  export type AccessRequest = z.infer<typeof accessRequestSchema>;
257
428
  export const accessResultSchema = z.object({ executionId: z.uuid(), result: z.unknown() });
429
+
430
+ /**
431
+ * The host-side input of a thread operation, taken from an `access/execute` input: exactly the keys
432
+ * that operation takes (no `resourceId`, no `limit`). Kingdom builds `thread.request` frames with it.
433
+ */
434
+ export const threadInputFromAccess = <O extends ThreadOperation>(
435
+ operation: O,
436
+ input: AccessRequest['input'],
437
+ ) => {
438
+ const schema = threadInputSchemas[operation];
439
+ const picked = Object.fromEntries(
440
+ Object.keys(schema.shape).flatMap((key) =>
441
+ key in input ? [[key, input[key as keyof typeof input]]] : [],
442
+ ),
443
+ );
444
+ return schema.safeParse(picked) as z.ZodSafeParseResult<z.infer<(typeof threadInputSchemas)[O]>>;
445
+ };
446
+
258
447
  /** What Kingdom pushes over the Installation socket, and what a poll of both reads assembles. */
259
448
  export const installationSnapshotSchema = installationInquiriesResponseSchema.extend(
260
449
  installationSignetsResponseSchema.shape,
261
450
  );
451
+
452
+ /** An entry a reader dropped instead of failing the whole read, and why (review U1). */
453
+ export type DroppedEntry = { list: string; index: number; id: string | null; reason: string };
454
+ export type InstallationSnapshot = z.infer<typeof installationSnapshotSchema> & {
455
+ dropped: DroppedEntry[];
456
+ };
457
+
458
+ const entryId = (value: unknown) => {
459
+ if (typeof value !== 'object' || value === null) return null;
460
+ const record = value as Record<string, unknown>;
461
+ const id = record.signetId ?? record.id;
462
+ return typeof id === 'string' ? id.slice(0, 80) : null;
463
+ };
464
+ /**
465
+ * Parse each entry on its own and drop the ones that don't parse. A Kingdom ahead of this package
466
+ * (a selector form it doesn't know, a new field shape) then costs only those entries: never the
467
+ * Installation's other grants or its own held Signets. A missing list reads as empty, reported.
468
+ */
469
+ export function parseEntries<T>(
470
+ list: string,
471
+ value: unknown,
472
+ schema: z.ZodType<T>,
473
+ dropped: DroppedEntry[],
474
+ check: (entry: T) => string | null = () => null,
475
+ ): T[] {
476
+ if (!Array.isArray(value)) {
477
+ dropped.push({ list, index: -1, id: null, reason: 'missing or not a list' });
478
+ return [];
479
+ }
480
+ return value.flatMap((raw, index) => {
481
+ const parsed = schema.safeParse(raw);
482
+ const reason = parsed.success
483
+ ? check(parsed.data)
484
+ : parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join('; ');
485
+ if (reason === null && parsed.success) return [parsed.data];
486
+ dropped.push({ list, index, id: entryId(raw), reason: (reason ?? 'invalid').slice(0, 500) });
487
+ return [];
488
+ });
489
+ }
490
+
491
+ const lensFits = (grant: Pick<SignetGrant, 'kind' | 'lens'>) =>
492
+ grantLensFitsKind(grant) ? null : `a ${grant.kind} resource carries a lens of another shape`;
493
+
494
+ /**
495
+ * Signets read per entry, and their resources per entry inside them: a resource this package can't
496
+ * read (a newer lens, a lens that doesn't fit its kind) is dropped and reported, and the Signet is
497
+ * kept, so the Installation never loses a held Signet (or mistakes a member for revoked) over one
498
+ * resource.
499
+ */
500
+ function parseSignetsWithGrants<S extends z.ZodObject<{ resources: typeof grantsSchema }>>(
501
+ list: string,
502
+ value: unknown,
503
+ schema: S,
504
+ dropped: DroppedEntry[],
505
+ ): z.infer<S>[] {
506
+ const identity = schema.omit({ resources: true }).extend({ resources: z.unknown().optional() });
507
+ return parseEntries(list, value, identity, dropped).map(
508
+ (entry) =>
509
+ ({
510
+ ...entry,
511
+ resources: parseEntries(
512
+ `${list}.${entryId(entry)}.resources`,
513
+ entry.resources,
514
+ signetGrantSchema,
515
+ dropped,
516
+ lensFits,
517
+ ),
518
+ }) as z.infer<S>,
519
+ );
520
+ }
521
+
522
+ const inquiriesScalars = installationInquiriesResponseSchema.pick({
523
+ pending: true,
524
+ declinedAt: true,
525
+ });
526
+
527
+ /** `installationInquiries`, per entry. */
528
+ export function parseInstallationInquiries(value: unknown, dropped: DroppedEntry[] = []) {
529
+ const record = z.object({}).loose().parse(value);
530
+ return {
531
+ ...inquiriesScalars.parse(record),
532
+ inquiries: parseEntries('inquiries', record.inquiries, installationInquirySchema, dropped),
533
+ hostedSignetRequests: parseEntries(
534
+ 'hostedSignetRequests',
535
+ record.hostedSignetRequests,
536
+ hostedSignetRequestSchema,
537
+ dropped,
538
+ ),
539
+ dropped,
540
+ };
541
+ }
542
+
543
+ /** `installationSignets`, per Signet and per resource inside it. */
544
+ export function parseInstallationSignets(value: unknown, dropped: DroppedEntry[] = []) {
545
+ const record = z.object({}).loose().parse(value);
546
+ return {
547
+ signets: parseSignetsWithGrants('signets', record.signets, installationSignetSchema, dropped),
548
+ hostedSignets: parseSignetsWithGrants(
549
+ 'hostedSignets',
550
+ record.hostedSignets,
551
+ hostedSignetSchema,
552
+ dropped,
553
+ ),
554
+ dropped,
555
+ };
556
+ }
557
+
558
+ /** The Installation socket's snapshot frame (or a poll of both reads), per entry. */
559
+ export function parseInstallationSnapshot(value: unknown): InstallationSnapshot {
560
+ const dropped: DroppedEntry[] = [];
561
+ const { dropped: _inquiries, ...inquiries } = parseInstallationInquiries(value, dropped);
562
+ const { dropped: _signets, ...signets } = parseInstallationSignets(value, dropped);
563
+ return { ...inquiries, ...signets, dropped };
564
+ }
565
+
566
+ export type ParsedSignetDescription = SignetDescription & { dropped: DroppedEntry[] };
567
+
568
+ /** `access/describe`, per entry: a resource whose lens doesn't parse, or doesn't fit its kind, is dropped. */
569
+ export function parseSignetDescription(value: unknown): ParsedSignetDescription {
570
+ const dropped: DroppedEntry[] = [];
571
+ const record = z
572
+ .object({ operations: z.array(z.object({}).loose()) })
573
+ .loose()
574
+ .parse(value);
575
+ const operations = record.operations.map((operation, index) => ({
576
+ ...signetDescriptionSchema.shape.operations.element.omit({ resources: true }).parse(operation),
577
+ resources: parseEntries(
578
+ `operations.${index}.resources`,
579
+ operation.resources,
580
+ signetDescriptionResourceSchema,
581
+ dropped,
582
+ (resource) => (grantLensFitsKind(resource) ? null : 'lens does not fit the resource kind'),
583
+ ),
584
+ }));
585
+ return { ...signetDescriptionSchema.parse({ ...record, operations }), dropped };
586
+ }
587
+
262
588
  /** The path a socket proof is bound to (POST), though the socket itself opens at the API origin. */
263
589
  export const installationSocketAction = 'installationSocket';
590
+
591
+ /**
592
+ * A Signet presenter's socket (design §5.6.1, review Round 4). Like the Installation socket: a DPoP
593
+ * proof bound to `POST /api/v1/access/signetSocket`, on a single-use Kingdom nonce, with `ath` over
594
+ * the access token. The socket then carries one actor, that Signet's presentation, until the token
595
+ * expires; `renewPresentation` (a new token, a new proof) extends it.
596
+ */
597
+ export const signetSocketAction = 'signetSocket';
598
+ const presentation = {
599
+ accessToken: z.string().regex(accessTokenPattern),
600
+ proof: z.string().max(8192),
601
+ };
602
+ export const authenticateSignetFrameSchema = z.strictObject({
603
+ action: z.literal('authenticateSignet'),
604
+ ...presentation,
605
+ });
606
+ export const renewPresentationFrameSchema = z.strictObject({
607
+ action: z.literal('renewPresentation'),
608
+ ...presentation,
609
+ });
610
+ /** Kingdom's answer to either: the presentation the socket now carries, and until when. */
611
+ export const signetPresentedFrameSchema = z.object({
612
+ type: z.literal('signet'),
613
+ signetId: z.uuid(),
614
+ enrollmentId: z.uuid(),
615
+ expiresAt: z.iso.datetime(),
616
+ });
617
+ export const signetRejectedFrameSchema = z.object({ type: z.literal('signetRejected') });
264
618
  /** An installation hands Kingdom the credential Kingdom uses to reach it (a Foundry's tunnel token). */
265
619
  export const setInstallationCredentialSchema = z.strictObject({
266
620
  integrationId: z.uuid(),
package/src/index.ts CHANGED
@@ -4,3 +4,5 @@ export * from './files';
4
4
  export * from './installationPairing';
5
5
  export * from './installationSocket';
6
6
  export * from './proof';
7
+ export * from './signetSocket';
8
+ export * from './threadStreams';