@kici-dev/orchestrator 0.1.22 → 0.1.24

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 (62) hide show
  1. package/dist/agent/host-roster.d.ts +39 -3
  2. package/dist/agent/token-store.d.ts +33 -0
  3. package/dist/app.d.ts +3 -0
  4. package/dist/cache/pending-inits.d.ts +3 -2
  5. package/dist/cli/api-client.d.ts +8 -0
  6. package/dist/cli/commands/attestations-reverify.d.ts +18 -0
  7. package/dist/cli/commands/attestations.d.ts +3 -0
  8. package/dist/cli/commands/shared/versioned-upgrade.d.ts +52 -0
  9. package/dist/cli.js +2284 -442
  10. package/dist/config.d.ts +2 -0
  11. package/dist/dashboard/attestation-filters.d.ts +70 -0
  12. package/dist/dashboard/handler.d.ts +33 -1
  13. package/dist/db/migrations/051_binding_host_pattern.d.ts +18 -0
  14. package/dist/db/migrations/052_host_reach_metadata.d.ts +19 -0
  15. package/dist/db/migrations/053_agent_token_single_use.d.ts +17 -0
  16. package/dist/db/migrations/054_local_working_tree.d.ts +15 -0
  17. package/dist/db/migrations/055_agent_token_mandatory_labels.d.ts +18 -0
  18. package/dist/db/migrations/056_execution_jobs_environments.d.ts +17 -0
  19. package/dist/db/migrations/057_step_concurrency.d.ts +4 -0
  20. package/dist/db/migrations/058_access_log_agent_label.d.ts +4 -0
  21. package/dist/db/migrations/059_attestation_verdict.d.ts +4 -0
  22. package/dist/db/migrations/060_run_trigger_actor.d.ts +4 -0
  23. package/dist/db/types.d.ts +78 -0
  24. package/dist/environments/binding-store.d.ts +12 -3
  25. package/dist/environments/held-runs.d.ts +24 -0
  26. package/dist/environments/protection/aggregate.d.ts +43 -0
  27. package/dist/environments/protection/satisfiability.d.ts +64 -0
  28. package/dist/index.js +9 -0
  29. package/dist/metrics/prometheus.d.ts +2 -0
  30. package/dist/orchestrator-core.d.ts +8 -0
  31. package/dist/pipeline/dispatch-matched-workflow.d.ts +60 -3
  32. package/dist/pipeline/flatten-lock-steps.d.ts +11 -0
  33. package/dist/pipeline/inline-eval.d.ts +2 -1
  34. package/dist/pipeline/job-environments.d.ts +71 -0
  35. package/dist/pipeline/manual-schedule.d.ts +22 -0
  36. package/dist/pipeline/test-pipeline.d.ts +15 -0
  37. package/dist/provenance/trust-root.d.ts +24 -0
  38. package/dist/provenance/verify-at-ingest.d.ts +24 -0
  39. package/dist/reporting/agent-failure-category.d.ts +27 -0
  40. package/dist/reporting/agent-run-result-mapper.d.ts +13 -0
  41. package/dist/reporting/execution-tracker.d.ts +22 -2
  42. package/dist/reporting/log-writer.d.ts +19 -0
  43. package/dist/reporting/run-aggregator.d.ts +161 -0
  44. package/dist/reporting/step-log-reader.d.ts +39 -0
  45. package/dist/routes/admin-registrations.d.ts +8 -0
  46. package/dist/routes/admin-runs.d.ts +7 -0
  47. package/dist/scaler/manager.d.ts +17 -0
  48. package/dist/secrets/index.d.ts +1 -1
  49. package/dist/secrets/secret-resolver.d.ts +19 -6
  50. package/dist/server.js +21535 -19131
  51. package/dist/standalone.js +26544 -24580
  52. package/dist/storage/loopback-guard.d.ts +49 -0
  53. package/dist/ws/agent-handler.d.ts +15 -1
  54. package/dist/ws/bringup-api.d.ts +76 -0
  55. package/dist/ws/dashboard-fleet-handler.d.ts +11 -1
  56. package/dist/ws/fleet-runs-on-all.d.ts +3 -0
  57. package/dist/ws/platform-client.d.ts +39 -1
  58. package/dist/ws/test-relay-handlers.d.ts +9 -0
  59. package/installer-image-digests.json +3 -3
  60. package/package.json +6 -8
  61. package/sbom.spdx.json +57 -57
  62. package/dist/secrets/crypto.d.ts +0 -49
@@ -27,6 +27,7 @@ import { ExecutionJobStatus, type LabelMatcher, type PeerHeartbeat, type PeerLog
27
27
  import { ScalerManager } from './scaler/index.js';
28
28
  import type { ScalerConfig } from './scaler/index.js';
29
29
  import type { CacheStorage } from './storage/index.js';
30
+ import { type ProvenanceTrustRoot } from './provenance/trust-root.js';
30
31
  import { SourceCache, BuildCoordinator, DepCache, UserCache, DispatchCacheRefTracker, PendingBuildTracker, PendingInitTracker, PendingDynamicTracker } from './cache/index.js';
31
32
  import { CheckRunReporter } from './reporting/check-run-reporter.js';
32
33
  import { StepLogBuffer } from './reporting/step-log-buffer.js';
@@ -70,6 +71,12 @@ export interface OrchestratorSubsystems {
70
71
  scalerManager: ScalerManager | null;
71
72
  scalerConfig: ScalerConfig | null;
72
73
  cacheStorage: CacheStorage | undefined;
74
+ /**
75
+ * Provenance trust root used to verify build-provenance bundles at ingest.
76
+ * The mode-specific hook (server.ts) wires the live issuer onto it from the
77
+ * Platform `auth.success` message via `onProvenanceIssuer`.
78
+ */
79
+ provenanceTrustRoot: ProvenanceTrustRoot;
73
80
  sourceCache: SourceCache | undefined;
74
81
  depCache: DepCache | undefined;
75
82
  userCache: UserCache | undefined;
@@ -315,6 +322,7 @@ export declare function mergeUpstreamOutputs(db: Kysely<Database>, runId: string
315
322
  upstreamJobOutputs: Record<string, Record<string, unknown>> | undefined;
316
323
  upstreamJobStatuses: Record<string, ExecutionJobStatus> | undefined;
317
324
  }>;
325
+ export declare function buildInternalJobConfigForWorkflow(workflow: any, job: any): Record<string, unknown>;
318
326
  export declare function bootstrapOrchestrator(config: AppConfig, hooks: OrchestratorHooks, options?: {
319
327
  otelSdk?: {
320
328
  shutdown(): Promise<void>;
@@ -14,10 +14,12 @@
14
14
  * results through the pipeline.
15
15
  */
16
16
  import { ExecutionJobStatus, InitFailureCategory, CacheRefScope } from '@kici-dev/engine';
17
- import type { LabelMatcher, LockWorkflow, LockJob, HostTargetSelector, SimulatedEvent, WorkflowDecision, MaterializedJob, ResolvedHostAgent } 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
  /**
@@ -69,6 +71,8 @@ export interface WorkflowDispatchContext {
69
71
  runId: string;
70
72
  trustResolution: TrustResolution | undefined;
71
73
  lockFileSource: string | undefined;
74
+ /** True when this run executes an uploaded local working tree (CLI remote run). */
75
+ localWorkingTree: boolean;
72
76
  /** True only when invoked from the cross-source dispatch shell. */
73
77
  crossSource: boolean;
74
78
  /** Composite dedup key `${info.deliveryId}:${reg.id}` (cross-source only). */
@@ -120,6 +124,12 @@ export interface WorkflowDispatchContext {
120
124
  * runsOnAll ∩ target. Narrow-only. Undefined for webhook runs (no narrowing).
121
125
  */
122
126
  target?: HostTargetSelector;
127
+ /**
128
+ * Resolved (coerced + defaulted + validated) workflow-dispatch inputs from
129
+ * `kici run --input`. Carried onto every dispatched job's request so the agent
130
+ * exposes them as `ctx.dispatchInputs`. Undefined for webhook runs.
131
+ */
132
+ dispatchInputs?: Record<string, unknown>;
123
133
  }
124
134
  export interface DispatchMatchedWorkflowResult {
125
135
  /** Number of jobs successfully dispatched (non-rejected). */
@@ -144,6 +154,30 @@ export interface DispatchMatchedWorkflowOptions {
144
154
  */
145
155
  reuseRunId?: string;
146
156
  }
157
+ type DispatchFn = (input: QueuedJobInput) => ReturnType<Dispatcher['dispatch']>;
158
+ interface DispatchSetup {
159
+ /** Wrapped dispatcher that injects ctx.extraJobConfig into every dispatch. */
160
+ dispatcher: {
161
+ dispatch: DispatchFn;
162
+ };
163
+ /** WebhookInfo overlaid with effective routing key + provider. */
164
+ info: WebhookInfo;
165
+ /** Composite delivery id on cross-source, otherwise info.deliveryId. */
166
+ effectiveDeliveryId: string;
167
+ workflowConcurrency: {
168
+ cancelInProgress?: boolean;
169
+ max?: number;
170
+ } | undefined;
171
+ workflowTimeoutMs: number | undefined;
172
+ /**
173
+ * Run mode for idempotent steps (`apply` | `check` | `check-fail-on-drift`),
174
+ * carried on `ctx.extraJobConfig` by the test-trigger / `kici run --check`
175
+ * path. Persisted onto the `execution_runs` row so `computeRunStatus` can
176
+ * fail a `check-fail-on-drift` run that detected drift. Undefined for the
177
+ * default apply-mode webhook path.
178
+ */
179
+ checkMode: string | undefined;
180
+ }
147
181
  interface RejectedJob {
148
182
  jobId: string;
149
183
  jobName: string;
@@ -157,6 +191,20 @@ interface RejectedJob {
157
191
  */
158
192
  terminalStatus?: ExecutionJobStatus;
159
193
  }
194
+ /**
195
+ * Build the QueuedJobInput for a synthetic `__bringup__` job: the orchestrator
196
+ * dispatches one per declared-but-un-agented `includeUninitialized` child to an
197
+ * agent holding `kici:capability:ssh-transport`, which runs the agent-side
198
+ * `ensureInitRunner(targetAgentId)` over SSH. The init-runner then connects
199
+ * under `targetAgentId`, and the child's pinned-hold (already queued with that
200
+ * pin) drains its bootstrap steps onto it. The bring-up job clones nothing and
201
+ * runs no sandbox (`bringupOnly`).
202
+ */
203
+ export declare function buildBringupJobInput(args: {
204
+ ctx: WorkflowDispatchContext;
205
+ setup: DispatchSetup;
206
+ targetAgentId: string;
207
+ }): QueuedJobInput;
160
208
  /**
161
209
  * Phase B orchestrator: probe caches, dispatch the build job (if needed),
162
210
  * and surface enough state for downstream phases to skip / continue
@@ -227,8 +275,9 @@ export interface WavePlan {
227
275
  * Compute the rolling-wave plan for a materialized job set.
228
276
  *
229
277
  * For each base job declaring `maxParallel` whose fan-out produced more than one
230
- * child, children are ordered deterministically by `variant_label` (the matrix
231
- * suffix / hostname, via `expandedName`) and every child at index `>=
278
+ * child, children are ordered deterministically by `fanoutIndex` (the
279
+ * agentId / variant-label rank assigned at materialization; falling back to
280
+ * `expandedName` for children with no index) and every child at index `>=
232
281
  * maxParallel` is held (`wave_gated=true`). The first `maxParallel` dispatch
233
282
  * immediately; held children release one-per-terminal via the wave-scheduler.
234
283
  * Every child of a bounded-wave base — held or not — gets a `policy` entry so
@@ -241,6 +290,14 @@ export declare function materializeStaticJobsSafe(staticJobs: readonly LockJob[]
241
290
  expansionMap: Map<string, readonly string[]>;
242
291
  matrixFailures: RejectedJob[];
243
292
  }>;
293
+ /**
294
+ * Build the per-host secret-resolution context for a materialized child. A
295
+ * `runsOnAll` host child carries its identity on `mat.agent` (preferred) or
296
+ * `mat.pinnedAgentId`/`mat.host`; a non-host child (matrix/static) has none, so
297
+ * resolution stays fleet-wide (`'**'`-only). Returns `undefined` when there are
298
+ * no host facts to scope by.
299
+ */
300
+ export declare function hostCtxFromMat(mat: MaterializedJob): HostFacts | undefined;
244
301
  export interface GeneratedJobConfig {
245
302
  /**
246
303
  * The generated lock job with its `name` and `needs` rewritten to expanded
@@ -0,0 +1,11 @@
1
+ import { type LockStep, type LockStepEntry } from '@kici-dev/engine';
2
+ /**
3
+ * Flatten a lock job's `steps` into the flat sequential list the orchestrator
4
+ * iterates by `stepIndex`. A `parallel` group's children are inlined in array
5
+ * order and the group wrapper is dropped (it consumes no flat index). This keeps
6
+ * the orchestrator's enumeration aligned with the agent's `extractAndNormalizeSteps`
7
+ * — the flat-stepIndex invariant: `flattenLockSteps(job.steps)[i]` is the step at
8
+ * agent `stepIndex i`.
9
+ */
10
+ export declare function flattenLockSteps(steps: readonly LockStepEntry[]): readonly LockStep[];
11
+ //# sourceMappingURL=flatten-lock-steps.d.ts.map
@@ -36,7 +36,8 @@ export declare function evaluateInlineRecord(expression: string, event: object):
36
36
  * failures are immediate dispatch failures (no init-job fallback).
37
37
  */
38
38
  export declare function evaluateInlineFields(lockJob: LockJob, event: object): {
39
- inlineEnvironmentName: string | undefined;
39
+ /** Resolved name per `environments` element, aligned by index; undefined for static or impure-dynamic elements. */
40
+ inlineEnvironmentNames: Array<string | undefined>;
40
41
  inlineEnv: Record<string, string> | undefined;
41
42
  inlineConcurrencyGroup: string | undefined;
42
43
  };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Multi-environment resolution helpers for the dispatch path.
3
+ *
4
+ * A job binds an ordered list of environments (`LockJob.environments`). This
5
+ * module resolves that list into concrete environment names (static values
6
+ * verbatim, pure-inline dynamic elements evaluated against the event) and folds
7
+ * the per-environment secrets/variables last-wins. It keeps the heavy fold logic
8
+ * out of `dispatchMatchedWorkflow`, which must stay under the function-length cap.
9
+ */
10
+ import { type Environment, type HostFacts, type LockJob } from '@kici-dev/engine';
11
+ import type { SecretResolverApi } from '../secrets/secret-resolver.js';
12
+ import type { VariableStore } from '../environments/variable-store.js';
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.
17
+ */
18
+ export declare const DYNAMIC_ENV_PLACEHOLDER = "(dynamic)";
19
+ /** Ordered resolved environment names plus whether any element still needs agent init. */
20
+ export interface ResolvedJobEnvironments {
21
+ /** Resolved static + pure-inline names, in order. */
22
+ names: string[];
23
+ /** True when an impure dynamic element must be resolved by an agent init job. */
24
+ needsInit: boolean;
25
+ }
26
+ /**
27
+ * Resolve the ordered bound-environment 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`.
31
+ */
32
+ export declare function resolveJobEnvironmentNames(lockJob: LockJob, inlineNames: ReadonlyArray<string | undefined>): ResolvedJobEnvironments;
33
+ /**
34
+ * Build the ordered bound-environment display list for persistence at dispatch.
35
+ * Unlike {@link resolveJobEnvironmentNames}, 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 environment.
41
+ */
42
+ export declare function buildJobEnvironmentDisplayNames(lockJob: LockJob, inlineNames: ReadonlyArray<string | undefined>): string[];
43
+ /** Merged secrets/variables across an ordered list of resolved environments. */
44
+ export interface MultiEnvMergedData {
45
+ environmentVars?: Record<string, string>;
46
+ jobSecrets?: Record<string, string>;
47
+ jobNamespacedSecrets?: Record<string, Record<string, string>>;
48
+ }
49
+ /**
50
+ * Resolve and fold variables + secrets across the ordered list of matched
51
+ * environments, last-wins. Each environment is resolved with the existing
52
+ * single-env logic (longest-scope-path-wins preserved within each environment),
53
+ * then folded in array order so a later environment overrides an earlier key.
54
+ * Secrets are also returned namespaced per environment so qualified
55
+ * `<env>:<secret>` references still resolve. `entries` carries the matched
56
+ * `Environment` for each name (in order); variables resolve by environment id.
57
+ */
58
+ export declare function resolveMultiEnvMergedData(args: {
59
+ deps: {
60
+ variableStore?: VariableStore;
61
+ secretResolver?: SecretResolverApi;
62
+ };
63
+ orgId: string;
64
+ entries: ReadonlyArray<{
65
+ name: string;
66
+ env: Environment;
67
+ }>;
68
+ hostCtx?: HostFacts;
69
+ routingKey?: string;
70
+ }): Promise<MultiEnvMergedData>;
71
+ //# sourceMappingURL=job-environments.d.ts.map
@@ -9,6 +9,7 @@
9
9
  * eventRouter fire-and-forget. The manual path needs request/response
10
10
  * correlation to return the newRunId to the dashboard.
11
11
  */
12
+ import type { LockWorkflow, MaterializedJob } from '@kici-dev/engine';
12
13
  import type { RerunDeps } from './rerun.js';
13
14
  import type { RegistrationIndex } from '../registration/registration-index.js';
14
15
  interface ManualScheduleDeps extends RerunDeps {
@@ -17,5 +18,26 @@ interface ManualScheduleDeps extends RerunDeps {
17
18
  export declare function handleManualSchedule(registrationId: string, triggeredBy: string | null, deps: ManualScheduleDeps): Promise<{
18
19
  newRunId: string;
19
20
  }>;
21
+ export declare function buildManualJobConfig(workflow: LockWorkflow, mat: MaterializedJob): {
22
+ resolvedHashFiles?: string[] | undefined;
23
+ contentHash?: string | undefined;
24
+ dispatchInputs?: Record<string, unknown> | undefined;
25
+ steps: readonly import("@kici-dev/engine").LockStepEntry[];
26
+ needs: readonly (string | {
27
+ name: string;
28
+ runOn: ("success" | "pending" | "failed" | "recovering" | "running" | "queued" | "skipped" | "cancelled" | "cancelling" | "timed_out_stale" | "drift_dropped")[];
29
+ } | {
30
+ group: string;
31
+ runOn: ("success" | "pending" | "failed" | "recovering" | "running" | "queued" | "skipped" | "cancelled" | "cancelling" | "timed_out_stale" | "drift_dropped")[];
32
+ })[];
33
+ rules: readonly import("@kici-dev/engine").LockRule[] | undefined;
34
+ name: string;
35
+ baseJobName: string;
36
+ matrixValues?: import("@kici-dev/engine").MatrixValues;
37
+ fanoutIndex?: number;
38
+ fanoutTotal?: number;
39
+ source: import("@kici-dev/engine").LockSource | undefined;
40
+ workflowName: string;
41
+ };
20
42
  export {};
21
43
  //# sourceMappingURL=manual-schedule.d.ts.map
@@ -73,6 +73,12 @@ export interface TestTriggerInput {
73
73
  * context, where it post-filters each runsOnAll job's matched roster.
74
74
  */
75
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>;
76
82
  }
77
83
  /**
78
84
  * Result of processing a test trigger.
@@ -87,6 +93,15 @@ interface TestTriggerResult {
87
93
  /** Dispatched job IDs. */
88
94
  jobIds: string[];
89
95
  }
96
+ /**
97
+ * Repo identity for an inline-lock (local working tree) run. Derived from the
98
+ * event payload the CLI stamps -- NOT the relay routing key, which is the
99
+ * Platform-internal `remote:<orgId>` anchor and is meaningless as a repo.
100
+ */
101
+ export declare function repoIdentityFromInlineInput(input: TestTriggerInput): {
102
+ repoIdentifier: string;
103
+ provider: string;
104
+ };
90
105
  /**
91
106
  * Process a test trigger through the shared dispatch core.
92
107
  *
@@ -0,0 +1,24 @@
1
+ import type { JSONWebKeySet } from 'jose';
2
+ /**
3
+ * Derive the JWKS URI for a provenance OIDC issuer. Identical to the Platform's
4
+ * own derivation (`packages/platform/src/dashboard/routes/runs.ts`) so a bundle
5
+ * verified client-side and at ingest resolve the same key set.
6
+ */
7
+ export declare function deriveJwksUri(issuer: string): string;
8
+ /**
9
+ * The orchestrator's view of the provenance trust root. The issuer arrives over
10
+ * the `auth.success` connect message for the live process, or from config/env
11
+ * (`KICI_PROVENANCE_ISSUER`) for the CLI backfill which has no live handshake.
12
+ * The JWKS is fetched lazily and cached with a single refetch-on-`kid`-miss.
13
+ */
14
+ export interface ProvenanceTrustRoot {
15
+ getIssuer(): string | null;
16
+ getJwks(kid?: string): Promise<JSONWebKeySet | null>;
17
+ setIssuer(issuer: string | null): void;
18
+ }
19
+ export declare function createProvenanceTrustRoot(opts?: {
20
+ issuer?: string | null;
21
+ fetchImpl?: typeof fetch;
22
+ ttlMs?: number;
23
+ }): ProvenanceTrustRoot;
24
+ //# sourceMappingURL=trust-root.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { type AttestationVerifyStatus } from '@kici-dev/engine';
2
+ import type { CacheStorage } from '../storage/types.js';
3
+ import type { ProvenanceTrustRoot } from './trust-root.js';
4
+ export interface AttestationVerdict {
5
+ verifyStatus: AttestationVerifyStatus;
6
+ verifyReason: string | null;
7
+ verifiedAt: Date | null;
8
+ }
9
+ /**
10
+ * Compute the verification verdict for a stored provenance bundle at ingest.
11
+ *
12
+ * Fail-closed: a missing trust root, unfetchable JWKS, missing storage, or an
13
+ * unreadable bundle all yield `unverifiable` (never silently `verified`). A
14
+ * bundle that verifies false yields `failed` with the first failure code. Any
15
+ * thrown error is caught and recorded as `unverifiable` — verification never
16
+ * fails the upload.
17
+ */
18
+ export declare function computeAttestationVerdict(opts: {
19
+ trustRoot: ProvenanceTrustRoot | undefined;
20
+ storage: CacheStorage | undefined;
21
+ storageKey: string;
22
+ logWarn?: (reason: string) => void;
23
+ }): Promise<AttestationVerdict>;
24
+ //# sourceMappingURL=verify-at-ingest.d.ts.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Derive a coarse, trusted run-failure classification from existing signals.
3
+ *
4
+ * Pure derivation — no heuristics, no flaky/regression detection (that's a
5
+ * deferred phase). KiCI computes this from data it already records, so the
6
+ * resulting category is a trusted field on the agent run-result.
7
+ */
8
+ import { AgentFailureCategory } from '@kici-dev/engine';
9
+ export interface FailureSignals {
10
+ /** `execution_runs.status`. */
11
+ runStatus: string;
12
+ /** A run- or job-scoped init failure was recorded. */
13
+ hasInitFailure: boolean;
14
+ /** The init-failure category (`InitFailureCategory`), if any. */
15
+ initFailureCategory: string | null;
16
+ /** Any job ended in `timed_out_stale` (or a workflow/job timeout fired). */
17
+ timedOut: boolean;
18
+ /** Any step recorded a non-zero exit code. */
19
+ anyStepNonZeroExit: boolean;
20
+ }
21
+ /**
22
+ * Returns the failure category, or null for a non-failed run (success, or still
23
+ * in flight). Precedence: init failure → timeout → cancelled → step exit →
24
+ * unknown.
25
+ */
26
+ export declare function deriveFailureCategory(s: FailureSignals): AgentFailureCategory | null;
27
+ //# sourceMappingURL=agent-failure-category.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Map the canonical run detail into the provenance-tagged `AgentRunResult`.
3
+ *
4
+ * Trusted (plain): ids, enum statuses, exit codes, durations, timestamps,
5
+ * hashes, the derived failure category. Untrusted (enveloped): every name, ref,
6
+ * error string, contributor, job output value — anything sourced from the user's
7
+ * repo / contributor / process output. Secret output *values* are never emitted;
8
+ * only their key names appear.
9
+ */
10
+ import { type AgentRunResult } from '@kici-dev/engine';
11
+ import type { CanonicalRunDetail } from './run-aggregator.js';
12
+ export declare function mapToAgentRunResult(d: CanonicalRunDetail): AgentRunResult;
13
+ //# sourceMappingURL=agent-run-result-mapper.d.ts.map
@@ -23,6 +23,8 @@ export interface ExecutionContext {
23
23
  provider: string;
24
24
  repoIdentifier: string;
25
25
  sha: string;
26
+ /** True when the run executed an uploaded local working tree (`kici run remote`). */
27
+ localWorkingTree?: boolean;
26
28
  installationId?: number;
27
29
  requestId?: string;
28
30
  routingKey?: string;
@@ -38,6 +40,14 @@ export interface ExecutionContext {
38
40
  originalRunId?: string | null;
39
41
  /** User identity that triggered this re-run (null/undefined for webhook-triggered). */
40
42
  triggeredBy?: string | null;
43
+ /**
44
+ * Provider login of the person who triggered the run (pusher / PR author).
45
+ * Captured for all event types; forwarded to the Platform run projection and
46
+ * used to resolve actor-scope notifications.
47
+ */
48
+ triggerActorUsername?: string | null;
49
+ /** Immutable provider user id of the triggering actor (preferred for resolution). */
50
+ triggerActorUserId?: string | null;
41
51
  /** Workflow-level concurrency config from the lock file. */
42
52
  concurrency?: {
43
53
  cancelInProgress?: boolean;
@@ -112,7 +122,9 @@ export interface ExecutionTrackerDeps {
112
122
  * Structured init-failure signal. Set for synthetic rejected-* / init-failed-*
113
123
  * jobs that never started. Persisted in execution_jobs.init_failure.
114
124
  */
115
- initFailure?: InitFailure) => void;
125
+ initFailure?: InitFailure,
126
+ /** Ordered bound deployment-environment names for this job (multi-env jobs). */
127
+ environments?: string[]) => void;
116
128
  /**
117
129
  * Optional callback to emit run.event messages to Platform.
118
130
  * Fires at orchestrator lifecycle points (dispatch, agent assignment, job start/complete).
@@ -208,6 +220,7 @@ export declare class ExecutionTracker {
208
220
  waveGated?: boolean;
209
221
  waveMaxParallel?: number;
210
222
  waveFailFast?: boolean;
223
+ environments?: string[];
211
224
  }>, routingKey?: string,
212
225
  /** Secret context names dispatched with jobs (for context-disable job lookup). */
213
226
  dispatchedContexts?: string[],
@@ -229,7 +242,13 @@ export declare class ExecutionTracker {
229
242
  /** Workflow-level wall-clock timeout in ms from the lock file. Sets the run deadline. */
230
243
  workflowTimeoutMs?: number,
231
244
  /** Run mode for idempotent steps; non-apply labels the run a check-mode preview. */
232
- checkMode?: string): Promise<void>;
245
+ checkMode?: string,
246
+ /** True when the run executes an uploaded local working tree (`kici run remote`). */
247
+ localWorkingTree?: boolean,
248
+ /** Provider login of the triggering actor (pusher / PR author). */
249
+ triggerActorUsername?: string | null,
250
+ /** Immutable provider user id of the triggering actor. */
251
+ triggerActorUserId?: string | null): Promise<void>;
233
252
  /**
234
253
  * Add additional jobs to an already-started execution run.
235
254
  * Used when build jobs are tracked early and regular jobs are dispatched later.
@@ -283,6 +302,7 @@ export declare class ExecutionTracker {
283
302
  baseJobName?: string;
284
303
  variantKind?: string;
285
304
  variantLabel?: string;
305
+ environments?: string[];
286
306
  }>, dispatchedContexts?: string[],
287
307
  /** Synthetic job ID to replace (e.g. needs-pending-deploy-{uuid}). */
288
308
  replaceSyntheticId?: string): Promise<void>;
@@ -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
  *
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Shared run-detail aggregator.
3
+ *
4
+ * One source of truth for "DB rows → canonical nested run detail (jobs with
5
+ * nested steps + needs + outputs)". The dashboard handler and the agent-facing
6
+ * structured run-result route both build on the same `buildRunDetailJobs` /
7
+ * `mapRunDetailStep` mapping, so the row→jobs shape never diverges between the
8
+ * two read surfaces.
9
+ *
10
+ * `aggregateRunDetail` is the warm-path convenience used by the agent route: it
11
+ * runs the run-header + per-run batch queries and returns the canonical detail.
12
+ * (Cold-store fallback stays a dashboard-handler concern — the agent read path
13
+ * is warm-only in v1.)
14
+ */
15
+ import type { Kysely } from 'kysely';
16
+ import type { ExecutionJobStatus, InitFailure } from '@kici-dev/engine';
17
+ import type { Database } from '../db/types.js';
18
+ /** A run-detail step row as queried from execution_steps (warm + cold paths). */
19
+ export interface RunDetailStepRow {
20
+ step_index: number;
21
+ step_name: string;
22
+ status: string;
23
+ started_at: Date | null;
24
+ completed_at: Date | null;
25
+ duration_ms: number | null;
26
+ exit_code: number | null;
27
+ error_message: string | null;
28
+ step_type: string;
29
+ secrets_accessed: string | null;
30
+ check_outcome: string | null;
31
+ drift_summary: string | null;
32
+ concurrency_kind: string | null;
33
+ group_id: string | null;
34
+ }
35
+ /** Map a step row to the dashboard run-detail step shape (epoch-ms timestamps). */
36
+ export declare function mapRunDetailStep(step: RunDetailStepRow): {
37
+ groupId?: string | undefined;
38
+ concurrencyKind?: string | undefined;
39
+ driftSummary?: string | undefined;
40
+ checkOutcome?: string | undefined;
41
+ secretsAccessed: string | null;
42
+ stepType?: string | undefined;
43
+ stepIndex: number;
44
+ stepName: string;
45
+ status: string;
46
+ startedAt: number | null;
47
+ completedAt: number | null;
48
+ durationMs: number | null;
49
+ exitCode: number | null;
50
+ errorMessage: string | null;
51
+ };
52
+ /** A run-detail job row as queried from execution_jobs (warm + cold paths). */
53
+ export interface RunDetailJobRow {
54
+ job_id: string;
55
+ job_name: string;
56
+ status: string;
57
+ matrix_values: unknown;
58
+ base_job_name: string | null;
59
+ variant_kind: string | null;
60
+ variant_label: string | null;
61
+ started_at: Date | null;
62
+ completed_at: Date | null;
63
+ duration_ms: number | null;
64
+ agent_id: string | null;
65
+ error_message: string | null;
66
+ runs_on_labels: unknown;
67
+ environments: unknown;
68
+ outputs: unknown;
69
+ init_failure: unknown;
70
+ }
71
+ /** Lookups threaded into {@link buildRunDetailJobs} from the per-run batch queries. */
72
+ export interface RunDetailJobLookups {
73
+ stepsByJob: Map<string, RunDetailStepRow[]>;
74
+ secretKeysByJob: Map<string, string[]>;
75
+ needsByJob: Map<string, Array<{
76
+ upstreamName: string;
77
+ runOn: ExecutionJobStatus[];
78
+ }>>;
79
+ }
80
+ /** Map queried job + step rows into the dashboard run-detail job DTO shape. */
81
+ export declare function buildRunDetailJobs(jobs: RunDetailJobRow[], lookups: RunDetailJobLookups): {
82
+ needs: {
83
+ upstreamName: string;
84
+ runOn: ExecutionJobStatus[];
85
+ }[] | null;
86
+ steps: {
87
+ groupId?: string | undefined;
88
+ concurrencyKind?: string | undefined;
89
+ driftSummary?: string | undefined;
90
+ checkOutcome?: string | undefined;
91
+ secretsAccessed: string | null;
92
+ stepType?: string | undefined;
93
+ stepIndex: number;
94
+ stepName: string;
95
+ status: string;
96
+ startedAt: number | null;
97
+ completedAt: number | null;
98
+ durationMs: number | null;
99
+ exitCode: number | null;
100
+ errorMessage: string | null;
101
+ }[];
102
+ initFailure?: {
103
+ scope: "run" | "job";
104
+ category: "secret_resolution" | "install_secrets" | "lock_resolution" | "build_coordination" | "environment_rules" | "dynamic_eval" | "no_agent" | "matrix_expansion";
105
+ message: string;
106
+ jobName?: string | undefined;
107
+ } | undefined;
108
+ jobId: string;
109
+ jobName: string;
110
+ status: string;
111
+ matrixValues: Record<string, unknown> | null;
112
+ baseJobName: string | null;
113
+ variantKind: string | null;
114
+ variantLabel: string | null;
115
+ startedAt: number | null;
116
+ completedAt: number | null;
117
+ durationMs: number | null;
118
+ agentId: string | null;
119
+ orchestratorId: null;
120
+ errorMessage: string | null;
121
+ runsOnLabels: string[] | null;
122
+ environments: string[] | null;
123
+ outputs: any;
124
+ secretOutputKeys: string[] | null;
125
+ }[];
126
+ /** A dashboard-shaped run-detail job (epoch-ms timestamps). */
127
+ export type CanonicalRunDetailJob = ReturnType<typeof buildRunDetailJobs>[number];
128
+ /**
129
+ * Canonical run detail: the run header (raw `Date` timestamps) plus the
130
+ * dashboard-shaped nested jobs. The agent mapper formats timestamps to ISO and
131
+ * wraps untrusted fields; the dashboard handler consumes `buildRunDetailJobs`
132
+ * directly.
133
+ */
134
+ export interface CanonicalRunDetail {
135
+ runId: string;
136
+ workflowName: string;
137
+ status: string;
138
+ provider: string;
139
+ repoIdentifier: string;
140
+ ref: string;
141
+ sha: string;
142
+ /** Best-effort base commit from provider context; null when unavailable. */
143
+ baseSha: string | null;
144
+ startedAt: Date | null;
145
+ completedAt: Date | null;
146
+ durationMs: number | null;
147
+ trustTier: string | null;
148
+ contributorUsername: string | null;
149
+ triggeredBy: string | null;
150
+ failureReason: string | null;
151
+ /** Run-scoped init failure (the run never executed a step). */
152
+ initFailure: InitFailure | null;
153
+ routingKey: string | null;
154
+ jobs: CanonicalRunDetailJob[];
155
+ }
156
+ /**
157
+ * Aggregate a run into the canonical nested detail (warm path). Returns null
158
+ * when the run row is absent in PostgreSQL.
159
+ */
160
+ export declare function aggregateRunDetail(db: Kysely<Database>, runId: string): Promise<CanonicalRunDetail | null>;
161
+ //# sourceMappingURL=run-aggregator.d.ts.map