@oisincoveney/pipeline 3.19.4 → 3.19.5
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.
- package/dist/argo-submit.d.ts +2 -0
- package/dist/argo-submit.js +27 -16
- package/dist/cli/submit-options.js +21 -4
- package/dist/moka-global-config.d.ts +1 -0
- package/dist/moka-global-config.js +1 -0
- package/dist/moka-submit.d.ts +5 -0
- package/dist/moka-submit.js +1 -0
- package/dist/remote/submit/argo-submission.d.ts +1 -0
- package/dist/remote/submit/argo-submission.js +1 -0
- package/dist/run-control/postgres/postgres-run-control-store.js +4 -5
- package/dist/run-control/run-control-store.js +5 -1
- package/dist/runtime/durable-store/acquisition.js +2 -1
- package/dist/runtime/durable-store/postgres/migrate-substrate.js +29 -0
- package/dist/runtime/durable-store/postgres/postgres-store.js +2 -4
- package/dist/runtime/services/kubernetes-argo-service.js +4 -0
- package/docs/adr-two-stage-submit-scheduling.md +112 -0
- package/package.json +1 -1
package/dist/argo-submit.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
|
26
26
|
Never: "Never";
|
|
27
27
|
}>>;
|
|
28
28
|
imagePullSecretName: z.ZodOptional<z.ZodString>;
|
|
29
|
+
kubeContext: z.ZodOptional<z.ZodString>;
|
|
29
30
|
kubeconfigPath: z.ZodOptional<z.ZodString>;
|
|
30
31
|
mcpGatewayAuth: z.ZodOptional<z.ZodObject<{
|
|
31
32
|
secretKey: z.ZodDefault<z.ZodString>;
|
|
@@ -59,6 +60,7 @@ declare const submitDynamicRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
|
59
60
|
Never: "Never";
|
|
60
61
|
}>>;
|
|
61
62
|
imagePullSecretName: z.ZodOptional<z.ZodString>;
|
|
63
|
+
kubeContext: z.ZodOptional<z.ZodString>;
|
|
62
64
|
kubeconfigPath: z.ZodOptional<z.ZodString>;
|
|
63
65
|
mcpGatewayAuth: z.ZodOptional<z.ZodObject<{
|
|
64
66
|
secretKey: z.ZodDefault<z.ZodString>;
|
package/dist/argo-submit.js
CHANGED
|
@@ -43,6 +43,7 @@ const submitRunnerArgoWorkflowBaseOptionShape = {
|
|
|
43
43
|
"Never"
|
|
44
44
|
]).optional(),
|
|
45
45
|
imagePullSecretName: z.string().min(1).optional(),
|
|
46
|
+
kubeContext: z.string().min(1).optional(),
|
|
46
47
|
kubeconfigPath: z.string().min(1).optional(),
|
|
47
48
|
mcpGatewayAuth: mcpGatewayAuthOptionSchema.optional(),
|
|
48
49
|
name: z.string().min(1).optional(),
|
|
@@ -166,16 +167,16 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
166
167
|
namespace: options.namespace,
|
|
167
168
|
options
|
|
168
169
|
});
|
|
169
|
-
return
|
|
170
|
-
body: runnerArgoWorkflowManifestSchema.parse(workflow),
|
|
170
|
+
return yield* submitWorkflowManifest({
|
|
171
171
|
dependencies,
|
|
172
172
|
namespace: options.namespace,
|
|
173
|
-
options
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
options,
|
|
174
|
+
resultExtras: {
|
|
175
|
+
payloadConfigMapName,
|
|
176
|
+
scheduleConfigMapName: scheduleArtifactConfigMapName,
|
|
177
|
+
taskDescriptorConfigMapName
|
|
178
|
+
},
|
|
179
|
+
workflow
|
|
179
180
|
});
|
|
180
181
|
});
|
|
181
182
|
}
|
|
@@ -219,8 +220,7 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
219
220
|
workflowId: options.workflowId
|
|
220
221
|
});
|
|
221
222
|
return Effect.gen(function* () {
|
|
222
|
-
|
|
223
|
-
yield* service.createConfigMap({
|
|
223
|
+
yield* (yield* KubernetesArgoService).createConfigMap({
|
|
224
224
|
body: configMapSchema.parse({
|
|
225
225
|
apiVersion: "v1",
|
|
226
226
|
data: { "payload.json": payloadJson },
|
|
@@ -235,14 +235,25 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
235
235
|
namespace: options.namespace,
|
|
236
236
|
options
|
|
237
237
|
});
|
|
238
|
-
return
|
|
239
|
-
body: runnerArgoWorkflowManifestSchema.parse(workflow),
|
|
238
|
+
return yield* submitWorkflowManifest({
|
|
240
239
|
dependencies,
|
|
241
240
|
namespace: options.namespace,
|
|
242
|
-
options
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
241
|
+
options,
|
|
242
|
+
resultExtras: { payloadConfigMapName },
|
|
243
|
+
workflow
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function submitWorkflowManifest(input) {
|
|
248
|
+
return Effect.gen(function* () {
|
|
249
|
+
return workflowSubmitResult(yield* (yield* KubernetesArgoService).createWorkflow({
|
|
250
|
+
body: runnerArgoWorkflowManifestSchema.parse(input.workflow),
|
|
251
|
+
dependencies: input.dependencies,
|
|
252
|
+
namespace: input.namespace,
|
|
253
|
+
options: input.options
|
|
254
|
+
}), input.workflow, {
|
|
255
|
+
namespace: input.namespace,
|
|
256
|
+
...input.resultExtras
|
|
246
257
|
});
|
|
247
258
|
});
|
|
248
259
|
}
|
|
@@ -4,8 +4,16 @@ import { loadMokaGlobalConfig } from "../moka-global-config.js";
|
|
|
4
4
|
import { submitMoka } from "../moka-submit.js";
|
|
5
5
|
import { Option } from "commander";
|
|
6
6
|
//#region src/cli/submit-options.ts
|
|
7
|
+
function resolveOptionalSecretRef(flags, fromGlobalConfig) {
|
|
8
|
+
if (flags.skip) return;
|
|
9
|
+
if (flags.secretName) return {
|
|
10
|
+
secretName: flags.secretName,
|
|
11
|
+
...flags.secretKey ? { secretKey: flags.secretKey } : {}
|
|
12
|
+
};
|
|
13
|
+
return fromGlobalConfig;
|
|
14
|
+
}
|
|
7
15
|
function addMokaSubmitOptions(command) {
|
|
8
|
-
return addRunnerArgoOptions(command.option("--quick", "submit the compact graph").option("--command", "treat input after -- as explicit argv").option("--schedule <path>", "approved schedule YAML to submit").option("--event-url <url>", "runner event sink URL").option("--open-pr", "append an open-pull-request delivery node (preview-labelled PR)").option("--task <text>", "task description for command-mode metadata"), { kubeconfig: true });
|
|
16
|
+
return addRunnerArgoOptions(command.option("--quick", "submit the compact graph").option("--command", "treat input after -- as explicit argv").option("--schedule <path>", "approved schedule YAML to submit").option("--event-url <url>", "runner event sink URL").option("--open-pr", "append an open-pull-request delivery node (preview-labelled PR)").option("--task <text>", "task description for command-mode metadata").option("--db-auth-secret-name <name>", "override momokaya.submit.dbAuth secret name").option("--db-auth-secret-key <key>", "override momokaya.submit.dbAuth secret key").option("--skip-db-auth", "omit MOKA_DB_URL injection regardless of global config").option("--mcp-gateway-auth-secret-name <name>", "override momokaya.submit.mcpGatewayAuth secret name").option("--mcp-gateway-auth-secret-key <key>", "override momokaya.submit.mcpGatewayAuth secret key").option("--skip-mcp-gateway-auth", "omit PIPELINE_MCP_GATEWAY_AUTHORIZATION injection regardless of global config"), { kubeconfig: true });
|
|
9
17
|
}
|
|
10
18
|
function runMokaSubmitFromCli(input, flags) {
|
|
11
19
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
@@ -36,8 +44,16 @@ function mokaCommonSubmitOptions(input) {
|
|
|
36
44
|
return {
|
|
37
45
|
brokerAuth: resolveMokaBrokerAuth(input.globalConfig),
|
|
38
46
|
config: input.config,
|
|
39
|
-
dbAuth:
|
|
40
|
-
|
|
47
|
+
dbAuth: resolveOptionalSecretRef({
|
|
48
|
+
secretKey: input.flags.dbAuthSecretKey,
|
|
49
|
+
secretName: input.flags.dbAuthSecretName,
|
|
50
|
+
skip: input.flags.skipDbAuth
|
|
51
|
+
}, momokaya?.submit.dbAuth),
|
|
52
|
+
mcpGatewayAuth: resolveOptionalSecretRef({
|
|
53
|
+
secretKey: input.flags.mcpGatewayAuthSecretKey,
|
|
54
|
+
secretName: input.flags.mcpGatewayAuthSecretName,
|
|
55
|
+
skip: input.flags.skipMcpGatewayAuth
|
|
56
|
+
}, momokaya?.submit.mcpGatewayAuth),
|
|
41
57
|
delivery: { pullRequest: input.flags.openPr === true },
|
|
42
58
|
eventUrl: input.eventUrl,
|
|
43
59
|
eventAuthSecretKey: momokaya?.submit.eventAuthSecretKey,
|
|
@@ -48,6 +64,7 @@ function mokaCommonSubmitOptions(input) {
|
|
|
48
64
|
image: input.flags.image,
|
|
49
65
|
imagePullPolicy: parseImagePullPolicy(input.flags.imagePullPolicy),
|
|
50
66
|
imagePullSecretName: input.flags.imagePullSecret ?? momokaya?.submit.imagePullSecretName,
|
|
67
|
+
kubeContext: input.flags.kubeContext ?? momokaya?.kubernetes.context,
|
|
51
68
|
kubeconfigPath: input.flags.kubeconfig ?? momokaya?.kubernetes.kubeconfig,
|
|
52
69
|
name: input.flags.name,
|
|
53
70
|
namespace: input.flags.namespace ?? momokaya?.kubernetes.namespace,
|
|
@@ -83,7 +100,7 @@ function submitMokaGraphInput(input, flags, commonOptions) {
|
|
|
83
100
|
}
|
|
84
101
|
function addRunnerArgoOptions(command, options = {}) {
|
|
85
102
|
command.option("--name <name>", "Workflow metadata.name").option("--generate-name <prefix>", "Workflow metadata.generateName").option("--namespace <namespace>", "Workflow namespace");
|
|
86
|
-
if (options.kubeconfig) command.option("--kubeconfig <path>", "kubeconfig path");
|
|
103
|
+
if (options.kubeconfig) command.option("--kubeconfig <path>", "kubeconfig path").option("--kube-context <name>", "kubeconfig context to target");
|
|
87
104
|
return command.option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").addOption(new Option("--image-pull-policy <policy>", "runner image pull policy").choices([
|
|
88
105
|
"Always",
|
|
89
106
|
"IfNotPresent",
|
|
@@ -29,6 +29,7 @@ const mokaSubmitGlobalConfigSchema = z.object({
|
|
|
29
29
|
serviceAccountName: z.string().min(1)
|
|
30
30
|
}).strict();
|
|
31
31
|
const mokaKubernetesGlobalConfigSchema = z.object({
|
|
32
|
+
context: z.string().min(1).optional(),
|
|
32
33
|
kubeconfig: z.string().min(1).optional(),
|
|
33
34
|
namespace: z.string().min(1)
|
|
34
35
|
}).strict();
|
package/dist/moka-submit.d.ts
CHANGED
|
@@ -197,6 +197,7 @@ declare const mokaSubmitBaseOptionsSchema: z.ZodObject<{
|
|
|
197
197
|
Never: "Never";
|
|
198
198
|
}>>;
|
|
199
199
|
imagePullSecretName: z.ZodOptional<z.ZodString>;
|
|
200
|
+
kubeContext: z.ZodOptional<z.ZodString>;
|
|
200
201
|
kubeconfigPath: z.ZodOptional<z.ZodString>;
|
|
201
202
|
name: z.ZodOptional<z.ZodString>;
|
|
202
203
|
namespace: z.ZodOptional<z.ZodString>;
|
|
@@ -310,6 +311,7 @@ declare const mokaGraphSubmitOptionsSchema: z.ZodObject<{
|
|
|
310
311
|
Never: "Never";
|
|
311
312
|
}>>;
|
|
312
313
|
imagePullSecretName: z.ZodOptional<z.ZodString>;
|
|
314
|
+
kubeContext: z.ZodOptional<z.ZodString>;
|
|
313
315
|
kubeconfigPath: z.ZodOptional<z.ZodString>;
|
|
314
316
|
name: z.ZodOptional<z.ZodString>;
|
|
315
317
|
namespace: z.ZodOptional<z.ZodString>;
|
|
@@ -440,6 +442,7 @@ declare const mokaCommandSubmitOptionsSchema: z.ZodObject<{
|
|
|
440
442
|
Never: "Never";
|
|
441
443
|
}>>;
|
|
442
444
|
imagePullSecretName: z.ZodOptional<z.ZodString>;
|
|
445
|
+
kubeContext: z.ZodOptional<z.ZodString>;
|
|
443
446
|
kubeconfigPath: z.ZodOptional<z.ZodString>;
|
|
444
447
|
name: z.ZodOptional<z.ZodString>;
|
|
445
448
|
namespace: z.ZodOptional<z.ZodString>;
|
|
@@ -565,6 +568,7 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
565
568
|
Never: "Never";
|
|
566
569
|
}>>;
|
|
567
570
|
imagePullSecretName: z.ZodOptional<z.ZodString>;
|
|
571
|
+
kubeContext: z.ZodOptional<z.ZodString>;
|
|
568
572
|
kubeconfigPath: z.ZodOptional<z.ZodString>;
|
|
569
573
|
name: z.ZodOptional<z.ZodString>;
|
|
570
574
|
namespace: z.ZodOptional<z.ZodString>;
|
|
@@ -694,6 +698,7 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
694
698
|
Never: "Never";
|
|
695
699
|
}>>;
|
|
696
700
|
imagePullSecretName: z.ZodOptional<z.ZodString>;
|
|
701
|
+
kubeContext: z.ZodOptional<z.ZodString>;
|
|
697
702
|
kubeconfigPath: z.ZodOptional<z.ZodString>;
|
|
698
703
|
name: z.ZodOptional<z.ZodString>;
|
|
699
704
|
namespace: z.ZodOptional<z.ZodString>;
|
package/dist/moka-submit.js
CHANGED
|
@@ -66,6 +66,7 @@ const mokaSubmitBaseOptionsSchema = z.object({
|
|
|
66
66
|
image: z.string().min(1).optional(),
|
|
67
67
|
imagePullPolicy: imagePullPolicySchema,
|
|
68
68
|
imagePullSecretName: z.string().min(1).optional(),
|
|
69
|
+
kubeContext: z.string().min(1).optional(),
|
|
69
70
|
kubeconfigPath: z.string().min(1).optional(),
|
|
70
71
|
name: z.string().min(1).optional(),
|
|
71
72
|
namespace: z.string().min(1).optional(),
|
|
@@ -50,6 +50,7 @@ function workflowSubmitOptions(options) {
|
|
|
50
50
|
image: options.image,
|
|
51
51
|
imagePullPolicy: options.imagePullPolicy,
|
|
52
52
|
imagePullSecretName: options.imagePullSecretName,
|
|
53
|
+
kubeContext: options.kubeContext,
|
|
53
54
|
kubeconfigPath: options.kubeconfigPath,
|
|
54
55
|
name: options.name,
|
|
55
56
|
namespace: requireSubmitOption(options.namespace, "namespace"),
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
+
import "../../runtime/durable-store/postgres/migrate-substrate.js";
|
|
1
2
|
import { DEFAULT_RUN_CONTROL_STALE_DETECTION, parseMokaNodeStatus, parseMokaRunController, parseMokaRunEvent, parseMokaRunManifest, parseMokaRunStatus, parseRunControlStaleDetection, parseRunEffort, parseRunMode, parseRunTarget } from "../contracts.js";
|
|
2
3
|
import { publishScheduleManifest } from "../store-manifest.js";
|
|
3
4
|
import { runControlEvent, runControlNodeArtifact, runControlNodeSession, runControlRun } from "./schema.js";
|
|
4
5
|
import { Effect } from "effect";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { asc, eq, inArray } from "drizzle-orm";
|
|
7
6
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
8
|
-
import "drizzle-orm/postgres-js/migrator";
|
|
9
7
|
import postgres from "postgres";
|
|
10
|
-
|
|
8
|
+
import { asc, eq, inArray } from "drizzle-orm";
|
|
9
|
+
//#region src/run-control/postgres/postgres-run-control-store.ts
|
|
11
10
|
function openClient(dbUrl) {
|
|
12
11
|
return postgres(dbUrl, { max: 1 });
|
|
13
12
|
}
|
|
@@ -39,7 +38,7 @@ function createRun(db, input) {
|
|
|
39
38
|
manifest,
|
|
40
39
|
runId: manifest.runId
|
|
41
40
|
}).onConflictDoNothing());
|
|
42
|
-
const existing = yield*
|
|
41
|
+
const existing = yield* loadBaseManifest(db, manifest.runId);
|
|
43
42
|
if (existing === void 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`Run ${manifest.runId} not found after createRun upsert.`));
|
|
44
43
|
return existing;
|
|
45
44
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadMokaDbUrl, requireMokaDbUrl } from "../moka-global-config.js";
|
|
2
|
+
import { migratePostgresSubstrate } from "../runtime/durable-store/postgres/migrate-substrate.js";
|
|
2
3
|
import { postgresRunControlStore } from "./postgres/postgres-run-control-store.js";
|
|
3
4
|
import "./store-paths.js";
|
|
4
5
|
import "./store.js";
|
|
@@ -19,7 +20,10 @@ import { Effect } from "effect";
|
|
|
19
20
|
* filesystem store holds no resources, so it is returned directly.
|
|
20
21
|
*/
|
|
21
22
|
function resolveRunControlStore(dbUrl, _workspaceRoot) {
|
|
22
|
-
return requireMokaDbUrl(dbUrl).pipe(Effect.flatMap((requiredDbUrl) => Effect.
|
|
23
|
+
return requireMokaDbUrl(dbUrl).pipe(Effect.flatMap((requiredDbUrl) => Effect.tryPromise({
|
|
24
|
+
catch: (error) => error,
|
|
25
|
+
try: () => migratePostgresSubstrate(requiredDbUrl)
|
|
26
|
+
}).pipe(Effect.flatMap(() => Effect.acquireRelease(Effect.sync(() => postgresRunControlStore(requiredDbUrl)), (store) => Effect.promise(() => store.close()))))));
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* PIPE-91.14: the single store-lifecycle wrapper shared by every run-control
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { requireMokaDbUrl } from "../../moka-global-config.js";
|
|
2
|
+
import { migratePostgresSubstrate } from "./postgres/migrate-substrate.js";
|
|
2
3
|
import { postgresDurableRunStore } from "./postgres/postgres-store.js";
|
|
3
4
|
import { Effect } from "effect";
|
|
4
5
|
//#region src/runtime/durable-store/acquisition.ts
|
|
5
6
|
function resolveDurableStore(dbUrl, runId) {
|
|
6
|
-
return requireMokaDbUrl(dbUrl).pipe(Effect.flatMap((requiredDbUrl) => Effect.acquireRelease(Effect.tryPromise(() => postgresDurableRunStore(requiredDbUrl, runId)), (store) => Effect.promise(() => store.close()))));
|
|
7
|
+
return requireMokaDbUrl(dbUrl).pipe(Effect.flatMap((requiredDbUrl) => Effect.tryPromise(() => migratePostgresSubstrate(requiredDbUrl)).pipe(Effect.flatMap(() => Effect.acquireRelease(Effect.tryPromise(() => postgresDurableRunStore(requiredDbUrl, runId)), (store) => Effect.promise(() => store.close()))))));
|
|
7
8
|
}
|
|
8
9
|
//#endregion
|
|
9
10
|
export { resolveDurableStore };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
3
|
+
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
|
4
|
+
import postgres from "postgres";
|
|
5
|
+
//#region src/runtime/durable-store/postgres/migrate-substrate.ts
|
|
6
|
+
const migrationsFolder = fileURLToPath(new URL("./migrations", import.meta.url));
|
|
7
|
+
const MIGRATION_LOCK_KEY = 8942017;
|
|
8
|
+
/**
|
|
9
|
+
* Apply the Drizzle migrations shared by the run-control and durable stores to
|
|
10
|
+
* `dbUrl`. Idempotent (Drizzle tracks applied migrations in
|
|
11
|
+
* `__drizzle_migrations` by content hash) and safe under concurrent callers
|
|
12
|
+
* (Postgres advisory lock serializes them). Opens and closes its own
|
|
13
|
+
* single-connection client.
|
|
14
|
+
*/
|
|
15
|
+
async function migratePostgresSubstrate(dbUrl) {
|
|
16
|
+
const client = postgres(dbUrl, { max: 1 });
|
|
17
|
+
try {
|
|
18
|
+
await client`select pg_advisory_lock(${MIGRATION_LOCK_KEY}::bigint)`;
|
|
19
|
+
try {
|
|
20
|
+
await migrate(drizzle(client), { migrationsFolder });
|
|
21
|
+
} finally {
|
|
22
|
+
await client`select pg_advisory_unlock(${MIGRATION_LOCK_KEY}::bigint)`;
|
|
23
|
+
}
|
|
24
|
+
} finally {
|
|
25
|
+
await client.end();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { migratePostgresSubstrate };
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
+
import "./migrate-substrate.js";
|
|
1
2
|
import { createSerializedWriteQueue } from "../../../serialized-write-queue.js";
|
|
2
3
|
import { buildRunJournal } from "../../run-journal.js";
|
|
3
4
|
import { durableNodeRecord, durableRun } from "./schema.js";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { eq } from "drizzle-orm";
|
|
6
5
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
7
|
-
import "drizzle-orm/postgres-js/migrator";
|
|
8
6
|
import postgres from "postgres";
|
|
7
|
+
import { eq } from "drizzle-orm";
|
|
9
8
|
import pino from "pino";
|
|
10
9
|
//#region src/runtime/durable-store/postgres/postgres-store.ts
|
|
11
10
|
const logger = pino({ name: "postgres-durable-store" });
|
|
12
|
-
fileURLToPath(new URL("./migrations", import.meta.url));
|
|
13
11
|
function openClient(dbUrl) {
|
|
14
12
|
return postgres(dbUrl, { max: 1 });
|
|
15
13
|
}
|
|
@@ -62,6 +62,10 @@ function resolveKubeConfig(options, dependencies) {
|
|
|
62
62
|
const kubeConfig = new KubeConfig();
|
|
63
63
|
if (options.kubeconfigPath) kubeConfig.loadFromFile(options.kubeconfigPath);
|
|
64
64
|
else kubeConfig.loadFromDefault();
|
|
65
|
+
if (options.kubeContext) {
|
|
66
|
+
if (!kubeConfig.getContextObject(options.kubeContext)) throw new Error(`Kube context '${options.kubeContext}' was not found in the resolved kubeconfig`);
|
|
67
|
+
kubeConfig.setCurrentContext(options.kubeContext);
|
|
68
|
+
}
|
|
65
69
|
return kubeConfig;
|
|
66
70
|
}
|
|
67
71
|
function apiClients(options, dependencies) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# ADR: Two-Stage `moka submit` — Schedule-Then-Dispatch
|
|
2
|
+
|
|
3
|
+
Status: Accepted
|
|
4
|
+
|
|
5
|
+
Date: 2026-07-01
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
|
|
9
|
+
`moka submit` is one flow with two stages, not two separate commands and not two
|
|
10
|
+
separate code paths:
|
|
11
|
+
|
|
12
|
+
1. **Schedule** — research, planning, and graph generation, producing a
|
|
13
|
+
`ScheduleArtifact` and persisting it to the durable Postgres run-control
|
|
14
|
+
store before any Argo Workflow exists.
|
|
15
|
+
2. **Dispatch** — compile the persisted (or explicitly supplied) schedule into
|
|
16
|
+
an Argo Workflow manifest and submit it, targeting whatever Kubernetes
|
|
17
|
+
cluster/context the operator configured.
|
|
18
|
+
|
|
19
|
+
Both stages run inside the single call chain:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
moka submit [args]
|
|
23
|
+
-> registerSubmitCommand() src/cli/program.ts:71
|
|
24
|
+
-> runMokaSubmitFromCli() src/cli/submit-options.ts:55
|
|
25
|
+
-> submitMoka() src/moka-submit.ts:238
|
|
26
|
+
-> submitParsedMoka() src/remote/submit/service.ts:47
|
|
27
|
+
- compileMokaSubmitPlan() src/remote/submit/compilation.ts:32
|
|
28
|
+
- upsertRunRecord() [Schedule -> DB]
|
|
29
|
+
- submitCompiledMokaWorkflow() src/remote/submit/argo-submission.ts:46 [Dispatch]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
There is exactly one WorkflowSpec builder chain (`buildRunnerArgoWorkflowManifest`
|
|
33
|
+
/ `buildDynamicRunnerArgoWorkflowManifest` in `src/argo-workflow.ts`) and exactly
|
|
34
|
+
one Kubernetes/Argo client (`KubernetesArgoService` in
|
|
35
|
+
`src/runtime/services/kubernetes-argo-service.ts`). Static and dynamic
|
|
36
|
+
scheduling are a discriminated union (`dynamicScheduling: boolean` on
|
|
37
|
+
`CompiledMokaSubmitPlan`), not parallel implementations.
|
|
38
|
+
|
|
39
|
+
## Stage 1 — Schedule (research -> planning -> graph, persisted)
|
|
40
|
+
|
|
41
|
+
When no `scheduleYaml`/`schedulePath` is supplied at submit time
|
|
42
|
+
(`dynamicScheduling: true`), the schedule is produced inside the runner pod by
|
|
43
|
+
three sequential phases, run via `moka runner-pre-schedule --phase <phase>`
|
|
44
|
+
(`src/runner-command/pre-schedule.ts`):
|
|
45
|
+
|
|
46
|
+
1. `pre-research` — produces research context (findings, risks, target).
|
|
47
|
+
2. `pre-planning` — consumes research, produces a `TicketPlan` (tickets +
|
|
48
|
+
dependencies), via `ticketPlanPlanningContext()`
|
|
49
|
+
(`src/planning/generate.ts:343`).
|
|
50
|
+
3. `generate-schedule` — calls the configured planner profile to produce the
|
|
51
|
+
final `ScheduleArtifact`. If the planner fails or emits an invalid
|
|
52
|
+
artifact, `ticketPlanScheduleArtifact()` (`src/planning/generate.ts:497`)
|
|
53
|
+
deterministically derives a schedule directly from the `TicketPlan` graph
|
|
54
|
+
instead — this is the accepted fallback shape, not a patch to remove.
|
|
55
|
+
|
|
56
|
+
The result is written once via `publishSchedule()`
|
|
57
|
+
(`src/run-control/run-control-store.ts:60`) into the run's manifest
|
|
58
|
+
(`moka_run_control_run.manifest` JSONB). `publishSchedule` is idempotent for an
|
|
59
|
+
identical schedule and rejects a conflicting one for the same run
|
|
60
|
+
(`tests/run-control-store-contract.test.ts:423`).
|
|
61
|
+
|
|
62
|
+
## Stage 2 — Dispatch (compile -> submit)
|
|
63
|
+
|
|
64
|
+
`submitParsedMoka()` (`src/remote/submit/service.ts:47`) upserts the run record
|
|
65
|
+
to Postgres **before** calling Argo, but a DB write failure never blocks
|
|
66
|
+
dispatch — the guard at `service.ts:56-61` logs and proceeds, because the
|
|
67
|
+
in-pod runner lifecycle (`src/runner-command/lifecycle.ts`) re-creates the same
|
|
68
|
+
run record idempotently once the pod starts. Postgres is a durability/inspection
|
|
69
|
+
substrate for `moka status`/`moka logs`/`moka resume`, not a precondition for
|
|
70
|
+
submission.
|
|
71
|
+
|
|
72
|
+
`submitCompiledMokaWorkflow()` then calls `submitRunnerArgoWorkflow()` (static,
|
|
73
|
+
schedule already known) or `submitDynamicRunnerArgoWorkflow()` (dynamic,
|
|
74
|
+
schedule generated in-pod) — both in `src/argo-submit.ts`, both routed through
|
|
75
|
+
the same `KubernetesArgoService`.
|
|
76
|
+
|
|
77
|
+
## Cluster targeting is one seam, not per-caller
|
|
78
|
+
|
|
79
|
+
Kubernetes context/kubeconfig resolution is a single function,
|
|
80
|
+
`resolveKubeConfig()` (`src/runtime/services/kubernetes-argo-service.ts`), used
|
|
81
|
+
by every Argo/K8s call (`createConfigMap`, `createWorkflow`,
|
|
82
|
+
`getWorkflowPhase`). Precedence, highest wins:
|
|
83
|
+
|
|
84
|
+
1. `--kube-context <name>` / `--kubeconfig <path>` CLI flags
|
|
85
|
+
(`src/cli/submit-options.ts`)
|
|
86
|
+
2. `momokaya.kubernetes.context` / `momokaya.kubernetes.kubeconfig` in
|
|
87
|
+
`~/.config/moka/config.yaml` (`src/moka-global-config.ts`)
|
|
88
|
+
3. The kubeconfig's own `current-context` (`KubeConfig.loadFromDefault()`)
|
|
89
|
+
|
|
90
|
+
An unresolvable `kubeContext` fails loud at resolution time (`kubeConfig.
|
|
91
|
+
getContextObject()` check) rather than silently falling through to whatever
|
|
92
|
+
context happened to be current — this is what makes "point submit at momokaya
|
|
93
|
+
or at a local orbstack context" a config change, never a code change. Prior to
|
|
94
|
+
this ADR, `kubeContext` existed for the `kubectl`-shellout polling path
|
|
95
|
+
(`cluster-doctor.ts`) but not for the actual submission API calls; that gap is
|
|
96
|
+
closed as part of this decision.
|
|
97
|
+
|
|
98
|
+
## Consequences
|
|
99
|
+
|
|
100
|
+
- Do not add a second "generate the graph" path or a second Argo client.
|
|
101
|
+
Static vs. dynamic scheduling is a data flag on `CompiledMokaSubmitPlan`, not
|
|
102
|
+
a reason to fork the builder or the service.
|
|
103
|
+
- Do not gate submission on the DB write succeeding — the guard in
|
|
104
|
+
`service.ts` is intentional, not a missing try/catch to "fix".
|
|
105
|
+
- Any new cluster-targeting option (a third context source, a per-namespace
|
|
106
|
+
override) is added to `resolveKubeConfig`'s precedence list, not threaded
|
|
107
|
+
ad hoc through individual call sites.
|
|
108
|
+
|
|
109
|
+
## References
|
|
110
|
+
|
|
111
|
+
- Run-control persistence model: `./run-control.md`
|
|
112
|
+
- PIPE-94 durable substrate: `oisin-ee` backlog PIPE-94.3/94.4/94.5/94.8
|
package/package.json
CHANGED
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"prepack": "nub run build:cli"
|
|
133
133
|
},
|
|
134
134
|
"type": "module",
|
|
135
|
-
"version": "3.19.
|
|
135
|
+
"version": "3.19.5",
|
|
136
136
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
137
137
|
"main": "./dist/index.js",
|
|
138
138
|
"types": "./dist/index.d.ts",
|