@kici-dev/orchestrator 0.8.0 → 0.9.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__/test-db.d.ts +21 -0
- package/dist/app.d.ts +6 -5
- package/dist/artifacts/artifact-store.d.ts +6 -10
- package/dist/cache/legacy-prefixes.d.ts +19 -0
- package/dist/cache/pending-precursor-db-watcher.d.ts +79 -0
- package/dist/cache/pending-tracker.d.ts +10 -0
- package/dist/cache/precursor-result.d.ts +68 -0
- package/dist/cache/source-cache.d.ts +2 -1
- package/dist/cache/user-cache.d.ts +37 -58
- package/dist/cli/api-client.d.ts +3 -6
- package/dist/cli/commands/cache.d.ts +51 -0
- package/dist/cli/commands/cluster-settings.d.ts +0 -11
- package/dist/cli/commands/trust-policy.d.ts +12 -42
- package/dist/cli/service/privilege.d.ts +27 -6
- package/dist/cli.js +1138 -845
- package/dist/cluster/cluster-settings-reader.d.ts +1 -3
- package/dist/cluster/coordinator.d.ts +2 -3
- package/dist/cluster/join-client.d.ts +1 -39
- package/dist/config.d.ts +0 -6
- package/dist/contexts/context-store.d.ts +3 -3
- package/dist/contexts/held-runs.d.ts +6 -6
- package/dist/contexts/protection/aggregate.d.ts +5 -5
- package/dist/dashboard/handler.d.ts +1 -1
- package/dist/db/migrations/142_sweep_deprecated_columns.d.ts +4 -0
- package/dist/db/migrations/143_execution_jobs_precursor_result.d.ts +26 -0
- package/dist/db/migrations/144_execution_runs_registration_window.d.ts +32 -0
- package/dist/db/types.d.ts +27 -13
- package/dist/index.js +115 -156
- package/dist/metrics/prometheus.d.ts +2 -2
- package/dist/oidc/id-token-claims.d.ts +9 -10
- package/dist/oidc/local-mint.d.ts +9 -12
- package/dist/oidc/oidc-mint-registration.d.ts +15 -28
- package/dist/oidc/orchestrator-mint.d.ts +10 -10
- package/dist/oidc/resolve-signer.d.ts +40 -0
- package/dist/orchestrator-core.d.ts +11 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +1 -2
- package/dist/pipeline/job-contexts.d.ts +2 -2
- package/dist/provenance/attestation-retrier.d.ts +11 -1
- package/dist/provenance/retrier-mint.d.ts +35 -0
- package/dist/providers/github/normalizer.d.ts +1 -28
- package/dist/providers/local/normalizer.d.ts +1 -15
- package/dist/queue/job-queue.d.ts +2 -6
- package/dist/queue/terminalize-unroutable.d.ts +1 -1
- package/dist/reporting/execution-tracker.d.ts +107 -0
- package/dist/scaler/label-matcher.d.ts +0 -1
- package/dist/scaler/manager.d.ts +2 -24
- package/dist/scaler/scaler-state-store.d.ts +2 -2
- package/dist/security/global-workflow-policy.d.ts +3 -28
- package/dist/security/lock-source.d.ts +1 -1
- package/dist/security/trust-policy-gate.d.ts +0 -14
- package/dist/security/trust-policy-store.d.ts +2 -3
- package/dist/security/trust-resolver.d.ts +1 -2
- package/dist/server.js +2666 -2471
- package/dist/stale-detector/all-terminal-runs.d.ts +31 -0
- package/dist/stale-detector/stale-run-detector.d.ts +20 -0
- package/dist/standalone.js +2818 -2561
- package/dist/ws/dashboard-global-workflows-handler.d.ts +3 -4
- package/dist/ws/platform-client.d.ts +0 -15
- package/installer-image-digests.json +3 -3
- package/package.json +18 -17
- package/sbom.spdx.json +1178 -1352
- package/dist/ws/oidc-token-relay.d.ts +0 -79
- package/dist/ws/orch-rpc.d.ts +0 -9
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded wait for the orchestrator's provenance signer.
|
|
3
|
+
*
|
|
4
|
+
* A mint that arrives before the leader has generated the key must NOT fall
|
|
5
|
+
* back to a Platform-signed bundle (it would fail verification against the
|
|
6
|
+
* orchestrator trust root), so the resolver waits for the key instead of
|
|
7
|
+
* deferring on the first miss. But the wait exists for ONE condition — a
|
|
8
|
+
* reconcile that returns `null` because a non-leader has not yet seen the row
|
|
9
|
+
* the leader is about to write. A reconcile that THROWS is a different answer:
|
|
10
|
+
* a signing key sealed under a master key this process does not hold, or `db`
|
|
11
|
+
* custody with no master key at all. Neither changes across the wait, so
|
|
12
|
+
* retrying it for the full window only turns every mint into a silent
|
|
13
|
+
* thirty-second stall that ends in the same `unavailable` defer, with the
|
|
14
|
+
* cause — which the reconcile phrases as an operator recovery instruction —
|
|
15
|
+
* swallowed on every attempt.
|
|
16
|
+
*
|
|
17
|
+
* So: retry the null, stop on the throw, and say why once per distinct cause.
|
|
18
|
+
*/
|
|
19
|
+
import type { Signer } from './signer.js';
|
|
20
|
+
export interface ResolveSignerOptions {
|
|
21
|
+
/** One reconcile pass: a signer, `null` while the key is not ready, or a throw. */
|
|
22
|
+
reconcile: () => Promise<{
|
|
23
|
+
signer: Signer;
|
|
24
|
+
} | null>;
|
|
25
|
+
/** Attempts before giving up on a still-null reconcile. */
|
|
26
|
+
maxAttempts: number;
|
|
27
|
+
/** Delay between two null attempts. */
|
|
28
|
+
delayMs: number;
|
|
29
|
+
/** Sink for the terminal-cause line; called once per distinct message. */
|
|
30
|
+
logError: (message: string, meta: Record<string, unknown>) => void;
|
|
31
|
+
/** Injectable for tests. */
|
|
32
|
+
sleep?: (ms: number) => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build a memoizing resolver: the first successful reconcile is cached for the
|
|
36
|
+
* life of the process, a null reconcile is retried up to `maxAttempts` times,
|
|
37
|
+
* and a thrown reconcile ends the call at once with `null`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createBoundedSignerResolver(opts: ResolveSignerOptions): () => Promise<Signer | null>;
|
|
40
|
+
//# sourceMappingURL=resolve-signer.d.ts.map
|
|
@@ -35,6 +35,7 @@ import type { ScalerConfig } from './scaler/index.js';
|
|
|
35
35
|
import type { CacheStorage } from './storage/index.js';
|
|
36
36
|
import { type ProvenanceTrustRoot } from './provenance/trust-root.js';
|
|
37
37
|
import { type ResolvedDashboardEncryptionKey } from './secrets/dashboard-encryption-key.js';
|
|
38
|
+
import type { Signer } from './oidc/signer.js';
|
|
38
39
|
import { SourceCache, BuildCoordinator, DepCache, UserCache, DispatchCacheRefTracker, PendingBuildTracker, PendingInitTracker, PendingDynamicTracker, PendingGlobalEvalTracker, GlobalEvalRoundCache } from './cache/index.js';
|
|
39
40
|
import { ArtifactStore } from './artifacts/artifact-store.js';
|
|
40
41
|
import { CheckRunReporter } from './reporting/check-run-reporter.js';
|
|
@@ -93,6 +94,16 @@ export interface OrchestratorSubsystems {
|
|
|
93
94
|
* Platform `auth.success` message via `onProvenanceIssuer`.
|
|
94
95
|
*/
|
|
95
96
|
provenanceTrustRoot: ProvenanceTrustRoot;
|
|
97
|
+
/**
|
|
98
|
+
* Orchestrator-owned provenance signing, present when
|
|
99
|
+
* `KICI_ORCHESTRATOR_PROVENANCE_ISSUER` is configured. The deferred-attestation
|
|
100
|
+
* retrier mints with it; `resolveSigner` reconciles the active key lazily and
|
|
101
|
+
* returns null while the key is not yet resolvable.
|
|
102
|
+
*/
|
|
103
|
+
provenanceSigning: {
|
|
104
|
+
issuer: string;
|
|
105
|
+
resolveSigner: () => Promise<Signer | null>;
|
|
106
|
+
} | undefined;
|
|
96
107
|
sourceCache: SourceCache | undefined;
|
|
97
108
|
depCache: DepCache | undefined;
|
|
98
109
|
userCache: UserCache | undefined;
|
|
@@ -382,7 +382,6 @@ interface DispatchSetup {
|
|
|
382
382
|
}
|
|
383
383
|
interface BuildPrepResult {
|
|
384
384
|
sourceTarUrl: string | undefined;
|
|
385
|
-
sourceTarHash: string | undefined;
|
|
386
385
|
/** SHA-256 of the source tarball's own bytes; the agent verifies against it. */
|
|
387
386
|
sourceTarDigest: string | undefined;
|
|
388
387
|
depsUrl: string | undefined;
|
|
@@ -729,7 +728,7 @@ export declare function evaluateJobContexts(args: {
|
|
|
729
728
|
* reaches it only where the operator said so.
|
|
730
729
|
*
|
|
731
730
|
* "Without a trusted emitter" covers both halves of the internal case: a
|
|
732
|
-
* `kiciEvent()` subscriber that inherited
|
|
731
|
+
* `kiciEvent()` subscriber that inherited an `unknown` tier, and one
|
|
733
732
|
* that inherited nothing at all (no emitting run, no persisted tier, a lookup
|
|
734
733
|
* that failed) — the strict fallback, which is not an "untrusted emitter".
|
|
735
734
|
*
|
|
@@ -25,8 +25,8 @@ export interface ResolvedJobContexts {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Resolve the ordered bound-context names from a lock job. Static elements
|
|
28
|
-
* use their value verbatim;
|
|
29
|
-
*
|
|
28
|
+
* use their value verbatim; a dynamic element is resolved by the agent's init
|
|
29
|
+
* job and flags `needsInit`.
|
|
30
30
|
*/
|
|
31
31
|
export declare function resolveJobContextNames(lockJob: LockJob): ResolvedJobContexts;
|
|
32
32
|
/**
|
|
@@ -7,13 +7,19 @@ export type RetrierMintResult = {
|
|
|
7
7
|
} | {
|
|
8
8
|
deferred: true;
|
|
9
9
|
code: string;
|
|
10
|
+
/**
|
|
11
|
+
* What the operator has to change for the deferral to ever clear. Logged
|
|
12
|
+
* once per drain, not per row: the whole queue defers for the same
|
|
13
|
+
* reason, and it defers again on every tick until the operator acts.
|
|
14
|
+
*/
|
|
15
|
+
operatorHint?: string;
|
|
10
16
|
} | {
|
|
11
17
|
rejected: true;
|
|
12
18
|
reason: string;
|
|
13
19
|
};
|
|
14
20
|
export interface AttestationRetrierDeps {
|
|
15
21
|
repo: PendingAttestationsRepo;
|
|
16
|
-
/** Mint the deferred token
|
|
22
|
+
/** Mint the deferred token with the orchestrator's own signer. */
|
|
17
23
|
requestMint: (args: {
|
|
18
24
|
orchestratorId: string;
|
|
19
25
|
runId: string;
|
|
@@ -59,6 +65,8 @@ export interface AttestationRetrierDeps {
|
|
|
59
65
|
export declare class AttestationRetrier {
|
|
60
66
|
private readonly deps;
|
|
61
67
|
private interval;
|
|
68
|
+
/** Operator hints the current drain has collected; flushed once per drain. */
|
|
69
|
+
private drainHints;
|
|
62
70
|
private running;
|
|
63
71
|
constructor(deps: AttestationRetrierDeps);
|
|
64
72
|
/**
|
|
@@ -87,6 +95,8 @@ export declare class AttestationRetrier {
|
|
|
87
95
|
stillPending: number;
|
|
88
96
|
rejected: number;
|
|
89
97
|
}>;
|
|
98
|
+
/** One warning per distinct hint per drain, naming how many rows it holds up. */
|
|
99
|
+
private flushDrainHints;
|
|
90
100
|
fulfilOne(row: PendingAttestationRow): Promise<'minted' | 'rejected' | 'deferred'>;
|
|
91
101
|
}
|
|
92
102
|
//# sourceMappingURL=attestation-retrier.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deferred re-mint the attestation retrier asks for.
|
|
3
|
+
*
|
|
4
|
+
* A deferred attestation is fulfilled with the orchestrator's own signing key,
|
|
5
|
+
* bound to the frozen statement hash. This module turns the orchestrator's
|
|
6
|
+
* signing configuration into the retrier's `requestMint` dependency.
|
|
7
|
+
*/
|
|
8
|
+
import type { Kysely } from 'kysely';
|
|
9
|
+
import type { Database } from '../db/types.js';
|
|
10
|
+
import type { Signer } from '../oidc/signer.js';
|
|
11
|
+
import type { AttestationRetrierDeps } from './attestation-retrier.js';
|
|
12
|
+
/**
|
|
13
|
+
* What the operator has to do when no provenance signer is configured at all.
|
|
14
|
+
* Surfaced once per retrier drain, never per row — the queue does not shrink
|
|
15
|
+
* until the issuer is set, so a per-row line would repeat every minute.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PROVENANCE_ISSUER_UNCONFIGURED_HINT = "No provenance signer is configured, so deferred attestations cannot be completed. Set KICI_ORCHESTRATOR_PROVENANCE_ISSUER on the orchestrator.";
|
|
18
|
+
export interface RetrierMintDeps {
|
|
19
|
+
db: Kysely<Database>;
|
|
20
|
+
/** `undefined` when the orchestrator has no provenance issuer configured. */
|
|
21
|
+
provenanceSigning: {
|
|
22
|
+
issuer: string;
|
|
23
|
+
resolveSigner: () => Promise<Signer | null>;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Test-only fault injection: the build-time test double supplies a predicate
|
|
27
|
+
* over the audience that forces a TERMINAL rejection, so an E2E can exercise
|
|
28
|
+
* the markRejected → gauge-exclusion → `--include-rejected` re-arm cycle with
|
|
29
|
+
* a real deferred row. It returns before the real mint, so the signing choke
|
|
30
|
+
* point is preserved. The shipped orchestrator leaves it undefined.
|
|
31
|
+
*/
|
|
32
|
+
remintReject?: (audience: string) => boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function createRetrierMintRequest(deps: RetrierMintDeps): AttestationRetrierDeps['requestMint'];
|
|
35
|
+
//# sourceMappingURL=retrier-mint.d.ts.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements the WebhookNormalizer interface from @kici-dev/engine for GitHub webhooks.
|
|
5
5
|
* Handles header extraction, HMAC-SHA256 signature verification, and event normalization.
|
|
6
6
|
*/
|
|
7
|
-
import type { WebhookNormalizer, SimulatedEvent
|
|
7
|
+
import type { WebhookNormalizer, SimulatedEvent } from '@kici-dev/engine';
|
|
8
8
|
/**
|
|
9
9
|
* GitHub-specific implementation of WebhookNormalizer.
|
|
10
10
|
*
|
|
@@ -73,32 +73,5 @@ export declare class GitHubWebhookNormalizer implements WebhookNormalizer {
|
|
|
73
73
|
* generating installation tokens for repo access.
|
|
74
74
|
*/
|
|
75
75
|
extractCredentials(payload: unknown): Record<string, unknown>;
|
|
76
|
-
/**
|
|
77
|
-
* Map GitHub membership-related webhook events to permission-cache
|
|
78
|
-
* invalidations. See WebhookNormalizer.getAccessCacheInvalidations, which is
|
|
79
|
-
* deprecated and has no caller.
|
|
80
|
-
*
|
|
81
|
-
* Covered event types:
|
|
82
|
-
*
|
|
83
|
-
* - `member` (`added` / `removed` / `edited`): a collaborator's repo
|
|
84
|
-
* permission changed -> `repo-user` invalidation for the exact
|
|
85
|
-
* `{repo, user}` pair.
|
|
86
|
-
* - `organization` (`member_added` / `member_removed`): a user was added
|
|
87
|
-
* to or removed from the org. The user's effective permission on every
|
|
88
|
-
* repo under the org may have shifted -> `user-in-org`.
|
|
89
|
-
* - `membership` (`added` / `removed`, usually team scope): same reasoning
|
|
90
|
-
* as `organization`. Slightly broader than strictly required, but the
|
|
91
|
-
* cache refills cheaply and over-invalidation is safe.
|
|
92
|
-
* - `team` (`added_to_repository` / `removed_from_repository`): every
|
|
93
|
-
* member of the team gained or lost repo access -> `repo`.
|
|
94
|
-
*
|
|
95
|
-
* Any other event type (including other `team` actions like `created` /
|
|
96
|
-
* `deleted` / `edited` which carry no repo context) returns `[]`.
|
|
97
|
-
*
|
|
98
|
-
* Payload fields are probed defensively; a missing field returns `[]`
|
|
99
|
-
* rather than throwing — this is best-effort and we do not want a
|
|
100
|
-
* malformed payload to crash webhook processing.
|
|
101
|
-
*/
|
|
102
|
-
getAccessCacheInvalidations(eventType: string, _action: string | null, payload: unknown): AccessCacheInvalidation[];
|
|
103
76
|
}
|
|
104
77
|
//# sourceMappingURL=normalizer.d.ts.map
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* verification='none'), and event type / routing key are extracted from custom
|
|
8
8
|
* headers.
|
|
9
9
|
*/
|
|
10
|
-
import type { WebhookNormalizer, SimulatedEvent
|
|
10
|
+
import type { WebhookNormalizer, SimulatedEvent } from '@kici-dev/engine';
|
|
11
11
|
/**
|
|
12
12
|
* Local provider implementation of WebhookNormalizer.
|
|
13
13
|
*
|
|
@@ -69,19 +69,5 @@ export declare class LocalWebhookNormalizer implements WebhookNormalizer {
|
|
|
69
69
|
* when no ref is present.
|
|
70
70
|
*/
|
|
71
71
|
normalizeEvent(eventType: string, _action: string | null, payload: unknown): SimulatedEvent | null;
|
|
72
|
-
/**
|
|
73
|
-
* Map membership-related local-source webhook events to permission-cache
|
|
74
|
-
* invalidations. See WebhookNormalizer.getAccessCacheInvalidations, which is
|
|
75
|
-
* deprecated and has no caller.
|
|
76
|
-
*
|
|
77
|
-
* Local-source payloads are GitHub-shaped by design, so the mapping
|
|
78
|
-
* mirrors the GitHub normalizer exactly:
|
|
79
|
-
*
|
|
80
|
-
* - `member`: repo-user
|
|
81
|
-
* - `organization`: user-in-org
|
|
82
|
-
* - `membership`: user-in-org
|
|
83
|
-
* - `team` (repo-scoped actions only): repo
|
|
84
|
-
*/
|
|
85
|
-
getAccessCacheInvalidations(eventType: string, _action: string | null, payload: unknown): AccessCacheInvalidation[];
|
|
86
72
|
}
|
|
87
73
|
//# sourceMappingURL=normalizer.d.ts.map
|
|
@@ -148,9 +148,7 @@ export interface QueuedJobInput {
|
|
|
148
148
|
timeoutMs?: number;
|
|
149
149
|
/** URL to pre-compiled bundle (from cache). Passed through to job.dispatch. */
|
|
150
150
|
sourceTarUrl?: string;
|
|
151
|
-
/**
|
|
152
|
-
sourceTarHash?: string;
|
|
153
|
-
/** SHA-256 of the source tarball's own bytes (what `sourceTarHash` never was). */
|
|
151
|
+
/** SHA-256 of the source tarball's own bytes, for integrity verification. */
|
|
154
152
|
sourceTarDigest?: string;
|
|
155
153
|
/** URL to pre-built dependency tarball (from dep cache). Passed through to job.dispatch. */
|
|
156
154
|
depsUrl?: string;
|
|
@@ -208,9 +206,7 @@ export interface QueuedJob {
|
|
|
208
206
|
routingKey: string;
|
|
209
207
|
/** URL to pre-compiled bundle (from cache). Passed through to job.dispatch. */
|
|
210
208
|
sourceTarUrl?: string;
|
|
211
|
-
/**
|
|
212
|
-
sourceTarHash?: string;
|
|
213
|
-
/** SHA-256 of the source tarball's own bytes (what `sourceTarHash` never was). */
|
|
209
|
+
/** SHA-256 of the source tarball's own bytes, for integrity verification. */
|
|
214
210
|
sourceTarDigest?: string;
|
|
215
211
|
/** URL to pre-built dependency tarball (from dep cache). Passed through to job.dispatch. */
|
|
216
212
|
depsUrl?: string;
|
|
@@ -12,7 +12,7 @@ import type { ExpiredJobInfo } from './job-queue.js';
|
|
|
12
12
|
* The predicate is allowed to answer "routable" conservatively: the scaler half
|
|
13
13
|
* matches exact labels only, so a pattern-only `runsOn` reads routable on a
|
|
14
14
|
* scaler-configured orchestrator. That costs precision on the status and on
|
|
15
|
-
* how quickly the job settles, never safety — a job that reads routable
|
|
15
|
+
* how quickly the job settles, never safety — a job that reads routable
|
|
16
16
|
* falls through to the queue-timeout backstop.
|
|
17
17
|
*/
|
|
18
18
|
export type CanRouteLabels = (requiredLabels: string[], requiredPatterns: LabelMatcher[], excludeLabels: string[], excludePatterns: LabelMatcher[]) => boolean;
|
|
@@ -232,6 +232,22 @@ export interface ExecutionTrackerDeps {
|
|
|
232
232
|
* `'__default__'` column default (the no-source fallback org).
|
|
233
233
|
*/
|
|
234
234
|
resolveOrgId?: (routingKey: string) => Promise<string>;
|
|
235
|
+
/**
|
|
236
|
+
* This coordinator's cluster instance id. Written to
|
|
237
|
+
* `execution_runs.registration_window_instance_id` while this coordinator's
|
|
238
|
+
* dispatch pipeline still has jobs to register for a run, so a sibling that
|
|
239
|
+
* rehydrates the run from the database defers finalizing it. Optional for
|
|
240
|
+
* the same reason `JobQueue`'s is: a worker with no cluster identity leaves
|
|
241
|
+
* it undefined, holds no durable window, and reads every holder as a sibling.
|
|
242
|
+
*/
|
|
243
|
+
instanceId?: string;
|
|
244
|
+
/**
|
|
245
|
+
* How stale a `cluster_instances` heartbeat may be and still read as live,
|
|
246
|
+
* for the registration-window holder check. Defaults to
|
|
247
|
+
* {@link instanceLivenessGraceMs} at the default recovery grace period —
|
|
248
|
+
* the same window the dispatch queue's ownership predicates use.
|
|
249
|
+
*/
|
|
250
|
+
instanceLivenessGraceMs?: number;
|
|
235
251
|
}
|
|
236
252
|
/**
|
|
237
253
|
* The per-job fields the tracker persists to an execution_jobs row.
|
|
@@ -287,7 +303,16 @@ export declare class ExecutionTracker {
|
|
|
287
303
|
private readonly orgId?;
|
|
288
304
|
private readonly jobQueue?;
|
|
289
305
|
private readonly resolveOrgIdFn?;
|
|
306
|
+
private readonly instanceId?;
|
|
307
|
+
private readonly livenessGraceMs;
|
|
290
308
|
private readonly runs;
|
|
309
|
+
/**
|
|
310
|
+
* Per-run chain of the durable registration-window writes. The write that
|
|
311
|
+
* opens a window is fire-and-forget from the synchronous
|
|
312
|
+
* `holdRunForPendingJobs`; the clear on the last release awaits the chain
|
|
313
|
+
* first, so a clear can never overtake the open it undoes.
|
|
314
|
+
*/
|
|
315
|
+
private readonly registrationWindowWrites;
|
|
291
316
|
/**
|
|
292
317
|
* Per-run async-mutex chain. `onJobStatus` and `addJobsToRun` mutate the same
|
|
293
318
|
* `run.jobs` Map / `execution_jobs` row; without serialization a job-status
|
|
@@ -514,6 +539,41 @@ export declare class ExecutionTracker {
|
|
|
514
539
|
*/
|
|
515
540
|
onJobStatus(runId: string, jobId: string, state: string, timestamp: number, agentId?: string, data?: Record<string, unknown>): Promise<void>;
|
|
516
541
|
private onJobStatusImpl;
|
|
542
|
+
/**
|
|
543
|
+
* Whether this coordinator may finalize the run now.
|
|
544
|
+
*
|
|
545
|
+
* The in-memory job map is only this coordinator's view: a run rehydrated
|
|
546
|
+
* from the database holds just the jobs its own agents reported, and even
|
|
547
|
+
* the run's owner holds a sibling-claimed job at the status it last heard.
|
|
548
|
+
* So once the map reads complete, the shared `execution_jobs` rows are
|
|
549
|
+
* folded in ({@link syncRunJobsFromRows}) and the check is repeated over the
|
|
550
|
+
* whole set — a job another coordinator is still running keeps the run
|
|
551
|
+
* open, and the status computed afterwards covers every job, not a subset.
|
|
552
|
+
*
|
|
553
|
+
* Then the registration window: while a LIVE sibling still has jobs to
|
|
554
|
+
* register (a build-window owner whose real jobs are dispatched only once
|
|
555
|
+
* the build finishes), the rows say nothing yet, so finalization is deferred
|
|
556
|
+
* to whichever coordinator sees the last job finish — or to that owner's
|
|
557
|
+
* own release, which re-runs this check. A dead holder reads as no window,
|
|
558
|
+
* and a run it left behind is finished by the stale detector's
|
|
559
|
+
* all-terminal sweep.
|
|
560
|
+
*/
|
|
561
|
+
private readyToFinalize;
|
|
562
|
+
/**
|
|
563
|
+
* Fold the run's persisted `execution_jobs` rows into its in-memory map:
|
|
564
|
+
* a row this coordinator never registered is added at the row's status, and
|
|
565
|
+
* a job it holds as non-terminal is lifted to the terminal status the row
|
|
566
|
+
* carries. Memory is never downgraded — the row for the job being reported
|
|
567
|
+
* was upserted before any completion check runs, so a terminal entry in
|
|
568
|
+
* memory is always at least as current as its row.
|
|
569
|
+
*/
|
|
570
|
+
private syncRunJobsFromRows;
|
|
571
|
+
/**
|
|
572
|
+
* Whether a live sibling coordinator has a registration window open on the
|
|
573
|
+
* run — see {@link holdRunForPendingJobs}. This coordinator's own window is
|
|
574
|
+
* governed by its in-memory token, so its own id reads as no sibling.
|
|
575
|
+
*/
|
|
576
|
+
private registrationWindowHeldByLiveSibling;
|
|
517
577
|
/**
|
|
518
578
|
* Phase 1a: recover run state from the DB when in-memory tracking is empty.
|
|
519
579
|
* Returns the rehydrated RunState or null if the run is unknown to the DB
|
|
@@ -933,6 +993,28 @@ export declare class ExecutionTracker {
|
|
|
933
993
|
* Each token must be paired with exactly one {@link releasePendingJobsHold}.
|
|
934
994
|
*/
|
|
935
995
|
holdRunForPendingJobs(runId: string): boolean;
|
|
996
|
+
/**
|
|
997
|
+
* Record this coordinator as the run's registration-window holder in
|
|
998
|
+
* `execution_runs.registration_window_instance_id` — the token's durable
|
|
999
|
+
* form, which a sibling coordinator finalizing the run consults.
|
|
1000
|
+
*
|
|
1001
|
+
* Chained rather than awaited: the caller is synchronous by contract (the
|
|
1002
|
+
* token must be counted before the next `await` can drop a sibling token),
|
|
1003
|
+
* so the write rides a per-run chain that the clear on the last release
|
|
1004
|
+
* awaits. `registrationWindowSettled` exposes the chain to a caller that
|
|
1005
|
+
* must know the window is durable before it starts waiting on a job a
|
|
1006
|
+
* sibling may finish. A coordinator with no instance id holds no durable
|
|
1007
|
+
* window.
|
|
1008
|
+
*/
|
|
1009
|
+
private openRegistrationWindow;
|
|
1010
|
+
/**
|
|
1011
|
+
* Clear the durable registration window, only if it still names this
|
|
1012
|
+
* coordinator — a sibling that took the run over owns the column now.
|
|
1013
|
+
*/
|
|
1014
|
+
private clearRegistrationWindow;
|
|
1015
|
+
private chainRegistrationWindowWrite;
|
|
1016
|
+
/** Resolves once every registration-window write queued so far for the run has landed. */
|
|
1017
|
+
registrationWindowSettled(runId: string): Promise<void>;
|
|
936
1018
|
/**
|
|
937
1019
|
* Drop one token taken by {@link holdRunForPendingJobs}, and finalize the run
|
|
938
1020
|
* if dropping the LAST one left every remaining job terminal.
|
|
@@ -1155,6 +1237,31 @@ export declare class ExecutionTracker {
|
|
|
1155
1237
|
* the callbacks nor the completion metrics may claim it.
|
|
1156
1238
|
*/
|
|
1157
1239
|
private completeRunFromMemoryState;
|
|
1240
|
+
/**
|
|
1241
|
+
* Drop the in-memory state of every run another writer already finished.
|
|
1242
|
+
*
|
|
1243
|
+
* A coordinator finalizes a run — and schedules its own prune — only when it
|
|
1244
|
+
* sees the run's last job finish. In a cluster the last job's frames may go
|
|
1245
|
+
* to a sibling, which finalizes the run and prunes ITS state; this
|
|
1246
|
+
* coordinator's copy has no later trigger and, without this sweep, stays in
|
|
1247
|
+
* memory for the life of the process, one `RunState` per cross-coordinator
|
|
1248
|
+
* run. The stale detector calls this on every tick. One query over the
|
|
1249
|
+
* non-finished runs held here; a run whose row is terminal is released the
|
|
1250
|
+
* way the finalizing path releases its own, minus the completion events and
|
|
1251
|
+
* the row write, which the finalizer already did.
|
|
1252
|
+
*
|
|
1253
|
+
* A run this coordinator legitimately still holds — its row non-terminal,
|
|
1254
|
+
* with or without an open registration window — is not touched.
|
|
1255
|
+
*/
|
|
1256
|
+
pruneRunsFinishedElsewhere(): Promise<number>;
|
|
1257
|
+
/**
|
|
1258
|
+
* Treat a run whose row another writer finished as finished here too:
|
|
1259
|
+
* mark it complete so no completion path re-enters it, shed the per-run
|
|
1260
|
+
* state the terminal callback sheds, and schedule the prune. Emits no
|
|
1261
|
+
* completion event and writes no row. Returns false when the run is not
|
|
1262
|
+
* held here or is already finishing.
|
|
1263
|
+
*/
|
|
1264
|
+
private releaseRunFinishedElsewhere;
|
|
1158
1265
|
/**
|
|
1159
1266
|
* Drop a finished run's in-memory state after a grace period, so a late
|
|
1160
1267
|
* status/heartbeat arriving just after completion still finds its run.
|
package/dist/scaler/manager.d.ts
CHANGED
|
@@ -125,16 +125,6 @@ export interface ScalerStatus {
|
|
|
125
125
|
resourceCap?: ResourceCap;
|
|
126
126
|
/** Machine-pool reference, if any. */
|
|
127
127
|
machinePool?: string;
|
|
128
|
-
/**
|
|
129
|
-
* The union of every entry in {@link labelSetMandatoryLabels}. Surfaced in
|
|
130
|
-
* heartbeat-side scaler capacity summaries for a peer that predates the
|
|
131
|
-
* per-label-set gate.
|
|
132
|
-
*
|
|
133
|
-
* @deprecated Use {@link labelSetMandatoryLabels}. On a scaler whose label
|
|
134
|
-
* sets declare different platforms this union names a taint no single set
|
|
135
|
-
* can satisfy, which is what made a mixed-platform scaler unroutable.
|
|
136
|
-
*/
|
|
137
|
-
mandatoryLabels: string[];
|
|
138
128
|
/**
|
|
139
129
|
* Labels a job MUST declare in `runsOn` to be allowed on each label set,
|
|
140
130
|
* index-aligned with `labelSets`. An empty entry means that set has no
|
|
@@ -685,18 +675,6 @@ export declare class ScalerManager {
|
|
|
685
675
|
* that holds one label set never has to reach for the whole backend.
|
|
686
676
|
*/
|
|
687
677
|
private labelSetMandatoryLabels;
|
|
688
|
-
/**
|
|
689
|
-
* The scaler-wide taint gate: {@link labelSetMandatoryLabels} unioned across
|
|
690
|
-
* every label set the backend declares.
|
|
691
|
-
*
|
|
692
|
-
* Its only remaining consumers are the deprecated scaler-wide fields — the
|
|
693
|
-
* `mandatoryLabels` entry on `ScalerStatus['backends']` and the peer
|
|
694
|
-
* scaler-capacity summary — which stay populated for a peer that predates the
|
|
695
|
-
* per-label-set gate. Do NOT gate routing or stamp an agent with this: on a
|
|
696
|
-
* scaler whose label sets declare different platforms the union names a taint
|
|
697
|
-
* no single set can satisfy.
|
|
698
|
-
*/
|
|
699
|
-
private effectiveMandatoryLabels;
|
|
700
678
|
/**
|
|
701
679
|
* Build enriched scaler entries with auto-labels injected into each label set.
|
|
702
680
|
* This ensures label matching accounts for auto-injected labels (kici:role:*,
|
|
@@ -1408,8 +1386,8 @@ export declare class ScalerManager {
|
|
|
1408
1386
|
*
|
|
1409
1387
|
* The `globalUsage` counter is recomputed from `perScalerUsage` to
|
|
1410
1388
|
* keep the cap math consistent. `eventBuffer` is NOT restored — events
|
|
1411
|
-
* emitted by the previous coord before correlation are lost (
|
|
1412
|
-
*
|
|
1389
|
+
* emitted by the previous coord before correlation are lost (they are
|
|
1390
|
+
* observability, not correctness: nothing downstream waits on them).
|
|
1413
1391
|
*
|
|
1414
1392
|
* Both reads are scoped to this instance's own rows. An unscoped read
|
|
1415
1393
|
* hydrates a peer's in-flight spawns and reservations as our own, so our
|
|
@@ -142,8 +142,8 @@ export interface ReservationSnapshot {
|
|
|
142
142
|
*
|
|
143
143
|
* The `eventBuffer` Map is also not persisted: events emitted before
|
|
144
144
|
* correlation are observability, not correctness. A coord crash before
|
|
145
|
-
* `correlateAgentToJob()` runs accepts losing those events (
|
|
146
|
-
*
|
|
145
|
+
* `correlateAgentToJob()` runs accepts losing those events (they are
|
|
146
|
+
* observability, not correctness: nothing downstream waits on them).
|
|
147
147
|
*
|
|
148
148
|
* Ownership columns (`owner_instance_id`, `adopted_by`) let several
|
|
149
149
|
* coordinators behind one shared endpoint divide the same tables between them.
|
|
@@ -15,11 +15,9 @@ interface GlobalWorkflowPermission {
|
|
|
15
15
|
* off — or the cluster row is unreadable, which fails closed — no repo may
|
|
16
16
|
* register or dispatch a global workflow, whatever the per-org lists say.
|
|
17
17
|
*
|
|
18
|
-
* With the switch on,
|
|
18
|
+
* With the switch on, two independent per-org axes apply (each stored as a
|
|
19
19
|
* jsonb array of `{routingKey?, pattern}` entries on `org_settings`). A missing
|
|
20
|
-
* `org_settings` row means "no per-org restrictions"
|
|
21
|
-
* axes, not a denial; elevated access still requires an explicit list, so a
|
|
22
|
-
* missing row grants none.
|
|
20
|
+
* `org_settings` row means "no per-org restrictions", not a denial.
|
|
23
21
|
*
|
|
24
22
|
* - Workflow-repo allow-list (`global_workflow_allowed_repos`): which repos
|
|
25
23
|
* may author global workflows. null/empty = any repo. Checked at
|
|
@@ -31,9 +29,6 @@ interface GlobalWorkflowPermission {
|
|
|
31
29
|
* untrusted contrib repos). Checked at dispatch time against the EVENT's
|
|
32
30
|
* repo (the repo that emitted the webhook).
|
|
33
31
|
*
|
|
34
|
-
* - Elevated access (`global_workflow_elevated_repos`): which workflow-
|
|
35
|
-
* authoring repos can read source-repo secrets during execution.
|
|
36
|
-
*
|
|
37
32
|
* Each entry can optionally pin a `routingKey`, restricting the entry to
|
|
38
33
|
* one webhook source. An entry without a routing key applies to any source
|
|
39
34
|
* in the org. An entry whose routing key no longer matches any current
|
|
@@ -53,7 +48,7 @@ export declare class GlobalWorkflowPolicy {
|
|
|
53
48
|
* The fleet-wide master gate, consulted before any per-org list.
|
|
54
49
|
*
|
|
55
50
|
* Returns `undefined` when the gate passes, or the denial to return
|
|
56
|
-
* verbatim when it does not — so
|
|
51
|
+
* verbatim when it does not — so both axes share one decision and one
|
|
57
52
|
* pair of reasons.
|
|
58
53
|
*
|
|
59
54
|
* Fails closed on `{ ok: false }`. That case is a database fault, not an
|
|
@@ -86,26 +81,6 @@ export declare class GlobalWorkflowPolicy {
|
|
|
86
81
|
* key AND whose pattern matches the source repo → not allowed.
|
|
87
82
|
*/
|
|
88
83
|
isSourceRepoAllowed(eventRoutingKey: string, sourceRepoIdentifier: string, customerId: string): Promise<GlobalWorkflowPermission>;
|
|
89
|
-
/**
|
|
90
|
-
* Check whether a workflow-authoring repository is on the elevated-access
|
|
91
|
-
* list. The workflow's routing key is matched against each entry's optional
|
|
92
|
-
* `routingKey` qualifier. Returns false if no org_settings row or the
|
|
93
|
-
* elevated list is null/empty.
|
|
94
|
-
*
|
|
95
|
-
* @deprecated Not enforced, and not enforceable in this shape. It was meant
|
|
96
|
-
* to gate a global workflow's job reading the *source* repository's secrets,
|
|
97
|
-
* but the organization-wide dispatch path resolves no secrets at all — it
|
|
98
|
-
* binds no secret contexts, and writes no secret material into a job config
|
|
99
|
-
* (asserted by `pipeline/process-webhook-globals-secrets.test.ts`). So there
|
|
100
|
-
* is no injection for a grant to widen, and this method has no caller.
|
|
101
|
-
*
|
|
102
|
-
* Granting it would not be a matter of calling this from the dispatch path:
|
|
103
|
-
* secrets are stored `(org_id, scope, key)` with no repository dimension, so
|
|
104
|
-
* "the source repository's secrets" is not a set the orchestrator can name
|
|
105
|
-
* today. The list, its admin route, its CLI mutators and its wire field are
|
|
106
|
-
* deprecated pending removal at v1.0.0 (`docs/user/deprecations.md`).
|
|
107
|
-
*/
|
|
108
|
-
isElevatedAccessAllowed(workflowRoutingKey: string, repoIdentifier: string, customerId: string): Promise<boolean>;
|
|
109
84
|
private getSettings;
|
|
110
85
|
}
|
|
111
86
|
export {};
|
|
@@ -18,7 +18,7 @@ import type { TrustTier } from '@kici-dev/engine';
|
|
|
18
18
|
* Select which branch's lock file to fetch.
|
|
19
19
|
*
|
|
20
20
|
* Invariant (customer-isolation): for any pull-request event, an
|
|
21
|
-
* untrusted ref (`tier === 'unknown' |
|
|
21
|
+
* untrusted ref (`tier === 'unknown' | undefined`) MUST NOT
|
|
22
22
|
* have its HEAD lock file evaluated by the orchestrator. The
|
|
23
23
|
* base-branch lock — controlled by the project's trusted maintainers —
|
|
24
24
|
* is the source of truth for trigger evaluation, trust-tier-based
|
|
@@ -22,20 +22,6 @@ import { ForkPolicy } from '@kici-dev/engine';
|
|
|
22
22
|
import type { OrchestratorMode, TrustPolicy, TrustTier } from '@kici-dev/engine';
|
|
23
23
|
import { SecurityHoldReason } from '../contexts/held-runs.js';
|
|
24
24
|
import type { StoredTrustPolicy } from './trust-policy-store.js';
|
|
25
|
-
/**
|
|
26
|
-
* The enforcement vocabulary the admin API reports and `kici-admin
|
|
27
|
-
* trust-policy show` renders.
|
|
28
|
-
*
|
|
29
|
-
* @deprecated The route reports `policy` unconditionally: `resolveEffectivePolicy`
|
|
30
|
-
* returns a policy for every input, so there is no state left in which the
|
|
31
|
-
* values are absent. The field and this enum stay so an older `kici-admin`
|
|
32
|
-
* binary keeps parsing the response. Removed at v1.0.0.
|
|
33
|
-
*/
|
|
34
|
-
export declare const TrustPolicyEnforcement: z.ZodEnum<{
|
|
35
|
-
legacy: "legacy";
|
|
36
|
-
policy: "policy";
|
|
37
|
-
}>;
|
|
38
|
-
export type TrustPolicyEnforcement = z.infer<typeof TrustPolicyEnforcement>;
|
|
39
25
|
/** The per-PR facts the fork switch is evaluated against. */
|
|
40
26
|
export interface TrustPolicySignals {
|
|
41
27
|
/** Resolved contributor tier; undefined when no tier was resolved. */
|
|
@@ -52,9 +52,8 @@ export declare class TrustPolicyStore {
|
|
|
52
52
|
* COMMITTED two concurrent PATCHes both read the pre-existing row, and the
|
|
53
53
|
* second `ON CONFLICT DO UPDATE` then overwrites every column from its own
|
|
54
54
|
* stale merge — silently dropping the first operator's change (a tightened
|
|
55
|
-
* `
|
|
56
|
-
*
|
|
57
|
-
* leak.
|
|
55
|
+
* `forkPolicy` reverting to whatever the second caller last saw). The lock
|
|
56
|
+
* releases on commit or rollback, so there is no unlock to leak.
|
|
58
57
|
*
|
|
59
58
|
* `onWrite` receives the same transaction and the merged result, so an audit
|
|
60
59
|
* row written there commits or rolls back with the policy itself — a
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* provider API call, no identity lookup.
|
|
8
8
|
*
|
|
9
9
|
* The stored/wire tier vocabulary keeps 'unknown' as the name for
|
|
10
|
-
* "untrusted" and 'trusted' for "trusted"
|
|
11
|
-
* that is no longer produced.
|
|
10
|
+
* "untrusted" and 'trusted' for "trusted".
|
|
12
11
|
*/
|
|
13
12
|
import type { TrustTier } from '@kici-dev/engine';
|
|
14
13
|
/** Result of ref-based trust resolution, with the reason recorded for audit. */
|