@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
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Tier-2 global eval round — orchestrator side.
3
+ *
4
+ * A global workflow can declare a `filter` predicate and `DynamicJobFn`
5
+ * generators, neither of which the orchestrator may run: author code never
6
+ * returns to this process. So the candidates that need either one are grouped by
7
+ * the workflow repo they live in and handed to ONE pre-run job per
8
+ * (event × workflow repo × registered SHA). That job checks out the workflow
9
+ * repo and the source repo once, runs every candidate's filter and then its
10
+ * generators, and reports a verdict per candidate.
11
+ *
12
+ * The round precedes any run row by design: its whole purpose is to decide which
13
+ * global workflows produce a run at all, so creating one up-front would defeat
14
+ * it.
15
+ *
16
+ * This module owns the partition, the grouping, and the dispatch-and-await. The
17
+ * caller decides what to do with the verdicts.
18
+ */
19
+ import type { GlobalEvalCandidateResult, LockWorkflow, SimulatedEvent, WorkflowDecision } from '@kici-dev/engine';
20
+ import { type GlobalEvalRoundCache } from '../cache/global-eval-round-cache.js';
21
+ import type { PendingGlobalEvalTracker } from '../cache/pending-global-evals.js';
22
+ import type { ClusterSettingsReader } from '../cluster/cluster-settings-reader.js';
23
+ import type { ProviderBundle } from '../provider-registry.js';
24
+ import type { QueuedJobInput } from '../queue/job-queue.js';
25
+ import type { RegisteredWorkflow } from '../registration/registration-index.js';
26
+ import type { WebhookInfo } from '../webhook/handler.js';
27
+ /** Name prefix identifying a round job in the queue, logs, and the dashboard. */
28
+ export declare const ROUND_JOB_PREFIX = "__globaleval__";
29
+ /** Metric label values for a per-candidate round verdict. */
30
+ export declare const GlobalEvalVerdictOutcome: {
31
+ /** The candidate's `filter` admitted it and its jobs are dispatching. */
32
+ readonly Run: 'run';
33
+ /** The candidate's `filter` returned false — a decided exclusion. */
34
+ readonly Filtered: 'filtered';
35
+ /** The round could not decide: it failed, breached a budget, or never reported. */
36
+ readonly Indeterminate: 'indeterminate';
37
+ };
38
+ export type GlobalEvalVerdictOutcome = (typeof GlobalEvalVerdictOutcome)[keyof typeof GlobalEvalVerdictOutcome];
39
+ /** Metric label values for a dispatched round's outcome. */
40
+ export declare const GlobalEvalRoundResultLabel: {
41
+ readonly Success: 'success';
42
+ readonly Error: 'error';
43
+ };
44
+ export type GlobalEvalRoundResultLabel = (typeof GlobalEvalRoundResultLabel)[keyof typeof GlobalEvalRoundResultLabel];
45
+ /** Metric label values for one round-cache lookup. */
46
+ export declare const GlobalEvalCacheLookupResult: {
47
+ readonly Hit: 'hit';
48
+ readonly Miss: 'miss';
49
+ /** The round input could not be serialized, so no key exists to look up. */
50
+ readonly Unkeyable: 'unkeyable';
51
+ };
52
+ export type GlobalEvalCacheLookupResult = (typeof GlobalEvalCacheLookupResult)[keyof typeof GlobalEvalCacheLookupResult];
53
+ /** One matched global workflow the round may have to decide on. */
54
+ export interface GlobalEvalCandidate {
55
+ reg: RegisteredWorkflow;
56
+ lockEntry: LockWorkflow;
57
+ /**
58
+ * The trigger-match decision this candidate came from, carried so the round's
59
+ * verdict can be appended to its trace. Optional because the round itself
60
+ * never reads it — only the caller that explains an exclusion does.
61
+ */
62
+ decision?: WorkflowDecision;
63
+ }
64
+ /**
65
+ * Stable identity for one candidate across a round's results.
66
+ *
67
+ * The registration id alone is unique, but the workflow name is appended so a
68
+ * lookup reads honestly at the call site and a malformed registration with a
69
+ * blank id still separates two workflows.
70
+ */
71
+ export declare function candidateKey(candidate: GlobalEvalCandidate): string;
72
+ /**
73
+ * Split matched global candidates into those that can dispatch straight away and
74
+ * those that must go through an eval round first.
75
+ *
76
+ * A candidate needs the round when it declares a `filter` (only the agent may
77
+ * run the predicate) or carries a `DynamicJobFn` (only the agent may run the
78
+ * generator). Everything else is fully described by the lock file, so routing it
79
+ * through a round would add a job dispatch and an agent round trip for nothing.
80
+ */
81
+ export declare function partitionCandidates(candidates: readonly GlobalEvalCandidate[]): {
82
+ immediate: GlobalEvalCandidate[];
83
+ needsRound: GlobalEvalCandidate[];
84
+ };
85
+ /**
86
+ * Group by workflow repo, routing key, and registered SHA.
87
+ *
88
+ * All three parts are load-bearing. The repo identifier and the SHA together pin
89
+ * the exact tree the round checks out, so two registrations of one repo at
90
+ * different commits can never share a checkout; the routing key pins which
91
+ * provider bundle mints the clone credentials, so two providers owning the same
92
+ * repo path stay separate.
93
+ */
94
+ export declare function groupCandidates(candidates: readonly GlobalEvalCandidate[]): Map<string, GlobalEvalCandidate[]>;
95
+ /** Minimal dispatcher surface the round needs (the real one is the job dispatcher). */
96
+ export interface GlobalEvalDispatcher {
97
+ dispatch(input: QueuedJobInput): Promise<{
98
+ status: string;
99
+ jobId: string;
100
+ }>;
101
+ /**
102
+ * Take a still-queued round job out of the queue when nobody is waiting for
103
+ * it any more. Optional so a test double can omit it.
104
+ */
105
+ cancelQueuedJob?(jobId: string, reason: string): Promise<void>;
106
+ }
107
+ /**
108
+ * Minimal provider-registry surface: resolving the bundle that owns a routing
109
+ * key. The round needs it because the workflow repo and the event's source repo
110
+ * can live behind different providers.
111
+ */
112
+ export interface GlobalEvalProviderRegistry {
113
+ getByRoutingKey(routingKey: string): ProviderBundle | undefined;
114
+ }
115
+ /** Minimal agent-registry surface used to pick the round job's platform labels. */
116
+ export interface GlobalEvalAgentRegistry {
117
+ findAvailable(labels: string[]): Array<{
118
+ platform: string;
119
+ arch: string;
120
+ version?: string | null;
121
+ }>;
122
+ }
123
+ /**
124
+ * First agent release whose job runner understands a `globalEvalRound`
125
+ * dispatch.
126
+ *
127
+ * Below it the agent has no round branch at all: the job falls through to the
128
+ * standard executor, whose `jobConfig` carries no `source.file`, so it either
129
+ * fails outright or reports success without the `globalEvalComplete` the
130
+ * orchestrator is waiting for. Either way the round never settles.
131
+ *
132
+ * Customers upgrade their orchestrator and their agents on their own schedule
133
+ * (`.claude/rules/compatibility.md`), so an orchestrator ahead of its fleet is
134
+ * a supported state and not an error — but it is one this module has to
135
+ * recognise, because the damage is not confined to the new feature. A global
136
+ * workflow that merely *contains* a generator now routes through the round, so
137
+ * an unrecognised round means static jobs that ran yesterday stop running.
138
+ */
139
+ export declare const MIN_GLOBAL_EVAL_AGENT_VERSION = "0.5.0";
140
+ export interface GlobalEvalRoundDeps {
141
+ dispatcher: GlobalEvalDispatcher;
142
+ pendingGlobalEvals: PendingGlobalEvalTracker;
143
+ /**
144
+ * Required, unlike the three optional deps below: it is what resolves the
145
+ * WORKFLOW repo's own provider bundle. Omitting it would leave the round with
146
+ * no clone URL for the workflow repo at all, so it is not a dep that can
147
+ * degrade quietly.
148
+ */
149
+ providerRegistry: GlobalEvalProviderRegistry;
150
+ clusterSettings?: ClusterSettingsReader;
151
+ globalEvalCache?: GlobalEvalRoundCache;
152
+ agentRegistry?: GlobalEvalAgentRegistry;
153
+ }
154
+ export interface GlobalEvalRoundArgs {
155
+ deps: GlobalEvalRoundDeps;
156
+ info: WebhookInfo;
157
+ event: SimulatedEvent;
158
+ candidates: readonly GlobalEvalCandidate[];
159
+ /** Source repo that triggered the event. */
160
+ repoIdentifier: string;
161
+ /** Source repo SHA the event lands on. */
162
+ ref: string;
163
+ dispatchBundle: ProviderBundle;
164
+ dispatchCredentials: Record<string, unknown>;
165
+ /** Cluster defaults for the round budgets and the wait ceiling (`config.ts`). */
166
+ config: {
167
+ globalEvalRoundTimeoutMs: number;
168
+ globalEvalCandidateTimeoutMs: number;
169
+ globalEvalWaitTimeoutMs: number;
170
+ };
171
+ }
172
+ /**
173
+ * Why a fleet cannot decide a round, or `null` when it might.
174
+ *
175
+ * Only ONE state is refused: every registered init-runner reports a version, and
176
+ * every one of them is below the minimum. Everything else returns `null`.
177
+ *
178
+ * An empty fleet is not refused — nothing is registered yet, an agent may
179
+ * register a second from now, and the queue's own unroutable handling already
180
+ * covers a round nobody picks up.
181
+ *
182
+ * A fleet containing even ONE agent whose version cannot be read is not refused
183
+ * either, and the bar is deliberately that strict rather than "all unknown".
184
+ * Refusing suppresses every global workflow for the delivery — the exact damage
185
+ * this function exists to prevent — so it must rest on proof, not on ignorance
186
+ * about part of the fleet. Getting it wrong in the other direction merely costs
187
+ * a slow failure with a less useful message.
188
+ *
189
+ * Refusing here rather than at dispatch is deliberate. A too-old fleet does not
190
+ * clear within a retry, so dispatching would spend two full wait ceilings of
191
+ * inline webhook latency to reach the same verdict — and reach it with a
192
+ * timeout message that says nothing about agent versions.
193
+ */
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;
209
+ /**
210
+ * Cap on the joined reason string a decided-nothing round reports.
211
+ *
212
+ * The reasons are authored by an agent running author code, so their combined
213
+ * length is unbounded — and this string travels verbatim into the commit
214
+ * check's `output.summary`, which GitHub caps at 65535 characters. An overflow
215
+ * is rejected with a 422 that the best-effort post swallows, so the check
216
+ * disappears in exactly the case it exists for. Realistic reasons are one short
217
+ * sentence each; this bound makes that structural instead of lucky.
218
+ */
219
+ export declare const MAX_ROUND_REASON_CHARS = 4000;
220
+ /**
221
+ * Truncate `text` to at most `max` UTF-16 code units, marking that it was cut.
222
+ *
223
+ * Two edges the naive slice gets wrong, both of which matter because the result
224
+ * is posted to an API with a hard character limit:
225
+ *
226
+ * - A `max` smaller than the marker would make `max - marker.length` negative,
227
+ * and appending the marker to an empty slice then returns a string LONGER
228
+ * than `max`. Such a `max` cannot carry both content and a marker, so the
229
+ * marker itself is truncated instead.
230
+ * - Slicing at an arbitrary index can cut a surrogate pair in half, leaving a
231
+ * lone surrogate that is not valid text. The cut backs off by one unit when
232
+ * it lands between the halves of a pair.
233
+ */
234
+ export declare function truncateReasonText(text: string, max: number): string;
235
+ /**
236
+ * One round that produced no verdicts after every attempt, described well enough
237
+ * for the caller to record it.
238
+ *
239
+ * There is exactly one of these per failed round — never one per candidate. The
240
+ * round exists to collapse N candidate workflows into a single pre-run job, so
241
+ * fanning its failure back out into N run rows and N checks would undo the fan-out
242
+ * reduction the whole design is for. `workflowNames` is what makes the single
243
+ * record honest: it names every workflow the failure suppressed.
244
+ */
245
+ export interface GlobalEvalRoundFailure {
246
+ /** The last attempt's round-job run id — what an errored run row is written under. */
247
+ runId: string;
248
+ /** Repo the suppressed workflows are authored in. */
249
+ workflowRepoIdentifier: string;
250
+ /** Every candidate workflow this round suppressed. */
251
+ workflowNames: string[];
252
+ /** The last attempt's error. */
253
+ error: string;
254
+ /** How many attempts were made before giving up. `0` ⇒ never dispatched. */
255
+ attempts: number;
256
+ /**
257
+ * True when the round itself completed and decided some candidates, leaving
258
+ * only {@link workflowNames} undecided.
259
+ *
260
+ * Recorded as its own record rather than folded into a total failure because
261
+ * the two read differently to an author: a total failure means nothing from
262
+ * this repo ran, a partial one means their workflow specifically could not be
263
+ * decided while its neighbours ran fine. Without it, whether a broken filter
264
+ * is visible at all depends on how many unrelated global workflows happen to
265
+ * share a workflow repo — a per-candidate budget breach in a group of one
266
+ * produces a check, and the identical fault in a group of two produces
267
+ * nothing.
268
+ */
269
+ partial?: boolean;
270
+ }
271
+ /** Every candidate's verdict, plus the rounds that produced none. */
272
+ export interface GlobalEvalRoundsOutcome {
273
+ /** One verdict per candidate, keyed by {@link candidateKey}. */
274
+ verdicts: Map<string, GlobalEvalCandidateResult>;
275
+ /** One entry per round that failed outright — never one per candidate. */
276
+ failures: GlobalEvalRoundFailure[];
277
+ }
278
+ /**
279
+ * Run every eval round the candidate set needs and return one verdict per
280
+ * candidate, plus one failure record per round that produced none.
281
+ *
282
+ * Rounds run one group at a time: a group is one dual checkout on one agent, and
283
+ * a push touching several workflow repos is the uncommon case. A group whose
284
+ * round fails marks only its own candidates indeterminate — one unreachable
285
+ * workflow repo must not suppress the workflows living in another.
286
+ *
287
+ * The failures are returned rather than recorded here: this module owns the
288
+ * partition, the grouping, and the dispatch-and-await, and has neither a
289
+ * database handle nor the inbound provider bundle a commit check must be posted
290
+ * through.
291
+ */
292
+ export declare function runGlobalEvalRounds(args: GlobalEvalRoundArgs): Promise<GlobalEvalRoundsOutcome>;
293
+ /**
294
+ * Meter candidates a round never got to decide on.
295
+ *
296
+ * The caller's fail-closed paths — no pending-eval tracker, so a round could
297
+ * never settle — return before {@link runGlobalEvalRounds} is reached, and they
298
+ * suppress EVERY global workflow for that delivery. That is this subsystem's
299
+ * most severe outcome, and without this call it appeared in none of its
300
+ * metrics: one `logger.warn` was the whole record.
301
+ *
302
+ * Counted as `indeterminate` because that is exactly what happened — nothing
303
+ * evaluated the workflows — which also keeps `candidates` equal to the sum of
304
+ * the verdicts on every path.
305
+ */
306
+ export declare function recordUnrunCandidates(count: number): void;
307
+ //# sourceMappingURL=global-eval-round.d.ts.map
@@ -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
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Invoke-gate executor.
3
+ *
4
+ * An invoke gate is a standard job whose only novel attribute is `invoke`: it
5
+ * never dispatches steps to an agent. When the gate becomes ready the
6
+ * orchestrator emits the gate's kici event at the source repo, matches the
7
+ * repo's opt-in subscribers, dispatches each as a normal in-repo run, and
8
+ * tracks each spawned run as a **proxy job** in the gate's own run.
9
+ *
10
+ * Correlation is a synchronous in-process match-and-dispatch — one orchestrator
11
+ * serves one org, so the executor matches + dispatches through an injected
12
+ * `summon` callback and gets the spawned run ids back directly. It ALSO persists
13
+ * `summoned_by_run_id` / `summoned_by_proxy_job` on each spawned run so proxy
14
+ * completion still resolves if the run finalizes on another HA instance.
15
+ *
16
+ * The gate itself is the aggregating graph node: downstream `needs` edges point
17
+ * at the gate, and its proxies are its fan-out children (`base_job_name` = the
18
+ * gate name). A zero-subscriber emit fails the gate by default; `optional: true`
19
+ * makes it a green skip.
20
+ */
21
+ import { ExecutionJobStatus } from '@kici-dev/engine';
22
+ import type { LockInvoke, LockJob } from '@kici-dev/engine';
23
+ import type { Kysely } from 'kysely';
24
+ import type { Database } from '../db/types.js';
25
+ import type { ExecutionTracker } from '../reporting/execution-tracker.js';
26
+ /**
27
+ * The invoke parameters carried from a gate's lock job to its (possibly deferred)
28
+ * execution. Persisted on the pending job context so a gate released later — or
29
+ * after a crash-recovery restore — still summons instead of reaching an agent.
30
+ */
31
+ export interface InvokeGateParams {
32
+ event: string;
33
+ payload?: Record<string, unknown>;
34
+ /** Require-by-default: false fails the gate on zero subscribers. */
35
+ optional: boolean;
36
+ /** Fan-out concurrency width applied to the gate's proxy children. */
37
+ maxParallel?: number;
38
+ /** Fan-out fail-fast policy applied to the gate's proxy children. */
39
+ failFast?: boolean;
40
+ /** The gate's own wall-clock timeout in ms (orchestrator-swept). */
41
+ timeoutMs?: number;
42
+ }
43
+ /** Extract the invoke parameters from a lock job, or undefined when it is not a gate. */
44
+ export declare function invokeParamsFromLockJob(lockJob: Pick<LockJob, 'invoke' | 'maxParallel' | 'failFast' | 'timeout'> & {
45
+ invoke?: LockInvoke;
46
+ }): InvokeGateParams | undefined;
47
+ /** True when a lock job is an invoke gate (carries an `invoke` action). */
48
+ export declare function isInvokeGate(lockJob: Pick<LockJob, 'invoke'>): boolean;
49
+ /**
50
+ * A summon that was REFUSED, as opposed to one that matched nothing.
51
+ *
52
+ * The two are not the same verdict and must never render the same. A gate
53
+ * carrying `optional: true` treats zero summoned runs as a green skip, so a
54
+ * refusal returned as `[]` would report success for work that was declined.
55
+ * Throwing this makes the confusion unrepresentable: {@link runInvokeGate}
56
+ * fails the gate job with the reason, whatever order its own checks run in.
57
+ */
58
+ export declare class SummonRefusedError extends Error {
59
+ constructor(reason: string);
60
+ }
61
+ /** One source-repo run summoned by an invoke gate. */
62
+ export interface SummonedRun {
63
+ /** The spawned run's id. */
64
+ runId: string;
65
+ /** The source repo the run belongs to (`owner/repo`). */
66
+ repo: string;
67
+ /** The subscribing workflow's name. */
68
+ workflow: string;
69
+ }
70
+ /** Arguments to the injected `summon` callback. */
71
+ export interface SummonArgs {
72
+ event: string;
73
+ payload?: Record<string, unknown>;
74
+ sourceRepo: string;
75
+ /** The chain depth to stamp on the summoned runs (already incremented). */
76
+ chainDepth: number;
77
+ /** The summoning gate's run id — tag each spawned run so its proxy resolves. */
78
+ summonedByRunId: string;
79
+ }
80
+ /** Dependencies for {@link runInvokeGate}. */
81
+ export interface InvokeGateDeps {
82
+ db: Kysely<Database>;
83
+ /** Insert the proxy rows + mark the gate terminal on a zero-match emit. */
84
+ executionTracker: Pick<ExecutionTracker, 'addJobsToRun' | 'onJobStatus' | 'reconcileSummonedRunIfTerminal'>;
85
+ /**
86
+ * Match the source repo's subscribers to `event`, dispatch each as a normal
87
+ * in-repo run, and return the spawned run ids. Implemented at the composition
88
+ * root, where it reuses the orchestrator's event match + trust-policy path.
89
+ *
90
+ * An empty array means the event matched no subscriber — a real outcome a
91
+ * gate may declare `optional`. A REFUSAL raises
92
+ * {@link SummonRefusedError} instead, so the two can never be read as the
93
+ * same thing.
94
+ */
95
+ summon: (args: SummonArgs) => Promise<SummonedRun[]>;
96
+ /** Max chain depth allowed for a summon (the circuit breaker's bound). */
97
+ maxChainDepth: number;
98
+ }
99
+ /** Arguments identifying the gate to run. */
100
+ export interface RunInvokeGateArgs {
101
+ runId: string;
102
+ gateJobId: string;
103
+ gateJobName: string;
104
+ event: string;
105
+ payload?: Record<string, unknown>;
106
+ /** When true, a zero-subscriber emit succeeds; when false it fails the gate. */
107
+ optional: boolean;
108
+ sourceRepo: string;
109
+ chainDepth: number;
110
+ /** Fan-out concurrency width stamped on the proxy children (`maxParallel`). */
111
+ maxParallel?: number;
112
+ /** Fan-out fail-fast policy stamped on the proxy children (`failFast`). */
113
+ failFast?: boolean;
114
+ }
115
+ /**
116
+ * Aggregate an invoke gate's status from its proxies' terminal statuses. The
117
+ * gate stays open (`allTerminal: false`) until every proxy is terminal; then it
118
+ * is `failed` if any proxy is a failure, else `success`. Pure — the tracker's
119
+ * aggregation hook drives the DB reads and the gate transition.
120
+ */
121
+ export declare function aggregateGateStatus(proxyStatuses: readonly string[]): {
122
+ allTerminal: boolean;
123
+ status?: ExecutionJobStatus;
124
+ };
125
+ /**
126
+ * The message a gate naming a reserved event fails with.
127
+ *
128
+ * Exported so the gate executor and its test state the same string, and so the
129
+ * SDK-side error can be compared against it.
130
+ */
131
+ export declare function reservedEventMessage(event: string, prefix: string): string;
132
+ /** The message a require-by-default gate fails with when nothing subscribes. */
133
+ export declare function zeroSubscriberMessage(sourceRepo: string, event: string): string;
134
+ /**
135
+ * The proxy job name for a summoned run — the gate name, a `repo:workflow`
136
+ * variant, and the summoned run id. The run id makes the name **unique per run**:
137
+ * two summoned runs with the same `repo`+`workflow` (duplicate registrations)
138
+ * would otherwise share one `job_name`, so the mirror's `job_name` lookup would
139
+ * resolve one proxy and leave the other hanging. The human-readable label for the
140
+ * graph is carried separately in `variant_label` (`repo:workflow`).
141
+ */
142
+ export declare function proxyJobName(gateJobName: string, run: SummonedRun): string;
143
+ /**
144
+ * Run an invoke gate: summon the source repo's subscribers and create one proxy
145
+ * child per spawned run. A zero-subscriber emit terminalizes the gate — failed
146
+ * by default, success under `optional`. When the chain-depth bound is reached
147
+ * the gate fails without summoning, so an invoke chain cannot loop.
148
+ */
149
+ export declare function runInvokeGate(deps: InvokeGateDeps, args: RunInvokeGateArgs): Promise<void>;
150
+ /** Dependencies for {@link releaseInvokeGate}. */
151
+ export interface ReleaseInvokeGateDeps {
152
+ db: Kysely<Database>;
153
+ executionTracker: ExecutionTracker;
154
+ invokeGateDeps: InvokeGateDeps;
155
+ }
156
+ /**
157
+ * Release a ready invoke gate: swap its synthetic needs-pending row for a real
158
+ * `gate` job row, then run the gate executor. Reads the summoning run's source
159
+ * repo and chain depth from `execution_runs` (the chain depth bounds recursion).
160
+ * Shared by the needs-release path (`dispatchReadyJob`) and the root-gate
161
+ * post-registration nudge, so both go through one code path.
162
+ */
163
+ export declare function releaseInvokeGate(deps: ReleaseInvokeGateDeps, runId: string, gateJobName: string, params: InvokeGateParams): Promise<void>;
164
+ //# sourceMappingURL=invoke-gate.d.ts.map
@@ -3,43 +3,42 @@
3
3
  *
4
4
  * A job binds an ordered list of contexts (`LockJob.contexts`). This
5
5
  * module resolves that list into concrete context names (static values
6
- * verbatim, pure-inline dynamic elements evaluated against the event) and folds
7
- * the per-context secrets/variables last-wins. It keeps the heavy fold logic
6
+ * verbatim; every dynamic element is resolved by the agent's init job) and
7
+ * folds the per-context secrets/variables last-wins. It keeps the heavy fold logic
8
8
  * out of `dispatchMatchedWorkflow`, which must stay under the function-length cap.
9
9
  */
10
10
  import { type Context, type HostFacts, type LockJob } from '@kici-dev/engine';
11
11
  import type { SecretResolverApi } from '../secrets/secret-resolver.js';
12
12
  import type { VariableStore } from '../contexts/variable-store.js';
13
13
  /**
14
- * Placeholder written into the persisted bound-env list for an impure dynamic
15
- * element the orchestrator cannot resolve at dispatch. The agent's init eval
16
- * later overwrites the list with the resolved name.
14
+ * Placeholder written into the persisted bound-env list for a dynamic element
15
+ * the orchestrator does not resolve at dispatch. The agent's init eval later
16
+ * overwrites the list with the resolved name.
17
17
  */
18
18
  export declare const DYNAMIC_ENV_PLACEHOLDER = "(dynamic)";
19
19
  /** Ordered resolved context names plus whether any element still needs agent init. */
20
20
  export interface ResolvedJobContexts {
21
- /** Resolved static + pure-inline names, in order. */
21
+ /** Resolved static names, in order. */
22
22
  names: string[];
23
- /** True when an impure dynamic element must be resolved by an agent init job. */
23
+ /** True when a dynamic element must be resolved by an agent init job. */
24
24
  needsInit: boolean;
25
25
  }
26
26
  /**
27
27
  * Resolve the ordered bound-context names from a lock job. Static elements
28
- * use their value verbatim; pure-inline dynamic elements use the matching
29
- * pre-evaluated inline name (aligned by index); an impure dynamic element cannot
30
- * be resolved here and flags `needsInit`.
28
+ * use their value verbatim; any dynamic element (inline or impure) is resolved
29
+ * by the agent's init job and flags `needsInit`.
31
30
  */
32
- export declare function resolveJobContextNames(lockJob: LockJob, inlineNames: ReadonlyArray<string | undefined>): ResolvedJobContexts;
31
+ export declare function resolveJobContextNames(lockJob: LockJob): ResolvedJobContexts;
33
32
  /**
34
33
  * Build the ordered bound-context display list for persistence at dispatch.
35
34
  * Unlike {@link resolveJobContextNames}, this never drops an unresolved
36
- * element: a static element uses its value, a pure-inline element uses its
37
- * resolved name when known, and any element the orchestrator cannot resolve at
38
- * dispatch (impure dynamic, or an unresolved pure-inline) becomes the
39
- * `(dynamic)` placeholder so the persisted column reflects every declared
40
- * slot in order. Returns an empty array when the job binds no context.
35
+ * element: a static element uses its value, and any dynamic element (which the
36
+ * orchestrator no longer resolves at dispatch) becomes the `(dynamic)`
37
+ * placeholder so the persisted column reflects every declared slot in order.
38
+ * The deferred-init flow-back overwrites the placeholder once the agent
39
+ * resolves the name. Returns an empty array when the job binds no context.
41
40
  */
42
- export declare function buildJobContextDisplayNames(lockJob: LockJob, inlineNames: ReadonlyArray<string | undefined>): string[];
41
+ export declare function buildJobContextDisplayNames(lockJob: LockJob): string[];
43
42
  /** Merged secrets/variables across an ordered list of resolved contexts. */
44
43
  export interface MultiEnvMergedData {
45
44
  contextVars?: Record<string, string>;