@oisincoveney/pipeline 1.23.2 → 1.24.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 (56) hide show
  1. package/README.md +43 -66
  2. package/dist/argo-graph.js +94 -0
  3. package/dist/argo-submit.d.ts +61 -0
  4. package/dist/argo-submit.js +206 -0
  5. package/dist/argo-workflow.d.ts +159 -0
  6. package/dist/argo-workflow.js +410 -0
  7. package/dist/commands/pipeline-command.js +6 -2
  8. package/dist/commands/runner-command-command.js +13 -0
  9. package/dist/config.d.ts +7 -16
  10. package/dist/config.js +19 -28
  11. package/dist/hooks.d.ts +1 -1
  12. package/dist/index.js +107 -28
  13. package/dist/install-commands.js +9 -9
  14. package/dist/mcp/bootstrap.js +1 -1
  15. package/dist/mcp/gateway.js +1 -1
  16. package/dist/moka-submit.js +191 -0
  17. package/dist/path-refs.js +1 -1
  18. package/dist/pipeline-runtime.d.ts +6 -1
  19. package/dist/pipeline-runtime.js +44 -101
  20. package/dist/run-state/git-refs.js +74 -0
  21. package/dist/runner-command/finalize.js +74 -0
  22. package/dist/runner-command/run.js +130 -0
  23. package/dist/runner-command/task-descriptor.js +17 -0
  24. package/dist/{runner-job-contract.d.ts → runner-command-contract.d.ts} +40 -88
  25. package/dist/{runner-job-contract.js → runner-command-contract.js} +85 -42
  26. package/dist/runner-event-sink.js +3 -3
  27. package/dist/runner.js +1 -1
  28. package/dist/runtime/agent-node/agent-node.js +2 -2
  29. package/dist/runtime/changed-files/changed-files.js +1 -1
  30. package/dist/runtime/context/context.js +3 -12
  31. package/dist/runtime/contracts/contracts.d.ts +1 -2
  32. package/dist/runtime/goal-state.js +1 -1
  33. package/dist/runtime/hooks/hooks.js +2 -2
  34. package/dist/runtime/json-validation/json-validation.js +1 -1
  35. package/dist/runtime/parallel-node/parallel-node.js +2 -7
  36. package/dist/runtime-machines/workflow-machine.js +1 -1
  37. package/dist/schedule-planner.d.ts +3 -315
  38. package/dist/schedule-planner.js +11 -45
  39. package/dist/standard-output-schemas.js +1 -1
  40. package/dist/workflow-planner.d.ts +0 -2
  41. package/dist/workflow-planner.js +1 -3
  42. package/docs/adr-opencode-first-goal-loop-runtime.md +2 -2
  43. package/docs/operator-guide.md +116 -132
  44. package/docs/pipeline-console-runner-contract.md +17 -17
  45. package/package.json +15 -10
  46. package/dist/commands/runner-job-command.js +0 -14
  47. package/dist/k8s-submit.js +0 -267
  48. package/dist/runner-job/delivery.js +0 -134
  49. package/dist/runner-job/devspace.js +0 -34
  50. package/dist/runner-job/pr-summary.js +0 -117
  51. package/dist/runner-job/run.js +0 -409
  52. package/dist/runner-job/workspace.js +0 -86
  53. package/dist/runner-job-k8s.d.ts +0 -68
  54. package/dist/runner-job-k8s.js +0 -146
  55. package/dist/runtime/worktrees/index.js +0 -2
  56. package/dist/runtime/worktrees/worktrees.js +0 -65
package/README.md CHANGED
@@ -116,20 +116,22 @@ target repository.
116
116
  ## Pipeline Console Runner Image
117
117
 
118
118
  `oisin-pipeline` is also the runner package/image used by `pipeline-console`.
119
- The console owns Kubernetes Job creation, run listing, cancellation, event
120
- storage, Kueue discovery, and UI rendering. This package owns the in-container
121
- `runner-job` command: payload validation, existing runtime invocation, event
122
- translation, authenticated event posting, signal cancellation, and final event
123
- flushing.
124
-
125
- The console starts the image with the payload as a mounted ConfigMap file and
126
- the event auth token as a mounted Secret file. The runner reads the payload
127
- from `--payload-file` and reads the event auth token from the file path
128
- specified in `events.authTokenFile`. No environment variables are used for
129
- payload or auth token delivery. The payload contract is documented in
119
+ The control plane owns Argo Workflow submission, run listing, cancellation,
120
+ event storage, Kueue discovery, and UI rendering. This package owns the
121
+ in-container `runner-command` command used by Argo Workflow DAG tasks: payload
122
+ validation, direct argv execution, event translation, authenticated event
123
+ posting, signal cancellation, and final event flushing.
124
+
125
+ Argo starts the image with payload, schedule, and per-task descriptor files
126
+ mounted from ConfigMaps, plus the event auth token mounted from a Secret. The
127
+ runner reads `--payload-file`, `--schedule-file`, and `/etc/pipeline/task.json`;
128
+ the payload contains the workflow id and the task descriptor contains the node
129
+ id. The event auth token still comes from the file path specified in
130
+ `events.authTokenFile`; payload JSON, task identity, and auth token material are
131
+ not delivered through environment variables. The payload contract is documented in
130
132
  [`docs/pipeline-console-runner-contract.md`](docs/pipeline-console-runner-contract.md).
131
133
  The executable contract is exported from
132
- `@oisincoveney/pipeline/runner-job-contract` for payload construction,
134
+ `@oisincoveney/pipeline/runner-command-contract` for payload construction,
133
135
  validation, contract-version checks, and JSON Schema generation.
134
136
  Use `PIPELINE_TARGET_PATH=/path/to/worktree` when the checked-out target repo is
135
137
  mounted somewhere other than the process working directory.
@@ -225,25 +227,18 @@ workflow. See `docs/config-architecture.md` for the host support matrix.
225
227
 
226
228
  ### Structural Parallelism
227
229
 
228
- Workflows can compose other workflows with fixed YAML structure. A
229
- `kind: workflow` node invokes another named workflow; without `worktree_root` it
230
- runs in the current worktree, and with `worktree_root` it runs in an isolated
231
- git worktree. A `kind: parallel` node contains a fixed set of child nodes that
232
- run concurrently after dependencies pass. This is structural parallelism, not
233
- dynamic fanout: agents may route work to tracks, but the branch topology stays
234
- auditable in YAML.
230
+ Workflows and generated schedules can express fixed parallel structure. A
231
+ `kind: parallel` node contains a fixed set of child nodes that run concurrently
232
+ after dependencies pass. A `kind: group` node groups existing nodes behind a
233
+ single dependency target. This is structural parallelism, not dynamic fanout:
234
+ agents may route work to tracks, but the branch topology stays auditable in YAML
235
+ or in the generated schedule artifact.
235
236
 
236
- The built-in `epic` entrypoint uses those primitives:
237
+ The scheduler emits explicit root DAGs for epics:
237
238
 
238
239
  ```yaml
239
- entrypoints:
240
- epic:
241
- workflow: epic-drain
242
- description: Route an epic's tickets into specialist tracks, run them in parallel, then thermo-nuclear review.
243
-
244
240
  workflows:
245
- epic-drain:
246
- description: Research, route, parallel-implement tracks in isolated worktrees, integrate, thermo-nuclear review.
241
+ root:
247
242
  nodes:
248
243
  - id: research
249
244
  kind: agent
@@ -257,39 +252,26 @@ workflows:
257
252
  needs: [plan]
258
253
  nodes:
259
254
  - id: test
260
- kind: workflow
261
- workflow: default
262
- worktree_root: .pipeline/runs/${runId}/test
255
+ kind: agent
256
+ profile: pipeline-code-writer
263
257
  - id: frontend
264
- kind: workflow
265
- workflow: default
266
- worktree_root: .pipeline/runs/${runId}/frontend
258
+ kind: agent
259
+ profile: pipeline-code-writer
267
260
  - id: backend
268
- kind: workflow
269
- workflow: default
270
- worktree_root: .pipeline/runs/${runId}/backend
261
+ kind: agent
262
+ profile: pipeline-code-writer
271
263
  - id: k8s
272
- kind: workflow
273
- workflow: infra
274
- worktree_root: .pipeline/runs/${runId}/k8s
275
- - id: merge
276
- kind: builtin
277
- builtin: drain-merge
278
- needs: [implement]
279
- - id: review
264
+ kind: agent
265
+ profile: pipeline-code-writer
266
+ - id: verify
280
267
  kind: agent
281
- profile: pipeline-thermo-nuclear-reviewer
282
- needs: [merge]
283
- gates:
284
- - { id: review-verdict, kind: verdict, target: stdout }
268
+ profile: pipeline-verifier
269
+ needs: [implement]
285
270
  ```
286
271
 
287
- Use `.pipeline/runs/${runId}/<track>` for isolated track worktrees; the default
288
- `.gitignore` excludes `.pipeline/runs/`. The `drain-merge` builtin consumes the
289
- parallel output, skips non-passing or non-worktree children, verifies mergeable
290
- branches share a base SHA, and merges passing branches into an integration
291
- branch in declaration order. It reports merge conflicts; it does not resolve
292
- them automatically.
272
+ For Argo execution, Git refs carry node state across DAG tasks. Runner tasks
273
+ derive state refs from `payload.run.id`, `payload.workflow.id`, and the mounted
274
+ task descriptor node id.
293
275
 
294
276
  Default profile skills and generated host resources are installed by
295
277
  `pipe init`. Runtime MCP projection and host-specific isolation policy live in
@@ -367,12 +349,6 @@ runners:
367
349
  - Parallel DAG batches run concurrently after dependencies and gates pass.
368
350
  - `kind: parallel` child sets are fixed in YAML; routing agents decide which
369
351
  work belongs in each declared track, not how many tracks exist.
370
- - `kind: workflow` nodes invoke named workflows and can run in isolated
371
- worktrees when `worktree_root` is set.
372
- - Worktree roots support `${runId}` and `${nodeId}` templates and should live
373
- under `.pipeline/runs/` for generated run artifacts.
374
- - `drain-merge` merges passing worktree branches in declaration order and
375
- reports conflicts for manual resolution.
376
352
  - Workflow execution can cap parallelism and enable fail-fast batch stopping.
377
353
  - Nodes can declare bounded retries, retry reasons, backoff, and execution
378
354
  timeouts.
@@ -418,11 +394,10 @@ Runner Job producers can import the shared payload contract:
418
394
 
419
395
  ```ts
420
396
  import {
421
- RUNNER_JOB_CONTRACT_VERSION,
422
- buildRunnerJobPayload,
423
- parseRunnerJobPayload,
424
- runnerJobPayloadJsonSchema,
425
- } from "@oisincoveney/pipeline/runner-job-contract";
397
+ buildRunnerCommandPayload,
398
+ parseRunnerCommandPayload,
399
+ runnerCommandPayloadSchema,
400
+ } from "@oisincoveney/pipeline/runner-command-contract";
426
401
  ```
427
402
 
428
403
  ## Verification
@@ -437,4 +412,6 @@ bun run build:cli
437
412
  ```
438
413
 
439
414
 
440
- Runner Kubernetes Jobs can be generated with `buildRunnerJobK8sManifest` from `@oisincoveney/pipeline/runner-job-k8s`.
415
+ Argo Workflows can be rendered with `buildRunnerArgoWorkflowManifest` from
416
+ `@oisincoveney/pipeline/argo-workflow` and submitted with
417
+ `submitRunnerArgoWorkflow` from `@oisincoveney/pipeline/argo-submit`.
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ //#region src/argo-graph.ts
3
+ const EXECUTABLE_NODE_KINDS = [
4
+ "agent",
5
+ "builtin",
6
+ "command"
7
+ ];
8
+ const argoExecutableTaskSchema = z.object({
9
+ dependencies: z.array(z.string().min(1)),
10
+ nodeId: z.string().min(1),
11
+ taskName: z.string().min(1),
12
+ templateName: z.string().min(1)
13
+ }).strict();
14
+ const argoExecutionGraphSchema = z.object({
15
+ terminalNodeIds: z.array(z.string().min(1)),
16
+ tasks: z.array(argoExecutableTaskSchema).min(1),
17
+ terminalTaskNames: z.array(z.string().min(1)),
18
+ workflowId: z.string().min(1)
19
+ }).strict();
20
+ function compileArgoExecutionGraph(plan) {
21
+ const compiler = new ArgoGraphCompiler(plan);
22
+ return argoExecutionGraphSchema.parse(compiler.compile());
23
+ }
24
+ var ArgoGraphCompiler = class {
25
+ nodeById = /* @__PURE__ */ new Map();
26
+ plan;
27
+ tasks = [];
28
+ constructor(plan) {
29
+ this.plan = plan;
30
+ this.indexNodes(plan.topologicalOrder);
31
+ }
32
+ compile() {
33
+ this.compileNodes(this.plan.topologicalOrder, []);
34
+ return {
35
+ terminalNodeIds: this.terminalTasks().map((task) => task.nodeId),
36
+ tasks: this.tasks,
37
+ terminalTaskNames: this.terminalTasks().map((task) => task.taskName),
38
+ workflowId: this.plan.workflowId
39
+ };
40
+ }
41
+ indexNodes(nodes) {
42
+ for (const node of nodes) {
43
+ if (this.nodeById.has(node.id)) throw new Error(`Argo schedule contains duplicate node id '${node.id}'`);
44
+ this.nodeById.set(node.id, node);
45
+ this.indexNodes(node.children ?? []);
46
+ }
47
+ }
48
+ compileNodes(nodes, inheritedNeeds) {
49
+ for (const node of nodes) {
50
+ if (isExecutableNode(node)) {
51
+ const dependencies = this.resolveDependencyTaskNames([...inheritedNeeds, ...node.needs]);
52
+ const task = argoExecutableTaskSchema.parse({
53
+ dependencies,
54
+ nodeId: node.id,
55
+ taskName: argoTaskName(node.id),
56
+ templateName: argoTemplateName(node.id)
57
+ });
58
+ this.tasks.push(task);
59
+ continue;
60
+ }
61
+ if (node.kind === "group") continue;
62
+ if (node.kind === "parallel") this.compileNodes(node.children ?? [], [...inheritedNeeds, ...node.needs]);
63
+ }
64
+ }
65
+ resolveDependencyTaskNames(nodeIds) {
66
+ return unique(nodeIds.flatMap((nodeId) => this.resolveDependencyNodeIds(nodeId).map((id) => argoTaskName(id))));
67
+ }
68
+ resolveDependencyNodeIds(nodeId) {
69
+ const node = this.nodeById.get(nodeId);
70
+ if (!node) return [];
71
+ if (isExecutableNode(node)) return [node.id];
72
+ if (node.kind === "group") return unique([...node.nodes ?? [], ...node.needs].flatMap((id) => this.resolveDependencyNodeIds(id)));
73
+ if (node.kind === "parallel") return unique((node.children ?? []).flatMap((child) => this.resolveDependencyNodeIds(child.id)));
74
+ return [];
75
+ }
76
+ terminalTasks() {
77
+ const dependedOn = new Set(this.tasks.flatMap((task) => task.dependencies));
78
+ return this.tasks.filter((task) => !dependedOn.has(task.taskName));
79
+ }
80
+ };
81
+ function isExecutableNode(node) {
82
+ return EXECUTABLE_NODE_KINDS.includes(node.kind);
83
+ }
84
+ function argoTaskName(nodeId) {
85
+ return `node-${nodeId}`;
86
+ }
87
+ function argoTemplateName(nodeId) {
88
+ return `task-${nodeId}`;
89
+ }
90
+ function unique(values) {
91
+ return [...new Set(values)];
92
+ }
93
+ //#endregion
94
+ export { compileArgoExecutionGraph };
@@ -0,0 +1,61 @@
1
+ import { PipelineConfig } from "./config.js";
2
+ import { z } from "zod";
3
+ import { CoreV1Api, CustomObjectsApi, KubeConfig } from "@kubernetes/client-node";
4
+
5
+ //#region src/argo-submit.d.ts
6
+ declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
7
+ codexAuthSecretName: z.ZodOptional<z.ZodString>;
8
+ eventAuthSecretKey: z.ZodOptional<z.ZodString>;
9
+ eventAuthSecretName: z.ZodOptional<z.ZodString>;
10
+ generateName: z.ZodOptional<z.ZodString>;
11
+ githubAuthSecretName: z.ZodOptional<z.ZodString>;
12
+ image: z.ZodOptional<z.ZodString>;
13
+ imagePullPolicy: z.ZodOptional<z.ZodEnum<{
14
+ Always: "Always";
15
+ IfNotPresent: "IfNotPresent";
16
+ Never: "Never";
17
+ }>>;
18
+ imagePullSecretName: z.ZodOptional<z.ZodString>;
19
+ kubeconfigPath: z.ZodOptional<z.ZodString>;
20
+ name: z.ZodOptional<z.ZodString>;
21
+ namespace: z.ZodDefault<z.ZodString>;
22
+ opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
23
+ orchestrator: z.ZodDefault<z.ZodEnum<{
24
+ codex: "codex";
25
+ opencode: "opencode";
26
+ }>>;
27
+ payloadJson: z.ZodString;
28
+ queueName: z.ZodOptional<z.ZodString>;
29
+ scheduleYaml: z.ZodString;
30
+ serviceAccountName: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strict>;
32
+ declare const submitRunnerArgoWorkflowResultSchema: z.ZodObject<{
33
+ namespace: z.ZodString;
34
+ payloadConfigMapName: z.ZodString;
35
+ scheduleConfigMapName: z.ZodString;
36
+ taskDescriptorConfigMapName: z.ZodString;
37
+ workflowName: z.ZodString;
38
+ workflowUid: z.ZodOptional<z.ZodString>;
39
+ }, z.core.$strict>;
40
+ declare const commandScheduleOptionsSchema: z.ZodObject<{
41
+ command: z.ZodArray<z.ZodString>;
42
+ generatedAt: z.ZodDefault<z.ZodDate>;
43
+ scheduleId: z.ZodOptional<z.ZodString>;
44
+ task: z.ZodString;
45
+ }, z.core.$strict>;
46
+ type SubmitRunnerArgoWorkflowOptions = z.input<typeof submitRunnerArgoWorkflowOptionsSchema> & {
47
+ config: PipelineConfig;
48
+ };
49
+ type SubmitRunnerArgoWorkflowResult = z.infer<typeof submitRunnerArgoWorkflowResultSchema>;
50
+ type CommandScheduleOptions = z.input<typeof commandScheduleOptionsSchema>;
51
+ type CoreApi = Pick<CoreV1Api, "createNamespacedConfigMap">;
52
+ type WorkflowApi = Pick<CustomObjectsApi, "createNamespacedCustomObject" | "getClusterCustomObject">;
53
+ interface SubmitRunnerArgoWorkflowDependencies {
54
+ coreApi?: CoreApi;
55
+ kubeConfig?: KubeConfig;
56
+ workflowApi?: WorkflowApi;
57
+ }
58
+ declare function submitRunnerArgoWorkflow(rawOptions: SubmitRunnerArgoWorkflowOptions, dependencies?: SubmitRunnerArgoWorkflowDependencies): Promise<SubmitRunnerArgoWorkflowResult>;
59
+ declare function buildCommandScheduleYaml(rawOptions: CommandScheduleOptions): string;
60
+ //#endregion
61
+ export { CommandScheduleOptions, SubmitRunnerArgoWorkflowDependencies, SubmitRunnerArgoWorkflowOptions, SubmitRunnerArgoWorkflowResult, buildCommandScheduleYaml, submitRunnerArgoWorkflow };
@@ -0,0 +1,206 @@
1
+ import { compileArgoExecutionGraph } from "./argo-graph.js";
2
+ import { buildRunnerTaskDescriptor } from "./runner-command/task-descriptor.js";
3
+ import { buildRunnerArgoWorkflowManifest, runnerArgoWorkflowManifestSchema } from "./argo-workflow.js";
4
+ import { parseRunnerCommandPayload, runnerCommandPayloadSchema } from "./runner-command-contract.js";
5
+ import { compileScheduleArtifact, parseScheduleArtifact } from "./schedule-planner.js";
6
+ import { stringify } from "yaml";
7
+ import { z } from "zod";
8
+ import { randomBytes } from "node:crypto";
9
+ import { CoreV1Api, CustomObjectsApi, KubeConfig } from "@kubernetes/client-node";
10
+ //#region src/argo-submit.ts
11
+ const scheduleIdSchema = z.string().regex(/^[a-z][a-z0-9-]*$/);
12
+ const configMapSchema = z.object({
13
+ apiVersion: z.literal("v1"),
14
+ data: z.record(z.string().min(1), z.string()),
15
+ kind: z.literal("ConfigMap"),
16
+ metadata: z.object({
17
+ labels: z.record(z.string().min(1), z.string().min(1)).optional(),
18
+ name: z.string().min(1),
19
+ namespace: z.string().min(1)
20
+ }).strict()
21
+ }).strict();
22
+ const submitRunnerArgoWorkflowOptionsSchema = z.object({
23
+ codexAuthSecretName: z.string().min(1).optional(),
24
+ eventAuthSecretKey: z.string().min(1).optional(),
25
+ eventAuthSecretName: z.string().min(1).optional(),
26
+ generateName: z.string().min(1).optional(),
27
+ githubAuthSecretName: z.string().min(1).optional(),
28
+ image: z.string().min(1).optional(),
29
+ imagePullPolicy: z.enum([
30
+ "Always",
31
+ "IfNotPresent",
32
+ "Never"
33
+ ]).optional(),
34
+ imagePullSecretName: z.string().min(1).optional(),
35
+ kubeconfigPath: z.string().min(1).optional(),
36
+ name: z.string().min(1).optional(),
37
+ namespace: z.string().min(1).default("momokaya-pipeline"),
38
+ opencodeAuthSecretName: z.string().min(1).optional(),
39
+ orchestrator: z.enum(["codex", "opencode"]).default("opencode"),
40
+ payloadJson: z.string().min(1),
41
+ queueName: z.string().min(1).optional(),
42
+ scheduleYaml: z.string().min(1),
43
+ serviceAccountName: z.string().min(1).optional()
44
+ }).strict().refine((options) => options.name !== void 0 || options.generateName !== void 0, { message: "Argo submit options must declare name or generateName" });
45
+ const submitRunnerArgoWorkflowResultSchema = z.object({
46
+ namespace: z.string().min(1),
47
+ payloadConfigMapName: z.string().min(1),
48
+ scheduleConfigMapName: z.string().min(1),
49
+ taskDescriptorConfigMapName: z.string().min(1),
50
+ workflowName: z.string().min(1),
51
+ workflowUid: z.string().min(1).optional()
52
+ }).strict();
53
+ const commandScheduleOptionsSchema = z.object({
54
+ command: z.array(z.string().min(1)).min(1),
55
+ generatedAt: z.date().default(() => /* @__PURE__ */ new Date()),
56
+ scheduleId: scheduleIdSchema.optional(),
57
+ task: z.string().min(1)
58
+ }).strict();
59
+ async function submitRunnerArgoWorkflow(rawOptions, dependencies = {}) {
60
+ const { config, ...schemaOptions } = rawOptions;
61
+ const options = submitRunnerArgoWorkflowOptionsSchema.parse(schemaOptions);
62
+ const payload = runnerCommandPayloadSchema.parse(parseRunnerCommandPayload(options.payloadJson));
63
+ const compiled = compileScheduleArtifact(config, parseScheduleArtifact(options.scheduleYaml, "schedule.yaml"));
64
+ const payloadConfigMapName = `pipeline-payload-${randomBytes(6).toString("hex")}`;
65
+ const scheduleArtifactConfigMapName = `pipeline-schedule-${randomBytes(6).toString("hex")}`;
66
+ const taskDescriptorConfigMapName = `pipeline-task-descriptors-${randomBytes(6).toString("hex")}`;
67
+ if (payload.workflow.id !== compiled.workflowId) throw new Error(`Runner payload workflow '${payload.workflow.id}' does not match schedule workflow '${compiled.workflowId}'`);
68
+ const graph = compileArgoExecutionGraph(compiled.plan);
69
+ const labels = {
70
+ "pipeline.oisin.dev/project": payload.run.project,
71
+ "pipeline.oisin.dev/run-id": payload.run.id,
72
+ "pipeline.oisin.dev/source": "argo-workflow",
73
+ "pipeline.oisin.dev/workflow": compiled.workflowId
74
+ };
75
+ const workflow = buildRunnerArgoWorkflowManifest({
76
+ codexAuthSecretName: options.codexAuthSecretName,
77
+ eventAuthSecretKey: options.eventAuthSecretKey,
78
+ eventAuthSecretName: options.eventAuthSecretName,
79
+ generateName: options.generateName,
80
+ githubAuthSecretName: options.githubAuthSecretName,
81
+ image: options.image,
82
+ imagePullPolicy: options.imagePullPolicy,
83
+ imagePullSecretName: options.imagePullSecretName,
84
+ labels,
85
+ name: options.name,
86
+ namespace: options.namespace,
87
+ opencodeAuthSecretName: options.opencodeAuthSecretName,
88
+ orchestrator: options.orchestrator,
89
+ payloadConfigMapName,
90
+ plan: compiled.plan,
91
+ queueName: options.queueName,
92
+ scheduleConfigMapName: scheduleArtifactConfigMapName,
93
+ serviceAccountName: options.serviceAccountName,
94
+ taskDescriptorConfigMapName
95
+ });
96
+ const { coreApi, workflowApi } = apiClients(options, dependencies);
97
+ await assertArgoWorkflowCrdAvailable(workflowApi);
98
+ await coreApi.createNamespacedConfigMap({
99
+ body: configMapSchema.parse({
100
+ apiVersion: "v1",
101
+ data: { "payload.json": options.payloadJson },
102
+ kind: "ConfigMap",
103
+ metadata: {
104
+ labels,
105
+ name: payloadConfigMapName,
106
+ namespace: options.namespace
107
+ }
108
+ }),
109
+ namespace: options.namespace
110
+ });
111
+ await coreApi.createNamespacedConfigMap({
112
+ body: configMapSchema.parse({
113
+ apiVersion: "v1",
114
+ data: Object.fromEntries(graph.tasks.map((task) => [`${task.taskName}.json`, `${JSON.stringify(buildRunnerTaskDescriptor(task.nodeId))}\n`])),
115
+ kind: "ConfigMap",
116
+ metadata: {
117
+ labels,
118
+ name: taskDescriptorConfigMapName,
119
+ namespace: options.namespace
120
+ }
121
+ }),
122
+ namespace: options.namespace
123
+ });
124
+ await coreApi.createNamespacedConfigMap({
125
+ body: configMapSchema.parse({
126
+ apiVersion: "v1",
127
+ data: { "schedule.yaml": options.scheduleYaml },
128
+ kind: "ConfigMap",
129
+ metadata: {
130
+ labels,
131
+ name: scheduleArtifactConfigMapName,
132
+ namespace: options.namespace
133
+ }
134
+ }),
135
+ namespace: options.namespace
136
+ });
137
+ const response = await workflowApi.createNamespacedCustomObject({
138
+ body: runnerArgoWorkflowManifestSchema.parse(workflow),
139
+ group: "argoproj.io",
140
+ namespace: options.namespace,
141
+ plural: "workflows",
142
+ version: "v1alpha1"
143
+ });
144
+ const created = z.object({ metadata: z.object({
145
+ name: z.string().min(1).optional(),
146
+ uid: z.string().min(1).optional()
147
+ }).passthrough() }).passthrough().parse(response);
148
+ return submitRunnerArgoWorkflowResultSchema.parse({
149
+ namespace: options.namespace,
150
+ payloadConfigMapName,
151
+ scheduleConfigMapName: scheduleArtifactConfigMapName,
152
+ taskDescriptorConfigMapName,
153
+ workflowName: created.metadata.name ?? workflow.metadata.name,
154
+ workflowUid: created.metadata.uid
155
+ });
156
+ }
157
+ async function assertArgoWorkflowCrdAvailable(workflowApi) {
158
+ try {
159
+ await workflowApi.getClusterCustomObject({
160
+ group: "apiextensions.k8s.io",
161
+ name: "workflows.argoproj.io",
162
+ plural: "customresourcedefinitions",
163
+ version: "v1"
164
+ });
165
+ } catch (error) {
166
+ if (isKubernetesNotFound(error)) throw new Error("Argo Workflows is not installed in the target cluster: missing CustomResourceDefinition workflows.argoproj.io");
167
+ throw error;
168
+ }
169
+ }
170
+ function isKubernetesNotFound(error) {
171
+ if (!(error instanceof Error)) return false;
172
+ return "code" in error && error.code === 404 || error.message.includes("404") || error.message.includes("NotFound");
173
+ }
174
+ function buildCommandScheduleYaml(rawOptions) {
175
+ const options = commandScheduleOptionsSchema.parse(rawOptions);
176
+ const scheduleId = options.scheduleId ?? `custom-${randomBytes(8).toString("hex")}`;
177
+ return stringify({
178
+ generated_at: options.generatedAt.toISOString(),
179
+ kind: "pipeline-schedule",
180
+ root_workflow: "root",
181
+ schedule_id: scheduleId,
182
+ source_entrypoint: "custom",
183
+ task: options.task,
184
+ version: 1,
185
+ workflows: { root: { nodes: [{
186
+ command: options.command,
187
+ id: "command",
188
+ kind: "command"
189
+ }] } }
190
+ });
191
+ }
192
+ function apiClients(options, dependencies) {
193
+ if (dependencies.coreApi && dependencies.workflowApi) return {
194
+ coreApi: dependencies.coreApi,
195
+ workflowApi: dependencies.workflowApi
196
+ };
197
+ const kubeConfig = dependencies.kubeConfig ?? new KubeConfig();
198
+ if (!dependencies.kubeConfig) if (options.kubeconfigPath) kubeConfig.loadFromFile(options.kubeconfigPath);
199
+ else kubeConfig.loadFromDefault();
200
+ return {
201
+ coreApi: dependencies.coreApi ?? kubeConfig.makeApiClient(CoreV1Api),
202
+ workflowApi: dependencies.workflowApi ?? kubeConfig.makeApiClient(CustomObjectsApi)
203
+ };
204
+ }
205
+ //#endregion
206
+ export { buildCommandScheduleYaml, submitRunnerArgoWorkflow };