@oisincoveney/pipeline 3.21.0 → 3.23.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 (51) hide show
  1. package/README.md +17 -17
  2. package/dist/argo-submit.d.ts +56 -0
  3. package/dist/argo-submit.js +205 -66
  4. package/dist/argo-workflow.d.ts +22 -0
  5. package/dist/cli/factory-commands.js +33 -0
  6. package/dist/cli/program.js +2 -0
  7. package/dist/commands/pipeline-command.js +3 -1
  8. package/dist/commands/runner-command-command.js +1 -1
  9. package/dist/factory/create-experiment.js +163 -0
  10. package/dist/factory/exec.js +17 -0
  11. package/dist/factory/stamp-answers.js +28 -0
  12. package/dist/factory/template-update.js +167 -0
  13. package/dist/factory-lane.d.ts +108 -0
  14. package/dist/factory-lane.js +101 -0
  15. package/dist/loop/controller-deps.js +8 -3
  16. package/dist/loop/controller.js +1 -0
  17. package/dist/moka-submit.d.ts +140 -0
  18. package/dist/moka-submit.js +5 -2
  19. package/dist/planning/generate.d.ts +1 -0
  20. package/dist/planning/generate.js +5 -2
  21. package/dist/remote/argo/model.d.ts +30 -1
  22. package/dist/remote/argo/model.js +36 -14
  23. package/dist/remote/argo/templates.js +6 -2
  24. package/dist/remote/submit/argo-submission.d.ts +4 -0
  25. package/dist/remote/submit/argo-submission.js +4 -1
  26. package/dist/remote/submit/compilation.js +14 -0
  27. package/dist/run-control/postgres/schema.js +7 -7
  28. package/dist/run-control/runtime-event-projection.js +1 -0
  29. package/dist/runner-command/finalize.js +65 -4
  30. package/dist/runner-command-contract.d.ts +9 -1
  31. package/dist/runner-command-contract.js +11 -1
  32. package/dist/runner-event-schema.d.ts +26 -0
  33. package/dist/runner-event-schema.js +10 -0
  34. package/dist/runner-event-sink.js +7 -5
  35. package/dist/runtime/contracts/contracts.d.ts +6 -0
  36. package/dist/runtime/durable-store/postgres/migrate-substrate.js +112 -5
  37. package/dist/runtime/durable-store/postgres/postgres-store.js +1 -1
  38. package/dist/runtime/durable-store/postgres/schema.js +8 -5
  39. package/dist/runtime/open-pull-request/open-pull-request.js +30 -4
  40. package/dist/runtime/services/kubernetes-argo-service.d.ts +5 -1
  41. package/dist/runtime/services/kubernetes-argo-service.js +22 -1
  42. package/dist/runtime/services/runner-command-io-service.js +11 -2
  43. package/dist/runtime/services/runner-event-sink-http-service.js +7 -1
  44. package/dist/schedule/passes/open-pull-request.js +4 -1
  45. package/dist/tickets/backlog-task-store.d.ts +41 -1
  46. package/dist/tickets/ticket-graph-dto.d.ts +60 -0
  47. package/dist/tickets/ticket-graph.d.ts +20 -1
  48. package/docs/adr-moka-postgres-schema-isolation.md +58 -0
  49. package/docs/operator-guide.md +26 -28
  50. package/docs/pipeline-console-runner-contract.md +19 -10
  51. package/package.json +9 -1
package/README.md CHANGED
@@ -44,12 +44,12 @@ Initialize package-owned pipeline support:
44
44
  moka init
45
45
  ```
46
46
 
47
- `moka init` installs or refreshes the whole per-machine harness in one step:
48
- the package's default skills, generated host command surfaces, the singleton
49
- `pipeline-gateway` MCP entry, copied hook files from private
50
- `oisin-ee/agent/hooks`, and global instruction files from `oisin-ee/agent/rules`.
51
- OpenCode is the package default runtime. The command does not create repo-local
52
- `.pipeline` config files.
47
+ `moka init` installs or refreshes Moka-owned host adapters: `/moka-quick`,
48
+ `/moka-execute`, and `/moka-inspect` command surfaces, native-agent projections,
49
+ and the singleton `pipeline-gateway` MCP host config. It does not install the
50
+ shared agent harness; skills, hooks, rules, and Claude settings come from
51
+ `oisin-ee/agent` via chezmoi. OpenCode is the package default runtime. The
52
+ command does not create repo-local `.pipeline` config files.
53
53
 
54
54
  The default MCP gateway can run locally or point at the hosted Momokaya gateway.
55
55
  Set `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP `Authorization` header
@@ -59,14 +59,14 @@ value before starting OpenCode when using a protected gateway:
59
59
  export PIPELINE_MCP_GATEWAY_AUTHORIZATION="Basic $(printf '%s' 'user:password' | base64)"
60
60
  ```
61
61
 
62
- Verify the generated harness (commands, hooks, rules) is current after package
63
- upgrades or edits to `oisin-ee/agent`, without writing anything:
62
+ Verify generated Moka host adapters are current after package upgrades, without
63
+ writing anything:
64
64
 
65
65
  ```shell
66
66
  moka init --check
67
67
  ```
68
68
 
69
- Refresh it, overwriting any locally edited harness files:
69
+ Refresh generated Moka host adapters, overwriting locally edited adapter files:
70
70
 
71
71
  ```shell
72
72
  moka init --force
@@ -107,10 +107,10 @@ Canonical commands:
107
107
  - `moka stop <run-id> [node-id]`: abort a run or one active node.
108
108
  - `moka export <run-id> --sanitize`: print a portable evidence bundle.
109
109
  - `moka doctor`: check local prerequisites and config health.
110
- - `moka init`: install or refresh the whole per-machine harness (skills,
111
- command surfaces, hooks, rules). `--check` verifies without writing,
112
- `--dry-run` previews, `--force` overwrites locally edited files. The harness
113
- is always installed globally; there is no `--scope`.
110
+ - `moka init`: install or refresh Moka-owned host adapters (command surfaces,
111
+ native-agent projections, and gateway config). `--check` verifies without
112
+ writing, `--dry-run` previews, `--force` overwrites locally edited adapter
113
+ files. The shared agent harness is installed separately by chezmoi.
114
114
 
115
115
  ```shell
116
116
  moka run "Implement PIPE-123 user-facing behavior"
@@ -197,10 +197,10 @@ For a compact command reference, see
197
197
  ## Momokaya Runner Image
198
198
 
199
199
  The package is also the runner code used by the Momokaya runner image. The
200
- control plane owns Argo Workflow submission, run listing, cancellation, event
201
- storage, Kueue discovery, and UI rendering. This package owns the in-container
202
- `moka runner-command` and `moka runner-finalize` commands used by Argo Workflow
203
- DAG tasks.
200
+ control plane owns Argo Workflow submission, status observation, run listing,
201
+ cancellation, event storage, and UI rendering. This package owns the
202
+ in-container `moka runner-command` and `moka runner-finalize` commands used by
203
+ Argo Workflow DAG tasks.
204
204
 
205
205
  Argo starts the image with payload, schedule, and per-task descriptor files
206
206
  mounted from ConfigMaps, plus the event auth token mounted from a Secret. The
@@ -5,6 +5,7 @@ import { z } from "zod";
5
5
 
6
6
  //#region src/argo-submit.d.ts
7
7
  declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
8
+ activeDeadlineSeconds: z.ZodOptional<z.ZodNumber>;
8
9
  brokerAuth: z.ZodObject<{
9
10
  secretKey: z.ZodDefault<z.ZodString>;
10
11
  secretName: z.ZodString;
@@ -36,10 +37,38 @@ declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
36
37
  namespace: z.ZodString;
37
38
  npmRegistryAuthSecretName: z.ZodOptional<z.ZodString>;
38
39
  payloadJson: z.ZodString;
40
+ podGC: z.ZodOptional<z.ZodObject<{
41
+ deleteDelayDuration: z.ZodOptional<z.ZodString>;
42
+ labelSelector: z.ZodOptional<z.ZodObject<{
43
+ matchExpressions: z.ZodOptional<z.ZodArray<z.ZodObject<{
44
+ key: z.ZodString;
45
+ operator: z.ZodEnum<{
46
+ DoesNotExist: "DoesNotExist";
47
+ Exists: "Exists";
48
+ In: "In";
49
+ NotIn: "NotIn";
50
+ }>;
51
+ values: z.ZodOptional<z.ZodArray<z.ZodString>>;
52
+ }, z.core.$strict>>>;
53
+ matchLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
54
+ }, z.core.$strict>>;
55
+ strategy: z.ZodOptional<z.ZodEnum<{
56
+ OnPodCompletion: "OnPodCompletion";
57
+ OnPodSuccess: "OnPodSuccess";
58
+ OnWorkflowCompletion: "OnWorkflowCompletion";
59
+ OnWorkflowSuccess: "OnWorkflowSuccess";
60
+ }>>;
61
+ }, z.core.$strict>>;
39
62
  serviceAccountName: z.ZodOptional<z.ZodString>;
63
+ ttlStrategy: z.ZodOptional<z.ZodObject<{
64
+ secondsAfterCompletion: z.ZodOptional<z.ZodNumber>;
65
+ secondsAfterFailure: z.ZodOptional<z.ZodNumber>;
66
+ secondsAfterSuccess: z.ZodOptional<z.ZodNumber>;
67
+ }, z.core.$strict>>;
40
68
  scheduleYaml: z.ZodString;
41
69
  }, z.core.$strict>;
42
70
  declare const submitDynamicRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
71
+ activeDeadlineSeconds: z.ZodOptional<z.ZodNumber>;
43
72
  brokerAuth: z.ZodObject<{
44
73
  secretKey: z.ZodDefault<z.ZodString>;
45
74
  secretName: z.ZodString;
@@ -71,7 +100,34 @@ declare const submitDynamicRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
71
100
  namespace: z.ZodString;
72
101
  npmRegistryAuthSecretName: z.ZodOptional<z.ZodString>;
73
102
  payloadJson: z.ZodString;
103
+ podGC: z.ZodOptional<z.ZodObject<{
104
+ deleteDelayDuration: z.ZodOptional<z.ZodString>;
105
+ labelSelector: z.ZodOptional<z.ZodObject<{
106
+ matchExpressions: z.ZodOptional<z.ZodArray<z.ZodObject<{
107
+ key: z.ZodString;
108
+ operator: z.ZodEnum<{
109
+ DoesNotExist: "DoesNotExist";
110
+ Exists: "Exists";
111
+ In: "In";
112
+ NotIn: "NotIn";
113
+ }>;
114
+ values: z.ZodOptional<z.ZodArray<z.ZodString>>;
115
+ }, z.core.$strict>>>;
116
+ matchLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
117
+ }, z.core.$strict>>;
118
+ strategy: z.ZodOptional<z.ZodEnum<{
119
+ OnPodCompletion: "OnPodCompletion";
120
+ OnPodSuccess: "OnPodSuccess";
121
+ OnWorkflowCompletion: "OnWorkflowCompletion";
122
+ OnWorkflowSuccess: "OnWorkflowSuccess";
123
+ }>>;
124
+ }, z.core.$strict>>;
74
125
  serviceAccountName: z.ZodOptional<z.ZodString>;
126
+ ttlStrategy: z.ZodOptional<z.ZodObject<{
127
+ secondsAfterCompletion: z.ZodOptional<z.ZodNumber>;
128
+ secondsAfterFailure: z.ZodOptional<z.ZodNumber>;
129
+ secondsAfterSuccess: z.ZodOptional<z.ZodNumber>;
130
+ }, z.core.$strict>>;
75
131
  workflowId: z.ZodString;
76
132
  }, z.core.$strict>;
77
133
  declare const commandScheduleOptionsSchema: z.ZodObject<{
@@ -1,5 +1,5 @@
1
1
  import { ArgoGraphCompilerError, compileArgoExecutionGraph } from "./argo-graph.js";
2
- import { dbAuthOptionSchema, mcpGatewayAuthOptionSchema } from "./remote/argo/model.js";
2
+ import { argoWorkflowActiveDeadlineSecondsSchema, argoWorkflowPodGcSchema, argoWorkflowTtlStrategySchema, dbAuthOptionSchema, mcpGatewayAuthOptionSchema } from "./remote/argo/model.js";
3
3
  import { brokerAuthOptionSchema } from "./credentials/broker.js";
4
4
  import { appendPullRequestDelivery } from "./schedule/passes/open-pull-request.js";
5
5
  import { compileScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
@@ -25,11 +25,19 @@ const configMapSchema = z.object({
25
25
  namespace: z.string().min(1)
26
26
  }).strict()
27
27
  }).strict();
28
+ const workflowOwnerReferenceSchema = z.object({
29
+ apiVersion: z.literal("argoproj.io/v1alpha1"),
30
+ kind: z.literal("Workflow"),
31
+ name: z.string().min(1),
32
+ uid: z.string().min(1)
33
+ }).strict();
34
+ const configMapOwnerReferencesPatchSchema = z.object({ metadata: z.object({ ownerReferences: z.tuple([workflowOwnerReferenceSchema]) }).strict() }).strict();
28
35
  const createdWorkflowSchema = z.object({ metadata: z.object({
29
36
  name: z.string().min(1).optional(),
30
37
  uid: z.string().min(1).optional()
31
38
  }).passthrough() }).passthrough();
32
39
  const submitRunnerArgoWorkflowBaseOptionShape = {
40
+ activeDeadlineSeconds: argoWorkflowActiveDeadlineSecondsSchema.optional(),
33
41
  brokerAuth: brokerAuthOptionSchema,
34
42
  dbAuth: dbAuthOptionSchema.optional(),
35
43
  eventAuthSecretKey: z.string().min(1).optional(),
@@ -51,7 +59,9 @@ const submitRunnerArgoWorkflowBaseOptionShape = {
51
59
  namespace: z.string().min(1),
52
60
  npmRegistryAuthSecretName: z.string().min(1).optional(),
53
61
  payloadJson: z.string().min(1),
54
- serviceAccountName: z.string().min(1).optional()
62
+ podGC: argoWorkflowPodGcSchema.optional(),
63
+ serviceAccountName: z.string().min(1).optional(),
64
+ ttlStrategy: argoWorkflowTtlStrategySchema.optional()
55
65
  };
56
66
  const submitRunnerArgoWorkflowOptionsSchema = z.object({
57
67
  ...submitRunnerArgoWorkflowBaseOptionShape,
@@ -102,6 +112,7 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
102
112
  "pipeline.oisin.dev/ticket-project": payload.run.project,
103
113
  "pipeline.oisin.dev/ticket-title": payload.task.title
104
114
  } : {},
115
+ activeDeadlineSeconds: options.activeDeadlineSeconds,
105
116
  brokerAuth: options.brokerAuth,
106
117
  dbAuth: options.dbAuth,
107
118
  mcpGatewayAuth: options.mcpGatewayAuth,
@@ -121,51 +132,21 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
121
132
  plan: compiled.plan,
122
133
  scheduleConfigMapName: scheduleArtifactConfigMapName,
123
134
  serviceAccountName: options.serviceAccountName,
124
- taskDescriptorConfigMapName
135
+ taskDescriptorConfigMapName,
136
+ ttlStrategy: options.ttlStrategy
125
137
  });
126
138
  return Effect.gen(function* () {
127
- const service = yield* KubernetesArgoService;
128
139
  const graph = yield* graphEffect;
129
- yield* service.createConfigMap({
130
- body: configMapSchema.parse({
131
- apiVersion: "v1",
132
- data: { "payload.json": payloadJson },
133
- kind: "ConfigMap",
134
- metadata: {
135
- labels,
136
- name: payloadConfigMapName,
137
- namespace: options.namespace
138
- }
139
- }),
140
- dependencies,
141
- namespace: options.namespace,
142
- options
143
- });
144
- yield* service.createConfigMap({
145
- body: configMapSchema.parse({
146
- apiVersion: "v1",
147
- data: Object.fromEntries(graph.tasks.map((task) => [`${task.taskName}.json`, `${JSON.stringify(buildRunnerTaskDescriptor(task.nodeId))}\n`])),
148
- kind: "ConfigMap",
149
- metadata: {
150
- labels,
151
- name: taskDescriptorConfigMapName,
152
- namespace: options.namespace
153
- }
154
- }),
155
- dependencies,
156
- namespace: options.namespace,
157
- options
158
- });
159
- yield* service.createConfigMap({
160
- body: configMapSchema.parse({
161
- apiVersion: "v1",
162
- data: { "schedule.yaml": options.scheduleYaml },
163
- kind: "ConfigMap",
164
- metadata: {
165
- labels,
166
- name: scheduleArtifactConfigMapName,
167
- namespace: options.namespace
168
- }
140
+ const createdConfigMapNames = yield* createRunConfigMaps({
141
+ configMaps: staticRunConfigMaps({
142
+ labels,
143
+ namespace: options.namespace,
144
+ payloadConfigMapName,
145
+ payloadJson,
146
+ scheduleConfigMapName: scheduleArtifactConfigMapName,
147
+ scheduleYaml: options.scheduleYaml,
148
+ taskDescriptorConfigMapName,
149
+ tasks: graph.tasks
169
150
  }),
170
151
  dependencies,
171
152
  namespace: options.namespace,
@@ -180,8 +161,21 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
180
161
  scheduleConfigMapName: scheduleArtifactConfigMapName,
181
162
  taskDescriptorConfigMapName
182
163
  },
183
- workflow
184
- });
164
+ workflow,
165
+ workflowFieldOverrides: { podGC: options.podGC }
166
+ }).pipe(Effect.flatMap((result) => ownRunConfigMaps({
167
+ configMapNames: createdConfigMapNames,
168
+ dependencies,
169
+ namespace: options.namespace,
170
+ options,
171
+ result
172
+ })), Effect.catch((error) => cleanupRunConfigMapsOnFailure({
173
+ configMapNames: createdConfigMapNames,
174
+ dependencies,
175
+ error,
176
+ namespace: options.namespace,
177
+ options
178
+ })));
185
179
  });
186
180
  }
187
181
  function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
@@ -199,12 +193,14 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
199
193
  "pipeline.oisin.dev/source": "argo-workflow",
200
194
  "pipeline.oisin.dev/workflow": options.workflowId
201
195
  };
196
+ const annotations = payload.task.kind === "ticket" ? {
197
+ "pipeline.oisin.dev/ticket-id": payload.task.id,
198
+ "pipeline.oisin.dev/ticket-project": payload.run.project,
199
+ "pipeline.oisin.dev/ticket-title": payload.task.title
200
+ } : {};
202
201
  const workflow = buildDynamicRunnerArgoWorkflowManifest({
203
- annotations: payload.task.kind === "ticket" ? {
204
- "pipeline.oisin.dev/ticket-id": payload.task.id,
205
- "pipeline.oisin.dev/ticket-project": payload.run.project,
206
- "pipeline.oisin.dev/ticket-title": payload.task.title
207
- } : {},
202
+ activeDeadlineSeconds: options.activeDeadlineSeconds,
203
+ annotations,
208
204
  brokerAuth: options.brokerAuth,
209
205
  dbAuth: options.dbAuth,
210
206
  eventAuthSecretKey: options.eventAuthSecretKey,
@@ -222,19 +218,16 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
222
218
  npmRegistryAuthSecretName: options.npmRegistryAuthSecretName,
223
219
  payloadConfigMapName,
224
220
  serviceAccountName: options.serviceAccountName,
221
+ ttlStrategy: options.ttlStrategy,
225
222
  workflowId: options.workflowId
226
223
  });
227
224
  return Effect.gen(function* () {
228
- yield* (yield* KubernetesArgoService).createConfigMap({
229
- body: configMapSchema.parse({
230
- apiVersion: "v1",
231
- data: { "payload.json": payloadJson },
232
- kind: "ConfigMap",
233
- metadata: {
234
- labels,
235
- name: payloadConfigMapName,
236
- namespace: options.namespace
237
- }
225
+ const createdConfigMapNames = yield* createRunConfigMaps({
226
+ configMaps: dynamicRunConfigMaps({
227
+ labels,
228
+ namespace: options.namespace,
229
+ payloadConfigMapName,
230
+ payloadJson
238
231
  }),
239
232
  dependencies,
240
233
  namespace: options.namespace,
@@ -245,23 +238,169 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
245
238
  namespace: options.namespace,
246
239
  options,
247
240
  resultExtras: { payloadConfigMapName },
248
- workflow
249
- });
241
+ workflow,
242
+ workflowFieldOverrides: { podGC: options.podGC }
243
+ }).pipe(Effect.flatMap((result) => ownRunConfigMaps({
244
+ configMapNames: createdConfigMapNames,
245
+ dependencies,
246
+ namespace: options.namespace,
247
+ options,
248
+ result
249
+ })), Effect.catch((error) => cleanupRunConfigMapsOnFailure({
250
+ configMapNames: createdConfigMapNames,
251
+ dependencies,
252
+ error,
253
+ namespace: options.namespace,
254
+ options
255
+ })));
250
256
  });
251
257
  }
258
+ function staticRunConfigMaps(input) {
259
+ return [
260
+ runConfigMap({
261
+ data: { "payload.json": input.payloadJson },
262
+ labels: input.labels,
263
+ name: input.payloadConfigMapName,
264
+ namespace: input.namespace
265
+ }),
266
+ runConfigMap({
267
+ data: Object.fromEntries(input.tasks.map((task) => [`${task.taskName}.json`, `${JSON.stringify(buildRunnerTaskDescriptor(task.nodeId))}\n`])),
268
+ labels: input.labels,
269
+ name: input.taskDescriptorConfigMapName,
270
+ namespace: input.namespace
271
+ }),
272
+ runConfigMap({
273
+ data: { "schedule.yaml": input.scheduleYaml },
274
+ labels: input.labels,
275
+ name: input.scheduleConfigMapName,
276
+ namespace: input.namespace
277
+ })
278
+ ];
279
+ }
280
+ function dynamicRunConfigMaps(input) {
281
+ return [runConfigMap({
282
+ data: { "payload.json": input.payloadJson },
283
+ labels: input.labels,
284
+ name: input.payloadConfigMapName,
285
+ namespace: input.namespace
286
+ })];
287
+ }
288
+ function runConfigMap(input) {
289
+ return {
290
+ body: configMapSchema.parse({
291
+ apiVersion: "v1",
292
+ data: input.data,
293
+ kind: "ConfigMap",
294
+ metadata: {
295
+ labels: input.labels,
296
+ name: input.name,
297
+ namespace: input.namespace
298
+ }
299
+ }),
300
+ name: input.name
301
+ };
302
+ }
303
+ function createRunConfigMaps(input) {
304
+ const createdConfigMapNames = [];
305
+ return Effect.gen(function* () {
306
+ const service = yield* KubernetesArgoService;
307
+ yield* Effect.forEach(input.configMaps, (configMap) => service.createConfigMap({
308
+ body: configMap.body,
309
+ dependencies: input.dependencies,
310
+ namespace: input.namespace,
311
+ options: input.options
312
+ }).pipe(Effect.tap(() => Effect.sync(() => {
313
+ createdConfigMapNames.push(configMap.name);
314
+ }))));
315
+ return createdConfigMapNames;
316
+ }).pipe(Effect.catch((error) => cleanupRunConfigMapsOnFailure({
317
+ configMapNames: createdConfigMapNames,
318
+ dependencies: input.dependencies,
319
+ error,
320
+ namespace: input.namespace,
321
+ options: input.options
322
+ })));
323
+ }
324
+ function ownRunConfigMaps(input) {
325
+ return patchRunConfigMapOwnerReferences({
326
+ body: configMapOwnerReferencesPatch(workflowOwnerReference(input.result)),
327
+ configMapNames: input.configMapNames,
328
+ dependencies: input.dependencies,
329
+ namespace: input.namespace,
330
+ options: input.options
331
+ }).pipe(Effect.as(input.result));
332
+ }
333
+ function cleanupRunConfigMapsOnFailure(input) {
334
+ return deleteRunConfigMaps({
335
+ configMapNames: input.configMapNames,
336
+ dependencies: input.dependencies,
337
+ namespace: input.namespace,
338
+ options: input.options
339
+ }).pipe(Effect.catch((cleanupError) => Effect.fail(/* @__PURE__ */ new Error(`Failed to clean up ConfigMaps after submit failure: ${errorMessage(input.error)}; cleanup failed: ${errorMessage(cleanupError)}`))), Effect.flatMap(() => Effect.fail(input.error)));
340
+ }
341
+ function patchRunConfigMapOwnerReferences(input) {
342
+ return Effect.gen(function* () {
343
+ const service = yield* KubernetesArgoService;
344
+ return yield* Effect.all(input.configMapNames.map((name) => service.patchConfigMapOwnerReferences({
345
+ body: input.body,
346
+ dependencies: input.dependencies,
347
+ name,
348
+ namespace: input.namespace,
349
+ options: input.options
350
+ })), { concurrency: "unbounded" });
351
+ });
352
+ }
353
+ function deleteRunConfigMaps(input) {
354
+ return Effect.gen(function* () {
355
+ const service = yield* KubernetesArgoService;
356
+ return yield* Effect.all(input.configMapNames.map((name) => service.deleteConfigMap({
357
+ dependencies: input.dependencies,
358
+ name,
359
+ namespace: input.namespace,
360
+ options: input.options
361
+ })), { concurrency: "unbounded" });
362
+ });
363
+ }
364
+ function workflowOwnerReference(result) {
365
+ if (result.workflowUid === void 0) throw new Error(`Created Argo Workflow '${result.workflowName}' did not include metadata.uid; cannot own ConfigMaps`);
366
+ return workflowOwnerReferenceSchema.parse({
367
+ apiVersion: "argoproj.io/v1alpha1",
368
+ kind: "Workflow",
369
+ name: result.workflowName,
370
+ uid: result.workflowUid
371
+ });
372
+ }
373
+ function configMapOwnerReferencesPatch(ownerReference) {
374
+ return configMapOwnerReferencesPatchSchema.parse({ metadata: { ownerReferences: [ownerReference] } });
375
+ }
376
+ function errorMessage(error) {
377
+ return error instanceof Error ? error.message : String(error);
378
+ }
252
379
  function submitWorkflowManifest(input) {
253
380
  return Effect.gen(function* () {
254
- return workflowSubmitResult(yield* (yield* KubernetesArgoService).createWorkflow({
255
- body: runnerArgoWorkflowManifestSchema.parse(input.workflow),
381
+ const service = yield* KubernetesArgoService;
382
+ const workflow = applyWorkflowFieldOverrides(input.workflow, input.workflowFieldOverrides);
383
+ return workflowSubmitResult(yield* service.createWorkflow({
384
+ body: runnerArgoWorkflowManifestSchema.parse(workflow),
256
385
  dependencies: input.dependencies,
257
386
  namespace: input.namespace,
258
387
  options: input.options
259
- }), input.workflow, {
388
+ }), workflow, {
260
389
  namespace: input.namespace,
261
390
  ...input.resultExtras
262
391
  });
263
392
  });
264
393
  }
394
+ function applyWorkflowFieldOverrides(workflow, overrides) {
395
+ if (overrides?.podGC === void 0) return workflow;
396
+ return runnerArgoWorkflowManifestSchema.parse({
397
+ ...workflow,
398
+ spec: {
399
+ ...workflow.spec,
400
+ podGC: overrides.podGC
401
+ }
402
+ });
403
+ }
265
404
  function workflowSubmitResult(response, workflow, base) {
266
405
  const created = createdWorkflowSchema.parse(response);
267
406
  return workflowSubmitResultSchema.parse({
@@ -126,6 +126,28 @@ declare const runnerArgoWorkflowManifestSchema: z.ZodObject<{
126
126
  }>;
127
127
  }, z.core.$strict>>;
128
128
  }, z.core.$strict>>;
129
+ podGC: z.ZodOptional<z.ZodObject<{
130
+ deleteDelayDuration: z.ZodOptional<z.ZodString>;
131
+ labelSelector: z.ZodOptional<z.ZodObject<{
132
+ matchExpressions: z.ZodOptional<z.ZodArray<z.ZodObject<{
133
+ key: z.ZodString;
134
+ operator: z.ZodEnum<{
135
+ DoesNotExist: "DoesNotExist";
136
+ Exists: "Exists";
137
+ In: "In";
138
+ NotIn: "NotIn";
139
+ }>;
140
+ values: z.ZodOptional<z.ZodArray<z.ZodString>>;
141
+ }, z.core.$strict>>>;
142
+ matchLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
143
+ }, z.core.$strict>>;
144
+ strategy: z.ZodOptional<z.ZodEnum<{
145
+ OnPodCompletion: "OnPodCompletion";
146
+ OnPodSuccess: "OnPodSuccess";
147
+ OnWorkflowCompletion: "OnWorkflowCompletion";
148
+ OnWorkflowSuccess: "OnWorkflowSuccess";
149
+ }>>;
150
+ }, z.core.$strict>>;
129
151
  ttlStrategy: z.ZodOptional<z.ZodObject<{
130
152
  secondsAfterCompletion: z.ZodOptional<z.ZodNumber>;
131
153
  secondsAfterFailure: z.ZodOptional<z.ZodNumber>;
@@ -0,0 +1,33 @@
1
+ import { runCreateExperiment } from "../factory/create-experiment.js";
2
+ import { runTemplateUpdate, summarizeTemplateUpdate } from "../factory/template-update.js";
3
+ import { Option } from "commander";
4
+ //#region src/cli/factory-commands.ts
5
+ function registerFactoryCommands(program) {
6
+ program.command("create-experiment").description("Birth a fleet experiment: copier-stamp momokaya-template, create+push the org repo, register it in infra's fleet registry").requiredOption("--name <name>", "app name (kebab-case)").addOption(new Option("--flavor <flavor>", "app flavor").choices(["web", "expo-web"]).default("web")).option("--no-db", "skip the database surface").option("--no-previews", "skip per-PR preview environments").option("--org <org>", "GitHub org for the new repo").option("--template-src <source>", "copier template source").option("--template-ref <ref>", "template tag/ref (default: latest tag)").option("--infra-repo-url <url>", "infra repo the registry entry lands in").action(async (flags) => {
7
+ const result = await runCreateExperiment({
8
+ db: flags.db,
9
+ flavor: flags.flavor,
10
+ ...flags.infraRepoUrl ? { infraRepoUrl: flags.infraRepoUrl } : {},
11
+ name: flags.name,
12
+ ...flags.org ? { org: flags.org } : {},
13
+ previews: flags.previews,
14
+ ...flags.templateRef ? { templateRef: flags.templateRef } : {},
15
+ ...flags.templateSrc ? { templateSource: flags.templateSrc } : {}
16
+ });
17
+ console.log(`Experiment born: ${result.repoUrl} (registry ${result.registryPath} @ infra ${result.infraCommitSha})`);
18
+ });
19
+ program.command("template-update").description("Fan copier-update PRs out across repos stamped from momokaya-template").option("--repos <repos>", "comma-separated repo list (skips fleet-registry discovery)").option("--org <org>", "GitHub org the stamped repos live in").option("--template-match <substring>", "answers-file _src_path filter for stamp detection").option("--template-ref <ref>", "template tag/ref (default: latest tag)").option("--infra-repo-url <url>", "infra repo used for discovery").action(async (flags) => {
20
+ const { results } = await runTemplateUpdate({
21
+ ...flags.infraRepoUrl ? { infraRepoUrl: flags.infraRepoUrl } : {},
22
+ ...flags.org ? { org: flags.org } : {},
23
+ ...flags.repos ? { repos: flags.repos.split(",").map((repo) => repo.trim()).filter((repo) => repo.length > 0) } : {},
24
+ ...flags.templateMatch ? { templateMatch: flags.templateMatch } : {},
25
+ ...flags.templateRef ? { templateRef: flags.templateRef } : {}
26
+ });
27
+ const { failed, opened } = summarizeTemplateUpdate(results);
28
+ console.log(`template-update: ${opened} PR(s) opened, ${failed} error(s)`);
29
+ if (failed > 0) process.exitCode = 1;
30
+ });
31
+ }
32
+ //#endregion
33
+ export { registerFactoryCommands };
@@ -7,6 +7,7 @@ import { registerTicketCommand } from "../commands/ticket-command.js";
7
7
  import { addMokaSubmitOptions, runMokaSubmitFromCli } from "./submit-options.js";
8
8
  import { registerRunControlCommands } from "../run-control/commands.js";
9
9
  import { registerBootstrapCommands } from "./bootstrap-commands.js";
10
+ import { registerFactoryCommands } from "./factory-commands.js";
10
11
  import { registerLoopCommand } from "./loop-commands.js";
11
12
  import { registerMcpGatewayCommands } from "./mcp-gateway-commands.js";
12
13
  import { registerPlanCommands } from "./plan-commands.js";
@@ -34,6 +35,7 @@ function registerApplicationCommands(program, options) {
34
35
  registerMcpGatewayCommands(program);
35
36
  registerSubmitCommand(program);
36
37
  registerLoopCommand(program);
38
+ registerFactoryCommands(program);
37
39
  registerRunnerCommandCommand(program);
38
40
  registerBenchCommand(program);
39
41
  registerTicketCommand(program, {
@@ -10,7 +10,9 @@ const BUILTIN_PIPE_COMMANDS = /* @__PURE__ */ new Set([
10
10
  "submit",
11
11
  "argo",
12
12
  "runner-command",
13
- "ticket"
13
+ "ticket",
14
+ "create-experiment",
15
+ "template-update"
14
16
  ]);
15
17
  var EntrypointCommandService = class extends Context.Service()("EntrypointCommandService") {};
16
18
  const createEntrypointCommandServiceLive = (runEntrypoint) => Layer.succeed(EntrypointCommandService, { runEntrypoint: (entrypoint, task, opts) => Effect.tryPromise({
@@ -51,7 +51,7 @@ function registerRunnerCommandCommand(program) {
51
51
  program.command("runner-command").description("Run one scheduled Argo Workflow task").requiredOption("--payload-file <path>", "Path to the runner payload JSON").option("--node-id <id>", "Node id to execute without a task descriptor").option("--schedule-file <path>", "Path to the schedule artifact YAML").option("--schedule-source <source>", "Schedule source: file or db").action((options) => runRunnerProgram(runRunnerCommandEffect(options)));
52
52
  program.command("runner-lifecycle").description("Run one Argo Workflow lifecycle phase").requiredOption("--phase <phase>", "Lifecycle phase to run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").action((options) => runRunnerProgram(runRunnerLifecycleEffect(options)));
53
53
  program.command("runner-pre-schedule").description("Run one dynamic pre-schedule phase").requiredOption("--phase <phase>", "pre-research, pre-planning, or generate-schedule").requiredOption("--payload-file <path>", "Path to the runner payload JSON").action((options) => runRunnerProgram(runPreScheduleEffect(options)));
54
- program.command("runner-finalize").description("Finalize one Argo Workflow run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").option("--schedule-file <path>", "Path to the schedule artifact YAML").option("--schedule-source <source>", "Schedule source: file or db").requiredOption("--argo-status <status>", "Argo Workflow status").action((options) => runRunnerProgram(runRunnerFinalizeEffect(options)));
54
+ program.command("runner-finalize").description("Finalize one Argo Workflow run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").option("--schedule-file <path>", "Path to the schedule artifact YAML").option("--schedule-source <source>", "Schedule source: file or db").requiredOption("--argo-status <status>", "Argo Workflow status").option("--argo-failures <json>", "Argo Workflow failure details JSON").action((options) => runRunnerProgram(runRunnerFinalizeEffect(options)));
55
55
  program.command("runner-select-ready-wave").description("Select DB-ready nodes for the next dynamic Argo wave").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--output-file <path>", "Path where the ready node id JSON array is written").action((options) => runRunnerProgram(runSelectReadyWaveEffect(options)));
56
56
  }
57
57
  //#endregion