@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
@@ -16,12 +16,14 @@
16
16
  import { ExecutionJobStatus, InitFailureCategory, CacheRefScope, HoldScope, TriggerSource } from '@kici-dev/engine';
17
17
  import type { LabelMatcher, LockWorkflow, LockJob, ApprovalRequirement, HostTargetSelector, SimulatedEvent, WorkflowDecision, MaterializedJob, ResolvedHostAgent, HostFacts } from '@kici-dev/engine';
18
18
  import { type HostRosterStore } from '../agent/host-roster.js';
19
+ import { gatherInvokeResults } from '../orchestrator-core.js';
19
20
  import type { WebhookInfo } from '../webhook/handler.js';
20
21
  import type { ProviderBundle } from '../provider-registry.js';
21
22
  import type { Dispatcher } from '../agent/dispatcher.js';
22
23
  import type { QueuedJobInput } from '../queue/job-queue.js';
23
24
  import type { TrustResolution } from '../security/trust-resolver.js';
24
25
  import type { TrustPolicyOutcome } from '../security/trust-policy-gate.js';
26
+ import type { CreateHeldRunData } from '../contexts/held-runs.js';
25
27
  import { type ProcessingDeps } from './processor.js';
26
28
  /**
27
29
  * Trusted refs (write+ contributor, default-branch) get the org-shared cache
@@ -85,6 +87,21 @@ export interface WorkflowDispatchContext {
85
87
  };
86
88
  };
87
89
  resolvedOrgId: string;
90
+ /**
91
+ * Siblings admitted against a context concurrency limit during THIS dispatch
92
+ * pass, keyed by {@link concurrencyAdmissionKey}.
93
+ *
94
+ * The gate's running count counts jobs whose status is already `running`, so
95
+ * it cannot see anything this pass has only just admitted. Without this tally
96
+ * every child of a fan-out is evaluated against that one blind value and all
97
+ * N are admitted against a single slot.
98
+ *
99
+ * Created lazily on first use and dropped with the ctx. It is per-pass state,
100
+ * never persisted and never shared between passes; two concurrent passes
101
+ * still race, which is accepted — this is a throughput control, not an
102
+ * isolation boundary.
103
+ */
104
+ concurrencyAdmissions?: Map<string, number>;
88
105
  workflow: LockWorkflow;
89
106
  decision: WorkflowDecision;
90
107
  runId: string;
@@ -106,6 +123,79 @@ export interface WorkflowDispatchContext {
106
123
  triggeredByAgentLabel?: string | null;
107
124
  /** True only when invoked from the cross-source dispatch shell. */
108
125
  crossSource: boolean;
126
+ /**
127
+ * Trigger-event string for the run's `triggerEvent`, stated explicitly rather
128
+ * than derived from `event.type`.
129
+ *
130
+ * The internal-event adapter needs this because a user `kiciEvent()` renders
131
+ * two different values: `jobConfig.event.type` is the literal `kici_event`,
132
+ * while the run carries the raw event name. `event.type` carries the former,
133
+ * so the latter has to be stated.
134
+ *
135
+ * Absent ⇒ the value derived from the event, unchanged for the webhook,
136
+ * CLI-remote-run and resume callers.
137
+ */
138
+ triggerEventOverride?: string;
139
+ /**
140
+ * The exact `jobConfig.event` envelope to ship, stated explicitly instead of
141
+ * spreading the `SimulatedEvent` this dispatch matched against.
142
+ *
143
+ * Same reason as `triggerEventOverride`: the matcher's event shape and the
144
+ * shape user code observes are not the same object. `SimulatedEvent` requires
145
+ * `targetBranch` and carries `changedFiles`; the envelope omits both. An
146
+ * internally triggered run genuinely has no changed files, and its branch is
147
+ * provenance the orchestrator evaluates (the trigger matcher and the context
148
+ * branch gate) rather than a field user code asked for — the envelope becomes
149
+ * `RuleContext.event` on the agent and the `event` half of
150
+ * `buildConcurrencyGroupContext`, so publishing the branch would silently
151
+ * re-key the documented `ctx.event.targetBranch ?? 'default'` concurrency
152
+ * group of every existing internal workflow.
153
+ *
154
+ * Absent ⇒ the envelope derived from the event, unchanged for the webhook,
155
+ * CLI-remote-run and resume callers.
156
+ */
157
+ eventEnvelopeOverride?: Record<string, unknown>;
158
+ /**
159
+ * Chain depth to stamp on the started run. A run summoned by an invoke gate
160
+ * carries its summoner's depth + 1, which is what bounds the chain-depth
161
+ * circuit breaker. Absent ⇒ 0, the column default (a webhook-triggered run
162
+ * starts a chain).
163
+ *
164
+ * Threading this is load-bearing: the breaker fails OPEN if the value is
165
+ * lost, so an unbounded summon recursion would go undetected. It is stamped
166
+ * as soon as the run row exists, because `releaseInvokeGate` reads the column
167
+ * back at gate-release time — which happens inside this same dispatch.
168
+ */
169
+ chainDepth?: number;
170
+ /**
171
+ * Marks a run dispatched by a failure-lifecycle trigger, so its own
172
+ * completion is excluded from batch accumulation and a broken notifier
173
+ * cannot re-trigger itself (`EventRouter.isFailureLifecycleRun`).
174
+ *
175
+ * Persisted as a field INSIDE the `trigger_decision` JSON blob, merged onto
176
+ * the decision summary rather than replacing it.
177
+ */
178
+ dispatchedByFailureLifecycle?: boolean;
179
+ /**
180
+ * Marks a run the orchestrator triggered itself — a schedule fire, a
181
+ * workflow/job completion, a failure batch, a user `kiciEvent()`, or an
182
+ * invoke-gate summon — as opposed to one a provider webhook triggered.
183
+ *
184
+ * Read by the context branch gate, and by nothing else. Such a run usually
185
+ * carries a real branch in `event.targetBranch` — a schedule fire presents
186
+ * its registration's default branch, every other internal trigger inherits
187
+ * the branch of the run that emitted its event — and the gate matches it like
188
+ * any other run's. The flag singles out the runs whose `targetBranch` is
189
+ * EMPTY: a failure batch or a scaler event (many runs behind it, or none), a
190
+ * registration whose default branch has never been captured, an emitting run
191
+ * that is gone. The gate rejects those naming that cause, instead of quoting
192
+ * an empty value as though it were a branch name. It does NOT weaken the
193
+ * gate — a run with no branch cannot satisfy a restriction, `*` included.
194
+ *
195
+ * Absent ⇒ webhook-triggered, unchanged for the webhook, CLI-remote-run and
196
+ * resume callers.
197
+ */
198
+ internallyTriggered?: boolean;
109
199
  /**
110
200
  * Outcome of the org trust-policy gate for this PR event. `pass` dispatches
111
201
  * normally; `hold` parks the run in the security queue; `reject` fails it
@@ -137,6 +227,19 @@ export interface WorkflowDispatchContext {
137
227
  * explicitly rather than leave it `pending` forever.
138
228
  */
139
229
  runRegisteredBeforeDispatch?: boolean;
230
+ /**
231
+ * Set once `setupDispatchContext` has posted this dispatch's queued
232
+ * `kici/<workflow>` check and one `kici/<workflow>/job/<name>` per static job.
233
+ *
234
+ * Those checks go up BEFORE anything decides whether the run will start, so
235
+ * every exit after setup owes them a conclusion. The named early exits each
236
+ * complete their own; a THROW does not, and nothing else can — the workflow
237
+ * check keys off a run whose jobs never registered, and the stale sweep only
238
+ * touches check runs already `in_progress`. Left alone they stay `queued`
239
+ * forever, which on a pull request is a check that never finishes and a
240
+ * branch-protection blocker.
241
+ */
242
+ pendingChecksPosted?: boolean;
140
243
  /** Composite dedup key `${info.deliveryId}:${reg.id}` (cross-source only). */
141
244
  crossSourceDeliveryId?: string;
142
245
  /**
@@ -193,6 +296,14 @@ export interface WorkflowDispatchContext {
193
296
  */
194
297
  dispatchInputs?: Record<string, unknown>;
195
298
  }
299
+ /**
300
+ * The minimal slice of a dispatch context the needs catch-up + ready-recompute
301
+ * helpers read. Both the per-repository `WorkflowDispatchContext` and the
302
+ * global-workflow dispatch path (which builds its own inputs and never
303
+ * constructs a full `WorkflowDispatchContext`) can drive the needs scheduler
304
+ * through this narrow shape.
305
+ */
306
+ export type NeedsSchedulingContext = Pick<WorkflowDispatchContext, 'deps' | 'runId'>;
196
307
  /**
197
308
  * Build the dispatch-envelope event, carrying the orchestrator's already-fetched
198
309
  * changed-files list + status from `eventWithFiles` as a fast-path (the agent
@@ -229,8 +340,6 @@ export interface DispatchMatchedWorkflowOptions {
229
340
  * so secrets resolve directly and the dispatch flows into job dispatch.
230
341
  */
231
342
  skipInstallProtectionGate?: boolean;
232
- /** The released held-run id being resumed (for logging / correlation). */
233
- reuseHeldRunId?: string;
234
343
  /**
235
344
  * The run id whose `held` execution_runs row should be reused (flipped to
236
345
  * pending) instead of inserting a fresh row.
@@ -312,6 +421,16 @@ interface JobEnvData {
312
421
  jobEnv?: Record<string, string>;
313
422
  jobSecrets?: Record<string, string>;
314
423
  jobNamespacedSecrets?: Record<string, Record<string, string>>;
424
+ /**
425
+ * Registry credentials for this job's container image, resolved from the
426
+ * secret NAMES the lock carries. Lifted to a top-level dispatch field (and
427
+ * stripped from jobConfig) before the message reaches the agent.
428
+ */
429
+ containerRegistryAuth?: {
430
+ username: string;
431
+ password: string;
432
+ serveraddress: string;
433
+ };
315
434
  held?: boolean;
316
435
  /**
317
436
  * Pending approval hold for this job, set when a context policy or
@@ -320,6 +439,13 @@ interface JobEnvData {
320
439
  * can re-dispatch after approval.
321
440
  */
322
441
  approvalHold?: PendingApprovalHold;
442
+ /**
443
+ * A non-reviewer hold (security / wait-timer / concurrency-queue) decided by a
444
+ * context's protection rules. Carried rather than written on the spot so
445
+ * `holdJobForApproval` can create the row and the job's resume path in ONE
446
+ * transaction — the reviewer branch uses `approvalHold` for the same purpose.
447
+ */
448
+ nonApprovalHold?: CreateHeldRunData;
323
449
  rejected?: boolean;
324
450
  rejectReason?: string;
325
451
  pendingInit?: boolean;
@@ -350,27 +476,14 @@ interface JobEnvEvalResult {
350
476
  runContextName: string | undefined;
351
477
  runContextId: string | undefined;
352
478
  }
353
- interface DispatchedJob {
354
- jobId: string;
355
- jobName: string;
356
- runsOnLabels?: string[];
357
- matrixValues?: Record<string, unknown>;
358
- baseJobName?: string;
359
- variantKind?: string;
360
- variantLabel?: string;
361
- /** Held by the rolling-wave gate (a fan-out child beyond maxParallel). */
362
- waveGated?: boolean;
363
- /** The base's wave width, stamped on every child of a bounded wave. */
364
- waveMaxParallel?: number;
365
- /** The base's failFast policy, stamped on every child of a bounded wave. */
366
- waveFailFast?: boolean;
367
- /** Ordered bound-context names persisted on the job row (multi-env jobs). */
368
- contexts?: string[];
369
- /** Bound contexts skipped on a test run (non-test / unconfigured). */
370
- skippedContexts?: string[];
371
- /** User-visible warning naming the skipped test-run contexts. */
372
- envWarning?: string;
373
- }
479
+ /**
480
+ * Synthetic job-id prefix every needs-gate site stamps on a job it holds back.
481
+ * The release path (`dispatchReadyJob` → `findSyntheticJobId` → `addJobsToRun`)
482
+ * keys on it, so it is load-bearing rather than cosmetic — which is what makes
483
+ * it a sound way to recover the gated set without threading a parallel list
484
+ * through both the single-orchestrator and cluster dispatch paths.
485
+ */
486
+ export declare const NEEDS_PENDING_JOB_ID_PREFIX = "needs-pending-";
374
487
  interface RejectedJob {
375
488
  jobId: string;
376
489
  jobName: string;
@@ -384,6 +497,32 @@ interface RejectedJob {
384
497
  */
385
498
  terminalStatus?: ExecutionJobStatus;
386
499
  }
500
+ /**
501
+ * Whether this dispatch must run a `__build__` job before its real jobs.
502
+ *
503
+ * Either cache missing is a reason to build. The two are keyed on different
504
+ * things — the source cache on the workflow source's contentHash, the dep cache
505
+ * on the lockfile hash — so they miss independently, and a dependency bump is
506
+ * exactly the case that leaves the source warm and the deps cold. Gating only on
507
+ * the source miss made that state permanent: no build job ran, so nothing ever
508
+ * uploaded the dep tarball, so every agent fell back to installing from the
509
+ * registry on every job — and an agent with no route to that registry (a
510
+ * cloud-hosted one-shot agent, an air-gapped runner) could not run the job at
511
+ * all. The `__build__` job already carries `buildSourceNeeded` /
512
+ * `buildDepsNeeded` separately and the agent already honors both, so a
513
+ * deps-only build was implemented and simply unreachable.
514
+ *
515
+ * A hash is required alongside its miss: with no hash there is no cache key to
516
+ * write, so building would produce an artifact nothing could ever look up.
517
+ */
518
+ export declare function buildIsNeeded(args: {
519
+ /** False when there is nothing to cache into (no bundle, no coordinator, …). */
520
+ cacheInfraAvailable: boolean;
521
+ sourceHit: boolean;
522
+ contentHash: string | undefined;
523
+ depHit: boolean;
524
+ lockfileHash: string | undefined;
525
+ }): boolean;
387
526
  /**
388
527
  * Build the QueuedJobInput for a synthetic `__bringup__` job: the orchestrator
389
528
  * dispatches one per declared-but-un-agented `includeUninitialized` child to an
@@ -435,6 +574,28 @@ export declare function runsOnSelectorsForLockJob(lockJob: {
435
574
  runsOn?: readonly LabelMatcher[];
436
575
  excludeLabels?: readonly LabelMatcher[];
437
576
  }): JobRoutingSelectors;
577
+ /**
578
+ * Runtime facts a job's own shape demands of the host that runs it.
579
+ *
580
+ * Only a `container.dockerfile` job gets one. Building shells out to a
581
+ * `docker` / `podman` CLI, so a host without one cannot run the job at all, and
582
+ * the agent self-reports `kici:runtime:container-build` when it has one.
583
+ *
584
+ * A job that names a finalized `image` deliberately gets NOTHING added. Adding
585
+ * an implicit requirement to jobs that already work is how container jobs were
586
+ * stranded once before: they had been running fine, and a routing gate the
587
+ * orchestrator could not actually evaluate made them match nothing. A dockerfile
588
+ * job is new, so requiring the fact strands no existing workflow — and an agent
589
+ * old enough not to report the fact is an agent that cannot build anyway.
590
+ *
591
+ * Applied when matching REGISTERED agents (the dispatcher), and deliberately
592
+ * NOT when consulting the scaler. A scaler backend is chosen by exact label-set
593
+ * containment, so a required label the operator never wrote in a pool's label
594
+ * set matches no backend at all — the job would be stranded `queued-no-backend`
595
+ * rather than spawned. The pool's hosts are the operator's to describe; what an
596
+ * agent can actually do is known only once it registers and says so.
597
+ */
598
+ export declare function requiredRuntimeLabelsFor(container: unknown): string[];
438
599
  /**
439
600
  * Resolve a generated job's single bare-`agentId` `runsOn` into a host pin.
440
601
  *
@@ -502,6 +663,19 @@ export declare function findInvalidApprovalTimeout(workflow: LockWorkflow): {
502
663
  * no host facts to scope by.
503
664
  */
504
665
  export declare function hostCtxFromMat(mat: MaterializedJob): HostFacts | undefined;
666
+ /**
667
+ * Key for the in-pass admission tally.
668
+ *
669
+ * The org id is part of the key so the tally inherits the cross-tenant scoping
670
+ * the running-count query already enforces on `execution_runs.customer_id`: a
671
+ * context name shared across tenants must not leak concurrency between them.
672
+ * One dispatch pass carries a single `resolvedOrgId`, so the org component is a
673
+ * standing invariant rather than a live discriminator — it is here so a future
674
+ * pass dispatching for more than one org cannot silently merge two orgs'
675
+ * tallies. `JSON.stringify` over the pair is used rather than string
676
+ * concatenation so no separator character can make two different pairs collide.
677
+ */
678
+ export declare function concurrencyAdmissionKey(orgId: string, concurrencyGroup: string): string;
505
679
  /**
506
680
  * Phase D — evaluate static jobs' context data, queue deferred-init jobs
507
681
  * for jobs with dynamic fields, and pick the first `runContextName` for
@@ -512,6 +686,85 @@ export declare function evaluateJobContexts(args: {
512
686
  setup: DispatchSetup;
513
687
  buildPrep: BuildPrepResult;
514
688
  }): Promise<JobEnvEvalResult>;
689
+ /**
690
+ * Refuse a Dockerfile build on an untrusted ref, unless the org opted in.
691
+ *
692
+ * A job may build its container image from a Dockerfile in the repository. That
693
+ * build runs arbitrary `RUN` commands on the agent host's daemon, OUTSIDE the
694
+ * hardened posture the job's own steps get — `docker build` cannot be
695
+ * capability-restricted the way a container run can. So an untrusted ref (a fork
696
+ * PR, an unresolved contributor, or an internally-triggered run without a
697
+ * trusted emitter — the same classification the user-cache write scope uses)
698
+ * reaches it only where the operator said so.
699
+ *
700
+ * "Without a trusted emitter" covers both halves of the internal case: a
701
+ * `kiciEvent()` subscriber that inherited a `known` / `unknown` tier, and one
702
+ * that inherited nothing at all (no emitting run, no persisted tier, a lookup
703
+ * that failed) — the strict fallback, which is not an "untrusted emitter".
704
+ *
705
+ * Enforced here, at dispatch, and nowhere else: the agent applies only what
706
+ * dispatch authorized, exactly as it does for the sandbox capability grant. Deny
707
+ * is loud and total — the build never starts.
708
+ */
709
+ export declare function resolveWorkflowDockerfileBuilds(workflow: LockWorkflow, opts: {
710
+ scope: CacheRefScope;
711
+ allowUntrusted: boolean;
712
+ }): {
713
+ allowed: true;
714
+ } | {
715
+ denied: {
716
+ reason: string;
717
+ };
718
+ };
719
+ /**
720
+ * Post the pending `KiCI Security` status, and record on every hold that gates
721
+ * this commit's check that the commit now carries it.
722
+ *
723
+ * The record is what a settle reads to decide whether a hold has a check to
724
+ * terminalize. Deriving that from the row's shape instead answers what the code
725
+ * INTENDED, and `postCheckStatus` CREATES the named run when it finds none — so
726
+ * a post the provider refused left a shape saying "posted" and a settle that
727
+ * put a completed `KiCI Security` run on a commit which never had one.
728
+ *
729
+ * `heldRunIds` is plural because the commit carries ONE check run: a job held on
730
+ * two independent requirements has both of them gating it, and marking only the
731
+ * one whose summary was rendered would let the first to end resolve a check the
732
+ * other is still gating.
733
+ *
734
+ * Awaited, where the post used to be fire-and-forget: the record can only be
735
+ * written once the provider has accepted, and a record racing the settle is a
736
+ * record the settle may not see. A failed post is still swallowed — the dispatch
737
+ * loop is never blocked by a provider error — but the round-trip is now
738
+ * serial and inside the per-job loop, so N held jobs cost N of them.
739
+ *
740
+ * On success and then a failed record the commit keeps a pending check the
741
+ * settle will decline to close. That is the residue the fire-and-forget post
742
+ * could already leave, now narrowed to whatever can fail between the accepted
743
+ * post and ONE statement. The record is therefore retried
744
+ * {@link PENDING_CHECK_MARK_ATTEMPTS} times before it is given up on, which
745
+ * closes the half of that window the doc above names as reachable without a
746
+ * process dying — a lost connection, a statement timeout, a deadlock. What is
747
+ * left is a process death inside the retry window, and nothing in reach closes
748
+ * that: a sweeper would have to ask the provider whether the check exists, and
749
+ * `CheckStatusPoster` has no read method. Adding one means a new method on a
750
+ * compat-protected engine interface, every implementation and every hand-built
751
+ * bundle, to recover a window measured in milliseconds — against a recovery
752
+ * that already exists, since pushing a new commit re-posts. A sweeper without
753
+ * that read could only guess, and a wrong guess FABRICATES a check, which is
754
+ * the worse direction either way: a fabricated failing check on a pull request
755
+ * is worse than a stuck one.
756
+ *
757
+ * The record is one statement over every id, never one per id. A partial mark
758
+ * would leave an unmarked hold uncounted by the contention query, so the first
759
+ * hold to end would terminalize the shared check while the other still gates
760
+ * the job — a fabricated PASSING check, the worse direction, and reachable
761
+ * without any process dying. See `markPendingCheckPosted`.
762
+ */
763
+ /**
764
+ * How many times {@link postPendingHoldCheck} tries to record an accepted post
765
+ * on the hold rows before giving up and leaving the check unclosable.
766
+ */
767
+ export declare const PENDING_CHECK_MARK_ATTEMPTS = 3;
515
768
  /**
516
769
  * Open the needs gate for any job whose upstreams already reached terminal
517
770
  * before this run's edges existed.
@@ -543,8 +796,12 @@ export declare function evaluateJobContexts(args: {
543
796
  * bypass the `maxParallel` window.
544
797
  */
545
798
  export declare function catchUpNeedsGatedJobs(args: {
546
- ctx: WorkflowDispatchContext;
547
- dispatchedJobs: readonly DispatchedJob[];
799
+ ctx: NeedsSchedulingContext;
800
+ dispatchedJobs: readonly {
801
+ jobId: string;
802
+ jobName: string;
803
+ waveGated?: boolean;
804
+ }[];
548
805
  }): Promise<void>;
549
806
  /** Why an init result must not lead to a dispatch. */
550
807
  export declare enum InitDispatchSuppression {
@@ -562,10 +819,24 @@ export declare enum InitDispatchSuppression {
562
819
  * predates the filter reports no verdict at all — reading that absence as
563
820
  * "suppress" would silently stop every dispatch it handles.
564
821
  *
565
- * `Gated` is belt-and-braces. `evaluateJobContexts` gives a rejected or held job
566
- * no init job in the first place, so this is unreachable today; if it ever
567
- * becomes reachable, dispatching would mean going straight past a protection
568
- * rule or an approval hold, which is the one outcome worth a redundant check.
822
+ * `Gated` covers a job that is already rejected or held. Its two halves now have
823
+ * very different reachability, and saying so is the point of this paragraph:
824
+ *
825
+ * - **rejected** is live and load-bearing. The flow-back consumes it (that is
826
+ * the `suppression === Gated && jobEnvData.rejected` branch) to stop a job its
827
+ * context rules rejected from dispatching.
828
+ * - **held** is NOT consumed here any more. A held job deliberately DOES get an
829
+ * init job — nothing else can resolve a dynamic value, so suppressing the
830
+ * round would make the job undispatchable rather than merely gated. The
831
+ * flow-back handles it after resolution instead, routing it to
832
+ * `holdExecutionAfterInit` so the hold and its resolved dispatch context are
833
+ * stored together.
834
+ *
835
+ * So do not read this as a guarantee that a held job cannot dispatch: that
836
+ * guarantee lives at the call site's `jobEnvData.held` branch, and — for the
837
+ * needs-scheduler and cluster paths, which reach a job by other routes — in
838
+ * `dispatchReadyJob`'s pending-hold check and the cluster path's own
839
+ * `holdJobForApproval` call.
569
840
  *
570
841
  * Exported for its own test: inline, the second branch could not be exercised at
571
842
  * all, and an untestable security check is one nobody can prove still works.
@@ -615,7 +886,8 @@ export declare function partitionGeneratedConfigsByPin(configs: readonly Generat
615
886
  * `evaluateDownstreams` already claimed is not returned here and cannot be
616
887
  * dispatched twice.
617
888
  */
618
- export declare function recomputeAndApplyReady(ctx: WorkflowDispatchContext, jobNames: readonly string[]): Promise<void>;
889
+ export declare function recomputeAndApplyReady(ctx: NeedsSchedulingContext, jobNames: readonly string[]): Promise<void>;
890
+ export { gatherInvokeResults };
619
891
  /**
620
892
  * Dispatch a single matched workflow.
621
893
  *
@@ -632,5 +904,4 @@ export declare function recomputeAndApplyReady(ctx: WorkflowDispatchContext, job
632
904
  * J. deferred dynamic dispatch (fire-and-forget per dynamic entry)
633
905
  */
634
906
  export declare function dispatchMatchedWorkflow(ctx: WorkflowDispatchContext, opts?: DispatchMatchedWorkflowOptions): Promise<DispatchMatchedWorkflowResult>;
635
- export {};
636
907
  //# sourceMappingURL=dispatch-matched-workflow.d.ts.map
@@ -192,6 +192,20 @@ export interface GlobalEvalRoundArgs {
192
192
  * timeout message that says nothing about agent versions.
193
193
  */
194
194
  export declare function unsupportedFleetReason(agentRegistry?: GlobalEvalAgentRegistry): string | null;
195
+ /**
196
+ * Narrow one wire verdict into a value the dispatch path can trust.
197
+ *
198
+ * The round-result schema proves only "array of objects with string keys" — its
199
+ * `jobs` entries are a deliberate unvalidated pass-through of `LockJob`, whose
200
+ * single source of truth is a TypeScript interface with no Zod mirror. So every
201
+ * field is re-checked here rather than dereferenced: `run` is compared, not
202
+ * coerced, and a generated job missing a usable `name` makes the whole candidate
203
+ * indeterminate rather than being dropped silently. Fail-closed is the right
204
+ * direction — a generator whose output we cannot read is a generator whose
205
+ * verdict we cannot act on, and half-dispatching its jobs would be worse than
206
+ * reporting that we could not decide.
207
+ */
208
+ export declare function narrowVerdict(raw: unknown, workflowName: string): GlobalEvalCandidateResult;
195
209
  /**
196
210
  * Cap on the joined reason string a decided-nothing round reports.
197
211
  *
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Adapter that dispatches an internally-triggered workflow through the SHARED
3
+ * pipeline (`dispatchMatchedWorkflow`) instead of a bespoke path.
4
+ *
5
+ * The bespoke path it replaces resolved no bound contexts, so no context vars,
6
+ * no scoped secrets and none of the context's protection rules reached an
7
+ * internally-triggered job; it also dropped every non-static job on the floor.
8
+ * Routing through the one pipeline is what keeps a fourth such gap from
9
+ * appearing.
10
+ */
11
+ import { z } from 'zod';
12
+ import type { LockFile, WorkflowDecision } from '@kici-dev/engine';
13
+ import { type ProcessingDeps } from './processor.js';
14
+ import type { ProviderBundle } from '../provider-registry.js';
15
+ /**
16
+ * The LIFECYCLE event names the orchestrator emits to itself. They are
17
+ * `__`-prefixed so they can never collide with a user `kiciEvent()` name, and
18
+ * each one maps onto a canonical trigger-event type (`__schedule_fire` is the
19
+ * one that is not a plain de-prefixing).
20
+ *
21
+ * NOT the whole set of orchestrator-minted events — the scaler manager mints two
22
+ * more under the other reserved prefix. NOT the trusted set either: two of these
23
+ * four are caused by a run and inherit its tier — `ORCHESTRATOR_MINTED_TRUSTED_EVENT_NAMES`
24
+ * is what the trust classification reads.
25
+ *
26
+ * Enumerated so this module compares against a checked value rather than a bare
27
+ * literal. It is NOT yet the single source: `cron/cron-scheduler.ts`,
28
+ * `events/event-emitter.ts` and `events/event-router.ts` still spell the same
29
+ * names inline, and converging them is a sweep this module's blast radius does
30
+ * not cover. The names are a protocol between those emitters, the router and
31
+ * this adapter, so a typo in any one of them silently stops matching.
32
+ */
33
+ export declare const InternalSystemEventName: z.ZodEnum<{
34
+ __job_complete: "__job_complete";
35
+ __schedule_fire: "__schedule_fire";
36
+ __workflow_complete: "__workflow_complete";
37
+ __workflows_failed_batch: "__workflows_failed_batch";
38
+ }>;
39
+ /**
40
+ * Context for dispatching one internally-triggered workflow.
41
+ *
42
+ * Declared here rather than in `orchestrator-core.ts` because the adapter is
43
+ * the consumer that defines the shape; the composition root imports it.
44
+ */
45
+ export interface InternalEventDispatchContext {
46
+ event: {
47
+ id: string;
48
+ eventName: string;
49
+ payload?: Record<string, unknown>;
50
+ };
51
+ routingKey: string;
52
+ repoIdentifier: string;
53
+ providerContext: Record<string, unknown>;
54
+ providerType: 'github' | undefined;
55
+ /**
56
+ * Provider bundle for the acting repository, resolved by the composition root
57
+ * from the live registry.
58
+ *
59
+ * Required, not derived here: every dispatch site builds its clone URL as
60
+ * `bundle?.repoUrlBuilder?.buildCloneUrl(repoIdentifier) ?? ''`, and nothing
61
+ * downstream resolves a bundle from `deps.providerRegistry`. Passing
62
+ * `undefined` therefore dispatches every job with an empty repo URL AND
63
+ * classifies the run as a local-working-tree dispatch (the source-pack build
64
+ * and the source-cache probe both gate on `!!ctx.bundle`), so no source
65
+ * tarball is produced and the agent falls through to cloning `''`. It also
66
+ * carries the clone-token provider and the check-status poster.
67
+ *
68
+ * Legitimately `undefined` when the event carries no routing key, or when the
69
+ * registry has no bundle for it — the same state the pre-pipeline path
70
+ * rendered as an empty repo URL.
71
+ */
72
+ bundle: ProviderBundle | undefined;
73
+ cronCommitSha: string;
74
+ /**
75
+ * The default branch of the registration this event dispatches, or `null`
76
+ * when it was never captured.
77
+ *
78
+ * A `__schedule_fire` run executes the default branch's lock file, so that
79
+ * branch IS the run's branch — which is what a context's branch restrictions
80
+ * evaluate. `null` presents no branch and the gate then rejects with its
81
+ * named-cause verdict; nothing substitutes a value, because a branch nobody
82
+ * proved is worse than an honest refusal.
83
+ */
84
+ registrationDefaultBranch: string | null;
85
+ /**
86
+ * The run that summoned this one through an invoke gate, when there is one.
87
+ *
88
+ * Set ONLY by the invoke-gate summon path, whose event id is synthesized and
89
+ * therefore matches no `kici_events` row. Stating the emitting run directly
90
+ * is what lets a summoned run inherit its summoner's trust tier — the same
91
+ * inheritance every other user `kiciEvent()` subscriber gets — instead of
92
+ * always falling back to isolated on a lookup that cannot succeed.
93
+ */
94
+ summonedByRunId?: string;
95
+ /**
96
+ * Chain depth to stamp on the started run. A webhook-triggered run leaves this
97
+ * unset (0); a run summoned by an invoke gate carries its summoner's depth + 1
98
+ * so the chain-depth circuit breaker bounds recursion when it fires its own
99
+ * gate.
100
+ */
101
+ chainDepth?: number;
102
+ }
103
+ /**
104
+ * The two identities an internal event renders into, which are NOT the same
105
+ * value for a user `kiciEvent()`:
106
+ *
107
+ * - `jobConfigType` is what workflows observe as `ctx.event.type` and what the
108
+ * trigger matcher matched against — the literal `kici_event` for a custom
109
+ * event.
110
+ * - `triggerEvent` is what the run carries and forwards to the Platform — the
111
+ * actual event name, so the dashboard can say which event fired.
112
+ *
113
+ * A system (`__`-prefixed) event renders both from its de-prefixed name, with
114
+ * `__schedule_fire` collapsing onto `schedule`.
115
+ */
116
+ export declare function deriveInternalEventIdentity(eventName: string): {
117
+ jobConfigType: string;
118
+ triggerEvent: string;
119
+ };
120
+ /**
121
+ * True when every tier the schema admits carries a rank.
122
+ *
123
+ * The `Record<TrustTier, number>` above already fails the build on an unranked
124
+ * tier, so this can only ever be false if the two drift through a cast. It is
125
+ * exported so a test asserts it against the live `TrustTierSchema.options`
126
+ * rather than against a transcription of it — the check has to read the schema
127
+ * to be worth having.
128
+ */
129
+ export declare function trustTierRanksCoverEverySchemaTier(): boolean;
130
+ /**
131
+ * Dispatch one matched workflow for an internal event through the shared
132
+ * pipeline. Returns the spawned-run summary the bespoke path returned, or
133
+ * `null` when the decision names a workflow the lock file does not carry.
134
+ */
135
+ export declare function dispatchInternalEventViaPipeline(decision: WorkflowDecision, lockFile: LockFile, ctx: InternalEventDispatchContext, deps: ProcessingDeps): Promise<{
136
+ runId: string;
137
+ repo: string;
138
+ workflow: string;
139
+ } | null>;
140
+ //# sourceMappingURL=internal-event-pipeline.d.ts.map