@kici-dev/orchestrator 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/__test-helpers__/fake-scaler-state-store.d.ts +24 -0
- package/dist/__test-helpers__/mock-db-query.d.ts +130 -0
- package/dist/__test-helpers__/mock-db.d.ts +23 -0
- package/dist/agent/dispatcher.d.ts +81 -13
- package/dist/agent/host-roster.d.ts +37 -5
- package/dist/agent/registry.d.ts +2 -0
- package/dist/app.d.ts +33 -6
- package/dist/approvals/apply-decision.d.ts +67 -19
- package/dist/approvals/held-run-release-wiring.d.ts +45 -0
- package/dist/approvals/independent-wiring.d.ts +55 -0
- package/dist/approvals/resume-router.d.ts +9 -0
- package/dist/approvals/triggerer-subject.d.ts +37 -0
- package/dist/cache/dep-cache.d.ts +54 -10
- package/dist/cache/global-eval-round-cache.d.ts +7 -0
- package/dist/cli/api-client.d.ts +18 -0
- package/dist/cli/commands/cluster-settings.d.ts +11 -0
- package/dist/cli/commands/diagnose.d.ts +6 -0
- package/dist/cli/commands/held-run.d.ts +58 -0
- package/dist/cli/commands/trust-policy.d.ts +117 -12
- package/dist/cli/kici-admin.d.ts +10 -0
- package/dist/cli.js +2272 -1025
- package/dist/cluster/cluster-identity.d.ts +3 -3
- package/dist/cluster/cluster-settings-reader.d.ts +7 -1
- package/dist/cluster/coordinator.d.ts +44 -0
- package/dist/cluster/index.d.ts +4 -0
- package/dist/cluster/peer-handler.d.ts +19 -0
- package/dist/cluster/peer-registry.d.ts +17 -0
- package/dist/cluster/plan-headroom-store.d.ts +26 -0
- package/dist/cluster/worker-eviction.d.ts +69 -0
- package/dist/cold-store/load-event-log-range.d.ts +10 -3
- package/dist/cold-store/load-secret-audit-log-range.d.ts +4 -3
- package/dist/cold-store/tables/secret-audit-log.d.ts +4 -3
- package/dist/config.d.ts +21 -21
- package/dist/contexts/held-runs.d.ts +123 -16
- package/dist/contexts/protection/aggregate.d.ts +3 -2
- package/dist/contexts/protection/branch-gate.d.ts +15 -0
- package/dist/contexts/protection/concurrency-gate.d.ts +8 -2
- package/dist/contexts/protection/pipeline.d.ts +18 -0
- package/dist/contexts/release-queued-holds.d.ts +35 -0
- package/dist/dashboard/attestation-filters.d.ts +8 -0
- package/dist/dashboard/handler.d.ts +6 -24
- package/dist/db/migrations/116_org_plan_headroom.d.ts +4 -0
- package/dist/db/migrations/117_invoke_gate_proxy_jobs.d.ts +4 -0
- package/dist/db/migrations/118_invoke_gate_scheduling.d.ts +4 -0
- package/dist/db/migrations/119_scaler_ha_ownership.d.ts +4 -0
- package/dist/db/migrations/120_cluster_settings_scaler_reaper.d.ts +4 -0
- package/dist/db/migrations/121_org_settings_allow_untrusted_dockerfile_builds.d.ts +19 -0
- package/dist/db/migrations/122_host_roster_scaler_managed.d.ts +25 -0
- package/dist/db/migrations/123_workflow_registrations_default_branch.d.ts +24 -0
- package/dist/db/migrations/124_cluster_settings_provision_backoff.d.ts +4 -0
- package/dist/db/migrations/125_org_trust_directory.d.ts +30 -0
- package/dist/db/migrations/126_held_runs_posted_pending_check.d.ts +28 -0
- package/dist/db/migrations/127_org_trust_policy_approval_expiry_seconds.d.ts +26 -0
- package/dist/db/migrations/128_execution_runs_global_eval_round.d.ts +23 -0
- package/dist/db/migrations/129_execution_runs_dispatch_routing_key.d.ts +24 -0
- package/dist/db/migrations/130_scaler_provision_outcomes.d.ts +32 -0
- package/dist/db/types.d.ts +368 -10
- package/dist/entry-helpers.d.ts +28 -0
- package/dist/events/event-emitter.d.ts +22 -0
- package/dist/events/event-router.d.ts +87 -1
- package/dist/events/types.d.ts +2 -23
- package/dist/git/credential-broker.d.ts +94 -0
- package/dist/git/inherited-secret.d.ts +24 -0
- package/dist/git/installation-token.d.ts +38 -0
- package/dist/git/job-context.d.ts +13 -0
- package/dist/index.js +324 -84
- package/dist/metrics/agent-metrics-aggregator.d.ts +2 -2
- package/dist/metrics/prometheus.d.ts +111 -3
- package/dist/oidc/oidc-mint-registration.d.ts +7 -3
- package/dist/orchestrator-core.d.ts +79 -23
- package/dist/pipeline/direct-ingress-deps.d.ts +100 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +302 -31
- package/dist/pipeline/global-eval-round.d.ts +14 -0
- package/dist/pipeline/internal-event-pipeline.d.ts +140 -0
- package/dist/pipeline/invoke-gate.d.ts +164 -0
- package/dist/pipeline/needs-scheduler.d.ts +13 -0
- package/dist/pipeline/process-webhook.d.ts +119 -31
- package/dist/pipeline/processor.d.ts +226 -21
- package/dist/pipeline/rerun.d.ts +21 -0
- package/dist/pipeline/resume-workflow.d.ts +40 -7
- package/dist/pipeline/route-or-dispatch-jobs.d.ts +9 -0
- package/dist/pipeline/security-hold-check.d.ts +217 -0
- package/dist/pipeline/undispatched-hold-checks.d.ts +23 -0
- package/dist/policy/dashboard-write-policy-listener.d.ts +62 -0
- package/dist/policy/dashboard-write-policy.d.ts +14 -0
- package/dist/provider-registry.d.ts +10 -2
- package/dist/providers/github/check-status-poster.d.ts +11 -0
- package/dist/providers/github/index.d.ts +0 -2
- package/dist/providers/github/normalizer.d.ts +3 -2
- package/dist/providers/local/normalizer.d.ts +3 -5
- package/dist/providers/universal-git/index.d.ts +11 -2
- package/dist/queue/job-queue.d.ts +43 -1
- package/dist/registration/extractor.d.ts +3 -3
- package/dist/registration/registration-index.d.ts +7 -0
- package/dist/registration/registration-store.d.ts +22 -0
- package/dist/reporting/check-run-reporter.d.ts +94 -1
- package/dist/reporting/check-run-summary.d.ts +25 -1
- package/dist/reporting/execution-tracker.d.ts +207 -2
- package/dist/reporting/run-aggregator.d.ts +4 -14
- package/dist/reporting/step-display-order.d.ts +43 -0
- package/dist/routes/admin-held-runs.d.ts +142 -0
- package/dist/routes/admin-trust-policy.d.ts +34 -6
- package/dist/routes/admin.d.ts +9 -0
- package/dist/scaler/backend-factory.d.ts +55 -0
- package/dist/scaler/bare-metal-backend.d.ts +43 -5
- package/dist/scaler/claim-store.d.ts +119 -0
- package/dist/scaler/config.d.ts +4 -0
- package/dist/scaler/container-backend.d.ts +18 -4
- package/dist/scaler/container-routing.d.ts +23 -0
- package/dist/scaler/container-spawn.d.ts +28 -0
- package/dist/scaler/event-backend.d.ts +124 -0
- package/dist/scaler/event-provision-reaper.d.ts +350 -0
- package/dist/scaler/failure-tracker.d.ts +1 -1
- package/dist/scaler/firecracker-backend.d.ts +33 -6
- package/dist/scaler/index.d.ts +11 -1
- package/dist/scaler/label-matcher.d.ts +4 -3
- package/dist/scaler/manager.d.ts +899 -72
- package/dist/scaler/resolve-container-auth.d.ts +45 -0
- package/dist/scaler/scaler-events.d.ts +11 -0
- package/dist/scaler/scaler-state-store.d.ts +294 -10
- package/dist/scaler/types.d.ts +131 -13
- package/dist/scaler/warm-pool.d.ts +132 -29
- package/dist/security/comment-handler.d.ts +50 -11
- package/dist/security/identity-link.d.ts +60 -0
- package/dist/security/lock-source.d.ts +11 -13
- package/dist/security/reduced-privilege-note.d.ts +59 -0
- package/dist/security/trust-directory-store.d.ts +233 -0
- package/dist/security/trust-policy-gate.d.ts +94 -59
- package/dist/security/trust-policy-store.d.ts +3 -0
- package/dist/security/trust-resolver.d.ts +14 -98
- package/dist/server.d.ts +11 -1
- package/dist/server.js +17671 -7350
- package/dist/stale-detector/gate-deadline-detector.d.ts +52 -0
- package/dist/stale-detector/stale-run-detector.d.ts +61 -3
- package/dist/standalone.js +24238 -13822
- package/dist/worker/in-memory-job-queue.d.ts +1 -1
- package/dist/ws/agent-handler.d.ts +28 -4
- package/dist/ws/dashboard-context-handler.d.ts +28 -4
- package/dist/ws/dashboard-global-workflows-handler.d.ts +6 -0
- package/dist/ws/failure-messages.d.ts +2 -0
- package/dist/ws/git-credential-relay.d.ts +39 -0
- package/dist/ws/oidc-token-relay.d.ts +6 -11
- package/dist/ws/platform-client.d.ts +54 -9
- package/installer-image-digests.json +3 -3
- package/package.json +24 -19
- package/sbom.spdx.json +864 -822
- package/dist/approvals/team-membership-lookup.d.ts +0 -13
- package/dist/providers/github/contributor-resolver.d.ts +0 -30
- package/dist/security/contributor-cache.d.ts +0 -83
|
@@ -1,79 +1,182 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Warm pool manager for pre-provisioned idle agents.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Each configured label set has a target `size`. On every tick the manager asks
|
|
5
|
+
* the agent registry how many agents could already serve a job with those
|
|
6
|
+
* labels, subtracts the spawns it has issued but not yet seen register, and
|
|
7
|
+
* starts the difference. It holds no list of its own: the agent registry is the
|
|
8
|
+
* single source of truth for what is ready, which is also what the dispatcher
|
|
9
|
+
* reads, so the two can never disagree.
|
|
10
|
+
*
|
|
11
|
+
* Uses a callback pattern to avoid circular dependencies with ScalerManager.
|
|
7
12
|
*/
|
|
8
13
|
export interface WarmPoolCallbacks {
|
|
9
14
|
/** Request the ScalerManager to spawn a new agent for the given label set */
|
|
10
15
|
onSpawnRequest: (labelSet: string[], backendName: string) => Promise<void>;
|
|
11
16
|
/** Request the ScalerManager to destroy an idle agent */
|
|
12
17
|
onDestroyRequest: (managedId: string, backendName: string) => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* How many registered agents could serve a job with this label set right
|
|
20
|
+
* now. Backed by `AgentRegistry.findAvailable`, which is the same query the
|
|
21
|
+
* dispatcher uses — so the warm pool and the dispatcher can never disagree
|
|
22
|
+
* about what "ready" means.
|
|
23
|
+
*
|
|
24
|
+
* A static agent that matches the labels counts: it removes the need for a
|
|
25
|
+
* warm one just as well as a spawned agent does.
|
|
26
|
+
*/
|
|
27
|
+
countAvailable: (labels: string[]) => number;
|
|
28
|
+
/**
|
|
29
|
+
* Agents this backend spawned that match the label set and have never run a
|
|
30
|
+
* job, with the timestamp they registered at. `registeredAt` doubles as
|
|
31
|
+
* "idle since": scaler agents are single-use, so an agent still registered
|
|
32
|
+
* with no active job has been idle since it came up.
|
|
33
|
+
*
|
|
34
|
+
* Scoped to the backend because the result feeds the reaper, and the reaper
|
|
35
|
+
* destroys what it is given. A static agent that happens to match the labels
|
|
36
|
+
* is ready capacity, not this pool's to tear down.
|
|
37
|
+
*/
|
|
38
|
+
listIdle: (labels: string[], backendName: string) => Array<{
|
|
39
|
+
agentId: string;
|
|
40
|
+
registeredAt: number;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* How many more agents this backend may still start before it reaches the
|
|
44
|
+
* scaler's `maxAgents` or the orchestrator's `globalMaxAgents`, whichever
|
|
45
|
+
* binds first. The deficit is clamped to it, so a pool that cannot reach its
|
|
46
|
+
* target stops asking instead of issuing a spawn the caps refuse every tick.
|
|
47
|
+
*/
|
|
48
|
+
capacityRemaining: (backendName: string) => number;
|
|
49
|
+
/**
|
|
50
|
+
* Called at the end of each tick, once the reap and the deficit pass have
|
|
51
|
+
* run. The ScalerManager publishes the warm-pool gauges from it, so they
|
|
52
|
+
* follow the pool rather than the scrape.
|
|
53
|
+
*/
|
|
54
|
+
onTick?: () => void;
|
|
55
|
+
}
|
|
56
|
+
/** One label set's fill state, as the warm-pool metrics report it. */
|
|
57
|
+
export interface WarmPoolStats {
|
|
58
|
+
/**
|
|
59
|
+
* The pool's identity: its normalized DECLARED label set, which is the
|
|
60
|
+
* `configs` map key.
|
|
61
|
+
*
|
|
62
|
+
* This — not `labels` — is what a per-pool metric dimension keys on. Two
|
|
63
|
+
* pools on one scaler can widen to the SAME query set (one label set
|
|
64
|
+
* declaring the plain platform label the other only gets from the taint), so
|
|
65
|
+
* a dimension built from `labels` collapses them into one series and silently
|
|
66
|
+
* drops a pool's fill state.
|
|
67
|
+
*/
|
|
68
|
+
key: string;
|
|
69
|
+
/** The widened query set the `ready` count was measured with. */
|
|
70
|
+
labels: string[];
|
|
71
|
+
backendName: string;
|
|
72
|
+
target: number;
|
|
73
|
+
ready: number;
|
|
74
|
+
inFlight: number;
|
|
13
75
|
}
|
|
14
76
|
export declare class WarmPoolManager {
|
|
15
77
|
private readonly callbacks;
|
|
16
|
-
/** Pools of idle agents keyed by normalized label set string */
|
|
17
|
-
private readonly pools;
|
|
18
78
|
/** Configuration per normalized label set */
|
|
19
79
|
private readonly configs;
|
|
20
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Warm spawns issued but not yet registered, per normalized label set.
|
|
82
|
+
*
|
|
83
|
+
* Load-bearing: without it, two passes landing before the first spawn
|
|
84
|
+
* registers would each see the full deficit and issue `size` spawns apiece.
|
|
85
|
+
*/
|
|
86
|
+
private readonly inFlight;
|
|
87
|
+
/** Periodic deficit + idle check interval */
|
|
21
88
|
private idleCheckInterval;
|
|
22
89
|
constructor(callbacks: WarmPoolCallbacks);
|
|
23
90
|
/**
|
|
24
91
|
* Configure the warm pool for a label set.
|
|
25
92
|
* Called during ScalerManager initialization based on YAML config.
|
|
93
|
+
*
|
|
94
|
+
* `normalizedLabels` keys the pool by its DECLARED label set, which is what
|
|
95
|
+
* every in-flight release path normalizes back to.
|
|
26
96
|
*/
|
|
27
97
|
configure(normalizedLabels: string, backendName: string, config: {
|
|
28
98
|
size: number;
|
|
29
99
|
idleTimeoutSeconds: number;
|
|
30
100
|
labels: string[];
|
|
101
|
+
spawnLabels: string[];
|
|
31
102
|
}): void;
|
|
32
103
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* After consuming, schedules replenishment on next tick.
|
|
104
|
+
* One deficit pass. For each configured label set, top the pool up to its
|
|
105
|
+
* target: `size - ready - inFlight`, clamped by the backend's remaining cap
|
|
106
|
+
* headroom.
|
|
107
|
+
*
|
|
108
|
+
* The per-backend budget is tracked across the whole pass: two label sets on
|
|
109
|
+
* one scaler draw from the same `maxAgents`, so clamping each against the
|
|
110
|
+
* same starting headroom would let them jointly overshoot it.
|
|
41
111
|
*/
|
|
42
|
-
|
|
112
|
+
evaluate(): void;
|
|
113
|
+
/** A warm spawn registered: release its in-flight slot. */
|
|
114
|
+
onWarmAgentRegistered(labels: string[]): void;
|
|
43
115
|
/**
|
|
44
|
-
*
|
|
116
|
+
* A warm spawn failed, timed out, or was pruned as never-registered: release
|
|
117
|
+
* its in-flight slot so the next pass retries it. Without this the slot is
|
|
118
|
+
* held forever and the pool sits permanently below target.
|
|
45
119
|
*/
|
|
46
|
-
|
|
120
|
+
onWarmSpawnFailed(labels: string[]): void;
|
|
121
|
+
private releaseInFlight;
|
|
122
|
+
/** Per-label-set fill state, for the warm-pool metrics. */
|
|
123
|
+
getStats(): WarmPoolStats[];
|
|
47
124
|
/**
|
|
48
|
-
* Start periodic
|
|
49
|
-
*
|
|
125
|
+
* Start the periodic tick: reap surplus agents past their idle timeout, then
|
|
126
|
+
* top the pools back up.
|
|
127
|
+
*
|
|
128
|
+
* The first pass runs immediately rather than a tick later. Both hosts call
|
|
129
|
+
* this only after `ensureHostsReady()`, so spawning here is safe — and
|
|
130
|
+
* waiting would leave the pool empty for 30 seconds after every restart,
|
|
131
|
+
* which is precisely the cold start it exists to remove.
|
|
50
132
|
*/
|
|
51
133
|
start(): void;
|
|
52
134
|
/**
|
|
53
|
-
* Stop the periodic
|
|
135
|
+
* Stop the periodic tick.
|
|
54
136
|
*/
|
|
55
137
|
stop(): void;
|
|
56
138
|
/**
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
getPoolSize(normalizedLabels: string): number;
|
|
60
|
-
/**
|
|
61
|
-
* Return total warm agents across all pools.
|
|
139
|
+
* Total ready agents across every configured pool. Surfaced by
|
|
140
|
+
* `getStatus().warmPoolCount`.
|
|
62
141
|
*/
|
|
63
142
|
getTotalPoolSize(): number;
|
|
64
143
|
/**
|
|
65
|
-
* Update configs.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
144
|
+
* Update configs. Destroy surplus agents in pools that shrunk, and drop
|
|
145
|
+
* pools whose config disappeared (the scaler was removed from the config,
|
|
146
|
+
* or its `warmPool.enabled` flipped to false) — a warm agent must not
|
|
147
|
+
* outlive the config that asked for it.
|
|
68
148
|
*/
|
|
69
149
|
reload(newConfigs: Map<string, {
|
|
70
150
|
backendName: string;
|
|
71
151
|
size: number;
|
|
72
152
|
idleTimeoutSeconds: number;
|
|
73
153
|
labels: string[];
|
|
154
|
+
spawnLabels: string[];
|
|
74
155
|
}>): void;
|
|
75
156
|
/**
|
|
76
|
-
*
|
|
157
|
+
* Destroy this pool's idle agents beyond `keep`, oldest first.
|
|
158
|
+
*
|
|
159
|
+
* Oldest-first because the survivors are the ones with the most time left
|
|
160
|
+
* before the idle reaper takes them, which is the set an arriving job is
|
|
161
|
+
* most likely to find still ready.
|
|
162
|
+
*/
|
|
163
|
+
private destroyIdle;
|
|
164
|
+
/**
|
|
165
|
+
* Destroy agents that are BOTH surplus to the pool's target AND idle past
|
|
166
|
+
* its timeout. `registeredAt` is the idle-since stamp: scaler agents are
|
|
167
|
+
* single-use, so an agent still registered with no active job has never run
|
|
168
|
+
* one.
|
|
169
|
+
*
|
|
170
|
+
* The target check is what stops the pool churning against itself. Reaping
|
|
171
|
+
* on age alone means a quiet pool destroys its own agent every
|
|
172
|
+
* `idleTimeoutSeconds` and immediately re-spawns it: the provisioning
|
|
173
|
+
* workflow runs forever on zero traffic, and — because an event-backend
|
|
174
|
+
* replacement takes minutes to boot — the pool is below target for much of
|
|
175
|
+
* every cycle. A warm pool that is cold most of the time is worse than none.
|
|
176
|
+
* So the timeout trims surplus only: a pool sitting at `size` never reaps.
|
|
177
|
+
*
|
|
178
|
+
* `size: 0` still drains, because then every ready agent is surplus.
|
|
179
|
+
*
|
|
77
180
|
* Exposed for testing.
|
|
78
181
|
*/
|
|
79
182
|
checkIdleTimeouts(): void;
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* approval/rejection of security holds. Verifies commenter identity
|
|
6
6
|
* via the trust policy cache and checks ci_trust:write+ before acting.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
10
|
-
import type {
|
|
8
|
+
import type { Kysely } from 'kysely';
|
|
9
|
+
import type { HeldRunStore, ReleaseSignal } from '../contexts/held-runs.js';
|
|
10
|
+
import type { Database, HeldRun } from '../db/types.js';
|
|
11
|
+
import { type IdentityLink, type PermissionLevel } from './identity-link.js';
|
|
12
|
+
import { type ResolveCheckStatusPoster } from '../pipeline/security-hold-check.js';
|
|
11
13
|
/** Parsed /kici command from a comment body. */
|
|
12
14
|
interface CommentCommand {
|
|
13
15
|
action: 'approve' | 'reject';
|
|
@@ -19,9 +21,8 @@ export interface HandleApprovalCommentParams {
|
|
|
19
21
|
commentBody: string;
|
|
20
22
|
commenterUsername: string;
|
|
21
23
|
/**
|
|
22
|
-
* Commenter immutable IDP-side numeric id from the webhook event.
|
|
23
|
-
*
|
|
24
|
-
* backfill window (see trust-resolver.findIdentityLink).
|
|
24
|
+
* Commenter immutable IDP-side numeric id from the webhook event. The only
|
|
25
|
+
* field an identity link is matched on (see `identity-link.findIdentityLink`).
|
|
25
26
|
*/
|
|
26
27
|
commenterUserId?: string;
|
|
27
28
|
provider: string;
|
|
@@ -31,11 +32,49 @@ export interface HandleApprovalCommentParams {
|
|
|
31
32
|
identityLinks: IdentityLink[];
|
|
32
33
|
orgMemberPermissions: Map<string, PermissionLevel>;
|
|
33
34
|
heldRunStore: HeldRunStore;
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Orchestrator database, for resolving each ended hold's own commit and the
|
|
37
|
+
* other holds still pending on it. Optional so a store-less deployment still
|
|
38
|
+
* processes the command.
|
|
39
|
+
*/
|
|
40
|
+
db?: Kysely<Database> | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the check poster of the provider bundle serving a routing key, so
|
|
43
|
+
* an ended hold's `KiCI Security` check can be terminalized on the commit the
|
|
44
|
+
* hold's own run acted on — which is not necessarily the PR head at comment
|
|
45
|
+
* time, and posting on the head would create a second check run there while
|
|
46
|
+
* leaving the real one pending.
|
|
47
|
+
*/
|
|
48
|
+
resolvePoster?: ResolveCheckStatusPoster;
|
|
49
|
+
/**
|
|
50
|
+
* Resume a job whose security hold was just approved, by re-dispatching it.
|
|
51
|
+
*
|
|
52
|
+
* Approving must RUN the gated work, not merely flip the row and post a green
|
|
53
|
+
* check. The signal is routed through the same `routeRelease` the dashboard
|
|
54
|
+
* applier and the stale detector use, so a job-scoped hold lands here and a
|
|
55
|
+
* workflow-scoped one lands on `onWorkflowRelease`. Optional so an
|
|
56
|
+
* orchestrator without the dispatch wiring degrades to flip-and-report rather
|
|
57
|
+
* than failing the comment.
|
|
58
|
+
*/
|
|
59
|
+
onJobRelease?: (signal: ReleaseSignal) => Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Resume a workflow-scoped hold that was just approved, by replaying its
|
|
62
|
+
* stored dispatch context. This is the path the org trust policy's PR-wide
|
|
63
|
+
* hold takes: it fires before any job is materialized, so there is no job to
|
|
64
|
+
* re-dispatch.
|
|
65
|
+
*/
|
|
66
|
+
onWorkflowRelease?: (signal: ReleaseSignal) => Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Cancel a workflow-scoped hold that was just rejected, dropping its stored
|
|
69
|
+
* dispatch context. Without it a rejected PR-wide hold leaves its run alive in
|
|
70
|
+
* `held` forever and strands the context row that would have replayed it.
|
|
71
|
+
*
|
|
72
|
+
* It also completes the hold's `KiCI Security` check and resolves to whether
|
|
73
|
+
* it actually wrote one, so this handler reports only the holds no write
|
|
74
|
+
* covered — suppression bound to a check being written, not to a delegate
|
|
75
|
+
* resolving.
|
|
76
|
+
*/
|
|
77
|
+
onWorkflowReject?: (hold: HeldRun, reason: string) => Promise<boolean>;
|
|
39
78
|
}
|
|
40
79
|
/** Result of handling a comment. */
|
|
41
80
|
interface HandleApprovalResult {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-link lookup: map a provider identity from a webhook event onto the
|
|
3
|
+
* KiCI user the Platform linked it to.
|
|
4
|
+
*
|
|
5
|
+
* The comment-approval path uses this to decide whether a `/kici approve`
|
|
6
|
+
* commenter is a linked member with a high enough `ci_trust` level.
|
|
7
|
+
*/
|
|
8
|
+
import type { CiTrustLevel } from '@kici-dev/engine';
|
|
9
|
+
/**
|
|
10
|
+
* RBAC permission levels from the Platform permission system.
|
|
11
|
+
*
|
|
12
|
+
* The same four values the wire schema carries for `memberCiTrustLevels`, so
|
|
13
|
+
* this aliases {@link CiTrustLevel} rather than restating them — a member's CI
|
|
14
|
+
* trust level IS the permission level this module's callers compare against.
|
|
15
|
+
*/
|
|
16
|
+
export type PermissionLevel = CiTrustLevel;
|
|
17
|
+
/** Identity link mapping a provider identity to a KiCI user. */
|
|
18
|
+
export interface IdentityLink {
|
|
19
|
+
userId: string;
|
|
20
|
+
provider: string;
|
|
21
|
+
providerUsername: string;
|
|
22
|
+
/**
|
|
23
|
+
* Immutable IDP-side numeric id (e.g. GitHub's `sender.id`). The only field
|
|
24
|
+
* a link is matched on; a link that carries no id can never match, so it
|
|
25
|
+
* grants nothing.
|
|
26
|
+
*/
|
|
27
|
+
providerUserId?: string | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The provider username the org's identity directory links a KiCI user id to,
|
|
31
|
+
* or undefined when it names none unambiguously.
|
|
32
|
+
*
|
|
33
|
+
* The reverse of {@link findIdentityLink}: that maps a provider identity onto a
|
|
34
|
+
* KiCI user, this maps a KiCI user back onto a name a human recognises. Used
|
|
35
|
+
* where a decision made in the dashboard / CLI / MCP — which carries only the
|
|
36
|
+
* actor's opaque subject id — has to be attributed in copy a contributor reads
|
|
37
|
+
* on a public commit check.
|
|
38
|
+
*
|
|
39
|
+
* A user may hold links on several providers. When they agree on the username
|
|
40
|
+
* the answer is unambiguous; when they disagree there is no way to tell here
|
|
41
|
+
* which provider serves the commit, so this answers undefined rather than
|
|
42
|
+
* naming the wrong account. Undefined is a safe answer at every call site: the
|
|
43
|
+
* attribution is simply omitted, never replaced by the raw subject id.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveLinkedUsername(identityLinks: IdentityLink[], userId: string): string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Match an identity link strictly by `(provider, providerUserId)`.
|
|
48
|
+
*
|
|
49
|
+
* Returns `null` whenever the numeric id is missing on either side or when no
|
|
50
|
+
* link's id matches the event's id. Matching by username is not supported, so
|
|
51
|
+
* renaming a provider account cannot be used to impersonate a linked member.
|
|
52
|
+
* A refused match is recorded under
|
|
53
|
+
* `kici_orch_trust_match_refused_no_id_total{reason}` so the rate of refusals
|
|
54
|
+
* stays observable.
|
|
55
|
+
*
|
|
56
|
+
* Pre-condition for callers: the Platform has filled `provider_user_id` for
|
|
57
|
+
* every row it pushes in `identity_links` — a row without one is inert here.
|
|
58
|
+
*/
|
|
59
|
+
export declare function findIdentityLink(identityLinks: IdentityLink[], provider: string, providerUsername: string, providerUserId: string | undefined): IdentityLink | null;
|
|
60
|
+
//# sourceMappingURL=identity-link.d.ts.map
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
* For pull-request events, the orchestrator must decide whether to fetch
|
|
5
5
|
* the workflow lock file from the PR head branch (the contributor's
|
|
6
6
|
* proposed change) or from the base branch (the protected target). Only
|
|
7
|
-
* trusted
|
|
7
|
+
* a trusted ref gets its HEAD lock evaluated; everything else
|
|
8
8
|
* (unknown / known / undefined trust resolution) falls back to the base
|
|
9
|
-
* branch's lock so
|
|
10
|
-
*
|
|
11
|
-
* vetted.
|
|
9
|
+
* branch's lock so a fork ref cannot inject triggers, jobs, or
|
|
10
|
+
* environment claims that the project's maintainers haven't vetted.
|
|
12
11
|
*
|
|
13
12
|
* The default for non-PR events is HEAD, because there is no untrusted
|
|
14
13
|
* "incoming" contribution to gate against — push events come directly
|
|
@@ -19,16 +18,15 @@ import type { TrustTier } from '@kici-dev/engine';
|
|
|
19
18
|
* Select which branch's lock file to fetch.
|
|
20
19
|
*
|
|
21
20
|
* Invariant (customer-isolation): for any pull-request event, an
|
|
22
|
-
* untrusted
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
21
|
+
* untrusted ref (`tier === 'unknown' | 'known' | undefined`) MUST NOT
|
|
22
|
+
* have its HEAD lock file evaluated by the orchestrator. The
|
|
23
|
+
* base-branch lock — controlled by the project's trusted maintainers —
|
|
24
|
+
* is the source of truth for trigger evaluation, trust-tier-based
|
|
25
|
+
* environment access, and contributor-controlled fields that downstream
|
|
26
|
+
* secret resolution depends on.
|
|
28
27
|
*
|
|
29
|
-
* Only `tier === 'trusted'` (
|
|
30
|
-
*
|
|
31
|
-
* `TrustResolver.resolveTrustTier`) can have their HEAD lock evaluated.
|
|
28
|
+
* Only `tier === 'trusted'` (a ref that lives in the base repo, per
|
|
29
|
+
* `resolveRefTrust`) can have its HEAD lock evaluated.
|
|
32
30
|
*/
|
|
33
31
|
export declare function selectLockFileSource(isPREvent: boolean, tier: TrustTier | undefined): 'head' | 'base';
|
|
34
32
|
//# sourceMappingURL=lock-source.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one rendering of a run's reduced-privilege posture, for the provider
|
|
3
|
+
* checks a contributor reads on a pull request.
|
|
4
|
+
*
|
|
5
|
+
* It lives beside `trust-resolver.ts` and `lock-source.ts` — the two modules
|
|
6
|
+
* that decide the values it reads — rather than in the pipeline, because both
|
|
7
|
+
* the dispatch pipeline and the check-run reporter render it and
|
|
8
|
+
* `pipeline/processor.ts` already imports from `reporting/`.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Lead-in of the reduced-privilege note. Exported so every surface that renders
|
|
12
|
+
* it, and their tests, name one string rather than a copy each.
|
|
13
|
+
*/
|
|
14
|
+
export declare const REDUCED_PRIVILEGE_MARKER = "**Reduced privileges (untrusted ref).**";
|
|
15
|
+
/**
|
|
16
|
+
* The reduced-privilege posture of a run whose ref resolved to a tier other
|
|
17
|
+
* than `trusted`.
|
|
18
|
+
*
|
|
19
|
+
* Attach it only where the run the check describes actually executes. It is NOT
|
|
20
|
+
* attached to the trust-policy REJECTION: that run is never dispatched, so on
|
|
21
|
+
* that check the clauses below are vacuously true and read as a promise about a
|
|
22
|
+
* run that will not happen. The trust-policy HOLD does carry it — approving one
|
|
23
|
+
* replays the dispatch under the same trust resolution, so the reductions the
|
|
24
|
+
* clauses name are the ones the resumed run really runs under. Storing a resume
|
|
25
|
+
* context is not by itself the trigger: `holdWorkflowForInstallGate` stores one
|
|
26
|
+
* and posts no check at all, so it has nothing to attach the note to. See the
|
|
27
|
+
* call sites in `dispatch-matched-workflow.ts` for which checks a resuming hold
|
|
28
|
+
* actually posts.
|
|
29
|
+
*
|
|
30
|
+
* Returns null for a trusted ref and for a run whose trust never resolved. An
|
|
31
|
+
* absent tier is deliberately NOT treated as untrusted: `isUntrustedTier` in
|
|
32
|
+
* `install-secrets-resolver.ts` reads it leniently and leaves the install
|
|
33
|
+
* secrets in place, so a run with no tier has no withholding to report. The
|
|
34
|
+
* predicate for a resolved tier is "other than `trusted`", matching
|
|
35
|
+
* `isUntrustedTier` and `selectLockFileSource`. `deriveCacheRefScope` is
|
|
36
|
+
* stricter — it isolates an absent tier too — so an unresolved run's cache
|
|
37
|
+
* really is isolated while this note stays silent about it. Under-reporting is
|
|
38
|
+
* the safe direction: the note never claims a reduction that did not happen.
|
|
39
|
+
*
|
|
40
|
+
* Each clause names something already decided for THIS run rather than a
|
|
41
|
+
* mechanism:
|
|
42
|
+
*
|
|
43
|
+
* - `resolveInstallSecrets` returns `npmRegistries: undefined` and
|
|
44
|
+
* `installEnvSecrets: undefined` for a resolved non-trusted tier, so the run
|
|
45
|
+
* carries neither.
|
|
46
|
+
* - `deriveCacheRefScope` returns the isolated scope for the same tiers, and
|
|
47
|
+
* `UserCache` confines WRITES to it; a restore still falls back to the shared
|
|
48
|
+
* scope, so the clause is about what the run saves, not what it can read.
|
|
49
|
+
* - the base-branch clause is keyed on the run's recorded `lockFileSource`, not
|
|
50
|
+
* on the tier: `selectLockFileSource` returns `base` only for a pull-request
|
|
51
|
+
* event, and an untrusted tier also reaches an internal-event child run that
|
|
52
|
+
* inherited it.
|
|
53
|
+
*
|
|
54
|
+
* Kept in step with the dashboard's reduced-privilege banner
|
|
55
|
+
* (`packages/dashboard/src/components/run-detail/degraded-run-banner.tsx`),
|
|
56
|
+
* which describes the same run and may be read beside these checks.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildReducedPrivilegeNote(tier: string | null | undefined, lockFileSource?: string | null): string | null;
|
|
59
|
+
//# sourceMappingURL=reduced-privilege-note.d.ts.map
|