@kici-dev/orchestrator 0.20.0 → 0.21.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.
@@ -0,0 +1,16 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * `execution_jobs.workflow_name`: the workflow that defines the job. A fixture
4
+ * run with no `--workflow` runs every matched workflow under one run id, so
5
+ * `execution_runs.workflow_name` (the first matched workflow) does not say
6
+ * which workflow a job belongs to.
7
+ *
8
+ * Nullable, with no backfill: a row written before this column reads as
9
+ * unknown, and the readers fall back to the run's workflow for a run that can
10
+ * span only one.
11
+ *
12
+ * Idempotent.
13
+ */
14
+ export declare function up(db: Kysely<unknown>): Promise<void>;
15
+ export declare function down(db: Kysely<unknown>): Promise<void>;
16
+ //# sourceMappingURL=160_execution_jobs_workflow_name.d.ts.map
@@ -651,6 +651,12 @@ export interface ExecutionJobTable {
651
651
  job_id: string;
652
652
  /** Job name (e.g. "test", "test[node-18]") */
653
653
  job_name: string;
654
+ /**
655
+ * The workflow that defines the job. A run can span several workflows (an older `kici`'s shared
656
+ * fixture run), so the run's own
657
+ * `workflow_name` does not name it. NULL on a row written before the column existed.
658
+ */
659
+ workflow_name: ColumnType<string | null, string | null | undefined, string | null>;
654
660
  /** Job status: an `ExecutionJobStatus` value (engine). */
655
661
  status: Generated<string>;
656
662
  /**
package/dist/index.js CHANGED
@@ -2793,6 +2793,7 @@ var AdminApiClient = class {
2793
2793
  workflowName: opts?.workflowName,
2794
2794
  repo: opts?.repo,
2795
2795
  since: opts?.since,
2796
+ runIdPrefix: opts?.runIdPrefix,
2796
2797
  limit: opts?.limit || void 0,
2797
2798
  offset: opts?.offset || void 0
2798
2799
  });
@@ -198,6 +198,13 @@ export interface WorkflowDispatchContext {
198
198
  * explicitly rather than leave it `pending` forever.
199
199
  */
200
200
  runRegisteredBeforeDispatch?: boolean;
201
+ /**
202
+ * Called each time this dispatch registers the run with the tracker. An older `kici`'s shared
203
+ * fixture run dispatches several
204
+ * workflows under one run id, and holds the run open from the first
205
+ * registration until every workflow has registered.
206
+ */
207
+ onRunRegistered?: () => void;
201
208
  /**
202
209
  * Set once `setupDispatchContext` posted the queued `kici/<workflow>` check
203
210
  * and one `kici/<workflow>/job/<name>` per static job. They go up BEFORE the
@@ -427,7 +434,8 @@ interface JobEnvEvalResult {
427
434
  * Synthetic job-id prefix every needs-gate site stamps on a held-back job. The
428
435
  * release path (`dispatchReadyJob` → `findSyntheticJobId` → `addJobsToRun`)
429
436
  * keys on it, so it soundly recovers the gated set without threading a parallel
430
- * list through both the single-orch and cluster paths. Owned by `db/synthetic-job-ids.ts` so the `contexts/`
437
+ * list through both the single-orch and cluster paths. Owned by `db/synthetic-job-ids.ts` so the
438
+ * `contexts/`
431
439
  * concurrency sweep need not import this module.
432
440
  */
433
441
  export { NEEDS_PENDING_JOB_ID_PREFIX };
@@ -20,7 +20,7 @@ import { ExecutionJobStatus } from '@kici-dev/engine';
20
20
  import type { LockInvoke, LockJob } from '@kici-dev/engine';
21
21
  import type { Kysely } from 'kysely';
22
22
  import type { Database } from '../db/types.js';
23
- import type { ExecutionTracker } from '../reporting/execution-tracker.js';
23
+ import { type ExecutionTracker } from '../reporting/execution-tracker.js';
24
24
  /**
25
25
  * The invoke parameters carried from a gate's lock job to its (possibly deferred)
26
26
  * execution. Persisted on the pending job context so a gate released later — or
@@ -9,7 +9,7 @@ import type { LockJob, MaterializedJob } from '@kici-dev/engine';
9
9
  import type { JobKind } from '../db/types.js';
10
10
  import type { RunContext, RunCoordinator } from '../cluster/coordinator.js';
11
11
  import type { Dispatcher } from '../agent/dispatcher.js';
12
- import type { ExecutionTracker } from '../reporting/execution-tracker.js';
12
+ import { type ExecutionTracker } from '../reporting/execution-tracker.js';
13
13
  export interface DispatchedJobEntry {
14
14
  jobId: string;
15
15
  jobName: string;
@@ -73,6 +73,8 @@ export declare function routeOrDispatchJobs(opts: RouteOrDispatchOptions): Promi
73
73
  }>;
74
74
  export interface RegisterDispatchedJobsOptions {
75
75
  newRunId: string;
76
+ /** The workflow the jobs belong to, recorded on each job row. */
77
+ workflowName: string;
76
78
  dispatchedJobs: DispatchedJobEntry[];
77
79
  rejectedJobs: RejectedJobEntry[];
78
80
  executionTracker: ExecutionTracker;
@@ -20,6 +20,10 @@
20
20
  * decorating secret resolver), and the fixture's `secrets` context mapping
21
21
  * resolves into namespaced secrets;
22
22
  * - direct workflow execution (bypass trigger matching) is supported.
23
+ *
24
+ * Like a webhook, it creates one run per matched workflow. A caller that
25
+ * follows a single run (no `acceptsMultipleRuns`) gets one shared run instead,
26
+ * and only when the workflows' job names cannot collide in it.
23
27
  */
24
28
  import { type ProcessingDeps } from './processor.js';
25
29
  import { type ActorPrincipal } from '@kici-dev/engine';
@@ -38,6 +42,8 @@ export interface TestTriggerInput {
38
42
  routingKey: string;
39
43
  /** Uploaded tarball reference. */
40
44
  uploadId?: string;
45
+ /** The commit the uploaded overlay was built from, as its upload recorded it. */
46
+ uploadSha?: string;
41
47
  /** Fixture secret context mappings `{ contextName: envName }`. */
42
48
  secrets?: Record<string, string>;
43
49
  /** Base64 X25519+AES-GCM blob of the developer's local secrets ({flat, contexts}). */
@@ -77,9 +83,18 @@ export interface TestTriggerInput {
77
83
  * here against the matched workflow's lock dispatch descriptor.
78
84
  */
79
85
  dispatchInputs?: Record<string, string>;
86
+ /**
87
+ * The caller follows every run in the result's `runs`. Without it, several
88
+ * matched workflows share one run, or the trigger is refused when their job
89
+ * names collide.
90
+ */
91
+ acceptsMultipleRuns?: boolean;
80
92
  }
81
93
  interface TestTriggerResult {
94
+ /** The first run's id: the only run when the caller follows one. */
82
95
  runId: string;
96
+ /** One entry per run, each executing one matched workflow. Absent for a shared run. */
97
+ runs?: TestTriggeredRun[];
83
98
  status: 'accepted' | 'rejected';
84
99
  reason?: string;
85
100
  jobIds: string[];
@@ -98,9 +113,16 @@ export declare function repoIdentityFromInlineInput(input: TestTriggerInput): {
98
113
  repoIdentifier: string;
99
114
  provider: string;
100
115
  };
116
+ /** One run the trigger created, with the matched workflow it executes. */
117
+ export interface TestTriggeredRun {
118
+ runId: string;
119
+ workflowName: string;
120
+ jobIds: string[];
121
+ }
101
122
  /**
102
123
  * Process a test trigger through the shared dispatch core: one
103
- * `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow`.
124
+ * `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow`,
125
+ * each under its own run id.
104
126
  */
105
127
  export declare function processTestTrigger(input: TestTriggerInput, deps: ProcessingDeps): Promise<TestTriggerResult>;
106
128
  export {};
@@ -31,6 +31,8 @@ export interface BackfillJobRow {
31
31
  run_id: string;
32
32
  job_id: string;
33
33
  job_name: string;
34
+ /** Optional: a loader that does not select it forwards no workflow. */
35
+ workflow_name?: string | null;
34
36
  status: string;
35
37
  started_at: Date | null;
36
38
  completed_at: Date | null;
@@ -192,6 +192,8 @@ interface UpdateStepProgressOptions {
192
192
  jobName: string;
193
193
  stepIndex: number;
194
194
  stepName: string;
195
+ /** The step's `step_type`, when the agent sent one. */
196
+ stepType?: string;
195
197
  state: 'running' | 'success' | 'failed' | 'skipped' | 'cancelled';
196
198
  durationMs?: number;
197
199
  installationId?: number;
@@ -42,7 +42,19 @@ export interface StepProgressEntry {
42
42
  name: string;
43
43
  status: 'pending' | 'running' | 'success' | 'failed' | 'skipped' | 'cancelled';
44
44
  durationMs?: number;
45
+ /** The step's stored index. An entry without one is read by its position (defensive). */
46
+ stepIndex?: number;
47
+ /** The step's `step_type`; places setup and init pseudo-steps first. */
48
+ stepType?: string;
45
49
  }
50
+ /**
51
+ * Record one step's progress: replace the entry for its index, or add it in display
52
+ * order. Pseudo-steps have high indices (init at 1_000_000 + n), so the list is keyed
53
+ * by index rather than position; a positional list would pad every gap up to it.
54
+ */
55
+ export declare function applyStepProgress(steps: StepProgressEntry[], entry: StepProgressEntry & {
56
+ stepIndex: number;
57
+ }): StepProgressEntry[];
46
58
  interface BuildCheckRunSummaryOptions {
47
59
  jobName: string;
48
60
  stepResults: StepResultData[];
@@ -206,7 +206,9 @@ export interface ExecutionTrackerDeps {
206
206
  */
207
207
  initFailure?: InitFailure,
208
208
  /** Ordered bound deployment-context names for this job (multi-env jobs). */
209
- contexts?: string[]) => void;
209
+ contexts?: string[],
210
+ /** The workflow that defines the job, when recorded. */
211
+ workflowName?: string) => void;
210
212
  /**
211
213
  * Optional callback to emit run.event messages to Platform.
212
214
  * Fires at orchestrator lifecycle points (dispatch, agent assignment, job start/complete).
@@ -282,6 +284,13 @@ export interface ExecutionTrackerDeps {
282
284
  export interface TrackedJobRow {
283
285
  jobId: string;
284
286
  jobName: string;
287
+ /**
288
+ * The workflow that defines the job. An older `kici`'s shared fixture run holds every matched
289
+ * workflow, so the run's own workflow
290
+ * does not name it. `onExecutionStarted` stamps its own workflow on a job without one;
291
+ * `addJobsToRun` inherits it from the placeholder the job replaces.
292
+ */
293
+ workflowName?: string;
285
294
  matrixValues?: Record<string, unknown>;
286
295
  runsOnLabels?: string[];
287
296
  baseJobName?: string;
@@ -307,6 +316,8 @@ export interface TrackedJobRow {
307
316
  /** For a gate job, its wall-clock timeout in ms (orchestrator-swept). */
308
317
  timeoutMs?: number;
309
318
  }
319
+ /** Stamp each job with `workflowName`, keeping a workflow a job already names. */
320
+ export declare function jobsOfWorkflow<T extends TrackedJobRow>(workflowName: string, jobs: T[]): T[];
310
321
  export declare class ExecutionTracker {
311
322
  private readonly db;
312
323
  private readonly observerRegistry?;
@@ -538,6 +549,11 @@ export declare class ExecutionTracker {
538
549
  /** Synthetic job ID to replace (e.g. needs-pending-deploy-{uuid}). */
539
550
  replaceSyntheticId?: string): Promise<void>;
540
551
  private addJobsToRunImpl;
552
+ /**
553
+ * The workflow of the placeholder a job replaces: from memory, or from its
554
+ * row when another peer registered it.
555
+ */
556
+ private placeholderWorkflowName;
541
557
  /** Mark a run as a test run for observer broadcasting (called by the test pipeline). */
542
558
  markTestRun(runId: string): void;
543
559
  isTestRun(runId: string): boolean;
@@ -814,6 +830,13 @@ export declare class ExecutionTracker {
814
830
  dispatchedByFailureLifecycle?: boolean;
815
831
  /** Re-run lineage, when this run re-runs another. */
816
832
  rerunLineage?: RerunLineage;
833
+ /**
834
+ * Set for a `kici run` test run, so its init failure stays readable on the
835
+ * test-run status and log routes, which serve only test runs.
836
+ */
837
+ testRun?: {
838
+ fixtureId: string;
839
+ };
817
840
  }): Promise<void>;
818
841
  /**
819
842
  * Write one `failed` execution_runs row for a global eval round that never
@@ -930,6 +953,10 @@ export declare class ExecutionTracker {
930
953
  * {@link ExecutionTracker.recordGlobalEvalRoundFailureRun}).
931
954
  */
932
955
  dispatchRoutingKey?: string;
956
+ /** Set for a `kici run` test run, as on {@link ExecutionTracker.recordInitFailureRun}. */
957
+ testRun?: {
958
+ fixtureId: string;
959
+ };
933
960
  }): Promise<void>;
934
961
  /**
935
962
  * Claim a held global evaluation round for release (`held` → `pending`).
@@ -1089,7 +1116,7 @@ export declare class ExecutionTracker {
1089
1116
  * worker's first status update creates the `execution_jobs` row. An existing
1090
1117
  * entry is never clobbered, so a later terminal status survives.
1091
1118
  */
1092
- registerJobName(runId: string, jobId: string, jobName: string): void;
1119
+ registerJobName(runId: string, jobId: string, jobName: string, workflowName?: string): void;
1093
1120
  /**
1094
1121
  * The in-memory run owning a dispatched jobId, so `getExecutionContext`
1095
1122
  * resolves for a job-scoped agent `event.emit`. In-memory only, like
@@ -1169,6 +1196,7 @@ export declare class ExecutionTracker {
1169
1196
  jobs: Array<{
1170
1197
  jobId: string;
1171
1198
  jobName: string;
1199
+ workflowName?: string;
1172
1200
  status: string;
1173
1201
  startedAt?: number;
1174
1202
  completedAt?: number;
@@ -39,6 +39,8 @@ export declare function mapRunDetailStep(step: RunDetailStepRow): {
39
39
  export interface RunDetailJobRow {
40
40
  job_id: string;
41
41
  job_name: string;
42
+ /** Absent from an archived row written before the column existed. */
43
+ workflow_name?: string | null;
42
44
  status: string;
43
45
  matrix_values: unknown;
44
46
  base_job_name: string | null;
@@ -67,6 +69,11 @@ export interface RunDetailJobLookups {
67
69
  upstreamName: string;
68
70
  runOn: ExecutionJobStatus[];
69
71
  }>>;
72
+ /** The run's own workflow, naming a job row that records none; see {@link jobWorkflowName}. */
73
+ runWorkflow?: {
74
+ workflow_name: string;
75
+ is_test_run: boolean;
76
+ };
70
77
  }
71
78
  /**
72
79
  * The routing reason as a reader should see it: only while the job is `pending`/`queued`.
@@ -82,6 +89,7 @@ export declare function visibleRoutingReason(status: string, routingReason: stri
82
89
  export declare function buildRunDetailJobs(jobs: RunDetailJobRow[], lookups: RunDetailJobLookups): {
83
90
  jobId: string;
84
91
  jobName: string;
92
+ workflowName: string | null;
85
93
  status: string;
86
94
  matrixValues: Record<string, unknown> | null;
87
95
  baseJobName: string | null;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The workflows a run spans, read from its job rows.
3
+ *
4
+ * An older `kici`'s shared fixture run holds every
5
+ * matched workflow. `execution_runs.workflow_name` holds the first of them; each job row
6
+ * records its own in `execution_jobs.workflow_name`.
7
+ */
8
+ import type { Kysely } from 'kysely';
9
+ import type { Database } from '../db/types.js';
10
+ /** The run fields the readers below need. */
11
+ export interface RunWorkflowRef {
12
+ run_id: string;
13
+ workflow_name: string;
14
+ }
15
+ /**
16
+ * Every workflow each run spans, the run's own workflow first and the rest in
17
+ * the order their first job registered. A run whose job rows name no workflow
18
+ * (written before the column existed) lists its own workflow alone.
19
+ */
20
+ export declare function loadRunWorkflowNames(db: Kysely<Database>, runs: readonly RunWorkflowRef[]): Promise<Map<string, string[]>>;
21
+ /**
22
+ * The workflow a job row belongs to. A row written before the column existed
23
+ * records none: a run that is not a fixture run spans one workflow, so the
24
+ * run's own names it; a fixture run may span several, so it stays unknown.
25
+ */
26
+ export declare function jobWorkflowName(jobWorkflow: string | null, run: {
27
+ workflow_name: string;
28
+ is_test_run: boolean;
29
+ }): string | null;
30
+ //# sourceMappingURL=run-workflow-names.d.ts.map
@@ -3,26 +3,28 @@
3
3
  *
4
4
  * Sorting `execution_steps` by `step_index` alone puts a pseudo-step where its index landed,
5
5
  * not where it ran. That suits the cache pseudo-steps (indices above the real steps, mostly
6
- * bracketing the work), but a job's image build runs strictly FIRST.
6
+ * bracketing the work), but a job's image build and its init commands run strictly FIRST.
7
7
  *
8
8
  * The index cannot express this: `step.status` rejects a negative index (an agent sending one
9
9
  * to an older orchestrator is disconnected mid-job), and the check-run reporter
10
10
  * (`steps[stepIndex]`) and the dashboard (log fetching gated on `stepIndex >= 0`) treat a
11
- * negative index as absent. So the ordering is expressed by TYPE, here, once.
11
+ * negative index as absent. So the ordering is expressed by TYPE, as the engine's
12
+ * `StepPhase`, here, once.
12
13
  */
13
14
  import { type Expression } from 'kysely';
14
15
  /**
15
- * Sort key placing setup pseudo-steps (run before the job's real steps) first. Apply BEFORE
16
- * the `step_index` sort:
16
+ * Sort key placing steps by `StepPhase`: setup pseudo-steps, then init commands, then the
17
+ * job's steps. Apply BEFORE the `step_index` sort:
17
18
  *
18
19
  * ```ts
19
20
  * qb.orderBy(setupStepsFirst()).orderBy('step_index', 'asc')
20
21
  * ```
21
22
  *
22
23
  * Untyped steps and every real step share one key, so `step_index` alone orders each group.
24
+ * The init pattern matches `initStepIndex`: the prefix followed by digits only.
23
25
  */
24
26
  export declare function setupStepsFirst(): Expression<number>;
25
- /** In-memory equivalent, kept beside the SQL so the two agree on what "setup" means. */
27
+ /** In-memory equivalent over DB rows, kept beside the SQL so the two agree on the phases. */
26
28
  export declare function compareStepsForDisplay(a: {
27
29
  step_type?: string | null;
28
30
  step_index: number;
@@ -30,4 +32,6 @@ export declare function compareStepsForDisplay(a: {
30
32
  step_type?: string | null;
31
33
  step_index: number;
32
34
  }): number;
35
+ /** {@link compareStepsForDisplay} over untyped rows, such as cold-store rows read back from S3. */
36
+ export declare function compareStepRowsForDisplay(a: Record<string, unknown>, b: Record<string, unknown>): number;
33
37
  //# sourceMappingURL=step-display-order.d.ts.map