@kici-dev/orchestrator 0.1.21 → 0.1.23

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 (53) hide show
  1. package/dist/agent/dispatcher.d.ts +44 -0
  2. package/dist/agent/host-roster-reaper.d.ts +2 -1
  3. package/dist/agent/host-roster.d.ts +57 -3
  4. package/dist/agent/token-store.d.ts +29 -0
  5. package/dist/approvals/step-approval-bridge.d.ts +5 -0
  6. package/dist/cli/commands/source-manifest.d.ts +8 -0
  7. package/dist/cli/service/compose.d.ts +13 -0
  8. package/dist/cli/service/deploy-env.d.ts +31 -0
  9. package/dist/cli.js +687 -219
  10. package/dist/config.d.ts +6 -0
  11. package/dist/dashboard/needs-edges.d.ts +4 -3
  12. package/dist/db/migrations/047_needs_run_on.d.ts +4 -0
  13. package/dist/db/migrations/048_host_reboot_pending.d.ts +19 -0
  14. package/dist/db/migrations/049_held_runs_payload.d.ts +14 -0
  15. package/dist/db/migrations/050_sources_slug.d.ts +17 -0
  16. package/dist/db/migrations/051_binding_host_pattern.d.ts +18 -0
  17. package/dist/db/migrations/052_host_reach_metadata.d.ts +19 -0
  18. package/dist/db/migrations/053_agent_token_single_use.d.ts +17 -0
  19. package/dist/db/types.d.ts +50 -4
  20. package/dist/deployment/deployment-identity.d.ts +9 -0
  21. package/dist/entry-helpers.d.ts +7 -0
  22. package/dist/environments/binding-store.d.ts +12 -3
  23. package/dist/environments/held-runs.d.ts +31 -1
  24. package/dist/github-app-name-refresher/github-app-name-refresher.d.ts +77 -0
  25. package/dist/index.js +21 -2
  26. package/dist/metrics/prometheus.d.ts +2 -0
  27. package/dist/orchestrator-core.d.ts +13 -1
  28. package/dist/pipeline/decorating-secret-resolver.d.ts +32 -0
  29. package/dist/pipeline/dispatch-matched-workflow.d.ts +132 -5
  30. package/dist/pipeline/install-secrets-resolver.d.ts +2 -2
  31. package/dist/pipeline/needs-scheduler.d.ts +22 -13
  32. package/dist/pipeline/processor.d.ts +2 -2
  33. package/dist/pipeline/test-pipeline.d.ts +37 -59
  34. package/dist/providers/github/manifest.d.ts +25 -0
  35. package/dist/reporting/log-writer.d.ts +19 -0
  36. package/dist/routes/admin-sources.d.ts +7 -0
  37. package/dist/scaler/manager.d.ts +17 -0
  38. package/dist/secrets/secret-resolver.d.ts +33 -5
  39. package/dist/server.js +21664 -19907
  40. package/dist/sources/source-store.d.ts +4 -0
  41. package/dist/sources/source-validator.d.ts +2 -0
  42. package/dist/stale-detector/reboot-deadline-sweep.d.ts +29 -0
  43. package/dist/standalone.js +25263 -24075
  44. package/dist/ws/agent-handler.d.ts +11 -6
  45. package/dist/ws/bringup-api.d.ts +76 -0
  46. package/dist/ws/dashboard-fleet-handler.d.ts +43 -0
  47. package/dist/ws/dashboard-fleet-write-handler.d.ts +60 -0
  48. package/dist/ws/fleet-runs-on-all.d.ts +19 -0
  49. package/dist/ws/platform-client.d.ts +16 -1
  50. package/dist/ws/test-relay-handlers.d.ts +13 -2
  51. package/installer-image-digests.json +3 -3
  52. package/package.json +4 -4
  53. package/sbom.spdx.json +50 -50
@@ -13,11 +13,13 @@
13
13
  * exported function is a narrative orchestrator that threads the typed
14
14
  * results through the pipeline.
15
15
  */
16
- import { CacheRefScope } from '@kici-dev/engine';
17
- import type { LabelMatcher, LockWorkflow, LockJob, SimulatedEvent, WorkflowDecision, MaterializedJob } from '@kici-dev/engine';
16
+ import { ExecutionJobStatus, InitFailureCategory, CacheRefScope } from '@kici-dev/engine';
17
+ import type { LabelMatcher, LockWorkflow, LockJob, HostTargetSelector, SimulatedEvent, WorkflowDecision, MaterializedJob, ResolvedHostAgent, HostFacts } from '@kici-dev/engine';
18
18
  import { type HostRosterStore } from '../agent/host-roster.js';
19
19
  import type { WebhookInfo } from '../webhook/handler.js';
20
20
  import type { ProviderBundle } from '../provider-registry.js';
21
+ import type { Dispatcher } from '../agent/dispatcher.js';
22
+ import type { QueuedJobInput } from '../queue/job-queue.js';
21
23
  import type { TrustResolution } from '../security/trust-resolver.js';
22
24
  import { type ProcessingDeps } from './processor.js';
23
25
  /**
@@ -41,7 +43,15 @@ export declare function deriveCacheRefScope(trust: TrustResolution | undefined):
41
43
  export interface WorkflowDispatchContext {
42
44
  info: WebhookInfo;
43
45
  deps: ProcessingDeps;
44
- bundle: ProviderBundle;
46
+ /**
47
+ * Provider bundle for the matched source. Undefined for local-repo test runs
48
+ * (`kici run` against an inline lock file with no remote provider): in that
49
+ * mode there is no clone-url builder / check-status poster / clone-token
50
+ * provider, and `repoUrl` falls back to `''` (the agent treats a missing url
51
+ * as a local/`fullRepo` clone). The webhook adapter always passes a defined
52
+ * bundle, so its dispatch behavior is unchanged.
53
+ */
54
+ bundle?: ProviderBundle;
45
55
  payload: unknown;
46
56
  repoIdentifier: string;
47
57
  credentials: Record<string, unknown>;
@@ -88,10 +98,42 @@ export interface WorkflowDispatchContext {
88
98
  * correct clone + logging.
89
99
  */
90
100
  extraJobConfig?: Record<string, unknown>;
101
+ /**
102
+ * Test-run provenance. Present only for `kici run` / test-trigger dispatches.
103
+ * When set, `recordRunStart` stamps `is_test_run = true` and
104
+ * `fixture_id = testRun.fixtureId` on the `execution_runs` row. Undefined for
105
+ * webhook runs (the stamp block is skipped).
106
+ */
107
+ testRun?: {
108
+ fixtureId: string;
109
+ };
110
+ /**
111
+ * Run-wide flat secrets layered onto EVERY dispatched job's `jobConfig.secrets`
112
+ * (env-declaring or not). Used by the test path to deliver `kici run --secret`
113
+ * / `--env` CLI flat secrets, which must reach a job regardless of whether it
114
+ * declares an `environment:`. Merged UNDER the per-job env-resolved secrets so
115
+ * the CLI value wins on a key collision (matching the prior B1-env -> A-CLI
116
+ * precedence). Undefined for webhook runs.
117
+ */
118
+ runWideFlatSecrets?: Record<string, string>;
119
+ /**
120
+ * Runtime host narrowing from `kici run --target` (Ansible `--limit`). Applied
121
+ * as a post-filter over each runsOnAll job's matched roster: effective hosts =
122
+ * runsOnAll ∩ target. Narrow-only. Undefined for webhook runs (no narrowing).
123
+ */
124
+ target?: HostTargetSelector;
125
+ /**
126
+ * Resolved (coerced + defaulted + validated) workflow-dispatch inputs from
127
+ * `kici run --input`. Carried onto every dispatched job's request so the agent
128
+ * exposes them as `ctx.dispatchInputs`. Undefined for webhook runs.
129
+ */
130
+ dispatchInputs?: Record<string, unknown>;
91
131
  }
92
132
  export interface DispatchMatchedWorkflowResult {
93
133
  /** Number of jobs successfully dispatched (non-rejected). */
94
134
  dispatchedJobCount: number;
135
+ /** Execution job ids of every dispatched/tracked job (root, gated, synthetic). */
136
+ dispatchedJobIds: string[];
95
137
  /** True when the workflow install gate paused the dispatch (held run). */
96
138
  held?: boolean;
97
139
  }
@@ -110,6 +152,77 @@ export interface DispatchMatchedWorkflowOptions {
110
152
  */
111
153
  reuseRunId?: string;
112
154
  }
155
+ type DispatchFn = (input: QueuedJobInput) => ReturnType<Dispatcher['dispatch']>;
156
+ interface DispatchSetup {
157
+ /** Wrapped dispatcher that injects ctx.extraJobConfig into every dispatch. */
158
+ dispatcher: {
159
+ dispatch: DispatchFn;
160
+ };
161
+ /** WebhookInfo overlaid with effective routing key + provider. */
162
+ info: WebhookInfo;
163
+ /** Composite delivery id on cross-source, otherwise info.deliveryId. */
164
+ effectiveDeliveryId: string;
165
+ workflowConcurrency: {
166
+ cancelInProgress?: boolean;
167
+ max?: number;
168
+ } | undefined;
169
+ workflowTimeoutMs: number | undefined;
170
+ /**
171
+ * Run mode for idempotent steps (`apply` | `check` | `check-fail-on-drift`),
172
+ * carried on `ctx.extraJobConfig` by the test-trigger / `kici run --check`
173
+ * path. Persisted onto the `execution_runs` row so `computeRunStatus` can
174
+ * fail a `check-fail-on-drift` run that detected drift. Undefined for the
175
+ * default apply-mode webhook path.
176
+ */
177
+ checkMode: string | undefined;
178
+ }
179
+ interface RejectedJob {
180
+ jobId: string;
181
+ jobName: string;
182
+ reason: string;
183
+ /** Explicit init-failure category override; inferred from reason when absent. */
184
+ category?: InitFailureCategory;
185
+ /**
186
+ * Terminal status to record for this job. Defaults to `failed`. A zeroed
187
+ * `runsOnAll` that intentionally narrowed to no hosts is recorded as `skipped`
188
+ * (no init-failure) so its downstreams' `when` sets govern propagation.
189
+ */
190
+ terminalStatus?: ExecutionJobStatus;
191
+ }
192
+ /**
193
+ * Build the QueuedJobInput for a synthetic `__bringup__` job: the orchestrator
194
+ * dispatches one per declared-but-un-agented `includeUninitialized` child to an
195
+ * agent holding `kici:capability:ssh-transport`, which runs the agent-side
196
+ * `ensureInitRunner(targetAgentId)` over SSH. The init-runner then connects
197
+ * under `targetAgentId`, and the child's pinned-hold (already queued with that
198
+ * pin) drains its bootstrap steps onto it. The bring-up job clones nothing and
199
+ * runs no sandbox (`bringupOnly`).
200
+ */
201
+ export declare function buildBringupJobInput(args: {
202
+ ctx: WorkflowDispatchContext;
203
+ setup: DispatchSetup;
204
+ targetAgentId: string;
205
+ }): QueuedJobInput;
206
+ /**
207
+ * Phase B orchestrator: probe caches, dispatch the build job (if needed),
208
+ * and surface enough state for downstream phases to skip / continue
209
+ * appropriately.
210
+ */
211
+ /**
212
+ * Materialize each static job's matrix into dispatchable children. A job whose
213
+ * matrix is invalid (zero combinations / over the cap) is dropped from the
214
+ * dispatch list and recorded as a `matrix_expansion` matrix failure so the run's
215
+ * other jobs still proceed. Dynamic-matrix jobs pass through with a
216
+ * `pendingDynamicMatrix` marker for the eval flow.
217
+ */
218
+ /**
219
+ * Resolve a `runsOnAll` lock job against the declared host roster and partition
220
+ * the matched hosts into the target set per the `onUnreachable` policy (R2):
221
+ * `ready` hosts always run; unreachable durable (`static`) hosts hold / fail /
222
+ * skip; stale ephemeral hosts are always skipped. Throws {@link FanoutError}
223
+ * when the run can't proceed (fail policy with an absent host, or zero targets).
224
+ */
225
+ export declare function resolveHostFanoutTargets(lockJob: LockJob, deps: ProcessingDeps, target?: HostTargetSelector): Promise<ResolvedHostAgent[]>;
113
226
  /** Exact labels + regex patterns partitioned from a lock job's selectors. */
114
227
  interface JobRoutingSelectors {
115
228
  runsOnLabels: string[];
@@ -160,8 +273,9 @@ export interface WavePlan {
160
273
  * Compute the rolling-wave plan for a materialized job set.
161
274
  *
162
275
  * For each base job declaring `maxParallel` whose fan-out produced more than one
163
- * child, children are ordered deterministically by `variant_label` (the matrix
164
- * suffix / hostname, via `expandedName`) and every child at index `>=
276
+ * child, children are ordered deterministically by `fanoutIndex` (the
277
+ * agentId / variant-label rank assigned at materialization; falling back to
278
+ * `expandedName` for children with no index) and every child at index `>=
165
279
  * maxParallel` is held (`wave_gated=true`). The first `maxParallel` dispatch
166
280
  * immediately; held children release one-per-terminal via the wave-scheduler.
167
281
  * Every child of a bounded-wave base — held or not — gets a `policy` entry so
@@ -169,6 +283,19 @@ export interface WavePlan {
169
283
  * job (single child) or one without `maxParallel` contributes nothing.
170
284
  */
171
285
  export declare function computeWavePlan(materializedJobs: readonly MaterializedJob[]): WavePlan;
286
+ export declare function materializeStaticJobsSafe(staticJobs: readonly LockJob[], deps: ProcessingDeps, target?: HostTargetSelector): Promise<{
287
+ materializedJobs: MaterializedJob[];
288
+ expansionMap: Map<string, readonly string[]>;
289
+ matrixFailures: RejectedJob[];
290
+ }>;
291
+ /**
292
+ * Build the per-host secret-resolution context for a materialized child. A
293
+ * `runsOnAll` host child carries its identity on `mat.agent` (preferred) or
294
+ * `mat.pinnedAgentId`/`mat.host`; a non-host child (matrix/static) has none, so
295
+ * resolution stays fleet-wide (`'**'`-only). Returns `undefined` when there are
296
+ * no host facts to scope by.
297
+ */
298
+ export declare function hostCtxFromMat(mat: MaterializedJob): HostFacts | undefined;
172
299
  export interface GeneratedJobConfig {
173
300
  /**
174
301
  * The generated lock job with its `name` and `needs` rewritten to expanded
@@ -31,7 +31,7 @@
31
31
  */
32
32
  import type { ApproverClause, LockRegistry } from '@kici-dev/engine';
33
33
  import type { TrustResolution } from '../security/trust-resolver.js';
34
- import type { SecretResolver } from '../secrets/secret-resolver.js';
34
+ import type { SecretResolverApi } from '../secrets/secret-resolver.js';
35
35
  import type { EnvironmentStore } from '../environments/environment-store.js';
36
36
  import { type JobDispatchContext } from '../environments/protection/pipeline.js';
37
37
  /** Registry spec carried on the dispatch message (token already resolved). */
@@ -48,7 +48,7 @@ export interface ResolveInstallSecretsArgs {
48
48
  resolvedOrgId: string;
49
49
  trustResolution: TrustResolution | undefined;
50
50
  environmentStore: EnvironmentStore | undefined;
51
- secretResolver: SecretResolver | undefined;
51
+ secretResolver: SecretResolverApi | undefined;
52
52
  protectionContext: JobDispatchContext;
53
53
  /**
54
54
  * Resume path: skip the protection-rule gate (already satisfied) and resolve
@@ -1,24 +1,29 @@
1
1
  /**
2
- * DB-backed needs-aware dispatch scheduler ( through).
2
+ * DB-backed needs-aware dispatch scheduler.
3
3
  *
4
- * This module is the core of 's behavioral change: it replaces the
5
- * "concurrent dispatch" model with event-driven scheduling that gates ALL
6
- * needs edges (static-to-static, static-to-dyn-group, dyn-to-static, dyn-to-dyn).
4
+ * Gates ALL needs edges (static-to-static, static-to-dyn-group, dyn-to-static,
5
+ * dyn-to-dyn) with event-driven scheduling instead of concurrent dispatch.
7
6
  *
8
7
  * The scheduler is pure DB — no in-memory state. Every scheduling decision is
9
8
  * a fresh DB query against execution_jobs + execution_job_needs. This means
10
9
  * zero recovery code on orchestrator restart.
11
10
  *
11
+ * Each edge carries a `run_on` status-set (the upstream terminal statuses that
12
+ * satisfy the edge). A downstream edge is dispatch-satisfied when the upstream's
13
+ * terminal status is a member of the edge's run_on set; otherwise the downstream
14
+ * is skipped. A downstream dispatches only when every edge is satisfied.
15
+ *
12
16
  * Entry points:
13
17
  * - insertEdgesForRun: called at run start for static-to-static edges
14
18
  * - resolveGroupEdges: called on dynamic-eval completion
15
19
  * - evaluateDownstreams: called from onJobStatus(terminal)
16
20
  * - recomputeNeedsSatisfied: batch recompute after group resolution
17
- * - checkSchedulerInvariant: Layer 3 defensive check
21
+ * - checkSchedulerInvariant: defensive stuck-job check
18
22
  * - getFailurePropagationTargets: cascade for transitive skip
19
23
  */
20
24
  import type { Kysely } from 'kysely';
21
25
  import type { Database } from '../db/types.js';
26
+ import { ExecutionJobStatus } from '@kici-dev/engine';
22
27
  import type { MaterializedJob } from '@kici-dev/engine';
23
28
  /** Result of evaluating downstream jobs after an upstream completes. */
24
29
  export interface SchedulerResult {
@@ -48,21 +53,22 @@ export declare function insertEdgesForRun(db: Kysely<Database>, runId: string, j
48
53
  * edge row. Empty groups (0 members) trigger immediate needs_satisfied=true
49
54
  * for dependents.
50
55
  *
51
- * CRITICAL: dependentStaticJobs carries per-job ifFailed policy from the
56
+ * CRITICAL: dependentStaticJobs carries the per-job run_on status-set from the
52
57
  * NeedsGroupEntry in the lock file. Without this, all group edges would
53
- * silently default to 'skip'.
58
+ * silently default to success-only.
54
59
  */
55
60
  export declare function resolveGroupEdges(db: Kysely<Database>, runId: string, groupName: string, memberJobNames: string[], dependentStaticJobs: Array<{
56
61
  jobName: string;
57
- ifFailed: 'skip' | 'run';
62
+ runOn: ExecutionJobStatus[];
58
63
  }>): Promise<void>;
59
64
  /**
60
65
  * Evaluate downstream jobs after an upstream reaches terminal state.
61
66
  *
62
67
  * This is the core scheduler hook. For each downstream of the completed job:
63
- * 1. If upstream failed and edge has if_failed='skip', mark downstream as 'skip'
64
- * 2. Otherwise, check if ALL upstreams are terminal
65
- * 3. If all satisfied, mark needs_satisfied=true and return for dispatch
68
+ * 1. If the completed status is not in this edge's run_on set, mark the
69
+ * downstream as 'skip' immediately.
70
+ * 2. Otherwise, check if ALL upstreams are terminal and satisfied.
71
+ * 3. If all satisfied, mark needs_satisfied=true and return for dispatch.
66
72
  */
67
73
  export declare function evaluateDownstreams(db: Kysely<Database>, runId: string, completedJobName: string, completedStatus: string): Promise<SchedulerResult[]>;
68
74
  /**
@@ -80,8 +86,11 @@ export declare function recomputeNeedsSatisfied(db: Kysely<Database>, runId: str
80
86
  */
81
87
  export declare function checkSchedulerInvariant(db: Kysely<Database>, runId: string): Promise<string[]>;
82
88
  /**
83
- * cascade: find all transitive downstreams that should be skipped
84
- * due to failure propagation. Only follows edges where if_failed='skip'.
89
+ * Failure-propagation cascade: find all transitive downstreams that should be
90
+ * skipped because a terminal upstream's status is not in their edge's run_on
91
+ * set. At each hop, the propagating job's actual terminal status decides which
92
+ * downstream edges propagate (status not in run_on → the downstream skips and
93
+ * propagates further).
85
94
  */
86
95
  export declare function getFailurePropagationTargets(db: Kysely<Database>, runId: string, failedJobName: string): Promise<string[]>;
87
96
  //# sourceMappingURL=needs-scheduler.d.ts.map
@@ -33,7 +33,7 @@ import type { HostRosterStore } from '../agent/host-roster.js';
33
33
  import type { RunCoordinator } from '../cluster/coordinator.js';
34
34
  import type { TeamMembershipLookup } from '../approvals/team-membership-lookup.js';
35
35
  import type { LogStorage } from '../reporting/log-storage.js';
36
- import type { SecretResolver } from '../secrets/secret-resolver.js';
36
+ import type { SecretResolverApi } from '../secrets/secret-resolver.js';
37
37
  import type { ContributorCache } from '../security/contributor-cache.js';
38
38
  import type { AccessLogWriter } from '../audit/access-log.js';
39
39
  import type { LockFile as FullLockFile, LockWorkflow, SimulatedEvent, WebhookNormalizer } from '@kici-dev/engine';
@@ -281,7 +281,7 @@ export interface ProcessingDeps {
281
281
  /** Run coordinator for multi-orchestrator job routing. Optional -- if not set, all jobs dispatch locally (single-orchestrator mode). */
282
282
  coordinator?: RunCoordinator;
283
283
  /** Secret resolver for dispatch-time secret resolution. Optional -- if not set, secrets are not resolved. */
284
- secretResolver?: SecretResolver;
284
+ secretResolver?: SecretResolverApi;
285
285
  /** Optional callback when source locations are extracted from a lock file workflow. */
286
286
  onSourceLocationsExtracted?: (workflowName: string, jobName: string, sourceLocations: Array<{
287
287
  file: string;
@@ -1,36 +1,28 @@
1
1
  /**
2
- * Test pipeline processor for CLI-initiated test runs.
2
+ * Test-trigger adapter for CLI-initiated runs (`kici run`).
3
3
  *
4
- * Reuses the existing webhook processing pipeline steps (lock file fetch,
5
- * trigger matching, job dispatch) but injects a synthetic event instead
6
- * of processing a real webhook. This avoids the anti-pattern of calling
7
- * processWebhook() directly (which expects WebhookInfo from a real webhook).
4
+ * Resolves the lock file, matched decisions, and fixture-specific concerns
5
+ * (inline-vs-provider lock, decision selection, fixture-payload storage, the
6
+ * `allow_local_execution` environment gate, CLI-secret overlay, in-memory
7
+ * test-run marking), then dispatches each matched workflow through the SAME
8
+ * shared core as webhooks (`dispatchMatchedWorkflow`). The test path is a thin
9
+ * adapter — needs-DAG scheduling, `expansionMap` fan-out edges, `runsOnAll`
10
+ * host fan-out, and deferred init/dynamic dispatch all come from the core.
8
11
  *
9
- * Key differences from processWebhook():
10
- * - Skips dedup check (test runs are always unique)
11
- * - Skips webhook normalization (event is already in SimulatedEvent shape)
12
- * - Skips repo extraction from payload (routing key is provided directly)
13
- * - deliveryId has `test:` prefix for identification
14
- * - Execution runs are marked with is_test_run=true and fixture_id
15
- * - Supports direct workflow execution (bypass trigger matching)
12
+ * Differences from a webhook run:
13
+ * - the synthetic event is injected directly (no provider normalization/dedup);
14
+ * - the lock file may be inline (local repos have no remote provider, so
15
+ * `bundle` is undefined);
16
+ * - `deliveryId` carries a `test:` prefix;
17
+ * - the run is stamped `is_test_run = true` + `fixture_id` (via the core's
18
+ * `testRun` meta) and marked in-memory for live-log broadcast to the CLI;
19
+ * - CLI-uploaded local secrets win over orchestrator env secrets (the
20
+ * decorating secret resolver), and the fixture's `secrets` context mapping
21
+ * resolves into namespaced secrets;
22
+ * - direct workflow execution (bypass trigger matching) is supported.
16
23
  */
17
- import type { LockFileCache } from '../lockfile-cache.js';
18
- import type { Dispatcher } from '../agent/dispatcher.js';
19
- import type { CheckRunReporter } from '../reporting/check-run-reporter.js';
20
- import type { ExecutionTracker } from '../reporting/execution-tracker.js';
21
- import type { AgentRegistry } from '../agent/registry.js';
22
- import type { ProviderRegistry } from '../provider-registry.js';
23
- import type { SourceCache } from '../cache/index.js';
24
- import type { BuildCoordinator } from '../cache/index.js';
25
- import type { DepCache } from '../cache/index.js';
26
- import type { PendingBuildTracker } from '../cache/index.js';
27
- import type { SecretResolver } from '../secrets/secret-resolver.js';
28
- import type { EnvironmentStore } from '../environments/environment-store.js';
29
- import type { VariableStore } from '../environments/variable-store.js';
30
- import type { LogStorage } from '../reporting/log-storage.js';
31
- import type { Kysely } from 'kysely';
32
- import type { Database } from '../db/types.js';
33
- import type { CheckMode } from '@kici-dev/engine';
24
+ import { type ProcessingDeps } from './processor.js';
25
+ import type { CheckMode, HostTargetSelector } from '@kici-dev/engine';
34
26
  /**
35
27
  * Input for a test trigger request.
36
28
  */
@@ -76,6 +68,17 @@ export interface TestTriggerInput {
76
68
  * Omitted means `apply`.
77
69
  */
78
70
  checkMode?: CheckMode;
71
+ /**
72
+ * Runtime host narrowing from `kici run --target`. Threaded onto the dispatch
73
+ * context, where it post-filters each runsOnAll job's matched roster.
74
+ */
75
+ target?: HostTargetSelector;
76
+ /**
77
+ * Raw operator-supplied `kici run --input KEY=VALUE` pairs (not coerced /
78
+ * defaulted). Validated + coerced + defaulted here against the matched
79
+ * workflow's lock dispatch descriptor before dispatch.
80
+ */
81
+ dispatchInputs?: Record<string, string>;
79
82
  }
80
83
  /**
81
84
  * Result of processing a test trigger.
@@ -91,37 +94,12 @@ interface TestTriggerResult {
91
94
  jobIds: string[];
92
95
  }
93
96
  /**
94
- * Dependencies for the test pipeline processor.
95
- * All injected for testability.
96
- */
97
- export interface TestPipelineDeps {
98
- lockFileCache: LockFileCache;
99
- dispatcher: Dispatcher;
100
- executionTracker?: ExecutionTracker;
101
- checkRunReporter?: CheckRunReporter;
102
- sourceCache?: SourceCache;
103
- buildCoordinator?: BuildCoordinator;
104
- depCache?: DepCache;
105
- pendingBuilds?: PendingBuildTracker;
106
- secretResolver?: SecretResolver;
107
- agentRegistry: AgentRegistry;
108
- providerRegistry: ProviderRegistry;
109
- /** Log storage for persisting test fixture payloads. Optional -- if not set, payload storage is skipped. */
110
- logStorage?: LogStorage;
111
- /** Database connection for environment protection checks. Optional. */
112
- db?: Kysely<Database>;
113
- /** Environment store for resolving environment ids in test dispatch parity. Optional. */
114
- environmentStore?: EnvironmentStore;
115
- /** Variable store for resolving environment variables in test dispatch parity. Optional. */
116
- variableStore?: VariableStore;
117
- }
118
- /**
119
- * Process a test trigger through the existing pipeline.
97
+ * Process a test trigger through the shared dispatch core.
120
98
  *
121
- * Reuses pipeline steps starting from lock file fetch, skipping webhook
122
- * normalization and dedup. Supports both trigger-matched and direct
123
- * workflow execution modes.
99
+ * Resolves the lock file + matched decisions + fixture concerns, then builds a
100
+ * `WorkflowDispatchContext` per matched workflow and calls
101
+ * `dispatchMatchedWorkflow` the same core the webhook path uses.
124
102
  */
125
- export declare function processTestTrigger(input: TestTriggerInput, deps: TestPipelineDeps): Promise<TestTriggerResult>;
103
+ export declare function processTestTrigger(input: TestTriggerInput, deps: ProcessingDeps): Promise<TestTriggerResult>;
126
104
  export {};
127
105
  //# sourceMappingURL=test-pipeline.d.ts.map
@@ -33,11 +33,21 @@ export interface GithubAppManifest {
33
33
  default_permissions: Record<string, string>;
34
34
  default_events: string[];
35
35
  }
36
+ /**
37
+ * Validate a self-hosted webhook URL supplied via `source add github
38
+ * --webhook-url`. Must be a well-formed absolute `https://` URL. Returns the URL
39
+ * verbatim on success; throws a clear error otherwise. The validated URL is
40
+ * baked into `manifest.hook_attributes.url` as-is — KiCI adds no ingress and
41
+ * does not receive events at it; the operator owns delivery.
42
+ */
43
+ export declare function validateWebhookUrl(value: string): string;
36
44
  export declare function buildGithubAppManifest(input: GithubManifestInput): GithubAppManifest;
37
45
  /** Credentials returned by GitHub's manifest-conversion endpoint. */
38
46
  export interface GithubAppCredentials {
39
47
  appId: string;
40
48
  slug: string;
49
+ /** GitHub's display name for the App (the authoritative stored name). */
50
+ name: string;
41
51
  privateKey: string;
42
52
  webhookSecret: string;
43
53
  clientId?: string;
@@ -52,6 +62,21 @@ export interface GithubAppCredentials {
52
62
  export declare function convertManifestCode(code: string, deps?: {
53
63
  octokit?: Pick<Octokit, 'request'>;
54
64
  }): Promise<GithubAppCredentials>;
65
+ /**
66
+ * Ask GitHub who this App is (`GET /app`, authenticated as the App via its
67
+ * JWT) and return its authoritative display `name` + `slug`. This is the single
68
+ * "fetch the App's identity from GitHub" helper, reused at source creation, by
69
+ * the daily refresher, and by `kici-admin source refresh`.
70
+ *
71
+ * GitHub is the source of truth: a rename in the GitHub UI changes the value
72
+ * `GET /app` returns, which is what keeps the dashboard name fresh.
73
+ */
74
+ export declare function fetchGithubAppIdentity(creds: Pick<GithubAppCredentials, 'appId' | 'privateKey'>, deps?: {
75
+ appOctokit?: Pick<Octokit, 'request'>;
76
+ }): Promise<{
77
+ name: string;
78
+ slug: string;
79
+ }>;
55
80
  /**
56
81
  * Poll GitHub (as the App, via a JWT) until at least one installation exists,
57
82
  * returning the first installation's id + account login. Throws on timeout.
@@ -21,7 +21,26 @@ export declare class LogWriter {
21
21
  private readonly logStorage;
22
22
  private readonly observerRegistry?;
23
23
  private readonly isTestRun?;
24
+ /**
25
+ * In-flight `logStorage.append` promises, keyed by runId. `appendChunk` is
26
+ * called fire-and-forget from the agent WS handler (it does not await the
27
+ * storage write), so a run can flip to a terminal status while the final
28
+ * log chunk's append is still pending. `drain(runId)` lets a reader (the
29
+ * test-run logs cursor endpoint) wait for those pending writes before it
30
+ * reports the stream as fully drained — without this, the `done` flag can
31
+ * be returned `true` while the last user-visible log line is not yet on
32
+ * disk, dropping it from a blocking `kici run remote` follow.
33
+ */
34
+ private readonly pendingAppends;
24
35
  constructor(deps: LogWriterDeps);
36
+ /**
37
+ * Await every in-flight log append for `runId` that was registered before
38
+ * this call. Settles even if an append rejected (errors are already logged
39
+ * by `appendChunk`); the point is ordering, not error propagation. New
40
+ * appends started after this call are not waited on — a terminal run emits
41
+ * no further chunks, so the snapshot taken at call time is complete.
42
+ */
43
+ drain(runId: string): Promise<void>;
25
44
  /**
26
45
  * Append log lines from an agent to storage in JSONL format.
27
46
  *
@@ -11,6 +11,7 @@
11
11
  import { Hono } from 'hono';
12
12
  import type { SourceStore } from '../sources/source-store.js';
13
13
  import type { Role } from '../secrets/rbac.js';
14
+ import { type FetchGithubAppIdentity } from '../github-app-name-refresher/github-app-name-refresher.js';
14
15
  interface SourceRouteDeps {
15
16
  sourceStore: SourceStore;
16
17
  /**
@@ -39,6 +40,12 @@ interface SourceRouteDeps {
39
40
  webhookUrl: string | null;
40
41
  webhookNote?: string;
41
42
  }>;
43
+ /**
44
+ * Fetch a GitHub App's authoritative `{ name, slug }` from GitHub. Injectable
45
+ * for tests; defaults to the real `fetchGithubAppIdentity`. Used by the
46
+ * `source refresh` route.
47
+ */
48
+ fetchAppIdentity?: FetchGithubAppIdentity;
42
49
  }
43
50
  type AdminSourcesEnv = {
44
51
  Variables: {
@@ -39,6 +39,23 @@ export interface ResolvedResources {
39
39
  memBytes: number;
40
40
  };
41
41
  }
42
+ /**
43
+ * Build the scaler-usage metric rows: one per active scaler (stamped with its
44
+ * backend type) plus a `__global__` rollup row. Pure so it is unit-testable
45
+ * without constructing a full ScalerManager.
46
+ */
47
+ export declare function buildScalerUsageRows(perScalerUsage: ReadonlyMap<string, {
48
+ cpus: number;
49
+ memBytes: number;
50
+ }>, globalUsage: {
51
+ cpus: number;
52
+ memBytes: number;
53
+ }, scalerTypeOf: (name: string) => string | undefined): Array<{
54
+ scaler: string;
55
+ scalerType?: string;
56
+ cpus: number;
57
+ memBytes: number;
58
+ }>;
42
59
  /**
43
60
  * Status summary for metrics and health endpoints.
44
61
  */
@@ -15,7 +15,7 @@
15
15
  * longest-path-wins uses scope path after stripping backend prefix.
16
16
  * audit log includes backend name.
17
17
  */
18
- import { type EnvironmentBinding, type ScopedSecret } from '@kici-dev/engine';
18
+ import { type EnvironmentBinding, type HostFacts, type ScopedSecret } from '@kici-dev/engine';
19
19
  import type { Logger } from '@kici-dev/shared';
20
20
  import type { AuditLogger } from './audit-logger.js';
21
21
  /**
@@ -62,6 +62,21 @@ export interface ResolvedSecretMeta {
62
62
  backend: string;
63
63
  scope: string;
64
64
  }
65
+ /**
66
+ * The public secret-resolution surface consumed by the dispatch path and the
67
+ * universal-git provider. `SecretResolver` implements this; the test adapter's
68
+ * `DecoratingSecretResolver` (CLI-secret overlay) implements it too, so either
69
+ * can flow through `ProcessingDeps.secretResolver`.
70
+ */
71
+ export interface SecretResolverApi {
72
+ resolveForJob(orgId: string, environmentName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
73
+ resolveNamed(orgId: string, scope: string, key: string, opts?: {
74
+ store?: string;
75
+ runId?: string;
76
+ jobId?: string;
77
+ }): Promise<string | null>;
78
+ resolveForJobWithMeta(orgId: string, environmentName: string, hostCtx?: HostFacts): Promise<Record<string, ResolvedSecretMeta>>;
79
+ }
65
80
  /**
66
81
  * Resolves secrets for a job by matching environment bindings against scoped secrets
67
82
  * from multiple backends.
@@ -71,7 +86,7 @@ export interface ResolvedSecretMeta {
71
86
  * are matched against binding patterns, and longest-path-wins uses the path AFTER
72
87
  * stripping the backend prefix.
73
88
  */
74
- export declare class SecretResolver {
89
+ export declare class SecretResolver implements SecretResolverApi {
75
90
  private readonly environmentStore;
76
91
  private readonly bindingStore;
77
92
  private readonly backendStores;
@@ -83,9 +98,13 @@ export declare class SecretResolver {
83
98
  *
84
99
  * @param orgId - Organization ID
85
100
  * @param environmentName - Environment name to resolve secrets for
101
+ * @param hostCtx - Optional fan-out child identity for per-host resolution.
102
+ * When supplied, each binding is gated by its `host_pattern` and its
103
+ * `scope_pattern` is templated per-child; when omitted, only fleet-wide
104
+ * (`'**'`) non-templated bindings contribute.
86
105
  * @returns Flat map of decrypted secret key-value pairs
87
106
  */
88
- resolveForJob(orgId: string, environmentName: string): Promise<Record<string, string>>;
107
+ resolveForJob(orgId: string, environmentName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
89
108
  /**
90
109
  * Resolve a single named secret by (orgId, scope, key), optionally scoped to
91
110
  * a specific backend. Bypasses environment bindings — this is a direct
@@ -111,7 +130,7 @@ export declare class SecretResolver {
111
130
  *
112
131
  * Returns the secret value along with which backend and scope provided it.
113
132
  */
114
- resolveForJobWithMeta(orgId: string, environmentName: string): Promise<Record<string, ResolvedSecretMeta>>;
133
+ resolveForJobWithMeta(orgId: string, environmentName: string, hostCtx?: HostFacts): Promise<Record<string, ResolvedSecretMeta>>;
115
134
  /**
116
135
  * Collect all secrets from all backend stores, prefixing scopes.
117
136
  * Per /: unreachable backends are tracked but not fatal here.
@@ -130,8 +149,17 @@ export declare class SecretResolver {
130
149
  */
131
150
  private checkScopedFailure;
132
151
  /**
133
- * Find the winning secret for a given key (highest scope depth after prefix strip).
152
+ * Find the winning secret for a given key, mirroring the engine scope
153
+ * resolver's host-aware matching and `(host specificity, scope depth)`
154
+ * precedence so the enriched metadata reports the same secret the flat
155
+ * resolution selected.
134
156
  */
135
157
  private findWinningSecret;
158
+ /**
159
+ * Resolve the effective scope pattern a binding contributes for a host,
160
+ * applying the host gate and per-child scope templating. Mirrors the engine
161
+ * scope resolver's `bindingScopeForHost`. Returns `null` to skip the binding.
162
+ */
163
+ private bindingScopeForHost;
136
164
  }
137
165
  //# sourceMappingURL=secret-resolver.d.ts.map