@1aboveio/skills 0.19.3 → 0.20.2

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 (206) hide show
  1. package/README.md +9 -4
  2. package/package.json +1 -1
  3. package/runtime/skills/distribution/generated/recipes.json +54 -48
  4. package/runtime/skills/distribution/scripts/bundles.mjs +113 -7
  5. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
  6. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
  7. package/skills/cicd-pipeline/cloud-build/SKILL.md +6 -6
  8. package/skills/cicd-pipeline/mergify/SKILL.md +1 -1
  9. package/skills/engineering/engineering-runtime/coherence/workflow.json +15 -84
  10. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
  11. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
  12. package/skills/engineering/rush-release/SKILL.md +46 -25
  13. package/skills/engineering/rush-release/evals/evals.json +22 -8
  14. package/skills/engineering/rush-release/references/cut.md +21 -4
  15. package/skills/engineering/rush-release/references/preflight.md +25 -6
  16. package/skills/engineering/rush-release/references/promotion.md +70 -0
  17. package/skills/engineering/rush-release/references/publish.md +49 -16
  18. package/skills/engineering/rush-release/scripts/plan.mjs +33 -7
  19. package/skills/engineering/smoke/SKILL.md +4 -4
  20. package/skills/engineering/smoke/references/manifest.md +3 -3
  21. package/skills/payment/payment-analysis/SKILL.md +5 -3
  22. package/skills/payment/tld-detection/SKILL.md +163 -0
  23. package/skills/payment/tld-detection/agents/openai.yaml +9 -0
  24. package/skills/payment/tld-detection/evals/evals.json +107 -0
  25. package/skills/payment/tld-detection/references/clean.md +51 -0
  26. package/skills/payment/tld-detection/references/clustering.md +219 -0
  27. package/skills/payment/tld-detection/references/decision.md +85 -0
  28. package/skills/payment/tld-detection/references/report-template.md +169 -0
  29. package/skills/payment/tld-detection/references/terminology.md +69 -0
  30. package/skills/payment/tld-detection/references/workflow.md +78 -0
  31. package/skills/payment/tld-detection/scripts/detect_ladders.py +390 -0
  32. package/skills/payment/tld-detection/tests/test_detect_ladders.py +132 -0
  33. package/skills/engineering/resolve-issues/SKILL.md +0 -168
  34. package/skills/engineering/resolve-issues/agents/openai.yaml +0 -9
  35. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +0 -499
  36. package/skills/engineering/resolve-issues/references/breaker.md +0 -82
  37. package/skills/engineering/resolve-issues/references/deliverables.md +0 -27
  38. package/skills/engineering/resolve-issues/references/delivery.md +0 -108
  39. package/skills/engineering/resolve-issues/references/evidence-lane.md +0 -21
  40. package/skills/engineering/resolve-issues/references/exact-head-ci.md +0 -287
  41. package/skills/engineering/resolve-issues/references/fan-out.md +0 -33
  42. package/skills/engineering/resolve-issues/references/finalization.md +0 -68
  43. package/skills/engineering/resolve-issues/references/guarantees.md +0 -10
  44. package/skills/engineering/resolve-issues/references/high-risk.md +0 -29
  45. package/skills/engineering/resolve-issues/references/incidents/848/README.md +0 -156
  46. package/skills/engineering/resolve-issues/references/intake.md +0 -86
  47. package/skills/engineering/resolve-issues/references/integration-gate.md +0 -53
  48. package/skills/engineering/resolve-issues/references/interference.md +0 -87
  49. package/skills/engineering/resolve-issues/references/loop.md +0 -134
  50. package/skills/engineering/resolve-issues/references/model-catalog.md +0 -9
  51. package/skills/engineering/resolve-issues/references/postmortem.md +0 -27
  52. package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +0 -41
  53. package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +0 -48
  54. package/skills/engineering/resolve-issues/references/pre-flight.md +0 -41
  55. package/skills/engineering/resolve-issues/references/regression-checklist.md +0 -26
  56. package/skills/engineering/resolve-issues/references/run-state.md +0 -288
  57. package/skills/engineering/resolve-issues/references/sandboxed-testing.md +0 -48
  58. package/skills/engineering/resolve-issues/references/spawn-contract.md +0 -96
  59. package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +0 -40
  60. package/skills/engineering/resolve-issues/references/why.md +0 -653
  61. package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +0 -49
  62. package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +0 -721
  63. package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +0 -962
  64. package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +0 -220
  65. package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +0 -420
  66. package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +0 -256
  67. package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +0 -168
  68. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
  69. package/skills/engineering/resolve-issues/scripts/doctrine.mjs +0 -62
  70. package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +0 -191
  71. package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +0 -413
  72. package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +0 -332
  73. package/skills/engineering/resolve-issues/scripts/finalize.mjs +0 -488
  74. package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +0 -3307
  75. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +0 -16
  76. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +0 -51
  77. package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +0 -17
  78. package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +0 -166
  79. package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +0 -92
  80. package/skills/engineering/resolve-issues/scripts/guide-index.mjs +0 -73
  81. package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +0 -159
  82. package/skills/engineering/resolve-issues/scripts/interference.mjs +0 -427
  83. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
  84. package/skills/engineering/resolve-issues/scripts/next-operations.mjs +0 -419
  85. package/skills/engineering/resolve-issues/scripts/postmortem.mjs +0 -909
  86. package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +0 -345
  87. package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +0 -415
  88. package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +0 -697
  89. package/skills/engineering/resolve-issues/scripts/report.mjs +0 -494
  90. package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +0 -172
  91. package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +0 -79
  92. package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +0 -334
  93. package/skills/engineering/resolve-issues/scripts/run-state.mjs +0 -4792
  94. package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +0 -395
  95. package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +0 -290
  96. package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +0 -170
  97. package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +0 -293
  98. package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +0 -197
  99. package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +0 -127
  100. package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +0 -893
  101. package/skills/engineering/resolve-issues/scripts/workspaces.mjs +0 -829
  102. package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +0 -290
  103. package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +0 -80
  104. package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +0 -102
  105. package/skills/engineering/resolve-release/LICENSE +0 -3
  106. package/skills/engineering/resolve-release/SKILL.md +0 -113
  107. package/skills/engineering/resolve-release/agents/openai.yaml +0 -9
  108. package/skills/engineering/resolve-release/references/assembly.md +0 -137
  109. package/skills/engineering/resolve-release/references/auto-when-green.md +0 -56
  110. package/skills/engineering/resolve-release/references/candidate.md +0 -167
  111. package/skills/engineering/resolve-release/references/exposure.md +0 -178
  112. package/skills/engineering/resolve-release/references/handoff.md +0 -24
  113. package/skills/engineering/resolve-release/references/postmortem.md +0 -230
  114. package/skills/engineering/resolve-release/references/preflight.md +0 -208
  115. package/skills/engineering/resolve-release/references/principles.md +0 -94
  116. package/skills/engineering/resolve-release/references/regression-checklist.md +0 -36
  117. package/skills/engineering/resolve-release/references/related-skills.md +0 -13
  118. package/skills/engineering/resolve-release/references/routing.md +0 -149
  119. package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +0 -285
  120. package/skills/engineering/resolve-release/references/versioning.md +0 -202
  121. package/skills/engineering/resolve-release/references/why.md +0 -53
  122. package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +0 -389
  123. package/skills/engineering/resolve-release/scripts/build-changes.mjs +0 -209
  124. package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +0 -407
  125. package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +0 -904
  126. package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +0 -81
  127. package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +0 -612
  128. package/skills/engineering/resolve-release/scripts/close-attempt.mjs +0 -135
  129. package/skills/engineering/resolve-release/scripts/closeout-release.mjs +0 -161
  130. package/skills/engineering/resolve-release/scripts/doctrine.mjs +0 -106
  131. package/skills/engineering/resolve-release/scripts/durable-processing.mjs +0 -522
  132. package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +0 -659
  133. package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +0 -1014
  134. package/skills/engineering/resolve-release/scripts/finalize-release.mjs +0 -526
  135. package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +0 -169
  136. package/skills/engineering/resolve-release/scripts/green-gate.mjs +0 -599
  137. package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +0 -752
  138. package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +0 -56
  139. package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +0 -1538
  140. package/skills/engineering/resolve-release/scripts/postmortem.mjs +0 -381
  141. package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +0 -510
  142. package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +0 -326
  143. package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +0 -272
  144. package/skills/engineering/resolve-release/scripts/report.mjs +0 -417
  145. package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +0 -114
  146. package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +0 -238
  147. package/skills/engineering/resolve-release/scripts/review-packet.mjs +0 -503
  148. package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +0 -263
  149. package/skills/engineering/resolve-release/scripts/version-assert.mjs +0 -339
  150. package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +0 -485
  151. package/skills/engineering/resolve-release/scripts/version.mjs +0 -1199
  152. package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +0 -449
  153. package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +0 -21
  154. package/skills/engineering/resolve-release/vendor/README.md +0 -60
  155. package/skills/engineering/resolve-release/vendor/acorn.mjs +0 -6233
  156. package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +0 -21
  157. package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +0 -341
  158. package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +0 -70
  159. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +0 -2
  160. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +0 -1
  161. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +0 -7
  162. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +0 -1
  163. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +0 -2033
  164. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +0 -2033
  165. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +0 -7
  166. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +0 -1
  167. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +0 -2
  168. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +0 -1
  169. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +0 -2
  170. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +0 -1
  171. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +0 -2
  172. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +0 -1
  173. package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +0 -49
  174. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +0 -21
  175. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +0 -5
  176. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +0 -549
  177. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +0 -549
  178. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +0 -2
  179. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +0 -1
  180. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +0 -2
  181. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +0 -1
  182. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +0 -36
  183. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +0 -47
  184. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +0 -82
  185. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +0 -11
  186. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +0 -22
  187. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +0 -21
  188. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +0 -21
  189. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +0 -11
  190. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +0 -11
  191. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +0 -25
  192. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
  193. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +0 -85
  194. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +0 -85
  195. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +0 -2
  196. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +0 -1
  197. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +0 -2
  198. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +0 -1
  199. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +0 -20
  200. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +0 -20
  201. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +0 -2
  202. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +0 -1
  203. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +0 -2
  204. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +0 -1
  205. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +0 -61
  206. /package/skills/{engineering/resolve-issues → payment/tld-detection}/LICENSE +0 -0
@@ -1,419 +0,0 @@
1
- // Exact next-operation rendering for resolve-issues. This module composes existing
2
- // state-machine and first-party CLI contracts; it owns no duplicate transition logic.
3
- import { execFileSync } from 'node:child_process'
4
- import { fileURLToPath } from 'node:url'
5
- import path from 'node:path'
6
-
7
- import { deliveryFlow } from './fix-rounds.mjs'
8
- import { terminalDisposition } from './terminal-dispositions.mjs'
9
- import { runIsComplete } from './postmortem.mjs'
10
- import { unitPrReconciliationStatus } from './reconcile-contained-unit-prs.mjs'
11
-
12
- const SCRIPT_PATHS = Object.freeze({
13
- runState: fileURLToPath(new URL('./run-state.mjs', import.meta.url)),
14
- watchDelivery: fileURLToPath(new URL('./watch-delivery.mjs', import.meta.url)),
15
- combineAndVerify: fileURLToPath(new URL('./combine-and-verify.mjs', import.meta.url)),
16
- exactHeadCi: fileURLToPath(new URL('./exact-head-ci.mjs', import.meta.url)),
17
- })
18
-
19
- const object = (value) => value != null && typeof value === 'object' && !Array.isArray(value)
20
- const text = (value) => typeof value === 'string' && value.trim().length > 0
21
- const numberRef = (value) => String(value ?? '').trim().match(/^#?(\d+)$/)?.[1] ?? null
22
-
23
- function tryExec(command, args, cwd) {
24
- try {
25
- return { ok: true, stdout: execFileSync(command, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() }
26
- } catch {
27
- return { ok: false, stdout: '' }
28
- }
29
- }
30
-
31
- function repositoryFromRemote(remote) {
32
- const match = String(remote ?? '').trim().replace(/\.git$/, '').match(/(?:github\.com[:/])([^/]+\/[^/]+)$/)
33
- return match?.[1] ?? null
34
- }
35
-
36
- export function probeNextOperations(manifest = {}, { cwd = process.cwd() } = {}) {
37
- const top = tryExec('git', ['rev-parse', '--show-toplevel'], cwd)
38
- const repoRoot = top.ok ? path.resolve(top.stdout) : path.resolve(cwd)
39
- const branch = tryExec('git', ['branch', '--show-current'], repoRoot)
40
- const status = tryExec('git', ['status', '--porcelain'], repoRoot)
41
- const unpublished = tryExec('git', ['rev-list', '--max-count=1', 'HEAD', '--not', '--remotes'], repoRoot)
42
- const remote = tryExec('git', ['remote', 'get-url', 'origin'], repoRoot)
43
- const repository = text(manifest.repository) ? manifest.repository.trim() : (remote.ok ? repositoryFromRemote(remote.stdout) : null)
44
- const originalBranchExists = text(manifest.originalBranch)
45
- ? tryExec('git', ['show-ref', '--verify', '--quiet', `refs/heads/${manifest.originalBranch}`], repoRoot).ok
46
- : false
47
- const prStates = {}
48
- const issueStates = {}
49
- for (const unit of Array.isArray(manifest.units) ? manifest.units : []) {
50
- const pr = numberRef(unit?.pr)
51
- const issue = numberRef(unit?.id)
52
- if (repository && pr && (unit?.status === 'merge-ready' || terminalDisposition(unit))) {
53
- const read = tryExec('gh', ['pr', 'view', pr, '--repo', repository, '--json', 'state,mergedAt,mergeCommit,headRefOid,isDraft'], repoRoot)
54
- if (read.ok) try { prStates[unit.id] = JSON.parse(read.stdout) } catch {}
55
- }
56
- if (repository && issue && terminalDisposition(unit)) {
57
- const read = tryExec('gh', ['issue', 'view', issue, '--repo', repository, '--json', 'state'], repoRoot)
58
- if (read.ok) try { issueStates[unit.id] = JSON.parse(read.stdout) } catch {}
59
- }
60
- }
61
- return {
62
- repository,
63
- repoRoot,
64
- currentBranch: branch.ok && branch.stdout ? branch.stdout : null,
65
- clean: status.ok && status.stdout === '',
66
- currentPublished: unpublished.ok && unpublished.stdout === '',
67
- originalBranchExists,
68
- prStates,
69
- issueStates,
70
- }
71
- }
72
-
73
- function operation({ id, kind, argv, cwd, expectedExitCodes, transition, authorizedBy = [], detail = null }) {
74
- return {
75
- id,
76
- kind,
77
- mutates: kind === 'mutation',
78
- argv,
79
- cwd,
80
- expectedExitCodes,
81
- authorizes: { transition },
82
- authorizedBy,
83
- ...(detail ? { detail } : {}),
84
- }
85
- }
86
-
87
- function exactHeadPaths(unit) {
88
- const config = object(unit?.exactHeadCi) ? unit.exactHeadCi : null
89
- if (!text(config?.requestPath) || !text(config?.artifactPath)
90
- || !path.isAbsolute(config.requestPath) || !path.isAbsolute(config.artifactPath)) return null
91
- return { request: config.requestPath, artifact: config.artifactPath }
92
- }
93
-
94
- function verdictPath(manifest, unit, probe) {
95
- if (text(unit.integrationVerdictPath) && path.isAbsolute(unit.integrationVerdictPath)) return unit.integrationVerdictPath
96
- const component = (Array.isArray(manifest.components) ? manifest.components : [])
97
- .find((entry) => Array.isArray(entry?.units) && entry.units.includes(unit.id))
98
- if (text(component?.integrationVerdictPath) && path.isAbsolute(component.integrationVerdictPath)) return component.integrationVerdictPath
99
- const observed = probe?.integrationVerdicts?.[unit.id]
100
- return text(observed) && path.isAbsolute(observed) ? observed : null
101
- }
102
-
103
- export function deriveNextOperations(manifest = {}, {
104
- slug = null,
105
- manifestPath = null,
106
- probe = {},
107
- actions = [],
108
- actionNames = {},
109
- } = {}) {
110
- const m = object(manifest) ? manifest : {}
111
- const runSlug = slug ?? (text(m.runSlug) ? m.runSlug : null)
112
- const file = manifestPath ? path.resolve(manifestPath) : null
113
- const repoRoot = path.resolve(probe.repoRoot || m.workspacePath || process.cwd())
114
- const repository = text(m.repository) ? m.repository.trim() : (text(probe.repository) ? probe.repository.trim() : null)
115
- const units = (Array.isArray(m.units) ? m.units : []).filter(object)
116
- const byId = new Map(units.filter((unit) => text(unit.id)).map((unit) => [unit.id, unit]))
117
- const invocationReady = text(runSlug) && file
118
- const inspections = []
119
- const mutations = []
120
- const blocked = []
121
- const block = (id, reason, transition) => blocked.push({ id, reason, authorizes: { transition } })
122
- const add = (entry) => {
123
- if (!Array.isArray(entry.argv) || entry.argv.some((arg) => typeof arg !== 'string') || !path.isAbsolute(entry.cwd)) {
124
- block(entry.id, 'exact argv/cwd could not be derived from state; no partial operation is emitted', entry.authorizes.transition)
125
- return
126
- }
127
- if (entry.mutates && !invocationReady) {
128
- block(entry.id, 'run slug and resolved manifest path are required before any mutation can be authorized', entry.authorizes.transition)
129
- return
130
- }
131
- ;(entry.mutates ? mutations : inspections).push(entry)
132
- }
133
- const runStateArgv = (...args) => [process.execPath, SCRIPT_PATHS.runState, ...args, ...(file ? ['--file', file] : [])]
134
- const operationExit = (id) => probe?.operationResults?.[id]
135
- const operationPassed = (id, exitCode = 0) => operationExit(id) === exitCode
136
- if (!invocationReady) block('invocation-identity', 'run slug and resolved manifest path are required for run-state-backed operations', 'state -> unchanged')
137
-
138
- for (const action of actions) {
139
- const unit = byId.get(action.unit)
140
- if (!unit) continue
141
- if ([actionNames.PRODUCER_SPAWN, actionNames.REVIEW_SPAWN, actionNames.FIX_ROUND].includes(action.action)) {
142
- const kind = action.action === actionNames.REVIEW_SPAWN ? 'review' : (action.action === actionNames.FIX_ROUND ? 'fix' : 'producer')
143
- add(operation({ id: `spawn-contract:${unit.id}`, kind: 'inspection', cwd: repoRoot, argv: runStateArgv('spawn-contract', runSlug, unit.id, '--kind', kind), expectedExitCodes: [0, 4], transition: `${unit.status ?? 'queued'} -> ${action.action}` }))
144
- continue
145
- }
146
- if (action.action === actionNames.BREAKER_DECISION) {
147
- add(operation({ id: `breaker-diagnosis:${unit.id}`, kind: 'inspection', cwd: repoRoot, argv: runStateArgv('diagnose-breaker', runSlug, unit.id), expectedExitCodes: [0, 1], transition: 'breaker-fired -> breaker-diagnosed' }))
148
- continue
149
- }
150
- if (action.action === actionNames.INTAKE_REENTRY) {
151
- add(operation({ id: `intake-reentry:${unit.id}`, kind: 'mutation', cwd: repoRoot, argv: runStateArgv('record-intake-reentry', runSlug, unit.id), expectedExitCodes: [0], transition: 'breaker-decided -> in-progress', authorizedBy: [{ state: 'units[].breaker.decision' }] }))
152
- continue
153
- }
154
- if (action.action === actionNames.WAIT_ON_DEPENDENCY) {
155
- add(operation({ id: `runnable:${unit.id}`, kind: 'inspection', cwd: repoRoot, argv: runStateArgv('runnable', runSlug, '--json'), expectedExitCodes: [0], transition: 'dependency-wait -> admission-decision' }))
156
- continue
157
- }
158
- if ([actionNames.HUMAN, actionNames.CANNOT_TELL].includes(action.action)) {
159
- block(`current-action:${unit.id}`, action.why, `${unit.status ?? 'unknown'} -> unresolved`)
160
- continue
161
- }
162
- if (![actionNames.DELIVER, actionNames.DELIVERY_WATCH].includes(action.action)) continue
163
-
164
- const component = action.componentId && Array.isArray(m.components)
165
- ? m.components.find((entry) => entry?.id === action.componentId)
166
- : null
167
- if (component?.delivery) {
168
- const delivery = component.delivery
169
- const componentId = component.id
170
- const pr = numberRef(delivery.pr ?? component.pr)
171
- const head = text(delivery.authorizedHeadSha) ? delivery.authorizedHeadSha.trim() : null
172
- const flow = deliveryFlow(m)
173
- const attempts = Array.isArray(delivery.attempts) ? delivery.attempts : []
174
- const observed = probe?.deliveryOutcomes?.[componentId] ?? probe?.deliveryOutcomes?.[unit.id] ?? null
175
- const integration = component.integrationVerdictPath ?? probe?.integrationVerdicts?.[componentId] ?? null
176
- if (!repository) block(`delivery:${componentId}`, 'repository identity is absent from the manifest and Git remote probe', 'component-authorized -> delivery-requested')
177
- if (!m.mergeShippable) block(`delivery-request:${componentId}`, 'mergeShippable authorization is absent/off; no merge or enqueue mutation may be emitted', 'component-authorized -> delivery-requested')
178
- if (!pr) block(`delivery:${componentId}`, 'a concrete component PR number is required', 'component-authorized -> delivery-requested')
179
- if (!head) block(`delivery:${componentId}`, 'components[].delivery.authorizedHeadSha is absent', 'component-authorized -> delivery-requested')
180
- if (!flow) block(`delivery:${componentId}`, 'delivery flow is unresolved; deliveryMode and queueProvider must come from the probe', 'component-authorized -> delivery-requested')
181
-
182
- if (repository && pr && head && flow) {
183
- if (attempts.length) {
184
- const watch = `delivery-watch:${componentId}`
185
- if (operationPassed(watch, 5)) block(watch, 'component delivery watch exited 5 (exact-head CI failed); reopen the component generation and do not record a terminal outcome', 'delivery-attempt-recorded -> fresh-authorization-required')
186
- else if (flow.watchable && operationExit(watch) === undefined && !observed) {
187
- add(operation({ id: watch, kind: 'inspection', cwd: repoRoot, argv: [process.execPath, SCRIPT_PATHS.watchDelivery, '--pr', pr, '--repo', repository, ...(flow.provider ? ['--provider', flow.provider] : []), '--head', head], expectedExitCodes: [0, 4, 5], transition: 'component-delivery-attempt-recorded -> component-delivery-observed' }))
188
- }
189
- } else {
190
- const gate = `delivery-preflight:${componentId}`
191
- const request = `delivery-request:${componentId}`
192
- if (!integration) block(request, 'component integration verdict path is absent; the authorized component cannot be delivered without its ALIVE artifact', 'component-authorized -> delivery-requested')
193
- else {
194
- const gateCommand = flow.mode === 'queue' ? 'enqueue-eligible' : 'mergeable'
195
- if (operationExit(gate) === undefined) add(operation({ id: gate, kind: 'inspection', cwd: repoRoot, argv: [process.execPath, SCRIPT_PATHS.combineAndVerify, gateCommand, '--in', integration, '--manifest', file, '--component', componentId, '--pr', pr], expectedExitCodes: [0, 1], transition: 'component-authorized -> delivery-authorized' }))
196
- if (operationPassed(gate) && operationExit(request) === undefined) {
197
- const argv = flow.mode === 'direct'
198
- ? ['gh', 'pr', 'merge', pr, '--repo', repository, '--squash']
199
- : flow.provider === 'mergify'
200
- ? ['gh', 'pr', 'comment', pr, '--repo', repository, '--body', '@mergifyio queue']
201
- : ['gh', 'pr', 'merge', pr, '--repo', repository, '--auto', '--squash']
202
- add(operation({ id: request, kind: 'mutation', cwd: repoRoot, argv, expectedExitCodes: [0], transition: 'component-authorized -> delivery-requested', authorizedBy: [{ operation: gate, exitCode: 0 }, { state: 'mergeShippable=true' }] }))
203
- } else if (!operationPassed(gate)) block(request, `${gate} ${operationExit(gate) === undefined ? 'has not produced exit 0' : `produced exit ${operationExit(gate)}, not 0`}; delivery authorization is absent`, 'component-authorized -> delivery-requested')
204
- }
205
- const observedAttempt = probe?.deliveryAttempts?.[componentId] ?? probe?.deliveryAttempts?.[unit.id]
206
- if (operationPassed(gate) && operationPassed(request) && object(observedAttempt)) {
207
- const payload = { generation: delivery.generation, headSha: head, attempt: observedAttempt }
208
- add(operation({ id: `record-component-delivery-attempt:${componentId}`, kind: 'mutation', cwd: repoRoot,
209
- argv: runStateArgv('record-component-delivery-attempt', runSlug, componentId, JSON.stringify(payload)), expectedExitCodes: [0], transition: 'delivery-requested -> component-delivery-attempt-recorded',
210
- authorizedBy: [{ operation: gate, exitCode: 0 }, { operation: request, exitCode: 0 }, { probe: `deliveryAttempts.${componentId}` }],
211
- }))
212
- } else block(`record-component-delivery-attempt:${componentId}`, 'delivery preflight and provider request have not both produced exit 0 with one observed provider payload', 'delivery-requested -> component-delivery-attempt-recorded')
213
- }
214
- }
215
-
216
- if (observed?.outcome === 'merged' && text(observed.mergedSha) && repository && pr && text(m.targetBranch)) {
217
- const proof = `delivery-proof:${componentId}`
218
- const queueArtifactReady = flow?.mode !== 'queue' || (text(observed.queueExecutionArtifactPath) && integration)
219
- const proofArgv = flow?.mode === 'queue'
220
- ? (queueArtifactReady ? [process.execPath, SCRIPT_PATHS.combineAndVerify, 'queue-delivered', '--in', integration, '--artifact', observed.queueExecutionArtifactPath, '--sha', observed.mergedSha, '--target', m.targetBranch, '--pr', pr] : null)
221
- : [process.execPath, SCRIPT_PATHS.combineAndVerify, 'delivered', '--sha', observed.mergedSha, '--target', m.targetBranch, '--pr', pr]
222
- if (!proofArgv) block(proof, 'queue delivery completion requires the integration verdict and queue-execution artifact path; direct ancestry proof cannot substitute for synthetic queue evidence', 'component-delivery-observed -> component-delivery-proved')
223
- else if (operationExit(proof) === undefined) add(operation({ id: proof, kind: 'inspection', cwd: repoRoot, argv: proofArgv, expectedExitCodes: [0, 1], transition: 'component-delivery-observed -> component-delivery-proved' }))
224
- if (attempts.length && operationPassed(proof)) {
225
- const queueExecution = object(observed.queueExecution)
226
- ? observed.queueExecution
227
- : text(observed.queueExecutionArtifactPath) && object(observed.queueExecutionArtifact)
228
- ? { artifact: observed.queueExecutionArtifactPath, evidence: observed.queueExecutionArtifact }
229
- : null
230
- const payload = {
231
- generation: delivery.generation,
232
- headSha: head,
233
- outcome: 'merged',
234
- mergedSha: observed.mergedSha,
235
- ...(observed.mergedAt ? { mergedAt: observed.mergedAt } : {}),
236
- ...(queueExecution ? { queueExecution } : {}),
237
- }
238
- add(operation({ id: `record-component-delivery-outcome:${componentId}`, kind: 'mutation', cwd: repoRoot,
239
- argv: runStateArgv('record-component-delivery-outcome', runSlug, componentId, JSON.stringify(payload)), expectedExitCodes: [0], transition: 'component-authorized -> delivered', authorizedBy: [{ operation: proof, exitCode: 0 }],
240
- }))
241
- } else if (!attempts.length) block(`record-component-delivery-outcome:${componentId}`, 'canonical component delivery requires its one provider attempt before merged outcome recording', 'component-authorized -> delivered')
242
- else block(`record-component-delivery-outcome:${componentId}`, `${proof} has not produced exit 0; ancestry/queue evidence authorization is absent`, 'component-authorized -> delivered')
243
- } else if (observed?.outcome === 'handed-back' && text(observed.blockedOn)) {
244
- const watch = `delivery-watch:${componentId}`
245
- const payload = { generation: delivery.generation, headSha: head, outcome: 'handed-back', blockedOn: observed.blockedOn }
246
- if (operationPassed(watch, 4)) add(operation({ id: `record-component-delivery-outcome:${componentId}`, kind: 'mutation', cwd: repoRoot,
247
- argv: runStateArgv('record-component-delivery-outcome', runSlug, componentId, JSON.stringify(payload)), expectedExitCodes: [0], transition: 'component-authorized -> handed-back', authorizedBy: [{ operation: watch, exitCode: 4 }],
248
- }))
249
- else block(`record-component-delivery-outcome:${componentId}`, `${watch} has not produced exit 4; hand-back authorization is absent`, 'component-authorized -> handed-back')
250
- }
251
- continue
252
- }
253
-
254
- const pr = numberRef(unit.pr)
255
- const head = text(unit.pushedSha) ? unit.pushedSha.trim() : null
256
- const flow = deliveryFlow(m)
257
- const exact = exactHeadPaths(unit)
258
- const attempts = Array.isArray(unit.delivery?.attempts) ? unit.delivery.attempts : []
259
- const observed = probe?.deliveryOutcomes?.[unit.id] ?? (() => {
260
- const state = probe?.prStates?.[unit.id]
261
- const oid = state?.mergeCommit?.oid
262
- return (state?.state === 'MERGED' || state?.mergedAt) && text(oid)
263
- ? { outcome: 'merged', mergedSha: oid, ...(state.mergedAt ? { mergedAt: state.mergedAt } : {}) }
264
- : null
265
- })()
266
- if (!repository) block(`delivery:${unit.id}`, 'repository identity is absent from the manifest and Git remote probe', 'merge-ready -> delivery-requested')
267
- if (!m.mergeShippable) block(`delivery-request:${unit.id}`, 'mergeShippable authorization is absent/off; no merge or enqueue mutation may be emitted', 'merge-ready -> delivery-requested')
268
- if (!pr) block(`delivery:${unit.id}`, 'a concrete PR number is required', 'merge-ready -> delivery-requested')
269
- if (!head) block(`delivery:${unit.id}`, 'units[].pushedSha is absent; exact HEAD delivery is required', 'merge-ready -> delivery-requested')
270
- if (!flow) block(`delivery:${unit.id}`, 'delivery flow is unresolved; deliveryMode and queueProvider must come from the probe', 'merge-ready -> delivery-requested')
271
-
272
- if (repository && pr && head && flow) {
273
- if (attempts.length) {
274
- const watch = `delivery-watch:${unit.id}`
275
- if (operationPassed(watch, 5)) block(watch, 'delivery watch exited 5 (exact-head CI failed); re-enter the fix/review loop and do not record a terminal outcome', 'delivery-attempt-recorded -> fresh-verdict-required')
276
- else if (flow.watchable && operationExit(watch) === undefined && !observed) {
277
- add(operation({ id: watch, kind: 'inspection', cwd: repoRoot, argv: [process.execPath, SCRIPT_PATHS.watchDelivery, '--pr', pr, '--repo', repository, ...(flow.provider ? ['--provider', flow.provider] : []), '--head', head], expectedExitCodes: [0, 4, 5], transition: 'delivery-attempt-recorded -> delivery-observed' }))
278
- }
279
- } else if (!exact) {
280
- block(`exact-head-ci:${unit.id}`, 'exact-head CI request/artifact paths are absent; delivery cannot start without the #889 adapter request', 'reviewed-head -> exact-head-ci-recorded')
281
- block(`delivery-request:${unit.id}`, 'exact-head CI authorization is absent', 'merge-ready -> delivery-requested')
282
- } else {
283
- const exactRun = `exact-head-ci:${unit.id}`
284
- const exactValidate = `exact-head-ci-validate:${unit.id}`
285
- const exactRunPassed = operationPassed(exactRun)
286
- const exactValidatePassed = exactRunPassed && operationPassed(exactValidate)
287
- if (operationExit(exactRun) === undefined) {
288
- add(operation({ id: exactRun, kind: 'mutation', cwd: repoRoot, argv: [process.execPath, SCRIPT_PATHS.exactHeadCi, 'run', '--input', exact.request, '--out', exact.artifact], expectedExitCodes: [0, 1], transition: 'reviewed-head -> exact-head-ci-recorded', authorizedBy: [{ state: `units[${unit.id}].exactHeadCi` }, { state: `units[${unit.id}].pushedSha=${head}` }] }))
289
- block(exactValidate, `${exactRun} has not produced exit 0`, 'exact-head-ci-recorded -> exact-head-ci-proved')
290
- } else if (!exactRunPassed) {
291
- block(exactValidate, `${exactRun} produced exit ${operationExit(exactRun)}, not 0`, 'exact-head-ci-recorded -> exact-head-ci-proved')
292
- } else if (operationExit(exactValidate) === undefined) {
293
- add(operation({ id: exactValidate, kind: 'inspection', cwd: repoRoot, argv: [process.execPath, SCRIPT_PATHS.exactHeadCi, 'validate', exact.artifact, '--head', head], expectedExitCodes: [0, 1], transition: 'exact-head-ci-recorded -> exact-head-ci-proved' }))
294
- } else if (!exactValidatePassed) {
295
- block(exactValidate, `${exactValidate} produced exit ${operationExit(exactValidate)}, not 0`, 'exact-head-ci-recorded -> exact-head-ci-proved')
296
- }
297
-
298
- const integration = verdictPath(m, unit, probe)
299
- const gate = `delivery-preflight:${unit.id}`
300
- const request = `delivery-request:${unit.id}`
301
- if (!m.mergeShippable) block(request, 'mergeShippable authorization is absent/off; hand back merge-ready and do not enqueue or merge', 'merge-ready -> delivery-requested')
302
- else if (!exactValidatePassed) block(request, `${exactRun} and ${exactValidate} have not both produced exit 0; exact-head authorization is absent`, 'merge-ready -> delivery-requested')
303
- else if (!integration) block(request, 'integration verdict path is absent; combine-and-verify mergeable must authorize delivery first', 'merge-ready -> delivery-requested')
304
- else {
305
- if (operationExit(gate) === undefined) add(operation({ id: gate, kind: 'inspection', cwd: repoRoot, argv: [process.execPath, SCRIPT_PATHS.combineAndVerify, 'mergeable', '--in', integration, '--manifest', file, '--pr', pr], expectedExitCodes: [0, 1], transition: 'merge-ready -> delivery-authorized' }))
306
- if (operationPassed(gate) && operationExit(request) === undefined) {
307
- const argv = flow.mode === 'direct'
308
- ? ['gh', 'pr', 'merge', pr, '--repo', repository, '--squash']
309
- : flow.provider === 'mergify'
310
- ? ['gh', 'pr', 'comment', pr, '--repo', repository, '--body', '@mergifyio queue']
311
- : ['gh', 'pr', 'merge', pr, '--repo', repository, '--auto', '--squash']
312
- add(operation({ id: request, kind: 'mutation', cwd: repoRoot, argv, expectedExitCodes: [0], transition: 'merge-ready -> delivery-requested', authorizedBy: [{ operation: gate, exitCode: 0 }, { state: 'mergeShippable=true' }] }))
313
- } else if (!operationPassed(gate)) block(request, `${gate} ${operationExit(gate) === undefined ? 'has not produced exit 0' : `produced exit ${operationExit(gate)}, not 0`}; delivery authorization is absent`, 'merge-ready -> delivery-requested')
314
- else if (!operationPassed(request)) block(request, `${request} produced exit ${operationExit(request)}, not 0`, 'merge-ready -> delivery-requested')
315
- }
316
-
317
- const observedAttempt = probe?.deliveryAttempts?.[unit.id]
318
- const requestPassed = operationPassed(gate) && operationPassed(request)
319
- if (requestPassed && object(observedAttempt)) add(operation({ id: `record-delivery-attempt:${unit.id}`, kind: 'mutation', cwd: repoRoot,
320
- argv: runStateArgv('record-delivery-attempt', runSlug, unit.id, JSON.stringify(observedAttempt)), expectedExitCodes: [0], transition: 'delivery-requested -> delivery-attempt-recorded',
321
- authorizedBy: [{ operation: gate, exitCode: 0 }, { operation: request, exitCode: 0 }, { probe: `deliveryAttempts.${unit.id}` }],
322
- }))
323
- else block(`record-delivery-attempt:${unit.id}`, `${gate} and ${request} have not both produced exit 0 with an observed provider payload; no attempt record is authorized`, 'delivery-requested -> delivery-attempt-recorded')
324
- }
325
- }
326
-
327
- if (observed?.outcome === 'merged' && text(observed.mergedSha) && repository && pr && text(m.targetBranch)) {
328
- const proof = `delivery-proof:${unit.id}`
329
- if (operationExit(proof) === undefined) add(operation({ id: proof, kind: 'inspection', cwd: repoRoot, argv: [process.execPath, SCRIPT_PATHS.combineAndVerify, 'delivered', '--sha', observed.mergedSha, '--target', m.targetBranch, '--pr', pr], expectedExitCodes: [0, 1], transition: 'delivery-observed -> delivery-proved' }))
330
- if (Array.isArray(unit.delivery?.attempts) && unit.delivery.attempts.length) {
331
- const payload = { outcome: 'merged', mergedSha: observed.mergedSha, ...(observed.mergedAt ? { mergedAt: observed.mergedAt } : {}) }
332
- if (operationPassed(proof)) add(operation({ id: `record-delivery-outcome:${unit.id}`, kind: 'mutation', cwd: repoRoot, argv: runStateArgv('record-delivery-outcome', runSlug, unit.id, JSON.stringify(payload)), expectedExitCodes: [0], transition: 'merge-ready -> delivered', authorizedBy: [{ operation: proof, exitCode: 0 }] }))
333
- else block(`record-delivery-outcome:${unit.id}`, `${proof} has not produced exit 0; ancestry authorization is absent`, 'merge-ready -> delivered')
334
- } else block(`record-delivery-outcome:${unit.id}`, 'typed delivery state requires a recorded attempt before merged outcome recording; do not invent an enqueue for an auto-merge observation', 'merge-ready -> delivered')
335
- } else if (observed?.outcome === 'handed-back' && text(observed.blockedOn)) {
336
- const payload = { outcome: 'handed-back', blockedOn: observed.blockedOn }
337
- const watch = `delivery-watch:${unit.id}`
338
- if (operationPassed(watch, 4)) add(operation({ id: `record-delivery-outcome:${unit.id}`, kind: 'mutation', cwd: repoRoot, argv: runStateArgv('record-delivery-outcome', runSlug, unit.id, JSON.stringify(payload)), expectedExitCodes: [0], transition: 'merge-ready -> handed-back', authorizedBy: [{ operation: watch, exitCode: 4 }] }))
339
- else block(`record-delivery-outcome:${unit.id}`, `${watch} has not produced exit 4; hand-back authorization is absent`, 'merge-ready -> handed-back')
340
- }
341
- }
342
-
343
- for (const unit of units) {
344
- if (unit.disposition === 'descoped' && terminalDisposition(unit) !== 'descoped') {
345
- if (unit.scopeDecision?.issueDisposition === 'closed-not-planned') block(`issue-disposition:${unit.id}`, 'scopeDecision is not a valid human descope; issue closure is not authorized', 'descoped -> issue-closed-not-planned')
346
- if (unit.scopeDecision?.prDisposition === 'closed-unmerged') block(`pr-disposition:${unit.id}`, 'scopeDecision is not a valid human descope; PR closure is not authorized', 'descoped -> pr-closed-unmerged')
347
- continue
348
- }
349
- if (terminalDisposition(unit) !== 'descoped' || !repository) continue
350
- const issue = numberRef(unit.id)
351
- const pr = numberRef(unit.pr)
352
- if (issue) add(operation({ id: `issue-disposition-check:${unit.id}`, kind: 'inspection', cwd: repoRoot, argv: ['gh', 'issue', 'view', issue, '--repo', repository, '--json', 'state'], expectedExitCodes: [0], transition: `${terminalDisposition(unit)} -> issue-disposition-observed` }))
353
- if (unit.scopeDecision?.issueDisposition === 'closed-not-planned' && issue && probe?.issueStates?.[unit.id]?.state === 'OPEN' && unit.scopeDecision?.decidedBy === 'human') {
354
- add(operation({ id: `issue-disposition:${unit.id}`, kind: 'mutation', cwd: repoRoot, argv: ['gh', 'issue', 'close', issue, '--repo', repository, '--reason', 'not planned'], expectedExitCodes: [0], transition: 'descoped -> issue-closed-not-planned', authorizedBy: [{ state: 'scopeDecision.decidedBy=human' }] }))
355
- } else if (unit.scopeDecision?.issueDisposition === 'closed-not-planned' && issue && probe?.issueStates?.[unit.id]?.state === 'OPEN') {
356
- block(`issue-disposition:${unit.id}`, 'scopeDecision.decidedBy is not human; issue closure is not authorized', 'descoped -> issue-closed-not-planned')
357
- }
358
- if (pr) add(operation({ id: `pr-disposition-check:${unit.id}`, kind: 'inspection', cwd: repoRoot, argv: ['gh', 'pr', 'view', pr, '--repo', repository, '--json', 'state'], expectedExitCodes: [0], transition: `${terminalDisposition(unit)} -> pr-disposition-observed` }))
359
- if (unit.scopeDecision?.prDisposition === 'closed-unmerged' && pr && probe?.prStates?.[unit.id]?.state === 'OPEN' && unit.scopeDecision?.decidedBy === 'human') {
360
- add(operation({ id: `pr-disposition:${unit.id}`, kind: 'mutation', cwd: repoRoot, argv: ['gh', 'pr', 'close', pr, '--repo', repository], expectedExitCodes: [0], transition: 'descoped -> pr-closed-unmerged', authorizedBy: [{ state: 'scopeDecision.decidedBy=human' }] }))
361
- } else if (unit.scopeDecision?.prDisposition === 'closed-unmerged' && pr && probe?.prStates?.[unit.id]?.state === 'OPEN') {
362
- block(`pr-disposition:${unit.id}`, 'scopeDecision.decidedBy is not human; PR closure is not authorized', 'descoped -> pr-closed-unmerged')
363
- }
364
- }
365
-
366
- if (runIsComplete(m)) {
367
- const reconciliation = unitPrReconciliationStatus(m)
368
- if (reconciliation.state === 'invalid') {
369
- block('reconcile-contained-unit-prs', reconciliation.reason, 'run-terminal -> unit-prs-reconciled')
370
- block('finalize', 'contained unit PR reconciliation state is invalid', 'run-terminal -> finalized')
371
- } else if (reconciliation.required && !reconciliation.complete) {
372
- add(operation({
373
- id: 'reconcile-contained-unit-prs',
374
- kind: 'mutation',
375
- cwd: repoRoot,
376
- argv: runStateArgv('reconcile-contained-unit-prs', runSlug),
377
- expectedExitCodes: [0, 1],
378
- transition: 'run-terminal -> unit-prs-reconciled',
379
- authorizedBy: [{ state: 'canonical combined component delivery is merged' }],
380
- detail: 'freshly verify exact unit heads, target ancestry, and the live component merge before closing superseded unit PRs',
381
- }))
382
- block('finalize', 'delivery-linked unit PRs require terminal reconciliation observations first', 'run-terminal -> finalized')
383
- } else {
384
- add(operation({
385
- id: 'finalize',
386
- kind: 'mutation',
387
- cwd: repoRoot,
388
- argv: runStateArgv('finalize', runSlug),
389
- expectedExitCodes: [0, 1],
390
- transition: 'run-terminal -> finalized',
391
- authorizedBy: [{ state: 'all units have typed terminal dispositions' }, ...(reconciliation.required ? [{ state: 'delivery-linked unit PRs have terminal reconciliation observations' }] : [])],
392
- detail: 'finalize owns and validates live terminal state, original-branch restoration, and workspace cleanup',
393
- }))
394
- }
395
- }
396
-
397
- return { version: 1, slug: runSlug, manifest: file, phase: runIsComplete(m) ? 'finalization' : 'active', actions, inspections, mutations, blocked }
398
- }
399
-
400
- export function renderNextOperations(result = {}) {
401
- const out = [`NEXT OPERATIONS ${result.slug ?? '(no slug)'} phase ${result.phase ?? 'unknown'}`]
402
- const section = (title, rows) => {
403
- out.push('', ` ${title}`)
404
- if (!rows?.length) { out.push(' (none)'); return }
405
- for (const row of rows) {
406
- out.push(` ${row.id} ${row.authorizes?.transition ?? 'no transition'}`)
407
- out.push(` cwd: ${row.cwd}`)
408
- out.push(` argv: ${JSON.stringify(row.argv)}`)
409
- out.push(` expected exits: ${row.expectedExitCodes.join(', ')}`)
410
- if (row.authorizedBy?.length) out.push(` authorized by: ${JSON.stringify(row.authorizedBy)}`)
411
- }
412
- }
413
- section('INSPECTIONS (read-only)', result.inspections)
414
- section('MUTATIONS (state/auth present)', result.mutations)
415
- out.push('', ' BLOCKED MUTATIONS')
416
- if (!result.blocked?.length) out.push(' (none)')
417
- else for (const row of result.blocked) out.push(` ${row.id} ${row.authorizes?.transition ?? 'no transition'} — ${row.reason}`)
418
- return out.join('\n')
419
- }