@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.
Files changed (63) hide show
  1. package/dist/__test-helpers__/test-db.d.ts +21 -0
  2. package/dist/app.d.ts +6 -5
  3. package/dist/artifacts/artifact-store.d.ts +6 -10
  4. package/dist/cache/legacy-prefixes.d.ts +19 -0
  5. package/dist/cache/pending-precursor-db-watcher.d.ts +79 -0
  6. package/dist/cache/pending-tracker.d.ts +10 -0
  7. package/dist/cache/precursor-result.d.ts +68 -0
  8. package/dist/cache/source-cache.d.ts +2 -1
  9. package/dist/cache/user-cache.d.ts +37 -58
  10. package/dist/cli/api-client.d.ts +3 -6
  11. package/dist/cli/commands/cache.d.ts +51 -0
  12. package/dist/cli/commands/cluster-settings.d.ts +0 -11
  13. package/dist/cli/commands/trust-policy.d.ts +12 -42
  14. package/dist/cli/service/privilege.d.ts +27 -6
  15. package/dist/cli.js +1138 -845
  16. package/dist/cluster/cluster-settings-reader.d.ts +1 -3
  17. package/dist/cluster/coordinator.d.ts +2 -3
  18. package/dist/cluster/join-client.d.ts +1 -39
  19. package/dist/config.d.ts +0 -6
  20. package/dist/contexts/context-store.d.ts +3 -3
  21. package/dist/contexts/held-runs.d.ts +6 -6
  22. package/dist/contexts/protection/aggregate.d.ts +5 -5
  23. package/dist/dashboard/handler.d.ts +1 -1
  24. package/dist/db/migrations/142_sweep_deprecated_columns.d.ts +4 -0
  25. package/dist/db/migrations/143_execution_jobs_precursor_result.d.ts +26 -0
  26. package/dist/db/migrations/144_execution_runs_registration_window.d.ts +32 -0
  27. package/dist/db/types.d.ts +27 -13
  28. package/dist/index.js +115 -156
  29. package/dist/metrics/prometheus.d.ts +2 -2
  30. package/dist/oidc/id-token-claims.d.ts +9 -10
  31. package/dist/oidc/local-mint.d.ts +9 -12
  32. package/dist/oidc/oidc-mint-registration.d.ts +15 -28
  33. package/dist/oidc/orchestrator-mint.d.ts +10 -10
  34. package/dist/oidc/resolve-signer.d.ts +40 -0
  35. package/dist/orchestrator-core.d.ts +11 -0
  36. package/dist/pipeline/dispatch-matched-workflow.d.ts +1 -2
  37. package/dist/pipeline/job-contexts.d.ts +2 -2
  38. package/dist/provenance/attestation-retrier.d.ts +11 -1
  39. package/dist/provenance/retrier-mint.d.ts +35 -0
  40. package/dist/providers/github/normalizer.d.ts +1 -28
  41. package/dist/providers/local/normalizer.d.ts +1 -15
  42. package/dist/queue/job-queue.d.ts +2 -6
  43. package/dist/queue/terminalize-unroutable.d.ts +1 -1
  44. package/dist/reporting/execution-tracker.d.ts +107 -0
  45. package/dist/scaler/label-matcher.d.ts +0 -1
  46. package/dist/scaler/manager.d.ts +2 -24
  47. package/dist/scaler/scaler-state-store.d.ts +2 -2
  48. package/dist/security/global-workflow-policy.d.ts +3 -28
  49. package/dist/security/lock-source.d.ts +1 -1
  50. package/dist/security/trust-policy-gate.d.ts +0 -14
  51. package/dist/security/trust-policy-store.d.ts +2 -3
  52. package/dist/security/trust-resolver.d.ts +1 -2
  53. package/dist/server.js +2666 -2471
  54. package/dist/stale-detector/all-terminal-runs.d.ts +31 -0
  55. package/dist/stale-detector/stale-run-detector.d.ts +20 -0
  56. package/dist/standalone.js +2818 -2561
  57. package/dist/ws/dashboard-global-workflows-handler.d.ts +3 -4
  58. package/dist/ws/platform-client.d.ts +0 -15
  59. package/installer-image-digests.json +3 -3
  60. package/package.json +18 -17
  61. package/sbom.spdx.json +1178 -1352
  62. package/dist/ws/oidc-token-relay.d.ts +0 -79
  63. 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 a `known` / `unknown` tier, and one
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; any dynamic element (inline or impure) is resolved
29
- * by the agent's init job and flags `needsInit`.
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 (the OIDC relay with deferred params, Task 5). */
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, AccessCacheInvalidation } from '@kici-dev/engine';
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, AccessCacheInvalidation } from '@kici-dev/engine';
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
- /** Content hash of the pre-compiled bundle for verification. */
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
- /** Content hash of the pre-compiled bundle for verification. */
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 simply
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.
@@ -65,7 +65,6 @@ export declare function findBackendForLabels(labels: string[], scalers: Array<{
65
65
  labelSets: Array<{
66
66
  labels: string[];
67
67
  }>;
68
- mandatoryLabels?: string[];
69
68
  labelSetMandatoryLabels?: string[][];
70
69
  }>, excludeLabels?: string[]): {
71
70
  scalerName: string;
@@ -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 (see
1412
- * wishlist for the rationale).
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 (see the
146
- * wishlist for the rationale).
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, three independent per-org axes apply (each stored as a
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" for the repo and source
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 all three axes share one decision and one
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' | 'known' | undefined`) MUST NOT
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
- * `unknownContributorPolicy` reverting to whatever the second caller last
56
- * saw). The lock releases on commit or rollback, so there is no unlock to
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"; 'known' is legacy vocabulary
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. */