@kici-dev/engine 0.5.0 → 0.6.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.
Files changed (68) hide show
  1. package/dist/audit/access-log-policy.js +1 -0
  2. package/dist/audit/retention-policy.js +2 -0
  3. package/dist/context/held-run-job-id.d.ts +26 -10
  4. package/dist/context/held-run-job-id.js +30 -11
  5. package/dist/context/index.d.ts +1 -1
  6. package/dist/context/index.js +3 -3
  7. package/dist/context/types.d.ts +10 -1
  8. package/dist/context/types.js +10 -1
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.js +14 -11
  11. package/dist/labels.d.ts +66 -12
  12. package/dist/labels.js +74 -17
  13. package/dist/mcp/held-run-resolve.d.ts +40 -0
  14. package/dist/mcp/held-run-resolve.js +68 -15
  15. package/dist/mcp/tool-schemas.d.ts +4 -0
  16. package/dist/mcp/tool-schemas.js +13 -1
  17. package/dist/metrics/catalog-policy.d.ts +6 -3
  18. package/dist/metrics/catalog-policy.js +31 -10
  19. package/dist/metrics/metric-catalog.generated.d.ts +110 -0
  20. package/dist/metrics/metric-catalog.generated.js +132 -0
  21. package/dist/protocol/dashboard-global-workflows.js +2 -2
  22. package/dist/protocol/event-log-payload.js +1 -1
  23. package/dist/protocol/messages/access-log.d.ts +5 -0
  24. package/dist/protocol/messages/access-log.js +1 -0
  25. package/dist/protocol/messages/actor.d.ts +13 -2
  26. package/dist/protocol/messages/actor.js +16 -5
  27. package/dist/protocol/messages/common.js +1 -1
  28. package/dist/protocol/messages/dashboard-global-workflows.d.ts +21 -0
  29. package/dist/protocol/messages/dashboard-global-workflows.js +28 -1
  30. package/dist/protocol/messages/dashboard.d.ts +46 -5
  31. package/dist/protocol/messages/dashboard.js +67 -7
  32. package/dist/protocol/messages/execution-status.d.ts +41 -0
  33. package/dist/protocol/messages/execution-status.js +53 -2
  34. package/dist/protocol/messages/git-credential-relay.d.ts +78 -0
  35. package/dist/protocol/messages/git-credential-relay.js +86 -0
  36. package/dist/protocol/messages/orchestrator-agent.d.ts +89 -0
  37. package/dist/protocol/messages/orchestrator-agent.js +98 -3
  38. package/dist/protocol/messages/peer.d.ts +7 -0
  39. package/dist/protocol/messages/peer.js +18 -1
  40. package/dist/protocol/messages/platform-orchestrator.d.ts +150 -0
  41. package/dist/protocol/messages/platform-orchestrator.js +164 -19
  42. package/dist/protocol/version.d.ts +19 -2
  43. package/dist/protocol/version.js +20 -3
  44. package/dist/provenance/verify.js +11 -10
  45. package/dist/provider/check-status-poster.d.ts +24 -3
  46. package/dist/provider/contributor-resolver.d.ts +11 -3
  47. package/dist/provider/git-credential.d.ts +77 -0
  48. package/dist/provider/git-credential.js +10 -0
  49. package/dist/provider/index.d.ts +2 -0
  50. package/dist/provider/index.js +2 -1
  51. package/dist/provider/webhook-normalizer.d.ts +12 -12
  52. package/dist/repo/pattern-negation.d.ts +73 -0
  53. package/dist/repo/pattern-negation.js +86 -0
  54. package/dist/scaler/registry-auth.d.ts +18 -0
  55. package/dist/scaler/registry-auth.js +28 -0
  56. package/dist/scaler/scaler-backend-type.d.ts +35 -0
  57. package/dist/scaler/scaler-backend-type.js +39 -2
  58. package/dist/scaler/scaler-events.d.ts +79 -0
  59. package/dist/scaler/scaler-events.js +87 -0
  60. package/dist/trigger/content-requirements.js +1 -1
  61. package/dist/trigger/decision-trace.d.ts +79 -0
  62. package/dist/trigger/decision-trace.js +116 -8
  63. package/dist/trigger/matcher.js +4 -2
  64. package/dist/trigger/types.d.ts +87 -7
  65. package/dist/trigger/types.js +6 -1
  66. package/dist/ws/rate-limiter.js +3 -3
  67. package/package.json +11 -3
  68. package/sbom.spdx.json +15 -15
@@ -24,25 +24,133 @@ import { z } from "zod";
24
24
  * number cannot drift between the two packages that enforce it.
25
25
  */
26
26
  const DEFAULT_APPROVAL_EXPIRY_HOURS = 72;
27
+ /** Seconds in an hour — the one conversion between the two expiry spellings. */
28
+ const SECONDS_PER_HOUR = 3600;
29
+ /**
30
+ * The same documented default as {@link DEFAULT_APPROVAL_EXPIRY_HOURS}, in the
31
+ * granularity the policy actually stores.
32
+ */
33
+ const DEFAULT_APPROVAL_EXPIRY_SECONDS = 72 * SECONDS_PER_HOUR;
34
+ /**
35
+ * The shortest expressible security-hold window.
36
+ *
37
+ * One second, not a rounder-looking number: the floor exists to stop a zero or
38
+ * negative value minting an already-expired hold, and any strictly positive
39
+ * integer discharges that. A larger floor would be an invented usability
40
+ * opinion that also puts the window back out of reach of a test.
41
+ */
42
+ const MIN_APPROVAL_EXPIRY_SECONDS = 1;
43
+ /** The longest window the Platform accepts: one year, the bound already applied to hours. */
44
+ const MAX_APPROVAL_EXPIRY_HOURS = 8760;
45
+ /** {@link MAX_APPROVAL_EXPIRY_HOURS} in seconds, so neither spelling outranges the other. */
46
+ const MAX_APPROVAL_EXPIRY_SECONDS = MAX_APPROVAL_EXPIRY_HOURS * SECONDS_PER_HOUR;
47
+ /**
48
+ * The window a policy actually means, in seconds.
49
+ *
50
+ * `approvalExpirySeconds` is the authority and `approvalExpiryHours` its coarse
51
+ * spelling, so the more specific field wins whenever both are present — the
52
+ * only rule that never discards what an operator asked for. A policy carrying
53
+ * neither (an older peer that sent no window at all) falls back to the
54
+ * documented default.
55
+ */
56
+ function approvalExpirySecondsOf(policy) {
57
+ if (policy.approvalExpirySeconds != null) return policy.approvalExpirySeconds;
58
+ if (policy.approvalExpiryHours != null) return policy.approvalExpiryHours * SECONDS_PER_HOUR;
59
+ return DEFAULT_APPROVAL_EXPIRY_SECONDS;
60
+ }
61
+ /**
62
+ * The coarse `approvalExpiryHours` view of a window stored in seconds.
63
+ *
64
+ * Rounds UP, and never below one hour. A sub-hour window has no exact hours
65
+ * spelling, and the peer reading this field is one that cannot express the real
66
+ * value anyway — so the choice is which way to be wrong. Rounding up yields a
67
+ * longer hold than asked for, which stays approvable; rounding down yields zero,
68
+ * which is the already-expired hold `MIN_APPROVAL_EXPIRY_SECONDS` exists to
69
+ * prevent.
70
+ */
71
+ function approvalExpiryHoursOf(seconds) {
72
+ return Math.max(1, Math.ceil(seconds / SECONDS_PER_HOUR));
73
+ }
74
+ /**
75
+ * The org-level fork switch: the setting an org's trust policy carries for how
76
+ * fork pull requests are treated. Each value names what the operator is
77
+ * choosing; which values the orchestrator's trust-policy gate honours, and the
78
+ * mechanism it uses, are owned there rather than here.
79
+ *
80
+ * - `ignore` — the operator declines fork pull requests.
81
+ * - `hold` — the operator requires approval first; the policy's approval expiry
82
+ * bounds how long that approval stays open.
83
+ * - `allow` — the operator permits fork pull requests to run with reduced
84
+ * privilege.
85
+ * - `reject` — deprecated in favour of `ignore`; removed at v1.0.0.
86
+ */
87
+ const ForkPolicy = z.enum([
88
+ "ignore",
89
+ "hold",
90
+ "reject",
91
+ "allow"
92
+ ]);
93
+ /**
94
+ * How much CI authority one org member holds.
95
+ *
96
+ * The level the `/kici approve` comment path reads once it has resolved a
97
+ * commenter to a KiCI user id: `write` or `admin` may release a security hold,
98
+ * `read` and `none` may not. Named here rather than spelled inline so the wire
99
+ * schema, the orchestrator's admin route, and `kici-admin` all offer exactly
100
+ * the same four values.
101
+ */
102
+ const CiTrustLevel = z.enum([
103
+ "none",
104
+ "read",
105
+ "write",
106
+ "admin"
107
+ ]);
27
108
  const trustPolicySchema = z.object({
28
- forkPolicy: z.enum([
29
- "hold",
30
- "reject",
31
- "allow"
32
- ]),
109
+ forkPolicy: ForkPolicy,
110
+ /**
111
+ * @deprecated Accepted for wire compatibility and still stored and echoed
112
+ * back, so an orchestrator or CLI on an older build keeps seeing the value it
113
+ * expects. The orchestrator's trust-policy gate does not read it, so it
114
+ * changes no dispatch outcome. Removed at v1.0.0.
115
+ */
33
116
  unknownContributorPolicy: z.enum(["hold", "reject"]),
117
+ /**
118
+ * @deprecated Accepted for wire compatibility and still stored and echoed
119
+ * back, so an orchestrator or CLI on an older build keeps seeing the value it
120
+ * expects. The orchestrator's trust-policy gate does not read it, so it
121
+ * changes no dispatch outcome. Removed at v1.0.0.
122
+ */
34
123
  workflowChangePolicy: z.enum([
35
124
  "hold",
36
125
  "reject",
37
126
  "allow"
38
127
  ]),
39
128
  /**
40
- * Hours a security hold stays open. Integer and positive: the column is
41
- * INTEGER NOT NULL, so a fractional value throws inside the fire-and-forget
42
- * persist and the policy is then silently never stored, and a zero or
43
- * negative value mints an already-expired hold.
129
+ * Hours a security hold stays open the coarse spelling of
130
+ * `approvalExpirySeconds`, kept required so an orchestrator or CLI on an
131
+ * older build still receives a window it can read. Not deprecated: it is read,
132
+ * enforced whenever no seconds value accompanies it, and remains the
133
+ * ergonomic way to say "72 hours".
134
+ *
135
+ * Integer and positive: the column is INTEGER NOT NULL, so a fractional value
136
+ * throws inside the fire-and-forget persist and the policy is then silently
137
+ * never stored, and a zero or negative value mints an already-expired hold.
44
138
  */
45
- approvalExpiryHours: z.number().int().min(1)
139
+ approvalExpiryHours: z.number().int().min(1),
140
+ /**
141
+ * Seconds a security hold stays open — the authoritative window, and the one
142
+ * granularity that can express a sub-hour hold.
143
+ *
144
+ * Optional because an older Platform sends only the hours field; a frame
145
+ * without it resolves through {@link approvalExpirySecondsOf}, which falls
146
+ * back to `approvalExpiryHours * SECONDS_PER_HOUR`. When both are present this
147
+ * one wins, at every layer.
148
+ *
149
+ * Integer and at least {@link MIN_APPROVAL_EXPIRY_SECONDS} for the same two
150
+ * reasons the hours field is: the column is INTEGER, and a non-positive window
151
+ * mints an already-expired hold.
152
+ */
153
+ approvalExpirySeconds: z.number().int().min(1).optional()
46
154
  });
47
155
  /** Trust policy update pushed from Platform to orchestrator when policy or identity links change. */
48
156
  const trustPolicyUpdateSchema = z.object({
@@ -62,12 +170,7 @@ const trustPolicyUpdateSchema = z.object({
62
170
  */
63
171
  providerUserId: z.string().nullish()
64
172
  })),
65
- memberCiTrustLevels: z.record(z.string(), z.enum([
66
- "none",
67
- "read",
68
- "write",
69
- "admin"
70
- ])),
173
+ memberCiTrustLevels: z.record(z.string(), CiTrustLevel),
71
174
  /**
72
175
  * Operator-defined teams and their member user ids. The orchestrator has no
73
176
  * identity store, so team membership is delivered here (next to
@@ -141,13 +244,13 @@ const WebhookRelayResult = z.enum([
141
244
  * 25 MiB matches GitHub's own webhook payload cap. Senders that need higher must
142
245
  * connect their orchestrator directly (bypassing Platform).
143
246
  */
144
- const WEBHOOK_RELAY_MAX_BODY_BYTES = 25 * 1024 * 1024;
247
+ const WEBHOOK_RELAY_MAX_BODY_BYTES = 26214400;
145
248
  /**
146
249
  * Recommended raw chunk size for the chunked relay protocol. 64 KiB raw becomes
147
250
  * ~85 KiB base64 in JSON; permessage-deflate compresses it well. Sender (Platform)
148
251
  * picks the actual size; receiver (orchestrator) just enforces totalSize and chunkCount.
149
252
  */
150
- const WEBHOOK_RELAY_CHUNK_SIZE = 64 * 1024;
253
+ const WEBHOOK_RELAY_CHUNK_SIZE = 65536;
151
254
  /**
152
255
  * Start frame of the chunked webhook relay protocol (Platform -> orchestrator).
153
256
  * Carries metadata + signature inputs only; body bytes follow in subsequent
@@ -306,6 +409,46 @@ const orchMetricsSchema = z.object({
306
409
  })).max(2e3),
307
410
  timestamp: z.number()
308
411
  });
412
+ /** Largest worker-peer snapshot accepted on one frame. */
413
+ const CLUSTER_MEMBERSHIP_MAX_WORKERS = 512;
414
+ /**
415
+ * Worker-peer membership snapshot, sent by a coordinator to the Platform.
416
+ *
417
+ * A snapshot rather than a delta: a dropped or reordered frame self-heals on
418
+ * the next send instead of corrupting a running tally. The Platform stores the
419
+ * reported size on `platform_connections` and aggregates it into the org's
420
+ * combined orchestrator count.
421
+ *
422
+ * The array bound is the first line of defence, for the same reason
423
+ * `orchMetricsSchema` bounds its own arrays: a malformed or hostile push must
424
+ * fail Zod parse at the WS edge and never reach the aggregate.
425
+ */
426
+ const clusterMembershipSchema = z.object({
427
+ type: z.literal("cluster.membership"),
428
+ workers: z.array(z.object({ instanceId: z.string().min(1).max(128) })).max(512),
429
+ timestamp: z.number()
430
+ });
431
+ /**
432
+ * Per-coordinator orchestrator ceiling, pushed by the Platform.
433
+ *
434
+ * `maxWorkerPeers` is an ABSOLUTE ceiling on this coordinator's connected
435
+ * worker peers, not a remaining allowance. The Platform computes it by
436
+ * excluding this connection's own workers from the org total, so it does not
437
+ * move when a local worker joins or leaves — the coordinator can enforce
438
+ * against its live peer count with no round trip and no staleness window.
439
+ *
440
+ * `orgLimit` and `orgTotal` are informational: they let the coordinator's
441
+ * rejection reason say why, rather than closing opaquely. `evictExcess` is set
442
+ * once the org has been over its limit for longer than the grace window, and
443
+ * asks the coordinator to drain its newest workers down to the ceiling.
444
+ */
445
+ const planHeadroomSchema = z.object({
446
+ type: z.literal("plan.headroom"),
447
+ maxWorkerPeers: z.number().int().min(0),
448
+ orgLimit: z.number().int().min(0),
449
+ orgTotal: z.number().int().min(0),
450
+ evictExcess: z.boolean()
451
+ });
309
452
  /**
310
453
  * Acknowledgment that a webhook was received and processing started.
311
454
  *
@@ -435,6 +578,7 @@ const platformToOrchestratorMessageSchema = z.discriminatedUnion("type", [
435
578
  staleCheckrunCleanupSchema,
436
579
  oidcMintResponseSchema,
437
580
  platformCapabilitiesMessageSchema,
581
+ planHeadroomSchema,
438
582
  nackSchema,
439
583
  ...dashboardPlatformToOrchSchema.options
440
584
  ]);
@@ -464,6 +608,7 @@ const orchestratorToPlatformMessageSchema = z.discriminatedUnion("type", [
464
608
  jobContextMessageSchema,
465
609
  orchMetricsSchema,
466
610
  oidcMintRequestSchema,
611
+ clusterMembershipSchema,
467
612
  nackSchema
468
613
  ]);
469
614
  /**
@@ -493,6 +638,6 @@ const ORCH_TO_PLATFORM_RECOGNIZED_TYPES = new Set(collectDiscriminatorTypes(orch
493
638
  */
494
639
  const PLATFORM_TO_ORCH_RECOGNIZED_TYPES = new Set(collectDiscriminatorTypes(platformToOrchestratorMessageSchema));
495
640
  //#endregion
496
- export { DEFAULT_APPROVAL_EXPIRY_HOURS, ORCH_TO_PLATFORM_RECOGNIZED_TYPES, OrchLogPhase, PLATFORM_TO_ORCH_RECOGNIZED_TYPES, WEBHOOK_RELAY_CHUNK_SIZE, WEBHOOK_RELAY_MAX_BODY_BYTES, WebhookRelayResult, cacheStatsSchema, collectDiscriminatorTypes, executionEventSchema, logChunkSchema, orchCapabilitiesUpdateSchema, orchLogChunkSchema, orchMetricsSchema, orchestratorToPlatformMessageSchema, peerDiscoverSchema, peerUpdateSchema, platformCapabilitiesMessageSchema, platformToOrchestratorMessageSchema, staleCheckrunCleanupSchema, trustPolicySchema, trustPolicyUpdateSchema, webhookAckSchema, webhookRelayChunkSchema, webhookRelaySchema, webhookRelayStartSchema };
641
+ export { CLUSTER_MEMBERSHIP_MAX_WORKERS, CiTrustLevel, DEFAULT_APPROVAL_EXPIRY_HOURS, DEFAULT_APPROVAL_EXPIRY_SECONDS, ForkPolicy, MAX_APPROVAL_EXPIRY_HOURS, MAX_APPROVAL_EXPIRY_SECONDS, MIN_APPROVAL_EXPIRY_SECONDS, ORCH_TO_PLATFORM_RECOGNIZED_TYPES, OrchLogPhase, PLATFORM_TO_ORCH_RECOGNIZED_TYPES, SECONDS_PER_HOUR, WEBHOOK_RELAY_CHUNK_SIZE, WEBHOOK_RELAY_MAX_BODY_BYTES, WebhookRelayResult, approvalExpiryHoursOf, approvalExpirySecondsOf, cacheStatsSchema, clusterMembershipSchema, collectDiscriminatorTypes, executionEventSchema, logChunkSchema, orchCapabilitiesUpdateSchema, orchLogChunkSchema, orchMetricsSchema, orchestratorToPlatformMessageSchema, peerDiscoverSchema, peerUpdateSchema, planHeadroomSchema, platformCapabilitiesMessageSchema, platformToOrchestratorMessageSchema, staleCheckrunCleanupSchema, trustPolicySchema, trustPolicyUpdateSchema, webhookAckSchema, webhookRelayChunkSchema, webhookRelaySchema, webhookRelayStartSchema };
497
642
 
498
643
  //# sourceMappingURL=platform-orchestrator.js.map
@@ -1,12 +1,29 @@
1
1
  /**
2
2
  * Protocol version. Sent during WebSocket handshake.
3
- * Increment on breaking changes to message schemas.
3
+ *
4
+ * Increment when a message schema gains something an older peer cannot parse,
5
+ * and pair the bump with a named floor (below) so a sender can gate on the
6
+ * version a peer negotiated instead of guessing.
4
7
  */
5
- export declare const PROTOCOL_VERSION = 1;
8
+ export declare const PROTOCOL_VERSION = 2;
6
9
  /**
7
10
  * Minimum protocol version accepted.
8
11
  * Connections below this are rejected.
9
12
  * Capabilities handle per-feature negotiation above this baseline.
10
13
  */
11
14
  export declare const MIN_PROTOCOL_VERSION = 1;
15
+ /**
16
+ * First protocol version whose `trust_policy.update` reader accepts
17
+ * `forkPolicy: 'ignore'`.
18
+ *
19
+ * A peer below this version may validate the pushed policy against a
20
+ * `forkPolicy` enum with no `ignore` member. `trust_policy.update` is a member
21
+ * of a discriminated union, so that value fails the WHOLE frame rather than one
22
+ * field: the org's identity links, member CI trust levels and team memberships
23
+ * are dropped with it. The Platform therefore rewrites `ignore` to the
24
+ * deprecated `reject` before sending to such a peer — the value that peer's
25
+ * enum does carry, that denies dispatch the same way, and that this build's own
26
+ * fork switch already resolves through the same arm as `ignore`.
27
+ */
28
+ export declare const FORK_POLICY_IGNORE_MIN_PROTOCOL_VERSION = 2;
12
29
  //# sourceMappingURL=version.d.ts.map
@@ -2,16 +2,33 @@ import "../rolldown-runtime-ClRpJifh.js";
2
2
  //#region src/protocol/version.ts
3
3
  /**
4
4
  * Protocol version. Sent during WebSocket handshake.
5
- * Increment on breaking changes to message schemas.
5
+ *
6
+ * Increment when a message schema gains something an older peer cannot parse,
7
+ * and pair the bump with a named floor (below) so a sender can gate on the
8
+ * version a peer negotiated instead of guessing.
6
9
  */
7
- const PROTOCOL_VERSION = 1;
10
+ const PROTOCOL_VERSION = 2;
8
11
  /**
9
12
  * Minimum protocol version accepted.
10
13
  * Connections below this are rejected.
11
14
  * Capabilities handle per-feature negotiation above this baseline.
12
15
  */
13
16
  const MIN_PROTOCOL_VERSION = 1;
17
+ /**
18
+ * First protocol version whose `trust_policy.update` reader accepts
19
+ * `forkPolicy: 'ignore'`.
20
+ *
21
+ * A peer below this version may validate the pushed policy against a
22
+ * `forkPolicy` enum with no `ignore` member. `trust_policy.update` is a member
23
+ * of a discriminated union, so that value fails the WHOLE frame rather than one
24
+ * field: the org's identity links, member CI trust levels and team memberships
25
+ * are dropped with it. The Platform therefore rewrites `ignore` to the
26
+ * deprecated `reject` before sending to such a peer — the value that peer's
27
+ * enum does carry, that denies dispatch the same way, and that this build's own
28
+ * fork switch already resolves through the same arm as `ignore`.
29
+ */
30
+ const FORK_POLICY_IGNORE_MIN_PROTOCOL_VERSION = 2;
14
31
  //#endregion
15
- export { MIN_PROTOCOL_VERSION, PROTOCOL_VERSION };
32
+ export { FORK_POLICY_IGNORE_MIN_PROTOCOL_VERSION, MIN_PROTOCOL_VERSION, PROTOCOL_VERSION };
16
33
 
17
34
  //# sourceMappingURL=version.js.map
@@ -107,17 +107,18 @@ async function verifyKiciBundle(opts) {
107
107
  failures.push("dsse_signature_invalid");
108
108
  }
109
109
  const attestationOrigin = resolveAttestationOrigin(claims, statement);
110
- if (statement && claims && statementBytes) if (attestationOrigin === AttestationOrigin.enum.live) {
111
- const ok = crossCheckBuildContext(statement, claims);
112
- checks.buildContext = ok ? "pass" : "fail";
113
- if (!ok) failures.push("build_context_mismatch");
110
+ if (statement && claims && statementBytes) {
111
+ if (attestationOrigin === AttestationOrigin.enum.live) {
112
+ const ok = crossCheckBuildContext(statement, claims);
113
+ checks.buildContext = ok ? "pass" : "fail";
114
+ if (!ok) failures.push("build_context_mismatch");
115
+ } else {
116
+ const actual = await computeStatementHash(statementBytes);
117
+ const ok = typeof claims.statement_hash === "string" && claims.statement_hash === actual;
118
+ checks.buildContext = ok ? "pass" : "fail";
119
+ if (!ok) failures.push("statement_hash_mismatch");
120
+ }
114
121
  } else {
115
- const actual = await computeStatementHash(statementBytes);
116
- const ok = typeof claims.statement_hash === "string" && claims.statement_hash === actual;
117
- checks.buildContext = ok ? "pass" : "fail";
118
- if (!ok) failures.push("statement_hash_mismatch");
119
- }
120
- else {
121
122
  checks.buildContext = "fail";
122
123
  failures.push("build_context_uncheckable");
123
124
  }
@@ -4,9 +4,20 @@
4
4
  * Used by the CI security system to post approval/hold status checks
5
5
  * on PRs, enabling visibility into trust-tier gating decisions.
6
6
  */
7
+ import type { CheckRunConclusion } from './check-run-conclusion.js';
7
8
  import type { ProviderType } from './types.js';
8
- /** Status values for a check run. */
9
- export type CheckStatus = 'pending' | 'success' | 'failure' | 'neutral';
9
+ /**
10
+ * Status values for a check run: still running, or one of the terminal
11
+ * conclusions.
12
+ *
13
+ * The terminal half is `CheckRunConclusion` — the same vocabulary the workflow
14
+ * and per-job `kici/…` check runs conclude with — plus `neutral`, which those
15
+ * runs never use and the informational security checks do. One vocabulary means
16
+ * a hold that ends is reported the same way on the security check and on the
17
+ * `kici/…` checks of the same event: `cancelled` for a rejection, `timed_out`
18
+ * for an elapsed approval window.
19
+ */
20
+ export type CheckStatus = 'pending' | 'neutral' | CheckRunConclusion;
10
21
  /** A single workflow-file change detected between base and head lock files. */
11
22
  export interface WorkflowModificationInfo {
12
23
  changeType: string;
@@ -45,8 +56,18 @@ export interface CheckStatusPoster {
45
56
  * "Held for approval" state with a completed conclusion.
46
57
  *
47
58
  * Optional so a provider bundle that has no notion of commit checks — or a
48
- * hand-built one — is simply silent rather than failing the delivery.
59
+ * hand-built one — is silent rather than failing the delivery.
49
60
  */
50
61
  postGlobalEvalFailedCheck?(repoIdentifier: string, commitSha: string, summary: string, credentials: unknown): Promise<void>;
62
+ /**
63
+ * Post the success conclusion on the organization-workflow-evaluation check —
64
+ * the same check name {@link postGlobalEvalFailedCheck} writes — after a
65
+ * re-run of the failed round completes cleanly, so a bot gating on all-green
66
+ * is unblocked without a new commit.
67
+ *
68
+ * Optional for the same reason as the failure poster: a bundle with no notion
69
+ * of commit checks is silent rather than failing the re-run.
70
+ */
71
+ postGlobalEvalSucceededCheck?(repoIdentifier: string, commitSha: string, summary: string, credentials: unknown): Promise<void>;
51
72
  }
52
73
  //# sourceMappingURL=check-status-poster.d.ts.map
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * ContributorResolver interface for determining contributor permissions.
3
3
  *
4
- * Used by the CI security system to check a contributor's permission level
5
- * on a repository, enabling trust-tier decisions for PR execution gating.
4
+ * Deprecated in favour of ref-based trust: the whole interface, including the
5
+ * `ContributorPermission` / `ContributorInfo` shapes it carries, is retained
6
+ * for compatibility and has no caller in this repo.
6
7
  */
7
8
  import type { ProviderType } from './types.js';
8
9
  /** Contributor's permission level on a repository. */
@@ -14,7 +15,14 @@ export interface ContributorInfo {
14
15
  /** Whether the PR comes from a fork */
15
16
  isForkPR: boolean;
16
17
  }
17
- /** Resolves contributor information from a git hosting provider. */
18
+ /**
19
+ * Resolves contributor information from a git hosting provider.
20
+ *
21
+ * @deprecated Trust is derived from the git ref, not from the contributor's
22
+ * permission level, so nothing in the webhook pipeline calls a resolver. The
23
+ * type stays exported for wire compatibility with external implementations and
24
+ * is removed at v1.0.0.
25
+ */
18
26
  export interface ContributorResolver {
19
27
  readonly provider: ProviderType;
20
28
  resolveContributor(repoIdentifier: string, username: string, credentials: unknown): Promise<ContributorInfo>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Git credential types shared by the orchestrator broker, the agent helper,
3
+ * and the SDK surface.
4
+ *
5
+ * Browser-safe by construction: these types sit on the `provider/` barrel that
6
+ * the dashboard transitively imports, so this module declares plain TypeScript
7
+ * and imports no Zod. The validating mirror lives in
8
+ * `protocol/messages/git-credential-relay.ts`.
9
+ */
10
+ import type { ProviderType } from './types.js';
11
+ /**
12
+ * A forge that can back a git credential. Derived from `ProviderType` rather
13
+ * than declared alongside it so the two cannot drift. `'local'` is excluded —
14
+ * it is an execution mode, not a forge.
15
+ */
16
+ export type ForgeName = Exclude<ProviderType, 'local'>;
17
+ /**
18
+ * One half of a credential field pair. Exactly one form is set: a qualified
19
+ * `<context>:<secret-name>` reference resolved from the secrets backend, or
20
+ * material supplied at runtime.
21
+ *
22
+ * The field NAME is the discriminator, so neither a reader nor the broker ever
23
+ * has to guess whether a value is a key or a credential. This follows the
24
+ * convention `packages/sdk/src/workflow.ts` already sets with
25
+ * `registries[].tokenSecret` and `isQualifiedSecretRef`.
26
+ */
27
+ export type Sourced<Name extends string> = {
28
+ [K in `${Name}Secret`]: string;
29
+ } | {
30
+ [K in `${Name}Value`]: string;
31
+ };
32
+ /** Where credential material comes from, for each supported credential shape. */
33
+ export type GitCredentialRef = ({
34
+ kind: 'app';
35
+ } & Sourced<'appId'> & Sourced<'installationId'> & Sourced<'privateKey'>) | ({
36
+ kind: 'token';
37
+ user?: string;
38
+ } & Sourced<'token'>) | ({
39
+ kind: 'ssh';
40
+ } & Sourced<'privateKey'>);
41
+ /**
42
+ * What a credential may actually do. Reported by the broker; never an echo of
43
+ * what the caller requested.
44
+ *
45
+ * `scoped: false` means the credential could not be narrowed at all — every
46
+ * static credential, where the key is read-write or it is not. `scoped: true`
47
+ * carries the permission map the forge actually granted.
48
+ */
49
+ export type GitCredentialGrant = {
50
+ scoped: false;
51
+ } | {
52
+ scoped: true;
53
+ permissions: Readonly<Record<string, string>>;
54
+ };
55
+ /** A broker request: which repo, on whose behalf, with what asked for. */
56
+ export interface GitCredentialRequest {
57
+ /** Repository identifier, e.g. `'kici-dev/kici-forge-app-token-tester'`. */
58
+ repository: string;
59
+ /** Omit for the source-scoped default credential. */
60
+ ref?: GitCredentialRef;
61
+ /**
62
+ * Requested permissions. Meaningful only for a minted shape; ignored for a
63
+ * static one, which reports `scoped: false`.
64
+ */
65
+ permissions?: Readonly<Record<string, string>>;
66
+ }
67
+ /** What the broker returns. `expiresAt` is null for a credential that does not expire. */
68
+ export interface GitCredentialResult {
69
+ kind: 'basic' | 'ssh';
70
+ user?: string;
71
+ secret: string;
72
+ grant: GitCredentialGrant;
73
+ expiresAt: string | null;
74
+ }
75
+ /** True when the ref requires minting (and therefore per-operation refresh). */
76
+ export declare function isMintedRef(ref: GitCredentialRef): boolean;
77
+ //# sourceMappingURL=git-credential.d.ts.map
@@ -0,0 +1,10 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ //#region src/provider/git-credential.ts
3
+ /** True when the ref requires minting (and therefore per-operation refresh). */
4
+ function isMintedRef(ref) {
5
+ return ref.kind === "app";
6
+ }
7
+ //#endregion
8
+ export { isMintedRef };
9
+
10
+ //# sourceMappingURL=git-credential.js.map
@@ -19,6 +19,8 @@ export { LockFileParseError } from './lock-file-parse-error.js';
19
19
  export type { ChangedFilesFetcher, ChangedFilesResult } from './changed-files-fetcher.js';
20
20
  export type { FileContentsFetcher } from './file-contents-fetcher.js';
21
21
  export type { CloneTokenProvider, ProviderGitAuth } from './clone-token-provider.js';
22
+ export type { ForgeName, Sourced, GitCredentialRef, GitCredentialGrant, GitCredentialRequest, GitCredentialResult, } from './git-credential.js';
23
+ export { isMintedRef } from './git-credential.js';
22
24
  export type { RepoUrlBuilder } from './repo-url-builder.js';
23
25
  export type { ContributorResolver, ContributorInfo, ContributorPermission, } from './contributor-resolver.js';
24
26
  export type { CheckStatusPoster, CheckStatus, WorkflowModificationInfo, } from './check-status-poster.js';
@@ -1,4 +1,5 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { LockFileParseError } from "./lock-file-parse-error.js";
3
+ import { isMintedRef } from "./git-credential.js";
3
4
  import { CheckRunConclusion } from "./check-run-conclusion.js";
4
- export { CheckRunConclusion, LockFileParseError };
5
+ export { CheckRunConclusion, LockFileParseError, isMintedRef };
@@ -8,8 +8,13 @@
8
8
  import type { SimulatedEvent } from '../trigger/types.js';
9
9
  import type { ProviderType } from './types.js';
10
10
  /**
11
- * Discriminated union describing which `ContributorCache` entries a webhook
12
- * event invalidates. Returned by `WebhookNormalizer.getAccessCacheInvalidations`.
11
+ * Discriminated union describing which cached provider-permission entries a
12
+ * webhook event invalidates. Returned by
13
+ * `WebhookNormalizer.getAccessCacheInvalidations`.
14
+ *
15
+ * @deprecated Trust is derived from the git ref, so the orchestrator holds no
16
+ * contributor-permission cache and nothing consumes these entries. Removed at
17
+ * v1.0.0.
13
18
  *
14
19
  * Three scopes:
15
20
  * - `repo-user`: a single `{repo, user}` permission changed (e.g. GitHub
@@ -142,17 +147,12 @@ export interface WebhookNormalizer {
142
147
  */
143
148
  extractDefaultBranch?(payload: unknown): string | null;
144
149
  /**
145
- * Map a webhook event to the `ContributorCache` entries it should drop.
146
- *
147
- * Optional. When implemented, the orchestrator calls this before
148
- * `normalizeEvent` and invalidates every returned entry so the next
149
- * permission check hits the provider API instead of relying on stale
150
- * cached data. Events that do not imply a permission shift (most events,
151
- * including `push` / `pull_request` / etc.) should return `[]`.
150
+ * Map a webhook event to the cached provider-permission entries it implies a
151
+ * shift in. Events that do not imply a permission shift (most events,
152
+ * including `push` / `pull_request` / etc.) return `[]`.
152
153
  *
153
- * The caller is responsible for pairing the returned entries with the
154
- * `provider` field of the matched bundle — this method receives no
155
- * provider argument.
154
+ * @deprecated Trust is derived from the git ref, so the orchestrator holds no
155
+ * contributor-permission cache and never calls this. Removed at v1.0.0.
156
156
  *
157
157
  * @param eventType Provider-specific event type (from extractEventType).
158
158
  * @param action Event action/sub-type, if applicable.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The one classifier for "picomatch would read this repo pattern as a
3
+ * negation".
4
+ *
5
+ * Repo patterns are written on lists whose direction is already fixed by the
6
+ * list itself — a role's allowed repositories, a global-workflow allow list, a
7
+ * global-workflow deny list. A pattern picomatch reads as a negation inverts
8
+ * that direction inside a single entry, so an entry that reads as a restriction
9
+ * matches as its complement. On an allow list that grants almost everything; on
10
+ * a deny list it admits the one repository the entry named.
11
+ *
12
+ * This lives in the engine, and not beside either consumer, because two
13
+ * hand-maintained ban lists for one pattern language cannot be kept in step.
14
+ * The two ways they drift apart are both live hazards: a list that misses the
15
+ * regular-expression assertions accepts a real inversion, and a list that
16
+ * refuses `[!…]` turns away a genuine restriction. Every surface that stores a
17
+ * repo pattern reads its verdict from here, so neither can happen on one
18
+ * surface alone.
19
+ */
20
+ /**
21
+ * The regular-expression negations picomatch passes through into the compiled
22
+ * matcher: the negative lookahead `(?!…)` and the negative lookbehind `(?<!…)`.
23
+ * The optional `<` is what makes one pattern cover both; the positive `(?=…)`,
24
+ * `(?<=…)`, `(?:…)` and a plain capture group are deliberately not matched.
25
+ */
26
+ export declare const REGEX_NEGATIVE_ASSERTION: RegExp;
27
+ /**
28
+ * Why picomatch would read `pattern` as a negation, or null when it would not.
29
+ *
30
+ * Four arms, because picomatch reads four negation forms, each of which turns a
31
+ * pattern that reads as a restriction into a grant.
32
+ *
33
+ * A leading `!` negates the whole pattern. The extglob complement `!(…)`
34
+ * negates wherever it appears, so `org/!(secret)` covers every repository under
35
+ * `org/` except that one — the same defect in a prefix-scoped shape. The
36
+ * negated character class `[^…]` does it one character at a time: `org/[^s]*`
37
+ * covers every repository under `org/` whose name does not begin with `s`. The
38
+ * negative assertions are the widest of the four, because they can spell a
39
+ * whole repository identifier rather than one character: picomatch compiles a
40
+ * pattern to a regular expression and passes a group it does not recognise
41
+ * through verbatim, so `(?!org/secret)**` reaches the matcher as a real
42
+ * lookahead and matches every repository in every organization except the one
43
+ * it names.
44
+ *
45
+ * The extglob arm matches the two-character sequence `!(` and nothing wider:
46
+ * `*(`, `+(`, `@(` and `?(` are the non-complementing extglob heads and do not
47
+ * invert, so rejecting a bare `(` would refuse four harmless forms for no gain.
48
+ * A `(` cannot appear in a repository identifier, so no legitimate pattern
49
+ * contains `!(`. The assertion arm is narrow for the same reason: `(?=…)`,
50
+ * `(?<=…)`, `(?:…)` and a plain capture group match strictly what they name, so
51
+ * only the two negative forms are refused.
52
+ *
53
+ * The character-class arm matches `[^` and nothing wider: a `[` cannot appear
54
+ * in a repository identifier either, and a bracket that is not a negation —
55
+ * `org/[abc]*` — is a legitimate restriction. It matches `[^` and NOT `[!`:
56
+ * picomatch does not read `[!…]` as the POSIX negation. It reads it as a
57
+ * literal class containing `!` and the listed characters, so `org/[!s]*`
58
+ * matches exactly the repositories whose name begins with `!` or `s` — the
59
+ * exact inverse of `org/[^s]*`, and a genuine restriction. Rejecting it would
60
+ * refuse a pattern that grants strictly less than it names while leaving the
61
+ * real inversion open.
62
+ *
63
+ * A bare `!` is different — it *can* appear in a repository name — so the first
64
+ * arm stays anchored: `org/we!rd` is a legitimate literal, and an
65
+ * `includes('!')` check would silently reject a valid repository name.
66
+ */
67
+ export declare function negatedPatternReason(pattern: string): string | null;
68
+ /**
69
+ * True when `pattern` is a picomatch negation — whole-pattern `!…`, extglob
70
+ * `!(…)`, negated character class `[^…]`, or a negative lookahead / lookbehind.
71
+ */
72
+ export declare function isNegatedPattern(pattern: string): boolean;
73
+ //# sourceMappingURL=pattern-negation.d.ts.map