@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
@@ -18,6 +18,7 @@ import type { WebhookInfo } from '../webhook/handler.js';
18
18
  import type { DedupCache } from '../webhook/dedup.js';
19
19
  import type { ProviderRegistry, ProviderBundle } from '../provider-registry.js';
20
20
  import type { LockFileCache } from '../lockfile-cache.js';
21
+ import type { ContentRequirementsCache } from '../content-requirements-cache.js';
21
22
  import type { Dispatcher } from '../agent/dispatcher.js';
22
23
  import type { PlatformClient } from '../ws/platform-client.js';
23
24
  import type { QueuedJobInput } from '../queue/job-queue.js';
@@ -27,20 +28,22 @@ import type { DepCache } from '../cache/index.js';
27
28
  import type { PendingBuildTracker } from '../cache/index.js';
28
29
  import type { PendingInitTracker } from '../cache/pending-inits.js';
29
30
  import type { PendingDynamicTracker } from '../cache/pending-dynamics.js';
31
+ import type { PendingGlobalEvalTracker } from '../cache/pending-global-evals.js';
32
+ import type { GlobalEvalRoundCache } from '../cache/global-eval-round-cache.js';
30
33
  import type { CheckRunReporter } from '../reporting/check-run-reporter.js';
31
34
  import type { ExecutionTracker } from '../reporting/execution-tracker.js';
32
35
  import type { AgentRegistry } from '../agent/registry.js';
33
36
  import type { HostRosterStore } from '../agent/host-roster.js';
34
37
  import type { RunCoordinator } from '../cluster/coordinator.js';
35
38
  import type { ClusterSettingsReader } from '../cluster/cluster-settings-reader.js';
36
- import type { TeamMembershipLookup } from '../approvals/team-membership-lookup.js';
37
39
  import type { LogStorage } from '../reporting/log-storage.js';
40
+ import type { LogWriter } from '../reporting/log-writer.js';
38
41
  import type { SecretResolverApi } from '../secrets/secret-resolver.js';
39
- import type { ContributorCache } from '../security/contributor-cache.js';
40
42
  import type { AccessLogWriter } from '../audit/access-log.js';
41
43
  import type { LockFile as FullLockFile, LockWorkflow, SimulatedEvent, WebhookNormalizer } from '@kici-dev/engine';
42
44
  import { LockFileParseError } from '@kici-dev/engine';
43
45
  import type { EventRouter } from '../events/event-router.js';
46
+ import type { InvokeGateDeps, InvokeGateParams } from './invoke-gate.js';
44
47
  import type { RegistrationStore } from '../registration/registration-store.js';
45
48
  import type { RegistrationIndex } from '../registration/registration-index.js';
46
49
  import type { CronScheduler } from '../cron/cron-scheduler.js';
@@ -50,10 +53,12 @@ import { EventLogSource } from '@kici-dev/engine';
50
53
  import type { LockJob } from '@kici-dev/engine';
51
54
  import type { ContextStore } from '../contexts/context-store.js';
52
55
  import type { VariableStore } from '../contexts/variable-store.js';
53
- import type { TrustResolver, IdentityLink, PermissionLevel } from '../security/trust-resolver.js';
56
+ import type { IdentityLink, PermissionLevel } from '../security/identity-link.js';
54
57
  import { type HeldRunStore } from '../contexts/held-runs.js';
58
+ import { type Context as EngineContext } from '@kici-dev/engine';
55
59
  import type { OrchestratorMode, WorkflowDecision } from '@kici-dev/engine';
56
60
  import type { TrustPolicyStore } from '../security/trust-policy-store.js';
61
+ import type { TrustDirectoryStore } from '../security/trust-directory-store.js';
57
62
  /**
58
63
  * Pending dispatch context for jobs gated by the needs scheduler.
59
64
  * Keyed by `${runId}:${jobName}`. Populated at processWebhook time for all
@@ -65,6 +70,12 @@ import type { TrustPolicyStore } from '../security/trust-policy-store.js';
65
70
  interface PendingJobContext {
66
71
  jobInput: QueuedJobInput;
67
72
  runsOnLabels: string[];
73
+ /**
74
+ * When set, this pending job is an invoke gate: on release it summons the
75
+ * source repo's subscribers (`runInvokeGate`) instead of dispatching `jobInput`
76
+ * to an agent.
77
+ */
78
+ invoke?: InvokeGateParams;
68
79
  }
69
80
  /**
70
81
  * Register an eval gate and return a promise that resolves when the scheduler
@@ -228,14 +239,31 @@ export declare function buildTriggerEvent(event: string, action: string | null |
228
239
  */
229
240
  export declare function extractInboundRepoIdentifier(payload: unknown): string | null;
230
241
  /**
231
- * Extract the first line of the commit message from a webhook payload.
232
- * Handles push (head_commit.message) and PR (pull_request.title) events.
242
+ * Extract the first line of the commit message from a webhook payload, for run
243
+ * display. Handles push (head_commit.message), PR (pull_request.title) and
244
+ * issue_comment (issue.title) events.
245
+ *
246
+ * The Tier-0 `commitMessage` trigger filter deliberately reads a DIFFERENT text
247
+ * (the full message, and PR title + body) — see `githubFilterText`.
233
248
  */
234
249
  export declare function extractCommitMessage(event: string, payload: unknown): string | undefined;
235
250
  /**
236
251
  * Build a human-readable summary for a security hold check.
252
+ *
253
+ * `tier` is the resolved trust tier, or undefined when trust never resolved;
254
+ * the summary displays `unknown` for the latter. The caller passes the resolved
255
+ * value rather than forging `unknown` for an absent one, so this summary and
256
+ * `buildReducedPrivilegeNote` (`../security/reduced-privilege-note.ts`) cannot
257
+ * disagree about which case a run is in.
258
+ *
259
+ * Carries no reduced-privilege note of its own: the note is appended by the
260
+ * call site, because the two summaries this builds are read on checks with
261
+ * different fates. The trust-policy HOLD stores a resume context, so
262
+ * `/kici approve` replays its dispatch and its call site
263
+ * (`holdRunForSecurityPolicy`) appends the note; the trust-policy REJECTION
264
+ * never runs, so `buildSecurityRejectionSummary`'s call site appends nothing.
237
265
  */
238
- export declare function buildSecurityHoldSummary(reason: string, tier: string, contributorUsername?: string): string;
266
+ export declare function buildSecurityHoldSummary(reason: string, tier: string | undefined, contributorUsername?: string): string;
239
267
  /**
240
268
  * Build the failure check-run description for a run the org trust policy
241
269
  * REJECTED.
@@ -244,8 +272,13 @@ export declare function buildSecurityHoldSummary(reason: string, tier: string, c
244
272
  * `held_runs` row, so telling the contributor to seek "approval from a user
245
273
  * with ci_trust:write or higher" points at a queue the run will never appear
246
274
  * in. Only an org policy change can unblock it.
275
+ *
276
+ * Carries no reduced-privilege note for the same reason: the summary's own next
277
+ * line says the run cannot be approved, so a posture note beside it would read
278
+ * as "it ran with reduced privileges and that is why it failed" for a run that
279
+ * never dispatched at all.
247
280
  */
248
- export declare function buildSecurityRejectionSummary(reason: string, message: string, tier: string, contributorUsername?: string): string;
281
+ export declare function buildSecurityRejectionSummary(reason: string, message: string, tier: string | undefined, contributorUsername?: string): string;
249
282
  /**
250
283
  * Build the pending check-run description for a job/workflow approval hold,
251
284
  * naming the clauses an approver must satisfy. `{team:X}` renders as
@@ -257,6 +290,19 @@ export declare function summarizeApprovalClauses(clauses: ReadonlyArray<{
257
290
  } | {
258
291
  user: string;
259
292
  }>): string;
293
+ /**
294
+ * The line appended to an approval hold's check description when a security
295
+ * trust hold gates the SAME job.
296
+ *
297
+ * Both holds must be answered before the job runs, and the check run carries one
298
+ * description. Without this line the check names only the approval clauses — so
299
+ * the named approver approves, nothing runs, and the text does not change: a
300
+ * contributor is left with a satisfied requirement, no statement of what is
301
+ * still outstanding, and no idea that a different permission clears it. Naming
302
+ * the second gate and how it is released is the minimum that makes the check
303
+ * honest.
304
+ */
305
+ export declare const SECURITY_HOLD_ALSO_GATES_NOTE: string;
260
306
  /**
261
307
  * Dependencies for the processing pipeline.
262
308
  * All injected for testability. Fully provider-agnostic.
@@ -267,7 +313,31 @@ export declare function summarizeApprovalClauses(clauses: ReadonlyArray<{
267
313
  export interface ProcessingDeps {
268
314
  dedup: DedupCache;
269
315
  providerRegistry: ProviderRegistry;
316
+ /**
317
+ * Re-register the provider bundle for a generic routing key from server
318
+ * truth, returning true when a bundle is now present.
319
+ *
320
+ * The registry is an in-memory CACHE of `generic_webhook_sources`, populated
321
+ * by three independent paths (startup enumeration, the admin write handler,
322
+ * and the LISTEN/NOTIFY drain). None of them can guarantee the entry is
323
+ * present for a delivery that arrives at an arbitrary moment, and a miss is
324
+ * not benign: `getByRoutingKey` used to substitute an unrelated `generic:`
325
+ * bundle whose normalizer reports "this payload has no repository", so the
326
+ * delivery was discarded with nothing above `debug` to say why.
327
+ *
328
+ * Optional — hand-built test deps and wirings with no generic-source manager
329
+ * keep the previous behaviour (a miss stays a miss, reported loudly).
330
+ */
331
+ ensureProviderBundle?: (routingKey: string) => Promise<boolean>;
270
332
  lockFileCache: LockFileCache;
333
+ /**
334
+ * Cache for the Tier-1 `requires` static content filter (source-file bytes at
335
+ * a ref, keyed by (repo, sha, path)). Optional so hand-built test deps and
336
+ * independent deployments that never use `requires` keep working; when absent,
337
+ * a candidate carrying `requires` is dropped fail-visible rather than
338
+ * dispatched unfiltered (see content-filter.ts).
339
+ */
340
+ contentRequirementsCache?: ContentRequirementsCache;
271
341
  dispatcher: Dispatcher;
272
342
  /** Null/undefined in Independent mode. send() buffers when disconnected. */
273
343
  platformClient?: PlatformClient;
@@ -285,6 +355,21 @@ export interface ProcessingDeps {
285
355
  pendingInits?: PendingInitTracker;
286
356
  /** Pending dynamic tracker -- waits for agents to evaluate DynamicJobFn and return generated LockJob[]. */
287
357
  pendingDynamics?: PendingDynamicTracker;
358
+ /** Pending global-eval tracker -- waits for the pre-run round that decides which global workflows apply. */
359
+ pendingGlobalEvals?: PendingGlobalEvalTracker;
360
+ /** Round-result cache for the pre-run global eval round. Optional -- if not set, every round re-runs. */
361
+ globalEvalCache?: GlobalEvalRoundCache;
362
+ /** Cluster default for the whole-round budget handed to the eval agent (ms).
363
+ * The live per-cluster override is `cluster_settings.global_eval_round_timeout_ms`. */
364
+ globalEvalRoundTimeoutMs?: number;
365
+ /** Cluster default for the per-candidate budget handed to the eval agent (ms).
366
+ * The live per-cluster override is `cluster_settings.global_eval_candidate_timeout_ms`. */
367
+ globalEvalCandidateTimeoutMs?: number;
368
+ /** Cluster default for the orchestrator's own ceiling on awaiting a round (ms).
369
+ * Unlike the two budgets above, this one is enforced here rather than by the
370
+ * agent, so it also bounds a round no agent ever picked up. The live
371
+ * per-cluster override is `cluster_settings.global_eval_wait_timeout_ms`. */
372
+ globalEvalWaitTimeoutMs?: number;
288
373
  /** Commit status reporter for setting pending/success/failure/error on commits. Optional. */
289
374
  checkRunReporter?: CheckRunReporter;
290
375
  /** Execution tracker for DB persistence. Optional -- if not set, execution tracking is skipped. */
@@ -309,6 +394,12 @@ export interface ProcessingDeps {
309
394
  } | undefined>) => void;
310
395
  /** Event router for registering lock file event subscriptions. Optional -- if not set, event routing is inactive. */
311
396
  eventRouter?: EventRouter;
397
+ /**
398
+ * Invoke-gate dependencies (summon callback + chain-depth bound). Optional --
399
+ * when absent an invoke gate cannot summon (it fails loudly rather than
400
+ * silently reaching an agent). Built at the composition root.
401
+ */
402
+ invokeGateDeps?: InvokeGateDeps;
312
403
  /** Registration store for persisting workflow registrations. Optional -- if not set, registration is skipped. */
313
404
  registrationStore?: RegistrationStore;
314
405
  /** Registration index for in-memory lookup. Optional -- if not set, registration is skipped. */
@@ -321,14 +412,19 @@ export interface ProcessingDeps {
321
412
  secretKey?: string;
322
413
  /** Log storage backend for persisting webhook payloads. Optional -- if not set, payload storage is skipped. */
323
414
  logStorage?: LogStorage;
415
+ /**
416
+ * Durable step-log writer. Optional -- when present, the deferred-init path
417
+ * uses it to surface a post-init env warning on the run's log stream so a
418
+ * blocking `kici run remote` test run prints it (the accept response has
419
+ * already been returned by the time the init round resolves).
420
+ */
421
+ logWriter?: Pick<LogWriter, 'appendChunk' | 'drain'>;
324
422
  /** Context store for looking up deployment contexts. Optional -- if not set, context features are inactive. */
325
423
  contextStore?: ContextStore;
326
424
  /** Variable store for resolving context variables. Optional -- if not set, context vars are not merged. */
327
425
  variableStore?: VariableStore;
328
426
  /** Held run store for persisting protection rule holds. Optional -- if not set, holds are not persisted. */
329
427
  heldRunStore?: HeldRunStore;
330
- /** Trust resolver for determining contributor trust tiers. Optional -- if not set, trust resolution is skipped. */
331
- trustResolver?: TrustResolver;
332
428
  /**
333
429
  * Cache of the Platform-owned org trust policy. Read per PR event by the
334
430
  * trust-policy gate. Optional so existing tests and independent deployments
@@ -343,16 +439,26 @@ export interface ProcessingDeps {
343
439
  * so a hand-built deps object never accidentally opens the gate.
344
440
  */
345
441
  orchestratorMode?: OrchestratorMode;
346
- /** Identity links pushed from Platform for trust resolution. Optional -- defaults to empty. */
442
+ /** Identity links pushed from Platform, read by the comment-approval path. Optional -- defaults to empty. */
347
443
  identityLinks?: IdentityLink[];
348
444
  /** ci_trust permission levels per user ID from Platform push. Optional -- defaults to empty. */
349
445
  orgMemberPermissions?: Map<string, PermissionLevel>;
350
446
  /**
351
- * Team-membership lookup pushed from the Platform (team name member set).
352
- * Consumed by the approval resolver to satisfy `{team}` approval clauses.
353
- * Optional -- defaults to "no teams".
447
+ * Persisted approval directory, read at `/kici approve` time when neither
448
+ * `identityLinks` nor `orgMemberPermissions` was supplied.
449
+ *
450
+ * The two fields above are the Platform-push path: `server.ts` keeps them in
451
+ * memory and refreshes them on every `trust_policy.update`, so it always
452
+ * supplies both and this store is never consulted there. Every other
453
+ * assembly of these deps — the direct-ingress pipeline in `app.ts`, which is
454
+ * the ONLY one an independent orchestrator has — supplies neither, and
455
+ * without this store its approval path would resolve an empty directory and
456
+ * refuse every commenter forever.
457
+ *
458
+ * Optional so a hand-built deps object keeps working; the read is skipped
459
+ * when it is absent.
354
460
  */
355
- teamMembershipLookup?: TeamMembershipLookup;
461
+ trustDirectoryStore?: TrustDirectoryStore;
356
462
  /** Global workflow policy for org-level permission enforcement. Optional -- if not set, global workflows are unrestricted. */
357
463
  globalWorkflowPolicy?: GlobalWorkflowPolicy;
358
464
  /** Inbound webhook delivery log writer. Optional -- if not set, deliveries are not persisted to event_log. */
@@ -361,11 +467,6 @@ export interface ProcessingDeps {
361
467
  * Used by the eventLog writer to populate the source column. Defaults to
362
468
  * 'direct' when omitted (independent / direct paths). */
363
469
  eventLogSource?: EventLogSource;
364
- /** Contributor permission cache. Optional -- if not set, membership-webhook
365
- * invalidations silently no-op. In platform/hybrid mode the singleton is
366
- * created in server.ts and threaded through both the Platform-relay WS
367
- * path and the generic webhook HTTP path. */
368
- contributorCache?: ContributorCache;
369
470
  /** Access-log writer for the orchestrator audit stream. Optional -- if not
370
471
  * set, hold-creation audit rows (`held_run.request`) are skipped. */
371
472
  accessLogWriter?: AccessLogWriter;
@@ -389,6 +490,120 @@ export interface ProcessingDeps {
389
490
  */
390
491
  export declare function anyTriggerHasPathPatterns(workflows: LockWorkflow[]): boolean;
391
492
  export { processWebhook } from './process-webhook.js';
493
+ /** How many times {@link hasPendingHold} reads before it gives up and refuses. */
494
+ export declare const PENDING_HOLD_READ_ATTEMPTS = 3;
495
+ /** Backoff between {@link hasPendingHold} attempts, multiplied by the attempt number. */
496
+ export declare const PENDING_HOLD_RETRY_BASE_MS = 25;
497
+ /**
498
+ * Is there a still-pending hold for this (run, job)?
499
+ *
500
+ * `held_runs.job_id` carries the expanded job NAME for job-scoped holds, which
501
+ * is the same key the pending dispatch context uses. A missing row, or any
502
+ * non-pending status, means nothing is gating the job.
503
+ *
504
+ * **Fails CLOSED** once the read has genuinely failed: a job whose hold state
505
+ * cannot be read is treated as held and left for the release path. This gate is
506
+ * the enforcement point for "every requirement answered" on a job carrying two
507
+ * holds — the reviewer row and the security row are written together and BOTH
508
+ * must leave `pending` before dispatch — so answering `false` on a read error
509
+ * dispatched a job with neither hold released and the approval boundary
510
+ * bypassed entirely. That is unrecoverable; the failure in the other direction
511
+ * is not.
512
+ *
513
+ * A refusal leaves the job pending rather than losing it: the context is not
514
+ * consumed (the check runs before the consume), every release path re-drives
515
+ * `dispatchReadyJob`, and the needs-scheduler recovery loop on the next start
516
+ * recomputes `needs_satisfied` for every non-terminal run and re-fires the
517
+ * ready jobs. So the worst case of a closed failure is a delay, against a
518
+ * silently bypassed approval for an open one.
519
+ *
520
+ * The retries are what keep that trade cheap. The realistic error here is a
521
+ * transient one — a deadlock, a statement timeout, a lost connection — and a
522
+ * single blip must not park a job until the next restart, so the read is
523
+ * attempted {@link PENDING_HOLD_READ_ATTEMPTS} times with a short linear
524
+ * backoff before the refusal stands.
525
+ */
526
+ export declare function hasPendingHold(db: Kysely<Database>, runId: string, jobName: string, opts?: {
527
+ attempts?: number;
528
+ retryBaseMs?: number;
529
+ }): Promise<boolean>;
530
+ /** Read attempts for the needs verdict, mirroring {@link PENDING_HOLD_READ_ATTEMPTS}. */
531
+ export declare const NEEDS_READ_ATTEMPTS = 3;
532
+ /** Linear backoff base between needs-verdict read attempts. */
533
+ export declare const NEEDS_RETRY_BASE_MS = 25;
534
+ /**
535
+ * Read whether a job's `needs` upstreams are satisfied, for the dispatch guard.
536
+ *
537
+ * Fails CLOSED, exactly as {@link hasPendingHold} does and for the same reason:
538
+ * an unreadable verdict must never be read as permission to dispatch. A closed
539
+ * failure returns `{ satisfied: false }`, which the guard treats as "upstream
540
+ * still pending" — the recoverable branch, since the start-up recovery loop
541
+ * recomputes `needs_satisfied` for every non-terminal run and re-fires its ready
542
+ * jobs, and the stale-run expiry sweep is the backstop.
543
+ *
544
+ * Reads only. It never writes `needs_satisfied` — the scheduler owns that claim.
545
+ */
546
+ export declare function readNeedsVerdict(db: Kysely<Database>, runId: string, jobName: string, opts?: {
547
+ attempts?: number;
548
+ retryBaseMs?: number;
549
+ }): Promise<{
550
+ satisfied: boolean;
551
+ action?: 'dispatch' | 'skip';
552
+ reason?: string;
553
+ }>;
554
+ /**
555
+ * The subset of a context row the ready-dispatch re-gate reads.
556
+ *
557
+ * `ContextStore.matchContext` returns the raw `contexts` row, so the field names
558
+ * are snake_case. `concurrency_strategy` and `hold_expiry_seconds` are optional
559
+ * because the release-path callers already in the tree narrow their closure to
560
+ * `concurrency_limit` alone; an absent strategy resolves to
561
+ * {@link DEFAULT_CONCURRENCY_STRATEGY} and an absent hold window to
562
+ * {@link DEFAULT_HOLD_EXPIRY_SECONDS}, which are also the columns' own defaults.
563
+ */
564
+ export interface ReadyDispatchContextRow {
565
+ id: string;
566
+ concurrency_limit: number | null;
567
+ concurrency_strategy?: string | null;
568
+ hold_expiry_seconds?: number | null;
569
+ }
570
+ /** Inputs the ready-dispatch concurrency re-gate needs. Absent = no re-gate. */
571
+ export interface ReadyDispatchGateDeps {
572
+ matchContext: (orgId: string, name: string) => Promise<ReadyDispatchContextRow | null>;
573
+ heldRunStore: Pick<HeldRunStore, 'create'>;
574
+ /**
575
+ * Audits each re-hold, mirroring the `held_run.request` row the dispatch-pass
576
+ * path writes for every hold it mints. Optional: a call site with no writer
577
+ * still gates, exactly as the dispatch path's own `accessLogWriter?.record`
578
+ * degrades.
579
+ */
580
+ accessLogWriter?: Pick<AccessLogWriter, 'record'>;
581
+ /** The routing key the audit row is attributed to, when the call site knows it. */
582
+ routingKey?: string | null;
583
+ }
584
+ /** What {@link resolveRunConcurrency} needs to evaluate the concurrency gate. */
585
+ export interface RunConcurrency {
586
+ orgId: string;
587
+ group: string;
588
+ contextId: string;
589
+ limit: number | null;
590
+ strategy: EngineContext['concurrencyStrategy'];
591
+ /**
592
+ * The context's own hold window, seconds. A queued hold this gate mints must
593
+ * expire on the same schedule as one the dispatch pass mints for the same
594
+ * context, or an operator's configured queue timeout applies to one path and
595
+ * not the other.
596
+ */
597
+ holdExpirySeconds: number;
598
+ }
599
+ /**
600
+ * Resolve a run's bound context to the inputs the concurrency gate needs.
601
+ *
602
+ * `null` means no concurrency constraint applies — the run has no bound context,
603
+ * or the context it names no longer exists. That is the common case, so the cost
604
+ * on the ready-dispatch path is one indexed lookup by `run_id`.
605
+ */
606
+ export declare function resolveRunConcurrency(db: Kysely<Database>, matchContext: ReadyDispatchGateDeps['matchContext'], runId: string): Promise<RunConcurrency | null>;
392
607
  /**
393
608
  * Dispatch a job that has become ready via the needs scheduler.
394
609
  *
@@ -397,20 +612,64 @@ export { processWebhook } from './process-webhook.js';
397
612
  * dispatches the job through the normal dispatcher path, and updates the
398
613
  * execution tracker with the real job ID.
399
614
  */
400
- export declare function dispatchReadyJob(runId: string, jobName: string, dispatcher: Dispatcher, executionTracker?: ExecutionTracker, coordinator?: RunCoordinator, db?: Kysely<Database>): Promise<void>;
615
+ export declare function dispatchReadyJob(runId: string, jobName: string, dispatcher: Dispatcher, executionTracker?: ExecutionTracker, coordinator?: RunCoordinator, db?: Kysely<Database>, invokeGateDeps?: InvokeGateDeps, gateDeps?: ReadyDispatchGateDeps): Promise<void>;
401
616
  /**
402
- * Check whether a webhook event is a push to the repository's default branch.
403
- * Used to trigger registration extraction for workflow event subscriptions.
617
+ * Read the repository's default branch out of a webhook payload.
404
618
  *
405
- * Resolution order for the default branch:
619
+ * Resolution order:
406
620
  * 1. `normalizer.extractDefaultBranch?(payload)` — provider-specific hook
407
621
  * (universal-git reads a JSONPath from the source's `payloadPaths.defaultBranch`).
408
622
  * 2. Fallback to `payload.repository.default_branch` — the GitHub-shaped
409
623
  * default that most forges mirror.
624
+ *
625
+ * `null` when neither source names one. Shared by `isDefaultBranchPush` (which
626
+ * compares it against the pushed branch) and the registration write path (which
627
+ * persists it, so a scheduled run can present it as its own branch).
628
+ */
629
+ export declare function extractDefaultBranch(payload: Record<string, unknown>, normalizer: WebhookNormalizer): string | null;
630
+ /**
631
+ * Check whether a webhook event is a push to the repository's default branch.
632
+ * Used to trigger registration extraction for workflow event subscriptions.
410
633
  */
411
634
  export declare function isDefaultBranchPush(info: WebhookInfo, event: SimulatedEvent, payload: Record<string, unknown>, normalizer: WebhookNormalizer): boolean;
635
+ /**
636
+ * Cap on the per-decision trace forwarded to the Platform.
637
+ *
638
+ * A fixed bound on a debug payload, not a behavior an operator tunes: the
639
+ * summary rides an `execution.event` and lands in a stored row, so an
640
+ * essay-length trace from a workflow with hundreds of triggers must not be able
641
+ * to grow either without limit.
642
+ */
643
+ export declare const DECISION_TRACE_MAX_CHECKS = 50;
412
644
  /**
413
645
  * Create a serializable summary of a workflow decision for Platform forwarding.
646
+ *
647
+ * Carries the individual checks, capped, so the dashboard can answer "why did
648
+ * this workflow not fire" from the delivery alone. `checksCount` stays the
649
+ * untruncated total, and `checksTruncated` marks a trace the cap shortened.
414
650
  */
415
651
  export declare function summarizeDecision(decision: WorkflowDecision): Record<string, unknown>;
652
+ /**
653
+ * Byte budget for the whole forwarded trace, across every workflow on the
654
+ * delivery.
655
+ *
656
+ * The per-decision check cap and the per-field text clamp bound one entry; this
657
+ * bounds the frame. One event is evaluated against every workflow in the lock
658
+ * file plus every organization-wide registration, so a repository with a
659
+ * hundred comment-triggered workflows multiplies a bounded entry into an
660
+ * unbounded message. A frame past the Platform's WebSocket payload ceiling
661
+ * closes the orchestrator's connection, stalling every delivery for that
662
+ * organization until it reconnects — so the budget sits well under the
663
+ * Platform's own storage guard, which is then a backstop rather than the only
664
+ * limit.
665
+ */
666
+ export declare const DECISION_TRACE_MAX_BYTES = 131072;
667
+ /**
668
+ * Bound the serialized trace, replacing whatever did not fit with a marker.
669
+ *
670
+ * Truncating rather than dropping keeps the delivery's answer to "why did my
671
+ * workflow not fire" partially readable, and says out loud that the rest was
672
+ * dropped.
673
+ */
674
+ export declare function capDecisionSummaries(summaries: readonly Record<string, unknown>[], maxBytes?: number): Record<string, unknown>[];
416
675
  //# sourceMappingURL=processor.d.ts.map
@@ -27,6 +27,7 @@ import type { SourceCache } from '../cache/index.js';
27
27
  import type { BuildCoordinator } from '../cache/index.js';
28
28
  import type { DepCache } from '../cache/index.js';
29
29
  import type { PendingBuildTracker } from '../cache/index.js';
30
+ import type { ProcessingDeps } from './processor.js';
30
31
  /**
31
32
  * Thrown when a rerun is attempted on a run whose row is absent from PG AND
32
33
  * the cold-store replay path failed or is unavailable (chunk missing,
@@ -65,6 +66,17 @@ export interface RerunDeps {
65
66
  * cold-store wired up.
66
67
  */
67
68
  coldStore: ColdStore | null;
69
+ /**
70
+ * The live webhook-processing bag, assembled on demand.
71
+ *
72
+ * Only the re-run of a failed global evaluation round needs it: that re-run
73
+ * re-drives the organization-wide pass, which reaches deps an ordinary
74
+ * workflow re-run never touches (the registration index, the policy reader,
75
+ * the pending-eval tracker). Supplied by the entry point that already
76
+ * assembles the bag for the inbound webhook path, so the two cannot drift.
77
+ * Absent means round re-runs are not available on this deployment.
78
+ */
79
+ processingDeps?: (() => ProcessingDeps) | null;
68
80
  }
69
81
  export declare function handleRerun(originalRunId: string, triggeredBy: string | null, triggeredByAgentLabel: string | null, deps: RerunDeps,
70
82
  /**
@@ -83,4 +95,13 @@ requestId: string,
83
95
  routingKeyHint?: string): Promise<{
84
96
  newRunId: string;
85
97
  }>;
98
+ /**
99
+ * Settle every detached re-evaluation started so far.
100
+ *
101
+ * Nothing in a running orchestrator calls this — the re-evaluation is detached
102
+ * precisely so no request waits on it. It exists so a caller that needs the
103
+ * work to have finished (a test asserting on the pass) can wait for it
104
+ * deterministically instead of racing the microtask queue.
105
+ */
106
+ export declare function settlePendingRoundReevaluations(): Promise<void>;
86
107
  //# sourceMappingURL=rerun.d.ts.map
@@ -4,23 +4,56 @@ import type { ProcessingDeps } from './processor.js';
4
4
  import type { ReleaseSignal } from '../contexts/held-runs.js';
5
5
  import { type WorkflowDispatchContext } from './dispatch-matched-workflow.js';
6
6
  import { type SerializableWorkflowDispatchInputs } from './pending-workflow-context.js';
7
+ import { type SecurityCheckHold } from './security-hold-check.js';
7
8
  /**
8
9
  * Rebuild a live `WorkflowDispatchContext` from the persisted serializable
9
10
  * inputs by re-attaching the orchestrator's live `deps` and reconstructing the
10
11
  * provider `bundle` from the live registry (keyed by the stored routing key).
11
12
  * Returns null when the provider bundle can no longer be resolved.
13
+ *
14
+ * The key it looks the bundle up by is `effectiveRoutingKey ?? info.routingKey`
15
+ * — the post-overlay key, the same one `setupDispatchContext` builds
16
+ * `setup.info` from and the same one the run row records. `info.routingKey` is
17
+ * the INBOUND key and never carries the overlay, so on a cross-source or
18
+ * fallback-resolved dispatch it names the source the event arrived on rather
19
+ * than the source the workflow belongs to: the resumed run would get the wrong
20
+ * app, the wrong credentials, and a check poster writing to the wrong place.
12
21
  */
13
22
  export declare function rebuildWorkflowDispatchContext(inputs: SerializableWorkflowDispatchInputs, deps: ProcessingDeps): WorkflowDispatchContext | null;
14
23
  /**
15
- * Resume a released workflow install-gate hold. Loads the pending context,
16
- * rebuilds the dispatch context, and re-dispatches with the gate skipped. On a
17
- * lost pending context (or unresolvable provider bundle) the run is failed
18
- * loudly rather than silently dropped.
24
+ * Resume a released workflow-scoped hold. Loads the pending context, rebuilds
25
+ * the dispatch context, and re-dispatches. On a lost pending context (or
26
+ * unresolvable provider bundle) the run is failed loudly rather than silently
27
+ * dropped.
19
28
  */
20
29
  export declare function resumeWorkflow(signal: ReleaseSignal, deps: ProcessingDeps, db: Kysely<Database> | undefined): Promise<void>;
21
30
  /**
22
- * Cancel a rejected workflow install-gate hold: mark the run cancelled and drop
23
- * the pending context.
31
+ * Cancel a rejected workflow-scoped hold (install gate or trust policy): mark
32
+ * the run cancelled, complete the check runs the dispatch already posted, and
33
+ * drop the pending context.
34
+ *
35
+ * The check runs are completed `cancelled`, matching the run row this writes —
36
+ * `cancelHeldRun` sets `execution_runs.status` to `cancelled` with failure class
37
+ * `cancelled`. Without this the workflow and per-job checks stay `queued` on the
38
+ * commit for a run that will never start; see `completeUndispatchedHoldChecks`.
39
+ * It runs before the delete because the pending context is what the check-run
40
+ * names are derived from.
41
+ *
42
+ * The hold row decides whether the `KiCI Security` check is terminalized too:
43
+ * the trust policy's PR-wide hold and the SDK's workflow-level `requireApproval`
44
+ * each posted one pending, while the install gate posted none and must not have
45
+ * one fabricated. That is `postedPendingSecurityCheck`'s job — see it for the
46
+ * per-shape derivation, and for why `queue_type` cannot make the distinction.
47
+ *
48
+ * This is the single writer of that check for a rejection, on both surfaces the
49
+ * shared applier serves: the dashboard / CLI / MCP reject reaches it through
50
+ * `applyDecision`'s `onWorkflowReject`, and `/kici reject` through the comment
51
+ * handler's. Both therefore render identically on the pull request, and the
52
+ * summary below is the one both check families carry. The returned boolean says
53
+ * whether a security check was actually WRITTEN, so a caller suppresses its own
54
+ * post on the strength of a write rather than of a delegate resolving —
55
+ * a rejection this declines to report (an install gate, or a commit whose other
56
+ * holds are still pending) leaves the caller free to decide for itself.
24
57
  */
25
- export declare function rejectWorkflow(runId: string, deps: ProcessingDeps, db: Kysely<Database> | undefined, reason: string): Promise<void>;
58
+ export declare function rejectWorkflow(hold: SecurityCheckHold, deps: ProcessingDeps, db: Kysely<Database> | undefined, reason: string): Promise<boolean>;
26
59
  //# sourceMappingURL=resume-workflow.d.ts.map
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { createLogger } from '@kici-dev/shared';
13
13
  import type { LockJob, MaterializedJob } from '@kici-dev/engine';
14
+ import type { JobKind } from '../db/types.js';
14
15
  import type { RunContext, RunCoordinator } from '../cluster/coordinator.js';
15
16
  import type { Dispatcher } from '../agent/dispatcher.js';
16
17
  import type { ExecutionTracker } from '../reporting/execution-tracker.js';
@@ -19,6 +20,20 @@ export interface DispatchedJobEntry {
19
20
  jobName: string;
20
21
  matrixValues?: Record<string, unknown>;
21
22
  runsOnLabels?: string[];
23
+ /**
24
+ * The unexpanded job name a materialized child came from. Persisted to
25
+ * `execution_jobs.base_job_name`, which is the key the rolling-wave scheduler
26
+ * groups a wave's children by — a NULL there makes the wave gate bail.
27
+ */
28
+ baseJobName?: string;
29
+ /**
30
+ * `gate` marks an invoke-gate row (runs the gate executor, never an agent).
31
+ * The global-workflow dispatch path registers a gate as a synthetic pending
32
+ * row and carries its kind through to `execution_jobs.job_kind`.
33
+ */
34
+ jobKind?: JobKind;
35
+ /** For a gate job, its wall-clock timeout in ms (orchestrator-swept). */
36
+ timeoutMs?: number;
22
37
  }
23
38
  export interface RejectedJobEntry {
24
39
  jobId: string;