@oisincoveney/pipeline 3.24.2 → 4.0.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.
- package/README.md +14 -2
- package/defaults/pipeline.yaml +23 -5
- package/defaults/profiles.yaml +104 -21
- package/dist/argo-graph.js +90 -88
- package/dist/argo-submit.d.ts +110 -138
- package/dist/argo-submit.js +363 -378
- package/dist/argo-workflow.d.ts +156 -177
- package/dist/argo-workflow.js +96 -138
- package/dist/backlog.js +5 -4
- package/dist/bench/eval-report.js +21 -12
- package/dist/claude-settings-config.js +23 -31
- package/dist/claude-user-config.js +6 -16
- package/dist/cli/bootstrap-commands.js +3 -3
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.js +176 -171
- package/dist/cli/factory-commands.js +13 -13
- package/dist/cli/format.js +92 -86
- package/dist/cli/loop-commands.js +21 -21
- package/dist/cli/mcp-gateway-commands.js +12 -12
- package/dist/cli/plan-commands.js +85 -116
- package/dist/cli/program.d.ts +3 -3
- package/dist/cli/program.js +41 -43
- package/dist/cli/run-command.d.ts +1 -1
- package/dist/cli/run-command.js +25 -25
- package/dist/cli/run-commands.js +31 -29
- package/dist/cli/run-resolver.d.ts +1 -1
- package/dist/cli/run-resolver.js +30 -32
- package/dist/cli/run-service.d.ts +2 -3
- package/dist/cli/run-service.js +229 -239
- package/dist/cli/submit-options.js +85 -83
- package/dist/cluster-doctor.js +147 -175
- package/dist/codex-config.js +24 -30
- package/dist/commands/bench-command.js +9 -7
- package/dist/commands/coordinator-command.js +35 -0
- package/dist/commands/pipeline-command.js +13 -7
- package/dist/commands/runner-command-command.js +39 -24
- package/dist/commands/runner-node-command.js +31 -0
- package/dist/commands/ticket/complete.js +29 -33
- package/dist/commands/ticket/create.js +84 -82
- package/dist/commands/ticket/graph-check.js +11 -11
- package/dist/commands/ticket/next.js +36 -36
- package/dist/commands/ticket/sequence.js +11 -12
- package/dist/commands/ticket/shared.d.ts +5 -2
- package/dist/commands/ticket/shared.js +63 -66
- package/dist/commands/ticket/start.js +58 -67
- package/dist/commands/ticket-command.js +2 -2
- package/dist/config/defaults.d.ts +139 -140
- package/dist/config/defaults.js +81 -80
- package/dist/config/lint.js +52 -64
- package/dist/config/load.d.ts +4 -5
- package/dist/config/load.js +64 -74
- package/dist/config/schema/catalog.d.ts +2 -1
- package/dist/config/schema/catalog.js +3 -2
- package/dist/config/schema/mcp.js +46 -52
- package/dist/config/schemas.d.ts +382 -528
- package/dist/config/schemas.js +400 -398
- package/dist/config/validate.d.ts +1 -2
- package/dist/config/validate.js +236 -201
- package/dist/config.d.ts +2 -2
- package/dist/context/repo-map.js +105 -119
- package/dist/coordinator/composition.js +169 -0
- package/dist/coordinator/config.js +71 -0
- package/dist/coordinator/run-owner.js +64 -0
- package/dist/coordinator/wave-advance.js +148 -0
- package/dist/coordinator/wave-scheduler.js +50 -0
- package/dist/credentials/broker.d.ts +6 -8
- package/dist/credentials/broker.js +17 -16
- package/dist/credentials/codex-config.js +15 -19
- package/dist/credentials/file-targets.js +9 -15
- package/dist/credentials/local-codex-auth-sync.js +55 -58
- package/dist/credentials/opencode-config.js +28 -32
- package/dist/credentials/runner.js +13 -13
- package/dist/effect-sync-errors.js +28 -0
- package/dist/factory/create-experiment.js +285 -121
- package/dist/factory/exec.js +10 -12
- package/dist/factory/git-credentials.js +4 -4
- package/dist/factory/stamp-answers.js +10 -11
- package/dist/factory/template-update.js +65 -44
- package/dist/factory-lane.d.ts +26 -40
- package/dist/factory-lane.js +26 -25
- package/dist/gates.js +3 -3
- package/dist/git-remote-url.js +17 -19
- package/dist/hooks.d.ts +26 -26
- package/dist/hooks.js +20 -22
- package/dist/index.d.ts +4 -3
- package/dist/index.js +27 -36
- package/dist/install-commands/claude-code.js +100 -92
- package/dist/install-commands/host-selection.js +3 -6
- package/dist/install-commands/opencode.js +264 -259
- package/dist/install-commands/planner.js +163 -181
- package/dist/install-commands/result-format.js +1 -3
- package/dist/install-commands/shared.js +25 -42
- package/dist/install-commands/writer.js +22 -25
- package/dist/install-commands.js +8 -8
- package/dist/jscpd-output.js +29 -0
- package/dist/json-config-merge.js +14 -20
- package/dist/json-line-values.js +17 -6
- package/dist/loop/argo-poll.d.ts +0 -1
- package/dist/loop/argo-poll.js +15 -95
- package/dist/loop/backlog-records.js +1 -3
- package/dist/loop/controller-deps.js +150 -161
- package/dist/loop/controller.js +164 -186
- package/dist/loop/gh-checks.js +62 -61
- package/dist/loop/gh-runner.js +22 -25
- package/dist/loop/loop-command.js +66 -58
- package/dist/loop/loop-controller-entrypoint.js +29 -30
- package/dist/loop/merge.js +36 -40
- package/dist/loop/run-terminal-poll.js +69 -0
- package/dist/mcp/gateway-config.js +25 -29
- package/dist/mcp/gateway-doctor.js +77 -119
- package/dist/mcp/gateway-reconcile.js +17 -22
- package/dist/mcp/gateway-runtime.js +1 -3
- package/dist/mcp/host-config.js +37 -40
- package/dist/mcp/host-renderers.js +10 -18
- package/dist/mcp/repo-local-backends.js +51 -54
- package/dist/mcp/toolhive-vmcp.js +39 -39
- package/dist/model-resolver.js +29 -33
- package/dist/moka-global-config.d.ts +78 -55
- package/dist/moka-global-config.js +93 -92
- package/dist/moka-submit.d.ts +2179 -866
- package/dist/moka-submit.js +97 -99
- package/dist/opencode-project-config.js +34 -40
- package/dist/package-assets.js +1 -3
- package/dist/path-refs.js +2 -2
- package/dist/pipeline-init.js +25 -25
- package/dist/pipeline-runtime.d.ts +15 -14
- package/dist/pipeline-runtime.js +51 -50
- package/dist/planned-node.js +6 -3
- package/dist/planning/compile.d.ts +5 -5
- package/dist/planning/compile.js +140 -139
- package/dist/planning/dependency-refs.js +5 -5
- package/dist/planning/generate.d.ts +1296 -1296
- package/dist/planning/generate.js +307 -314
- package/dist/planning/graph.d.ts +0 -1
- package/dist/planning/graph.js +131 -149
- package/dist/remote/argo/model.d.ts +104 -122
- package/dist/remote/argo/model.js +205 -197
- package/dist/remote/argo/policy.js +39 -47
- package/dist/remote/argo/storage.js +81 -85
- package/dist/remote/jobs/execution-identity.js +70 -0
- package/dist/remote/jobs/job-outcome.js +108 -0
- package/dist/remote/jobs/manifest.js +96 -0
- package/dist/remote/jobs/model.d.ts +158 -0
- package/dist/remote/jobs/model.js +162 -0
- package/dist/remote/jobs/node-bootstrap.js +213 -0
- package/dist/remote/jobs/policy.js +123 -0
- package/dist/remote/jobs/reconcile.js +100 -0
- package/dist/remote/jobs/run-spec-client.js +64 -0
- package/dist/remote/jobs/run-spec-path.js +31 -0
- package/dist/remote/jobs/run-spec-server.d.ts +34 -0
- package/dist/remote/jobs/run-spec-server.js +116 -0
- package/dist/remote/jobs/submit.d.ts +23 -0
- package/dist/remote/jobs/submit.js +116 -0
- package/dist/remote/jobs/watch.js +59 -0
- package/dist/remote/submit/argo-submission.d.ts +3 -1
- package/dist/remote/submit/argo-submission.js +53 -58
- package/dist/remote/submit/compilation.d.ts +2 -0
- package/dist/remote/submit/compilation.js +46 -49
- package/dist/remote/submit/event-boundary.js +62 -76
- package/dist/remote/submit/hook-events.d.ts +6 -0
- package/dist/remote/submit/io.d.ts +7 -1
- package/dist/remote/submit/io.js +40 -43
- package/dist/remote/submit/jobs-submission.d.ts +59 -0
- package/dist/remote/submit/jobs-submission.js +97 -0
- package/dist/remote/submit/jobs-submit-result.d.ts +25 -0
- package/dist/remote/submit/jobs-submit-result.js +34 -0
- package/dist/remote/submit/options.d.ts +49 -0
- package/dist/remote/submit/options.js +27 -0
- package/dist/remote/submit/service.d.ts +40 -1
- package/dist/remote/submit/service.js +67 -49
- package/dist/run-control/command-context.js +6 -8
- package/dist/run-control/commands.js +10 -10
- package/dist/run-control/contracts.d.ts +52 -101
- package/dist/run-control/contracts.js +83 -69
- package/dist/run-control/detach.js +66 -72
- package/dist/run-control/execution-store.js +0 -0
- package/dist/run-control/file-errors.js +1 -3
- package/dist/run-control/logical-segment.js +3 -3
- package/dist/run-control/next-node.js +67 -78
- package/dist/run-control/postgres/postgres-run-control-store.js +196 -225
- package/dist/run-control/resume-command.js +34 -41
- package/dist/run-control/run-artifacts-command.js +84 -105
- package/dist/run-control/run-command-domain.js +7 -11
- package/dist/run-control/run-control-store.js +9 -9
- package/dist/run-control/run-query-command.js +73 -100
- package/dist/run-control/run-record.js +5 -3
- package/dist/run-control/run-state-lock.js +5 -5
- package/dist/run-control/runtime-event-projection.js +38 -50
- package/dist/run-control/runtime-reporter.js +86 -102
- package/dist/run-control/stop-command.js +42 -53
- package/dist/run-control/store-fs-effects.js +3 -1
- package/dist/run-control/store-manifest.js +38 -13
- package/dist/run-control/store-paths.js +3 -3
- package/dist/run-control/store.js +1 -1
- package/dist/run-control/submit-result.js +21 -21
- package/dist/run-control/supervisor.js +78 -68
- package/dist/run-control/workspace.js +2 -2
- package/dist/run-state/git-refs.js +258 -307
- package/dist/runner/opencode-excludes.js +4 -4
- package/dist/runner/subprocess-result.js +28 -37
- package/dist/runner/subprocess.js +41 -41
- package/dist/runner/timeouts.js +14 -13
- package/dist/runner-command/dynamic-command.js +46 -39
- package/dist/runner-command/environment-commands.js +91 -0
- package/dist/runner-command/finalize.js +174 -153
- package/dist/runner-command/lifecycle-context.js +115 -130
- package/dist/runner-command/lifecycle.js +78 -72
- package/dist/runner-command/pre-schedule.js +203 -223
- package/dist/runner-command/run.js +450 -448
- package/dist/runner-command/schedule-source-options.js +8 -10
- package/dist/runner-command/select-ready-wave.js +39 -30
- package/dist/runner-command/task-descriptor.js +11 -17
- package/dist/runner-command-contract.d.ts +147 -157
- package/dist/runner-command-contract.js +403 -411
- package/dist/runner-event-schema.d.ts +364 -505
- package/dist/runner-event-schema.js +187 -175
- package/dist/runner-event-sink.js +37 -43
- package/dist/runner-output.js +6 -6
- package/dist/runner.d.ts +57 -37
- package/dist/runner.js +176 -127
- package/dist/runtime/actor-ids.d.ts +1 -0
- package/dist/runtime/actor-ids.js +2 -2
- package/dist/runtime/agent-node/agent-node.js +99 -110
- package/dist/runtime/agent-node/handoff-finalization.js +23 -24
- package/dist/runtime/agent-node/model-selection.js +19 -22
- package/dist/runtime/agent-node/output-finalization.js +174 -204
- package/dist/runtime/agent-node/prompt-rendering.js +208 -251
- package/dist/runtime/agent-node/session-execution.js +43 -39
- package/dist/runtime/builtins/builtins.js +218 -284
- package/dist/runtime/changed-files/changed-files.js +29 -34
- package/dist/runtime/command-executor/command-executor.js +31 -23
- package/dist/runtime/config-error.js +1 -3
- package/dist/runtime/context/context.js +35 -37
- package/dist/runtime/contracts/contracts.d.ts +3 -0
- package/dist/runtime/contracts/index.d.ts +1 -1
- package/dist/runtime/detached-race.js +11 -14
- package/dist/runtime/drain-merge/drain-merge.js +139 -164
- package/dist/runtime/durable-store/acquisition.js +5 -3
- package/dist/runtime/durable-store/durable-store.d.ts +3 -1
- package/dist/runtime/durable-store/postgres/client.js +29 -0
- package/dist/runtime/durable-store/postgres/migrate-substrate.js +34 -29
- package/dist/runtime/durable-store/postgres/postgres-store.js +18 -20
- package/dist/runtime/events/events.js +273 -331
- package/dist/runtime/events/index.js +2 -2
- package/dist/runtime/execution-envelope.js +44 -0
- package/dist/runtime/gates/adjudication/llm-judge.js +21 -24
- package/dist/runtime/gates/adjudication/structured-claim.js +15 -16
- package/dist/runtime/gates/adjudicator/adjudicator.js +27 -27
- package/dist/runtime/gates/adjudicator/index.js +2 -2
- package/dist/runtime/gates/contract.js +5 -9
- package/dist/runtime/gates/gates.js +9 -8
- package/dist/runtime/gates/kinds/acceptance/acceptance.js +68 -71
- package/dist/runtime/gates/kinds/acceptance/index.js +2 -2
- package/dist/runtime/gates/kinds/artifact/artifact.js +4 -4
- package/dist/runtime/gates/kinds/artifact/index.js +2 -2
- package/dist/runtime/gates/kinds/builtin/builtin.js +4 -4
- package/dist/runtime/gates/kinds/builtin/index.js +2 -2
- package/dist/runtime/gates/kinds/changed-files/changed-files.js +31 -35
- package/dist/runtime/gates/kinds/changed-files/index.js +2 -2
- package/dist/runtime/gates/kinds/command/command.js +3 -3
- package/dist/runtime/gates/kinds/command/index.js +2 -2
- package/dist/runtime/gates/kinds/json-schema/index.js +2 -2
- package/dist/runtime/gates/kinds/json-schema/json-schema.js +7 -6
- package/dist/runtime/gates/kinds/verdict/index.js +2 -2
- package/dist/runtime/gates/kinds/verdict/verdict.js +3 -3
- package/dist/runtime/gates/orchestrator.js +72 -86
- package/dist/runtime/gates/registry.js +1 -3
- package/dist/runtime/handoff.d.ts +2 -1
- package/dist/runtime/handoff.js +43 -46
- package/dist/runtime/hooks/command-hook.js +57 -71
- package/dist/runtime/hooks/context.js +21 -25
- package/dist/runtime/hooks/events.js +84 -100
- package/dist/runtime/hooks/execution.js +6 -12
- package/dist/runtime/hooks/hooks.js +34 -23
- package/dist/runtime/hooks/invocation.js +23 -29
- package/dist/runtime/hooks/module-hook.js +36 -35
- package/dist/runtime/hooks/policy.js +32 -29
- package/dist/runtime/hooks/results.js +56 -66
- package/dist/runtime/journal-acquisition.d.ts +3 -2
- package/dist/runtime/journal-acquisition.js +14 -14
- package/dist/runtime/json-validation/json-validation.js +51 -72
- package/dist/runtime/launch-plan-args.js +39 -0
- package/dist/runtime/local-scheduler.js +46 -48
- package/dist/runtime/node-execution.js +376 -445
- package/dist/runtime/node-protocol/node-protocol.js +36 -79
- package/dist/runtime/node-state-store.d.ts +4 -1
- package/dist/runtime/node-state-store.js +13 -57
- package/dist/runtime/node-state-tracker.js +46 -54
- package/dist/runtime/open-pull-request/open-pull-request.js +201 -187
- package/dist/runtime/opencode-adapter.js +23 -24
- package/dist/runtime/opencode-agent-name.js +6 -6
- package/dist/runtime/opencode-runtime.js +115 -93
- package/dist/runtime/opencode-server.js +39 -53
- package/dist/runtime/opencode-session-executor.js +312 -344
- package/dist/runtime/parallel-node/parallel-node.js +123 -134
- package/dist/runtime/parallel-worktrees/parallel-worktrees.js +90 -73
- package/dist/runtime/protected-paths/protected-paths.js +70 -71
- package/dist/runtime/remediation/remediation.js +202 -231
- package/dist/runtime/retry.js +8 -10
- package/dist/runtime/run-journal.d.ts +2 -0
- package/dist/runtime/run-journal.js +7 -7
- package/dist/runtime/runtime-results.js +54 -69
- package/dist/runtime/scheduled-dependencies.d.ts +2 -1
- package/dist/runtime/scheduled-dependencies.js +61 -75
- package/dist/runtime/scheduler.js +179 -202
- package/dist/runtime/services/agent-node-runtime-service.js +3 -3
- package/dist/runtime/services/backlog-service.d.ts +1 -2
- package/dist/runtime/services/backlog-service.js +8 -14
- package/dist/runtime/services/command-executor-service.js +1 -1
- package/dist/runtime/services/config-io-service.js +24 -21
- package/dist/runtime/services/drain-merge-git-service.js +1 -1
- package/dist/runtime/services/file-system-service.js +9 -7
- package/dist/runtime/services/git-porcelain-service.js +6 -6
- package/dist/runtime/services/kubernetes-argo-service.d.ts +10 -3
- package/dist/runtime/services/kubernetes-argo-service.js +90 -84
- package/dist/runtime/services/kubernetes-jobs-read-service.js +115 -0
- package/dist/runtime/services/kubernetes-jobs-service.d.ts +37 -0
- package/dist/runtime/services/kubernetes-jobs-service.js +45 -0
- package/dist/runtime/services/kubernetes-jobs-watch-service.js +61 -0
- package/dist/runtime/services/kubernetes-lease-service.js +21 -0
- package/dist/runtime/services/mcp-gateway-service.js +143 -94
- package/dist/runtime/services/open-pull-request-git-service.js +1 -3
- package/dist/runtime/services/opencode-runtime-server-service.js +10 -12
- package/dist/runtime/services/opencode-sdk-service.js +7 -7
- package/dist/runtime/services/repo-io-service.js +10 -12
- package/dist/runtime/services/run-journal-file-service.js +2 -2
- package/dist/runtime/services/runner-command-io-service.js +48 -52
- package/dist/runtime/services/runner-event-sink-http-service.js +32 -39
- package/dist/runtime/step/step-node.js +15 -15
- package/dist/runtime/workflow-execution.js +56 -57
- package/dist/runtime/workflow-lifecycle.js +82 -101
- package/dist/runtime/yeet-event-record.js +104 -0
- package/dist/runtime/yeet-executor.js +184 -0
- package/dist/safe-json.js +16 -16
- package/dist/schedule/backlog-context.js +122 -116
- package/dist/schedule/baseline.js +249 -255
- package/dist/schedule/passes/coverage.js +63 -68
- package/dist/schedule/passes/drain-merge.js +34 -40
- package/dist/schedule/passes/ids.js +15 -17
- package/dist/schedule/passes/models.js +40 -37
- package/dist/schedule/passes/open-pull-request.js +16 -20
- package/dist/schedule/passes/references.js +18 -22
- package/dist/schedule/prompts.js +135 -141
- package/dist/schedule/scheduling-roles.js +8 -14
- package/dist/schema-boundary.d.ts +4 -0
- package/dist/schema-boundary.js +145 -0
- package/dist/serialized-write-queue.js +2 -2
- package/dist/standard-output-schemas.js +61 -59
- package/dist/strings.js +12 -12
- package/dist/task-ref.js +5 -5
- package/dist/tickets/apply-ticket-plan.js +52 -61
- package/dist/tickets/backlog-task-store.d.ts +15 -23
- package/dist/tickets/backlog-task-store.js +82 -94
- package/dist/tickets/completion/complete-ticket.js +53 -59
- package/dist/tickets/ticket-graph-dto.d.ts +24 -48
- package/dist/tickets/ticket-graph-dto.js +40 -40
- package/dist/tickets/ticket-graph.d.ts +7 -2
- package/dist/tickets/ticket-graph.js +36 -50
- package/dist/tickets/ticket-plan-command-args.js +6 -10
- package/dist/tickets/ticket-plan-render.js +10 -16
- package/dist/tickets/ticket-plan.d.ts +32 -41
- package/dist/tickets/ticket-plan.js +97 -77
- package/dist/tickets/ticket-selection.d.ts +3 -1
- package/dist/tickets/ticket-selection.js +37 -52
- package/dist/tickets/ticket-task-index.js +13 -17
- package/dist/tickets/validation-error-format.js +3 -10
- package/dist/token-estimator.js +12 -7
- package/dist/unknown-fields.js +21 -0
- package/dist/workflow-submit-contract.d.ts +9 -10
- package/dist/workflow-submit-contract.js +10 -9
- package/docs/adr-moka-postgres-schema-isolation.md +48 -0
- package/docs/adr-opencode-first-goal-loop-runtime.md +11 -11
- package/docs/config-architecture.md +13 -13
- package/docs/mcp-host-isolation.md +1 -0
- package/docs/moka-orchestrator-design.md +3 -3
- package/docs/operator-guide.md +1 -1
- package/docs/pipeline-console-runner-contract.md +10 -6
- package/docs/runtime-actor-model.md +18 -18
- package/docs/runtime-test-subsystems.md +200 -0
- package/docs/slash-command-adapter-contract.md +3 -3
- package/package.json +49 -85
- package/dist/config/schema/reference-validation.js +0 -83
|
@@ -1,92 +1,112 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { formatSchemaIssueList, mutableArray, nonEmptyMutableArray, parseResultWithSchema, struct, trimmedRequiredString, withDefault } from "../schema-boundary.js";
|
|
2
|
+
import { errorMessage } from "./validation-error-format.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Option$1 from "effect/Option";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Arr from "effect/Array";
|
|
7
|
+
import * as HashMap from "effect/HashMap";
|
|
4
8
|
//#region src/tickets/ticket-plan.ts
|
|
5
|
-
const LOCAL_KEY_RE = /^[a-z][a-z0-9-]
|
|
6
|
-
const nonEmptyStringSchema =
|
|
7
|
-
const localKeySchema = nonEmptyStringSchema.
|
|
8
|
-
|
|
9
|
+
const LOCAL_KEY_RE = /^[a-z][a-z0-9-]*$/u;
|
|
10
|
+
const nonEmptyStringSchema = trimmedRequiredString;
|
|
11
|
+
const localKeySchema = nonEmptyStringSchema.check(Schema.makeFilter((value) => LOCAL_KEY_RE.test(value) || "local keys must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens", {
|
|
12
|
+
description: "Lowercase local ticket key with letters, numbers, and hyphens.",
|
|
13
|
+
identifier: "TicketPlanLocalKey",
|
|
14
|
+
title: "Ticket plan local key"
|
|
15
|
+
}));
|
|
16
|
+
const acceptanceCriterionSchema = struct({
|
|
9
17
|
evidence: nonEmptyStringSchema,
|
|
10
18
|
text: nonEmptyStringSchema
|
|
11
|
-
})
|
|
12
|
-
const plannedTaskSchema =
|
|
13
|
-
acceptance_criteria:
|
|
14
|
-
depends_on:
|
|
19
|
+
});
|
|
20
|
+
const plannedTaskSchema = struct({
|
|
21
|
+
acceptance_criteria: nonEmptyMutableArray(acceptanceCriterionSchema),
|
|
22
|
+
depends_on: withDefault(mutableArray(localKeySchema), []),
|
|
15
23
|
description: nonEmptyStringSchema,
|
|
16
24
|
key: localKeySchema,
|
|
17
|
-
likely_files:
|
|
25
|
+
likely_files: withDefault(mutableArray(nonEmptyStringSchema), []),
|
|
18
26
|
plan: nonEmptyStringSchema,
|
|
19
|
-
priority:
|
|
27
|
+
priority: Schema.optional(Schema.Literals([
|
|
20
28
|
"high",
|
|
21
29
|
"medium",
|
|
22
30
|
"low"
|
|
23
|
-
])
|
|
24
|
-
references:
|
|
31
|
+
])),
|
|
32
|
+
references: withDefault(mutableArray(nonEmptyStringSchema), []),
|
|
25
33
|
title: nonEmptyStringSchema
|
|
26
|
-
}).strict();
|
|
27
|
-
const epicTaskSchema = plannedTaskSchema.omit({ depends_on: true }).extend({ key: localKeySchema.default("epic") }).strict();
|
|
28
|
-
const ticketPlanSchema = z.object({
|
|
29
|
-
epic: epicTaskSchema.optional(),
|
|
30
|
-
tickets: z.array(plannedTaskSchema).min(1)
|
|
31
|
-
}).strict().superRefine((plan, ctx) => {
|
|
32
|
-
const seenKeys = validateUniqueTicketKeys(plan.tickets, ctx);
|
|
33
|
-
validateEpicKey(plan.epic, seenKeys, ctx);
|
|
34
|
-
validateLocalDependencies(plan.tickets, seenKeys, ctx);
|
|
35
34
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
35
|
+
const epicTaskSchema = struct({
|
|
36
|
+
acceptance_criteria: nonEmptyMutableArray(acceptanceCriterionSchema),
|
|
37
|
+
description: nonEmptyStringSchema,
|
|
38
|
+
key: withDefault(localKeySchema, "epic"),
|
|
39
|
+
likely_files: withDefault(mutableArray(nonEmptyStringSchema), []),
|
|
40
|
+
plan: nonEmptyStringSchema,
|
|
41
|
+
priority: Schema.optional(Schema.Literals([
|
|
42
|
+
"high",
|
|
43
|
+
"medium",
|
|
44
|
+
"low"
|
|
45
|
+
])),
|
|
46
|
+
references: withDefault(mutableArray(nonEmptyStringSchema), []),
|
|
47
|
+
title: nonEmptyStringSchema
|
|
48
|
+
});
|
|
49
|
+
const emptyTicketKeyValidation = {
|
|
50
|
+
issues: [],
|
|
51
|
+
seenKeys: HashMap.empty()
|
|
52
|
+
};
|
|
53
|
+
var TicketPlanError = class extends Schema.TaggedErrorClass()("TicketPlanError", { message: Schema.String }) {};
|
|
54
|
+
const validateUniqueTicketKeys = (tickets) => Arr.reduce(tickets, emptyTicketKeyValidation, (state, ticket, index) => {
|
|
55
|
+
const duplicateIssue = Option$1.map(HashMap.get(state.seenKeys, ticket.key), (firstIndex) => ({
|
|
56
|
+
issue: `duplicate local ticket key '${ticket.key}' first used at tickets.${firstIndex}.key`,
|
|
57
|
+
path: [
|
|
58
|
+
"tickets",
|
|
59
|
+
index,
|
|
60
|
+
"key"
|
|
61
|
+
]
|
|
62
|
+
}));
|
|
63
|
+
return {
|
|
64
|
+
issues: [...state.issues, ...Arr.fromOption(duplicateIssue)],
|
|
65
|
+
seenKeys: HashMap.set(state.seenKeys, ticket.key, index)
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
const validateEpicKey = (epic, seenKeys) => Option$1.match(epic, {
|
|
69
|
+
onNone: () => [],
|
|
70
|
+
onSome: (value) => HashMap.has(seenKeys, value.key) ? [{
|
|
71
|
+
issue: `epic key '${value.key}' conflicts with a ticket key`,
|
|
70
72
|
path: ["epic", "key"]
|
|
73
|
+
}] : []
|
|
74
|
+
});
|
|
75
|
+
const validateTicketDependencies = (ticket, ticketIndex, seenKeys) => Arr.flatMap(ticket.depends_on, (dependencyKey, dependencyIndex) => HashMap.has(seenKeys, dependencyKey) ? [] : [{
|
|
76
|
+
issue: `unknown dependency key '${dependencyKey}'`,
|
|
77
|
+
path: [
|
|
78
|
+
"tickets",
|
|
79
|
+
ticketIndex,
|
|
80
|
+
"depends_on",
|
|
81
|
+
dependencyIndex
|
|
82
|
+
]
|
|
83
|
+
}]);
|
|
84
|
+
const validateLocalDependencies = (tickets, seenKeys) => Arr.flatMap(tickets, (ticket, ticketIndex) => validateTicketDependencies(ticket, ticketIndex, seenKeys));
|
|
85
|
+
const ticketPlanSchema = struct({
|
|
86
|
+
epic: Schema.optional(epicTaskSchema),
|
|
87
|
+
tickets: nonEmptyMutableArray(plannedTaskSchema)
|
|
88
|
+
}).check(Schema.makeFilter((plan) => {
|
|
89
|
+
const { issues: keyIssues, seenKeys } = validateUniqueTicketKeys(plan.tickets);
|
|
90
|
+
const issues = [
|
|
91
|
+
...keyIssues,
|
|
92
|
+
...validateEpicKey(Option$1.fromUndefinedOr(plan.epic), seenKeys),
|
|
93
|
+
...validateLocalDependencies(plan.tickets, seenKeys)
|
|
94
|
+
];
|
|
95
|
+
return Arr.match(issues, {
|
|
96
|
+
onEmpty: () => true,
|
|
97
|
+
onNonEmpty: (values) => values
|
|
71
98
|
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
ticketIndex,
|
|
85
|
-
"depends_on",
|
|
86
|
-
dependencyIndex
|
|
87
|
-
]
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
99
|
+
}, {
|
|
100
|
+
description: "Ticket plan keys must be unique and dependencies must resolve.",
|
|
101
|
+
identifier: "TicketPlanGraphIntegrity",
|
|
102
|
+
title: "Ticket plan graph integrity"
|
|
103
|
+
}));
|
|
104
|
+
const parseTicketPlanEffect = (source) => Effect.gen(function* effectBody() {
|
|
105
|
+
const json = parseResultWithSchema(Schema.UnknownFromJsonString, source);
|
|
106
|
+
if (!json.ok) return yield* Effect.fail(new TicketPlanError({ message: `Could not parse ticket plan JSON: ${errorMessage(json.error)}` }));
|
|
107
|
+
const decoded = parseResultWithSchema(ticketPlanSchema, json.value, { onExcessProperty: "error" });
|
|
108
|
+
if (decoded.ok) return decoded.value;
|
|
109
|
+
return yield* Effect.fail(new TicketPlanError({ message: `Invalid ticket plan: ${formatSchemaIssueList(decoded.issues)}` }));
|
|
110
|
+
});
|
|
91
111
|
//#endregion
|
|
92
112
|
export { parseTicketPlanEffect, ticketPlanSchema };
|
|
@@ -1,55 +1,27 @@
|
|
|
1
1
|
import { compareTaskIds, predecessorIds, scopedTicketIds } from "./ticket-graph.js";
|
|
2
|
+
import { Option } from "effect";
|
|
2
3
|
//#region src/tickets/ticket-selection.ts
|
|
3
4
|
const PRIORITY_RANK = {
|
|
4
5
|
high: 0,
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
low: 2,
|
|
7
|
+
medium: 1
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
function selectNextTicket(graph, options = {}) {
|
|
14
|
-
return selectReadyTickets(graph, options)[0];
|
|
15
|
-
}
|
|
16
|
-
function isReadyTicket(graph, task, options) {
|
|
17
|
-
return task.status === "To Do" && (options.includeParents === true || !hasIncompleteChildren(graph, task.id)) && predecessorIds(graph, task.id).every((dependencyId) => graph.tasksById.get(dependencyId)?.status === "Done");
|
|
18
|
-
}
|
|
19
|
-
function hasIncompleteChildren(graph, taskId) {
|
|
20
|
-
return (graph.childrenByParentId.get(taskId) ?? []).some((child) => child.status !== "Done");
|
|
21
|
-
}
|
|
22
|
-
function orderByTraversal(graph, ready, options) {
|
|
23
|
-
const remaining = new Map(ready.map((task) => [task.id, task]));
|
|
24
|
-
const ordered = [];
|
|
25
|
-
for (const id of traversalIds(graph, options.rootId, options.strategy)) {
|
|
26
|
-
const task = remaining.get(id);
|
|
27
|
-
if (task) {
|
|
28
|
-
ordered.push(task);
|
|
29
|
-
remaining.delete(id);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return [...ordered, ...[...remaining.values()].sort(compareReadyTickets)];
|
|
33
|
-
}
|
|
34
|
-
function traversalIds(graph, rootId, strategy) {
|
|
35
|
-
const roots = rootId ? [rootId] : rootIds(graph);
|
|
36
|
-
return strategy === "dfs" ? dfsIds(graph, roots) : bfsIds(graph, roots);
|
|
37
|
-
}
|
|
38
|
-
function rootIds(graph) {
|
|
39
|
-
return scopedTicketIds(graph).filter((id) => !graph.tasksById.get(id)?.parentTaskId).sort(compareTaskIds);
|
|
40
|
-
}
|
|
41
|
-
function bfsIds(graph, roots) {
|
|
9
|
+
const hasIncompleteChildren = (graph, taskId) => (graph.childrenByParentId.get(taskId) ?? []).some((child) => child.status !== "Done");
|
|
10
|
+
const isReadyTicket = (graph, task, options) => task.status === "To Do" && (options.includeParents === true || !hasIncompleteChildren(graph, task.id)) && predecessorIds(graph, task.id).every((dependencyId) => graph.tasksById.get(dependencyId)?.status === "Done");
|
|
11
|
+
const rootIds = (graph) => scopedTicketIds(graph).filter((id) => graph.tasksById.get(id)?.parentTaskId === void 0).toSorted(compareTaskIds);
|
|
12
|
+
const childIds = (graph, id) => [...graph.childrenByParentId.get(id) ?? []].map((task) => task.id).toSorted(compareTaskIds);
|
|
13
|
+
const bfsIds = (graph, roots) => {
|
|
42
14
|
const result = [];
|
|
43
15
|
const queue = [...roots];
|
|
44
16
|
while (queue.length > 0) {
|
|
45
17
|
const id = queue.shift();
|
|
46
|
-
if (
|
|
18
|
+
if (id === void 0) continue;
|
|
47
19
|
result.push(id);
|
|
48
20
|
queue.push(...childIds(graph, id));
|
|
49
21
|
}
|
|
50
22
|
return result;
|
|
51
|
-
}
|
|
52
|
-
|
|
23
|
+
};
|
|
24
|
+
const dfsIds = (graph, roots) => {
|
|
53
25
|
const result = [];
|
|
54
26
|
const visit = (id) => {
|
|
55
27
|
result.push(id);
|
|
@@ -57,18 +29,31 @@ function dfsIds(graph, roots) {
|
|
|
57
29
|
};
|
|
58
30
|
for (const root of roots) visit(root);
|
|
59
31
|
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
32
|
+
};
|
|
33
|
+
const traversalIds = (graph, rootId, strategy) => {
|
|
34
|
+
const roots = Option.isSome(rootId) ? [rootId.value] : rootIds(graph);
|
|
35
|
+
return Option.isSome(strategy) && strategy.value === "dfs" ? dfsIds(graph, roots) : bfsIds(graph, roots);
|
|
36
|
+
};
|
|
37
|
+
const priorityRank = (task) => PRIORITY_RANK[task.priority ?? "medium"];
|
|
38
|
+
const ordinal = (task) => task.ordinal ?? Number.MAX_SAFE_INTEGER;
|
|
39
|
+
const compareReadyTickets = (a, b) => priorityRank(a) - priorityRank(b) || ordinal(a) - ordinal(b) || compareTaskIds(a.id, b.id);
|
|
40
|
+
const orderByTraversal = (graph, ready, options) => {
|
|
41
|
+
const remaining = new Map(ready.map((task) => [task.id, task]));
|
|
42
|
+
const ordered = [];
|
|
43
|
+
for (const id of traversalIds(graph, Option.fromUndefinedOr(options.rootId), Option.fromUndefinedOr(options.strategy))) {
|
|
44
|
+
const task = remaining.get(id);
|
|
45
|
+
if (task !== void 0) {
|
|
46
|
+
ordered.push(task);
|
|
47
|
+
remaining.delete(id);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...ordered, ...[...remaining.values()].toSorted(compareReadyTickets)];
|
|
51
|
+
};
|
|
52
|
+
const selectReadyTickets = (graph, options = {}) => {
|
|
53
|
+
const ready = scopedTicketIds(graph, options.rootId).map((id) => graph.tasksById.get(id)).filter((task) => task !== void 0).filter((task) => isReadyTicket(graph, task, options));
|
|
54
|
+
if (options.strategy === "bfs" || options.strategy === "dfs") return orderByTraversal(graph, ready, options);
|
|
55
|
+
return ready.toSorted(compareReadyTickets);
|
|
56
|
+
};
|
|
57
|
+
const selectNextTicket = (graph, options = {}) => Option.fromUndefinedOr(selectReadyTickets(graph, options)[0]);
|
|
73
58
|
//#endregion
|
|
74
59
|
export { selectNextTicket, selectReadyTickets };
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
//#region src/tickets/ticket-task-index.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return compareTicketIds(a.id, b.id);
|
|
7
|
-
}
|
|
8
|
-
function indexChildrenByParentId(tasks) {
|
|
9
|
-
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
10
|
-
for (const task of tasks) addTaskToParentIndex(childrenByParentId, task);
|
|
11
|
-
sortParentIndex(childrenByParentId);
|
|
12
|
-
return childrenByParentId;
|
|
13
|
-
}
|
|
14
|
-
function addTaskToParentIndex(childrenByParentId, task) {
|
|
15
|
-
if (!task.parentTaskId) return;
|
|
2
|
+
const compareTicketIds = (a, b) => a.localeCompare(b, void 0, { numeric: true });
|
|
3
|
+
const compareBacklogTasks = (a, b) => compareTicketIds(a.id, b.id);
|
|
4
|
+
const addTaskToParentIndex = (childrenByParentId, task) => {
|
|
5
|
+
if (task.parentTaskId === void 0 || task.parentTaskId.length === 0) return;
|
|
16
6
|
const siblings = childrenByParentId.get(task.parentTaskId) ?? [];
|
|
17
7
|
siblings.push(task);
|
|
18
8
|
childrenByParentId.set(task.parentTaskId, siblings);
|
|
19
|
-
}
|
|
20
|
-
|
|
9
|
+
};
|
|
10
|
+
const sortParentIndex = (childrenByParentId) => {
|
|
21
11
|
for (const siblings of childrenByParentId.values()) siblings.sort(compareBacklogTasks);
|
|
22
|
-
}
|
|
12
|
+
};
|
|
13
|
+
const indexChildrenByParentId = (tasks) => {
|
|
14
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const task of tasks) addTaskToParentIndex(childrenByParentId, task);
|
|
16
|
+
sortParentIndex(childrenByParentId);
|
|
17
|
+
return childrenByParentId;
|
|
18
|
+
};
|
|
23
19
|
//#endregion
|
|
24
20
|
export { compareBacklogTasks, compareTicketIds, indexChildrenByParentId };
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
+
import { formatSchemaIssueList } from "../schema-boundary.js";
|
|
1
2
|
//#region src/tickets/validation-error-format.ts
|
|
2
|
-
|
|
3
|
-
return issues.map((issue) => {
|
|
4
|
-
const path = issue.path.join(".");
|
|
5
|
-
return path ? `${path}: ${issue.message}` : issue.message;
|
|
6
|
-
}).join("; ");
|
|
7
|
-
}
|
|
8
|
-
function errorMessage(error) {
|
|
9
|
-
return error instanceof Error ? error.message : String(error);
|
|
10
|
-
}
|
|
3
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
11
4
|
//#endregion
|
|
12
|
-
export { errorMessage,
|
|
5
|
+
export { errorMessage, formatSchemaIssueList as formatSchemaIssues };
|
package/dist/token-estimator.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
1
2
|
import { getEncoding } from "js-tiktoken";
|
|
2
3
|
//#region src/token-estimator.ts
|
|
3
4
|
/**
|
|
@@ -10,14 +11,18 @@ import { getEncoding } from "js-tiktoken";
|
|
|
10
11
|
* budget/routing decisions only. For exact counts on Anthropic runners, use the
|
|
11
12
|
* Anthropic `count_tokens` API.
|
|
12
13
|
*/
|
|
13
|
-
let encoder;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
let encoder = Option.none();
|
|
15
|
+
const encoding = () => {
|
|
16
|
+
if (Option.isNone(encoder)) {
|
|
17
|
+
const resolved = getEncoding("o200k_base");
|
|
18
|
+
encoder = Option.some(resolved);
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
return encoder.value;
|
|
22
|
+
};
|
|
23
|
+
const estimateTokens = (text) => {
|
|
19
24
|
if (text.length === 0) return 0;
|
|
20
25
|
return encoding().encode(text).length;
|
|
21
|
-
}
|
|
26
|
+
};
|
|
22
27
|
//#endregion
|
|
23
28
|
export { estimateTokens };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as Option$1 from "effect/Option";
|
|
2
|
+
import * as P from "effect/Predicate";
|
|
3
|
+
//#region src/unknown-fields.ts
|
|
4
|
+
const isUnknownRecord = (value) => P.isObject(value) && !Array.isArray(value);
|
|
5
|
+
const stringField = (value, field) => {
|
|
6
|
+
if (!isUnknownRecord(value)) return Option$1.none();
|
|
7
|
+
const fieldValue = value[field];
|
|
8
|
+
return P.isString(fieldValue) ? Option$1.some(fieldValue) : Option$1.none();
|
|
9
|
+
};
|
|
10
|
+
const numberField = (value, field) => {
|
|
11
|
+
if (!isUnknownRecord(value)) return Option$1.none();
|
|
12
|
+
const fieldValue = value[field];
|
|
13
|
+
return P.isNumber(fieldValue) ? Option$1.some(fieldValue) : Option$1.none();
|
|
14
|
+
};
|
|
15
|
+
const booleanField = (value, field) => {
|
|
16
|
+
if (!isUnknownRecord(value)) return Option$1.none();
|
|
17
|
+
const fieldValue = value[field];
|
|
18
|
+
return P.isBoolean(fieldValue) ? Option$1.some(fieldValue) : Option$1.none();
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { booleanField, numberField, stringField };
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
3
2
|
//#region src/workflow-submit-contract.d.ts
|
|
4
|
-
declare const workflowSubmitResultSchema:
|
|
5
|
-
namespace:
|
|
6
|
-
payloadConfigMapName:
|
|
7
|
-
scheduleConfigMapName:
|
|
8
|
-
taskDescriptorConfigMapName:
|
|
9
|
-
workflowName:
|
|
10
|
-
workflowUid:
|
|
11
|
-
}
|
|
3
|
+
declare const workflowSubmitResultSchema: Schema.Struct<{
|
|
4
|
+
readonly namespace: Schema.NonEmptyString;
|
|
5
|
+
readonly payloadConfigMapName: Schema.NonEmptyString;
|
|
6
|
+
readonly scheduleConfigMapName: Schema.optional<Schema.NonEmptyString>;
|
|
7
|
+
readonly taskDescriptorConfigMapName: Schema.optional<Schema.NonEmptyString>;
|
|
8
|
+
readonly workflowName: Schema.NonEmptyString;
|
|
9
|
+
readonly workflowUid: Schema.optional<Schema.NonEmptyString>;
|
|
10
|
+
}>;
|
|
12
11
|
//#endregion
|
|
13
12
|
export { workflowSubmitResultSchema };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requiredString, struct } from "./schema-boundary.js";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
2
3
|
//#region src/workflow-submit-contract.ts
|
|
3
|
-
const workflowSubmitResultSchema =
|
|
4
|
-
namespace:
|
|
5
|
-
payloadConfigMapName:
|
|
6
|
-
scheduleConfigMapName:
|
|
7
|
-
taskDescriptorConfigMapName:
|
|
8
|
-
workflowName:
|
|
9
|
-
workflowUid:
|
|
10
|
-
})
|
|
4
|
+
const workflowSubmitResultSchema = struct({
|
|
5
|
+
namespace: requiredString,
|
|
6
|
+
payloadConfigMapName: requiredString,
|
|
7
|
+
scheduleConfigMapName: Schema.optional(requiredString),
|
|
8
|
+
taskDescriptorConfigMapName: Schema.optional(requiredString),
|
|
9
|
+
workflowName: requiredString,
|
|
10
|
+
workflowUid: Schema.optional(requiredString)
|
|
11
|
+
});
|
|
11
12
|
//#endregion
|
|
12
13
|
export { workflowSubmitResultSchema };
|
|
@@ -56,3 +56,51 @@ operator-reviewed rollback because application safety depends on the runtime rol
|
|
|
56
56
|
Fresh installs create Moka tables in `moka`, not `public`. Existing public layouts get an in-place table move without
|
|
57
57
|
row copy or data loss. Future migrations must keep schema-qualified Drizzle declarations and keep the runtime role's
|
|
58
58
|
public grants restricted.
|
|
59
|
+
|
|
60
|
+
## Runner connection targets `moka` (ENG-44.1)
|
|
61
|
+
|
|
62
|
+
Every runner client that opens `MOKA_DB_URL` — the durable store and the run-control store — is opened through the one
|
|
63
|
+
factory `openMokaSubstrateClient` (`src/runtime/durable-store/postgres/client.ts`). It pins the session `search_path`
|
|
64
|
+
to `moka` via the `postgres.js` `connection` startup parameters, so `MOKA_DB_URL` genuinely *targets* the dedicated
|
|
65
|
+
schema: unqualified SQL and an ad-hoc `psql "$MOKA_DB_URL"` resolve in `moka`, never `public`. Runtime queries are
|
|
66
|
+
already schema-qualified through `pgSchema("moka")`; the connection-level `search_path` is defense in depth, **not**
|
|
67
|
+
the access control. The access control is the runtime role's grants, which are infra-owned (below).
|
|
68
|
+
|
|
69
|
+
## Provisioning the limited runtime role (infra-owned)
|
|
70
|
+
|
|
71
|
+
The limited role is **not** created by the runner migrator — the migrator runs *as* `MOKA_DB_URL`'s role, so it cannot
|
|
72
|
+
mint its own least-privilege identity. Provisioning it is infra's job (CloudNativePG owns prod roles declaratively; a
|
|
73
|
+
runner cannot `CREATE ROLE` on prod without becoming a second, drift-prone source of truth — see the Momokaya GitOps
|
|
74
|
+
deploy standard). As of ENG-44.1 the prod cluster `momokaya-db-v3` has **no** dedicated `moka` role: `MOKA_DB_URL`, if
|
|
75
|
+
wired, would use the database owner `momokaya`, which has full `public` access. Infra must provision:
|
|
76
|
+
|
|
77
|
+
1. A dedicated login role `moka` on `momokaya-db-v3`, via `spec.managed.roles` in the infra CNPG `Cluster` manifest
|
|
78
|
+
(`infra k8s/manifests/pipeline-console-db/cluster.yaml`), with its password in a managed `Secret`.
|
|
79
|
+
2. Grants: `GRANT USAGE, CREATE ON SCHEMA moka TO moka;` and ownership of the `moka` schema objects (so the migrator's
|
|
80
|
+
`CREATE`/`ALTER TABLE ... SET SCHEMA` succeed) — e.g. `ALTER SCHEMA moka OWNER TO moka;` plus reassignment of the
|
|
81
|
+
existing `moka.*` tables from `momokaya` to `moka`.
|
|
82
|
+
3. **No** grant on schema `public`. On PostgreSQL 15+ the default `PUBLIC` `CREATE` grant on `public` is already
|
|
83
|
+
removed, so a fresh non-owner role has no `public` access unless one is added — infra must simply not add one.
|
|
84
|
+
4. Point the runner's `dbAuthSecretName` / `dbAuthSecretKey` (chart `pipeline.runner.secretRefs`) at the `moka`-role
|
|
85
|
+
DSN secret, so `MOKA_DB_URL` uses the limited role rather than the owner.
|
|
86
|
+
|
|
87
|
+
### Grant assertion (recorded 2026-07-09, `momokaya-db-v3`, PostgreSQL 18.4)
|
|
88
|
+
|
|
89
|
+
Verified with `psql` as superuser against the reachable prod DB. The limited-role model was exercised for real inside a
|
|
90
|
+
rolled-back transaction (`CREATE ROLE` + `GRANT` + `SET ROLE` + a `CREATE TABLE public.*` attempt), leaving no persisted
|
|
91
|
+
prod change:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
[roles] only momokaya (db/schema owner), postgres, cnpg_metrics_exporter, streaming_replica — no dedicated moka role
|
|
95
|
+
[schemas] moka owner=momokaya acl=(owner-only; no PUBLIC/other grants)
|
|
96
|
+
public owner=momokaya acl={momokaya=UC/momokaya} (no PUBLIC grant — PG18 default)
|
|
97
|
+
[today] momokaya USAGE public = t, CREATE public = t # MOKA_DB_URL uses the owner => full public access
|
|
98
|
+
[probe: limited moka role granted ONLY schema moka, rolled back]
|
|
99
|
+
moka_probe USAGE moka = t, CREATE moka = t
|
|
100
|
+
moka_probe USAGE public = f, CREATE public = f
|
|
101
|
+
SET ROLE moka_probe; CREATE TABLE public.x => ERROR: permission denied for schema public
|
|
102
|
+
[cleanup] probe rolled back — 0 moka_probe rows persist
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The assertion confirms the design holds — a role granted only `moka` cannot touch `public` — and that the enforcing
|
|
106
|
+
role is still absent on prod, so item 1–4 above remain outstanding infra work.
|
|
@@ -32,17 +32,17 @@ The package still needs its own scheduler and state model because the pipeline c
|
|
|
32
32
|
|
|
33
33
|
## Library-First Vetting
|
|
34
34
|
|
|
35
|
-
| Item
|
|
36
|
-
|
|
|
37
|
-
| `@opencode-ai/plugin`
|
|
38
|
-
| `@opencode-ai/sdk`
|
|
39
|
-
| `opencode-handoff`
|
|
40
|
-
| DCP code
|
|
41
|
-
| `@devtheops/opencode-plugin-otel` | OpenTelemetry plugin reference
|
|
42
|
-
| `opencode-snip`
|
|
43
|
-
| `opencode-background-agents`
|
|
44
|
-
| `opencode-mem`
|
|
45
|
-
| `cupcake`
|
|
35
|
+
| Item | Role in the Stack | Decision |
|
|
36
|
+
| --------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
37
|
+
| `@opencode-ai/plugin` | Official plugin API for OpenCode lifecycle hooks | Use when package code implements OpenCode plugins. |
|
|
38
|
+
| `@opencode-ai/sdk` | Official programmatic API for OpenCode integration | Use when package code needs OpenCode API access outside generated config. |
|
|
39
|
+
| `opencode-handoff` | Handoff/session capture pattern | Incorporate the relevant handoff behaviour into package-owned continuation flows. |
|
|
40
|
+
| DCP code | Dynamic context pruning for long OpenCode sessions | Include in the default OpenCode stack and wire it into continuation/context policy. |
|
|
41
|
+
| `@devtheops/opencode-plugin-otel` | OpenTelemetry plugin reference | Use as telemetry pattern input for package-owned runtime observability. |
|
|
42
|
+
| `opencode-snip` | Snippet/prompt helper pattern | Use as prompt-discipline and reusable snippet input. |
|
|
43
|
+
| `opencode-background-agents` | Background/delegated agent pattern | Use as delegation input for scheduler-generated team mode. |
|
|
44
|
+
| `opencode-mem` | Memory/context helper pattern | Use as input for durable goal context and memory projection. |
|
|
45
|
+
| `cupcake` | OpenCode ecosystem utility surfaced during research | Keep in the surfaced ecosystem inventory when relevant to package workflows. |
|
|
46
46
|
|
|
47
47
|
## Consequences
|
|
48
48
|
|
|
@@ -322,11 +322,11 @@ timeouts, output limits, sanitized env, and explicit trust flags.
|
|
|
322
322
|
|
|
323
323
|
## Host Support Matrix
|
|
324
324
|
|
|
325
|
-
| Runner | Native subagents
|
|
326
|
-
| ----------- |
|
|
327
|
-
| OpenCode | yes
|
|
328
|
-
| Claude Code | via `opencode run` | yes (skill) | yes
|
|
329
|
-
| command | no
|
|
325
|
+
| Runner | Native subagents | Rules | Skills | MCP | Outputs | Generated resources |
|
|
326
|
+
| ----------- | ------------------ | ----------- | ------ | --- | ------------------------- | -------------------------------------- |
|
|
327
|
+
| OpenCode | yes | yes | yes | yes | text, JSON, JSONL, schema | commands, agents, skills, plugins, LSP |
|
|
328
|
+
| Claude Code | via `opencode run` | yes (skill) | yes | yes | declared by runner | commands, wrapper agents, settings |
|
|
329
|
+
| command | no | no | no | no | declared by runner | subprocess argv |
|
|
330
330
|
|
|
331
331
|
Generated host resources follow a native runner rule. OpenCode runner nodes use
|
|
332
332
|
OpenCode native agents. The Claude Code host does not run MoKa profiles natively:
|
|
@@ -373,15 +373,15 @@ overflow the call stack on deep generated workflow chains.
|
|
|
373
373
|
The shipped `defaults/pipeline.yaml` turns **`context_handoff`, `repo_map`, and
|
|
374
374
|
`durability` ON** — moka uses its architecture by default; `parallel_worktrees`
|
|
375
375
|
is opt-in. Each block can be overridden in `pipeline.yaml`; the per-block
|
|
376
|
-
`# default …` notes below mark the
|
|
376
|
+
`# default …` notes below mark the _schema_ default that applies when a block is
|
|
377
377
|
omitted entirely.
|
|
378
378
|
|
|
379
379
|
### `context_handoff` — curated node-to-node handoffs
|
|
380
380
|
|
|
381
381
|
```yaml
|
|
382
382
|
context_handoff:
|
|
383
|
-
enabled: true
|
|
384
|
-
model: openai/gpt-5.5-fast
|
|
383
|
+
enabled: true # default false
|
|
384
|
+
model: openai/gpt-5.5-fast # optional cheap model for the deriving call
|
|
385
385
|
```
|
|
386
386
|
|
|
387
387
|
When enabled, each agent node derives a structured `NodeHandoff`
|
|
@@ -396,7 +396,7 @@ raw output text, so disabling the flag is byte-identical to prior behaviour.
|
|
|
396
396
|
|
|
397
397
|
```yaml
|
|
398
398
|
parallel_worktrees:
|
|
399
|
-
enabled: true
|
|
399
|
+
enabled: true # default false
|
|
400
400
|
```
|
|
401
401
|
|
|
402
402
|
When enabled, every child of a `kind: parallel` node runs in its own git
|
|
@@ -404,15 +404,15 @@ worktree on an auto-named branch (`.pipeline/worktrees/...`), so concurrent
|
|
|
404
404
|
candidate edits cannot collide. Teardown is idempotent and crash-safe: a
|
|
405
405
|
worktree with dirty or unpushed work is retained (never deleted), and orphaned
|
|
406
406
|
worktrees are GC'd on the next parallel node under the same guard. A worktree is
|
|
407
|
-
|
|
407
|
+
_not_ a sandbox (node_modules/build state are shared) — real isolation remains
|
|
408
408
|
k8s mode. SDK-runner nodes honour the per-child directory via `plan.cwd`.
|
|
409
409
|
|
|
410
410
|
### `durability` — durable crash-resume
|
|
411
411
|
|
|
412
412
|
```yaml
|
|
413
413
|
durability:
|
|
414
|
-
enabled: true
|
|
415
|
-
dir: .pipeline/journal
|
|
414
|
+
enabled: true # default false
|
|
415
|
+
dir: .pipeline/journal # default
|
|
416
416
|
```
|
|
417
417
|
|
|
418
418
|
When enabled, the scheduler journals each terminal node result to an
|
|
@@ -431,7 +431,7 @@ default → the scheduler runs purely in-memory, exactly as before.
|
|
|
431
431
|
mcp_gateway:
|
|
432
432
|
provider: toolhive
|
|
433
433
|
mode: hosted
|
|
434
|
-
host_scope: global
|
|
434
|
+
host_scope: global # default "project"
|
|
435
435
|
```
|
|
436
436
|
|
|
437
437
|
The singleton pipeline gateway is normally synthesized into each repo's
|
|
@@ -11,6 +11,7 @@ OpenCode project config is generated at `.opencode/opencode.json` with only the
|
|
|
11
11
|
config environment variables; agents inherit the project-level config.
|
|
12
12
|
|
|
13
13
|
References:
|
|
14
|
+
|
|
14
15
|
- https://opencode.ai/docs/config/
|
|
15
16
|
- https://dev.opencode.ai/docs/mcp-servers/
|
|
16
17
|
|