@kici-dev/orchestrator 0.4.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.
Files changed (183) hide show
  1. package/dist/__test-helpers__/fake-scaler-state-store.d.ts +24 -0
  2. package/dist/__test-helpers__/mock-db-query.d.ts +130 -0
  3. package/dist/__test-helpers__/mock-db.d.ts +27 -0
  4. package/dist/agent/agent-version.d.ts +34 -0
  5. package/dist/agent/dispatcher.d.ts +151 -13
  6. package/dist/agent/host-roster.d.ts +37 -5
  7. package/dist/agent/registry.d.ts +2 -0
  8. package/dist/app.d.ts +56 -7
  9. package/dist/approvals/apply-decision.d.ts +67 -19
  10. package/dist/approvals/held-run-release-wiring.d.ts +45 -0
  11. package/dist/approvals/independent-wiring.d.ts +55 -0
  12. package/dist/approvals/resume-router.d.ts +9 -0
  13. package/dist/approvals/triggerer-subject.d.ts +37 -0
  14. package/dist/cache/dep-cache.d.ts +54 -10
  15. package/dist/cache/global-eval-round-cache.d.ts +95 -0
  16. package/dist/cache/index.d.ts +3 -0
  17. package/dist/cache/pending-global-evals.d.ts +42 -0
  18. package/dist/cache/pending-inits.d.ts +10 -0
  19. package/dist/cli/api-client.d.ts +18 -0
  20. package/dist/cli/commands/cluster-settings.d.ts +52 -3
  21. package/dist/cli/commands/diagnose.d.ts +6 -0
  22. package/dist/cli/commands/held-run.d.ts +58 -0
  23. package/dist/cli/commands/runs.d.ts +1 -0
  24. package/dist/cli/commands/trust-policy.d.ts +117 -12
  25. package/dist/cli/kici-admin.d.ts +10 -0
  26. package/dist/cli.js +2770 -938
  27. package/dist/cluster/cluster-identity.d.ts +3 -3
  28. package/dist/cluster/cluster-settings-reader.d.ts +59 -1
  29. package/dist/cluster/coordinator.d.ts +44 -0
  30. package/dist/cluster/index.d.ts +4 -0
  31. package/dist/cluster/peer-handler.d.ts +19 -0
  32. package/dist/cluster/peer-registry.d.ts +17 -0
  33. package/dist/cluster/plan-headroom-store.d.ts +26 -0
  34. package/dist/cluster/worker-eviction.d.ts +69 -0
  35. package/dist/cold-store/load-event-log-range.d.ts +10 -3
  36. package/dist/cold-store/load-secret-audit-log-range.d.ts +4 -3
  37. package/dist/cold-store/tables/secret-audit-log.d.ts +4 -3
  38. package/dist/config.d.ts +48 -21
  39. package/dist/content-requirements-cache.d.ts +55 -0
  40. package/dist/contexts/held-runs.d.ts +123 -16
  41. package/dist/contexts/protection/aggregate.d.ts +3 -2
  42. package/dist/contexts/protection/branch-gate.d.ts +15 -0
  43. package/dist/contexts/protection/concurrency-gate.d.ts +8 -2
  44. package/dist/contexts/protection/pipeline.d.ts +18 -0
  45. package/dist/contexts/release-queued-holds.d.ts +35 -0
  46. package/dist/dashboard/attestation-filters.d.ts +8 -0
  47. package/dist/dashboard/handler.d.ts +6 -24
  48. package/dist/db/migrations/109_cluster_settings_cache_knobs.d.ts +4 -0
  49. package/dist/db/migrations/110_cluster_settings_global_eval_knobs.d.ts +4 -0
  50. package/dist/db/migrations/111_cluster_settings_global_eval_wait.d.ts +4 -0
  51. package/dist/db/migrations/112_execution_runs_workflow_repo.d.ts +4 -0
  52. package/dist/db/migrations/113_execution_runs_workflow_repo_index.d.ts +30 -0
  53. package/dist/db/migrations/114_ingest_queue_claim.d.ts +4 -0
  54. package/dist/db/migrations/115_global_workflows_cluster_switch.d.ts +9 -0
  55. package/dist/db/migrations/116_org_plan_headroom.d.ts +4 -0
  56. package/dist/db/migrations/117_invoke_gate_proxy_jobs.d.ts +4 -0
  57. package/dist/db/migrations/118_invoke_gate_scheduling.d.ts +4 -0
  58. package/dist/db/migrations/119_scaler_ha_ownership.d.ts +4 -0
  59. package/dist/db/migrations/120_cluster_settings_scaler_reaper.d.ts +4 -0
  60. package/dist/db/migrations/121_org_settings_allow_untrusted_dockerfile_builds.d.ts +19 -0
  61. package/dist/db/migrations/122_host_roster_scaler_managed.d.ts +25 -0
  62. package/dist/db/migrations/123_workflow_registrations_default_branch.d.ts +24 -0
  63. package/dist/db/migrations/124_cluster_settings_provision_backoff.d.ts +4 -0
  64. package/dist/db/migrations/125_org_trust_directory.d.ts +30 -0
  65. package/dist/db/migrations/126_held_runs_posted_pending_check.d.ts +28 -0
  66. package/dist/db/migrations/127_org_trust_policy_approval_expiry_seconds.d.ts +26 -0
  67. package/dist/db/migrations/128_execution_runs_global_eval_round.d.ts +23 -0
  68. package/dist/db/migrations/129_execution_runs_dispatch_routing_key.d.ts +24 -0
  69. package/dist/db/migrations/130_scaler_provision_outcomes.d.ts +32 -0
  70. package/dist/db/types.d.ts +428 -12
  71. package/dist/entry-helpers.d.ts +28 -0
  72. package/dist/events/event-emitter.d.ts +22 -0
  73. package/dist/events/event-router.d.ts +87 -1
  74. package/dist/events/types.d.ts +2 -23
  75. package/dist/git/credential-broker.d.ts +94 -0
  76. package/dist/git/inherited-secret.d.ts +24 -0
  77. package/dist/git/installation-token.d.ts +38 -0
  78. package/dist/git/job-context.d.ts +13 -0
  79. package/dist/index.js +324 -84
  80. package/dist/metrics/agent-metrics-aggregator.d.ts +4 -4
  81. package/dist/metrics/prometheus.d.ts +170 -3
  82. package/dist/oidc/oidc-mint-registration.d.ts +7 -3
  83. package/dist/orchestrator-core.d.ts +91 -24
  84. package/dist/pipeline/content-filter.d.ts +71 -0
  85. package/dist/pipeline/direct-ingress-deps.d.ts +100 -0
  86. package/dist/pipeline/dispatch-matched-workflow.d.ts +521 -11
  87. package/dist/pipeline/global-eval-round.d.ts +307 -0
  88. package/dist/pipeline/internal-event-pipeline.d.ts +140 -0
  89. package/dist/pipeline/invoke-gate.d.ts +164 -0
  90. package/dist/pipeline/job-contexts.d.ts +16 -17
  91. package/dist/pipeline/needs-scheduler.d.ts +13 -0
  92. package/dist/pipeline/process-webhook.d.ts +126 -31
  93. package/dist/pipeline/processor.d.ts +282 -23
  94. package/dist/pipeline/rerun.d.ts +21 -0
  95. package/dist/pipeline/resume-workflow.d.ts +40 -7
  96. package/dist/pipeline/route-or-dispatch-jobs.d.ts +15 -0
  97. package/dist/pipeline/security-hold-check.d.ts +217 -0
  98. package/dist/pipeline/test-pipeline.d.ts +12 -0
  99. package/dist/pipeline/undispatched-hold-checks.d.ts +23 -0
  100. package/dist/pipeline/webhook-payload-store.d.ts +20 -0
  101. package/dist/policy/dashboard-write-policy-listener.d.ts +62 -0
  102. package/dist/policy/dashboard-write-policy.d.ts +14 -0
  103. package/dist/provenance/backfill-run.d.ts +10 -1
  104. package/dist/provider-registry.d.ts +47 -4
  105. package/dist/providers/github/check-status-poster.d.ts +33 -3
  106. package/dist/providers/github/commit-message.d.ts +20 -0
  107. package/dist/providers/github/file-contents.d.ts +40 -0
  108. package/dist/providers/github/index.d.ts +2 -2
  109. package/dist/providers/github/normalizer.d.ts +3 -2
  110. package/dist/providers/local/normalizer.d.ts +3 -5
  111. package/dist/providers/universal-git/config.d.ts +2 -0
  112. package/dist/providers/universal-git/index.d.ts +11 -2
  113. package/dist/providers/universal-git/normalizer.d.ts +10 -0
  114. package/dist/queue/cleanup.d.ts +7 -1
  115. package/dist/queue/job-queue.d.ts +112 -7
  116. package/dist/queue/terminalize-unroutable.d.ts +13 -0
  117. package/dist/registration/extractor.d.ts +3 -3
  118. package/dist/registration/registration-index.d.ts +7 -0
  119. package/dist/registration/registration-run-match.d.ts +47 -0
  120. package/dist/registration/registration-store.d.ts +22 -0
  121. package/dist/reporting/check-run-reporter.d.ts +146 -2
  122. package/dist/reporting/check-run-summary.d.ts +25 -1
  123. package/dist/reporting/execution-tracker.d.ts +324 -9
  124. package/dist/reporting/log-chunk-sink.d.ts +8 -5
  125. package/dist/reporting/run-aggregator.d.ts +4 -14
  126. package/dist/reporting/step-display-order.d.ts +43 -0
  127. package/dist/routes/admin-held-runs.d.ts +142 -0
  128. package/dist/routes/admin-org-settings.d.ts +5 -0
  129. package/dist/routes/admin-trust-policy.d.ts +34 -6
  130. package/dist/routes/admin.d.ts +15 -0
  131. package/dist/scaler/backend-factory.d.ts +55 -0
  132. package/dist/scaler/bare-metal-backend.d.ts +43 -5
  133. package/dist/scaler/claim-store.d.ts +119 -0
  134. package/dist/scaler/config.d.ts +4 -0
  135. package/dist/scaler/container-backend.d.ts +18 -4
  136. package/dist/scaler/container-routing.d.ts +23 -0
  137. package/dist/scaler/container-spawn.d.ts +28 -0
  138. package/dist/scaler/event-backend.d.ts +124 -0
  139. package/dist/scaler/event-provision-reaper.d.ts +350 -0
  140. package/dist/scaler/failure-tracker.d.ts +1 -1
  141. package/dist/scaler/firecracker-backend.d.ts +33 -6
  142. package/dist/scaler/index.d.ts +11 -1
  143. package/dist/scaler/label-matcher.d.ts +4 -3
  144. package/dist/scaler/manager.d.ts +905 -76
  145. package/dist/scaler/resolve-container-auth.d.ts +45 -0
  146. package/dist/scaler/scaler-events.d.ts +11 -0
  147. package/dist/scaler/scaler-state-store.d.ts +294 -10
  148. package/dist/scaler/types.d.ts +131 -13
  149. package/dist/scaler/warm-pool.d.ts +132 -29
  150. package/dist/security/comment-handler.d.ts +50 -11
  151. package/dist/security/global-workflow-policy.d.ts +52 -12
  152. package/dist/security/identity-link.d.ts +60 -0
  153. package/dist/security/lock-source.d.ts +11 -13
  154. package/dist/security/reduced-privilege-note.d.ts +59 -0
  155. package/dist/security/trust-directory-store.d.ts +233 -0
  156. package/dist/security/trust-policy-gate.d.ts +94 -59
  157. package/dist/security/trust-policy-store.d.ts +3 -0
  158. package/dist/security/trust-resolver.d.ts +14 -98
  159. package/dist/server.d.ts +11 -1
  160. package/dist/server.js +56329 -42234
  161. package/dist/stale-detector/gate-deadline-detector.d.ts +52 -0
  162. package/dist/stale-detector/stale-run-detector.d.ts +61 -3
  163. package/dist/standalone.js +37619 -23570
  164. package/dist/webhook/ingest-accept.d.ts +70 -0
  165. package/dist/webhook/ingest-overflow-buffer.d.ts +35 -4
  166. package/dist/webhook/ingest-overflow-replayer.d.ts +50 -6
  167. package/dist/worker/in-memory-job-queue.d.ts +1 -1
  168. package/dist/ws/agent-handler.d.ts +31 -4
  169. package/dist/ws/dashboard-context-handler.d.ts +28 -4
  170. package/dist/ws/dashboard-global-workflows-handler.d.ts +36 -9
  171. package/dist/ws/execution-status-frame.d.ts +32 -0
  172. package/dist/ws/failure-messages.d.ts +2 -0
  173. package/dist/ws/git-credential-relay.d.ts +39 -0
  174. package/dist/ws/oidc-token-relay.d.ts +6 -11
  175. package/dist/ws/platform-client.d.ts +68 -9
  176. package/dist/ws/test-relay-handlers.d.ts +35 -10
  177. package/installer-image-digests.json +3 -3
  178. package/package.json +24 -19
  179. package/sbom.spdx.json +868 -821
  180. package/dist/approvals/team-membership-lookup.d.ts +0 -13
  181. package/dist/pipeline/inline-eval.d.ts +0 -44
  182. package/dist/providers/github/contributor-resolver.d.ts +0 -30
  183. package/dist/security/contributor-cache.d.ts +0 -83
@@ -72,6 +72,7 @@ export declare const UniversalGitPayloadPathsSchema: z.ZodObject<{
72
72
  commitsAdded: z.ZodString;
73
73
  commitsModified: z.ZodString;
74
74
  commitsRemoved: z.ZodString;
75
+ commitMessage: z.ZodOptional<z.ZodString>;
75
76
  }, z.core.$strip>;
76
77
  export type UniversalGitPayloadPaths = z.infer<typeof UniversalGitPayloadPathsSchema>;
77
78
  /**
@@ -129,6 +130,7 @@ export declare const UniversalGitConfigSchema: z.ZodObject<{
129
130
  commitsAdded: z.ZodString;
130
131
  commitsModified: z.ZodString;
131
132
  commitsRemoved: z.ZodString;
133
+ commitMessage: z.ZodOptional<z.ZodString>;
132
134
  }, z.core.$strip>>;
133
135
  eventMapping: z.ZodOptional<z.ZodObject<{
134
136
  push: z.ZodArray<z.ZodString>;
@@ -18,8 +18,17 @@
18
18
  * - `UniversalGitRepoUrlBuilder` — substitutes `{owner}`/`{name}`/`{repo}`
19
19
  * in the source's `gitUrlTemplate`.
20
20
  *
21
- * No `ContributorResolver` or `CheckStatusPoster` is wired for v1 — forge
22
- * API support for those is uneven and adding it is a separate phase.
21
+ * No `CheckStatusPoster` is wired for v1 — forge API support for it is uneven
22
+ * and adding it is a separate phase.
23
+ *
24
+ * The bundle also leaves `hasForkModel` unset. The normalizer does report an
25
+ * `isForkPR`, but it compares two repo names that `extractRepoFullName` reads
26
+ * from fixed keys (`repo.full_name`, then `full_name`) on the payload's `head`
27
+ * and `base` — keys many forges' PR payloads do not carry, and which no
28
+ * `payloadPaths` entry can redirect. It yields `false` whenever either is
29
+ * absent, so it fails toward trust. A pull-request event here therefore
30
+ * resolves no trust tier: it reads the base branch's lock file and writes an
31
+ * isolated cache scope, and the org fork policy never gates it.
23
32
  */
24
33
  import type { ProviderBundle } from '../../provider-registry.js';
25
34
  import type { GenericWebhookSource } from '../../db/types.js';
@@ -84,6 +84,16 @@ export declare class UniversalGitWebhookNormalizer implements WebhookNormalizer
84
84
  * is safe to ship ahead of the interface change in Phase 3.
85
85
  */
86
86
  extractDefaultBranch(payload: unknown): string | null;
87
+ /**
88
+ * Text a `commitMessage` trigger filter is tested against.
89
+ *
90
+ * Push/tag reads the configured JSONPath, because forges genuinely differ on
91
+ * where the head commit lives. The PR read is STRUCTURAL rather than
92
+ * configured, matching how this normalizer already resolves base/head refs —
93
+ * `pull_request` on Gitea-family forges, `object_attributes` on GitLab, whose
94
+ * body field is spelled `description`.
95
+ */
96
+ private extractCommitMessage;
87
97
  /** Classify the raw event header against the source's eventMapping. */
88
98
  private classifyEvent;
89
99
  }
@@ -6,7 +6,7 @@ import type { CheckRunTrackingStore } from '../reporting/check-run-tracking-stor
6
6
  import type { ClusterSettingsReader } from '../cluster/cluster-settings-reader.js';
7
7
  import type { LogStorage } from '../reporting/log-storage.js';
8
8
  import { JobQueue } from './job-queue.js';
9
- import { type CanRouteLabels } from './terminalize-unroutable.js';
9
+ import { type CanRouteLabels, type TerminalizeDeps } from './terminalize-unroutable.js';
10
10
  /**
11
11
  * Optional cleanup dependencies + retention knobs. Present in platform/hybrid
12
12
  * mode (the only wiring site, `orchestrator-core`, always supplies them); the
@@ -67,6 +67,12 @@ export interface CleanupExtras {
67
67
  * safety: the job is terminal either way and the run fails either way.
68
68
  */
69
69
  canRouteLabels?: CanRouteLabels;
70
+ /**
71
+ * The Tier-2 global-eval tracker, forwarded to
72
+ * {@link terminalizeUnroutableJob} so a round job this sweep settles also
73
+ * settles the webhook request awaiting its verdict.
74
+ */
75
+ pendingGlobalEvals?: TerminalizeDeps['pendingGlobalEvals'];
70
76
  }
71
77
  /**
72
78
  * Run a single cleanup pass: remove expired dedup_cache entries and
@@ -180,6 +180,23 @@ export interface QueuedJob {
180
180
  /** For a runsOnAll host-fanout child: the agent this job is pinned to. */
181
181
  pinnedAgentId?: string;
182
182
  }
183
+ /**
184
+ * The resource shape a job declares, read from the one location that always
185
+ * carries it.
186
+ *
187
+ * `jobConfig.resources` is where the declaration is stored and transported: it
188
+ * is what `job_config` holds across the DB round trip and what a worker
189
+ * receives over the wire. The typed `resources` field on {@link QueuedJobInput}
190
+ * and {@link QueuedJob} is a convenience mirror, and it is optional — several
191
+ * dispatch paths build their input without it (the worker's reroute handler and
192
+ * the webhook dispatch path among them). So a consumer that reads only the
193
+ * mirror sees a job that declares nothing, and a shape-sensitive decision made
194
+ * on that reading is wrong rather than merely unaware.
195
+ *
196
+ * Callers that hold a mirror still prefer it — it is the same value — and fall
197
+ * back here.
198
+ */
199
+ export declare function resourcesFromJobConfig(jobConfig: Record<string, unknown> | undefined): ResourceRequest | undefined;
183
200
  /**
184
201
  * DB-backed FIFO job dispatch queue using Kysely (PostgreSQL only).
185
202
  * Uses SQL-based JSONB containment queries (@> operator) for label matching.
@@ -236,21 +253,69 @@ export declare class JobQueue {
236
253
  * @param agentLabels Labels the agent provides.
237
254
  * @param agentMandatoryLabels Mandatory labels the spawning scaler declared
238
255
  * (empty for static / non-scaler agents).
256
+ * @param canServe Optional extra predicate applied to every candidate row
257
+ * before it is claimed. Labels cannot express whether a pre-spawned agent's
258
+ * fixed cpu / memory / image fit a job, so a caller that has to answer that
259
+ * supplies it here — the row must never be claimed and put back, because a
260
+ * claim-then-release both strands it as Dispatched for a window and burns a
261
+ * dispatch attempt. Supplying it opts out of the single-statement fast
262
+ * path, so pass it only for an agent that actually needs the check.
239
263
  * @returns The matching job, or null if none found.
240
264
  */
241
- dequeueForLabels(agentLabels: string[], agentMandatoryLabels?: string[], agentId?: string): Promise<QueuedJob | null>;
265
+ dequeueForLabels(agentLabels: string[], agentMandatoryLabels?: string[], agentId?: string, canServe?: (job: QueuedJob) => boolean): Promise<QueuedJob | null>;
266
+ /**
267
+ * The column writes that constitute a claim. Identical to what
268
+ * {@link markDispatched} sets, so a claim and the caller's follow-up
269
+ * markDispatched are the same transition applied twice rather than two
270
+ * different half-transitions — and a row is never observable as Dispatched
271
+ * with no owner.
272
+ *
273
+ * `agentId` is optional only because `dequeueForLabels` accepts it optionally;
274
+ * every production caller supplies it.
275
+ */
276
+ private claimTransition;
277
+ /**
278
+ * Conditionally claim one row by id: flip Pending -> Dispatched, returning
279
+ * whether this caller won. The `status = Pending` guard is the arbiter — a
280
+ * loser updates zero rows and must treat that as "someone else took it",
281
+ * never as an error.
282
+ *
283
+ * Used by every claim path that has to run a JS post-filter before claiming
284
+ * (the regex matchers), since that filter runs after the SELECT's
285
+ * per-statement lock window has already closed.
286
+ */
287
+ private claimRowById;
242
288
  /**
243
289
  * Build the shared drain WHERE chain (status / expiry / exact-label @> /
244
290
  * exclude-label / pin / mandatory-label gate) common to both drain passes.
245
291
  * The pattern columns are NOT filtered here — each pass adds its own
246
292
  * pattern-free / pattern-bearing guard on top.
293
+ *
294
+ * No projection is attached: the pattern-free pass selects `id` alone (it
295
+ * embeds this as the sub-select of its claiming UPDATE), while the pattern
296
+ * pass selects every column so it can run the JS matcher post-filter.
247
297
  */
248
298
  private drainBaseQuery;
249
299
  /**
250
300
  * Fast path: claim the oldest pending pattern-free row. The
251
301
  * `runs_on_patterns = '[]' AND exclude_patterns = '[]'` guard restricts this
252
- * pass to rows that need no JS post-filter, so the single-row atomic claim
253
- * (FOR UPDATE SKIP LOCKED) keeps the original hot-path semantics intact.
302
+ * pass to rows that need no JS post-filter, which is what lets the whole
303
+ * claim be ONE statement.
304
+ *
305
+ * Selecting a row and transitioning it in two statements is not a claim.
306
+ * Outside an explicit transaction the `FOR UPDATE` lock lives only for the
307
+ * duration of its own SELECT, so a second agent arriving between the SELECT
308
+ * and the UPDATE reads the row still Pending, skips nothing, and dispatches
309
+ * the same job — which is one job executing twice on two agents, side effects
310
+ * and all.
311
+ *
312
+ * So the sub-select is embedded in the claiming UPDATE: its `FOR UPDATE SKIP
313
+ * LOCKED` row lock is now taken inside the UPDATE's own transaction and held
314
+ * until commit. That buys both halves at once — exactly one claimant can win
315
+ * a given row, and a concurrent claimant SKIPs the locked row and takes the
316
+ * *next* one instead of coming back empty-handed, which a select-then-claim
317
+ * retry loop would not preserve. The redundant outer `status = Pending` is
318
+ * belt-and-braces on the arbiter.
254
319
  */
255
320
  private claimPatternFree;
256
321
  /**
@@ -263,8 +328,29 @@ export declare class JobQueue {
263
328
  * Pending` makes exactly one of them win. The claim transitions the row to
264
329
  * Dispatched, matching the value the caller-side markDispatched would set
265
330
  * (which then re-sets it idempotently).
331
+ *
332
+ * Losing the claim continues to the next candidate rather than returning
333
+ * null, so a lost race costs this agent a candidate and not a whole drain.
266
334
  */
267
335
  private claimWithPatterns;
336
+ /**
337
+ * Both drain passes at once, with an extra JS predicate between the SELECT
338
+ * and the claim.
339
+ *
340
+ * Same shape as {@link claimWithPatterns} and for the same reason: the filter
341
+ * has to run BEFORE the row transitions, so the claim is a conditional
342
+ * `status = Pending` UPDATE by id rather than the embedded sub-select the
343
+ * fast path uses. Claiming first and releasing on a rejection is not an
344
+ * option — it strands the row as Dispatched for the width of the round trip
345
+ * and spends one of the job's bounded dispatch attempts every time an
346
+ * unsuitable agent polls.
347
+ *
348
+ * It covers pattern-free rows too, which the fast path would otherwise take
349
+ * first, so the predicate is applied to every candidate rather than to the
350
+ * pattern-bearing minority. Losing a claim continues to the next candidate,
351
+ * exactly as the pattern pass does.
352
+ */
353
+ private claimWithPostFilter;
268
354
  /**
269
355
  * Atomically claim the oldest pending job pinned to a specific agent. Used by
270
356
  * the eager pin drain when the pinned agent (re)registers or frees a slot —
@@ -276,6 +362,13 @@ export declare class JobQueue {
276
362
  * `runsOn`/`exclude` patterns no longer match the agent's current labels must
277
363
  * not be claimed. The single matching authority is the engine's
278
364
  * `matcherSatisfiedBy` (never a Postgres `~`).
365
+ *
366
+ * The claim is the conditional UPDATE, not the SELECT: the JS post-filter has
367
+ * to run first (claiming and then releasing a pattern-rejected row would
368
+ * strand it as Dispatched), which puts the filter outside the SELECT's
369
+ * per-statement lock window. Losing that claim returns null, and
370
+ * `onAgentAvailable` then falls through to the generic label drain — which
371
+ * also matches jobs pinned to this agent — so a lost race is not a stall.
279
372
  */
280
373
  dequeueByPinnedAgent(agentId: string, agentLabels?: string[]): Promise<QueuedJob | null>;
281
374
  /**
@@ -292,10 +385,22 @@ export declare class JobQueue {
292
385
  * spawned the agent and was reassigned to a different queued job).
293
386
  *
294
387
  * Returns null if the job is gone, no longer pending, expired, its label
295
- * requirements are no longer satisfied by the agent, or the agent's gate
296
- * is not satisfied by the job's `runsOn`.
297
- */
298
- dequeueById(jobId: string, agentLabels: string[], agentMandatoryLabels?: string[]): Promise<QueuedJob | null>;
388
+ * requirements are no longer satisfied by the agent, the agent's gate is not
389
+ * satisfied by the job's `runsOn`, or another claimant won the row first.
390
+ *
391
+ * That last case is the one this shares with every other claim path: the
392
+ * eager bound claim and the generic drain can target the same row moments
393
+ * apart, and a SELECT that returns the row still Pending lets both dispatch
394
+ * it. The conditional UPDATE below is the arbiter, and it runs after the JS
395
+ * post-filter so a pattern-rejected row is never claimed and stranded. A
396
+ * loser returns null, and `dispatchBoundJob` then falls back to the generic
397
+ * `onAgentAvailable` drain exactly as it does for an already-gone job.
398
+ *
399
+ * @param claimingAgentId Recorded as the row's durable owner as part of the
400
+ * claim. Optional so existing 3-arg callers keep working; the caller's
401
+ * markDispatched sets the same column immediately afterwards either way.
402
+ */
403
+ dequeueById(jobId: string, agentLabels: string[], agentMandatoryLabels?: string[], claimingAgentId?: string): Promise<QueuedJob | null>;
299
404
  /**
300
405
  * Insert a job directly with status='dispatched' (bypasses the queue).
301
406
  * Used when an agent is immediately available and the job doesn't need to wait.
@@ -3,6 +3,7 @@ import { ExecutionJobStatus, type LabelMatcher } from '@kici-dev/engine';
3
3
  import type { Database } from '../db/types.js';
4
4
  import type { ExecutionTracker } from '../reporting/execution-tracker.js';
5
5
  import type { CheckRunReporter } from '../reporting/check-run-reporter.js';
6
+ import type { PendingGlobalEvalTracker } from '../cache/pending-global-evals.js';
6
7
  import type { ExpiredJobInfo } from './job-queue.js';
7
8
  /**
8
9
  * Whether ANYTHING could ever run a job with these selectors — a registered
@@ -23,6 +24,18 @@ export interface TerminalizeDeps {
23
24
  executionTracker: ExecutionTracker;
24
25
  checkRunReporter?: Pick<CheckRunReporter, 'updateJobStatus'>;
25
26
  canRouteLabels?: CanRouteLabels;
27
+ /**
28
+ * The Tier-2 global-eval tracker, so a round job settled here also settles
29
+ * the webhook request awaiting its verdict.
30
+ *
31
+ * A round job is the one queue entry with an in-process awaiter and no
32
+ * `execution_runs` row — the round decides whether any run exists at all —
33
+ * so the rest of this function skips it entirely and the awaiter would
34
+ * otherwise wait out its full ceiling for a job the queue has already
35
+ * declared dead. With the shipped defaults that is a 120s definitive
36
+ * fast-fail followed by a 240s wait, twice.
37
+ */
38
+ pendingGlobalEvals?: Pick<PendingGlobalEvalTracker, 'reject'>;
26
39
  }
27
40
  export declare function unroutableMessage(job: JobRoutingFacts): string;
28
41
  /**
@@ -29,9 +29,9 @@ export declare function extractGlobalWorkflows(lockFile: LockFile): LockWorkflow
29
29
  * Extract workflows that have at least one registerable trigger, OR that
30
30
  * have repo patterns (global workflows).
31
31
  *
32
- * Since phase 28.5, Git-provider triggers (push, pr, tag, …) are ALSO
33
- * registerable so cross-source dispatch can resolve them via the registration
34
- * index when a generic webhook targets an externally-hosted repo. The
32
+ * Git-provider triggers (push, pr, tag, …) are ALSO registerable, so
33
+ * cross-source dispatch can resolve them via the registration index when a
34
+ * generic webhook targets an externally-hosted repo. The
35
35
  * per-event lock file pipeline remains the primary matching path for
36
36
  * same-source git events; registration is an additive index.
37
37
  *
@@ -21,6 +21,13 @@ export interface RegisteredWorkflow {
21
21
  */
22
22
  customerId: string;
23
23
  commitSha: string | null;
24
+ /**
25
+ * The repository's default branch at last registration. A `__schedule_fire`
26
+ * run executes this branch's lock file, so it is the branch such a run
27
+ * presents to a context's branch restrictions. `null` until the repo's next
28
+ * default-branch push populates it.
29
+ */
30
+ defaultBranch: string | null;
24
31
  sourceFile: string | null;
25
32
  }
26
33
  /**
@@ -0,0 +1,47 @@
1
+ import type { ExpressionBuilder, ExpressionWrapper, SqlBool } from 'kysely';
2
+ import type { Database } from '../db/types.js';
3
+ /**
4
+ * Match `execution_runs` rows to the registrations that define their workflow.
5
+ *
6
+ * A registration always names the repository the workflow is DEFINED in. A run
7
+ * names two repositories: `repo_identifier` is the one it acted on and whose
8
+ * code its jobs checked out, and `workflow_repo_identifier` is the one that
9
+ * defines the workflow — recorded only when the two differ, which is exactly an
10
+ * organization-wide workflow dispatched against another repository.
11
+ *
12
+ * So the repository a registration must be matched on is
13
+ * `workflow_repo_identifier ?? repo_identifier`. Matching on `repo_identifier`
14
+ * alone is wrong in both directions at once: a global registration matches none
15
+ * of its own runs, and a same-named registration in the acted-on repository
16
+ * matches all of them.
17
+ *
18
+ * Both consumers — the "last triggered" enrichment and the delete path's
19
+ * in-flight cancellation — share this one predicate deliberately. Fixing one
20
+ * and not the other would leave the dashboard showing a global workflow as
21
+ * triggered while deleting it still cancelled nothing.
22
+ *
23
+ * A NULL marker is evidence of a per-repository run only because every
24
+ * recording site states which repository defines the workflow it is recording,
25
+ * and the sites narrow that to NULL exactly when it is the repository the run
26
+ * acted on. The predicate never infers the answer, and the recording sites do
27
+ * not leave it to a default: `recordInitFailureRun`, `recordRunHeld` and
28
+ * `recordGlobalEvalRoundFailureRun` take the defining repository as a required
29
+ * argument, and `WorkflowDispatchContext` carries it as a required field, so a
30
+ * dispatch path that does not state it does not compile. The one run-start
31
+ * recorder that takes it as a trailing positional, `onExecutionStarted`, is
32
+ * supplied by the global dispatch path — the only path where the two
33
+ * repositories differ.
34
+ */
35
+ export declare function runsDefinedByRepos(eb: ExpressionBuilder<Database, 'execution_runs'>, repoIdentifiers: readonly string[]): ExpressionWrapper<Database, 'execution_runs', SqlBool>;
36
+ /**
37
+ * The repository that defines the workflow a run executed — the read-side
38
+ * counterpart of {@link runsDefinedByRepos}, applied to a row the query
39
+ * returned. Selecting `workflow_repo_identifier` alongside `repo_identifier`
40
+ * and folding here keeps the grouping key the same expression the predicate
41
+ * filters on.
42
+ */
43
+ export declare function definingRepoOfRun(row: {
44
+ repo_identifier: string;
45
+ workflow_repo_identifier: string | null;
46
+ }): string;
47
+ //# sourceMappingURL=registration-run-match.d.ts.map
@@ -24,6 +24,16 @@ export interface RegistrationRow {
24
24
  */
25
25
  customerId: string;
26
26
  commitSha: string | null;
27
+ /**
28
+ * The repository's default branch, captured from the push that last updated
29
+ * this registration. A `__schedule_fire` run executes this branch's lock
30
+ * file, so this is the branch such a run presents to a context's branch
31
+ * restrictions.
32
+ *
33
+ * `null` when the registration predates the column or the payload named no
34
+ * default branch — the run then presents no branch and the gate rejects.
35
+ */
36
+ defaultBranch: string | null;
27
37
  sourceFile: string | null;
28
38
  created_at: Date;
29
39
  updated_at: Date;
@@ -40,6 +50,18 @@ interface ReplaceAllOptions {
40
50
  */
41
51
  customerId: string;
42
52
  commitSha?: string;
53
+ /**
54
+ * The repository's default branch, read off the registering push's payload.
55
+ * Persisted so a `__schedule_fire` run can present it as its own branch.
56
+ *
57
+ * Three states, and the difference between the last two is load-bearing on an
58
+ * UPDATE: a string is written; `null` means the caller LOOKED and the payload
59
+ * named none, so the stored value is cleared; **omitting the field** means the
60
+ * caller does not know, and an already-captured branch is preserved. Only a
61
+ * default-branch push knows a repository's default branch, so a caller that
62
+ * is not one (the manual registration route) must not erase what one proved.
63
+ */
64
+ defaultBranch?: string | null;
43
65
  sourceFile?: string;
44
66
  /** Set of workflow names that should be marked as global */
45
67
  globalWorkflowNames?: Set<string>;
@@ -30,7 +30,7 @@ import type { ProviderRegistry } from '../provider-registry.js';
30
30
  import type { StepLogBuffer } from './step-log-buffer.js';
31
31
  import { type SourceLocationData } from './check-run-summary.js';
32
32
  import type { CheckRunTrackingStore } from './check-run-tracking-store.js';
33
- import { type TerminalJobStatus } from '@kici-dev/engine';
33
+ import { CheckRunConclusion, type TerminalJobStatus } from '@kici-dev/engine';
34
34
  /**
35
35
  * Dependencies for the CheckRunReporter.
36
36
  */
@@ -87,6 +87,15 @@ interface SetPendingOptions {
87
87
  repo: string;
88
88
  sha: string;
89
89
  workflowName: string;
90
+ /**
91
+ * The repository that DEFINES the workflow, when that is not the repository
92
+ * the run acted on — an organization-wide workflow dispatched against
93
+ * another repository. Qualifies the check-run name so the run cannot share a
94
+ * check run with a same-named workflow of the acted-on repository; see
95
+ * `workflowLabel`. Passing the acted-on repository here is a no-op, so a
96
+ * caller cannot change a per-repository run's name by accident.
97
+ */
98
+ workflowRepoIdentifier?: string;
90
99
  jobNames: string[];
91
100
  installationId?: number;
92
101
  /** Routing key for per-app credential lookup (e.g., "github:12345"). */
@@ -105,6 +114,8 @@ interface SetBuildPendingOptions {
105
114
  repo: string;
106
115
  sha: string;
107
116
  workflowName: string;
117
+ /** See {@link SetPendingOptions.workflowRepoIdentifier}. */
118
+ workflowRepoIdentifier?: string;
108
119
  installationId?: number;
109
120
  /** Routing key for per-app credential lookup (e.g., "github:12345"). */
110
121
  routingKey?: string;
@@ -122,6 +133,8 @@ interface SetBuildCompleteOptions {
122
133
  repo: string;
123
134
  sha: string;
124
135
  workflowName: string;
136
+ /** See {@link SetPendingOptions.workflowRepoIdentifier}. */
137
+ workflowRepoIdentifier?: string;
125
138
  status: TerminalJobStatus;
126
139
  installationId?: number;
127
140
  /** Routing key for per-app credential lookup (e.g., "github:12345"). */
@@ -141,6 +154,8 @@ interface UpdateJobStatusOptions {
141
154
  repo: string;
142
155
  sha: string;
143
156
  workflowName: string;
157
+ /** See {@link SetPendingOptions.workflowRepoIdentifier}. */
158
+ workflowRepoIdentifier?: string;
144
159
  jobName: string;
145
160
  state: TerminalJobStatus;
146
161
  installationId?: number;
@@ -157,6 +172,15 @@ interface UpdateJobStatusOptions {
157
172
  runIdForLogs?: string;
158
173
  /** Job ID for StepLogBuffer lookup. */
159
174
  jobId?: string;
175
+ /**
176
+ * The run's resolved trust tier and lock-file branch. When the tier is
177
+ * anything other than `trusted`, the completion summary leads with the
178
+ * reduced-privilege note so a contributor reading a failed job on a fork pull
179
+ * request can see which parts of the build environment the run did not have.
180
+ * Both absent for a run whose trust never resolved.
181
+ */
182
+ trustTier?: string;
183
+ lockFileSource?: string;
160
184
  }
161
185
  /**
162
186
  * Options for updateWorkflowStatus.
@@ -167,6 +191,8 @@ interface UpdateWorkflowStatusOptions {
167
191
  repo: string;
168
192
  sha: string;
169
193
  workflowName: string;
194
+ /** See {@link SetPendingOptions.workflowRepoIdentifier}. */
195
+ workflowRepoIdentifier?: string;
170
196
  overallStatus: TerminalJobStatus;
171
197
  installationId?: number;
172
198
  /** Routing key for per-app credential lookup (e.g., "github:12345"). */
@@ -176,6 +202,43 @@ interface UpdateWorkflowStatusOptions {
176
202
  requestId?: string;
177
203
  /** Explicit runId for trace context (falls back to AsyncLocalStorage context). */
178
204
  runId?: string;
205
+ /**
206
+ * The run's resolved trust tier and lock-file branch, same contract as
207
+ * {@link UpdateJobStatusOptions}. The roll-up check carries the note too: a
208
+ * contributor who reads only `kici/<workflow>` — the one branch protection
209
+ * usually requires — would otherwise see a run fail with no explanation.
210
+ */
211
+ trustTier?: string;
212
+ lockFileSource?: string;
213
+ }
214
+ /**
215
+ * Options for completeUndispatchedCheckRuns.
216
+ */
217
+ export interface CompleteUndispatchedCheckRunsOptions {
218
+ provider: string;
219
+ owner: string;
220
+ repo: string;
221
+ sha: string;
222
+ workflowName: string;
223
+ /** See {@link SetPendingOptions.workflowRepoIdentifier}. */
224
+ workflowRepoIdentifier?: string;
225
+ /**
226
+ * The job names `setPending` / `setPendingAwait` created a check run for.
227
+ * Pass the same list that call used, or the names will not match the check
228
+ * runs on the commit.
229
+ */
230
+ jobNames: string[];
231
+ installationId?: number;
232
+ /** Routing key for per-app credential lookup (e.g., "github:12345"). */
233
+ routingKey?: string;
234
+ /** Explicit requestId for trace context (falls back to AsyncLocalStorage context). */
235
+ requestId?: string;
236
+ /** Explicit runId for trace context (falls back to AsyncLocalStorage context). */
237
+ runId?: string;
238
+ /** The conclusion to complete each check run with. */
239
+ conclusion: CheckRunConclusion;
240
+ /** The check-run output body. The title stays the standard `KiCI: <label>` form. */
241
+ summary: string;
179
242
  }
180
243
  /**
181
244
  * Options for updateStepProgress.
@@ -186,6 +249,8 @@ interface UpdateStepProgressOptions {
186
249
  repo: string;
187
250
  sha: string;
188
251
  workflowName: string;
252
+ /** See {@link SetPendingOptions.workflowRepoIdentifier}. */
253
+ workflowRepoIdentifier?: string;
189
254
  jobName: string;
190
255
  stepIndex: number;
191
256
  stepName: string;
@@ -257,7 +322,26 @@ export declare class CheckRunReporter {
257
322
  * still find every key for a runId at cleanup time.
258
323
  */
259
324
  private readonly runIdToKeys;
325
+ /**
326
+ * Per-key serialization of GitHub check-run PATCHes. Two updates for one
327
+ * check run must never be in flight at GitHub simultaneously: the terminal
328
+ * `completed` write and an earlier `in_progress` write race last-write-wins,
329
+ * and if the `in_progress` PATCH lands second it reopens the check run to the
330
+ * permanently-unresolved `{ status: in_progress, conclusion: <terminal> }`
331
+ * state. The `terminalSent` re-check inside `updateCheckRun` cannot help once
332
+ * an `in_progress` PATCH has passed it and is awaiting the network — it is a
333
+ * check-then-await. Chaining every PATCH for a key through this map makes the
334
+ * re-check and the PATCH atomic relative to the completion write.
335
+ */
336
+ private readonly updateLocks;
260
337
  constructor(deps: CheckRunReporterDeps);
338
+ /**
339
+ * Run `fn` after every previously-queued check-run PATCH for `key` has
340
+ * settled, so all updates to one check run execute strictly in order. The
341
+ * chain swallows prior errors (each is surfaced to its own caller) so one
342
+ * failed PATCH does not wedge the key.
343
+ */
344
+ private runUpdateExclusive;
261
345
  /**
262
346
  * Update the provider registry used for per-routing-key credential lookup.
263
347
  * Called after config reload when the provider registry is rebuilt.
@@ -301,6 +385,33 @@ export declare class CheckRunReporter {
301
385
  * Fire-and-forget: errors are logged but don't block the pipeline.
302
386
  */
303
387
  updateWorkflowStatus(opts: UpdateWorkflowStatusOptions): void;
388
+ /**
389
+ * Complete the queued check runs of a workflow that never dispatched a job.
390
+ *
391
+ * `setPendingAwait` creates `kici/<workflow>` and one
392
+ * `kici/<workflow>/job/<name>` per static job before the pipeline knows
393
+ * whether the workflow will run. When the workflow then ends without
394
+ * dispatching one — a hold rejected or expired, or any pre-dispatch init
395
+ * failure — none of these names ever reaches a job or run record, so
396
+ * `updateJobStatus` and `updateWorkflowStatus` are never called for them, and
397
+ * `doCleanupStaleCheckRuns` skips them because that sweep only updates check
398
+ * runs whose status is `in_progress`. They stay `queued` on the commit, which
399
+ * on a pull request reads as a check that never finishes and blocks branch
400
+ * protection.
401
+ *
402
+ * Completes only the check runs this reporter can resolve an id for (L1 cache
403
+ * or the `check_run_tracking` row), and skips a key already latched terminal.
404
+ *
405
+ * The build check `kici/<workflow>/setup` is deliberately not in the set,
406
+ * because that latch does not reliably cover it. `setBuildComplete` stamps
407
+ * `terminal_sent_at` on the row but adds nothing to the in-process
408
+ * `terminalSent` set, and `resolveCheckRunId` returns on an L1 hit without
409
+ * reading the row — so a build check this process completed a moment ago
410
+ * resolves with no latch, and completing it again would overwrite a real
411
+ * build conclusion. An unreadable row is the harmless direction: it yields
412
+ * no id and the name is skipped.
413
+ */
414
+ completeUndispatchedCheckRuns(opts: CompleteUndispatchedCheckRunsOptions): Promise<void>;
304
415
  /**
305
416
  * Clean up stale check runs left by a dead orchestrator.
306
417
  *
@@ -321,6 +432,8 @@ export declare class CheckRunReporter {
321
432
  repo: string;
322
433
  sha: string;
323
434
  workflowName: string;
435
+ /** See {@link SetPendingOptions.workflowRepoIdentifier}. */
436
+ workflowRepoIdentifier?: string;
324
437
  jobNames: string[];
325
438
  }): void;
326
439
  /**
@@ -337,7 +450,8 @@ export declare class CheckRunReporter {
337
450
  * Separate from execution check runs so users see build progress independently.
338
451
  * Fire-and-forget: errors are logged but don't block the pipeline.
339
452
  *
340
- * Check run name format: kici/{workflowName}/setup
453
+ * Check run name format: kici/{workflowName}/setup — prefixed with the
454
+ * defining repository for a cross-repository global run, see `workflowLabel`.
341
455
  */
342
456
  setBuildPending(opts: SetBuildPendingOptions): void;
343
457
  /**
@@ -390,6 +504,13 @@ export declare class CheckRunReporter {
390
504
  * Read-through helper: look up a check-run ID. Checks L1 first, falls
391
505
  * through to the store on miss, caches the result on hit. Returns
392
506
  * undefined when neither layer has the ID — the caller logs + skips.
507
+ *
508
+ * The same row also rehydrates the `terminalSent` latch. Both L1 entries are
509
+ * dropped together by `cleanupRun`, and only one of them used to come back:
510
+ * the id reloaded from here while the latch did not, which is exactly the
511
+ * pair that lets a late step-progress update resolve a check run and PATCH
512
+ * `status: in_progress` over its completion. Reading `terminal_sent_at` off
513
+ * the row this query already selects costs nothing and closes that gap.
393
514
  */
394
515
  private resolveCheckRunId;
395
516
  /**
@@ -425,6 +546,28 @@ export declare class CheckRunReporter {
425
546
  */
426
547
  private appendTraceIds;
427
548
  private checkRunKey;
549
+ /**
550
+ * The workflow label every check-run name and title is built from.
551
+ *
552
+ * A check run's identity is `(owner, repo, sha, check name)` — on the
553
+ * provider, in `check_run_tracking`'s primary key, and in this class's L1
554
+ * keys. There is no run id anywhere in it. Two per-repository runs cannot
555
+ * collide on that identity, because one lock file cannot define a workflow
556
+ * name twice. An organization-wide workflow can: it is defined in ANOTHER
557
+ * repository, so its name is free to equal a workflow name of the repository
558
+ * it was dispatched against, and on the same commit the two runs then resolve
559
+ * to one check run. The global run's conclusion would complete the acted-on
560
+ * repository's check — the signal branch protection reads — and point its
561
+ * `details_url` at the wrong run, while `cleanupRun` would evict the other
562
+ * run's check-run id and terminal latch on prune.
563
+ *
564
+ * Qualifying the label with the defining repository keeps them apart. The
565
+ * "differs from the acted-on repository" narrowing lives here rather than at
566
+ * the call sites, so a per-repository run's name — which is customer-visible
567
+ * and may sit in a branch-protection required-check list — cannot move
568
+ * because a caller passed the field where it did not apply.
569
+ */
570
+ private workflowLabel;
428
571
  /**
429
572
  * Build the `details_url` for a check run pointing at the dashboard's
430
573
  * public-alias resolver (`/r/orgs/<oal_xxx>/runs/<runId>`). Returns
@@ -468,6 +611,7 @@ export declare class CheckRunReporter {
468
611
  * Supports both 'completed' and 'in_progress' statuses.
469
612
  */
470
613
  private updateCheckRun;
614
+ private updateCheckRunLocked;
471
615
  }
472
616
  /**
473
617
  * Build a meaningful failure description from agent job status data.