@kya-os/contracts 1.10.0 → 1.12.1
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/dist/agentshield-api/schemas.d.ts +14 -0
- package/dist/cli.d.ts +93 -0
- package/dist/cli.js +108 -1
- package/dist/handshake.d.ts +77 -0
- package/dist/handshake.js +73 -1
- package/dist/index.js +1 -0
- package/dist/molti/schemas.d.ts +1 -1
- package/dist/policy/schemas.d.ts +160 -160
- package/dist/proof.d.ts +30 -0
- package/dist/proof.js +6 -0
- package/dist/reputation/api.d.ts +0 -17
- package/dist/reputation/api.js +1 -21
- package/dist/review/canonicalize.d.ts +87 -0
- package/dist/review/canonicalize.js +122 -0
- package/dist/review/fix-attestation.d.ts +151 -0
- package/dist/review/fix-attestation.js +105 -0
- package/dist/review/index.d.ts +9 -0
- package/dist/review/index.js +25 -0
- package/dist/review/registry.d.ts +328 -0
- package/dist/review/registry.js +269 -0
- package/dist/review/schemas.d.ts +249 -0
- package/dist/review/schemas.js +102 -0
- package/dist/review/verify.d.ts +37 -0
- package/dist/review/verify.js +58 -0
- package/dist/test.d.ts +12 -12
- package/dist/vault/schemas.d.ts +2 -2
- package/package.json +9 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Review Module Exports
|
|
4
|
+
*
|
|
5
|
+
* Types, Zod schemas, and canonicalization for PR review verdicts.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
19
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
__exportStar(require("./schemas.js"), exports);
|
|
23
|
+
__exportStar(require("./canonicalize.js"), exports);
|
|
24
|
+
__exportStar(require("./registry.js"), exports);
|
|
25
|
+
__exportStar(require("./fix-attestation.js"), exports);
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewer Registry Schema
|
|
3
|
+
*
|
|
4
|
+
* Maps a human reviewer to their GitHub login, their review agent's DID,
|
|
5
|
+
* and per-person operational config (which env var holds their publish
|
|
6
|
+
* token, where their `review-pr` delegation VC lives on disk). Nothing in
|
|
7
|
+
* the ecosystem persisted this mapping before this schema:
|
|
8
|
+
* `ReviewCoordinator`'s reviewer set
|
|
9
|
+
* (`packages/compute/src/didcomm/review-coordinator.ts`) is an in-memory
|
|
10
|
+
* `Map` filled only via `registerReviewer(did, delegationVC)`, with no
|
|
11
|
+
* file, table, or env format that seeds it, and the only place a GitHub
|
|
12
|
+
* login is read (`packages/cli/src/utils/dco/github.ts`'s `getGhUser`) is
|
|
13
|
+
* transient, used during `dco setup` and never persisted next to the agent
|
|
14
|
+
* DID.
|
|
15
|
+
*
|
|
16
|
+
* Everything downstream needs this mapping: hub wiring registers reviewers
|
|
17
|
+
* from it, a checkpoint credential script reads it to know which DIDs to
|
|
18
|
+
* issue VCs for, a publisher picks per-person GitHub tokens from it, and a
|
|
19
|
+
* fix dispatcher joins PR author login to author agent DID through it. See
|
|
20
|
+
* `packages/compute/src/didcomm/reviewer-registry.ts` for the loader that
|
|
21
|
+
* turns a registry file (or inline JSON) into this shape.
|
|
22
|
+
*
|
|
23
|
+
* Pure Zod schemas only, matching the rest of `@kya-os/contracts/review`.
|
|
24
|
+
*
|
|
25
|
+
* 2026-08-21 amendment (review-loop P15): one person can run more than one
|
|
26
|
+
* agent (e.g. a `dco setup`-configured agent plus a detected Claude Code
|
|
27
|
+
* profile from `kya-os init`'s agent-detection stage), so uniqueness moved
|
|
28
|
+
* from `githubLogin` alone to the `(githubLogin, slug)` pair -- see the
|
|
29
|
+
* `superRefine` below. `agentDid` stays fully unique across entries, and a
|
|
30
|
+
* new refinement caps `fixes: true` at one entry per `githubLogin`: the fix
|
|
31
|
+
* dispatcher's author-login join must stay single-valued even when a person
|
|
32
|
+
* has multiple agents.
|
|
33
|
+
*
|
|
34
|
+
* @package @kya-os/contracts/review
|
|
35
|
+
*/
|
|
36
|
+
import { z } from 'zod';
|
|
37
|
+
/**
|
|
38
|
+
* Reviewer Registry Entry Schema
|
|
39
|
+
*
|
|
40
|
+
* One person and their review agent.
|
|
41
|
+
*/
|
|
42
|
+
export declare const ReviewerRegistryEntrySchema: z.ZodObject<{
|
|
43
|
+
/** Display name, e.g. "Marshall". */
|
|
44
|
+
person: z.ZodString;
|
|
45
|
+
/** GitHub login, e.g. "MarshallBorham". Must be unique across entries. */
|
|
46
|
+
githubLogin: z.ZodString;
|
|
47
|
+
/**
|
|
48
|
+
* DCO agent slug, e.g. "marshall-agent". Restricted to the same
|
|
49
|
+
* `[a-z0-9][a-z0-9-]*` shape as `SLUG_PATTERN` in
|
|
50
|
+
* `packages/cli/src/utils/cli-kit/paths.ts` (mirrored here rather than
|
|
51
|
+
* imported: contracts has no dependency on cli). That pattern's own doc
|
|
52
|
+
* comment explains why: slugs are often sourced from a file or env var
|
|
53
|
+
* an untrusted source can influence, and become a path segment -- the
|
|
54
|
+
* same shape of risk this registry's file/env-sourced `slug` carries.
|
|
55
|
+
*/
|
|
56
|
+
slug: z.ZodString;
|
|
57
|
+
/** "did:web:knowthat.ai:agents:<slug>". Must be unique across entries. */
|
|
58
|
+
agentDid: z.ZodString;
|
|
59
|
+
/** Whether this person's agent acts as a reviewer (counts toward `quorumN`). */
|
|
60
|
+
reviews: z.ZodBoolean;
|
|
61
|
+
/** Whether this person receives fix dispatches for their own PRs. */
|
|
62
|
+
fixes: z.ZodBoolean;
|
|
63
|
+
/**
|
|
64
|
+
* NAME of an environment variable holding this person's GitHub publish
|
|
65
|
+
* token, never the token value itself. The registry file is expected to
|
|
66
|
+
* be committed to private deploy config; a literal token here would be a
|
|
67
|
+
* leak.
|
|
68
|
+
*/
|
|
69
|
+
publishTokenEnv: z.ZodOptional<z.ZodString>;
|
|
70
|
+
/**
|
|
71
|
+
* Path to this agent's `review-pr` delegation VC. Resolved by the loader
|
|
72
|
+
* relative to the registry file's directory, not to the process cwd.
|
|
73
|
+
*/
|
|
74
|
+
delegationVcPath: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, "strip", z.ZodTypeAny, {
|
|
76
|
+
agentDid: string;
|
|
77
|
+
reviews: boolean;
|
|
78
|
+
person: string;
|
|
79
|
+
githubLogin: string;
|
|
80
|
+
slug: string;
|
|
81
|
+
fixes: boolean;
|
|
82
|
+
publishTokenEnv?: string | undefined;
|
|
83
|
+
delegationVcPath?: string | undefined;
|
|
84
|
+
}, {
|
|
85
|
+
agentDid: string;
|
|
86
|
+
reviews: boolean;
|
|
87
|
+
person: string;
|
|
88
|
+
githubLogin: string;
|
|
89
|
+
slug: string;
|
|
90
|
+
fixes: boolean;
|
|
91
|
+
publishTokenEnv?: string | undefined;
|
|
92
|
+
delegationVcPath?: string | undefined;
|
|
93
|
+
}>;
|
|
94
|
+
export type ReviewerRegistryEntry = z.infer<typeof ReviewerRegistryEntrySchema>;
|
|
95
|
+
/**
|
|
96
|
+
* Reviewer Registry Schema
|
|
97
|
+
*
|
|
98
|
+
* The full registry: org identity, quorum size, watched repos, and the
|
|
99
|
+
* per-person entries. Enforced invariants (see the `superRefine` below):
|
|
100
|
+
* `agentDid` unique across entries, `githubLogin` unique across entries,
|
|
101
|
+
* and `quorumN` no larger than the number of entries with `reviews: true`.
|
|
102
|
+
*
|
|
103
|
+
* Worked example, a three-person org where two of the three vote
|
|
104
|
+
* (`quorumN: 2`, Brian is a fix-only participant who doesn't review), and
|
|
105
|
+
* Marshall runs a second agent (a `kya-os init`-detected Claude Code
|
|
106
|
+
* profile) alongside his original one -- same `githubLogin`, distinct
|
|
107
|
+
* `slug`/`agentDid`, and `fixes: true` only on the original so fix dispatch
|
|
108
|
+
* stays single-valued per person:
|
|
109
|
+
*
|
|
110
|
+
* ```json
|
|
111
|
+
* {
|
|
112
|
+
* "orgId": "vouched",
|
|
113
|
+
* "orgRootDid": "did:web:kya.vouched.id:orgs:vouched",
|
|
114
|
+
* "quorumN": 2,
|
|
115
|
+
* "repos": ["modelcontextprotocol-identity/xmcp-i"],
|
|
116
|
+
* "entries": [
|
|
117
|
+
* {
|
|
118
|
+
* "person": "Marshall",
|
|
119
|
+
* "githubLogin": "marshall-vouched",
|
|
120
|
+
* "slug": "marshall-agent",
|
|
121
|
+
* "agentDid": "did:web:knowthat.ai:agents:marshall-agent",
|
|
122
|
+
* "reviews": true,
|
|
123
|
+
* "fixes": true,
|
|
124
|
+
* "publishTokenEnv": "MARSHALL_GITHUB_TOKEN",
|
|
125
|
+
* "delegationVcPath": "./vcs/marshall-agent.review-pr.vc.json"
|
|
126
|
+
* },
|
|
127
|
+
* {
|
|
128
|
+
* "person": "Marshall",
|
|
129
|
+
* "githubLogin": "marshall-vouched",
|
|
130
|
+
* "slug": "marshall-claude-code",
|
|
131
|
+
* "agentDid": "did:web:knowthat.ai:agents:marshall-claude-code",
|
|
132
|
+
* "reviews": true,
|
|
133
|
+
* "fixes": false
|
|
134
|
+
* },
|
|
135
|
+
* {
|
|
136
|
+
* "person": "Dylan",
|
|
137
|
+
* "githubLogin": "dylan-vouched",
|
|
138
|
+
* "slug": "dylan-agent",
|
|
139
|
+
* "agentDid": "did:web:knowthat.ai:agents:dylan-agent",
|
|
140
|
+
* "reviews": true,
|
|
141
|
+
* "fixes": true,
|
|
142
|
+
* "publishTokenEnv": "DYLAN_GITHUB_TOKEN",
|
|
143
|
+
* "delegationVcPath": "./vcs/dylan-agent.review-pr.vc.json"
|
|
144
|
+
* },
|
|
145
|
+
* {
|
|
146
|
+
* "person": "Brian",
|
|
147
|
+
* "githubLogin": "brian-vouched",
|
|
148
|
+
* "slug": "brian-agent",
|
|
149
|
+
* "agentDid": "did:web:knowthat.ai:agents:brian-agent",
|
|
150
|
+
* "reviews": false,
|
|
151
|
+
* "fixes": true,
|
|
152
|
+
* "publishTokenEnv": "BRIAN_GITHUB_TOKEN"
|
|
153
|
+
* }
|
|
154
|
+
* ]
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export declare const ReviewerRegistrySchema: z.ZodEffects<z.ZodObject<{
|
|
159
|
+
orgId: z.ZodString;
|
|
160
|
+
/** "did:web:kya.vouched.id:orgs:<slug>". */
|
|
161
|
+
orgRootDid: z.ZodString;
|
|
162
|
+
/**
|
|
163
|
+
* Minimum number of `reviews: true` entries whose verdicts must agree.
|
|
164
|
+
*
|
|
165
|
+
* SECURITY NOTE for whoever wires this registry into quorum counting
|
|
166
|
+
* (e.g. `ReviewCoordinator.registerReviewer()` /
|
|
167
|
+
* `packages/compute/src/didcomm/hub.ts`'s `recordApprovalAndCheckQuorum`,
|
|
168
|
+
* which tracks approvals in a `Set<agentDid>`): quorum there is counted
|
|
169
|
+
* per DID, not per person. Since the 2026-08-21 relaxation above allows
|
|
170
|
+
* one `githubLogin` to own multiple `reviews: true` entries (multiple
|
|
171
|
+
* agent DIDs), a single person running several locally-created agent
|
|
172
|
+
* profiles (e.g. via `kya-os init --profiles claude-code,codex`) can
|
|
173
|
+
* supply multiple distinct DIDs and, if each is registered as a
|
|
174
|
+
* reviewer, satisfy `quorumN >= 2` alone. Use `countDistinctApprovers`
|
|
175
|
+
* (exported below) wherever quorum is counted against this registry --
|
|
176
|
+
* it de-dupes by `githubLogin` instead of raw DID, closing exactly this
|
|
177
|
+
* gap. Restricting `reviews: true` to one entry per `githubLogin` is
|
|
178
|
+
* NOT the fix: the worked example above intentionally wants a person's
|
|
179
|
+
* second agent to also review.
|
|
180
|
+
*/
|
|
181
|
+
quorumN: z.ZodNumber;
|
|
182
|
+
/**
|
|
183
|
+
* "owner/name" repo slugs this registry's reviewers watch. Character
|
|
184
|
+
* class matches `isValidRepoSlug`'s `REPO_SLUG_PATTERN` in
|
|
185
|
+
* `packages/compute/src/didcomm/github-client.ts` (mirrored here rather
|
|
186
|
+
* than imported: contracts has no dependency on compute). Downstream
|
|
187
|
+
* code concatenates these slugs directly into unencoded GitHub REST
|
|
188
|
+
* URLs, so a looser pattern (e.g. one that only excludes `/` and
|
|
189
|
+
* whitespace) would let `owner/repo?x` through as a "valid" slug.
|
|
190
|
+
*
|
|
191
|
+
* Each segment additionally can't be all dots (`.`, `..`, ...): the
|
|
192
|
+
* character class alone still accepts `../evil`, since `.` is a
|
|
193
|
+
* legal repo-name character and `..` is just two of them -- the same
|
|
194
|
+
* gap `REPO_SLUG_PATTERN` itself has. No real GitHub owner or repo
|
|
195
|
+
* name is ever pure punctuation, so excluding it closes the traversal
|
|
196
|
+
* case without rejecting any legitimate slug.
|
|
197
|
+
*/
|
|
198
|
+
repos: z.ZodArray<z.ZodString, "many">;
|
|
199
|
+
entries: z.ZodArray<z.ZodObject<{
|
|
200
|
+
/** Display name, e.g. "Marshall". */
|
|
201
|
+
person: z.ZodString;
|
|
202
|
+
/** GitHub login, e.g. "MarshallBorham". Must be unique across entries. */
|
|
203
|
+
githubLogin: z.ZodString;
|
|
204
|
+
/**
|
|
205
|
+
* DCO agent slug, e.g. "marshall-agent". Restricted to the same
|
|
206
|
+
* `[a-z0-9][a-z0-9-]*` shape as `SLUG_PATTERN` in
|
|
207
|
+
* `packages/cli/src/utils/cli-kit/paths.ts` (mirrored here rather than
|
|
208
|
+
* imported: contracts has no dependency on cli). That pattern's own doc
|
|
209
|
+
* comment explains why: slugs are often sourced from a file or env var
|
|
210
|
+
* an untrusted source can influence, and become a path segment -- the
|
|
211
|
+
* same shape of risk this registry's file/env-sourced `slug` carries.
|
|
212
|
+
*/
|
|
213
|
+
slug: z.ZodString;
|
|
214
|
+
/** "did:web:knowthat.ai:agents:<slug>". Must be unique across entries. */
|
|
215
|
+
agentDid: z.ZodString;
|
|
216
|
+
/** Whether this person's agent acts as a reviewer (counts toward `quorumN`). */
|
|
217
|
+
reviews: z.ZodBoolean;
|
|
218
|
+
/** Whether this person receives fix dispatches for their own PRs. */
|
|
219
|
+
fixes: z.ZodBoolean;
|
|
220
|
+
/**
|
|
221
|
+
* NAME of an environment variable holding this person's GitHub publish
|
|
222
|
+
* token, never the token value itself. The registry file is expected to
|
|
223
|
+
* be committed to private deploy config; a literal token here would be a
|
|
224
|
+
* leak.
|
|
225
|
+
*/
|
|
226
|
+
publishTokenEnv: z.ZodOptional<z.ZodString>;
|
|
227
|
+
/**
|
|
228
|
+
* Path to this agent's `review-pr` delegation VC. Resolved by the loader
|
|
229
|
+
* relative to the registry file's directory, not to the process cwd.
|
|
230
|
+
*/
|
|
231
|
+
delegationVcPath: z.ZodOptional<z.ZodString>;
|
|
232
|
+
}, "strip", z.ZodTypeAny, {
|
|
233
|
+
agentDid: string;
|
|
234
|
+
reviews: boolean;
|
|
235
|
+
person: string;
|
|
236
|
+
githubLogin: string;
|
|
237
|
+
slug: string;
|
|
238
|
+
fixes: boolean;
|
|
239
|
+
publishTokenEnv?: string | undefined;
|
|
240
|
+
delegationVcPath?: string | undefined;
|
|
241
|
+
}, {
|
|
242
|
+
agentDid: string;
|
|
243
|
+
reviews: boolean;
|
|
244
|
+
person: string;
|
|
245
|
+
githubLogin: string;
|
|
246
|
+
slug: string;
|
|
247
|
+
fixes: boolean;
|
|
248
|
+
publishTokenEnv?: string | undefined;
|
|
249
|
+
delegationVcPath?: string | undefined;
|
|
250
|
+
}>, "many">;
|
|
251
|
+
}, "strip", z.ZodTypeAny, {
|
|
252
|
+
entries: {
|
|
253
|
+
agentDid: string;
|
|
254
|
+
reviews: boolean;
|
|
255
|
+
person: string;
|
|
256
|
+
githubLogin: string;
|
|
257
|
+
slug: string;
|
|
258
|
+
fixes: boolean;
|
|
259
|
+
publishTokenEnv?: string | undefined;
|
|
260
|
+
delegationVcPath?: string | undefined;
|
|
261
|
+
}[];
|
|
262
|
+
orgId: string;
|
|
263
|
+
orgRootDid: string;
|
|
264
|
+
quorumN: number;
|
|
265
|
+
repos: string[];
|
|
266
|
+
}, {
|
|
267
|
+
entries: {
|
|
268
|
+
agentDid: string;
|
|
269
|
+
reviews: boolean;
|
|
270
|
+
person: string;
|
|
271
|
+
githubLogin: string;
|
|
272
|
+
slug: string;
|
|
273
|
+
fixes: boolean;
|
|
274
|
+
publishTokenEnv?: string | undefined;
|
|
275
|
+
delegationVcPath?: string | undefined;
|
|
276
|
+
}[];
|
|
277
|
+
orgId: string;
|
|
278
|
+
orgRootDid: string;
|
|
279
|
+
quorumN: number;
|
|
280
|
+
repos: string[];
|
|
281
|
+
}>, {
|
|
282
|
+
entries: {
|
|
283
|
+
agentDid: string;
|
|
284
|
+
reviews: boolean;
|
|
285
|
+
person: string;
|
|
286
|
+
githubLogin: string;
|
|
287
|
+
slug: string;
|
|
288
|
+
fixes: boolean;
|
|
289
|
+
publishTokenEnv?: string | undefined;
|
|
290
|
+
delegationVcPath?: string | undefined;
|
|
291
|
+
}[];
|
|
292
|
+
orgId: string;
|
|
293
|
+
orgRootDid: string;
|
|
294
|
+
quorumN: number;
|
|
295
|
+
repos: string[];
|
|
296
|
+
}, {
|
|
297
|
+
entries: {
|
|
298
|
+
agentDid: string;
|
|
299
|
+
reviews: boolean;
|
|
300
|
+
person: string;
|
|
301
|
+
githubLogin: string;
|
|
302
|
+
slug: string;
|
|
303
|
+
fixes: boolean;
|
|
304
|
+
publishTokenEnv?: string | undefined;
|
|
305
|
+
delegationVcPath?: string | undefined;
|
|
306
|
+
}[];
|
|
307
|
+
orgId: string;
|
|
308
|
+
orgRootDid: string;
|
|
309
|
+
quorumN: number;
|
|
310
|
+
repos: string[];
|
|
311
|
+
}>;
|
|
312
|
+
export type ReviewerRegistry = z.infer<typeof ReviewerRegistrySchema>;
|
|
313
|
+
/**
|
|
314
|
+
* Counts DISTINCT PEOPLE (by `githubLogin`) among a set of approving agent
|
|
315
|
+
* DIDs -- not the raw DID count. Quorum counting against this registry MUST
|
|
316
|
+
* use this (or an equivalent per-person dedup), never `approvedDids.size`
|
|
317
|
+
* directly: since the 2026-08-21 relaxation above, one `githubLogin` can own
|
|
318
|
+
* more than one `reviews: true` entry (multiple agent DIDs), so counting raw
|
|
319
|
+
* DIDs lets one person satisfy a multi-reviewer `quorumN` alone by running
|
|
320
|
+
* several agents. See the `SECURITY NOTE` on `quorumN` above for the full
|
|
321
|
+
* threat and the consumer (`packages/compute/src/didcomm/hub.ts`'s
|
|
322
|
+
* `recordApprovalAndCheckQuorum`) this exists for.
|
|
323
|
+
*
|
|
324
|
+
* A DID not present in `registry.entries` is ignored (contributes nothing),
|
|
325
|
+
* matching the existing quorum consumer's own trust boundary: only DIDs the
|
|
326
|
+
* registry itself vouches for should ever count toward quorum.
|
|
327
|
+
*/
|
|
328
|
+
export declare function countDistinctApprovers(registry: ReviewerRegistry, approvedDids: ReadonlySet<string> | readonly string[]): number;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reviewer Registry Schema
|
|
4
|
+
*
|
|
5
|
+
* Maps a human reviewer to their GitHub login, their review agent's DID,
|
|
6
|
+
* and per-person operational config (which env var holds their publish
|
|
7
|
+
* token, where their `review-pr` delegation VC lives on disk). Nothing in
|
|
8
|
+
* the ecosystem persisted this mapping before this schema:
|
|
9
|
+
* `ReviewCoordinator`'s reviewer set
|
|
10
|
+
* (`packages/compute/src/didcomm/review-coordinator.ts`) is an in-memory
|
|
11
|
+
* `Map` filled only via `registerReviewer(did, delegationVC)`, with no
|
|
12
|
+
* file, table, or env format that seeds it, and the only place a GitHub
|
|
13
|
+
* login is read (`packages/cli/src/utils/dco/github.ts`'s `getGhUser`) is
|
|
14
|
+
* transient, used during `dco setup` and never persisted next to the agent
|
|
15
|
+
* DID.
|
|
16
|
+
*
|
|
17
|
+
* Everything downstream needs this mapping: hub wiring registers reviewers
|
|
18
|
+
* from it, a checkpoint credential script reads it to know which DIDs to
|
|
19
|
+
* issue VCs for, a publisher picks per-person GitHub tokens from it, and a
|
|
20
|
+
* fix dispatcher joins PR author login to author agent DID through it. See
|
|
21
|
+
* `packages/compute/src/didcomm/reviewer-registry.ts` for the loader that
|
|
22
|
+
* turns a registry file (or inline JSON) into this shape.
|
|
23
|
+
*
|
|
24
|
+
* Pure Zod schemas only, matching the rest of `@kya-os/contracts/review`.
|
|
25
|
+
*
|
|
26
|
+
* 2026-08-21 amendment (review-loop P15): one person can run more than one
|
|
27
|
+
* agent (e.g. a `dco setup`-configured agent plus a detected Claude Code
|
|
28
|
+
* profile from `kya-os init`'s agent-detection stage), so uniqueness moved
|
|
29
|
+
* from `githubLogin` alone to the `(githubLogin, slug)` pair -- see the
|
|
30
|
+
* `superRefine` below. `agentDid` stays fully unique across entries, and a
|
|
31
|
+
* new refinement caps `fixes: true` at one entry per `githubLogin`: the fix
|
|
32
|
+
* dispatcher's author-login join must stay single-valued even when a person
|
|
33
|
+
* has multiple agents.
|
|
34
|
+
*
|
|
35
|
+
* @package @kya-os/contracts/review
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.ReviewerRegistrySchema = exports.ReviewerRegistryEntrySchema = void 0;
|
|
39
|
+
exports.countDistinctApprovers = countDistinctApprovers;
|
|
40
|
+
const zod_1 = require("zod");
|
|
41
|
+
/**
|
|
42
|
+
* Reviewer Registry Entry Schema
|
|
43
|
+
*
|
|
44
|
+
* One person and their review agent.
|
|
45
|
+
*/
|
|
46
|
+
exports.ReviewerRegistryEntrySchema = zod_1.z.object({
|
|
47
|
+
/** Display name, e.g. "Marshall". */
|
|
48
|
+
person: zod_1.z.string().min(1),
|
|
49
|
+
/** GitHub login, e.g. "MarshallBorham". Must be unique across entries. */
|
|
50
|
+
githubLogin: zod_1.z.string().min(1),
|
|
51
|
+
/**
|
|
52
|
+
* DCO agent slug, e.g. "marshall-agent". Restricted to the same
|
|
53
|
+
* `[a-z0-9][a-z0-9-]*` shape as `SLUG_PATTERN` in
|
|
54
|
+
* `packages/cli/src/utils/cli-kit/paths.ts` (mirrored here rather than
|
|
55
|
+
* imported: contracts has no dependency on cli). That pattern's own doc
|
|
56
|
+
* comment explains why: slugs are often sourced from a file or env var
|
|
57
|
+
* an untrusted source can influence, and become a path segment -- the
|
|
58
|
+
* same shape of risk this registry's file/env-sourced `slug` carries.
|
|
59
|
+
*/
|
|
60
|
+
slug: zod_1.z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "must match [a-z0-9][a-z0-9-]*"),
|
|
61
|
+
/** "did:web:knowthat.ai:agents:<slug>". Must be unique across entries. */
|
|
62
|
+
agentDid: zod_1.z.string().min(1),
|
|
63
|
+
/** Whether this person's agent acts as a reviewer (counts toward `quorumN`). */
|
|
64
|
+
reviews: zod_1.z.boolean(),
|
|
65
|
+
/** Whether this person receives fix dispatches for their own PRs. */
|
|
66
|
+
fixes: zod_1.z.boolean(),
|
|
67
|
+
/**
|
|
68
|
+
* NAME of an environment variable holding this person's GitHub publish
|
|
69
|
+
* token, never the token value itself. The registry file is expected to
|
|
70
|
+
* be committed to private deploy config; a literal token here would be a
|
|
71
|
+
* leak.
|
|
72
|
+
*/
|
|
73
|
+
publishTokenEnv: zod_1.z.string().min(1).optional(),
|
|
74
|
+
/**
|
|
75
|
+
* Path to this agent's `review-pr` delegation VC. Resolved by the loader
|
|
76
|
+
* relative to the registry file's directory, not to the process cwd.
|
|
77
|
+
*/
|
|
78
|
+
delegationVcPath: zod_1.z.string().min(1).optional(),
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* Reviewer Registry Schema
|
|
82
|
+
*
|
|
83
|
+
* The full registry: org identity, quorum size, watched repos, and the
|
|
84
|
+
* per-person entries. Enforced invariants (see the `superRefine` below):
|
|
85
|
+
* `agentDid` unique across entries, `githubLogin` unique across entries,
|
|
86
|
+
* and `quorumN` no larger than the number of entries with `reviews: true`.
|
|
87
|
+
*
|
|
88
|
+
* Worked example, a three-person org where two of the three vote
|
|
89
|
+
* (`quorumN: 2`, Brian is a fix-only participant who doesn't review), and
|
|
90
|
+
* Marshall runs a second agent (a `kya-os init`-detected Claude Code
|
|
91
|
+
* profile) alongside his original one -- same `githubLogin`, distinct
|
|
92
|
+
* `slug`/`agentDid`, and `fixes: true` only on the original so fix dispatch
|
|
93
|
+
* stays single-valued per person:
|
|
94
|
+
*
|
|
95
|
+
* ```json
|
|
96
|
+
* {
|
|
97
|
+
* "orgId": "vouched",
|
|
98
|
+
* "orgRootDid": "did:web:kya.vouched.id:orgs:vouched",
|
|
99
|
+
* "quorumN": 2,
|
|
100
|
+
* "repos": ["modelcontextprotocol-identity/xmcp-i"],
|
|
101
|
+
* "entries": [
|
|
102
|
+
* {
|
|
103
|
+
* "person": "Marshall",
|
|
104
|
+
* "githubLogin": "marshall-vouched",
|
|
105
|
+
* "slug": "marshall-agent",
|
|
106
|
+
* "agentDid": "did:web:knowthat.ai:agents:marshall-agent",
|
|
107
|
+
* "reviews": true,
|
|
108
|
+
* "fixes": true,
|
|
109
|
+
* "publishTokenEnv": "MARSHALL_GITHUB_TOKEN",
|
|
110
|
+
* "delegationVcPath": "./vcs/marshall-agent.review-pr.vc.json"
|
|
111
|
+
* },
|
|
112
|
+
* {
|
|
113
|
+
* "person": "Marshall",
|
|
114
|
+
* "githubLogin": "marshall-vouched",
|
|
115
|
+
* "slug": "marshall-claude-code",
|
|
116
|
+
* "agentDid": "did:web:knowthat.ai:agents:marshall-claude-code",
|
|
117
|
+
* "reviews": true,
|
|
118
|
+
* "fixes": false
|
|
119
|
+
* },
|
|
120
|
+
* {
|
|
121
|
+
* "person": "Dylan",
|
|
122
|
+
* "githubLogin": "dylan-vouched",
|
|
123
|
+
* "slug": "dylan-agent",
|
|
124
|
+
* "agentDid": "did:web:knowthat.ai:agents:dylan-agent",
|
|
125
|
+
* "reviews": true,
|
|
126
|
+
* "fixes": true,
|
|
127
|
+
* "publishTokenEnv": "DYLAN_GITHUB_TOKEN",
|
|
128
|
+
* "delegationVcPath": "./vcs/dylan-agent.review-pr.vc.json"
|
|
129
|
+
* },
|
|
130
|
+
* {
|
|
131
|
+
* "person": "Brian",
|
|
132
|
+
* "githubLogin": "brian-vouched",
|
|
133
|
+
* "slug": "brian-agent",
|
|
134
|
+
* "agentDid": "did:web:knowthat.ai:agents:brian-agent",
|
|
135
|
+
* "reviews": false,
|
|
136
|
+
* "fixes": true,
|
|
137
|
+
* "publishTokenEnv": "BRIAN_GITHUB_TOKEN"
|
|
138
|
+
* }
|
|
139
|
+
* ]
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
exports.ReviewerRegistrySchema = zod_1.z
|
|
144
|
+
.object({
|
|
145
|
+
orgId: zod_1.z.string().min(1),
|
|
146
|
+
/** "did:web:kya.vouched.id:orgs:<slug>". */
|
|
147
|
+
orgRootDid: zod_1.z.string().min(1),
|
|
148
|
+
/**
|
|
149
|
+
* Minimum number of `reviews: true` entries whose verdicts must agree.
|
|
150
|
+
*
|
|
151
|
+
* SECURITY NOTE for whoever wires this registry into quorum counting
|
|
152
|
+
* (e.g. `ReviewCoordinator.registerReviewer()` /
|
|
153
|
+
* `packages/compute/src/didcomm/hub.ts`'s `recordApprovalAndCheckQuorum`,
|
|
154
|
+
* which tracks approvals in a `Set<agentDid>`): quorum there is counted
|
|
155
|
+
* per DID, not per person. Since the 2026-08-21 relaxation above allows
|
|
156
|
+
* one `githubLogin` to own multiple `reviews: true` entries (multiple
|
|
157
|
+
* agent DIDs), a single person running several locally-created agent
|
|
158
|
+
* profiles (e.g. via `kya-os init --profiles claude-code,codex`) can
|
|
159
|
+
* supply multiple distinct DIDs and, if each is registered as a
|
|
160
|
+
* reviewer, satisfy `quorumN >= 2` alone. Use `countDistinctApprovers`
|
|
161
|
+
* (exported below) wherever quorum is counted against this registry --
|
|
162
|
+
* it de-dupes by `githubLogin` instead of raw DID, closing exactly this
|
|
163
|
+
* gap. Restricting `reviews: true` to one entry per `githubLogin` is
|
|
164
|
+
* NOT the fix: the worked example above intentionally wants a person's
|
|
165
|
+
* second agent to also review.
|
|
166
|
+
*/
|
|
167
|
+
quorumN: zod_1.z.number().int().positive(),
|
|
168
|
+
/**
|
|
169
|
+
* "owner/name" repo slugs this registry's reviewers watch. Character
|
|
170
|
+
* class matches `isValidRepoSlug`'s `REPO_SLUG_PATTERN` in
|
|
171
|
+
* `packages/compute/src/didcomm/github-client.ts` (mirrored here rather
|
|
172
|
+
* than imported: contracts has no dependency on compute). Downstream
|
|
173
|
+
* code concatenates these slugs directly into unencoded GitHub REST
|
|
174
|
+
* URLs, so a looser pattern (e.g. one that only excludes `/` and
|
|
175
|
+
* whitespace) would let `owner/repo?x` through as a "valid" slug.
|
|
176
|
+
*
|
|
177
|
+
* Each segment additionally can't be all dots (`.`, `..`, ...): the
|
|
178
|
+
* character class alone still accepts `../evil`, since `.` is a
|
|
179
|
+
* legal repo-name character and `..` is just two of them -- the same
|
|
180
|
+
* gap `REPO_SLUG_PATTERN` itself has. No real GitHub owner or repo
|
|
181
|
+
* name is ever pure punctuation, so excluding it closes the traversal
|
|
182
|
+
* case without rejecting any legitimate slug.
|
|
183
|
+
*/
|
|
184
|
+
repos: zod_1.z
|
|
185
|
+
.array(zod_1.z.string().regex(/^(?!\.+\/)[A-Za-z0-9._-]+\/(?!\.+$)[A-Za-z0-9._-]+$/, 'must be "owner/name"'))
|
|
186
|
+
.min(1),
|
|
187
|
+
entries: zod_1.z.array(exports.ReviewerRegistryEntrySchema).min(1),
|
|
188
|
+
})
|
|
189
|
+
.superRefine((data, ctx) => {
|
|
190
|
+
const seenDids = new Map();
|
|
191
|
+
const seenLoginSlugPairs = new Map();
|
|
192
|
+
const fixesTrueIndexByLogin = new Map();
|
|
193
|
+
data.entries.forEach((entry, index) => {
|
|
194
|
+
const priorDidIndex = seenDids.get(entry.agentDid);
|
|
195
|
+
if (priorDidIndex !== undefined) {
|
|
196
|
+
ctx.addIssue({
|
|
197
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
198
|
+
message: `Duplicate agentDid "${entry.agentDid}" (also at entries[${priorDidIndex}])`,
|
|
199
|
+
path: ['entries', index, 'agentDid'],
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
seenDids.set(entry.agentDid, index);
|
|
204
|
+
}
|
|
205
|
+
// One person may run more than one agent, so githubLogin alone is no
|
|
206
|
+
// longer unique; the (githubLogin, slug) pair still must be.
|
|
207
|
+
const pairKey = `${entry.githubLogin}${entry.slug}`;
|
|
208
|
+
const priorPairIndex = seenLoginSlugPairs.get(pairKey);
|
|
209
|
+
if (priorPairIndex !== undefined) {
|
|
210
|
+
ctx.addIssue({
|
|
211
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
212
|
+
message: `Duplicate (githubLogin, slug) "${entry.githubLogin}", "${entry.slug}" (also at entries[${priorPairIndex}])`,
|
|
213
|
+
path: ['entries', index, 'slug'],
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
seenLoginSlugPairs.set(pairKey, index);
|
|
218
|
+
}
|
|
219
|
+
// The fix dispatcher joins PR author login to author agent DID, which
|
|
220
|
+
// must stay single-valued even when a person has multiple agents.
|
|
221
|
+
if (entry.fixes) {
|
|
222
|
+
const priorFixesIndex = fixesTrueIndexByLogin.get(entry.githubLogin);
|
|
223
|
+
if (priorFixesIndex !== undefined) {
|
|
224
|
+
ctx.addIssue({
|
|
225
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
226
|
+
message: `Duplicate fixes: true for githubLogin "${entry.githubLogin}" (also at entries[${priorFixesIndex}]); at most one agent per person may receive fix dispatches`,
|
|
227
|
+
path: ['entries', index, 'fixes'],
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
fixesTrueIndexByLogin.set(entry.githubLogin, index);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
const reviewerCount = data.entries.filter((entry) => entry.reviews).length;
|
|
236
|
+
if (data.quorumN > reviewerCount) {
|
|
237
|
+
ctx.addIssue({
|
|
238
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
239
|
+
message: `quorumN (${data.quorumN}) exceeds the number of entries with reviews: true (${reviewerCount})`,
|
|
240
|
+
path: ['quorumN'],
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
/**
|
|
245
|
+
* Counts DISTINCT PEOPLE (by `githubLogin`) among a set of approving agent
|
|
246
|
+
* DIDs -- not the raw DID count. Quorum counting against this registry MUST
|
|
247
|
+
* use this (or an equivalent per-person dedup), never `approvedDids.size`
|
|
248
|
+
* directly: since the 2026-08-21 relaxation above, one `githubLogin` can own
|
|
249
|
+
* more than one `reviews: true` entry (multiple agent DIDs), so counting raw
|
|
250
|
+
* DIDs lets one person satisfy a multi-reviewer `quorumN` alone by running
|
|
251
|
+
* several agents. See the `SECURITY NOTE` on `quorumN` above for the full
|
|
252
|
+
* threat and the consumer (`packages/compute/src/didcomm/hub.ts`'s
|
|
253
|
+
* `recordApprovalAndCheckQuorum`) this exists for.
|
|
254
|
+
*
|
|
255
|
+
* A DID not present in `registry.entries` is ignored (contributes nothing),
|
|
256
|
+
* matching the existing quorum consumer's own trust boundary: only DIDs the
|
|
257
|
+
* registry itself vouches for should ever count toward quorum.
|
|
258
|
+
*/
|
|
259
|
+
function countDistinctApprovers(registry, approvedDids) {
|
|
260
|
+
const loginByDid = new Map(registry.entries.map((e) => [e.agentDid, e.githubLogin]));
|
|
261
|
+
const distinctLogins = new Set();
|
|
262
|
+
for (const did of approvedDids) {
|
|
263
|
+
const login = loginByDid.get(did);
|
|
264
|
+
if (login) {
|
|
265
|
+
distinctLogins.add(login);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return distinctLogins.size;
|
|
269
|
+
}
|