@inixiative/signet 0.8.0 → 0.10.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/README.md +1 -1
- package/package.json +2 -2
- package/src/contracts.ts +38 -4
- package/src/github.ts +64 -0
- package/src/threads.ts +6 -0
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ await signet.describe();
|
|
|
61
61
|
|
|
62
62
|
## Declared operations
|
|
63
63
|
|
|
64
|
-
An integration whose operations Kingdom executes generically declares them here, one module per resource kind: `threads.ts` for a Foundry's `threads` resource, `archive.ts` for an Archive's `archiveLibrary
|
|
64
|
+
An integration whose operations Kingdom executes generically declares them here, one module per resource kind: `threads.ts` for a Foundry's `threads` resource, `archive.ts` for an Archive's `archiveLibrary`, `github.ts` for a GitHub `repository` (reads by object id; its lens pins a grant to commits, and a pinned grant reaches no tree or blob, since reading a commit never reached its tree). Each declares its operation enum, display names, the input each operation takes, its lens schema and the lens's narrowing. `declaredOperationSchema` and `operationInputSchemas` join them (operation names are unique across contracts), `operationInputKeys(operation)` gives the `access/execute` keys an operation takes besides `resourceId` and `limit`, and `operationInputFromAccess(operation, input)` picks and parses exactly those. Kingdom's catalog, its input check and its dispatch read these, so a new operation is added here first.
|
|
65
65
|
|
|
66
66
|
- **Archive** (`archiveLibrary`): `documents.read`, `documents.search`, `sessions.write`, `sessions.delete`. The lens is `{ documentIds?, fields? }` (`{}` reaches every document) and `narrowsArchiveLens` narrows it. `archiveSourceOperations` (`sessions.write`, `sessions.delete`) are a paired local Archive acting on its own sessions: Kingdom binds them to its `sourceId`, so their grant takes no lens. `archiveLensRefusal(operation, lens)` says why a grant can't carry a lens (search needs content over every document).
|
|
67
67
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inixiative/signet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Present Kingdom Signets: the wire contracts, DPoP proofs, device pairing and a renewing client shared by Kingdom, Archive, Foundry and Oracle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@asteasolutions/zod-to-openapi": "^9.1.0",
|
|
37
37
|
"@biomejs/biome": "2.5.2",
|
|
38
|
-
"@inixiative/config": "^0.5.
|
|
38
|
+
"@inixiative/config": "^0.5.169",
|
|
39
39
|
"@types/bun": "1.4.4",
|
|
40
40
|
"lefthook": "2.1.9",
|
|
41
41
|
"typescript": "6.0.3",
|
package/src/contracts.ts
CHANGED
|
@@ -8,6 +8,14 @@ import {
|
|
|
8
8
|
archiveOperationSchema,
|
|
9
9
|
narrowsArchiveLens,
|
|
10
10
|
} from './archive';
|
|
11
|
+
import {
|
|
12
|
+
githubInputSchemas,
|
|
13
|
+
githubOperationSchema,
|
|
14
|
+
gitObjectIdSchema,
|
|
15
|
+
narrowsRepositoryLens,
|
|
16
|
+
repositoryLensSchema,
|
|
17
|
+
repositoryResourceKind,
|
|
18
|
+
} from './github';
|
|
11
19
|
import {
|
|
12
20
|
foundryIdSchema,
|
|
13
21
|
narrowsThreadsLens,
|
|
@@ -21,6 +29,7 @@ import {
|
|
|
21
29
|
} from './threads';
|
|
22
30
|
|
|
23
31
|
export * from './archive';
|
|
32
|
+
export * from './github';
|
|
24
33
|
export * from './threads';
|
|
25
34
|
|
|
26
35
|
const token43 = /^[A-Za-z0-9_-]{43}$/;
|
|
@@ -80,7 +89,11 @@ export const emptyLensSchema = z.strictObject({});
|
|
|
80
89
|
* kind, and Kingdom checks that with `resourceLensSchema` against the kind it stores (review R9).
|
|
81
90
|
* `{}` parses as the empty Archive lens: "no lens", valid for every kind but `threads`.
|
|
82
91
|
*/
|
|
83
|
-
export const signetLensSchema = z.union([
|
|
92
|
+
export const signetLensSchema = z.union([
|
|
93
|
+
archiveLensSchema,
|
|
94
|
+
threadsLensSchema,
|
|
95
|
+
repositoryLensSchema,
|
|
96
|
+
]);
|
|
84
97
|
export type SignetLens = z.infer<typeof signetLensSchema>;
|
|
85
98
|
|
|
86
99
|
/** The lens shape a resource of `kind` takes. */
|
|
@@ -89,7 +102,9 @@ export const resourceLensSchema = (kind: string) =>
|
|
|
89
102
|
? archiveLensSchema
|
|
90
103
|
: kind === threadsResourceKind
|
|
91
104
|
? threadsLensSchema
|
|
92
|
-
:
|
|
105
|
+
: kind === repositoryResourceKind
|
|
106
|
+
? repositoryLensSchema
|
|
107
|
+
: emptyLensSchema;
|
|
93
108
|
|
|
94
109
|
/**
|
|
95
110
|
* `granted` reaches nothing `requested` doesn't, for lenses of the same resource. Approving someone
|
|
@@ -99,7 +114,22 @@ export const resourceLensSchema = (kind: string) =>
|
|
|
99
114
|
export const narrowsSignetLens = (granted: SignetLens, requested: SignetLens): boolean => {
|
|
100
115
|
if ('threads' in granted || 'threads' in requested)
|
|
101
116
|
return 'threads' in granted && 'threads' in requested && narrowsThreadsLens(granted, requested);
|
|
102
|
-
|
|
117
|
+
if ('commits' in granted || 'commits' in requested) {
|
|
118
|
+
const repositoryGranted = repositoryLensSchema.safeParse(granted);
|
|
119
|
+
const repositoryRequested = repositoryLensSchema.safeParse(requested);
|
|
120
|
+
return (
|
|
121
|
+
repositoryGranted.success &&
|
|
122
|
+
repositoryRequested.success &&
|
|
123
|
+
narrowsRepositoryLens(repositoryGranted.data, repositoryRequested.data)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const archiveGranted = archiveLensSchema.safeParse(granted);
|
|
127
|
+
const archiveRequested = archiveLensSchema.safeParse(requested);
|
|
128
|
+
return (
|
|
129
|
+
archiveGranted.success &&
|
|
130
|
+
archiveRequested.success &&
|
|
131
|
+
narrowsArchiveLens(archiveGranted.data, archiveRequested.data)
|
|
132
|
+
);
|
|
103
133
|
};
|
|
104
134
|
|
|
105
135
|
export const signetResourceSchema = z.strictObject({
|
|
@@ -419,10 +449,12 @@ export const accessInputSchema = z.strictObject({
|
|
|
419
449
|
postId: z.uuid().optional(),
|
|
420
450
|
text: z.string().min(1).max(threadTextLimit).optional(),
|
|
421
451
|
inReplyTo: foundryIdSchema.optional(),
|
|
452
|
+
aside: threadInputSchemas['thread.prompt'].shape.aside,
|
|
422
453
|
afterSeq: z.number().int().min(0).optional(),
|
|
423
454
|
floor: threadFloorActionSchema.optional(),
|
|
424
455
|
promptId: foundryIdSchema.optional(),
|
|
425
456
|
answer: threadInputSchemas['prompt.resolve'].shape.answer.optional(),
|
|
457
|
+
objectId: gitObjectIdSchema.optional(),
|
|
426
458
|
});
|
|
427
459
|
export const accessRequestSchema = z.strictObject({
|
|
428
460
|
requestId: z.uuid(),
|
|
@@ -437,18 +469,20 @@ export type AccessRequest = z.infer<typeof accessRequestSchema>;
|
|
|
437
469
|
export const accessResultSchema = z.object({ executionId: z.uuid(), result: z.unknown() });
|
|
438
470
|
|
|
439
471
|
/**
|
|
440
|
-
* Every operation a contract in this package declares (`threads.ts`, `archive.ts`), and the input
|
|
472
|
+
* Every operation a contract in this package declares (`threads.ts`, `archive.ts`, `github.ts`), and the input
|
|
441
473
|
* each takes. Kingdom's `access/execute` check, its catalog and its dispatch read these, so a new
|
|
442
474
|
* operation is added here and nowhere else. Operation names are unique across contracts.
|
|
443
475
|
*/
|
|
444
476
|
export const declaredOperationSchema = z.enum([
|
|
445
477
|
...threadOperationSchema.options,
|
|
446
478
|
...archiveOperationSchema.options,
|
|
479
|
+
...githubOperationSchema.options,
|
|
447
480
|
]);
|
|
448
481
|
export type DeclaredOperation = z.infer<typeof declaredOperationSchema>;
|
|
449
482
|
export const operationInputSchemas = {
|
|
450
483
|
...threadInputSchemas,
|
|
451
484
|
...archiveInputSchemas,
|
|
485
|
+
...githubInputSchemas,
|
|
452
486
|
} as const satisfies Record<DeclaredOperation, z.ZodObject>;
|
|
453
487
|
export type OperationInput<O extends DeclaredOperation> = z.infer<
|
|
454
488
|
(typeof operationInputSchemas)[O]
|
package/src/github.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GitHub repositories (Kingdom `docs/OPERATOR-ACCESS.md` §9). A GitHub integration is one GitHub
|
|
5
|
+
* account; each repository it reaches is a `repository` resource, named `owner/name`. Kingdom admits
|
|
6
|
+
* each call through a Signet and reads the repository with the integration's credential, so the
|
|
7
|
+
* lens is applied by Kingdom.
|
|
8
|
+
*/
|
|
9
|
+
export const repositoryResourceKind = 'repository';
|
|
10
|
+
|
|
11
|
+
/** A Git object id: an immutable commit, tree or blob SHA-1. */
|
|
12
|
+
export const gitObjectIdSchema = z.string().regex(/^[a-f0-9]{40}$/);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Which commits of a `repository` a grant reaches; `{}` reaches the whole repository. A grant pinned
|
|
16
|
+
* to commits reads only those commits: reading a commit never reaches its tree, so a pinned grant
|
|
17
|
+
* reaches no tree or blob.
|
|
18
|
+
*/
|
|
19
|
+
export const repositoryLensSchema = z.strictObject({
|
|
20
|
+
commits: z.array(gitObjectIdSchema).min(1).max(1000).optional(),
|
|
21
|
+
});
|
|
22
|
+
export type RepositoryLens = z.infer<typeof repositoryLensSchema>;
|
|
23
|
+
|
|
24
|
+
/** `granted` reaches no commit `requested` doesn't. */
|
|
25
|
+
export const narrowsRepositoryLens = (granted: RepositoryLens, requested: RepositoryLens) =>
|
|
26
|
+
!requested.commits ||
|
|
27
|
+
(granted.commits?.every((commit) => requested.commits?.includes(commit)) ?? false);
|
|
28
|
+
|
|
29
|
+
/** The operations a `repository` resource carries. */
|
|
30
|
+
export const githubOperationSchema = z.enum(['commits.read', 'trees.read', 'blobs.read']);
|
|
31
|
+
export type GithubOperation = z.infer<typeof githubOperationSchema>;
|
|
32
|
+
|
|
33
|
+
export const githubOperationNames: Record<GithubOperation, string> = {
|
|
34
|
+
'commits.read': 'Read an immutable repository commit',
|
|
35
|
+
'trees.read': 'Read an immutable repository tree',
|
|
36
|
+
'blobs.read': 'Read an immutable repository file',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The input each repository operation takes: the object to read. `access/execute` carries the same
|
|
41
|
+
* field beside `resourceId` and `limit` (`accessInputSchema`).
|
|
42
|
+
*/
|
|
43
|
+
export const githubInputSchemas = {
|
|
44
|
+
'commits.read': z.strictObject({ objectId: gitObjectIdSchema }),
|
|
45
|
+
'trees.read': z.strictObject({ objectId: gitObjectIdSchema }),
|
|
46
|
+
'blobs.read': z.strictObject({ objectId: gitObjectIdSchema }),
|
|
47
|
+
} as const satisfies Record<GithubOperation, z.ZodObject>;
|
|
48
|
+
|
|
49
|
+
/** Why a grant of `operation` can't carry `lens`, or null when it can. */
|
|
50
|
+
export const repositoryLensRefusal = (operation: GithubOperation, lens: RepositoryLens) =>
|
|
51
|
+
lens.commits && operation !== 'commits.read'
|
|
52
|
+
? 'A grant pinned to commits reads only those commits; trees and blobs need an unpinned grant'
|
|
53
|
+
: null;
|
|
54
|
+
|
|
55
|
+
/** Whether every lens a call is admitted under reaches `objectId` for `operation`. */
|
|
56
|
+
export const lensesSelectObject = (
|
|
57
|
+
lenses: RepositoryLens[],
|
|
58
|
+
operation: GithubOperation,
|
|
59
|
+
objectId: string,
|
|
60
|
+
) =>
|
|
61
|
+
lenses.every(
|
|
62
|
+
(lens) =>
|
|
63
|
+
!repositoryLensRefusal(operation, lens) && (!lens.commits || lens.commits.includes(objectId)),
|
|
64
|
+
);
|
package/src/threads.ts
CHANGED
|
@@ -165,6 +165,12 @@ export const threadInputSchemas = {
|
|
|
165
165
|
text: z.string().min(1).max(threadTextLimit),
|
|
166
166
|
/** The invitation a `speak-when-spoken-to` reply answers. */
|
|
167
167
|
inReplyTo: foundryIdSchema.optional(),
|
|
168
|
+
/**
|
|
169
|
+
* An aside: person to person, out of the thread's log (entry kind `aside`). Every person in the
|
|
170
|
+
* thread sees it and no agent does, until someone promotes it to a post. A person types it, never
|
|
171
|
+
* an agent; it is not floor-gated and starts no turn.
|
|
172
|
+
*/
|
|
173
|
+
aside: z.literal(true).optional(),
|
|
168
174
|
}),
|
|
169
175
|
'thread.floor': z.strictObject({ threadId: threadIdSchema, floor: threadFloorActionSchema }),
|
|
170
176
|
'prompt.resolve': z.strictObject({
|