@kici-dev/orchestrator 0.5.0 → 0.6.1

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 (149) 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 +23 -0
  4. package/dist/agent/dispatcher.d.ts +81 -13
  5. package/dist/agent/host-roster.d.ts +37 -5
  6. package/dist/agent/registry.d.ts +2 -0
  7. package/dist/app.d.ts +33 -6
  8. package/dist/approvals/apply-decision.d.ts +67 -19
  9. package/dist/approvals/held-run-release-wiring.d.ts +45 -0
  10. package/dist/approvals/independent-wiring.d.ts +55 -0
  11. package/dist/approvals/resume-router.d.ts +9 -0
  12. package/dist/approvals/triggerer-subject.d.ts +37 -0
  13. package/dist/cache/dep-cache.d.ts +54 -10
  14. package/dist/cache/global-eval-round-cache.d.ts +7 -0
  15. package/dist/cli/api-client.d.ts +18 -0
  16. package/dist/cli/commands/cluster-settings.d.ts +11 -0
  17. package/dist/cli/commands/diagnose.d.ts +6 -0
  18. package/dist/cli/commands/held-run.d.ts +58 -0
  19. package/dist/cli/commands/trust-policy.d.ts +117 -12
  20. package/dist/cli/kici-admin.d.ts +10 -0
  21. package/dist/cli.js +2272 -1025
  22. package/dist/cluster/cluster-identity.d.ts +3 -3
  23. package/dist/cluster/cluster-settings-reader.d.ts +7 -1
  24. package/dist/cluster/coordinator.d.ts +44 -0
  25. package/dist/cluster/index.d.ts +4 -0
  26. package/dist/cluster/peer-handler.d.ts +19 -0
  27. package/dist/cluster/peer-registry.d.ts +17 -0
  28. package/dist/cluster/plan-headroom-store.d.ts +26 -0
  29. package/dist/cluster/worker-eviction.d.ts +69 -0
  30. package/dist/cold-store/load-event-log-range.d.ts +10 -3
  31. package/dist/cold-store/load-secret-audit-log-range.d.ts +4 -3
  32. package/dist/cold-store/tables/secret-audit-log.d.ts +4 -3
  33. package/dist/config.d.ts +21 -21
  34. package/dist/contexts/held-runs.d.ts +123 -16
  35. package/dist/contexts/protection/aggregate.d.ts +3 -2
  36. package/dist/contexts/protection/branch-gate.d.ts +15 -0
  37. package/dist/contexts/protection/concurrency-gate.d.ts +8 -2
  38. package/dist/contexts/protection/pipeline.d.ts +18 -0
  39. package/dist/contexts/release-queued-holds.d.ts +35 -0
  40. package/dist/dashboard/attestation-filters.d.ts +8 -0
  41. package/dist/dashboard/handler.d.ts +6 -24
  42. package/dist/db/migrations/116_org_plan_headroom.d.ts +4 -0
  43. package/dist/db/migrations/117_invoke_gate_proxy_jobs.d.ts +4 -0
  44. package/dist/db/migrations/118_invoke_gate_scheduling.d.ts +4 -0
  45. package/dist/db/migrations/119_scaler_ha_ownership.d.ts +4 -0
  46. package/dist/db/migrations/120_cluster_settings_scaler_reaper.d.ts +4 -0
  47. package/dist/db/migrations/121_org_settings_allow_untrusted_dockerfile_builds.d.ts +19 -0
  48. package/dist/db/migrations/122_host_roster_scaler_managed.d.ts +25 -0
  49. package/dist/db/migrations/123_workflow_registrations_default_branch.d.ts +24 -0
  50. package/dist/db/migrations/124_cluster_settings_provision_backoff.d.ts +4 -0
  51. package/dist/db/migrations/125_org_trust_directory.d.ts +30 -0
  52. package/dist/db/migrations/126_held_runs_posted_pending_check.d.ts +28 -0
  53. package/dist/db/migrations/127_org_trust_policy_approval_expiry_seconds.d.ts +26 -0
  54. package/dist/db/migrations/128_execution_runs_global_eval_round.d.ts +23 -0
  55. package/dist/db/migrations/129_execution_runs_dispatch_routing_key.d.ts +24 -0
  56. package/dist/db/migrations/130_scaler_provision_outcomes.d.ts +32 -0
  57. package/dist/db/types.d.ts +368 -10
  58. package/dist/entry-helpers.d.ts +28 -0
  59. package/dist/events/event-emitter.d.ts +22 -0
  60. package/dist/events/event-router.d.ts +87 -1
  61. package/dist/events/types.d.ts +2 -23
  62. package/dist/git/credential-broker.d.ts +94 -0
  63. package/dist/git/inherited-secret.d.ts +24 -0
  64. package/dist/git/installation-token.d.ts +38 -0
  65. package/dist/git/job-context.d.ts +13 -0
  66. package/dist/index.js +324 -84
  67. package/dist/metrics/agent-metrics-aggregator.d.ts +2 -2
  68. package/dist/metrics/prometheus.d.ts +111 -3
  69. package/dist/oidc/oidc-mint-registration.d.ts +7 -3
  70. package/dist/orchestrator-core.d.ts +79 -23
  71. package/dist/pipeline/direct-ingress-deps.d.ts +100 -0
  72. package/dist/pipeline/dispatch-matched-workflow.d.ts +302 -31
  73. package/dist/pipeline/global-eval-round.d.ts +14 -0
  74. package/dist/pipeline/internal-event-pipeline.d.ts +140 -0
  75. package/dist/pipeline/invoke-gate.d.ts +164 -0
  76. package/dist/pipeline/needs-scheduler.d.ts +13 -0
  77. package/dist/pipeline/process-webhook.d.ts +119 -31
  78. package/dist/pipeline/processor.d.ts +226 -21
  79. package/dist/pipeline/rerun.d.ts +21 -0
  80. package/dist/pipeline/resume-workflow.d.ts +40 -7
  81. package/dist/pipeline/route-or-dispatch-jobs.d.ts +9 -0
  82. package/dist/pipeline/security-hold-check.d.ts +217 -0
  83. package/dist/pipeline/undispatched-hold-checks.d.ts +23 -0
  84. package/dist/policy/dashboard-write-policy-listener.d.ts +62 -0
  85. package/dist/policy/dashboard-write-policy.d.ts +14 -0
  86. package/dist/provider-registry.d.ts +10 -2
  87. package/dist/providers/github/check-status-poster.d.ts +11 -0
  88. package/dist/providers/github/index.d.ts +0 -2
  89. package/dist/providers/github/normalizer.d.ts +3 -2
  90. package/dist/providers/local/normalizer.d.ts +3 -5
  91. package/dist/providers/universal-git/index.d.ts +11 -2
  92. package/dist/queue/job-queue.d.ts +43 -1
  93. package/dist/registration/extractor.d.ts +3 -3
  94. package/dist/registration/registration-index.d.ts +7 -0
  95. package/dist/registration/registration-store.d.ts +22 -0
  96. package/dist/reporting/check-run-reporter.d.ts +94 -1
  97. package/dist/reporting/check-run-summary.d.ts +25 -1
  98. package/dist/reporting/execution-tracker.d.ts +207 -2
  99. package/dist/reporting/run-aggregator.d.ts +4 -14
  100. package/dist/reporting/step-display-order.d.ts +43 -0
  101. package/dist/routes/admin-held-runs.d.ts +142 -0
  102. package/dist/routes/admin-trust-policy.d.ts +34 -6
  103. package/dist/routes/admin.d.ts +9 -0
  104. package/dist/scaler/backend-factory.d.ts +55 -0
  105. package/dist/scaler/bare-metal-backend.d.ts +43 -5
  106. package/dist/scaler/claim-store.d.ts +119 -0
  107. package/dist/scaler/config.d.ts +4 -0
  108. package/dist/scaler/container-backend.d.ts +18 -4
  109. package/dist/scaler/container-routing.d.ts +23 -0
  110. package/dist/scaler/container-spawn.d.ts +28 -0
  111. package/dist/scaler/event-backend.d.ts +124 -0
  112. package/dist/scaler/event-provision-reaper.d.ts +350 -0
  113. package/dist/scaler/failure-tracker.d.ts +1 -1
  114. package/dist/scaler/firecracker-backend.d.ts +33 -6
  115. package/dist/scaler/index.d.ts +11 -1
  116. package/dist/scaler/label-matcher.d.ts +4 -3
  117. package/dist/scaler/manager.d.ts +899 -72
  118. package/dist/scaler/resolve-container-auth.d.ts +45 -0
  119. package/dist/scaler/scaler-events.d.ts +11 -0
  120. package/dist/scaler/scaler-state-store.d.ts +294 -10
  121. package/dist/scaler/types.d.ts +131 -13
  122. package/dist/scaler/warm-pool.d.ts +132 -29
  123. package/dist/security/comment-handler.d.ts +50 -11
  124. package/dist/security/identity-link.d.ts +60 -0
  125. package/dist/security/lock-source.d.ts +11 -13
  126. package/dist/security/reduced-privilege-note.d.ts +59 -0
  127. package/dist/security/trust-directory-store.d.ts +233 -0
  128. package/dist/security/trust-policy-gate.d.ts +94 -59
  129. package/dist/security/trust-policy-store.d.ts +3 -0
  130. package/dist/security/trust-resolver.d.ts +14 -98
  131. package/dist/server.d.ts +11 -1
  132. package/dist/server.js +17684 -7351
  133. package/dist/stale-detector/gate-deadline-detector.d.ts +52 -0
  134. package/dist/stale-detector/stale-run-detector.d.ts +61 -3
  135. package/dist/standalone.js +24238 -13822
  136. package/dist/worker/in-memory-job-queue.d.ts +1 -1
  137. package/dist/ws/agent-handler.d.ts +28 -4
  138. package/dist/ws/dashboard-context-handler.d.ts +28 -4
  139. package/dist/ws/dashboard-global-workflows-handler.d.ts +6 -0
  140. package/dist/ws/failure-messages.d.ts +2 -0
  141. package/dist/ws/git-credential-relay.d.ts +39 -0
  142. package/dist/ws/oidc-token-relay.d.ts +6 -11
  143. package/dist/ws/platform-client.d.ts +54 -9
  144. package/installer-image-digests.json +3 -3
  145. package/package.json +24 -19
  146. package/sbom.spdx.json +864 -822
  147. package/dist/approvals/team-membership-lookup.d.ts +0 -13
  148. package/dist/providers/github/contributor-resolver.d.ts +0 -30
  149. package/dist/security/contributor-cache.d.ts +0 -83
@@ -36,15 +36,14 @@ import type { AgentRegistry } from '../agent/registry.js';
36
36
  import type { HostRosterStore } from '../agent/host-roster.js';
37
37
  import type { RunCoordinator } from '../cluster/coordinator.js';
38
38
  import type { ClusterSettingsReader } from '../cluster/cluster-settings-reader.js';
39
- import type { TeamMembershipLookup } from '../approvals/team-membership-lookup.js';
40
39
  import type { LogStorage } from '../reporting/log-storage.js';
41
40
  import type { LogWriter } from '../reporting/log-writer.js';
42
41
  import type { SecretResolverApi } from '../secrets/secret-resolver.js';
43
- import type { ContributorCache } from '../security/contributor-cache.js';
44
42
  import type { AccessLogWriter } from '../audit/access-log.js';
45
43
  import type { LockFile as FullLockFile, LockWorkflow, SimulatedEvent, WebhookNormalizer } from '@kici-dev/engine';
46
44
  import { LockFileParseError } from '@kici-dev/engine';
47
45
  import type { EventRouter } from '../events/event-router.js';
46
+ import type { InvokeGateDeps, InvokeGateParams } from './invoke-gate.js';
48
47
  import type { RegistrationStore } from '../registration/registration-store.js';
49
48
  import type { RegistrationIndex } from '../registration/registration-index.js';
50
49
  import type { CronScheduler } from '../cron/cron-scheduler.js';
@@ -54,10 +53,12 @@ import { EventLogSource } from '@kici-dev/engine';
54
53
  import type { LockJob } from '@kici-dev/engine';
55
54
  import type { ContextStore } from '../contexts/context-store.js';
56
55
  import type { VariableStore } from '../contexts/variable-store.js';
57
- import type { TrustResolver, IdentityLink, PermissionLevel } from '../security/trust-resolver.js';
56
+ import type { IdentityLink, PermissionLevel } from '../security/identity-link.js';
58
57
  import { type HeldRunStore } from '../contexts/held-runs.js';
58
+ import { type Context as EngineContext } from '@kici-dev/engine';
59
59
  import type { OrchestratorMode, WorkflowDecision } from '@kici-dev/engine';
60
60
  import type { TrustPolicyStore } from '../security/trust-policy-store.js';
61
+ import type { TrustDirectoryStore } from '../security/trust-directory-store.js';
61
62
  /**
62
63
  * Pending dispatch context for jobs gated by the needs scheduler.
63
64
  * Keyed by `${runId}:${jobName}`. Populated at processWebhook time for all
@@ -69,6 +70,12 @@ import type { TrustPolicyStore } from '../security/trust-policy-store.js';
69
70
  interface PendingJobContext {
70
71
  jobInput: QueuedJobInput;
71
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;
72
79
  }
73
80
  /**
74
81
  * Register an eval gate and return a promise that resolves when the scheduler
@@ -242,8 +249,21 @@ export declare function extractInboundRepoIdentifier(payload: unknown): string |
242
249
  export declare function extractCommitMessage(event: string, payload: unknown): string | undefined;
243
250
  /**
244
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.
245
265
  */
246
- export declare function buildSecurityHoldSummary(reason: string, tier: string, contributorUsername?: string): string;
266
+ export declare function buildSecurityHoldSummary(reason: string, tier: string | undefined, contributorUsername?: string): string;
247
267
  /**
248
268
  * Build the failure check-run description for a run the org trust policy
249
269
  * REJECTED.
@@ -252,8 +272,13 @@ export declare function buildSecurityHoldSummary(reason: string, tier: string, c
252
272
  * `held_runs` row, so telling the contributor to seek "approval from a user
253
273
  * with ci_trust:write or higher" points at a queue the run will never appear
254
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.
255
280
  */
256
- 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;
257
282
  /**
258
283
  * Build the pending check-run description for a job/workflow approval hold,
259
284
  * naming the clauses an approver must satisfy. `{team:X}` renders as
@@ -265,6 +290,19 @@ export declare function summarizeApprovalClauses(clauses: ReadonlyArray<{
265
290
  } | {
266
291
  user: string;
267
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;
268
306
  /**
269
307
  * Dependencies for the processing pipeline.
270
308
  * All injected for testability. Fully provider-agnostic.
@@ -356,6 +394,12 @@ export interface ProcessingDeps {
356
394
  } | undefined>) => void;
357
395
  /** Event router for registering lock file event subscriptions. Optional -- if not set, event routing is inactive. */
358
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;
359
403
  /** Registration store for persisting workflow registrations. Optional -- if not set, registration is skipped. */
360
404
  registrationStore?: RegistrationStore;
361
405
  /** Registration index for in-memory lookup. Optional -- if not set, registration is skipped. */
@@ -381,8 +425,6 @@ export interface ProcessingDeps {
381
425
  variableStore?: VariableStore;
382
426
  /** Held run store for persisting protection rule holds. Optional -- if not set, holds are not persisted. */
383
427
  heldRunStore?: HeldRunStore;
384
- /** Trust resolver for determining contributor trust tiers. Optional -- if not set, trust resolution is skipped. */
385
- trustResolver?: TrustResolver;
386
428
  /**
387
429
  * Cache of the Platform-owned org trust policy. Read per PR event by the
388
430
  * trust-policy gate. Optional so existing tests and independent deployments
@@ -397,16 +439,26 @@ export interface ProcessingDeps {
397
439
  * so a hand-built deps object never accidentally opens the gate.
398
440
  */
399
441
  orchestratorMode?: OrchestratorMode;
400
- /** 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. */
401
443
  identityLinks?: IdentityLink[];
402
444
  /** ci_trust permission levels per user ID from Platform push. Optional -- defaults to empty. */
403
445
  orgMemberPermissions?: Map<string, PermissionLevel>;
404
446
  /**
405
- * Team-membership lookup pushed from the Platform (team name member set).
406
- * Consumed by the approval resolver to satisfy `{team}` approval clauses.
407
- * 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.
408
460
  */
409
- teamMembershipLookup?: TeamMembershipLookup;
461
+ trustDirectoryStore?: TrustDirectoryStore;
410
462
  /** Global workflow policy for org-level permission enforcement. Optional -- if not set, global workflows are unrestricted. */
411
463
  globalWorkflowPolicy?: GlobalWorkflowPolicy;
412
464
  /** Inbound webhook delivery log writer. Optional -- if not set, deliveries are not persisted to event_log. */
@@ -415,11 +467,6 @@ export interface ProcessingDeps {
415
467
  * Used by the eventLog writer to populate the source column. Defaults to
416
468
  * 'direct' when omitted (independent / direct paths). */
417
469
  eventLogSource?: EventLogSource;
418
- /** Contributor permission cache. Optional -- if not set, membership-webhook
419
- * invalidations silently no-op. In platform/hybrid mode the singleton is
420
- * created in server.ts and threaded through both the Platform-relay WS
421
- * path and the generic webhook HTTP path. */
422
- contributorCache?: ContributorCache;
423
470
  /** Access-log writer for the orchestrator audit stream. Optional -- if not
424
471
  * set, hold-creation audit rows (`held_run.request`) are skipped. */
425
472
  accessLogWriter?: AccessLogWriter;
@@ -443,6 +490,120 @@ export interface ProcessingDeps {
443
490
  */
444
491
  export declare function anyTriggerHasPathPatterns(workflows: LockWorkflow[]): boolean;
445
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>;
446
607
  /**
447
608
  * Dispatch a job that has become ready via the needs scheduler.
448
609
  *
@@ -451,20 +612,64 @@ export { processWebhook } from './process-webhook.js';
451
612
  * dispatches the job through the normal dispatcher path, and updates the
452
613
  * execution tracker with the real job ID.
453
614
  */
454
- 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>;
455
616
  /**
456
- * Check whether a webhook event is a push to the repository's default branch.
457
- * Used to trigger registration extraction for workflow event subscriptions.
617
+ * Read the repository's default branch out of a webhook payload.
458
618
  *
459
- * Resolution order for the default branch:
619
+ * Resolution order:
460
620
  * 1. `normalizer.extractDefaultBranch?(payload)` — provider-specific hook
461
621
  * (universal-git reads a JSONPath from the source's `payloadPaths.defaultBranch`).
462
622
  * 2. Fallback to `payload.repository.default_branch` — the GitHub-shaped
463
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.
464
633
  */
465
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;
466
644
  /**
467
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.
468
650
  */
469
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>[];
470
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';
@@ -25,6 +26,14 @@ export interface DispatchedJobEntry {
25
26
  * groups a wave's children by — a NULL there makes the wave gate bail.
26
27
  */
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;
28
37
  }
29
38
  export interface RejectedJobEntry {
30
39
  jobId: string;