@kici-dev/engine 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/access-log-policy.js +1 -0
- package/dist/audit/retention-policy.js +2 -0
- package/dist/context/held-run-job-id.d.ts +26 -10
- package/dist/context/held-run-job-id.js +30 -11
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +3 -3
- package/dist/context/types.d.ts +10 -1
- package/dist/context/types.js +10 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +14 -11
- package/dist/labels.d.ts +66 -12
- package/dist/labels.js +74 -17
- package/dist/mcp/held-run-resolve.d.ts +40 -0
- package/dist/mcp/held-run-resolve.js +68 -15
- package/dist/mcp/tool-schemas.d.ts +4 -0
- package/dist/mcp/tool-schemas.js +13 -1
- package/dist/metrics/catalog-policy.d.ts +6 -3
- package/dist/metrics/catalog-policy.js +31 -10
- package/dist/metrics/metric-catalog.generated.d.ts +110 -0
- package/dist/metrics/metric-catalog.generated.js +132 -0
- package/dist/protocol/dashboard-global-workflows.js +2 -2
- package/dist/protocol/event-log-payload.js +1 -1
- package/dist/protocol/messages/access-log.d.ts +5 -0
- package/dist/protocol/messages/access-log.js +1 -0
- package/dist/protocol/messages/actor.d.ts +13 -2
- package/dist/protocol/messages/actor.js +16 -5
- package/dist/protocol/messages/common.js +1 -1
- package/dist/protocol/messages/dashboard-global-workflows.d.ts +21 -0
- package/dist/protocol/messages/dashboard-global-workflows.js +28 -1
- package/dist/protocol/messages/dashboard.d.ts +43 -5
- package/dist/protocol/messages/dashboard.js +55 -6
- package/dist/protocol/messages/execution-status.d.ts +41 -0
- package/dist/protocol/messages/execution-status.js +53 -2
- package/dist/protocol/messages/git-credential-relay.d.ts +78 -0
- package/dist/protocol/messages/git-credential-relay.js +86 -0
- package/dist/protocol/messages/orchestrator-agent.d.ts +89 -0
- package/dist/protocol/messages/orchestrator-agent.js +98 -3
- package/dist/protocol/messages/peer.d.ts +7 -0
- package/dist/protocol/messages/peer.js +18 -1
- package/dist/protocol/messages/platform-orchestrator.d.ts +150 -0
- package/dist/protocol/messages/platform-orchestrator.js +164 -19
- package/dist/protocol/version.d.ts +19 -2
- package/dist/protocol/version.js +20 -3
- package/dist/provenance/verify.js +11 -10
- package/dist/provider/check-status-poster.d.ts +24 -3
- package/dist/provider/contributor-resolver.d.ts +11 -3
- package/dist/provider/git-credential.d.ts +77 -0
- package/dist/provider/git-credential.js +10 -0
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.js +2 -1
- package/dist/provider/webhook-normalizer.d.ts +12 -12
- package/dist/repo/pattern-negation.d.ts +73 -0
- package/dist/repo/pattern-negation.js +86 -0
- package/dist/scaler/registry-auth.d.ts +18 -0
- package/dist/scaler/registry-auth.js +28 -0
- package/dist/scaler/scaler-backend-type.d.ts +35 -0
- package/dist/scaler/scaler-backend-type.js +39 -2
- package/dist/scaler/scaler-events.d.ts +79 -0
- package/dist/scaler/scaler-events.js +87 -0
- package/dist/trigger/content-requirements.js +1 -1
- package/dist/trigger/decision-trace.d.ts +79 -0
- package/dist/trigger/decision-trace.js +116 -8
- package/dist/trigger/matcher.js +4 -2
- package/dist/trigger/types.d.ts +87 -7
- package/dist/trigger/types.js +6 -1
- package/dist/ws/rate-limiter.js +3 -3
- package/package.json +11 -3
- 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:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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(),
|
|
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 =
|
|
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 =
|
|
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
|
-
*
|
|
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 =
|
|
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
|
package/dist/protocol/version.js
CHANGED
|
@@ -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
|
-
*
|
|
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 =
|
|
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)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
/**
|
|
9
|
-
|
|
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
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
/**
|
|
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
|
package/dist/provider/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/provider/index.js
CHANGED
|
@@ -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
|
|
12
|
-
* event invalidates. Returned by
|
|
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
|
|
146
|
-
*
|
|
147
|
-
*
|
|
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
|
-
*
|
|
154
|
-
*
|
|
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
|