@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.
Files changed (382) hide show
  1. package/README.md +14 -2
  2. package/defaults/pipeline.yaml +23 -5
  3. package/defaults/profiles.yaml +104 -21
  4. package/dist/argo-graph.js +90 -88
  5. package/dist/argo-submit.d.ts +110 -138
  6. package/dist/argo-submit.js +363 -378
  7. package/dist/argo-workflow.d.ts +156 -177
  8. package/dist/argo-workflow.js +96 -138
  9. package/dist/backlog.js +5 -4
  10. package/dist/bench/eval-report.js +21 -12
  11. package/dist/claude-settings-config.js +23 -31
  12. package/dist/claude-user-config.js +6 -16
  13. package/dist/cli/bootstrap-commands.js +3 -3
  14. package/dist/cli/doctor.d.ts +1 -1
  15. package/dist/cli/doctor.js +176 -171
  16. package/dist/cli/factory-commands.js +13 -13
  17. package/dist/cli/format.js +92 -86
  18. package/dist/cli/loop-commands.js +21 -21
  19. package/dist/cli/mcp-gateway-commands.js +12 -12
  20. package/dist/cli/plan-commands.js +85 -116
  21. package/dist/cli/program.d.ts +3 -3
  22. package/dist/cli/program.js +41 -43
  23. package/dist/cli/run-command.d.ts +1 -1
  24. package/dist/cli/run-command.js +25 -25
  25. package/dist/cli/run-commands.js +31 -29
  26. package/dist/cli/run-resolver.d.ts +1 -1
  27. package/dist/cli/run-resolver.js +30 -32
  28. package/dist/cli/run-service.d.ts +2 -3
  29. package/dist/cli/run-service.js +229 -239
  30. package/dist/cli/submit-options.js +85 -83
  31. package/dist/cluster-doctor.js +147 -175
  32. package/dist/codex-config.js +24 -30
  33. package/dist/commands/bench-command.js +9 -7
  34. package/dist/commands/coordinator-command.js +35 -0
  35. package/dist/commands/pipeline-command.js +13 -7
  36. package/dist/commands/runner-command-command.js +39 -24
  37. package/dist/commands/runner-node-command.js +31 -0
  38. package/dist/commands/ticket/complete.js +29 -33
  39. package/dist/commands/ticket/create.js +84 -82
  40. package/dist/commands/ticket/graph-check.js +11 -11
  41. package/dist/commands/ticket/next.js +36 -36
  42. package/dist/commands/ticket/sequence.js +11 -12
  43. package/dist/commands/ticket/shared.d.ts +5 -2
  44. package/dist/commands/ticket/shared.js +63 -66
  45. package/dist/commands/ticket/start.js +58 -67
  46. package/dist/commands/ticket-command.js +2 -2
  47. package/dist/config/defaults.d.ts +139 -140
  48. package/dist/config/defaults.js +81 -80
  49. package/dist/config/lint.js +52 -64
  50. package/dist/config/load.d.ts +4 -5
  51. package/dist/config/load.js +64 -74
  52. package/dist/config/schema/catalog.d.ts +2 -1
  53. package/dist/config/schema/catalog.js +3 -2
  54. package/dist/config/schema/mcp.js +46 -52
  55. package/dist/config/schemas.d.ts +382 -528
  56. package/dist/config/schemas.js +400 -398
  57. package/dist/config/validate.d.ts +1 -2
  58. package/dist/config/validate.js +236 -201
  59. package/dist/config.d.ts +2 -2
  60. package/dist/context/repo-map.js +105 -119
  61. package/dist/coordinator/composition.js +169 -0
  62. package/dist/coordinator/config.js +71 -0
  63. package/dist/coordinator/run-owner.js +64 -0
  64. package/dist/coordinator/wave-advance.js +148 -0
  65. package/dist/coordinator/wave-scheduler.js +50 -0
  66. package/dist/credentials/broker.d.ts +6 -8
  67. package/dist/credentials/broker.js +17 -16
  68. package/dist/credentials/codex-config.js +15 -19
  69. package/dist/credentials/file-targets.js +9 -15
  70. package/dist/credentials/local-codex-auth-sync.js +55 -58
  71. package/dist/credentials/opencode-config.js +28 -32
  72. package/dist/credentials/runner.js +13 -13
  73. package/dist/effect-sync-errors.js +28 -0
  74. package/dist/factory/create-experiment.js +285 -121
  75. package/dist/factory/exec.js +10 -12
  76. package/dist/factory/git-credentials.js +4 -4
  77. package/dist/factory/stamp-answers.js +10 -11
  78. package/dist/factory/template-update.js +65 -44
  79. package/dist/factory-lane.d.ts +26 -40
  80. package/dist/factory-lane.js +26 -25
  81. package/dist/gates.js +3 -3
  82. package/dist/git-remote-url.js +17 -19
  83. package/dist/hooks.d.ts +26 -26
  84. package/dist/hooks.js +20 -22
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.js +27 -36
  87. package/dist/install-commands/claude-code.js +100 -92
  88. package/dist/install-commands/host-selection.js +3 -6
  89. package/dist/install-commands/opencode.js +264 -259
  90. package/dist/install-commands/planner.js +163 -181
  91. package/dist/install-commands/result-format.js +1 -3
  92. package/dist/install-commands/shared.js +25 -42
  93. package/dist/install-commands/writer.js +22 -25
  94. package/dist/install-commands.js +8 -8
  95. package/dist/jscpd-output.js +29 -0
  96. package/dist/json-config-merge.js +14 -20
  97. package/dist/json-line-values.js +17 -6
  98. package/dist/loop/argo-poll.d.ts +0 -1
  99. package/dist/loop/argo-poll.js +15 -95
  100. package/dist/loop/backlog-records.js +1 -3
  101. package/dist/loop/controller-deps.js +150 -161
  102. package/dist/loop/controller.js +164 -186
  103. package/dist/loop/gh-checks.js +62 -61
  104. package/dist/loop/gh-runner.js +22 -25
  105. package/dist/loop/loop-command.js +66 -58
  106. package/dist/loop/loop-controller-entrypoint.js +29 -30
  107. package/dist/loop/merge.js +36 -40
  108. package/dist/loop/run-terminal-poll.js +69 -0
  109. package/dist/mcp/gateway-config.js +25 -29
  110. package/dist/mcp/gateway-doctor.js +77 -119
  111. package/dist/mcp/gateway-reconcile.js +17 -22
  112. package/dist/mcp/gateway-runtime.js +1 -3
  113. package/dist/mcp/host-config.js +37 -40
  114. package/dist/mcp/host-renderers.js +10 -18
  115. package/dist/mcp/repo-local-backends.js +51 -54
  116. package/dist/mcp/toolhive-vmcp.js +39 -39
  117. package/dist/model-resolver.js +29 -33
  118. package/dist/moka-global-config.d.ts +78 -55
  119. package/dist/moka-global-config.js +93 -92
  120. package/dist/moka-submit.d.ts +2179 -866
  121. package/dist/moka-submit.js +97 -99
  122. package/dist/opencode-project-config.js +34 -40
  123. package/dist/package-assets.js +1 -3
  124. package/dist/path-refs.js +2 -2
  125. package/dist/pipeline-init.js +25 -25
  126. package/dist/pipeline-runtime.d.ts +15 -14
  127. package/dist/pipeline-runtime.js +51 -50
  128. package/dist/planned-node.js +6 -3
  129. package/dist/planning/compile.d.ts +5 -5
  130. package/dist/planning/compile.js +140 -139
  131. package/dist/planning/dependency-refs.js +5 -5
  132. package/dist/planning/generate.d.ts +1296 -1296
  133. package/dist/planning/generate.js +307 -314
  134. package/dist/planning/graph.d.ts +0 -1
  135. package/dist/planning/graph.js +131 -149
  136. package/dist/remote/argo/model.d.ts +104 -122
  137. package/dist/remote/argo/model.js +205 -197
  138. package/dist/remote/argo/policy.js +39 -47
  139. package/dist/remote/argo/storage.js +81 -85
  140. package/dist/remote/jobs/execution-identity.js +70 -0
  141. package/dist/remote/jobs/job-outcome.js +108 -0
  142. package/dist/remote/jobs/manifest.js +96 -0
  143. package/dist/remote/jobs/model.d.ts +158 -0
  144. package/dist/remote/jobs/model.js +162 -0
  145. package/dist/remote/jobs/node-bootstrap.js +213 -0
  146. package/dist/remote/jobs/policy.js +123 -0
  147. package/dist/remote/jobs/reconcile.js +100 -0
  148. package/dist/remote/jobs/run-spec-client.js +64 -0
  149. package/dist/remote/jobs/run-spec-path.js +31 -0
  150. package/dist/remote/jobs/run-spec-server.d.ts +34 -0
  151. package/dist/remote/jobs/run-spec-server.js +116 -0
  152. package/dist/remote/jobs/submit.d.ts +23 -0
  153. package/dist/remote/jobs/submit.js +116 -0
  154. package/dist/remote/jobs/watch.js +59 -0
  155. package/dist/remote/submit/argo-submission.d.ts +3 -1
  156. package/dist/remote/submit/argo-submission.js +53 -58
  157. package/dist/remote/submit/compilation.d.ts +2 -0
  158. package/dist/remote/submit/compilation.js +46 -49
  159. package/dist/remote/submit/event-boundary.js +62 -76
  160. package/dist/remote/submit/hook-events.d.ts +6 -0
  161. package/dist/remote/submit/io.d.ts +7 -1
  162. package/dist/remote/submit/io.js +40 -43
  163. package/dist/remote/submit/jobs-submission.d.ts +59 -0
  164. package/dist/remote/submit/jobs-submission.js +97 -0
  165. package/dist/remote/submit/jobs-submit-result.d.ts +25 -0
  166. package/dist/remote/submit/jobs-submit-result.js +34 -0
  167. package/dist/remote/submit/options.d.ts +49 -0
  168. package/dist/remote/submit/options.js +27 -0
  169. package/dist/remote/submit/service.d.ts +40 -1
  170. package/dist/remote/submit/service.js +67 -49
  171. package/dist/run-control/command-context.js +6 -8
  172. package/dist/run-control/commands.js +10 -10
  173. package/dist/run-control/contracts.d.ts +52 -101
  174. package/dist/run-control/contracts.js +83 -69
  175. package/dist/run-control/detach.js +66 -72
  176. package/dist/run-control/execution-store.js +0 -0
  177. package/dist/run-control/file-errors.js +1 -3
  178. package/dist/run-control/logical-segment.js +3 -3
  179. package/dist/run-control/next-node.js +67 -78
  180. package/dist/run-control/postgres/postgres-run-control-store.js +196 -225
  181. package/dist/run-control/resume-command.js +34 -41
  182. package/dist/run-control/run-artifacts-command.js +84 -105
  183. package/dist/run-control/run-command-domain.js +7 -11
  184. package/dist/run-control/run-control-store.js +9 -9
  185. package/dist/run-control/run-query-command.js +73 -100
  186. package/dist/run-control/run-record.js +5 -3
  187. package/dist/run-control/run-state-lock.js +5 -5
  188. package/dist/run-control/runtime-event-projection.js +38 -50
  189. package/dist/run-control/runtime-reporter.js +86 -102
  190. package/dist/run-control/stop-command.js +42 -53
  191. package/dist/run-control/store-fs-effects.js +3 -1
  192. package/dist/run-control/store-manifest.js +38 -13
  193. package/dist/run-control/store-paths.js +3 -3
  194. package/dist/run-control/store.js +1 -1
  195. package/dist/run-control/submit-result.js +21 -21
  196. package/dist/run-control/supervisor.js +78 -68
  197. package/dist/run-control/workspace.js +2 -2
  198. package/dist/run-state/git-refs.js +258 -307
  199. package/dist/runner/opencode-excludes.js +4 -4
  200. package/dist/runner/subprocess-result.js +28 -37
  201. package/dist/runner/subprocess.js +41 -41
  202. package/dist/runner/timeouts.js +14 -13
  203. package/dist/runner-command/dynamic-command.js +46 -39
  204. package/dist/runner-command/environment-commands.js +91 -0
  205. package/dist/runner-command/finalize.js +174 -153
  206. package/dist/runner-command/lifecycle-context.js +115 -130
  207. package/dist/runner-command/lifecycle.js +78 -72
  208. package/dist/runner-command/pre-schedule.js +203 -223
  209. package/dist/runner-command/run.js +450 -448
  210. package/dist/runner-command/schedule-source-options.js +8 -10
  211. package/dist/runner-command/select-ready-wave.js +39 -30
  212. package/dist/runner-command/task-descriptor.js +11 -17
  213. package/dist/runner-command-contract.d.ts +147 -157
  214. package/dist/runner-command-contract.js +403 -411
  215. package/dist/runner-event-schema.d.ts +364 -505
  216. package/dist/runner-event-schema.js +187 -175
  217. package/dist/runner-event-sink.js +37 -43
  218. package/dist/runner-output.js +6 -6
  219. package/dist/runner.d.ts +57 -37
  220. package/dist/runner.js +176 -127
  221. package/dist/runtime/actor-ids.d.ts +1 -0
  222. package/dist/runtime/actor-ids.js +2 -2
  223. package/dist/runtime/agent-node/agent-node.js +99 -110
  224. package/dist/runtime/agent-node/handoff-finalization.js +23 -24
  225. package/dist/runtime/agent-node/model-selection.js +19 -22
  226. package/dist/runtime/agent-node/output-finalization.js +174 -204
  227. package/dist/runtime/agent-node/prompt-rendering.js +208 -251
  228. package/dist/runtime/agent-node/session-execution.js +43 -39
  229. package/dist/runtime/builtins/builtins.js +218 -284
  230. package/dist/runtime/changed-files/changed-files.js +29 -34
  231. package/dist/runtime/command-executor/command-executor.js +31 -23
  232. package/dist/runtime/config-error.js +1 -3
  233. package/dist/runtime/context/context.js +35 -37
  234. package/dist/runtime/contracts/contracts.d.ts +3 -0
  235. package/dist/runtime/contracts/index.d.ts +1 -1
  236. package/dist/runtime/detached-race.js +11 -14
  237. package/dist/runtime/drain-merge/drain-merge.js +139 -164
  238. package/dist/runtime/durable-store/acquisition.js +5 -3
  239. package/dist/runtime/durable-store/durable-store.d.ts +3 -1
  240. package/dist/runtime/durable-store/postgres/client.js +29 -0
  241. package/dist/runtime/durable-store/postgres/migrate-substrate.js +34 -29
  242. package/dist/runtime/durable-store/postgres/postgres-store.js +18 -20
  243. package/dist/runtime/events/events.js +273 -331
  244. package/dist/runtime/events/index.js +2 -2
  245. package/dist/runtime/execution-envelope.js +44 -0
  246. package/dist/runtime/gates/adjudication/llm-judge.js +21 -24
  247. package/dist/runtime/gates/adjudication/structured-claim.js +15 -16
  248. package/dist/runtime/gates/adjudicator/adjudicator.js +27 -27
  249. package/dist/runtime/gates/adjudicator/index.js +2 -2
  250. package/dist/runtime/gates/contract.js +5 -9
  251. package/dist/runtime/gates/gates.js +9 -8
  252. package/dist/runtime/gates/kinds/acceptance/acceptance.js +68 -71
  253. package/dist/runtime/gates/kinds/acceptance/index.js +2 -2
  254. package/dist/runtime/gates/kinds/artifact/artifact.js +4 -4
  255. package/dist/runtime/gates/kinds/artifact/index.js +2 -2
  256. package/dist/runtime/gates/kinds/builtin/builtin.js +4 -4
  257. package/dist/runtime/gates/kinds/builtin/index.js +2 -2
  258. package/dist/runtime/gates/kinds/changed-files/changed-files.js +31 -35
  259. package/dist/runtime/gates/kinds/changed-files/index.js +2 -2
  260. package/dist/runtime/gates/kinds/command/command.js +3 -3
  261. package/dist/runtime/gates/kinds/command/index.js +2 -2
  262. package/dist/runtime/gates/kinds/json-schema/index.js +2 -2
  263. package/dist/runtime/gates/kinds/json-schema/json-schema.js +7 -6
  264. package/dist/runtime/gates/kinds/verdict/index.js +2 -2
  265. package/dist/runtime/gates/kinds/verdict/verdict.js +3 -3
  266. package/dist/runtime/gates/orchestrator.js +72 -86
  267. package/dist/runtime/gates/registry.js +1 -3
  268. package/dist/runtime/handoff.d.ts +2 -1
  269. package/dist/runtime/handoff.js +43 -46
  270. package/dist/runtime/hooks/command-hook.js +57 -71
  271. package/dist/runtime/hooks/context.js +21 -25
  272. package/dist/runtime/hooks/events.js +84 -100
  273. package/dist/runtime/hooks/execution.js +6 -12
  274. package/dist/runtime/hooks/hooks.js +34 -23
  275. package/dist/runtime/hooks/invocation.js +23 -29
  276. package/dist/runtime/hooks/module-hook.js +36 -35
  277. package/dist/runtime/hooks/policy.js +32 -29
  278. package/dist/runtime/hooks/results.js +56 -66
  279. package/dist/runtime/journal-acquisition.d.ts +3 -2
  280. package/dist/runtime/journal-acquisition.js +14 -14
  281. package/dist/runtime/json-validation/json-validation.js +51 -72
  282. package/dist/runtime/launch-plan-args.js +39 -0
  283. package/dist/runtime/local-scheduler.js +46 -48
  284. package/dist/runtime/node-execution.js +376 -445
  285. package/dist/runtime/node-protocol/node-protocol.js +36 -79
  286. package/dist/runtime/node-state-store.d.ts +4 -1
  287. package/dist/runtime/node-state-store.js +13 -57
  288. package/dist/runtime/node-state-tracker.js +46 -54
  289. package/dist/runtime/open-pull-request/open-pull-request.js +201 -187
  290. package/dist/runtime/opencode-adapter.js +23 -24
  291. package/dist/runtime/opencode-agent-name.js +6 -6
  292. package/dist/runtime/opencode-runtime.js +115 -93
  293. package/dist/runtime/opencode-server.js +39 -53
  294. package/dist/runtime/opencode-session-executor.js +312 -344
  295. package/dist/runtime/parallel-node/parallel-node.js +123 -134
  296. package/dist/runtime/parallel-worktrees/parallel-worktrees.js +90 -73
  297. package/dist/runtime/protected-paths/protected-paths.js +70 -71
  298. package/dist/runtime/remediation/remediation.js +202 -231
  299. package/dist/runtime/retry.js +8 -10
  300. package/dist/runtime/run-journal.d.ts +2 -0
  301. package/dist/runtime/run-journal.js +7 -7
  302. package/dist/runtime/runtime-results.js +54 -69
  303. package/dist/runtime/scheduled-dependencies.d.ts +2 -1
  304. package/dist/runtime/scheduled-dependencies.js +61 -75
  305. package/dist/runtime/scheduler.js +179 -202
  306. package/dist/runtime/services/agent-node-runtime-service.js +3 -3
  307. package/dist/runtime/services/backlog-service.d.ts +1 -2
  308. package/dist/runtime/services/backlog-service.js +8 -14
  309. package/dist/runtime/services/command-executor-service.js +1 -1
  310. package/dist/runtime/services/config-io-service.js +24 -21
  311. package/dist/runtime/services/drain-merge-git-service.js +1 -1
  312. package/dist/runtime/services/file-system-service.js +9 -7
  313. package/dist/runtime/services/git-porcelain-service.js +6 -6
  314. package/dist/runtime/services/kubernetes-argo-service.d.ts +10 -3
  315. package/dist/runtime/services/kubernetes-argo-service.js +90 -84
  316. package/dist/runtime/services/kubernetes-jobs-read-service.js +115 -0
  317. package/dist/runtime/services/kubernetes-jobs-service.d.ts +37 -0
  318. package/dist/runtime/services/kubernetes-jobs-service.js +45 -0
  319. package/dist/runtime/services/kubernetes-jobs-watch-service.js +61 -0
  320. package/dist/runtime/services/kubernetes-lease-service.js +21 -0
  321. package/dist/runtime/services/mcp-gateway-service.js +143 -94
  322. package/dist/runtime/services/open-pull-request-git-service.js +1 -3
  323. package/dist/runtime/services/opencode-runtime-server-service.js +10 -12
  324. package/dist/runtime/services/opencode-sdk-service.js +7 -7
  325. package/dist/runtime/services/repo-io-service.js +10 -12
  326. package/dist/runtime/services/run-journal-file-service.js +2 -2
  327. package/dist/runtime/services/runner-command-io-service.js +48 -52
  328. package/dist/runtime/services/runner-event-sink-http-service.js +32 -39
  329. package/dist/runtime/step/step-node.js +15 -15
  330. package/dist/runtime/workflow-execution.js +56 -57
  331. package/dist/runtime/workflow-lifecycle.js +82 -101
  332. package/dist/runtime/yeet-event-record.js +104 -0
  333. package/dist/runtime/yeet-executor.js +184 -0
  334. package/dist/safe-json.js +16 -16
  335. package/dist/schedule/backlog-context.js +122 -116
  336. package/dist/schedule/baseline.js +249 -255
  337. package/dist/schedule/passes/coverage.js +63 -68
  338. package/dist/schedule/passes/drain-merge.js +34 -40
  339. package/dist/schedule/passes/ids.js +15 -17
  340. package/dist/schedule/passes/models.js +40 -37
  341. package/dist/schedule/passes/open-pull-request.js +16 -20
  342. package/dist/schedule/passes/references.js +18 -22
  343. package/dist/schedule/prompts.js +135 -141
  344. package/dist/schedule/scheduling-roles.js +8 -14
  345. package/dist/schema-boundary.d.ts +4 -0
  346. package/dist/schema-boundary.js +145 -0
  347. package/dist/serialized-write-queue.js +2 -2
  348. package/dist/standard-output-schemas.js +61 -59
  349. package/dist/strings.js +12 -12
  350. package/dist/task-ref.js +5 -5
  351. package/dist/tickets/apply-ticket-plan.js +52 -61
  352. package/dist/tickets/backlog-task-store.d.ts +15 -23
  353. package/dist/tickets/backlog-task-store.js +82 -94
  354. package/dist/tickets/completion/complete-ticket.js +53 -59
  355. package/dist/tickets/ticket-graph-dto.d.ts +24 -48
  356. package/dist/tickets/ticket-graph-dto.js +40 -40
  357. package/dist/tickets/ticket-graph.d.ts +7 -2
  358. package/dist/tickets/ticket-graph.js +36 -50
  359. package/dist/tickets/ticket-plan-command-args.js +6 -10
  360. package/dist/tickets/ticket-plan-render.js +10 -16
  361. package/dist/tickets/ticket-plan.d.ts +32 -41
  362. package/dist/tickets/ticket-plan.js +97 -77
  363. package/dist/tickets/ticket-selection.d.ts +3 -1
  364. package/dist/tickets/ticket-selection.js +37 -52
  365. package/dist/tickets/ticket-task-index.js +13 -17
  366. package/dist/tickets/validation-error-format.js +3 -10
  367. package/dist/token-estimator.js +12 -7
  368. package/dist/unknown-fields.js +21 -0
  369. package/dist/workflow-submit-contract.d.ts +9 -10
  370. package/dist/workflow-submit-contract.js +10 -9
  371. package/docs/adr-moka-postgres-schema-isolation.md +48 -0
  372. package/docs/adr-opencode-first-goal-loop-runtime.md +11 -11
  373. package/docs/config-architecture.md +13 -13
  374. package/docs/mcp-host-isolation.md +1 -0
  375. package/docs/moka-orchestrator-design.md +3 -3
  376. package/docs/operator-guide.md +1 -1
  377. package/docs/pipeline-console-runner-contract.md +10 -6
  378. package/docs/runtime-actor-model.md +18 -18
  379. package/docs/runtime-test-subsystems.md +200 -0
  380. package/docs/slash-command-adapter-contract.md +3 -3
  381. package/package.json +49 -85
  382. package/dist/config/schema/reference-validation.js +0 -83
@@ -1,92 +1,112 @@
1
- import { errorMessage, formatZodIssues } from "./validation-error-format.js";
2
- import { Data, Effect } from "effect";
3
- import { z } from "zod";
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 = z.string().trim().min(1);
7
- const localKeySchema = nonEmptyStringSchema.regex(LOCAL_KEY_RE, { message: "local keys must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens" });
8
- const acceptanceCriterionSchema = z.object({
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
- }).strict();
12
- const plannedTaskSchema = z.object({
13
- acceptance_criteria: z.array(acceptanceCriterionSchema).min(1),
14
- depends_on: z.array(localKeySchema).default([]),
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: z.array(nonEmptyStringSchema).default([]),
25
+ likely_files: withDefault(mutableArray(nonEmptyStringSchema), []),
18
26
  plan: nonEmptyStringSchema,
19
- priority: z.enum([
27
+ priority: Schema.optional(Schema.Literals([
20
28
  "high",
21
29
  "medium",
22
30
  "low"
23
- ]).optional(),
24
- references: z.array(nonEmptyStringSchema).default([]),
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
- var TicketPlanError = class extends Data.TaggedError("TicketPlanError") {};
37
- function parseTicketPlanEffect(source) {
38
- return Effect.gen(function* () {
39
- const json = yield* Effect.try({
40
- catch: (error) => new TicketPlanError({ message: `Could not parse ticket plan JSON: ${errorMessage(error)}` }),
41
- try: () => JSON.parse(source)
42
- });
43
- const decoded = ticketPlanSchema.safeParse(json);
44
- if (decoded.success) return decoded.data;
45
- return yield* Effect.fail(new TicketPlanError({ message: `Invalid ticket plan: ${formatZodIssues(decoded.error.issues)}` }));
46
- });
47
- }
48
- function validateUniqueTicketKeys(tickets, ctx) {
49
- const seenKeys = /* @__PURE__ */ new Map();
50
- for (const [index, ticket] of tickets.entries()) {
51
- const firstIndex = seenKeys.get(ticket.key);
52
- if (firstIndex !== void 0) ctx.addIssue({
53
- code: "custom",
54
- message: `duplicate local ticket key '${ticket.key}' first used at tickets.${firstIndex}.key`,
55
- path: [
56
- "tickets",
57
- index,
58
- "key"
59
- ]
60
- });
61
- seenKeys.set(ticket.key, index);
62
- }
63
- return seenKeys;
64
- }
65
- function validateEpicKey(epic, seenKeys, ctx) {
66
- if (!(epic && seenKeys.has(epic.key))) return;
67
- ctx.addIssue({
68
- code: "custom",
69
- message: `epic key '${epic.key}' conflicts with a ticket key`,
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
- function validateLocalDependencies(tickets, seenKeys, ctx) {
74
- for (const [ticketIndex, ticket] of tickets.entries()) validateTicketDependencies(ticket, ticketIndex, seenKeys, ctx);
75
- }
76
- function validateTicketDependencies(ticket, ticketIndex, seenKeys, ctx) {
77
- for (const [dependencyIndex, dependencyKey] of ticket.depends_on.entries()) {
78
- if (seenKeys.has(dependencyKey)) continue;
79
- ctx.addIssue({
80
- code: "custom",
81
- message: `unknown dependency key '${dependencyKey}'`,
82
- path: [
83
- "tickets",
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 +1,3 @@
1
- export { };
1
+ import "./backlog-task-store.js";
2
+ import "./ticket-graph.js";
3
+ import { Option } from "effect";
@@ -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
- medium: 1,
6
- low: 2
6
+ low: 2,
7
+ medium: 1
7
8
  };
8
- function selectReadyTickets(graph, options = {}) {
9
- const ready = scopedTicketIds(graph, options.rootId).map((id) => graph.tasksById.get(id)).filter((task) => task !== void 0).filter((task) => isReadyTicket(graph, task, options));
10
- if (options.strategy === "bfs" || options.strategy === "dfs") return orderByTraversal(graph, ready, options);
11
- return ready.sort(compareReadyTickets);
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 (!id) continue;
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
- function dfsIds(graph, roots) {
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
- function childIds(graph, id) {
62
- return [...graph.childrenByParentId.get(id) ?? []].map((task) => task.id).sort(compareTaskIds);
63
- }
64
- function compareReadyTickets(a, b) {
65
- return priorityRank(a) - priorityRank(b) || ordinal(a) - ordinal(b) || compareTaskIds(a.id, b.id);
66
- }
67
- function priorityRank(task) {
68
- return PRIORITY_RANK[task.priority ?? "medium"];
69
- }
70
- function ordinal(task) {
71
- return task.ordinal ?? Number.MAX_SAFE_INTEGER;
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
- function compareTicketIds(a, b) {
3
- return a.localeCompare(b, void 0, { numeric: true });
4
- }
5
- function compareBacklogTasks(a, b) {
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
- function sortParentIndex(childrenByParentId) {
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
- function formatZodIssues(issues) {
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, formatZodIssues };
5
+ export { errorMessage, formatSchemaIssueList as formatSchemaIssues };
@@ -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
- function encoding() {
15
- encoder ??= getEncoding("o200k_base");
16
- return encoder;
17
- }
18
- function estimateTokens(text) {
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 { z } from "zod";
2
-
1
+ import * as Schema from "effect/Schema";
3
2
  //#region src/workflow-submit-contract.d.ts
4
- declare const workflowSubmitResultSchema: z.ZodObject<{
5
- namespace: z.ZodString;
6
- payloadConfigMapName: z.ZodString;
7
- scheduleConfigMapName: z.ZodOptional<z.ZodString>;
8
- taskDescriptorConfigMapName: z.ZodOptional<z.ZodString>;
9
- workflowName: z.ZodString;
10
- workflowUid: z.ZodOptional<z.ZodString>;
11
- }, z.core.$strict>;
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 { z } from "zod";
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 = z.object({
4
- namespace: z.string().min(1),
5
- payloadConfigMapName: z.string().min(1),
6
- scheduleConfigMapName: z.string().min(1).optional(),
7
- taskDescriptorConfigMapName: z.string().min(1).optional(),
8
- workflowName: z.string().min(1),
9
- workflowUid: z.string().min(1).optional()
10
- }).strict();
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 | 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. |
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 | 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 |
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 *schema* default that applies when a block is
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 # default false
384
- model: openai/gpt-5.5-fast # optional cheap model for the deriving call
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 # default false
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
- *not* a sandbox (node_modules/build state are shared) — real isolation remains
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 # default false
415
- dir: .pipeline/journal # default
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 # default "project"
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