@revisium/orchestrator 0.2.0-alpha.3 → 0.2.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/README.md +4 -4
  2. package/control-plane/bootstrap.config.json +108 -550
  3. package/control-plane/default-playbook/catalog/pipelines.json +446 -1123
  4. package/control-plane/default-playbook/catalog/roles.json +1 -1
  5. package/control-plane/default-playbook/catalog/run-profiles.json +338 -0
  6. package/control-plane/default-playbook/package.json +1 -1
  7. package/control-plane/default-playbook/playbook.json +2 -1
  8. package/control-plane/default-playbook/prompts/developer.md +6 -6
  9. package/control-plane/default-playbook/prompts/integrator.md +1 -1
  10. package/dist/__generated__/client/browser.js +18 -0
  11. package/dist/__generated__/client/browser.js.map +1 -0
  12. package/dist/__generated__/client/client.js +32 -0
  13. package/dist/__generated__/client/client.js.map +1 -0
  14. package/dist/__generated__/client/commonInputTypes.js +11 -0
  15. package/dist/__generated__/client/commonInputTypes.js.map +1 -0
  16. package/dist/__generated__/client/enums.js +19 -0
  17. package/dist/__generated__/client/enums.js.map +1 -0
  18. package/dist/__generated__/client/internal/class.js +53 -0
  19. package/dist/__generated__/client/internal/class.js.map +1 -0
  20. package/dist/__generated__/client/internal/prismaNamespace.js +450 -0
  21. package/dist/__generated__/client/internal/prismaNamespace.js.map +1 -0
  22. package/dist/__generated__/client/internal/prismaNamespaceBrowser.js +421 -0
  23. package/dist/__generated__/client/internal/prismaNamespaceBrowser.js.map +1 -0
  24. package/dist/__generated__/client/models/Branch.js +2 -0
  25. package/dist/__generated__/client/models/Branch.js.map +1 -0
  26. package/dist/__generated__/client/models/CostLedgerEntry.js +2 -0
  27. package/dist/__generated__/client/models/CostLedgerEntry.js.map +1 -0
  28. package/dist/__generated__/client/models/FileBlob.js +2 -0
  29. package/dist/__generated__/client/models/FileBlob.js.map +1 -0
  30. package/dist/__generated__/client/models/InboxItem.js +2 -0
  31. package/dist/__generated__/client/models/InboxItem.js.map +1 -0
  32. package/dist/__generated__/client/models/ProjectFileUsage.js +2 -0
  33. package/dist/__generated__/client/models/ProjectFileUsage.js.map +1 -0
  34. package/dist/__generated__/client/models/Revision.js +2 -0
  35. package/dist/__generated__/client/models/Revision.js.map +1 -0
  36. package/dist/__generated__/client/models/RevoProject.js +2 -0
  37. package/dist/__generated__/client/models/RevoProject.js.map +1 -0
  38. package/dist/__generated__/client/models/Row.js +2 -0
  39. package/dist/__generated__/client/models/Row.js.map +1 -0
  40. package/dist/__generated__/client/models/RunAttempt.js +2 -0
  41. package/dist/__generated__/client/models/RunAttempt.js.map +1 -0
  42. package/dist/__generated__/client/models/RunEvent.js +2 -0
  43. package/dist/__generated__/client/models/RunEvent.js.map +1 -0
  44. package/dist/__generated__/client/models/RunOutput.js +2 -0
  45. package/dist/__generated__/client/models/RunOutput.js.map +1 -0
  46. package/dist/__generated__/client/models/RunTask.js +2 -0
  47. package/dist/__generated__/client/models/RunTask.js.map +1 -0
  48. package/dist/__generated__/client/models/Table.js +2 -0
  49. package/dist/__generated__/client/models/Table.js.map +1 -0
  50. package/dist/__generated__/client/models/TableMigration.js +2 -0
  51. package/dist/__generated__/client/models/TableMigration.js.map +1 -0
  52. package/dist/__generated__/client/models/TaskRun.js +2 -0
  53. package/dist/__generated__/client/models/TaskRun.js.map +1 -0
  54. package/dist/__generated__/client/models.js +2 -0
  55. package/dist/__generated__/client/models.js.map +1 -0
  56. package/dist/api/graphql-api/graphql-ws/pubsub.module.js +2 -1
  57. package/dist/api/graphql-api/graphql-ws/pubsub.module.js.map +1 -1
  58. package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js +110 -28
  59. package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js.map +1 -1
  60. package/dist/api/graphql-api/graphql-ws/subscription-mappers.js +3 -1
  61. package/dist/api/graphql-api/graphql-ws/subscription-mappers.js.map +1 -1
  62. package/dist/api/graphql-api/inbox/inputs/resolve-gate.input.js +9 -0
  63. package/dist/api/graphql-api/inbox/inputs/resolve-gate.input.js.map +1 -1
  64. package/dist/api/graphql-api/method/inputs/create-run-profile.input.js +54 -0
  65. package/dist/api/graphql-api/method/inputs/create-run-profile.input.js.map +1 -0
  66. package/dist/api/graphql-api/method/inputs/deprecate-run-profile.input.js +37 -0
  67. package/dist/api/graphql-api/method/inputs/deprecate-run-profile.input.js.map +1 -0
  68. package/dist/api/graphql-api/method/inputs/get-run-profile.input.js +32 -0
  69. package/dist/api/graphql-api/method/inputs/get-run-profile.input.js.map +1 -0
  70. package/dist/api/graphql-api/method/inputs/list-run-profiles.input.js +33 -0
  71. package/dist/api/graphql-api/method/inputs/list-run-profiles.input.js.map +1 -0
  72. package/dist/api/graphql-api/method/inputs/update-run-profile.input.js +59 -0
  73. package/dist/api/graphql-api/method/inputs/update-run-profile.input.js.map +1 -0
  74. package/dist/api/graphql-api/method/inputs/validate-run-profile.input.js +33 -0
  75. package/dist/api/graphql-api/method/inputs/validate-run-profile.input.js.map +1 -0
  76. package/dist/api/graphql-api/method/method.resolver.js +76 -1
  77. package/dist/api/graphql-api/method/method.resolver.js.map +1 -1
  78. package/dist/api/graphql-api/method/model/run-profile-connection.model.js +16 -0
  79. package/dist/api/graphql-api/method/model/run-profile-connection.model.js.map +1 -0
  80. package/dist/api/graphql-api/method/model/run-profile-status.model.js +6 -0
  81. package/dist/api/graphql-api/method/model/run-profile-status.model.js.map +1 -0
  82. package/dist/api/graphql-api/method/model/run-profile.model.js +79 -0
  83. package/dist/api/graphql-api/method/model/run-profile.model.js.map +1 -0
  84. package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js +6 -0
  85. package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js.map +1 -1
  86. package/dist/api/graphql-api/registerGraphqlEnums.js +6 -0
  87. package/dist/api/graphql-api/registerGraphqlEnums.js.map +1 -1
  88. package/dist/api/graphql-api/runs/inputs/create-run.input.js +17 -1
  89. package/dist/api/graphql-api/runs/inputs/create-run.input.js.map +1 -1
  90. package/dist/api/graphql-api/runs/inputs/simulate-route.input.js +11 -1
  91. package/dist/api/graphql-api/runs/inputs/simulate-route.input.js.map +1 -1
  92. package/dist/api/graphql-api/runs/model/run-workflow.model.js +5 -0
  93. package/dist/api/graphql-api/runs/model/run-workflow.model.js.map +1 -1
  94. package/dist/api/graphql-api/share/model/issue-action.model.js +7 -0
  95. package/dist/api/graphql-api/share/model/issue-action.model.js.map +1 -0
  96. package/dist/cli/commands/doctor-report.js +1 -13
  97. package/dist/cli/commands/doctor-report.js.map +1 -1
  98. package/dist/cli/commands/lifecycle.js +50 -65
  99. package/dist/cli/commands/lifecycle.js.map +1 -1
  100. package/dist/cli/commands/{revisium-helpers.js → process-helpers.js} +1 -24
  101. package/dist/cli/commands/process-helpers.js.map +1 -0
  102. package/dist/config.js +17 -55
  103. package/dist/config.js.map +1 -1
  104. package/dist/control-plane/bootstrap-json-validator.js +65 -0
  105. package/dist/control-plane/bootstrap-json-validator.js.map +1 -0
  106. package/dist/control-plane/bootstrap.js +25 -38
  107. package/dist/control-plane/bootstrap.js.map +1 -1
  108. package/dist/control-plane/change-notifications.js +45 -14
  109. package/dist/control-plane/change-notifications.js.map +1 -1
  110. package/dist/control-plane/data-access.js +1 -106
  111. package/dist/control-plane/data-access.js.map +1 -1
  112. package/dist/control-plane/default-playbook-policy.js +488 -94
  113. package/dist/control-plane/default-playbook-policy.js.map +1 -1
  114. package/dist/control-plane/definitions.js +4 -5
  115. package/dist/control-plane/definitions.js.map +1 -1
  116. package/dist/control-plane/engine-transport.js +383 -0
  117. package/dist/control-plane/engine-transport.js.map +1 -0
  118. package/dist/control-plane/inbox.js +15 -7
  119. package/dist/control-plane/inbox.js.map +1 -1
  120. package/dist/control-plane/index.js +0 -1
  121. package/dist/control-plane/index.js.map +1 -1
  122. package/dist/control-plane/json-fields.js +1 -1
  123. package/dist/control-plane/json-fields.js.map +1 -1
  124. package/dist/control-plane/merge-override-audit.js +2 -2
  125. package/dist/control-plane/merge-override-audit.js.map +1 -1
  126. package/dist/control-plane/pipeline-coverage-registry.js +486 -0
  127. package/dist/control-plane/pipeline-coverage-registry.js.map +1 -0
  128. package/dist/control-plane/query-types.js +2 -0
  129. package/dist/control-plane/query-types.js.map +1 -0
  130. package/dist/control-plane/run-profiles.js +175 -0
  131. package/dist/control-plane/run-profiles.js.map +1 -0
  132. package/dist/control-plane/schema-migration.js +0 -35
  133. package/dist/control-plane/schema-migration.js.map +1 -1
  134. package/dist/control-plane/seed-default-playbook.js +2 -31
  135. package/dist/control-plane/seed-default-playbook.js.map +1 -1
  136. package/dist/control-plane/tables.js +8 -0
  137. package/dist/control-plane/tables.js.map +1 -1
  138. package/dist/control-plane/transport.js +16 -0
  139. package/dist/control-plane/transport.js.map +1 -0
  140. package/dist/control-plane/versioned-meaning.js +72 -15
  141. package/dist/control-plane/versioned-meaning.js.map +1 -1
  142. package/dist/e2e/kit/agents.js +63 -8
  143. package/dist/e2e/kit/agents.js.map +1 -1
  144. package/dist/e2e/kit/assertions.js +15 -3
  145. package/dist/e2e/kit/assertions.js.map +1 -1
  146. package/dist/e2e/kit/crash.js +8 -4
  147. package/dist/e2e/kit/crash.js.map +1 -1
  148. package/dist/e2e/kit/drive.js +46 -11
  149. package/dist/e2e/kit/drive.js.map +1 -1
  150. package/dist/e2e/kit/env.js +17 -1
  151. package/dist/e2e/kit/env.js.map +1 -1
  152. package/dist/e2e/kit/fake-integrator.js +9 -22
  153. package/dist/e2e/kit/fake-integrator.js.map +1 -1
  154. package/dist/e2e/kit/gh-emulator.js +166 -26
  155. package/dist/e2e/kit/gh-emulator.js.map +1 -1
  156. package/dist/e2e/kit/harness.js +44 -10
  157. package/dist/e2e/kit/harness.js.map +1 -1
  158. package/dist/e2e/kit/index.js +2 -0
  159. package/dist/e2e/kit/index.js.map +1 -1
  160. package/dist/e2e/kit/run-profiles.js +50 -0
  161. package/dist/e2e/kit/run-profiles.js.map +1 -0
  162. package/dist/e2e/kit/scenario.js +207 -0
  163. package/dist/e2e/kit/scenario.js.map +1 -0
  164. package/dist/e2e/kit/scenarios.js +38 -45
  165. package/dist/e2e/kit/scenarios.js.map +1 -1
  166. package/dist/e2e/recovery-crash-child.js +2 -2
  167. package/dist/e2e/recovery-crash-child.js.map +1 -1
  168. package/dist/e2e/recovery-dd-crash-child.js +8 -6
  169. package/dist/e2e/recovery-dd-crash-child.js.map +1 -1
  170. package/dist/engine/dbos.service.js +8 -1
  171. package/dist/engine/dbos.service.js.map +1 -1
  172. package/dist/features/inbox/commands/impl/resolve-gate.command.js.map +1 -1
  173. package/dist/features/method/commands/handlers/method-command.handlers.js +64 -0
  174. package/dist/features/method/commands/handlers/method-command.handlers.js.map +1 -0
  175. package/dist/features/method/commands/impl/create-run-profile.command.js +7 -0
  176. package/dist/features/method/commands/impl/create-run-profile.command.js.map +1 -0
  177. package/dist/features/method/commands/impl/deprecate-run-profile.command.js +7 -0
  178. package/dist/features/method/commands/impl/deprecate-run-profile.command.js.map +1 -0
  179. package/dist/features/method/commands/impl/update-run-profile.command.js +7 -0
  180. package/dist/features/method/commands/impl/update-run-profile.command.js.map +1 -0
  181. package/dist/features/method/commands/index.js +6 -1
  182. package/dist/features/method/commands/index.js.map +1 -1
  183. package/dist/features/method/method-api.service.js +31 -3
  184. package/dist/features/method/method-api.service.js.map +1 -1
  185. package/dist/features/method/queries/handlers/method-query.handlers.js +58 -1
  186. package/dist/features/method/queries/handlers/method-query.handlers.js.map +1 -1
  187. package/dist/features/method/queries/impl/get-run-profile.query.js +7 -0
  188. package/dist/features/method/queries/impl/get-run-profile.query.js.map +1 -0
  189. package/dist/features/method/queries/impl/list-run-profiles.query.js +7 -0
  190. package/dist/features/method/queries/impl/list-run-profiles.query.js.map +1 -0
  191. package/dist/features/method/queries/impl/validate-run-profile.query.js +7 -0
  192. package/dist/features/method/queries/impl/validate-run-profile.query.js.map +1 -0
  193. package/dist/features/method/queries/index.js +4 -1
  194. package/dist/features/method/queries/index.js.map +1 -1
  195. package/dist/features/pr/queries/impl/get-pr-readiness.query.js.map +1 -1
  196. package/dist/features/runs/commands/impl/create-run.command.js.map +1 -1
  197. package/dist/features/runs/queries/impl/simulate-route.query.js.map +1 -1
  198. package/dist/features/shared/connection.js +6 -3
  199. package/dist/features/shared/connection.js.map +1 -1
  200. package/dist/host/daemon.js +32 -8
  201. package/dist/host/daemon.js.map +1 -1
  202. package/dist/host/ensure-host.js +3 -3
  203. package/dist/host/ensure-host.js.map +1 -1
  204. package/dist/host/host-runtime.js.map +1 -1
  205. package/dist/host/host.lifecycle.js +3 -16
  206. package/dist/host/host.lifecycle.js.map +1 -1
  207. package/dist/http/graphql-host.js +17 -8
  208. package/dist/http/graphql-host.js.map +1 -1
  209. package/dist/mcp/mcp-capabilities.js +8 -2
  210. package/dist/mcp/mcp-capabilities.js.map +1 -1
  211. package/dist/mcp/mcp-facade.service.js +53 -10
  212. package/dist/mcp/mcp-facade.service.js.map +1 -1
  213. package/dist/mcp/mcp-tool-result.js +22 -0
  214. package/dist/mcp/mcp-tool-result.js.map +1 -0
  215. package/dist/mcp/mcp-tools.js +108 -1
  216. package/dist/mcp/mcp-tools.js.map +1 -1
  217. package/dist/pipeline/await-human.js +27 -8
  218. package/dist/pipeline/await-human.js.map +1 -1
  219. package/dist/pipeline/data-driven-task.workflow.js +573 -105
  220. package/dist/pipeline/data-driven-task.workflow.js.map +1 -1
  221. package/dist/pipeline/pipeline.service.js +28 -12
  222. package/dist/pipeline/pipeline.service.js.map +1 -1
  223. package/dist/pipeline/route-contract.js +80 -39
  224. package/dist/pipeline/route-contract.js.map +1 -1
  225. package/dist/pipeline-core/condition-scopes.js +15 -0
  226. package/dist/pipeline-core/condition-scopes.js.map +1 -0
  227. package/dist/pipeline-core/index.js +2 -0
  228. package/dist/pipeline-core/index.js.map +1 -1
  229. package/dist/pipeline-core/kit/fixtures.js +81 -44
  230. package/dist/pipeline-core/kit/fixtures.js.map +1 -1
  231. package/dist/pipeline-core/materialize.js +221 -0
  232. package/dist/pipeline-core/materialize.js.map +1 -0
  233. package/dist/pipeline-core/types.js +5 -0
  234. package/dist/pipeline-core/types.js.map +1 -1
  235. package/dist/pipeline-core/validate-loops.js +2 -15
  236. package/dist/pipeline-core/validate-loops.js.map +1 -1
  237. package/dist/pipeline-core/validate-resilience.js +257 -0
  238. package/dist/pipeline-core/validate-resilience.js.map +1 -0
  239. package/dist/pipeline-core/validate.js +2 -0
  240. package/dist/pipeline-core/validate.js.map +1 -1
  241. package/dist/playbook/catalog-loader.js +40 -3
  242. package/dist/playbook/catalog-loader.js.map +1 -1
  243. package/dist/playbook/catalog-schema-validator.js +468 -0
  244. package/dist/playbook/catalog-schema-validator.js.map +1 -0
  245. package/dist/playbook/import-mapper.js +55 -3
  246. package/dist/playbook/import-mapper.js.map +1 -1
  247. package/dist/playbook/manifest.js +5 -0
  248. package/dist/playbook/manifest.js.map +1 -1
  249. package/dist/playbook/playbook-installer.js +17 -1
  250. package/dist/playbook/playbook-installer.js.map +1 -1
  251. package/dist/poller/pr-readiness-core.js +257 -93
  252. package/dist/poller/pr-readiness-core.js.map +1 -1
  253. package/dist/poller/pr-readiness.js +2 -2
  254. package/dist/poller/pr-readiness.js.map +1 -1
  255. package/dist/revisium/inbox.service.js +8 -8
  256. package/dist/revisium/inbox.service.js.map +1 -1
  257. package/dist/revisium/playbooks.service.js +311 -21
  258. package/dist/revisium/playbooks.service.js.map +1 -1
  259. package/dist/revisium/revisium.module.js +8 -8
  260. package/dist/revisium/revisium.module.js.map +1 -1
  261. package/dist/revisium/roles.service.js +3 -1
  262. package/dist/revisium/roles.service.js.map +1 -1
  263. package/dist/revisium/run.service.js +9 -8
  264. package/dist/revisium/run.service.js.map +1 -1
  265. package/dist/revisium/tokens.js +0 -1
  266. package/dist/revisium/tokens.js.map +1 -1
  267. package/dist/run/create-run.js +1 -4
  268. package/dist/run/create-run.js.map +1 -1
  269. package/dist/run/inspect-run.js +33 -29
  270. package/dist/run/inspect-run.js.map +1 -1
  271. package/dist/run/issue-ref.js +97 -2
  272. package/dist/run/issue-ref.js.map +1 -1
  273. package/dist/run/prisma-runtime-data-access.js +616 -0
  274. package/dist/run/prisma-runtime-data-access.js.map +1 -0
  275. package/dist/run/run-outputs.js +39 -14
  276. package/dist/run/run-outputs.js.map +1 -1
  277. package/dist/run/terminal-run-status.js.map +1 -1
  278. package/dist/runners/gh-identity.js +29 -7
  279. package/dist/runners/gh-identity.js.map +1 -1
  280. package/dist/runners/integrator.js +692 -89
  281. package/dist/runners/integrator.js.map +1 -1
  282. package/dist/runners/worktree.service.js +10 -8
  283. package/dist/runners/worktree.service.js.map +1 -1
  284. package/dist/schema/ajv-errors.js +15 -0
  285. package/dist/schema/ajv-errors.js.map +1 -0
  286. package/dist/smoke/isolation.js +7 -16
  287. package/dist/smoke/isolation.js.map +1 -1
  288. package/dist/storage/ensure-storage.js +118 -0
  289. package/dist/storage/ensure-storage.js.map +1 -0
  290. package/dist/storage/revo-database.js +39 -0
  291. package/dist/storage/revo-database.js.map +1 -0
  292. package/dist/storage/revo-prisma.service.js +31 -0
  293. package/dist/storage/revo-prisma.service.js.map +1 -0
  294. package/dist/storage/revo-storage.module.js +18 -0
  295. package/dist/storage/revo-storage.module.js.map +1 -0
  296. package/dist/task-control-plane/pr-readiness.service.js +5 -2
  297. package/dist/task-control-plane/pr-readiness.service.js.map +1 -1
  298. package/dist/task-control-plane/run-watch.service.js +3 -6
  299. package/dist/task-control-plane/run-watch.service.js.map +1 -1
  300. package/dist/task-control-plane/task-control-plane-api.service.js +518 -85
  301. package/dist/task-control-plane/task-control-plane-api.service.js.map +1 -1
  302. package/dist/testing/runtime-data-access.js +175 -0
  303. package/dist/testing/runtime-data-access.js.map +1 -0
  304. package/dist/worker/build-context.js +178 -2
  305. package/dist/worker/build-context.js.map +1 -1
  306. package/dist/worker/claude-code-runner.js +23 -6
  307. package/dist/worker/claude-code-runner.js.map +1 -1
  308. package/dist/worker/codex-runner.js +6 -1
  309. package/dist/worker/codex-runner.js.map +1 -1
  310. package/package.json +20 -7
  311. package/prisma/migrations/20260707080500_initial_schema/migration.sql +259 -0
  312. package/prisma/migrations/20260707103000_prisma_runtime_tables/migration.sql +234 -0
  313. package/prisma/migrations/migration_lock.toml +3 -0
  314. package/prisma/schema.prisma +338 -0
  315. package/prisma.config.ts +11 -0
  316. package/dist/cli/commands/revisium-helpers.js.map +0 -1
  317. package/dist/control-plane/client-transport.js +0 -168
  318. package/dist/control-plane/client-transport.js.map +0 -1
  319. package/dist/engine/ensure-postgres.js +0 -46
  320. package/dist/engine/ensure-postgres.js.map +0 -1
  321. package/dist/host/ensure-revisium.js +0 -135
  322. package/dist/host/ensure-revisium.js.map +0 -1
  323. package/revisium.config.json +0 -10
@@ -1,3 +1,4 @@
1
+ import { PR_LIFECYCLE_NODES } from './run-profiles.js';
1
2
  export const DEFAULT_PLAYBOOK_POLICY_DIAGNOSTIC_CODES = [
2
3
  'DEFAULT_POLICY_WRONG_PIPELINE',
3
4
  'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
@@ -8,7 +9,19 @@ export const DEFAULT_PLAYBOOK_POLICY_DIAGNOSTIC_CODES = [
8
9
  'DEFAULT_POLICY_BLOCKED_TERMINAL_MISSING',
9
10
  'DEFAULT_POLICY_CANCELLED_TERMINAL_MISSING',
10
11
  'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
12
+ 'DEFAULT_POLICY_RECOVERABLE_CATCH_TERMINAL',
13
+ 'DEFAULT_POLICY_CAP_EXHAUSTION_OFFRAMP_MISSING',
14
+ 'DEFAULT_POLICY_APPROVE_REVERIFY_MISSING',
15
+ 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING',
16
+ 'DEFAULT_POLICY_MERGE_READINESS_FRESHNESS_MISSING',
17
+ 'DEFAULT_POLICY_CONFIRM_MERGE_FAILURE_TERMINAL',
18
+ 'DEFAULT_POLICY_POST_MERGE_CLEANUP_MISSING',
19
+ 'DEFAULT_POLICY_GATE_OUTCOMES_IMPLICIT',
11
20
  ];
21
+ export const POLICY_VERSION = '1';
22
+ const SUPPORTED_PIPELINE_IDS = ['feature-development'];
23
+ const POLL_LOOP_SCOPE = 'pollLoop';
24
+ const POLL_LOOP_CAP = 8;
12
25
  class PolicySink {
13
26
  pipelineId;
14
27
  diagnostics = [];
@@ -25,19 +38,28 @@ class PolicySink {
25
38
  });
26
39
  }
27
40
  }
28
- export function validateDefaultFeatureDevelopmentPolicy(template) {
41
+ export function validateDefaultPlaybookPolicy(template) {
29
42
  const sink = new PolicySink(template.pipelineId);
30
- if (template.pipelineId !== 'feature-development') {
31
- sink.error('DEFAULT_POLICY_WRONG_PIPELINE', 'default playbook policy verifier only applies to feature-development', { expected: 'feature-development', actual: template.pipelineId });
43
+ if (!SUPPORTED_PIPELINE_IDS.includes(template.pipelineId)) {
44
+ sink.error('DEFAULT_POLICY_WRONG_PIPELINE', 'default playbook policy verifier only applies to feature-development variants', { expected: SUPPORTED_PIPELINE_IDS.join('|'), actual: template.pipelineId });
32
45
  return sink.diagnostics;
33
46
  }
34
47
  checkBlockedTerminal(template, sink);
35
48
  checkCancelledTerminal(template, sink);
36
49
  checkProducedChangeHandoff(template, sink);
37
50
  checkPrFreshnessWiring(template, sink);
51
+ checkApproveReverifyBeforeMerge(template, sink);
52
+ checkOverrideMergeRouting(template, sink);
53
+ checkMergeConsumesFreshReadiness(template, sink);
38
54
  checkMergeGateRecheckRouting(template, sink);
55
+ checkRecoveryGateRouting(template, sink);
39
56
  checkReviewFeedbackLoop(template, sink);
40
57
  checkLoopExhaustionEscalation(template, sink);
58
+ checkRecoverableCatches(template, sink);
59
+ checkCapExhaustionOffRamp(template, sink);
60
+ checkConfirmMergeFailureRecoverable(template, sink);
61
+ checkPostMergeCleanup(template, sink);
62
+ checkGateOutcomesExplicit(template, sink);
41
63
  return sink.diagnostics;
42
64
  }
43
65
  function checkBlockedTerminal(template, sink) {
@@ -61,23 +83,43 @@ function checkCancelledTerminal(template, sink) {
61
83
  });
62
84
  }
63
85
  function checkProducedChangeHandoff(template, sink) {
64
- for (const nodeId of ['developer', 'reworkDeveloper', 'stuckReworkDeveloper', 'ciRework', 'reviewRework']) {
86
+ for (const nodeId of ['developer', 'reworkDeveloper', 'stuckReworkDeveloper', 'ciRework', 'reviewRework', 'questionReviewRework']) {
65
87
  expectChangeProducer(template, sink, nodeId);
66
88
  }
67
- expectConsume(template, sink, {
68
- code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
69
- consumerId: 'codeReview',
70
- producerId: 'developer',
71
- as: 'developerChange',
72
- staleOk: true,
73
- });
74
- expectConsume(template, sink, {
75
- code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
76
- consumerId: 'codeReview',
77
- producerId: 'reworkDeveloper',
78
- as: 'reworkChange',
79
- optional: true,
80
- });
89
+ if (template.nodes['codeReviewFanout']) {
90
+ for (const branchId of ['codeReviewPrimary', 'codeReviewSecondary']) {
91
+ expectConsume(template, sink, {
92
+ code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
93
+ consumerId: branchId,
94
+ producerId: 'developer',
95
+ as: 'developerChange',
96
+ staleOk: true,
97
+ });
98
+ expectConsume(template, sink, {
99
+ code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
100
+ consumerId: branchId,
101
+ producerId: 'reworkDeveloper',
102
+ as: 'reworkChange',
103
+ optional: true,
104
+ });
105
+ }
106
+ }
107
+ else {
108
+ expectConsume(template, sink, {
109
+ code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
110
+ consumerId: 'codeReview',
111
+ producerId: 'developer',
112
+ as: 'developerChange',
113
+ staleOk: true,
114
+ });
115
+ expectConsume(template, sink, {
116
+ code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
117
+ consumerId: 'codeReview',
118
+ producerId: 'reworkDeveloper',
119
+ as: 'reworkChange',
120
+ optional: true,
121
+ });
122
+ }
81
123
  expectConsume(template, sink, {
82
124
  code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
83
125
  consumerId: 'integrator',
@@ -113,52 +155,129 @@ function checkProducedChangeHandoff(template, sink) {
113
155
  producerId: 'reviewRework',
114
156
  as: 'reviewChange',
115
157
  });
158
+ expectConsume(template, sink, {
159
+ code: 'DEFAULT_POLICY_CHANGE_HANDOFF_MISSING',
160
+ consumerId: 'questionReviewIntegrator',
161
+ producerId: 'questionReviewRework',
162
+ as: 'reviewChange',
163
+ });
116
164
  }
117
165
  function checkPrFreshnessWiring(template, sink) {
118
- expectScript(template, sink, {
166
+ expectScopeConfig(template, sink, {
167
+ scopeId: POLL_LOOP_SCOPE,
168
+ cap: POLL_LOOP_CAP,
169
+ parent: null,
170
+ });
171
+ for (const hop of [
172
+ { scriptId: 'pollPr', routerId: 'prRouter', cleanTarget: 'mergeReadiness', recheckTarget: 'pollPr' },
173
+ {
174
+ scriptId: 'mergeReadiness',
175
+ routerId: 'mergeReadinessRouter',
176
+ cleanTarget: 'mergeGate',
177
+ recheckTarget: 'mergeReadiness',
178
+ },
179
+ ]) {
180
+ expectPollPrFeedbackHop(template, sink, hop);
181
+ }
182
+ expectGateArtifact(template, sink, {
119
183
  code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
120
- nodeId: 'pollPr',
121
- scriptRef: 'script:pollPr',
122
- next: 'prRouter',
123
- resultSchema: 'schema:prFeedback',
124
- produces: 'prFeedback',
184
+ nodeId: 'mergeGate',
185
+ producerId: 'mergeReadiness',
186
+ as: 'prFeedback',
125
187
  });
188
+ }
189
+ function checkApproveReverifyBeforeMerge(template, sink) {
126
190
  expectRoute(template, sink, {
127
- code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
128
- nodeId: 'prRouter',
129
- verdict: 'clean',
130
- target: 'mergeReadiness',
191
+ code: 'DEFAULT_POLICY_APPROVE_REVERIFY_MISSING',
192
+ nodeId: 'mergeGate',
193
+ verdict: 'approved',
194
+ target: 'mergeApproveReverify',
131
195
  });
132
196
  expectScript(template, sink, {
133
- code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
134
- nodeId: 'mergeReadiness',
197
+ code: 'DEFAULT_POLICY_APPROVE_REVERIFY_MISSING',
198
+ nodeId: 'mergeApproveReverify',
135
199
  scriptRef: 'script:pollPr',
136
- next: 'mergeReadinessRouter',
200
+ next: 'mergeApproveReverifyRouter',
137
201
  resultSchema: 'schema:prFeedback',
138
202
  produces: 'prFeedback',
139
203
  });
140
204
  expectRoute(template, sink, {
141
- code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
142
- nodeId: 'mergeReadinessRouter',
205
+ code: 'DEFAULT_POLICY_APPROVE_REVERIFY_MISSING',
206
+ nodeId: 'mergeApproveReverifyRouter',
143
207
  verdict: 'clean',
144
- target: 'mergeGate',
208
+ target: 'confirmMerge',
145
209
  });
146
- expectGateArtifact(template, sink, {
147
- code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
148
- nodeId: 'mergeGate',
149
- producerId: 'mergeReadiness',
150
- as: 'prFeedback',
210
+ expectRoute(template, sink, {
211
+ code: 'DEFAULT_POLICY_APPROVE_REVERIFY_MISSING',
212
+ nodeId: 'mergeApproveReverifyRouter',
213
+ verdict: 'merged',
214
+ target: 'cleanupWorktree',
151
215
  });
152
216
  expectRoute(template, sink, {
153
- code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
217
+ code: 'DEFAULT_POLICY_APPROVE_REVERIFY_MISSING',
218
+ nodeId: 'mergeApproveReverifyRouter',
219
+ verdict: 'closed',
220
+ target: 'recoveryGate',
221
+ });
222
+ expectDefaultRoute(template, sink, {
223
+ code: 'DEFAULT_POLICY_APPROVE_REVERIFY_MISSING',
224
+ nodeId: 'mergeApproveReverifyRouter',
225
+ target: 'classifyRecovery',
226
+ });
227
+ }
228
+ function checkOverrideMergeRouting(template, sink) {
229
+ expectRoute(template, sink, {
230
+ code: 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING',
154
231
  nodeId: 'mergeGate',
155
- verdict: 'approved',
156
- target: 'confirmMerge',
232
+ verdict: 'override_merge',
233
+ target: 'overrideMerge',
234
+ });
235
+ expectScript(template, sink, {
236
+ code: 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING',
237
+ nodeId: 'overrideMerge',
238
+ scriptRef: 'script:overrideMerge',
239
+ next: 'overrideMergeRouter',
240
+ resultSchema: 'schema:prFeedback',
241
+ produces: 'prFeedback',
157
242
  });
158
243
  expectConsume(template, sink, {
159
- code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
244
+ code: 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING',
245
+ consumerId: 'overrideMerge',
246
+ producerId: 'mergeGate',
247
+ as: 'gateResolution',
248
+ });
249
+ expectRoutesWithDefault(template, sink, 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING', 'overrideMergeRouter', [
250
+ ['clean', 'overrideConfirmMerge'],
251
+ ['merged', 'cleanupWorktree'],
252
+ ['closed', 'recoveryGate'],
253
+ ], 'classifyRecovery');
254
+ expectScript(template, sink, {
255
+ code: 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING',
256
+ nodeId: 'overrideConfirmMerge',
257
+ scriptRef: 'script:confirmMerge',
258
+ next: 'cleanupWorktree',
259
+ resultSchema: 'schema:integration',
260
+ });
261
+ expectConsume(template, sink, {
262
+ code: 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING',
263
+ consumerId: 'overrideConfirmMerge',
264
+ producerId: 'overrideMerge',
265
+ as: 'mergeReadiness',
266
+ });
267
+ expectConsume(template, sink, {
268
+ code: 'DEFAULT_POLICY_OVERRIDE_MERGE_ROUTE_MISSING',
269
+ consumerId: 'classifyRecovery',
270
+ producerId: 'overrideMerge',
271
+ as: 'overrideFeedback',
272
+ optional: true,
273
+ staleOk: true,
274
+ });
275
+ }
276
+ function checkMergeConsumesFreshReadiness(template, sink) {
277
+ expectConsume(template, sink, {
278
+ code: 'DEFAULT_POLICY_MERGE_READINESS_FRESHNESS_MISSING',
160
279
  consumerId: 'confirmMerge',
161
- producerId: 'mergeReadiness',
280
+ producerId: 'mergeApproveReverify',
162
281
  as: 'mergeReadiness',
163
282
  });
164
283
  }
@@ -168,36 +287,13 @@ function checkMergeGateRecheckRouting(template, sink) {
168
287
  nodeId: 'mergeGate',
169
288
  outcomes: ['approved', 'recheck', 'address_review_threads', 'return_to_development', 'override_merge', 'cancel'],
170
289
  });
171
- expectRoute(template, sink, {
172
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
173
- nodeId: 'mergeGate',
174
- verdict: 'recheck',
175
- target: 'mergeRecheck',
176
- });
177
- expectRoute(template, sink, {
178
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
179
- nodeId: 'mergeGate',
180
- verdict: 'address_review_threads',
181
- target: 'triage',
182
- });
183
- expectRoute(template, sink, {
184
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
185
- nodeId: 'mergeGate',
186
- verdict: 'return_to_development',
187
- target: 'triage',
188
- });
189
- expectRoute(template, sink, {
190
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
191
- nodeId: 'mergeGate',
192
- verdict: 'override_merge',
193
- target: 'confirmMerge',
194
- });
195
- expectRoute(template, sink, {
196
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
197
- nodeId: 'mergeGate',
198
- verdict: 'cancel',
199
- target: 'cancelledEnd',
200
- });
290
+ expectRoutes(template, sink, 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING', 'mergeGate', [
291
+ ['recheck', 'mergeRecheck'],
292
+ ['address_review_threads', 'triage'],
293
+ ['return_to_development', 'triage'],
294
+ ['override_merge', 'overrideMerge'],
295
+ ['cancel', 'cancelledEnd'],
296
+ ]);
201
297
  expectScript(template, sink, {
202
298
  code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
203
299
  nodeId: 'mergeRecheck',
@@ -206,18 +302,13 @@ function checkMergeGateRecheckRouting(template, sink) {
206
302
  resultSchema: 'schema:prFeedback',
207
303
  produces: 'prFeedback',
208
304
  });
209
- expectRoute(template, sink, {
210
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
211
- nodeId: 'mergeRecheckRouter',
212
- verdict: 'clean',
213
- target: 'blockedEnd',
214
- });
215
- expectRoute(template, sink, {
216
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
217
- nodeId: 'mergeRecheckRouter',
218
- verdict: 'review_changes',
219
- target: 'triage',
220
- });
305
+ expectRoutes(template, sink, 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING', 'mergeRecheckRouter', [
306
+ ['clean', 'mergeGate'],
307
+ ['merged', 'cleanupWorktree'],
308
+ ['closed', 'recoveryGate'],
309
+ ['review_changes', 'triage'],
310
+ ['recheck', 'mergeReadiness'],
311
+ ]);
221
312
  expectBoundedRoute(template, sink, {
222
313
  code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
223
314
  nodeId: 'mergeRecheckRouter',
@@ -226,16 +317,10 @@ function checkMergeGateRecheckRouting(template, sink) {
226
317
  scope: 'ciLoop',
227
318
  value: 3,
228
319
  });
229
- expectRoute(template, sink, {
230
- code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
231
- nodeId: 'mergeRecheckRouter',
232
- verdict: 'recheck',
233
- target: 'mergeReadiness',
234
- });
235
320
  expectDefaultRoute(template, sink, {
236
321
  code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
237
322
  nodeId: 'mergeRecheckRouter',
238
- target: 'blockedEnd',
323
+ target: 'recoveryGate',
239
324
  });
240
325
  expectConsume(template, sink, {
241
326
  code: 'DEFAULT_POLICY_MERGE_RECHECK_ROUTE_MISSING',
@@ -254,6 +339,22 @@ function checkMergeGateRecheckRouting(template, sink) {
254
339
  staleOk: true,
255
340
  });
256
341
  }
342
+ function checkRecoveryGateRouting(template, sink) {
343
+ expectHumanGateOutcomes(template, sink, {
344
+ code: 'DEFAULT_POLICY_GATE_OUTCOMES_IMPLICIT',
345
+ nodeId: 'recoveryGate',
346
+ outcomes: ['recheck', 'cancel'],
347
+ });
348
+ expectRoutes(template, sink, 'DEFAULT_POLICY_GATE_OUTCOMES_IMPLICIT', 'recoveryGate', [
349
+ ['recheck', 'pollPr'],
350
+ ['cancel', 'cancelledEnd'],
351
+ ]);
352
+ expectDefaultRoute(template, sink, {
353
+ code: 'DEFAULT_POLICY_GATE_OUTCOMES_IMPLICIT',
354
+ nodeId: 'recoveryGate',
355
+ target: 'blockedEnd',
356
+ });
357
+ }
257
358
  function checkReviewFeedbackLoop(template, sink) {
258
359
  expectRoute(template, sink, {
259
360
  code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
@@ -316,6 +417,16 @@ function checkReviewFeedbackLoop(template, sink) {
316
417
  verdict: 'question',
317
418
  target: 'questionGate',
318
419
  });
420
+ expectHumanGateOutcomes(template, sink, {
421
+ code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
422
+ nodeId: 'questionGate',
423
+ outcomes: ['fix', 'wontfix', 'cancel'],
424
+ });
425
+ expectRoutesWithDefault(template, sink, 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING', 'questionGate', [
426
+ ['fix', 'questionReviewRework'],
427
+ ['wontfix', 'respondThreads'],
428
+ ['cancel', 'cancelledEnd'],
429
+ ], 'recoveryGate');
319
430
  expectRoute(template, sink, {
320
431
  code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
321
432
  nodeId: 'triageRouter',
@@ -328,6 +439,30 @@ function checkReviewFeedbackLoop(template, sink) {
328
439
  verdict: 'wontfix',
329
440
  target: 'respondThreads',
330
441
  });
442
+ expectNodeNext(template, sink, {
443
+ code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
444
+ nodeId: 'questionReviewRework',
445
+ target: 'questionReviewIntegrator',
446
+ });
447
+ expectConsume(template, sink, {
448
+ code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
449
+ consumerId: 'questionReviewRework',
450
+ producerId: 'triage',
451
+ as: 'triage',
452
+ });
453
+ expectConsume(template, sink, {
454
+ code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
455
+ consumerId: 'questionReviewRework',
456
+ producerId: 'questionGate',
457
+ as: 'gateResolution',
458
+ });
459
+ expectScript(template, sink, {
460
+ code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
461
+ nodeId: 'questionReviewIntegrator',
462
+ scriptRef: 'script:integrator',
463
+ next: 'respondThreads',
464
+ resultSchema: 'schema:integration',
465
+ });
331
466
  expectNodeNext(template, sink, {
332
467
  code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
333
468
  nodeId: 'reviewRework',
@@ -353,13 +488,47 @@ function checkReviewFeedbackLoop(template, sink) {
353
488
  producerId: 'triage',
354
489
  as: 'triage',
355
490
  });
491
+ expectConsume(template, sink, {
492
+ code: 'DEFAULT_POLICY_REVIEW_CHANGES_ROUTE_MISSING',
493
+ consumerId: 'respondThreads',
494
+ producerId: 'questionGate',
495
+ as: 'gateResolution',
496
+ optional: true,
497
+ staleOk: true,
498
+ });
356
499
  }
357
500
  function checkLoopExhaustionEscalation(template, sink) {
501
+ expectRoutes(template, sink, 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING', 'planReviewRouter', [
502
+ ['approved', 'planGate'],
503
+ ['clean', 'planGate'],
504
+ ]);
505
+ expectBoundedRoute(template, sink, {
506
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
507
+ nodeId: 'planReviewRouter',
508
+ verdict: 'blocker',
509
+ target: 'analyst',
510
+ scope: 'planReviewLoop',
511
+ value: 4,
512
+ });
513
+ expectBoundedRoute(template, sink, {
514
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
515
+ nodeId: 'planReviewRouter',
516
+ verdict: 'changes_requested',
517
+ target: 'analyst',
518
+ scope: 'planReviewLoop',
519
+ value: 4,
520
+ });
358
521
  expectHumanGateOutcomes(template, sink, {
359
522
  code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
360
523
  nodeId: 'planGate',
361
524
  outcomes: ['approved', 'rework', 'cancel'],
362
525
  });
526
+ expectRoute(template, sink, {
527
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
528
+ nodeId: 'planGate',
529
+ verdict: 'approved',
530
+ target: 'developer',
531
+ });
363
532
  expectRoute(template, sink, {
364
533
  code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
365
534
  nodeId: 'planGate',
@@ -372,6 +541,11 @@ function checkLoopExhaustionEscalation(template, sink) {
372
541
  verdict: 'cancel',
373
542
  target: 'cancelledEnd',
374
543
  });
544
+ expectDefaultRoute(template, sink, {
545
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
546
+ nodeId: 'planGate',
547
+ target: 'blockedEnd',
548
+ });
375
549
  expectRouterDefaultGate(template, sink, {
376
550
  routerId: 'planReviewRouter',
377
551
  gateId: 'planStuckGate',
@@ -394,6 +568,26 @@ function checkLoopExhaustionEscalation(template, sink) {
394
568
  verdict: 'cancel',
395
569
  target: 'cancelledEnd',
396
570
  });
571
+ expectRoutes(template, sink, 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING', 'codeReviewRouter', [
572
+ ['approved', 'integrator'],
573
+ ['clean', 'integrator'],
574
+ ]);
575
+ expectBoundedRoute(template, sink, {
576
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
577
+ nodeId: 'codeReviewRouter',
578
+ verdict: 'blocker',
579
+ target: 'reworkDeveloper',
580
+ scope: 'codeReviewLoop',
581
+ value: 3,
582
+ });
583
+ expectBoundedRoute(template, sink, {
584
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
585
+ nodeId: 'codeReviewRouter',
586
+ verdict: 'changes_requested',
587
+ target: 'reworkDeveloper',
588
+ scope: 'codeReviewLoop',
589
+ value: 3,
590
+ });
397
591
  expectRouterDefaultGate(template, sink, {
398
592
  routerId: 'codeReviewRouter',
399
593
  gateId: 'codeStuckGate',
@@ -435,6 +629,168 @@ function checkLoopExhaustionEscalation(template, sink) {
435
629
  parent: null,
436
630
  });
437
631
  }
632
+ function checkRecoverableCatches(template, sink) {
633
+ const recoverableNodes = PR_LIFECYCLE_NODES.filter((nodeId) => nodeId !== 'confirmMerge' && nodeId !== 'overrideConfirmMerge');
634
+ for (const nodeId of recoverableNodes) {
635
+ const node = effectNode(template, nodeId);
636
+ if (!node)
637
+ continue;
638
+ for (const entry of node.catch ?? []) {
639
+ if (isTerminalNode(template, entry.goto)) {
640
+ sink.error('DEFAULT_POLICY_RECOVERABLE_CATCH_TERMINAL', `node ${nodeId} catch for ${entry.onError} must not route to a terminal`, {
641
+ nodeId,
642
+ expected: `${entry.onError} -> non-terminal`,
643
+ actual: `${entry.onError} -> ${entry.goto}`,
644
+ });
645
+ }
646
+ }
647
+ }
648
+ }
649
+ function checkCapExhaustionOffRamp(template, sink) {
650
+ const capRouters = [
651
+ 'prRouter',
652
+ 'mergeReadinessRouter',
653
+ 'mergeRecheckRouter',
654
+ 'overrideMergeRouter',
655
+ 'triageRouter',
656
+ 'recoveryRouter',
657
+ 'planReviewRouter',
658
+ 'codeReviewRouter',
659
+ ];
660
+ for (const nodeId of capRouters) {
661
+ const node = routingNode(template, nodeId);
662
+ if (!node)
663
+ continue;
664
+ const target = defaultBranchTarget(node);
665
+ const targetNode = target ? template.nodes[target] : undefined;
666
+ const isHumanGate = targetNode?.kind === 'humanGate';
667
+ const isClassifier = target === 'classifyRecovery';
668
+ if (!isHumanGate && !isClassifier) {
669
+ sink.error('DEFAULT_POLICY_CAP_EXHAUSTION_OFFRAMP_MISSING', `cap-router ${nodeId} default must route to a humanGate or classifyRecovery, not a terminal`, {
670
+ nodeId,
671
+ expected: 'default -> humanGate or classifyRecovery',
672
+ actual: target ? `default -> ${target} (${targetNode?.kind ?? 'missing'})` : 'missing default branch',
673
+ });
674
+ }
675
+ }
676
+ }
677
+ function checkConfirmMergeFailureRecoverable(template, sink) {
678
+ for (const nodeId of ['confirmMerge', 'overrideConfirmMerge']) {
679
+ const node = effectNode(template, nodeId);
680
+ if (!node)
681
+ continue;
682
+ for (const entry of node.catch ?? []) {
683
+ if (isTerminalNode(template, entry.goto)) {
684
+ sink.error('DEFAULT_POLICY_CONFIRM_MERGE_FAILURE_TERMINAL', `${nodeId} catch for ${entry.onError} must not route to a terminal`, {
685
+ nodeId,
686
+ expected: `${entry.onError} -> non-terminal`,
687
+ actual: `${entry.onError} -> ${entry.goto}`,
688
+ });
689
+ }
690
+ }
691
+ }
692
+ }
693
+ function checkPostMergeCleanup(template, sink) {
694
+ for (const nodeId of ['confirmMerge', 'overrideConfirmMerge']) {
695
+ expectNodeNext(template, sink, {
696
+ code: 'DEFAULT_POLICY_POST_MERGE_CLEANUP_MISSING',
697
+ nodeId,
698
+ target: 'cleanupWorktree',
699
+ });
700
+ }
701
+ expectScript(template, sink, {
702
+ code: 'DEFAULT_POLICY_POST_MERGE_CLEANUP_MISSING',
703
+ nodeId: 'cleanupWorktree',
704
+ scriptRef: 'script:cleanupWorktree',
705
+ next: 'mergedEnd',
706
+ resultSchema: 'schema:integration',
707
+ });
708
+ for (const onError of ['revo.ScriptBlocked', 'revo.ScriptFailed']) {
709
+ expectCatch(template, sink, {
710
+ code: 'DEFAULT_POLICY_POST_MERGE_CLEANUP_MISSING',
711
+ nodeId: 'cleanupWorktree',
712
+ onError,
713
+ target: 'mergedEnd',
714
+ });
715
+ }
716
+ }
717
+ function checkGateOutcomesExplicit(template, sink) {
718
+ const expectedDefaults = {
719
+ mergeGate: 'blockedEnd',
720
+ };
721
+ for (const [nodeId, node] of Object.entries(template.nodes)) {
722
+ if (node.kind !== 'humanGate')
723
+ continue;
724
+ for (const outcome of node.outcomes) {
725
+ const branch = guardedBranchForVerdict(node, outcome);
726
+ if (!branch) {
727
+ sink.error('DEFAULT_POLICY_GATE_OUTCOMES_IMPLICIT', `gate ${nodeId} declares ${outcome} but has no guarded branch for it`, {
728
+ nodeId,
729
+ expected: `guarded branch for ${outcome}`,
730
+ actual: 'missing guarded branch',
731
+ });
732
+ }
733
+ }
734
+ const defaultTarget = expectedDefaults[nodeId];
735
+ if (defaultTarget) {
736
+ expectDefaultRoute(template, sink, {
737
+ code: 'DEFAULT_POLICY_GATE_OUTCOMES_IMPLICIT',
738
+ nodeId,
739
+ target: defaultTarget,
740
+ });
741
+ }
742
+ }
743
+ }
744
+ function expectPollPrFeedbackHop(template, sink, hop) {
745
+ expectScript(template, sink, {
746
+ code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
747
+ nodeId: hop.scriptId,
748
+ scriptRef: 'script:pollPr',
749
+ next: hop.routerId,
750
+ resultSchema: 'schema:prFeedback',
751
+ produces: 'prFeedback',
752
+ });
753
+ expectIncrementCounters(template, sink, {
754
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
755
+ nodeId: hop.scriptId,
756
+ scopes: [POLL_LOOP_SCOPE],
757
+ });
758
+ expectRoute(template, sink, {
759
+ code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
760
+ nodeId: hop.routerId,
761
+ verdict: 'clean',
762
+ target: hop.cleanTarget,
763
+ });
764
+ expectRoute(template, sink, {
765
+ code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
766
+ nodeId: hop.routerId,
767
+ verdict: 'merged',
768
+ target: 'cleanupWorktree',
769
+ });
770
+ expectRoute(template, sink, {
771
+ code: 'DEFAULT_POLICY_PR_FRESHNESS_WIRING_MISSING',
772
+ nodeId: hop.routerId,
773
+ verdict: 'closed',
774
+ target: 'recoveryGate',
775
+ });
776
+ expectBoundedRoute(template, sink, {
777
+ code: 'DEFAULT_POLICY_LOOP_EXHAUSTION_ESCALATION_MISSING',
778
+ nodeId: hop.routerId,
779
+ verdict: 'recheck',
780
+ target: hop.recheckTarget,
781
+ scope: POLL_LOOP_SCOPE,
782
+ value: POLL_LOOP_CAP,
783
+ });
784
+ }
785
+ function expectRoutes(template, sink, code, nodeId, routes) {
786
+ for (const [verdict, target] of routes) {
787
+ expectRoute(template, sink, { code, nodeId, verdict, target });
788
+ }
789
+ }
790
+ function expectRoutesWithDefault(template, sink, code, nodeId, routes, defaultTarget) {
791
+ expectRoutes(template, sink, code, nodeId, routes);
792
+ expectDefaultRoute(template, sink, { code, nodeId, target: defaultTarget });
793
+ }
438
794
  function expectChangeProducer(template, sink, nodeId) {
439
795
  const node = effectNode(template, nodeId);
440
796
  if (node?.resultSchema === 'schema:change' && node.produces?.name === 'change')
@@ -475,6 +831,41 @@ function expectScript(template, sink, rule) {
475
831
  actual: describeNode(node),
476
832
  });
477
833
  }
834
+ function expectCatch(template, sink, rule) {
835
+ const node = effectNode(template, rule.nodeId);
836
+ const actual = node?.catch?.find((entry) => entry.onError === rule.onError)?.goto;
837
+ if (actual === rule.target)
838
+ return;
839
+ let actualDescription;
840
+ if (actual) {
841
+ actualDescription = `${rule.onError} -> ${actual}`;
842
+ }
843
+ else if (node) {
844
+ actualDescription = `missing catch for ${rule.onError}`;
845
+ }
846
+ else {
847
+ actualDescription = describeNode(template.nodes[rule.nodeId]);
848
+ }
849
+ sink.error(rule.code, `node ${rule.nodeId} must route ${rule.onError} to ${rule.target}`, {
850
+ nodeId: rule.nodeId,
851
+ path: 'catch',
852
+ expected: `${rule.onError} -> ${rule.target}`,
853
+ actual: actualDescription,
854
+ });
855
+ }
856
+ function expectIncrementCounters(template, sink, rule) {
857
+ const node = effectNode(template, rule.nodeId);
858
+ const actual = node?.incrementCounters ?? [];
859
+ if (node && actual.length === rule.scopes.length && actual.every((scope, index) => scope === rule.scopes[index])) {
860
+ return;
861
+ }
862
+ sink.error(rule.code, `node ${rule.nodeId} must increment ${rule.scopes.join(',')}`, {
863
+ nodeId: rule.nodeId,
864
+ path: 'incrementCounters',
865
+ expected: `incrementCounters=${rule.scopes.join(',')}`,
866
+ actual: node ? `incrementCounters=${actual.join(',')}` : describeNode(template.nodes[rule.nodeId]),
867
+ });
868
+ }
478
869
  function expectGateOutcomes(template, sink, rule) {
479
870
  const node = template.nodes[rule.nodeId];
480
871
  if (node?.kind === 'humanGate' &&
@@ -482,7 +873,7 @@ function expectGateOutcomes(template, sink, rule) {
482
873
  node.outcomes.every((outcome, index) => outcome === rule.outcomes[index])) {
483
874
  return;
484
875
  }
485
- sink.error(rule.code, `gate ${rule.nodeId} must map approve to ${rule.outcomes[0]} and reject to ${rule.outcomes.at(-1)}`, {
876
+ sink.error(rule.code, `gate ${rule.nodeId} must declare explicit outcomes in order: ${rule.outcomes.join(',')}`, {
486
877
  nodeId: rule.nodeId,
487
878
  path: 'outcomes',
488
879
  expected: rule.outcomes.join(','),
@@ -621,6 +1012,9 @@ function expectRouterDefaultGate(template, sink, rule) {
621
1012
  actual: gate ? `kind=${gate.kind} ${approveVerdict}=${approveTarget ?? 'missing'} default=${gateDefault ?? 'missing'}` : 'missing gate',
622
1013
  });
623
1014
  }
1015
+ function isTerminalNode(template, nodeId) {
1016
+ return template.nodes[nodeId]?.kind === 'terminal';
1017
+ }
624
1018
  function effectNode(template, nodeId) {
625
1019
  const node = template.nodes[nodeId];
626
1020
  if (node?.kind === 'agent' || node?.kind === 'script')