@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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Review Module Exports
3
+ *
4
+ * Types, Zod schemas, and canonicalization for PR review verdicts.
5
+ */
6
+ export * from './schemas.js';
7
+ export * from './canonicalize.js';
8
+ export * from './registry.js';
9
+ export * from './fix-attestation.js';
@@ -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
+ }