@msn-control/liftoff 0.10.4 → 0.11.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 (700) hide show
  1. package/DEVELOPER.md +238 -77
  2. package/README.md +38 -33
  3. package/assets/supported-stack.json +3 -136
  4. package/dist/adapters/azure/governance-assessment.d.ts +2 -0
  5. package/dist/adapters/azure/governance-assessment.js +21 -0
  6. package/dist/adapters/azure/governance-assessment.js.map +1 -0
  7. package/dist/adapters/filesystem/atomic-write.d.ts +2 -0
  8. package/dist/adapters/filesystem/atomic-write.js +74 -0
  9. package/dist/adapters/filesystem/atomic-write.js.map +1 -0
  10. package/dist/adapters/filesystem/errors.d.ts +2 -0
  11. package/dist/adapters/filesystem/errors.js +8 -0
  12. package/dist/adapters/filesystem/errors.js.map +1 -0
  13. package/dist/adapters/filesystem/manifest-file.d.ts +1 -0
  14. package/dist/adapters/filesystem/manifest-file.js +21 -0
  15. package/dist/adapters/filesystem/manifest-file.js.map +1 -0
  16. package/dist/adapters/filesystem/project-config.d.ts +4 -0
  17. package/dist/adapters/filesystem/project-config.js +157 -0
  18. package/dist/adapters/filesystem/project-config.js.map +1 -0
  19. package/dist/adapters/filesystem/project-discovery.d.ts +2 -0
  20. package/dist/adapters/filesystem/project-discovery.js +30 -0
  21. package/dist/adapters/filesystem/project-discovery.js.map +1 -0
  22. package/dist/adapters/filesystem/project-files.d.ts +6 -0
  23. package/dist/adapters/filesystem/project-files.js +68 -0
  24. package/dist/adapters/filesystem/project-files.js.map +1 -0
  25. package/dist/adapters/filesystem/project-lock.d.ts +8 -0
  26. package/dist/adapters/filesystem/project-lock.js +164 -0
  27. package/dist/adapters/filesystem/project-lock.js.map +1 -0
  28. package/dist/adapters/filesystem/project-paths.d.ts +2 -0
  29. package/dist/adapters/filesystem/project-paths.js +60 -0
  30. package/dist/adapters/filesystem/project-paths.js.map +1 -0
  31. package/dist/adapters/filesystem/project-transaction.d.ts +24 -0
  32. package/dist/adapters/filesystem/project-transaction.js +177 -0
  33. package/dist/adapters/filesystem/project-transaction.js.map +1 -0
  34. package/dist/adapters/filesystem/reviewed-update-transaction.d.ts +53 -0
  35. package/dist/adapters/filesystem/reviewed-update-transaction.js +864 -0
  36. package/dist/adapters/filesystem/reviewed-update-transaction.js.map +1 -0
  37. package/dist/adapters/filesystem/update-previews.d.ts +50 -0
  38. package/dist/adapters/filesystem/update-previews.js +613 -0
  39. package/dist/adapters/filesystem/update-previews.js.map +1 -0
  40. package/dist/adapters/git/governance-assessment.d.ts +22 -0
  41. package/dist/adapters/git/governance-assessment.js +161 -0
  42. package/dist/adapters/git/governance-assessment.js.map +1 -0
  43. package/dist/adapters/github/governance-assessment.d.ts +2 -0
  44. package/dist/adapters/github/governance-assessment.js +18 -0
  45. package/dist/adapters/github/governance-assessment.js.map +1 -0
  46. package/dist/adapters/packaged-assets/package-root.d.ts +3 -0
  47. package/dist/adapters/packaged-assets/package-root.js +21 -0
  48. package/dist/adapters/packaged-assets/package-root.js.map +1 -0
  49. package/dist/adapters/packaged-assets/supported-stack.d.ts +4 -0
  50. package/dist/adapters/packaged-assets/supported-stack.js +18 -0
  51. package/dist/adapters/packaged-assets/supported-stack.js.map +1 -0
  52. package/dist/adapters/packaged-assets/template-assets.d.ts +23 -0
  53. package/dist/adapters/packaged-assets/template-assets.js +36 -0
  54. package/dist/adapters/packaged-assets/template-assets.js.map +1 -0
  55. package/dist/adapters/process/shell-command.d.ts +6 -0
  56. package/dist/adapters/process/shell-command.js +27 -0
  57. package/dist/adapters/process/shell-command.js.map +1 -0
  58. package/dist/application/context.d.ts +25 -0
  59. package/dist/application/context.js +2 -0
  60. package/dist/application/context.js.map +1 -0
  61. package/dist/application/diagnose/doctor.d.ts +21 -0
  62. package/dist/application/diagnose/doctor.js +580 -0
  63. package/dist/application/diagnose/doctor.js.map +1 -0
  64. package/dist/application/diagnose/generated-project.d.ts +1 -0
  65. package/dist/application/diagnose/generated-project.js +105 -0
  66. package/dist/application/diagnose/generated-project.js.map +1 -0
  67. package/dist/application/diagnose/validate.d.ts +6 -0
  68. package/dist/application/diagnose/validate.js +27 -0
  69. package/dist/application/diagnose/validate.js.map +1 -0
  70. package/dist/application/initialize/fixture.d.ts +2 -0
  71. package/dist/application/initialize/fixture.js +39 -0
  72. package/dist/application/initialize/fixture.js.map +1 -0
  73. package/dist/application/initialize/inputs.d.ts +3 -0
  74. package/dist/application/initialize/inputs.js +35 -0
  75. package/dist/application/initialize/inputs.js.map +1 -0
  76. package/dist/application/initialize/use-case.d.ts +23 -0
  77. package/dist/application/initialize/use-case.js +360 -0
  78. package/dist/application/initialize/use-case.js.map +1 -0
  79. package/dist/application/migrate/use-case.d.ts +7 -0
  80. package/dist/application/migrate/use-case.js +378 -0
  81. package/dist/application/migrate/use-case.js.map +1 -0
  82. package/dist/application/project/catalog.d.ts +46 -0
  83. package/dist/application/project/catalog.js +12 -0
  84. package/dist/application/project/catalog.js.map +1 -0
  85. package/dist/application/project/manifest.d.ts +2 -0
  86. package/dist/application/project/manifest.js +29 -0
  87. package/dist/application/project/manifest.js.map +1 -0
  88. package/dist/application/project/planning.d.ts +6 -0
  89. package/dist/application/project/planning.js +12 -0
  90. package/dist/application/project/planning.js.map +1 -0
  91. package/dist/application/update/approval.d.ts +39 -0
  92. package/dist/application/update/approval.js +54 -0
  93. package/dist/application/update/approval.js.map +1 -0
  94. package/dist/application/update/command-guidance.d.ts +2 -0
  95. package/dist/application/update/command-guidance.js +14 -0
  96. package/dist/application/update/command-guidance.js.map +1 -0
  97. package/dist/application/update/inspection.d.ts +37 -0
  98. package/dist/application/update/inspection.js +235 -0
  99. package/dist/application/update/inspection.js.map +1 -0
  100. package/dist/application/update/migration-runtime.d.ts +14 -0
  101. package/dist/application/update/migration-runtime.js +178 -0
  102. package/dist/application/update/migration-runtime.js.map +1 -0
  103. package/dist/application/update/output.d.ts +134 -0
  104. package/dist/application/update/output.js +182 -0
  105. package/dist/application/update/output.js.map +1 -0
  106. package/dist/application/update/planning.d.ts +40 -0
  107. package/dist/application/update/planning.js +293 -0
  108. package/dist/application/update/planning.js.map +1 -0
  109. package/dist/application/update/preview.d.ts +49 -0
  110. package/dist/application/update/preview.js +220 -0
  111. package/dist/application/update/preview.js.map +1 -0
  112. package/dist/application/update/protected-source.d.ts +25 -0
  113. package/dist/application/update/protected-source.js +81 -0
  114. package/dist/application/update/protected-source.js.map +1 -0
  115. package/dist/application/update/reporting.d.ts +42 -0
  116. package/dist/application/update/reporting.js +326 -0
  117. package/dist/application/update/reporting.js.map +1 -0
  118. package/dist/application/update/revalidation-plan.d.ts +22 -0
  119. package/dist/application/update/revalidation-plan.js +108 -0
  120. package/dist/application/update/revalidation-plan.js.map +1 -0
  121. package/dist/application/update/revalidation.d.ts +70 -0
  122. package/dist/application/update/revalidation.js +337 -0
  123. package/dist/application/update/revalidation.js.map +1 -0
  124. package/dist/application/update/review-plan.d.ts +25 -0
  125. package/dist/application/update/review-plan.js +89 -0
  126. package/dist/application/update/review-plan.js.map +1 -0
  127. package/dist/application/update/transaction-approval.d.ts +29 -0
  128. package/dist/application/update/transaction-approval.js +82 -0
  129. package/dist/application/update/transaction-approval.js.map +1 -0
  130. package/dist/application/update/use-case.d.ts +9 -0
  131. package/dist/application/update/use-case.js +271 -0
  132. package/dist/application/update/use-case.js.map +1 -0
  133. package/dist/application/update/write-plan.d.ts +15 -0
  134. package/dist/application/update/write-plan.js +115 -0
  135. package/dist/application/update/write-plan.js.map +1 -0
  136. package/dist/application/upgrade/use-case.d.ts +6 -0
  137. package/dist/application/upgrade/use-case.js +82 -0
  138. package/dist/application/upgrade/use-case.js.map +1 -0
  139. package/dist/args.d.ts +5 -65
  140. package/dist/args.js +4 -472
  141. package/dist/args.js.map +1 -1
  142. package/dist/artifact-lifecycle.d.ts +1 -21
  143. package/dist/artifact-lifecycle.js +1 -75
  144. package/dist/artifact-lifecycle.js.map +1 -1
  145. package/dist/catalogs.d.ts +1 -50
  146. package/dist/catalogs.js +1 -472
  147. package/dist/catalogs.js.map +1 -1
  148. package/dist/cli/args/contracts.d.ts +51 -0
  149. package/dist/cli/args/contracts.js +2 -0
  150. package/dist/cli/args/contracts.js.map +1 -0
  151. package/dist/cli/args/definitions.d.ts +6 -0
  152. package/dist/cli/args/definitions.js +188 -0
  153. package/dist/cli/args/definitions.js.map +1 -0
  154. package/dist/cli/args/help.d.ts +5 -0
  155. package/dist/cli/args/help.js +142 -0
  156. package/dist/cli/args/help.js.map +1 -0
  157. package/dist/cli/args/parser.d.ts +5 -0
  158. package/dist/cli/args/parser.js +160 -0
  159. package/dist/cli/args/parser.js.map +1 -0
  160. package/dist/cli/args/readers.d.ts +4 -0
  161. package/dist/cli/args/readers.js +19 -0
  162. package/dist/cli/args/readers.js.map +1 -0
  163. package/dist/cli/commands/diagnose.d.ts +4 -0
  164. package/dist/cli/commands/diagnose.js +12 -0
  165. package/dist/cli/commands/diagnose.js.map +1 -0
  166. package/dist/cli/commands/dispatch.d.ts +3 -0
  167. package/dist/cli/commands/dispatch.js +94 -0
  168. package/dist/cli/commands/dispatch.js.map +1 -0
  169. package/dist/cli/commands/help.d.ts +3 -0
  170. package/dist/cli/commands/help.js +32 -0
  171. package/dist/cli/commands/help.js.map +1 -0
  172. package/dist/cli/commands/helpers.d.ts +8 -0
  173. package/dist/cli/commands/helpers.js +97 -0
  174. package/dist/cli/commands/helpers.js.map +1 -0
  175. package/dist/cli/commands/initialize.d.ts +3 -0
  176. package/dist/cli/commands/initialize.js +7 -0
  177. package/dist/cli/commands/initialize.js.map +1 -0
  178. package/dist/cli/commands/migrate.d.ts +3 -0
  179. package/dist/cli/commands/migrate.js +8 -0
  180. package/dist/cli/commands/migrate.js.map +1 -0
  181. package/dist/cli/commands/plan.d.ts +3 -0
  182. package/dist/cli/commands/plan.js +34 -0
  183. package/dist/cli/commands/plan.js.map +1 -0
  184. package/dist/cli/commands/reference.d.ts +5 -0
  185. package/dist/cli/commands/reference.js +43 -0
  186. package/dist/cli/commands/reference.js.map +1 -0
  187. package/dist/cli/commands/update.d.ts +3 -0
  188. package/dist/cli/commands/update.js +10 -0
  189. package/dist/cli/commands/update.js.map +1 -0
  190. package/dist/cli/commands/upgrade.d.ts +3 -0
  191. package/dist/cli/commands/upgrade.js +7 -0
  192. package/dist/cli/commands/upgrade.js.map +1 -0
  193. package/dist/cli/context.d.ts +1 -0
  194. package/dist/cli/context.js +2 -0
  195. package/dist/cli/context.js.map +1 -0
  196. package/dist/cli/project-options.d.ts +3 -0
  197. package/dist/cli/project-options.js +41 -0
  198. package/dist/cli/project-options.js.map +1 -0
  199. package/dist/cli.d.ts +3 -3
  200. package/dist/cli.js +2 -2
  201. package/dist/cli.js.map +1 -1
  202. package/dist/commands.d.ts +4 -35
  203. package/dist/commands.js +3 -2969
  204. package/dist/commands.js.map +1 -1
  205. package/dist/container-validation.d.ts +1 -1
  206. package/dist/domain/governance/activation/approvals.d.ts +351 -0
  207. package/dist/domain/governance/activation/approvals.js +528 -0
  208. package/dist/domain/governance/activation/approvals.js.map +1 -0
  209. package/dist/domain/governance/activation/canonical-json.d.ts +4 -0
  210. package/dist/domain/governance/activation/canonical-json.js +39 -0
  211. package/dist/domain/governance/activation/canonical-json.js.map +1 -0
  212. package/dist/domain/governance/activation/capabilities.d.ts +7 -0
  213. package/dist/domain/governance/activation/capabilities.js +33 -0
  214. package/dist/domain/governance/activation/capabilities.js.map +1 -0
  215. package/dist/domain/governance/activation/evidence.d.ts +63 -0
  216. package/dist/domain/governance/activation/evidence.js +444 -0
  217. package/dist/domain/governance/activation/evidence.js.map +1 -0
  218. package/dist/domain/governance/activation/graph.d.ts +397 -0
  219. package/dist/domain/governance/activation/graph.js +441 -0
  220. package/dist/domain/governance/activation/graph.js.map +1 -0
  221. package/dist/domain/governance/activation/inputs.d.ts +25 -0
  222. package/dist/domain/governance/activation/inputs.js +63 -0
  223. package/dist/domain/governance/activation/inputs.js.map +1 -0
  224. package/dist/domain/governance/activation/operations.d.ts +19 -0
  225. package/dist/domain/governance/activation/operations.js +181 -0
  226. package/dist/domain/governance/activation/operations.js.map +1 -0
  227. package/dist/domain/governance/activation/readiness.d.ts +27 -0
  228. package/dist/domain/governance/activation/readiness.js +269 -0
  229. package/dist/domain/governance/activation/readiness.js.map +1 -0
  230. package/dist/domain/governance/activation/types.d.ts +399 -0
  231. package/dist/domain/governance/activation/types.js +109 -0
  232. package/dist/domain/governance/activation/types.js.map +1 -0
  233. package/dist/domain/governance/activation/validators.d.ts +22 -0
  234. package/dist/domain/governance/activation/validators.js +1347 -0
  235. package/dist/domain/governance/activation/validators.js.map +1 -0
  236. package/dist/domain/governance/assessment/catalog.d.ts +14 -0
  237. package/dist/domain/governance/assessment/catalog.js +105 -0
  238. package/dist/domain/governance/assessment/catalog.js.map +1 -0
  239. package/dist/domain/governance/assessment/errors.d.ts +10 -0
  240. package/dist/domain/governance/assessment/errors.js +22 -0
  241. package/dist/domain/governance/assessment/errors.js.map +1 -0
  242. package/dist/domain/governance/assessment/index.d.ts +8 -0
  243. package/dist/domain/governance/assessment/index.js +9 -0
  244. package/dist/domain/governance/assessment/index.js.map +1 -0
  245. package/dist/domain/governance/assessment/live-normalize.d.ts +19 -0
  246. package/dist/domain/governance/assessment/live-normalize.js +403 -0
  247. package/dist/domain/governance/assessment/live-normalize.js.map +1 -0
  248. package/dist/domain/governance/assessment/predicates.d.ts +36 -0
  249. package/dist/domain/governance/assessment/predicates.js +545 -0
  250. package/dist/domain/governance/assessment/predicates.js.map +1 -0
  251. package/dist/domain/governance/assessment/report.d.ts +23 -0
  252. package/dist/domain/governance/assessment/report.js +266 -0
  253. package/dist/domain/governance/assessment/report.js.map +1 -0
  254. package/dist/domain/governance/assessment/sanitize.d.ts +8 -0
  255. package/dist/domain/governance/assessment/sanitize.js +60 -0
  256. package/dist/domain/governance/assessment/sanitize.js.map +1 -0
  257. package/dist/domain/governance/assessment/types.d.ts +174 -0
  258. package/dist/domain/governance/assessment/types.js +13 -0
  259. package/dist/domain/governance/assessment/types.js.map +1 -0
  260. package/dist/domain/governance/assessment/yaml.d.ts +6 -0
  261. package/dist/domain/governance/assessment/yaml.js +30 -0
  262. package/dist/domain/governance/assessment/yaml.js.map +1 -0
  263. package/dist/domain/governance/policy/identity.d.ts +64 -0
  264. package/dist/domain/governance/policy/identity.js +112 -0
  265. package/dist/domain/governance/policy/identity.js.map +1 -0
  266. package/dist/domain/migration/dependencies.d.ts +7 -0
  267. package/dist/domain/migration/dependencies.js +221 -0
  268. package/dist/domain/migration/dependencies.js.map +1 -0
  269. package/dist/domain/migration/inventory.d.ts +3 -0
  270. package/dist/domain/migration/inventory.js +15 -0
  271. package/dist/domain/migration/inventory.js.map +1 -0
  272. package/dist/domain/project/artifact-lifecycle.d.ts +69 -0
  273. package/dist/domain/project/artifact-lifecycle.js +137 -0
  274. package/dist/domain/project/artifact-lifecycle.js.map +1 -0
  275. package/dist/domain/project/catalog.d.ts +48 -0
  276. package/dist/domain/project/catalog.js +507 -0
  277. package/dist/domain/project/catalog.js.map +1 -0
  278. package/dist/domain/project/contracts.d.ts +247 -0
  279. package/dist/domain/project/contracts.js +2 -0
  280. package/dist/domain/project/contracts.js.map +1 -0
  281. package/dist/domain/project/errors.d.ts +3 -0
  282. package/dist/domain/project/errors.js +7 -0
  283. package/dist/domain/project/errors.js.map +1 -0
  284. package/dist/domain/project/infrastructure-layout.d.ts +26 -0
  285. package/dist/domain/project/infrastructure-layout.js +125 -0
  286. package/dist/domain/project/infrastructure-layout.js.map +1 -0
  287. package/dist/domain/project/inputs.d.ts +21 -0
  288. package/dist/domain/project/inputs.js +41 -0
  289. package/dist/domain/project/inputs.js.map +1 -0
  290. package/dist/domain/project/manifest/artifacts.d.ts +8 -0
  291. package/dist/domain/project/manifest/artifacts.js +150 -0
  292. package/dist/domain/project/manifest/artifacts.js.map +1 -0
  293. package/dist/domain/project/manifest/context.d.ts +13 -0
  294. package/dist/domain/project/manifest/context.js +2 -0
  295. package/dist/domain/project/manifest/context.js.map +1 -0
  296. package/dist/domain/project/manifest/fields.d.ts +6 -0
  297. package/dist/domain/project/manifest/fields.js +36 -0
  298. package/dist/domain/project/manifest/fields.js.map +1 -0
  299. package/dist/domain/project/manifest/governance.d.ts +8 -0
  300. package/dist/domain/project/manifest/governance.js +161 -0
  301. package/dist/domain/project/manifest/governance.js.map +1 -0
  302. package/dist/domain/project/manifest/project-identity.d.ts +6 -0
  303. package/dist/domain/project/manifest/project-identity.js +273 -0
  304. package/dist/domain/project/manifest/project-identity.js.map +1 -0
  305. package/dist/domain/project/manifest/reader.d.ts +9 -0
  306. package/dist/domain/project/manifest/reader.js +141 -0
  307. package/dist/domain/project/manifest/reader.js.map +1 -0
  308. package/dist/domain/project/paths.d.ts +2 -0
  309. package/dist/domain/project/paths.js +29 -0
  310. package/dist/domain/project/paths.js.map +1 -0
  311. package/dist/domain/project/planning.d.ts +19 -0
  312. package/dist/domain/project/planning.js +329 -0
  313. package/dist/domain/project/planning.js.map +1 -0
  314. package/dist/domain/project/retired-workload.d.ts +4 -0
  315. package/dist/domain/project/retired-workload.js +18 -0
  316. package/dist/domain/project/retired-workload.js.map +1 -0
  317. package/dist/domain/project/reviewed-update-artifacts.d.ts +2 -0
  318. package/dist/domain/project/reviewed-update-artifacts.js +3 -0
  319. package/dist/domain/project/reviewed-update-artifacts.js.map +1 -0
  320. package/dist/domain/project/supported-stack.d.ts +113 -0
  321. package/dist/domain/project/supported-stack.js +426 -0
  322. package/dist/domain/project/supported-stack.js.map +1 -0
  323. package/dist/domain/workstation/constraints.d.ts +6 -0
  324. package/dist/domain/workstation/constraints.js +8 -0
  325. package/dist/domain/workstation/constraints.js.map +1 -0
  326. package/dist/file-system.d.ts +10 -44
  327. package/dist/file-system.js +9 -1245
  328. package/dist/file-system.js.map +1 -1
  329. package/dist/framework-adapters.d.ts +1 -1
  330. package/dist/framework-validation.d.ts +1 -1
  331. package/dist/framework-validation.js +1 -1
  332. package/dist/framework-validation.js.map +1 -1
  333. package/dist/genai-templates.d.ts +2 -9
  334. package/dist/genai-templates.js +4 -5
  335. package/dist/genai-templates.js.map +1 -1
  336. package/dist/generators/common/artifacts.d.ts +6 -0
  337. package/dist/generators/common/artifacts.js +30 -0
  338. package/dist/generators/common/artifacts.js.map +1 -0
  339. package/dist/generators/common/base.d.ts +17 -0
  340. package/dist/generators/common/base.js +425 -0
  341. package/dist/generators/common/base.js.map +1 -0
  342. package/dist/generators/common/dotenv-validation.d.ts +2 -0
  343. package/dist/generators/common/dotenv-validation.js +80 -0
  344. package/dist/generators/common/dotenv-validation.js.map +1 -0
  345. package/dist/generators/common/environments.d.ts +3 -0
  346. package/dist/generators/common/environments.js +15 -0
  347. package/dist/generators/common/environments.js.map +1 -0
  348. package/dist/generators/common/frontend.d.ts +15 -0
  349. package/dist/generators/common/frontend.js +166 -0
  350. package/dist/generators/common/frontend.js.map +1 -0
  351. package/dist/generators/common/python-settings.d.ts +2 -0
  352. package/dist/generators/common/python-settings.js +70 -0
  353. package/dist/generators/common/python-settings.js.map +1 -0
  354. package/dist/generators/common/spec-workflow.d.ts +28 -0
  355. package/dist/generators/common/spec-workflow.js +567 -0
  356. package/dist/generators/common/spec-workflow.js.map +1 -0
  357. package/dist/generators/common/values.d.ts +13 -0
  358. package/dist/generators/common/values.js +16 -0
  359. package/dist/generators/common/values.js.map +1 -0
  360. package/dist/generators/containers/compose.d.ts +7 -0
  361. package/dist/generators/containers/compose.js +196 -0
  362. package/dist/generators/containers/compose.js.map +1 -0
  363. package/dist/generators/containers/context.d.ts +1 -0
  364. package/dist/generators/containers/context.js +43 -0
  365. package/dist/generators/containers/context.js.map +1 -0
  366. package/dist/generators/containers/images.d.ts +7 -0
  367. package/dist/generators/containers/images.js +119 -0
  368. package/dist/generators/containers/images.js.map +1 -0
  369. package/dist/generators/context.d.ts +42 -0
  370. package/dist/generators/context.js +38 -0
  371. package/dist/generators/context.js.map +1 -0
  372. package/dist/generators/genai/backend.d.ts +20 -0
  373. package/dist/generators/genai/backend.js +667 -0
  374. package/dist/generators/genai/backend.js.map +1 -0
  375. package/dist/generators/genai/configuration.d.ts +3 -0
  376. package/dist/generators/genai/configuration.js +43 -0
  377. package/dist/generators/genai/configuration.js.map +1 -0
  378. package/dist/generators/genai/database.d.ts +7 -0
  379. package/dist/generators/genai/database.js +71 -0
  380. package/dist/generators/genai/database.js.map +1 -0
  381. package/dist/generators/genai/functions.d.ts +15 -0
  382. package/dist/generators/genai/functions.js +156 -0
  383. package/dist/generators/genai/functions.js.map +1 -0
  384. package/dist/generators/genai/index.d.ts +8 -0
  385. package/dist/generators/genai/index.js +11 -0
  386. package/dist/generators/genai/index.js.map +1 -0
  387. package/dist/generators/genai/patterns.d.ts +9 -0
  388. package/dist/generators/genai/patterns.js +309 -0
  389. package/dist/generators/genai/patterns.js.map +1 -0
  390. package/dist/generators/infrastructure/azure.d.ts +18 -0
  391. package/dist/generators/infrastructure/azure.js +655 -0
  392. package/dist/generators/infrastructure/azure.js.map +1 -0
  393. package/dist/generators/infrastructure/names.d.ts +23 -0
  394. package/dist/generators/infrastructure/names.js +51 -0
  395. package/dist/generators/infrastructure/names.js.map +1 -0
  396. package/dist/generators/standard/configuration.d.ts +3 -0
  397. package/dist/generators/standard/configuration.js +26 -0
  398. package/dist/generators/standard/configuration.js.map +1 -0
  399. package/dist/generators/standard/go.d.ts +17 -0
  400. package/dist/generators/standard/go.js +338 -0
  401. package/dist/generators/standard/go.js.map +1 -0
  402. package/dist/generators/standard/index.d.ts +12 -0
  403. package/dist/generators/standard/index.js +12 -0
  404. package/dist/generators/standard/index.js.map +1 -0
  405. package/dist/generators/standard/node.d.ts +20 -0
  406. package/dist/generators/standard/node.js +314 -0
  407. package/dist/generators/standard/node.js.map +1 -0
  408. package/dist/generators/standard/python.d.ts +18 -0
  409. package/dist/generators/standard/python.js +191 -0
  410. package/dist/generators/standard/python.js.map +1 -0
  411. package/dist/go-template-assets.js +3 -3
  412. package/dist/go-template-assets.js.map +1 -1
  413. package/dist/governance-activation/activation-state.d.ts +1 -1
  414. package/dist/governance-activation/activation-state.js +49 -9
  415. package/dist/governance-activation/activation-state.js.map +1 -1
  416. package/dist/governance-activation/approvals.d.ts +1 -188
  417. package/dist/governance-activation/approvals.js +1 -507
  418. package/dist/governance-activation/approvals.js.map +1 -1
  419. package/dist/governance-activation/canonical-json.d.ts +1 -3
  420. package/dist/governance-activation/canonical-json.js +1 -35
  421. package/dist/governance-activation/canonical-json.js.map +1 -1
  422. package/dist/governance-activation/commands.d.ts +6 -1
  423. package/dist/governance-activation/commands.js +135 -107
  424. package/dist/governance-activation/commands.js.map +1 -1
  425. package/dist/governance-activation/compatibility.d.ts +30 -5
  426. package/dist/governance-activation/compatibility.js +74 -8
  427. package/dist/governance-activation/compatibility.js.map +1 -1
  428. package/dist/governance-activation/credentials.d.ts +3 -3
  429. package/dist/governance-activation/credentials.js +5 -5
  430. package/dist/governance-activation/credentials.js.map +1 -1
  431. package/dist/governance-activation/doctor.d.ts +1 -1
  432. package/dist/governance-activation/doctor.js +59 -100
  433. package/dist/governance-activation/doctor.js.map +1 -1
  434. package/dist/governance-activation/evidence.d.ts +1 -49
  435. package/dist/governance-activation/evidence.js +1 -245
  436. package/dist/governance-activation/evidence.js.map +1 -1
  437. package/dist/governance-activation/git-ignore.d.ts +9 -0
  438. package/dist/governance-activation/git-ignore.js +76 -0
  439. package/dist/governance-activation/git-ignore.js.map +1 -0
  440. package/dist/governance-activation/graph.d.ts +1 -397
  441. package/dist/governance-activation/graph.js +1 -440
  442. package/dist/governance-activation/graph.js.map +1 -1
  443. package/dist/governance-activation/historical-state.d.ts +81 -0
  444. package/dist/governance-activation/historical-state.js +817 -0
  445. package/dist/governance-activation/historical-state.js.map +1 -0
  446. package/dist/governance-activation/history-contracts.d.ts +109 -0
  447. package/dist/governance-activation/history-contracts.js +303 -0
  448. package/dist/governance-activation/history-contracts.js.map +1 -0
  449. package/dist/governance-activation/identity.d.ts +1 -35
  450. package/dist/governance-activation/identity.js +1 -88
  451. package/dist/governance-activation/identity.js.map +1 -1
  452. package/dist/governance-activation/index.d.ts +3 -0
  453. package/dist/governance-activation/index.js +3 -0
  454. package/dist/governance-activation/index.js.map +1 -1
  455. package/dist/governance-activation/inputs.d.ts +10 -0
  456. package/dist/governance-activation/inputs.js +158 -0
  457. package/dist/governance-activation/inputs.js.map +1 -0
  458. package/dist/governance-activation/migration-history.d.ts +97 -0
  459. package/dist/governance-activation/migration-history.js +388 -0
  460. package/dist/governance-activation/migration-history.js.map +1 -0
  461. package/dist/governance-activation/migration.d.ts +4 -3
  462. package/dist/governance-activation/migration.js +14 -6
  463. package/dist/governance-activation/migration.js.map +1 -1
  464. package/dist/governance-activation/phase-bootstrap-state.d.ts +3 -0
  465. package/dist/governance-activation/phase-bootstrap-state.js +128 -0
  466. package/dist/governance-activation/phase-bootstrap-state.js.map +1 -0
  467. package/dist/governance-activation/phase-discovery.d.ts +2 -0
  468. package/dist/governance-activation/phase-discovery.js +88 -0
  469. package/dist/governance-activation/phase-discovery.js.map +1 -0
  470. package/dist/governance-activation/phase-governance.d.ts +4 -0
  471. package/dist/governance-activation/phase-governance.js +141 -0
  472. package/dist/governance-activation/phase-governance.js.map +1 -0
  473. package/dist/governance-activation/phase-publication.d.ts +8 -0
  474. package/dist/governance-activation/phase-publication.js +318 -0
  475. package/dist/governance-activation/phase-publication.js.map +1 -0
  476. package/dist/governance-activation/proof-records.d.ts +3 -0
  477. package/dist/governance-activation/proof-records.js +78 -0
  478. package/dist/governance-activation/proof-records.js.map +1 -0
  479. package/dist/governance-activation/read-only.d.ts +30 -0
  480. package/dist/governance-activation/read-only.js +33 -0
  481. package/dist/governance-activation/read-only.js.map +1 -0
  482. package/dist/governance-activation/readiness.d.ts +1 -25
  483. package/dist/governance-activation/readiness.js +1 -222
  484. package/dist/governance-activation/readiness.js.map +1 -1
  485. package/dist/governance-activation/reconciliation.d.ts +1 -1
  486. package/dist/governance-activation/reconciliation.js +3 -3
  487. package/dist/governance-activation/reconciliation.js.map +1 -1
  488. package/dist/governance-activation/release-integrity.d.ts +1 -1
  489. package/dist/governance-activation/release-integrity.js +2 -2
  490. package/dist/governance-activation/release-integrity.js.map +1 -1
  491. package/dist/governance-activation/seed-lifecycle.d.ts +36 -6
  492. package/dist/governance-activation/seed-lifecycle.js +444 -77
  493. package/dist/governance-activation/seed-lifecycle.js.map +1 -1
  494. package/dist/governance-activation/source-of-truth.d.ts +5 -3
  495. package/dist/governance-activation/source-of-truth.js +32 -52
  496. package/dist/governance-activation/source-of-truth.js.map +1 -1
  497. package/dist/governance-activation/spec-kit-seed.d.ts +9 -0
  498. package/dist/governance-activation/spec-kit-seed.js +43 -0
  499. package/dist/governance-activation/spec-kit-seed.js.map +1 -0
  500. package/dist/governance-activation/task-projection.d.ts +1 -1
  501. package/dist/governance-activation/transition-planning.d.ts +17 -0
  502. package/dist/governance-activation/transition-planning.js +259 -0
  503. package/dist/governance-activation/transition-planning.js.map +1 -0
  504. package/dist/governance-activation/transition-ports.d.ts +150 -0
  505. package/dist/governance-activation/transition-ports.js +2 -0
  506. package/dist/governance-activation/transition-ports.js.map +1 -0
  507. package/dist/governance-activation/transition-process.d.ts +7 -0
  508. package/dist/governance-activation/transition-process.js +21 -0
  509. package/dist/governance-activation/transition-process.js.map +1 -0
  510. package/dist/governance-activation/transition-records.d.ts +56 -0
  511. package/dist/governance-activation/transition-records.js +208 -0
  512. package/dist/governance-activation/transition-records.js.map +1 -0
  513. package/dist/governance-activation/transitions.d.ts +14 -167
  514. package/dist/governance-activation/transitions.js +173 -2061
  515. package/dist/governance-activation/transitions.js.map +1 -1
  516. package/dist/governance-activation/types.d.ts +1 -390
  517. package/dist/governance-activation/types.js +1 -107
  518. package/dist/governance-activation/types.js.map +1 -1
  519. package/dist/governance-activation/validators.d.ts +1 -17
  520. package/dist/governance-activation/validators.js +1 -1304
  521. package/dist/governance-activation/validators.js.map +1 -1
  522. package/dist/governance-assessment/catalog.d.ts +1 -2
  523. package/dist/governance-assessment/catalog.js +18 -85
  524. package/dist/governance-assessment/catalog.js.map +1 -1
  525. package/dist/governance-assessment/command.d.ts +1 -1
  526. package/dist/governance-assessment/command.js +8 -12
  527. package/dist/governance-assessment/command.js.map +1 -1
  528. package/dist/governance-assessment/engine.d.ts +6 -11
  529. package/dist/governance-assessment/engine.js +490 -114
  530. package/dist/governance-assessment/engine.js.map +1 -1
  531. package/dist/governance-assessment/live-normalize.d.ts +1 -19
  532. package/dist/governance-assessment/live-normalize.js +1 -402
  533. package/dist/governance-assessment/live-normalize.js.map +1 -1
  534. package/dist/governance-assessment/live-transport.d.ts +2 -6
  535. package/dist/governance-assessment/live-transport.js +10 -22
  536. package/dist/governance-assessment/live-transport.js.map +1 -1
  537. package/dist/governance-assessment/live.js +55 -5
  538. package/dist/governance-assessment/live.js.map +1 -1
  539. package/dist/governance-assessment/predicates.d.ts +1 -27
  540. package/dist/governance-assessment/predicates.js +1 -329
  541. package/dist/governance-assessment/predicates.js.map +1 -1
  542. package/dist/governance-assessment/project.d.ts +20 -17
  543. package/dist/governance-assessment/project.js +101 -151
  544. package/dist/governance-assessment/project.js.map +1 -1
  545. package/dist/governance-assessment/readers.d.ts +1 -5
  546. package/dist/governance-assessment/readers.js +5 -13
  547. package/dist/governance-assessment/readers.js.map +1 -1
  548. package/dist/governance-assessment/report.d.ts +1 -23
  549. package/dist/governance-assessment/report.js +1 -265
  550. package/dist/governance-assessment/report.js.map +1 -1
  551. package/dist/governance-assessment/sanitize.d.ts +1 -8
  552. package/dist/governance-assessment/sanitize.js +1 -56
  553. package/dist/governance-assessment/sanitize.js.map +1 -1
  554. package/dist/governance-assessment/types.d.ts +1 -173
  555. package/dist/governance-assessment/types.js +1 -12
  556. package/dist/governance-assessment/types.js.map +1 -1
  557. package/dist/governance-assessment/yaml.d.ts +1 -6
  558. package/dist/governance-assessment/yaml.js +1 -29
  559. package/dist/governance-assessment/yaml.js.map +1 -1
  560. package/dist/init-filesystem.d.ts +1 -1
  561. package/dist/init-filesystem.js +126 -174
  562. package/dist/init-filesystem.js.map +1 -1
  563. package/dist/interactive.d.ts +2 -2
  564. package/dist/interactive.js +42 -41
  565. package/dist/interactive.js.map +1 -1
  566. package/dist/migrate-plan.d.ts +1 -1
  567. package/dist/migrate-plan.js +14 -8
  568. package/dist/migrate-plan.js.map +1 -1
  569. package/dist/npm-template-assets.js +5 -4
  570. package/dist/npm-template-assets.js.map +1 -1
  571. package/dist/openspec-profile.d.ts +1 -1
  572. package/dist/opentofu-template-assets.js +3 -3
  573. package/dist/opentofu-template-assets.js.map +1 -1
  574. package/dist/package-identity.d.ts +3 -0
  575. package/dist/package-identity.js +13 -2
  576. package/dist/package-identity.js.map +1 -1
  577. package/dist/planner.d.ts +1 -19
  578. package/dist/planner.js +1 -565
  579. package/dist/planner.js.map +1 -1
  580. package/dist/process-runner.d.ts +1 -1
  581. package/dist/project-dependencies.d.ts +5 -3
  582. package/dist/project-dependencies.js +74 -63
  583. package/dist/project-dependencies.js.map +1 -1
  584. package/dist/published-verifier.js +61 -30
  585. package/dist/published-verifier.js.map +1 -1
  586. package/dist/python-template-assets.js +6 -9
  587. package/dist/python-template-assets.js.map +1 -1
  588. package/dist/reconcile.d.ts +1 -1
  589. package/dist/reconcile.js +2 -2
  590. package/dist/reconcile.js.map +1 -1
  591. package/dist/repository-governance.d.ts +5 -2
  592. package/dist/repository-governance.js +140 -113
  593. package/dist/repository-governance.js.map +1 -1
  594. package/dist/runtime.js +1 -1
  595. package/dist/runtime.js.map +1 -1
  596. package/dist/scan.d.ts +6 -2
  597. package/dist/scan.js +78 -30
  598. package/dist/scan.js.map +1 -1
  599. package/dist/self-upgrade.d.ts +1 -1
  600. package/dist/self-upgrade.js +20 -7
  601. package/dist/self-upgrade.js.map +1 -1
  602. package/dist/stable-release.js +7 -1
  603. package/dist/stable-release.js.map +1 -1
  604. package/dist/standard-templates.d.ts +2 -2
  605. package/dist/standard-templates.js +6 -841
  606. package/dist/standard-templates.js.map +1 -1
  607. package/dist/supported-stack.d.ts +3 -115
  608. package/dist/supported-stack.js +4 -438
  609. package/dist/supported-stack.js.map +1 -1
  610. package/dist/telemetry/index.d.ts +1 -1
  611. package/dist/templates.d.ts +10 -20
  612. package/dist/templates.js +52 -3261
  613. package/dist/templates.js.map +1 -1
  614. package/dist/types.d.ts +1 -263
  615. package/dist/types.js +1 -1
  616. package/dist/types.js.map +1 -1
  617. package/dist/version.js +2 -6
  618. package/dist/version.js.map +1 -1
  619. package/dist/workstation-catalog.d.ts +5 -2
  620. package/dist/workstation-catalog.js +33 -2
  621. package/dist/workstation-catalog.js.map +1 -1
  622. package/dist/workstation.d.ts +4 -3
  623. package/dist/workstation.js +86 -32
  624. package/dist/workstation.js.map +1 -1
  625. package/docs/assets/liftoff-terminal.svg +3 -3
  626. package/docs/azure-deployment.md +107 -10
  627. package/docs/cli-reference.md +144 -35
  628. package/docs/configuration-and-manifests.md +152 -35
  629. package/docs/existing-repositories.md +48 -12
  630. package/docs/getting-started.md +44 -31
  631. package/docs/prerequisites.md +34 -35
  632. package/docs/project-structure.md +39 -46
  633. package/docs/repository-governance.md +96 -26
  634. package/docs/safety-and-consent.md +90 -28
  635. package/docs/spec-workflows-and-agents.md +31 -28
  636. package/docs/supported-stack.md +11 -9
  637. package/docs/troubleshooting.md +114 -54
  638. package/docs/workloads.md +52 -67
  639. package/package.json +4 -7
  640. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/UPSTREAM_LICENSE.txt +0 -21
  641. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/catalog.json +0 -520
  642. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/packaged/gitignore +0 -27
  643. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/README.md +0 -25
  644. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/components.json +0 -22
  645. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/eslint.config.js +0 -23
  646. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/index.html +0 -13
  647. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package-lock.json +0 -6156
  648. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package.json +0 -60
  649. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/public/power-apps.svg +0 -55
  650. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/App.tsx +0 -17
  651. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/assets/react.svg +0 -1
  652. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/mode-toggle.tsx +0 -37
  653. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/badge.tsx +0 -46
  654. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/button.tsx +0 -60
  655. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/calendar.tsx +0 -211
  656. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/card.tsx +0 -92
  657. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/chart.tsx +0 -355
  658. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/checkbox.tsx +0 -32
  659. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/command.tsx +0 -184
  660. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/dialog.tsx +0 -141
  661. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/dropdown-menu.tsx +0 -257
  662. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/input.tsx +0 -21
  663. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/label.tsx +0 -22
  664. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/popover.tsx +0 -46
  665. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/progress.tsx +0 -29
  666. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/select.tsx +0 -185
  667. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/separator.tsx +0 -26
  668. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/skeleton.tsx +0 -13
  669. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/table.tsx +0 -114
  670. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/tabs.tsx +0 -66
  671. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/textarea.tsx +0 -18
  672. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/tooltip.tsx +0 -61
  673. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/hooks/use-theme.ts +0 -11
  674. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/index.css +0 -131
  675. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/lib/utils.ts +0 -6
  676. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/main.tsx +0 -10
  677. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/_layout.tsx +0 -31
  678. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/home.tsx +0 -59
  679. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/not-found.tsx +0 -14
  680. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/query-provider.tsx +0 -26
  681. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/sonner-provider.tsx +0 -23
  682. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/theme-provider.tsx +0 -64
  683. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/router.tsx +0 -24
  684. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.app.json +0 -34
  685. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.json +0 -13
  686. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.node.json +0 -26
  687. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/vite.config.ts +0 -19
  688. package/assets/power-apps-code-app/README.md +0 -27
  689. package/dist/code-apps-plugin.d.ts +0 -13
  690. package/dist/code-apps-plugin.js +0 -69
  691. package/dist/code-apps-plugin.js.map +0 -1
  692. package/dist/power-apps-assets.d.ts +0 -30
  693. package/dist/power-apps-assets.js +0 -131
  694. package/dist/power-apps-assets.js.map +0 -1
  695. package/dist/power-apps-templates.d.ts +0 -3
  696. package/dist/power-apps-templates.js +0 -177
  697. package/dist/power-apps-templates.js.map +0 -1
  698. package/dist/power-apps-validation.d.ts +0 -1
  699. package/dist/power-apps-validation.js +0 -79
  700. package/dist/power-apps-validation.js.map +0 -1
@@ -1,1947 +1,91 @@
1
- import { readdir, stat } from 'node:fs/promises';
2
- import path from 'node:path';
1
+ import { executeSeedOperations } from './seed-lifecycle.js';
2
+ import { executeGitOperations } from './phase-publication.js';
3
+ import { discoverPhase0 } from './phase-discovery.js';
4
+ import { executeActivationApproval, executeCredentialReady, executeRulesetPhase } from './phase-governance.js';
5
+ import { remoteImportRetention, executeBootstrapStateDisposal } from './phase-bootstrap-state.js';
6
+ import { phaseOrder, rollbackPlanFromCompletedOperations, phaseById, assertPlanOperationsAllowed, assertOperationAllowed, rollbackPlanForPhase } from '../domain/governance/activation/operations.js';
3
7
  import { NodeCommandRunner } from '../process-runner.js';
4
- import { applyProjectFileTransaction, readProjectFile, resolveProjectPath, validateArtifactPathParts } from '../file-system.js';
5
- import { archiveGeneratedSeedForPhase, generatedSeedChangeName, validateGeneratedSeedForPhase, verifyGeneratedSeedBaselineForPhase } from './seed-lifecycle.js';
6
- import { evaluateApprovalForTransitionPlan, transitionPlanForPhase } from './approvals.js';
7
- import { canonicalJson, canonicalSha256 } from './canonical-json.js';
8
- import { credentialPolicyPathParts, detectCredentialLeaks } from './credentials.js';
9
- import { evidenceHeaderDigest, selectLatestPhaseEvidence } from './evidence.js';
10
- import { phaseContractDigests } from './graph.js';
11
- import { buildApprovedPhase0FactsFromState, renderGovernanceChangeWritePlan, stateWithSelectedActiveChange } from './source-of-truth.js';
12
- import { phaseIds } from './types.js';
13
- import { validateEvidenceHeader, validateSavedTransitionPlan, validateUserActivationState } from './validators.js';
8
+ import { validateManifestActivationForExecution } from '../domain/governance/activation/validators.js';
9
+ import { withProjectMutationLock } from '../adapters/filesystem/project-lock.js';
10
+ import { initializeExecutionAnchor, saveTransitionPlan, blockedState, writeOutcomeTransaction, safeTimestamp, evidenceHeaderFor, evidencePathParts, nextStateForOutcome, evidenceWriteOperation, stateWriteOperation } from './transition-records.js';
11
+ import { canonicalJson, canonicalSha256, isRecord } from '../domain/governance/activation/canonical-json.js';
12
+ import { buildSavedTransitionPlan, previewApplyNext, comparePlanFreshness } from './transition-planning.js';
13
+ import { readActivationInputSnapshot, phaseInputDigest, remoteBindingDigest } from './inputs.js';
14
14
  import { activationStateContentHash } from './activation-state.js';
15
- export const governancePlanDirectoryPathParts = ['governance', 'plans'];
16
- const planLifetimeMs = 15 * 60 * 1000;
17
- const dayMs = 24 * 60 * 60 * 1000;
18
- const remoteImportPayloadKind = 'remote-import-verified.v1';
19
- const workflowSourcePayloadKind = 'workflow-source-ready.v1';
20
- const greenRedPayloadKind = 'green-red-proof.v1';
21
- const bootstrapStateDisposedPayloadKind = 'bootstrap-state-disposed.v1';
22
- const engineProducer = 'liftoff-governance-transition-engine';
23
- function errorMessage(error) {
24
- return error instanceof Error ? error.message : String(error);
25
- }
26
- function commandSucceeded(result) {
27
- return result.status === 0 && !result.timedOut && !result.errorCode;
28
- }
29
- function commandFailure(result) {
30
- if (result.timedOut) {
31
- return `${result.displayCommand} timed out`;
32
- }
33
- if (result.errorCode || result.errorMessage) {
34
- return `${result.displayCommand}: ${[result.errorCode, result.errorMessage].filter(Boolean).join(': ')}`;
35
- }
36
- return `${result.displayCommand} exited ${result.status ?? 'unknown'}${result.stderr ? `: ${result.stderr.trim()}` : ''}`;
37
- }
38
- function phaseById(graph, phaseId) {
39
- const phase = graph.phases.find((entry) => entry.id === phaseId);
40
- if (!phase) {
41
- throw new Error(`Unknown phase ${phaseId}.`);
42
- }
43
- return phase;
44
- }
45
- function safeTimestamp(value) {
46
- return value.replace(/[^0-9A-Za-z]/g, '');
47
- }
48
- export function transitionPlanPathParts(plan) {
49
- return validateArtifactPathParts([
50
- ...governancePlanDirectoryPathParts,
51
- `${plan.phaseId}-${safeTimestamp(plan.createdAt)}-${plan.planDigest.slice(0, 12)}.json`
52
- ], 'Governance transition plan path');
53
- }
54
- function evidencePathParts(evidenceId) {
55
- return validateArtifactPathParts(['governance', 'evidence', `${evidenceId}.json`], 'Governance evidence path');
56
- }
57
- function activationStatePathParts() {
58
- return validateArtifactPathParts(['governance', 'activation-state.json'], 'Activation state path');
59
- }
60
- function cloneState(state) {
61
- return JSON.parse(JSON.stringify(state));
62
- }
63
- function isRecord(value) {
64
- return typeof value === 'object' && value !== null && !Array.isArray(value);
65
- }
66
- function phaseOrder(phaseId) {
67
- return phaseIds.indexOf(phaseId);
68
- }
69
- function transitionDestination(type, identity, extras = {}) {
70
- return { type, identity, ...extras };
71
- }
72
- function operation(input) {
73
- return input;
74
- }
75
- const allowedActionsByPhase = {
76
- 'seed-valid': [
77
- 'openspec.seed.validate',
78
- 'governance.evidence.write',
79
- 'governance.activation-state.write'
80
- ],
81
- 'seed-verified': [
82
- 'openspec.seed.baseline-verify',
83
- 'governance.evidence.write',
84
- 'governance.activation-state.write'
85
- ],
86
- 'seed-archived': [
87
- 'openspec.seed.archive',
88
- 'governance.evidence.write',
89
- 'governance.activation-state.write'
90
- ],
91
- committed: [
92
- 'git.init',
93
- 'git.add-reviewed',
94
- 'git.commit-reviewed',
95
- 'git.verify-existing-commit',
96
- 'governance.evidence.write',
97
- 'governance.activation-state.write'
98
- ],
99
- pushed: [
100
- 'git.push-approved-ref',
101
- 'git.verify-existing-push',
102
- 'governance.evidence.write',
103
- 'governance.activation-state.write'
104
- ],
105
- 'phase-0-complete': [
106
- 'github.phase0.discover',
107
- 'azure.phase0.discover',
108
- 'governance.evidence.write',
109
- 'governance.activation-state.write'
110
- ],
111
- 'activation-approved': [
112
- 'openspec.governance.create-change',
113
- 'spec-kit.governance.create-change',
114
- 'governance.activation-state.write'
115
- ],
116
- 'credential-ready': [
117
- 'github.credential.verify-policy',
118
- 'github.credential.enroll-masked',
119
- 'governance.evidence.write',
120
- 'governance.activation-state.write'
121
- ],
122
- 'provider-ready': [
123
- 'azure.provider.ensure-ready',
124
- 'governance.evidence.write',
125
- 'governance.activation-state.write'
126
- ],
127
- 'state-path-selected': [
128
- 'azure.state-path.select',
129
- 'governance.evidence.write',
130
- 'governance.activation-state.write'
131
- ],
132
- 'existing-private-path': [
133
- 'azure.existing-private-path.verify',
134
- 'governance.evidence.write',
135
- 'governance.activation-state.write'
136
- ],
137
- 'bootstrap-local': [
138
- 'azure.bootstrap-local.apply',
139
- 'github.bootstrap-local.configure',
140
- 'governance.evidence.write',
141
- 'governance.activation-state.write'
142
- ],
143
- 'runner-ready': [
144
- 'github.runner.ensure-ready',
145
- 'governance.evidence.write',
146
- 'governance.activation-state.write'
147
- ],
148
- 'private-backend-proof': [
149
- 'github.runner.backend-proof',
150
- 'azure.remote-state.read',
151
- 'governance.evidence.write',
152
- 'governance.activation-state.write'
153
- ],
154
- 'remote-import-verified': [
155
- 'azure.remote-import.verify',
156
- 'governance.evidence.write',
157
- 'governance.activation-state.write'
158
- ],
159
- 'remote-ready': [
160
- 'azure.remote-ready.verify',
161
- 'governance.evidence.write',
162
- 'governance.activation-state.write'
163
- ],
164
- 'application-foundation': [
165
- 'azure.application-foundation.apply',
166
- 'openspec.governance.update',
167
- 'governance.evidence.write',
168
- 'governance.activation-state.write'
169
- ],
170
- 'workflow-source-ready': [
171
- 'local.workflow-source.write',
172
- 'local.ruleset-source.write',
173
- 'governance.evidence.write',
174
- 'governance.activation-state.write'
175
- ],
176
- 'dev-proof': [
177
- 'github.checks.dev-proof',
178
- 'governance.evidence.write',
179
- 'governance.activation-state.write'
180
- ],
181
- 'staging-qualified': [
182
- 'github.checks.staging',
183
- 'azure.staging.readback',
184
- 'governance.evidence.write',
185
- 'governance.activation-state.write'
186
- ],
187
- 'production-rehearsed': [
188
- 'github.checks.production-rehearsal',
189
- 'azure.production-readback',
190
- 'governance.evidence.write',
191
- 'governance.activation-state.write'
192
- ],
193
- 'green-red-proof': [
194
- 'github.checks.green-red-proof',
195
- 'governance.evidence.write',
196
- 'governance.activation-state.write'
197
- ],
198
- 'enforcement-approved': [
199
- 'governance.activation-state.write'
200
- ],
201
- 'rulesets-applied': [
202
- 'github.ruleset.apply',
203
- 'github.ruleset.readback',
204
- 'governance.evidence.write',
205
- 'governance.activation-state.write'
206
- ],
207
- 'live-readback': [
208
- 'github.ruleset.readback',
209
- 'governance.evidence.write',
210
- 'governance.activation-state.write'
211
- ],
212
- 'bootstrap-state-disposed': [
213
- 'local.bootstrap-state.dispose',
214
- 'governance.evidence.write',
215
- 'governance.activation-state.write'
216
- ]
15
+ import { evidenceHeaderDigest, validateEvidenceFreshness } from '../domain/governance/activation/evidence.js';
16
+ export { governancePlanDirectoryPathParts, transitionPlanPathParts } from './transition-records.js';
17
+ export { buildSavedTransitionPlan, previewApplyNext } from './transition-planning.js';
18
+ export { rollbackPlanFromCompletedOperations, planDigestFor } from '../domain/governance/activation/operations.js';
19
+ const builtInExecutors = {
20
+ 'seed-valid': executeSeedOperations,
21
+ 'seed-verified': executeSeedOperations,
22
+ 'seed-archived': executeSeedOperations,
23
+ committed: executeGitOperations,
24
+ pushed: executeGitOperations,
25
+ 'phase-0-complete': discoverPhase0,
26
+ 'activation-approved': executeActivationApproval,
27
+ 'credential-ready': executeCredentialReady,
28
+ 'remote-ready': remoteImportRetention,
29
+ 'bootstrap-state-disposed': executeBootstrapStateDisposal,
30
+ 'rulesets-applied': executeRulesetPhase,
31
+ 'live-readback': executeRulesetPhase
217
32
  };
218
- function isAllowedAction(phaseId, actionId) {
219
- return allowedActionsByPhase[phaseId].includes(actionId);
220
- }
221
- function assertOperationAllowed(phase, op) {
222
- if (op.phaseId !== phase.id) {
223
- throw new Error(`Operation ${op.actionId} belongs to ${op.phaseId}, not ${phase.id}.`);
224
- }
225
- if (!isAllowedAction(phase.id, op.actionId)) {
226
- throw new Error(`Operation ${op.actionId} is not allowlisted for phase ${phase.id}.`);
227
- }
228
- const allowed = op.remote ? phase.allowedMutations.remote : phase.allowedMutations.local;
229
- if (!allowed.includes(op.mutationClass)) {
230
- throw new Error(`Operation ${op.actionId} mutation ${op.mutationClass} is not declared in phase ${phase.id} ${op.remote ? 'remote' : 'local'} mutations.`);
231
- }
232
- }
233
- function assertPlanOperationsAllowed(plan, phase) {
234
- for (const op of plan.operations) {
235
- assertOperationAllowed(phase, op);
236
- }
237
- }
238
- function evidenceWriteOperation(phase, pathParts) {
239
- return operation({
240
- adapter: 'local-evidence',
241
- actionId: 'governance.evidence.write',
242
- mutationClass: 'write-evidence',
243
- phaseId: phase.id,
244
- inputs: { pathParts },
245
- destination: transitionDestination('local', pathParts.join('/'), { pathParts }),
246
- remote: false,
247
- destructive: false
248
- });
249
- }
250
- function stateWriteOperation(phase) {
251
- return operation({
252
- adapter: 'local-evidence',
253
- actionId: 'governance.activation-state.write',
254
- mutationClass: 'write-activation-state',
255
- phaseId: phase.id,
256
- inputs: { pathParts: activationStatePathParts() },
257
- destination: transitionDestination('local', 'governance/activation-state.json', {
258
- pathParts: activationStatePathParts()
259
- }),
260
- remote: false,
261
- destructive: false
262
- });
263
- }
264
- async function allProjectFiles(projectRoot) {
265
- const files = [];
266
- async function visit(current, relative) {
267
- const entries = await readdir(current, { withFileTypes: true });
268
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name, 'en'))) {
269
- if (entry.name === '.git' ||
270
- entry.name === 'node_modules' ||
271
- entry.name === '.cache' ||
272
- (relative === '' && entry.name === 'governance')) {
273
- continue;
274
- }
275
- const child = path.join(current, entry.name);
276
- const childRelative = relative ? `${relative}/${entry.name}` : entry.name;
277
- if (entry.isDirectory()) {
278
- await visit(child, childRelative);
279
- }
280
- else if (entry.isFile()) {
281
- files.push(childRelative);
282
- }
283
- }
284
- }
285
- await visit(projectRoot, '');
286
- return files;
287
- }
288
- function parsePorcelainStatus(output) {
289
- const entries = output.split('\0').filter(Boolean);
290
- const parsed = [];
291
- for (const entry of entries) {
292
- const index = entry.slice(0, 1);
293
- const worktree = entry.slice(1, 2);
294
- const rawPath = entry.slice(3);
295
- const renamed = rawPath.includes('\0') ? rawPath.split('\0').at(-1) : rawPath;
296
- parsed.push({ index, worktree, path: renamed });
297
- }
298
- return parsed.filter((entry) => !entry.path.startsWith('governance/'));
299
- }
300
- async function runCommand(runner, command, cwd) {
301
- return await runner.run(command, { cwd });
302
- }
303
- async function runGit(runner, projectRoot, args) {
304
- return await runCommand(runner, { executable: 'git', args }, projectRoot);
305
- }
306
- async function inspectGitRepository(projectRoot, runner) {
307
- const rootResult = await runGit(runner, projectRoot, ['rev-parse', '--show-toplevel']);
308
- if (!commandSucceeded(rootResult)) {
309
- return {
310
- insideWorkTree: false,
311
- root: null,
312
- branch: null,
313
- head: null,
314
- upstream: null,
315
- status: [],
316
- remotes: [],
317
- issues: []
318
- };
319
- }
320
- const root = path.resolve(rootResult.stdout.trim());
321
- if (root !== path.resolve(projectRoot)) {
322
- return {
323
- insideWorkTree: true,
324
- root,
325
- branch: null,
326
- head: null,
327
- upstream: null,
328
- status: [],
329
- remotes: [],
330
- issues: [`Git repository root ${root} does not match Liftoff project root ${projectRoot}.`]
331
- };
332
- }
333
- const branchResult = await runGit(runner, projectRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
334
- const branch = commandSucceeded(branchResult) ? branchResult.stdout.trim() : null;
335
- const headResult = await runGit(runner, projectRoot, ['rev-parse', '--verify', 'HEAD']);
336
- const head = commandSucceeded(headResult) ? headResult.stdout.trim() : null;
337
- const upstreamResult = await runGit(runner, projectRoot, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']);
338
- const upstream = commandSucceeded(upstreamResult) ? upstreamResult.stdout.trim() : null;
339
- const statusResult = await runGit(runner, projectRoot, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);
340
- const status = commandSucceeded(statusResult) ? parsePorcelainStatus(statusResult.stdout) : [];
341
- const remoteResult = await runGit(runner, projectRoot, ['remote', '-v']);
342
- const remotes = commandSucceeded(remoteResult) ? parseRemotes(remoteResult.stdout) : [];
343
- const issues = [];
344
- if (!branch) {
345
- issues.push('Detached HEAD or unborn branch cannot be reviewed for deterministic governance commit/push.');
346
- }
347
- if (!commandSucceeded(statusResult)) {
348
- issues.push(commandFailure(statusResult));
349
- }
350
- if (status.some((entry) => entry.index === 'D' || entry.worktree === 'D')) {
351
- issues.push('Unknown file deletion is outside the reviewed initial commit plan.');
352
- }
353
- return { insideWorkTree: true, root, branch, head, upstream, status, remotes, issues };
354
- }
355
- function parseRemotes(output) {
356
- const remotes = new Map();
357
- for (const line of output.split(/\r?\n/u)) {
358
- const match = line.match(/^(\S+)\s+(\S+)\s+\((fetch|push)\)$/u);
359
- if (match?.[3] === 'fetch') {
360
- remotes.set(match[1], match[2]);
361
- }
362
- }
363
- return [...remotes.entries()]
364
- .map(([name, url]) => ({ name, url }))
365
- .sort((left, right) => left.name.localeCompare(right.name, 'en'));
366
- }
367
- function gitStatusDigest(inspection) {
368
- return canonicalSha256({
369
- branch: inspection.branch,
370
- head: inspection.head,
371
- status: inspection.status,
372
- remotes: inspection.remotes
373
- });
374
- }
375
- async function remoteHead(runner, projectRoot, remote, branch) {
376
- const result = await runGit(runner, projectRoot, ['ls-remote', '--heads', remote, branch]);
377
- if (!commandSucceeded(result)) {
378
- throw new Error(commandFailure(result));
379
- }
380
- const first = result.stdout.trim().split(/\s+/u)[0];
381
- return first && /^[a-f0-9]{40}$/u.test(first) ? first : null;
382
- }
383
- async function isAncestor(runner, projectRoot, ancestor, descendant) {
384
- const result = await runGit(runner, projectRoot, ['merge-base', '--is-ancestor', ancestor, descendant]);
385
- return result.status === 0;
386
- }
387
- function defaultBranch(state) {
388
- return state.repository.defaultBranch || 'main';
389
- }
390
- async function gitCommitOperations(inspection, phase, runner) {
391
- const git = await inspectGitRepository(inspection.projectRoot, runner);
392
- if (git.issues.length > 0) {
393
- throw new Error(git.issues.join(' '));
394
- }
395
- const branch = defaultBranch(inspection.state);
396
- if (!git.insideWorkTree) {
397
- const files = await allProjectFiles(inspection.projectRoot);
398
- return [
399
- operation({
400
- adapter: 'git',
401
- actionId: 'git.init',
402
- mutationClass: 'git-commit',
403
- phaseId: phase.id,
404
- inputs: { branch },
405
- destination: transitionDestination('local', '.git'),
406
- remote: false,
407
- destructive: false
408
- }),
409
- operation({
410
- adapter: 'git',
411
- actionId: 'git.add-reviewed',
412
- mutationClass: 'git-commit',
413
- phaseId: phase.id,
414
- inputs: { paths: files, statusDigest: canonicalSha256(files) },
415
- destination: transitionDestination('local', 'git-index'),
416
- remote: false,
417
- destructive: false
418
- }),
419
- operation({
420
- adapter: 'git',
421
- actionId: 'git.commit-reviewed',
422
- mutationClass: 'git-commit',
423
- phaseId: phase.id,
424
- inputs: {
425
- message: 'Initial Liftoff baseline',
426
- branch,
427
- paths: files
428
- },
429
- destination: transitionDestination('local', branch, { ref: branch }),
430
- remote: false,
431
- destructive: false
432
- })
433
- ];
434
- }
435
- if (git.branch !== branch) {
436
- throw new Error(`Expected branch ${branch}, found ${git.branch ?? 'detached'}.`);
437
- }
438
- if (git.upstream) {
439
- const divergence = await runGit(runner, inspection.projectRoot, ['rev-list', '--left-right', '--count', `${git.upstream}...HEAD`]);
440
- if (!commandSucceeded(divergence)) {
441
- throw new Error(commandFailure(divergence));
442
- }
443
- const [behind, ahead] = divergence.stdout.trim().split(/\s+/u).map((value) => Number.parseInt(value, 10));
444
- if ((behind ?? 0) > 0 || (ahead ?? 0) > 0) {
445
- throw new Error(`Branch ${branch} diverges from ${git.upstream}; refusing commit before explicit reconciliation.`);
446
- }
447
- }
448
- if (git.status.length === 0 && git.head) {
449
- return [
450
- operation({
451
- adapter: 'git',
452
- actionId: 'git.verify-existing-commit',
453
- mutationClass: 'read-worktree',
454
- phaseId: phase.id,
455
- inputs: { head: git.head, branch, statusDigest: gitStatusDigest(git) },
456
- destination: transitionDestination('local', branch, { ref: branch }),
457
- remote: false,
458
- destructive: false
459
- })
460
- ];
461
- }
462
- const paths = git.status.map((entry) => entry.path).sort((left, right) => left.localeCompare(right, 'en'));
463
- return [
464
- operation({
465
- adapter: 'git',
466
- actionId: 'git.add-reviewed',
467
- mutationClass: 'git-commit',
468
- phaseId: phase.id,
469
- inputs: { paths, statusDigest: gitStatusDigest(git) },
470
- destination: transitionDestination('local', 'git-index'),
471
- remote: false,
472
- destructive: false
473
- }),
474
- operation({
475
- adapter: 'git',
476
- actionId: 'git.commit-reviewed',
477
- mutationClass: 'git-commit',
478
- phaseId: phase.id,
479
- inputs: {
480
- message: 'Initial Liftoff baseline',
481
- branch,
482
- paths
483
- },
484
- destination: transitionDestination('local', branch, { ref: branch }),
485
- remote: false,
486
- destructive: false
487
- })
488
- ];
489
- }
490
- async function gitPushOperations(inspection, phase, runner) {
491
- const git = await inspectGitRepository(inspection.projectRoot, runner);
492
- if (git.issues.length > 0) {
493
- throw new Error(git.issues.join(' '));
494
- }
495
- if (!git.insideWorkTree || !git.head) {
496
- throw new Error('A reviewed local commit is required before push.');
497
- }
498
- const branch = defaultBranch(inspection.state);
499
- if (git.branch !== branch) {
500
- throw new Error(`Expected branch ${branch}, found ${git.branch ?? 'detached'}.`);
501
- }
502
- if (git.status.length > 0) {
503
- throw new Error(`Worktree has unreviewed changes outside governance/plans: ${git.status.map((entry) => entry.path).join(', ')}.`);
504
- }
505
- const remoteNames = new Set(git.remotes.map((remote) => remote.name));
506
- if (!remoteNames.has('origin') || git.remotes.length !== 1) {
507
- throw new Error(`Expected exactly one approved origin remote; found ${git.remotes.map((remote) => remote.name).join(', ') || 'none'}.`);
508
- }
509
- const remoteSha = await remoteHead(runner, inspection.projectRoot, 'origin', branch);
510
- if (remoteSha && remoteSha !== git.head && !(await isAncestor(runner, inspection.projectRoot, remoteSha, git.head))) {
511
- throw new Error('Remote branch is not a fast-forward target; force, reset, rebase, and non-fast-forward push are forbidden.');
512
- }
513
- return [
514
- operation({
515
- adapter: 'git',
516
- actionId: remoteSha === git.head ? 'git.verify-existing-push' : 'git.push-approved-ref',
517
- mutationClass: remoteSha === git.head ? 'github-read' : 'git-push',
518
- phaseId: phase.id,
519
- inputs: {
520
- remote: 'origin',
521
- branch,
522
- localHead: git.head,
523
- remoteHead: remoteSha,
524
- statusDigest: gitStatusDigest(git)
525
- },
526
- destination: transitionDestination('repository', inspection.state.repository.name, {
527
- repository: inspection.state.repository.name,
528
- ref: `refs/heads/${branch}`
529
- }),
530
- remote: true,
531
- destructive: false
532
- })
533
- ];
534
- }
535
- async function phaseOperations(inspection, phase, runner, createdAt) {
536
- const statePath = activationStatePathParts();
537
- const baseEvidencePath = evidencePathParts(`${phase.id}-${safeTimestamp(createdAt)}`);
538
- const writeOps = (includeEvidence = true) => [
539
- ...(includeEvidence ? [evidenceWriteOperation(phase, baseEvidencePath)] : []),
540
- stateWriteOperation(phase)
541
- ];
542
- switch (phase.id) {
543
- case 'seed-valid':
544
- return [
545
- operation({
546
- adapter: 'selected-spec-workflow',
547
- actionId: 'openspec.seed.validate',
548
- mutationClass: 'read-worktree',
549
- phaseId: phase.id,
550
- inputs: { changeName: generatedSeedChangeName(inspection.manifest) },
551
- destination: transitionDestination('local', inspection.projectRoot),
552
- remote: false,
553
- destructive: false
554
- }),
555
- ...writeOps()
556
- ];
557
- case 'seed-verified':
558
- return [
559
- operation({
560
- adapter: 'selected-spec-workflow',
561
- actionId: 'openspec.seed.baseline-verify',
562
- mutationClass: 'read-worktree',
563
- phaseId: phase.id,
564
- inputs: { changeName: generatedSeedChangeName(inspection.manifest) },
565
- destination: transitionDestination('local', inspection.projectRoot),
566
- remote: false,
567
- destructive: false
568
- }),
569
- ...writeOps()
570
- ];
571
- case 'seed-archived':
572
- return [
573
- operation({
574
- adapter: 'selected-spec-workflow',
575
- actionId: 'openspec.seed.archive',
576
- mutationClass: 'write-openspec-seed',
577
- phaseId: phase.id,
578
- inputs: { changeName: generatedSeedChangeName(inspection.manifest) },
579
- destination: transitionDestination('local', 'openspec/changes'),
580
- remote: false,
581
- destructive: false
582
- }),
583
- ...writeOps()
584
- ];
585
- case 'committed':
586
- return [...await gitCommitOperations(inspection, phase, runner), ...writeOps()];
587
- case 'pushed':
588
- return [...await gitPushOperations(inspection, phase, runner), ...writeOps()];
589
- case 'phase-0-complete':
590
- return [
591
- operation({
592
- adapter: 'github',
593
- actionId: 'github.phase0.discover',
594
- mutationClass: 'github-read',
595
- phaseId: phase.id,
596
- inputs: { repository: inspection.state.repository.name },
597
- destination: transitionDestination('repository', inspection.state.repository.name, {
598
- repository: inspection.state.repository.name
599
- }),
600
- remote: true,
601
- destructive: false
602
- }),
603
- operation({
604
- adapter: 'azure-opentofu',
605
- actionId: 'azure.phase0.discover',
606
- mutationClass: 'azure-read',
607
- phaseId: phase.id,
608
- inputs: { cloud: 'cloud' in inspection.manifest.project.workload ? inspection.manifest.project.workload.cloud : 'power-platform' },
609
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
610
- subscriptionId: 'phase-0-discovered-subscription'
611
- }),
612
- remote: true,
613
- destructive: false
614
- }),
615
- ...writeOps()
616
- ];
617
- case 'activation-approved': {
618
- const actionId = inspection.manifest.project.specWorkflow === 'openspec'
619
- ? 'openspec.governance.create-change'
620
- : 'spec-kit.governance.create-change';
621
- return [
622
- operation({
623
- adapter: 'selected-spec-workflow',
624
- actionId,
625
- mutationClass: 'write-openspec-governance',
626
- phaseId: phase.id,
627
- inputs: { workflowKind: inspection.manifest.project.specWorkflow },
628
- destination: transitionDestination('local', 'governance change'),
629
- remote: false,
630
- destructive: false
631
- }),
632
- stateWriteOperation(phase)
633
- ];
634
- }
635
- case 'credential-ready':
636
- return [
637
- operation({
638
- adapter: 'github',
639
- actionId: 'github.credential.verify-policy',
640
- mutationClass: 'github-read',
641
- phaseId: phase.id,
642
- inputs: { policyPathParts: credentialPolicyPathParts },
643
- destination: transitionDestination('repository', inspection.state.repository.name, {
644
- repository: inspection.state.repository.name
645
- }),
646
- remote: true,
647
- destructive: false
648
- }),
649
- ...writeOps()
650
- ];
651
- case 'provider-ready':
652
- return [
653
- operation({
654
- adapter: 'azure-opentofu',
655
- actionId: 'azure.provider.ensure-ready',
656
- mutationClass: 'azure-provider-register',
657
- phaseId: phase.id,
658
- inputs: { retainedCapability: true },
659
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
660
- subscriptionId: 'phase-0-discovered-subscription'
661
- }),
662
- remote: true,
663
- destructive: false
664
- }),
665
- ...writeOps()
666
- ];
667
- case 'state-path-selected':
668
- return [
669
- operation({
670
- adapter: 'azure-opentofu',
671
- actionId: 'azure.state-path.select',
672
- mutationClass: 'azure-read',
673
- phaseId: phase.id,
674
- inputs: { allowed: ['existing-private', 'bootstrap-local'] },
675
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
676
- subscriptionId: 'phase-0-discovered-subscription'
677
- }),
678
- remote: true,
679
- destructive: false
680
- }),
681
- ...writeOps()
682
- ];
683
- case 'existing-private-path':
684
- return [
685
- operation({
686
- adapter: 'azure-opentofu',
687
- actionId: 'azure.existing-private-path.verify',
688
- mutationClass: 'azure-read',
689
- phaseId: phase.id,
690
- inputs: { statePath: 'existing-private' },
691
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
692
- subscriptionId: 'phase-0-discovered-subscription'
693
- }),
694
- remote: true,
695
- destructive: false
696
- }),
697
- ...writeOps()
698
- ];
699
- case 'bootstrap-local':
700
- return [
701
- operation({
702
- adapter: 'azure-opentofu',
703
- actionId: 'azure.bootstrap-local.apply',
704
- mutationClass: 'azure-network-provision',
705
- phaseId: phase.id,
706
- inputs: { boundedLocalBootstrap: true },
707
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
708
- subscriptionId: 'phase-0-discovered-subscription'
709
- }),
710
- remote: true,
711
- destructive: false
712
- }),
713
- operation({
714
- adapter: 'github',
715
- actionId: 'github.bootstrap-local.configure',
716
- mutationClass: 'github-write',
717
- phaseId: phase.id,
718
- inputs: { repository: inspection.state.repository.name },
719
- destination: transitionDestination('repository', inspection.state.repository.name, {
720
- repository: inspection.state.repository.name
721
- }),
722
- remote: true,
723
- destructive: false
724
- }),
725
- ...writeOps()
726
- ];
727
- case 'runner-ready':
728
- return [
729
- operation({
730
- adapter: 'github',
731
- actionId: 'github.runner.ensure-ready',
732
- mutationClass: 'github-write',
733
- phaseId: phase.id,
734
- inputs: { restrictedRunner: true },
735
- destination: transitionDestination('repository', inspection.state.repository.name, {
736
- repository: inspection.state.repository.name
737
- }),
738
- remote: true,
739
- destructive: false
740
- }),
741
- ...writeOps()
742
- ];
743
- case 'private-backend-proof':
744
- return [
745
- operation({
746
- adapter: 'github',
747
- actionId: 'github.runner.backend-proof',
748
- mutationClass: 'github-read',
749
- phaseId: phase.id,
750
- inputs: { requiredConclusion: 'success' },
751
- destination: transitionDestination('repository', inspection.state.repository.name, {
752
- repository: inspection.state.repository.name
753
- }),
754
- remote: true,
755
- destructive: false
756
- }),
757
- ...writeOps()
758
- ];
759
- case 'remote-import-verified':
760
- return [
761
- operation({
762
- adapter: 'azure-opentofu',
763
- actionId: 'azure.remote-import.verify',
764
- mutationClass: 'azure-state-import',
765
- phaseId: phase.id,
766
- inputs: { noChangePlanRequired: true },
767
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
768
- subscriptionId: 'phase-0-discovered-subscription'
769
- }),
770
- remote: true,
771
- destructive: false
772
- }),
773
- ...writeOps()
774
- ];
775
- case 'remote-ready':
776
- return [
777
- operation({
778
- adapter: 'azure-opentofu',
779
- actionId: 'azure.remote-ready.verify',
780
- mutationClass: 'azure-read',
781
- phaseId: phase.id,
782
- inputs: { retainBootstrapStateForDays: 30 },
783
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
784
- subscriptionId: 'phase-0-discovered-subscription'
785
- }),
786
- remote: true,
787
- destructive: false
788
- }),
789
- ...writeOps()
790
- ];
791
- case 'application-foundation':
792
- return [
793
- operation({
794
- adapter: 'azure-opentofu',
795
- actionId: 'azure.application-foundation.apply',
796
- mutationClass: 'azure-resource-provision',
797
- phaseId: phase.id,
798
- inputs: { noProviderUnregisterRollback: true },
799
- destination: transitionDestination('subscription', 'phase-0-discovered-subscription', {
800
- subscriptionId: 'phase-0-discovered-subscription'
801
- }),
802
- remote: true,
803
- destructive: false
804
- }),
805
- ...writeOps()
806
- ];
807
- case 'workflow-source-ready':
808
- return [
809
- operation({
810
- adapter: 'local-state',
811
- actionId: 'local.workflow-source.write',
812
- mutationClass: 'write-workflows',
813
- phaseId: phase.id,
814
- inputs: { path: '.github/workflows' },
815
- destination: transitionDestination('local', '.github/workflows', {
816
- pathParts: ['.github', 'workflows']
817
- }),
818
- remote: false,
819
- destructive: false
820
- }),
821
- operation({
822
- adapter: 'local-state',
823
- actionId: 'local.ruleset-source.write',
824
- mutationClass: 'write-ruleset-source',
825
- phaseId: phase.id,
826
- inputs: { path: '.github/rulesets' },
827
- destination: transitionDestination('local', '.github/rulesets', {
828
- pathParts: ['.github', 'rulesets']
829
- }),
830
- remote: false,
831
- destructive: false
832
- }),
833
- ...writeOps()
834
- ];
835
- case 'dev-proof':
836
- return [
837
- operation({
838
- adapter: 'github',
839
- actionId: 'github.checks.dev-proof',
840
- mutationClass: 'github-read',
841
- phaseId: phase.id,
842
- inputs: { acceptedConclusions: ['success'] },
843
- destination: transitionDestination('repository', inspection.state.repository.name, {
844
- repository: inspection.state.repository.name
845
- }),
846
- remote: true,
847
- destructive: false
848
- }),
849
- ...writeOps()
850
- ];
851
- case 'staging-qualified':
852
- return [
853
- operation({
854
- adapter: 'github',
855
- actionId: 'github.checks.staging',
856
- mutationClass: 'github-read',
857
- phaseId: phase.id,
858
- inputs: { acceptedConclusions: ['success'] },
859
- destination: transitionDestination('repository', inspection.state.repository.name, {
860
- repository: inspection.state.repository.name
861
- }),
862
- remote: true,
863
- destructive: false
864
- }),
865
- ...writeOps()
866
- ];
867
- case 'production-rehearsed':
868
- return [
869
- operation({
870
- adapter: 'github',
871
- actionId: 'github.checks.production-rehearsal',
872
- mutationClass: 'github-read',
873
- phaseId: phase.id,
874
- inputs: { acceptedConclusions: ['success'] },
875
- destination: transitionDestination('repository', inspection.state.repository.name, {
876
- repository: inspection.state.repository.name
877
- }),
878
- remote: true,
879
- destructive: false
880
- }),
881
- ...writeOps()
882
- ];
883
- case 'green-red-proof':
884
- return [
885
- operation({
886
- adapter: 'github',
887
- actionId: 'github.checks.green-red-proof',
888
- mutationClass: 'github-read',
889
- phaseId: phase.id,
890
- inputs: { required: ['green-success', 'deliberate-red-failure'] },
891
- destination: transitionDestination('repository', inspection.state.repository.name, {
892
- repository: inspection.state.repository.name
893
- }),
894
- remote: true,
895
- destructive: false
896
- }),
897
- ...writeOps()
898
- ];
899
- case 'enforcement-approved':
900
- return [stateWriteOperation(phase)];
901
- case 'rulesets-applied':
902
- return [
903
- operation({
904
- adapter: 'github',
905
- actionId: 'github.ruleset.apply',
906
- mutationClass: 'github-ruleset-write',
907
- phaseId: phase.id,
908
- inputs: { sourceDigest: rulesetSourceDigestFromEvidence(inspection) ?? 'missing' },
909
- destination: transitionDestination('repository', inspection.state.repository.name, {
910
- repository: inspection.state.repository.name
911
- }),
912
- remote: true,
913
- destructive: false
914
- }),
915
- operation({
916
- adapter: 'github',
917
- actionId: 'github.ruleset.readback',
918
- mutationClass: 'github-read',
919
- phaseId: phase.id,
920
- inputs: { sourceDigest: rulesetSourceDigestFromEvidence(inspection) ?? 'missing' },
921
- destination: transitionDestination('repository', inspection.state.repository.name, {
922
- repository: inspection.state.repository.name
923
- }),
924
- remote: true,
925
- destructive: false
926
- }),
927
- ...writeOps()
928
- ];
929
- case 'live-readback':
930
- return [
931
- operation({
932
- adapter: 'github',
933
- actionId: 'github.ruleset.readback',
934
- mutationClass: 'github-read',
935
- phaseId: phase.id,
936
- inputs: { sourceDigest: rulesetSourceDigestFromEvidence(inspection) ?? 'missing' },
937
- destination: transitionDestination('repository', inspection.state.repository.name, {
938
- repository: inspection.state.repository.name
939
- }),
940
- remote: true,
941
- destructive: false
942
- }),
943
- ...writeOps()
944
- ];
945
- case 'bootstrap-state-disposed': {
946
- const paths = disposalPathInputs(inspection.state.bootstrapState);
947
- return [
948
- operation({
949
- adapter: 'local-state',
950
- actionId: 'local.bootstrap-state.dispose',
951
- mutationClass: 'delete-local-state',
952
- phaseId: phase.id,
953
- inputs: paths,
954
- destination: transitionDestination('local', 'bootstrap-state', {
955
- pathParts: statePath
956
- }),
957
- remote: false,
958
- destructive: true
959
- }),
960
- ...writeOps()
961
- ];
962
- }
963
- }
964
- }
965
- function disposalPathInputs(retention) {
966
- return {
967
- encryptedStatePathParts: retention?.encryptedStatePathParts ?? [],
968
- encryptionKeyPathParts: retention?.encryptionKeyPathParts ?? []
969
- };
970
- }
971
- function rulesetSourceDigestFromEvidence(inspection) {
972
- const record = latestRecordWithPayload(inspection, 'workflow-source-ready');
973
- if (!record || !isRecord(record.payload)) {
974
- return null;
975
- }
976
- if (record.payload.kind !== workflowSourcePayloadKind) {
977
- return null;
978
- }
979
- const digest = record.payload.rulesetSourceDigest;
980
- return typeof digest === 'string' && /^[a-f0-9]{64}$/u.test(digest) ? digest : null;
981
- }
982
- function latestRecordWithPayload(inspection, phaseId) {
983
- const records = inspection.evidence.filter((record) => record.header.phaseId === phaseId);
984
- const selection = selectLatestPhaseEvidence(records, inspection.contexts[phaseId]);
985
- if (!selection.selected) {
986
- return null;
987
- }
988
- return records.find((record) => record.evidenceId === selection.selected?.evidenceId) ?? null;
989
- }
990
- function rollbackPlanForPhase(phase, completed = []) {
991
- return rollbackPlanFromCompletedOperations(phase.id, phase.rollback.kind, phase.rollback.target, completed);
992
- }
993
- export function rollbackPlanFromCompletedOperations(phaseId, strategy, target, completed) {
994
- const retained = [];
995
- const cleanupWarnings = [];
996
- const operations = [];
997
- for (const op of [...completed].reverse()) {
998
- if (op.mutationClass === 'azure-provider-register') {
999
- retained.push(`${op.phaseId}:${op.actionId}:provider-registration`);
1000
- continue;
1001
- }
1002
- if (op.actionId.toLowerCase().includes('unregister')) {
1003
- cleanupWarnings.push(`Refused to generate provider unregister rollback for ${op.actionId}.`);
1004
- continue;
1005
- }
1006
- if (op.mutationClass === 'github-ruleset-write') {
1007
- operations.push({
1008
- adapter: 'github',
1009
- actionId: 'github.ruleset.disable',
1010
- mutationClass: 'github-ruleset-write',
1011
- phaseId,
1012
- inputs: { fromOperation: op.actionId, cannotExpandScope: true },
1013
- destination: op.destination,
1014
- remote: true,
1015
- destructive: false
1016
- });
1017
- continue;
1018
- }
1019
- if (op.mutationClass === 'azure-network-provision' || op.mutationClass === 'azure-resource-provision') {
1020
- operations.push({
1021
- adapter: 'azure-opentofu',
1022
- actionId: 'azure.resource.cleanup',
1023
- mutationClass: op.mutationClass,
1024
- phaseId,
1025
- inputs: { fromOperation: op.actionId, noProviderUnregister: true },
1026
- destination: op.destination,
1027
- remote: true,
1028
- destructive: true
1029
- });
1030
- continue;
1031
- }
1032
- if (op.mutationClass === 'write-local-state' || op.mutationClass === 'delete-local-state') {
1033
- operations.push({
1034
- adapter: 'local-state',
1035
- actionId: 'local.state.rollback',
1036
- mutationClass: op.mutationClass,
1037
- phaseId,
1038
- inputs: { fromOperation: op.actionId },
1039
- destination: op.destination,
1040
- remote: false,
1041
- destructive: op.mutationClass === 'delete-local-state'
1042
- });
1043
- }
1044
- }
1045
- return {
1046
- phaseId,
1047
- strategy,
1048
- target,
1049
- operations,
1050
- retained,
1051
- cleanupWarnings
1052
- };
1053
- }
1054
- export function planDigestFor(input) {
1055
- return canonicalSha256({
1056
- phaseId: input.phase.id,
1057
- transitionDigest: input.transitionDigest,
1058
- approvalPlanDigest: input.approvalPlanDigest,
1059
- operations: input.operations
1060
- });
1061
- }
1062
- function stateHash(inspection) {
1063
- return inspection.loadedState?.contentHash ?? null;
1064
- }
1065
- function assertNoSecrets(plan) {
1066
- const scan = detectCredentialLeaks([{
1067
- source: 'generated-artifact',
1068
- label: 'governance transition plan',
1069
- text: canonicalJson(plan)
1070
- }]);
1071
- if (scan.status === 'compromised') {
1072
- throw new Error(`Governance transition plan contains credential-shaped content: ${scan.leaks.map((leak) => leak.pattern).join(', ')}.`);
1073
- }
1074
- }
1075
- export async function buildSavedTransitionPlan(input) {
1076
- const phaseId = input.inspection.readiness.nextReadyPhase;
1077
- if (!phaseId) {
1078
- return null;
1079
- }
1080
- const runner = input.runner ?? new NodeCommandRunner();
1081
- const phase = phaseById(input.inspection.graph, phaseId);
1082
- const createdAt = (input.now ?? new Date()).toISOString();
1083
- const expiresAt = new Date(Date.parse(createdAt) + planLifetimeMs).toISOString();
1084
- const context = input.inspection.contexts[phaseId];
1085
- const approvalPlan = transitionPlanForPhase(phase, input.inspection.state, context.transition, input.inspection.projectRoot);
1086
- const evaluation = evaluateApprovalForTransitionPlan(approvalPlan, input.inspection.approvals, {
1087
- now: input.now
1088
- });
1089
- const operations = await phaseOperations(input.inspection, phase, runner, createdAt);
1090
- const digest = planDigestFor({
1091
- phase,
1092
- transitionDigest: context.transition.transitionDigest,
1093
- operations,
1094
- approvalPlanDigest: approvalPlan.planDigest
1095
- });
1096
- const plan = validateSavedTransitionPlan({
1097
- schemaVersion: 1,
1098
- phaseId,
1099
- createdAt,
1100
- expiresAt,
1101
- identity: input.inspection.state.identity,
1102
- graphHash: input.inspection.graphHash,
1103
- stateHash: stateHash(input.inspection),
1104
- baselineDigest: context.baselineSha,
1105
- inputDigest: context.inputDigest,
1106
- transitionDigest: context.transition.transitionDigest,
1107
- planDigest: digest,
1108
- mutationClasses: phase.allowedMutations,
1109
- operations,
1110
- approval: {
1111
- gateKind: phase.approvalGate.kind,
1112
- required: phase.approvalGate.required,
1113
- evaluation,
1114
- envelopeId: evaluation.envelopeId,
1115
- envelopeHash: evaluation.envelopeHash
1116
- },
1117
- rollbackPlan: rollbackPlanForPhase(phase),
1118
- noSecrets: true
1119
- });
1120
- assertPlanOperationsAllowed(plan, phase);
1121
- assertNoSecrets(plan);
1122
- return plan;
1123
- }
1124
- export async function previewApplyNext(input) {
1125
- let plan = null;
1126
- const blockers = [];
1127
- try {
1128
- plan = await buildSavedTransitionPlan(input);
1129
- }
1130
- catch (error) {
1131
- blockers.push(errorMessage(error));
1132
- }
1133
- const nextReadyPhase = input.inspection.readiness.nextReadyPhase;
1134
- if (!nextReadyPhase || blockers.length > 0 || !plan) {
1135
- return {
1136
- schemaVersion: 1,
1137
- command: 'governance apply-next',
1138
- projectRoot: input.inspection.projectRoot,
1139
- execute: input.execute,
1140
- applied: false,
1141
- authorized: false,
1142
- reason: 'blocked',
1143
- message: blockers[0] ?? 'No phase is ready for execution.',
1144
- selectedPhase: nextReadyPhase,
1145
- nextReadyPhase,
1146
- approval: plan?.approval ?? null,
1147
- proposedMutations: {
1148
- local: plan?.mutationClasses.local ?? ['none'],
1149
- remote: plan?.mutationClasses.remote ?? ['none'],
1150
- operations: plan?.operations ?? []
1151
- },
1152
- savedPlan: null,
1153
- noWrites: true,
1154
- blockers: blockers.length > 0 ? blockers : ['No phase is ready for execution.']
1155
- };
1156
- }
1157
- if (plan.approval.evaluation.approvalRequired) {
1158
- return {
1159
- schemaVersion: 1,
1160
- command: 'governance apply-next',
1161
- projectRoot: input.inspection.projectRoot,
1162
- execute: input.execute,
1163
- applied: false,
1164
- authorized: false,
1165
- reason: 'approval-required',
1166
- message: plan.approval.evaluation.reasons.join('; '),
1167
- selectedPhase: nextReadyPhase,
1168
- nextReadyPhase,
1169
- approval: plan.approval,
1170
- proposedMutations: {
1171
- local: plan.mutationClasses.local,
1172
- remote: plan.mutationClasses.remote,
1173
- operations: plan.operations
1174
- },
1175
- savedPlan: null,
1176
- noWrites: true,
1177
- blockers: plan.approval.evaluation.reasons
1178
- };
1179
- }
1180
- return {
1181
- schemaVersion: 1,
1182
- command: 'governance apply-next',
1183
- projectRoot: input.inspection.projectRoot,
1184
- execute: input.execute,
1185
- applied: false,
1186
- authorized: true,
1187
- reason: input.execute ? 'execute-requested' : 'execute-required',
1188
- message: input.execute
1189
- ? 'Execution requested; the plan must be saved and revalidated before any mutation.'
1190
- : 'Preview only. Rerun with --execute to save this plan and execute at most one phase.',
1191
- selectedPhase: nextReadyPhase,
1192
- nextReadyPhase,
1193
- approval: plan.approval,
1194
- proposedMutations: {
1195
- local: plan.mutationClasses.local,
1196
- remote: plan.mutationClasses.remote,
1197
- operations: plan.operations
1198
- },
1199
- savedPlan: null,
1200
- noWrites: true,
1201
- blockers: []
1202
- };
1203
- }
1204
- async function saveTransitionPlan(projectRoot, plan) {
1205
- const validated = validateSavedTransitionPlan(plan);
1206
- assertNoSecrets(validated);
1207
- const pathParts = transitionPlanPathParts(validated);
1208
- const existing = await readProjectFile(projectRoot, pathParts);
1209
- if (existing !== undefined) {
1210
- throw new Error(`Refusing to overwrite existing governance transition plan ${pathParts.join('/')}.`);
1211
- }
1212
- const content = `${canonicalJson(validated)}\n`;
1213
- await applyProjectFileTransaction(projectRoot, [{
1214
- type: 'write',
1215
- pathParts,
1216
- content
1217
- }]);
1218
- return { pathParts, digest: canonicalSha256(validated) };
1219
- }
1220
- function comparePlanFreshness(saved, fresh) {
1221
- if (!fresh) {
1222
- return ['No phase remained ready after saving the transition plan.'];
1223
- }
1224
- const issues = [];
1225
- if (saved.phaseId !== fresh.phaseId) {
1226
- issues.push(`Ready phase changed from ${saved.phaseId} to ${fresh.phaseId}.`);
1227
- }
1228
- for (const field of ['stateHash', 'baselineDigest', 'inputDigest', 'transitionDigest']) {
1229
- if (saved[field] !== fresh[field]) {
1230
- issues.push(`${field} changed after plan save.`);
1231
- }
1232
- }
1233
- if (canonicalSha256(saved.operations) !== canonicalSha256(fresh.operations)) {
1234
- issues.push('Proposed operations changed after plan save.');
1235
- }
1236
- if (saved.approval.evaluation.approvalRequired || fresh.approval.evaluation.approvalRequired) {
1237
- issues.push('Approval was not valid immediately before execution.');
1238
- }
1239
- return issues;
1240
- }
1241
- function evidenceHeaderFor(input) {
1242
- const context = input.inspection.contexts[input.phase.id];
1243
- return validateEvidenceHeader({
1244
- schemaVersion: input.inspection.state.identity.evidenceHeaderSchemaVersion,
1245
- repositoryId: input.inspection.state.repository.id,
1246
- identity: input.inspection.state.identity,
1247
- phaseGraphHash: input.inspection.state.identity.phaseGraphHash,
1248
- phaseId: input.phase.id,
1249
- phaseContractDigest: phaseContractDigests(input.inspection.graph)[input.phase.id],
1250
- inputDigest: input.plan.inputDigest,
1251
- baselineSha: input.plan.baselineDigest,
1252
- transition: context.transition,
1253
- producedAt: input.now.toISOString(),
1254
- producer: engineProducer,
1255
- result: input.result
1256
- });
1257
- }
1258
- function appendUnique(values, value) {
1259
- if (value === null || values.includes(value)) {
1260
- return [...values];
1261
- }
1262
- return [...values, value];
1263
- }
1264
- function nextStateForOutcome(input) {
1265
- const base = cloneState(input.override ?? input.inspection.state);
1266
- if (input.inspection.sourceOfTruth.status === 'selected' &&
1267
- input.inspection.sourceOfTruth.recordActiveChangeOnNextMutation) {
1268
- const selected = stateWithSelectedActiveChange(base, input.inspection.sourceOfTruth.selected);
1269
- base.activeChange = selected.activeChange;
1270
- }
1271
- const phaseState = input.resultState === 'approved' ? 'approved' : input.resultState;
1272
- base.phases[input.phase.id] = {
1273
- state: phaseState,
1274
- updatedAt: input.now.toISOString(),
1275
- evidence: input.evidenceReference
1276
- ? [...base.phases[input.phase.id].evidence, input.evidenceReference]
1277
- : base.phases[input.phase.id].evidence,
1278
- approvals: appendUnique(base.phases[input.phase.id].approvals, input.plan.approval.envelopeId),
1279
- blockers: input.blocker ? [input.blocker] : []
1280
- };
1281
- base.updatedAt = input.now.toISOString();
1282
- return validateUserActivationState(base);
1283
- }
1284
- function blockedState(input) {
1285
- const base = cloneState(input.inspection.state);
1286
- base.phases[input.phase.id] = {
1287
- state: 'blocked',
1288
- updatedAt: input.now.toISOString(),
1289
- evidence: base.phases[input.phase.id].evidence,
1290
- approvals: appendUnique(base.phases[input.phase.id].approvals, input.plan.approval.envelopeId),
1291
- blockers: [input.blocker]
1292
- };
1293
- base.updatedAt = input.now.toISOString();
1294
- return validateUserActivationState(base);
1295
- }
1296
- async function assertLoadedStateHash(projectRoot, expectedHash) {
1297
- const bytes = await readProjectFile(projectRoot, activationStatePathParts());
1298
- const currentHash = bytes === undefined ? null : activationStateContentHash(bytes);
1299
- if (currentHash !== expectedHash) {
1300
- throw new Error(`Activation state changed after inspection: expected ${expectedHash ?? 'absent'}, found ${currentHash ?? 'absent'}.`);
1301
- }
1302
- }
1303
- async function writeOutcomeTransaction(input) {
1304
- await assertLoadedStateHash(input.projectRoot, input.plan.stateHash);
1305
- const stateContent = `${canonicalJson(validateUserActivationState(input.nextState))}\n`;
1306
- const mutations = [
1307
- ...(input.fileMutations ?? []).map((mutation) => ({
1308
- ...mutation,
1309
- pathParts: validateArtifactPathParts([...mutation.pathParts], 'Governance transition file mutation path')
1310
- })),
1311
- ...(input.evidenceRecord && input.evidencePathParts
1312
- ? [{
1313
- type: 'write',
1314
- pathParts: [...input.evidencePathParts],
1315
- content: `${canonicalJson(input.evidenceRecord)}\n`
1316
- }]
1317
- : []),
1318
- {
1319
- type: 'write',
1320
- pathParts: activationStatePathParts(),
1321
- content: stateContent
1322
- }
1323
- ];
1324
- if (input.evidencePathParts) {
1325
- const existing = await readProjectFile(input.projectRoot, [...input.evidencePathParts]);
1326
- if (existing !== undefined) {
1327
- throw new Error(`Refusing to overwrite existing governance evidence ${input.evidencePathParts.join('/')}.`);
1328
- }
1329
- }
1330
- await applyProjectFileTransaction(input.projectRoot, mutations);
1331
- return {
1332
- stateHash: activationStateContentHash(stateContent),
1333
- evidence: input.evidenceRecord && input.evidencePathParts
1334
- ? {
1335
- evidenceId: input.evidenceRecord.evidenceId,
1336
- pathParts: input.evidencePathParts,
1337
- headerDigest: evidenceHeaderDigest(input.evidenceRecord.header),
1338
- result: input.evidenceRecord.header.result
1339
- }
1340
- : null
1341
- };
1342
- }
1343
- async function executeGitOperations(input, operations) {
1344
- if (input.phase.id !== 'committed' && input.phase.id !== 'pushed') {
1345
- return null;
1346
- }
1347
- const completed = [];
1348
- for (const op of operations.filter((entry) => entry.adapter === 'git')) {
1349
- if (op.actionId === 'git.verify-existing-commit' || op.actionId === 'git.verify-existing-push') {
1350
- completed.push(op);
1351
- continue;
1352
- }
1353
- if (op.actionId === 'git.init') {
1354
- const result = await runGit(input.runner, input.inspection.projectRoot, ['init', '-b', String(op.inputs.branch)]);
1355
- if (!commandSucceeded(result)) {
1356
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1357
- }
1358
- completed.push(op);
1359
- }
1360
- else if (op.actionId === 'git.add-reviewed') {
1361
- const paths = Array.isArray(op.inputs.paths) ? op.inputs.paths.map(String) : [];
1362
- if (paths.length === 0) {
1363
- completed.push(op);
1364
- continue;
1365
- }
1366
- const result = await runGit(input.runner, input.inspection.projectRoot, ['add', '--', ...paths]);
1367
- if (!commandSucceeded(result)) {
1368
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1369
- }
1370
- completed.push(op);
1371
- }
1372
- else if (op.actionId === 'git.commit-reviewed') {
1373
- const message = String(op.inputs.message);
1374
- const result = await runGit(input.runner, input.inspection.projectRoot, ['commit', '-m', message]);
1375
- if (!commandSucceeded(result)) {
1376
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1377
- }
1378
- completed.push(op);
1379
- }
1380
- else if (op.actionId === 'git.push-approved-ref') {
1381
- const branch = String(op.inputs.branch);
1382
- const result = await runGit(input.runner, input.inspection.projectRoot, ['push', 'origin', `HEAD:refs/heads/${branch}`]);
1383
- if (!commandSucceeded(result)) {
1384
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1385
- }
1386
- completed.push(op);
1387
- }
1388
- }
1389
- return {
1390
- status: 'completed',
1391
- resultState: 'verified',
1392
- completedOperations: completed
1393
- };
1394
- }
1395
- async function executeSeedOperations(input) {
1396
- if (!input.phase.id.startsWith('seed-')) {
1397
- return null;
1398
- }
1399
- if (input.phase.id === 'seed-valid') {
1400
- const result = await validateGeneratedSeedForPhase(input.inspection.projectRoot, input.runner);
1401
- if (result.status === 'blocked') {
1402
- return { status: 'blocked', blocker: result.issues[0] ?? 'Seed validation failed.', completedOperations: [] };
1403
- }
1404
- return { status: 'completed', resultState: 'verified', completedOperations: input.plan.operations.filter((op) => op.actionId === 'openspec.seed.validate') };
1405
- }
1406
- if (input.phase.id === 'seed-verified') {
1407
- const result = await verifyGeneratedSeedBaselineForPhase(input.inspection.projectRoot, input.runner);
1408
- if (result.status === 'blocked') {
1409
- return { status: 'blocked', blocker: result.issues[0] ?? 'Seed baseline verification failed.', completedOperations: [] };
1410
- }
1411
- return {
1412
- status: 'completed',
1413
- resultState: 'verified',
1414
- evidencePayload: {
1415
- kind: 'seed-verified.v1',
1416
- checks: result.checks.map((check) => ({
1417
- id: check.id,
1418
- taskId: check.taskId,
1419
- status: check.status
1420
- }))
1421
- },
1422
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'openspec.seed.baseline-verify')
1423
- };
1424
- }
1425
- const result = await archiveGeneratedSeedForPhase(input.inspection.projectRoot, input.runner);
1426
- if (result.status === 'blocked') {
1427
- return {
1428
- status: 'blocked',
1429
- blocker: result.issues[0] ?? 'Seed archive failed.',
1430
- completedOperations: result.archiveCompleted
1431
- ? input.plan.operations.filter((op) => op.actionId === 'openspec.seed.archive')
1432
- : [],
1433
- retryableWithoutStateMutation: result.retryableAfterRepair
1434
- };
33
+ async function executeBuiltInPhase(input, localRevalidation = false) {
34
+ if (localRevalidation) {
35
+ const outcome = await executeSeedOperations(input, { localRevalidation: true });
36
+ if (!outcome)
37
+ throw new Error(`Local revalidation cannot execute ${input.phase.id}.`);
38
+ return outcome;
1435
39
  }
1436
- return {
1437
- status: 'completed',
1438
- resultState: 'verified',
1439
- evidencePayload: {
1440
- kind: 'seed-archived.v1',
1441
- archiveSyncBehavior: result.archiveSyncBehavior ?? null
1442
- },
1443
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'openspec.seed.archive')
1444
- };
1445
- }
1446
- async function discoverPhase0(input) {
1447
- if (input.phase.id !== 'phase-0-complete') {
1448
- return null;
1449
- }
1450
- const gh = await runCommand(input.runner, {
1451
- executable: 'gh',
1452
- args: ['repo', 'view', '--json', 'id,nameWithOwner,defaultBranchRef,isPrivate']
1453
- }, input.inspection.projectRoot);
1454
- if (!commandSucceeded(gh)) {
1455
- return {
1456
- status: 'blocked',
1457
- blocker: `Phase 0 GitHub read-only discovery failed: ${commandFailure(gh)}`,
1458
- completedOperations: []
1459
- };
1460
- }
1461
- let repo;
1462
- try {
1463
- repo = JSON.parse(gh.stdout);
1464
- }
1465
- catch (error) {
1466
- return {
1467
- status: 'blocked',
1468
- blocker: `Phase 0 GitHub discovery returned invalid JSON: ${errorMessage(error)}`,
1469
- completedOperations: []
1470
- };
1471
- }
1472
- if (!repo.id || !repo.nameWithOwner || !repo.defaultBranchRef?.name) {
1473
- return {
1474
- status: 'blocked',
1475
- blocker: 'Phase 0 GitHub discovery did not return repository id, nameWithOwner, and default branch.',
1476
- completedOperations: []
1477
- };
1478
- }
1479
- const az = await runCommand(input.runner, {
1480
- executable: 'az',
1481
- args: ['account', 'show', '--output', 'json']
1482
- }, input.inspection.projectRoot);
1483
- const azureAccount = commandSucceeded(az) ? safeJson(az.stdout) : null;
1484
- const git = await inspectGitRepository(input.inspection.projectRoot, input.runner);
1485
- const baselineDigest = canonicalSha256({
1486
- gitHead: git.head,
1487
- repositoryId: repo.id,
1488
- repositoryName: repo.nameWithOwner,
1489
- defaultBranch: repo.defaultBranchRef.name
1490
- });
1491
- const facts = {
1492
- repositoryId: repo.id,
1493
- repositoryName: repo.nameWithOwner,
1494
- defaultBranch: repo.defaultBranchRef.name,
1495
- baselineDigest,
1496
- privateStagingDast: false,
1497
- credentialRequired: false,
1498
- statePath: 'none',
1499
- approvedFacts: [
1500
- { id: 'repository.id', value: repo.id },
1501
- { id: 'repository.nameWithOwner', value: repo.nameWithOwner },
1502
- { id: 'repository.defaultBranch', value: repo.defaultBranchRef.name },
1503
- { id: 'repository.isPrivate', value: repo.isPrivate ?? null },
1504
- { id: 'azure.accountReadable', value: azureAccount !== null }
1505
- ]
1506
- };
1507
- const nextState = cloneState(input.inspection.state);
1508
- nextState.repository = {
1509
- id: facts.repositoryId,
1510
- name: facts.repositoryName,
1511
- defaultBranch: facts.defaultBranch
1512
- };
1513
- nextState.applicability = {
1514
- statePath: facts.statePath,
1515
- privateStagingDast: facts.privateStagingDast,
1516
- credentialRequired: facts.credentialRequired
1517
- };
1518
- return {
1519
- status: 'completed',
1520
- resultState: 'verified',
1521
- evidencePayload: {
1522
- kind: 'phase-0-discovery.v1',
1523
- facts: facts.approvedFacts,
1524
- activationCreateChangePlan: {
1525
- deterministic: true,
1526
- graphHash: input.inspection.graphHash
1527
- }
1528
- },
1529
- stateOverride: nextState,
1530
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'github.phase0.discover' || op.actionId === 'azure.phase0.discover')
1531
- };
1532
- }
1533
- function safeJson(value) {
1534
- try {
1535
- return JSON.parse(value);
1536
- }
1537
- catch {
1538
- return null;
1539
- }
1540
- }
1541
- async function executeActivationApproval(input) {
1542
- if (input.phase.id !== 'activation-approved') {
1543
- return null;
1544
- }
1545
- const state = cloneState(input.inspection.state);
1546
- const fileMutations = [];
1547
- if (input.inspection.sourceOfTruth.status === 'none') {
1548
- const facts = buildApprovedPhase0FactsFromState(input.inspection.manifest, input.inspection.state, input.inspection.evidence);
1549
- if (!facts || input.inspection.sourceOfTruth.createPlan.status !== 'ready') {
1550
- return {
1551
- status: 'blocked',
1552
- blocker: input.inspection.sourceOfTruth.createPlan.reason,
1553
- completedOperations: []
1554
- };
1555
- }
1556
- const writePlan = renderGovernanceChangeWritePlan(facts);
1557
- for (const file of writePlan.files) {
1558
- const existing = await readProjectFile(input.inspection.projectRoot, [...file.pathParts]);
1559
- if (existing !== undefined) {
1560
- return {
1561
- status: 'blocked',
1562
- blocker: `Refusing to overwrite existing governance artifact ${file.pathParts.join('/')}.`,
1563
- completedOperations: []
1564
- };
1565
- }
1566
- fileMutations.push({
1567
- type: 'write',
1568
- pathParts: [...file.pathParts],
1569
- content: file.content
1570
- });
1571
- }
1572
- state.activeChange = {
1573
- id: writePlan.changeId,
1574
- kind: writePlan.workflowKind === 'openspec' ? 'openspec' : 'spec-kit'
1575
- };
1576
- }
1577
- else if (input.inspection.sourceOfTruth.status === 'selected') {
1578
- state.activeChange = {
1579
- id: input.inspection.sourceOfTruth.selected.changeId,
1580
- kind: input.inspection.sourceOfTruth.selected.workflowKind === 'openspec' ? 'openspec' : 'spec-kit'
1581
- };
1582
- }
1583
- else {
1584
- return {
1585
- status: 'blocked',
1586
- blocker: 'A compatible governance source of truth is required before activation approval can be recorded.',
1587
- completedOperations: []
1588
- };
1589
- }
1590
- return {
1591
- status: 'completed',
1592
- resultState: 'approved',
1593
- stateOverride: state,
1594
- fileMutations,
1595
- completedOperations: input.plan.operations.filter((op) => op.actionId.endsWith('governance.create-change'))
1596
- };
1597
- }
1598
- async function executeCredentialReady(input) {
1599
- if (input.phase.id !== 'credential-ready') {
1600
- return null;
1601
- }
1602
- const policy = await readProjectFile(input.inspection.projectRoot, [...credentialPolicyPathParts]);
1603
- if (policy === undefined) {
1604
- return {
1605
- status: 'blocked',
1606
- blocker: 'Credential enrollment requires a secure masked input channel or an existing selected-repository App; automated noninteractive apply cannot collect a PAT.',
1607
- completedOperations: []
1608
- };
1609
- }
1610
- const scan = detectCredentialLeaks([{
1611
- source: 'imported-evidence',
1612
- label: credentialPolicyPathParts.join('/'),
1613
- text: policy.toString('utf8')
1614
- }]);
1615
- if (scan.status === 'compromised') {
1616
- return {
1617
- status: 'blocked',
1618
- blocker: scan.guidance.join(' '),
1619
- completedOperations: []
1620
- };
1621
- }
1622
- return {
1623
- status: 'completed',
1624
- resultState: 'verified',
1625
- evidencePayload: {
1626
- kind: 'credential-ready.v1',
1627
- policyPathParts: credentialPolicyPathParts,
1628
- payloadFree: true,
1629
- policyDigest: canonicalSha256(policy.toString('utf8'))
1630
- },
1631
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'github.credential.verify-policy')
1632
- };
1633
- }
1634
- function remoteImportRetention(input) {
1635
- if (input.phase.id !== 'remote-ready') {
1636
- return null;
1637
- }
1638
- if (input.inspection.state.applicability.statePath !== 'bootstrap-local') {
1639
- return {
1640
- status: 'completed',
1641
- resultState: 'verified',
1642
- evidencePayload: { kind: 'remote-ready.v1', retention: 'not-applicable' },
1643
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'azure.remote-ready.verify')
1644
- };
1645
- }
1646
- const importRecord = latestRecordWithPayload(input.inspection, 'remote-import-verified');
1647
- if (!importRecord || !isRecord(importRecord.payload) || importRecord.payload.kind !== remoteImportPayloadKind) {
1648
- return {
1649
- status: 'blocked',
1650
- blocker: 'Remote import retention requires immutable remote-import evidence with state and key identifiers.',
1651
- completedOperations: []
1652
- };
1653
- }
1654
- const encryptedStatePathParts = pathPartLists(importRecord.payload.encryptedStatePathParts, 'encryptedStatePathParts');
1655
- const encryptionKeyPathParts = pathPartLists(importRecord.payload.encryptionKeyPathParts, 'encryptionKeyPathParts');
1656
- const retainedAt = input.now.toISOString();
1657
- const disposeAfter = new Date(input.now.getTime() + 30 * dayMs).toISOString();
1658
- const state = cloneState(input.inspection.state);
1659
- state.bootstrapState = {
1660
- status: 'retained',
1661
- remoteImportEvidenceId: importRecord.evidenceId,
1662
- remoteImportEvidenceDigest: evidenceHeaderDigest(importRecord.header),
1663
- retainedAt,
1664
- disposeAfter,
1665
- encryptedStatePathParts,
1666
- encryptionKeyPathParts
1667
- };
1668
- return {
1669
- status: 'completed',
1670
- resultState: 'retained',
1671
- stateOverride: state,
1672
- evidencePayload: {
1673
- kind: 'remote-ready.v1',
1674
- retention: state.bootstrapState
1675
- },
1676
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'azure.remote-ready.verify')
1677
- };
1678
- }
1679
- function pathPartLists(value, label) {
1680
- if (!Array.isArray(value)) {
1681
- throw new Error(`${label} must be an array of path-part arrays.`);
1682
- }
1683
- return value.map((entry, index) => validateArtifactPathParts(entry, `${label}[${index}]`));
1684
- }
1685
- async function executeBootstrapStateDisposal(input) {
1686
- if (input.phase.id !== 'bootstrap-state-disposed') {
1687
- return null;
1688
- }
1689
- const retention = input.inspection.state.bootstrapState;
1690
- if (!retention || input.inspection.state.applicability.statePath !== 'bootstrap-local') {
1691
- return {
1692
- status: 'completed',
1693
- resultState: 'inapplicable',
1694
- evidencePayload: { kind: bootstrapStateDisposedPayloadKind, reason: 'no retained bootstrap state' },
1695
- completedOperations: []
1696
- };
1697
- }
1698
- if (retention.status === 'disposed') {
1699
- return {
1700
- status: 'completed',
1701
- resultState: 'disposed',
1702
- evidencePayload: { kind: bootstrapStateDisposedPayloadKind, reason: 'already disposed', deletionEvidenceId: retention.deletionEvidenceId ?? null },
1703
- completedOperations: []
1704
- };
1705
- }
1706
- if (Date.parse(retention.disposeAfter) > input.now.getTime()) {
1707
- return {
1708
- status: 'blocked',
1709
- blocker: `Retained bootstrap state is not disposable until ${retention.disposeAfter}.`,
1710
- completedOperations: []
1711
- };
1712
- }
1713
- const importRecord = input.inspection.evidence.find((record) => record.evidenceId === retention.remoteImportEvidenceId);
1714
- if (!importRecord || evidenceHeaderDigest(importRecord.header) !== retention.remoteImportEvidenceDigest) {
1715
- return {
1716
- status: 'blocked',
1717
- blocker: 'Destructive disposal requires the immutable remote-import evidence referenced by retained bootstrap state.',
1718
- completedOperations: []
1719
- };
1720
- }
1721
- if (!isRecord(importRecord.payload) || importRecord.payload.kind !== remoteImportPayloadKind) {
1722
- return {
1723
- status: 'blocked',
1724
- blocker: 'Destructive disposal requires verified remote backend and no-change evidence.',
1725
- completedOperations: []
1726
- };
1727
- }
1728
- const remoteBackendDigest = importRecord.payload.remoteBackendDigest;
1729
- const noChangePlanDigest = importRecord.payload.noChangePlanDigest;
1730
- if (typeof remoteBackendDigest !== 'string' ||
1731
- !/^[a-f0-9]{64}$/u.test(remoteBackendDigest) ||
1732
- typeof noChangePlanDigest !== 'string' ||
1733
- !/^[a-f0-9]{64}$/u.test(noChangePlanDigest)) {
1734
- return {
1735
- status: 'blocked',
1736
- blocker: 'Destructive disposal requires payload-free remote backend and no-change plan digests.',
1737
- completedOperations: []
1738
- };
1739
- }
1740
- const allPaths = [...retention.encryptedStatePathParts, ...retention.encryptionKeyPathParts]
1741
- .map((parts) => validateArtifactPathParts([...parts], 'Bootstrap disposal path'));
1742
- const incompleteCleanup = [];
1743
- for (const parts of allPaths) {
1744
- try {
1745
- const target = await resolveProjectPath(input.inspection.projectRoot, parts);
1746
- const details = await stat(target);
1747
- if (!details.isFile()) {
1748
- incompleteCleanup.push(`${parts.join('/')} is not a regular file.`);
1749
- }
1750
- }
1751
- catch (error) {
1752
- incompleteCleanup.push(`${parts.join('/')} was already absent before disposal: ${errorMessage(error)}`);
1753
- }
1754
- }
1755
- const state = cloneState(input.inspection.state);
1756
- state.bootstrapState = {
1757
- ...retention,
1758
- status: 'disposed',
1759
- disposedAt: input.now.toISOString(),
1760
- deletionEvidenceId: `${input.phase.id}-${safeTimestamp(input.now.toISOString())}`,
1761
- incompleteCleanup
1762
- };
1763
- return {
1764
- status: 'completed',
1765
- resultState: 'disposed',
1766
- stateOverride: state,
1767
- evidencePayload: {
1768
- kind: bootstrapStateDisposedPayloadKind,
1769
- deletedPathParts: allPaths,
1770
- remoteBackendDigest,
1771
- noChangePlanDigest,
1772
- incompleteCleanup,
1773
- payloadFree: true
1774
- },
1775
- fileMutations: allPaths.map((parts) => ({ type: 'delete', pathParts: parts })),
1776
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'local.bootstrap-state.dispose'),
1777
- cleanupWarnings: incompleteCleanup
1778
- };
1779
- }
1780
- function assertGreenRedProof(input) {
1781
- const record = latestRecordWithPayload(input.inspection, 'green-red-proof');
1782
- if (!record || !isRecord(record.payload) || record.payload.kind !== greenRedPayloadKind) {
1783
- throw new Error('Ruleset enforcement requires current green-red-proof evidence with typed proof payload.');
1784
- }
1785
- const green = isRecord(record.payload.green) ? record.payload.green : null;
1786
- const red = isRecord(record.payload.deliberateRed) ? record.payload.deliberateRed : null;
1787
- const greenConclusion = green?.conclusion;
1788
- const redConclusion = red?.conclusion;
1789
- const badConclusions = [greenConclusion, redConclusion].filter((value) => value === 'skipped' || value === 'cancelled' || value === 'neutral');
1790
- if (badConclusions.length > 0) {
1791
- throw new Error('Skipped, cancelled, and neutral checks cannot satisfy green/red proof.');
1792
- }
1793
- if (!red || greenConclusion !== 'success' || redConclusion !== 'failure' || red.deliberate !== true) {
1794
- throw new Error('Ruleset enforcement requires green success and deliberate red failure proof.');
1795
- }
1796
- return record;
1797
- }
1798
- async function executeRulesetPhase(input) {
1799
- if (input.phase.id !== 'rulesets-applied' && input.phase.id !== 'live-readback') {
1800
- return null;
1801
- }
1802
- try {
1803
- assertGreenRedProof(input);
1804
- }
1805
- catch (error) {
1806
- return { status: 'blocked', blocker: errorMessage(error), completedOperations: [] };
1807
- }
1808
- const sourceDigest = rulesetSourceDigestFromEvidence(input.inspection);
1809
- if (!sourceDigest) {
1810
- return {
1811
- status: 'blocked',
1812
- blocker: 'Ruleset enforcement requires a saved ruleset source digest from workflow-source-ready evidence.',
1813
- completedOperations: []
1814
- };
1815
- }
1816
- const adapter = input.adapters.githubRulesets;
1817
- if (!adapter) {
1818
- return {
1819
- status: 'blocked',
1820
- blocker: 'No injected GitHub ruleset adapter is configured; refusing live ruleset calls in this execution context.',
1821
- completedOperations: []
1822
- };
1823
- }
1824
- const envelopeId = input.plan.approval.envelopeId;
1825
- if (input.phase.id === 'rulesets-applied' && !envelopeId) {
1826
- return {
1827
- status: 'blocked',
1828
- blocker: 'Ruleset enforcement requires a persisted enforcement approval envelope.',
1829
- completedOperations: []
1830
- };
1831
- }
1832
- const completed = [];
1833
- const write = input.phase.id === 'rulesets-applied'
1834
- ? await adapter.applyRuleset({
1835
- repository: input.inspection.state.repository.name,
1836
- sourceDigest,
1837
- approvalEnvelopeId: envelopeId ?? 'ungated-readback'
1838
- })
1839
- : await adapter.readRuleset({
1840
- repository: input.inspection.state.repository.name,
1841
- sourceDigest
1842
- });
1843
- completed.push(...input.plan.operations.filter((op) => input.phase.id === 'rulesets-applied'
1844
- ? op.actionId === 'github.ruleset.apply' || op.actionId === 'github.ruleset.readback'
1845
- : op.actionId === 'github.ruleset.readback'));
1846
- if (write.sourceDigest !== sourceDigest || write.readbackDigest !== sourceDigest) {
1847
- return {
1848
- status: 'blocked',
1849
- blocker: 'Post-write live ruleset readback did not match the saved source digest.',
1850
- completedOperations: completed
1851
- };
1852
- }
1853
- const header = evidenceHeaderFor({
1854
- inspection: input.inspection,
1855
- phase: input.phase,
1856
- plan: input.plan,
1857
- result: 'verified',
1858
- now: input.now
1859
- });
1860
- const liveReadback = {
1861
- schemaVersion: input.inspection.state.identity.evidenceHeaderSchemaVersion,
1862
- repositoryId: header.repositoryId,
1863
- identity: header.identity,
1864
- phaseGraphHash: header.phaseGraphHash,
1865
- phaseId: header.phaseId,
1866
- baselineSha: header.baselineSha,
1867
- inputDigest: header.inputDigest,
1868
- transition: header.transition,
1869
- observedAt: input.now.toISOString(),
1870
- provider: 'github',
1871
- resourceType: 'ruleset',
1872
- resourceId: write.resourceId,
1873
- sourceDigest,
1874
- readbackDigest: write.readbackDigest,
1875
- matches: true
1876
- };
1877
- return {
1878
- status: 'completed',
1879
- resultState: 'verified',
1880
- liveReadback: [liveReadback],
1881
- evidencePayload: {
1882
- kind: `${input.phase.id}.v1`,
1883
- sourceDigest,
1884
- resourceId: write.resourceId,
1885
- readbackDigest: write.readbackDigest
1886
- },
1887
- completedOperations: completed
1888
- };
1889
- }
1890
- async function executeBuiltInPhase(input) {
1891
40
  const custom = input.adapters.phases?.[input.phase.id];
1892
- if (custom) {
41
+ if (custom)
1893
42
  return await custom.execute(input);
1894
- }
1895
- for (const attempt of [
1896
- executeSeedOperations,
1897
- async (execution) => executeGitOperations(execution, execution.plan.operations),
1898
- discoverPhase0,
1899
- executeActivationApproval,
1900
- executeCredentialReady,
1901
- async (execution) => remoteImportRetention(execution),
1902
- executeBootstrapStateDisposal,
1903
- executeRulesetPhase
1904
- ]) {
1905
- const outcome = await attempt(input);
1906
- if (outcome) {
1907
- return outcome;
1908
- }
1909
- }
1910
- return {
43
+ return await builtInExecutors[input.phase.id]?.(input) ?? {
1911
44
  status: 'blocked',
1912
45
  blocker: `No production adapter is configured for ${input.phase.id}; refusing success-shaped fallback.`,
1913
46
  completedOperations: []
1914
47
  };
1915
48
  }
1916
49
  function sourceOfTruthAllowsPhase(inspection, phaseId) {
1917
- if (phaseOrder(phaseId) <= phaseOrder('phase-0-complete')) {
50
+ if (phaseOrder(phaseId) <= phaseOrder('phase-0-complete'))
1918
51
  return null;
1919
- }
1920
52
  const source = inspection.sourceOfTruth;
1921
- if (phaseId === 'activation-approved' && (source.status === 'none' || source.status === 'selected')) {
53
+ if (phaseId === 'activation-approved' && (source.status === 'none' || source.status === 'selected'))
1922
54
  return null;
1923
- }
1924
- if (source.status === 'selected' && source.reconciliation.status === 'not-required') {
55
+ if (source.status === 'selected' && source.reconciliation.status === 'not-required')
1925
56
  return null;
1926
- }
1927
- if (source.status === 'seed-blocked') {
1928
- return source.blockers.join('; ');
1929
- }
1930
- if (source.status === 'ambiguous' || source.status === 'incompatible') {
57
+ if (source.status === 'seed-blocked' || source.status === 'ambiguous' || source.status === 'incompatible')
1931
58
  return source.blockers.join('; ');
1932
- }
1933
- if (source.status === 'none') {
59
+ if (source.status === 'none')
1934
60
  return source.createPlan.reason;
1935
- }
1936
61
  return 'Active governance source of truth is not ready.';
1937
62
  }
1938
63
  export async function executeApplyNext(input) {
64
+ validateManifestActivationForExecution(input.inspection.manifest);
65
+ if (input.localRevalidation && (!input.inspection.loadedState || input.inspection.state.repository.id === 'unbound' ||
66
+ !input.assertReviewedPlan || !input.assertProtectedInputs)) {
67
+ throw new Error('Local revalidation requires a committed anchored v2 state and exact reviewed-plan/protected-input guards.');
68
+ }
69
+ return withProjectMutationLock(input.inspection.projectRoot, async (lease) => {
70
+ await lease.assertHeld();
71
+ await input.assertProtectedInputs?.();
72
+ if (!input.inspection.loadedState && input.inspection.state.repository.id === 'unbound' &&
73
+ input.inspection.readiness.nextReadyPhase?.startsWith('seed-')) {
74
+ await initializeExecutionAnchor(input.inspection, input.now ?? input.clock?.() ?? new Date());
75
+ input = { ...input, inspection: await input.reinspect() };
76
+ }
77
+ return executeApplyNextLocked(input, lease);
78
+ });
79
+ }
80
+ async function executeApplyNextLocked(input, lease) {
1939
81
  const runner = input.runner ?? new NodeCommandRunner();
1940
82
  const adapters = input.adapters ?? {};
1941
- const now = input.now ?? new Date();
1942
- const initialPlan = await buildSavedTransitionPlan({ inspection: input.inspection, runner, now });
83
+ const clock = () => input.now ?? input.clock?.() ?? new Date();
84
+ const now = clock();
85
+ const localRevalidation = input.localRevalidation ?? false;
86
+ const initialPlan = await buildSavedTransitionPlan({ inspection: input.inspection, runner, now, localRevalidation });
1943
87
  if (!initialPlan) {
1944
- const preview = await previewApplyNext({ inspection: input.inspection, runner, now, execute: true });
88
+ const preview = await previewApplyNext({ inspection: input.inspection, runner, now, execute: true, localRevalidation });
1945
89
  return {
1946
90
  ...preview,
1947
91
  applied: false,
@@ -1957,170 +101,158 @@ export async function executeApplyNext(input) {
1957
101
  const phase = phaseById(input.inspection.graph, initialPlan.phaseId);
1958
102
  assertPlanOperationsAllowed(initialPlan, phase);
1959
103
  if (initialPlan.approval.evaluation.approvalRequired) {
1960
- const preview = await previewApplyNext({ inspection: input.inspection, runner, now, execute: true });
104
+ const preview = await previewApplyNext({ inspection: input.inspection, runner, now, execute: true, localRevalidation });
1961
105
  return {
1962
- ...preview,
1963
- applied: false,
1964
- executedPhase: null,
1965
- noWrites: false,
1966
- executedOperations: [],
1967
- evidence: null,
1968
- stateHash: null,
1969
- rollbackPlan: initialPlan.rollbackPlan,
1970
- cleanupWarnings: []
106
+ ...preview, applied: false, executedPhase: null, noWrites: false,
107
+ executedOperations: [], evidence: null, stateHash: null, rollbackPlan: initialPlan.rollbackPlan, cleanupWarnings: []
1971
108
  };
1972
109
  }
110
+ await input.assertReviewedPlan?.(initialPlan);
111
+ await input.assertProtectedInputs?.();
1973
112
  const saved = await saveTransitionPlan(input.inspection.projectRoot, initialPlan);
1974
113
  const freshInspection = await input.reinspect();
1975
- const freshPlan = await buildSavedTransitionPlan({ inspection: freshInspection, runner, now });
114
+ const freshPlan = await buildSavedTransitionPlan({ inspection: freshInspection, runner, now, localRevalidation });
1976
115
  const freshnessIssues = comparePlanFreshness(initialPlan, freshPlan);
1977
116
  if (freshnessIssues.length > 0) {
1978
117
  return {
1979
- schemaVersion: 1,
1980
- command: 'governance apply-next',
1981
- projectRoot: input.inspection.projectRoot,
1982
- execute: true,
1983
- applied: false,
1984
- authorized: false,
1985
- reason: 'stale-after-plan-save',
1986
- message: freshnessIssues.join(' '),
1987
- selectedPhase: initialPlan.phaseId,
1988
- executedPhase: null,
1989
- nextReadyPhase: freshInspection.readiness.nextReadyPhase,
118
+ schemaVersion: 1, command: 'governance apply-next', projectRoot: input.inspection.projectRoot,
119
+ execute: true, applied: false, authorized: false, reason: 'stale-after-plan-save', message: freshnessIssues.join(' '),
120
+ selectedPhase: initialPlan.phaseId, executedPhase: null, nextReadyPhase: freshInspection.readiness.nextReadyPhase,
1990
121
  approval: initialPlan.approval,
1991
- proposedMutations: {
1992
- local: initialPlan.mutationClasses.local,
1993
- remote: initialPlan.mutationClasses.remote,
1994
- operations: initialPlan.operations
1995
- },
1996
- savedPlan: saved,
1997
- noWrites: false,
1998
- blockers: freshnessIssues,
1999
- executedOperations: [],
2000
- evidence: null,
2001
- stateHash: null,
2002
- rollbackPlan: initialPlan.rollbackPlan,
2003
- cleanupWarnings: []
122
+ proposedMutations: { local: initialPlan.mutationClasses.local, remote: initialPlan.mutationClasses.remote, operations: initialPlan.operations },
123
+ savedPlan: saved, noWrites: false, blockers: freshnessIssues, executedOperations: [],
124
+ evidence: null, stateHash: null, rollbackPlan: initialPlan.rollbackPlan, cleanupWarnings: []
2004
125
  };
2005
126
  }
127
+ if (freshPlan)
128
+ await input.assertReviewedPlan?.(freshPlan);
129
+ await input.assertProtectedInputs?.();
2006
130
  const sourceBlocker = sourceOfTruthAllowsPhase(freshInspection, initialPlan.phaseId);
2007
131
  if (sourceBlocker) {
2008
132
  const nextState = blockedState({ inspection: freshInspection, phase, plan: initialPlan, blocker: sourceBlocker, now });
2009
- const write = await writeOutcomeTransaction({
2010
- projectRoot: freshInspection.projectRoot,
2011
- plan: initialPlan,
2012
- nextState
2013
- });
133
+ const write = await writeOutcomeTransaction({ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState });
2014
134
  return executionBlockedResult(freshInspection, initialPlan, saved, sourceBlocker, [], write.stateHash);
2015
135
  }
136
+ await lease.assertHeld();
2016
137
  const outcome = await executeBuiltInPhase({
2017
- inspection: freshInspection,
2018
- plan: initialPlan,
2019
- phase,
2020
- runner,
2021
- adapters,
2022
- now
2023
- });
138
+ inspection: freshInspection, plan: initialPlan, phase, runner, adapters, now,
139
+ clock, lease
140
+ }, localRevalidation);
141
+ await input.assertProtectedInputs?.();
2024
142
  const completedOperations = outcome.completedOperations ?? [];
143
+ for (const completed of completedOperations) {
144
+ assertOperationAllowed(phase, completed);
145
+ if (!initialPlan.operations.some((planned) => canonicalSha256(planned) === canonicalSha256(completed))) {
146
+ throw new Error(`Completed operation ${completed.actionId} was not in the reviewed plan.`);
147
+ }
148
+ }
149
+ for (const mutation of outcome.fileMutations ?? []) {
150
+ if (localRevalidation)
151
+ throw new Error('Local revalidation cannot persist source, spec, or seed file mutations.');
152
+ const allowed = initialPlan.operations.some((operation) => {
153
+ const prefix = operation.destination.pathParts;
154
+ return !operation.remote && prefix && prefix.every((part, index) => mutation.pathParts[index] === part) &&
155
+ ['write-seed-tasks', 'write-openspec-governance', 'write-workflows', 'write-ruleset-source', 'delete-local-state'].includes(operation.mutationClass) &&
156
+ (mutation.type !== 'delete' || operation.destructive && prefix.length === mutation.pathParts.length);
157
+ });
158
+ if (!allowed)
159
+ throw new Error(`Outcome file mutation ${mutation.pathParts.join('/')} is outside the reviewed plan destinations.`);
160
+ }
161
+ const postSnapshot = await readActivationInputSnapshot(freshInspection.projectRoot, freshInspection.manifest, runner);
162
+ if (postSnapshot.baselineSha !== initialPlan.baselineDigest || phaseInputDigest(phase.id, postSnapshot) !== initialPlan.inputDigest) {
163
+ return executionBlockedResult(freshInspection, initialPlan, saved, 'Relevant project inputs changed during execution; no outcome was persisted.', completedOperations, freshInspection.loadedState?.contentHash ?? '', outcome.cleanupWarnings ?? [], false);
164
+ }
2025
165
  if (outcome.status === 'blocked') {
2026
166
  const blocker = outcome.blocker ?? `Phase ${phase.id} blocked.`;
2027
167
  if (outcome.retryableWithoutStateMutation) {
2028
- const stateHash = freshInspection.loadedState?.contentHash ??
2029
- activationStateContentHash(canonicalJson(freshInspection.state));
168
+ const stateHash = freshInspection.loadedState?.contentHash ?? activationStateContentHash(canonicalJson(freshInspection.state));
2030
169
  return executionBlockedResult(freshInspection, initialPlan, saved, blocker, completedOperations, stateHash, outcome.cleanupWarnings ?? [], false);
2031
170
  }
2032
171
  const nextState = blockedState({ inspection: freshInspection, phase, plan: initialPlan, blocker, now });
2033
- const write = await writeOutcomeTransaction({
2034
- projectRoot: freshInspection.projectRoot,
2035
- plan: initialPlan,
2036
- nextState
2037
- });
172
+ const write = await writeOutcomeTransaction({ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState });
2038
173
  return executionBlockedResult(freshInspection, initialPlan, saved, blocker, completedOperations, write.stateHash, outcome.cleanupWarnings ?? []);
2039
174
  }
2040
175
  const resultState = outcome.resultState ?? 'verified';
2041
176
  if (!phase.terminalStates.includes(resultState)) {
2042
177
  const blocker = `Phase adapter returned ${resultState}, which is not an allowed terminal state for ${phase.id}.`;
2043
178
  const nextState = blockedState({ inspection: freshInspection, phase, plan: initialPlan, blocker, now });
2044
- const write = await writeOutcomeTransaction({
2045
- projectRoot: freshInspection.projectRoot,
2046
- plan: initialPlan,
2047
- nextState
2048
- });
179
+ const write = await writeOutcomeTransaction({ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState });
2049
180
  return executionBlockedResult(freshInspection, initialPlan, saved, blocker, completedOperations, write.stateHash, outcome.cleanupWarnings ?? []);
2050
181
  }
182
+ if (outcome.stateOverride && outcome.stateOverride.repository.id !== freshInspection.state.repository.id) {
183
+ throw new Error('A phase outcome must not replace the immutable local execution anchor.');
184
+ }
185
+ if (outcome.stateOverride) {
186
+ if (canonicalSha256(outcome.stateOverride.phases) !== canonicalSha256(freshInspection.state.phases)) {
187
+ throw new Error('A phase adapter cannot rewrite authoritative phase history or unrelated phase states.');
188
+ }
189
+ if (phase.id !== 'phase-0-complete' &&
190
+ canonicalSha256(outcome.stateOverride.remoteBinding ?? null) !== canonicalSha256(freshInspection.state.remoteBinding ?? null)) {
191
+ throw new Error('Only verified Phase 0 discovery may establish a remote repository binding.');
192
+ }
193
+ }
194
+ if (phase.approvalGate.required) {
195
+ const finalInspection = await input.reinspect();
196
+ const finalPlan = await buildSavedTransitionPlan({ inspection: finalInspection, runner, now: clock(), localRevalidation });
197
+ if (finalPlan)
198
+ await input.assertReviewedPlan?.(finalPlan);
199
+ if (!finalPlan || finalPlan.phaseId !== phase.id || finalPlan.approval.evaluation.approvalRequired ||
200
+ finalPlan.approval.envelopeHash !== initialPlan.approval.envelopeHash) {
201
+ return executionBlockedResult(freshInspection, initialPlan, saved, 'Approval changed or expired before outcome persistence; no successful outcome was recorded.', completedOperations, freshInspection.loadedState?.contentHash ?? '', outcome.cleanupWarnings ?? [], false);
202
+ }
203
+ }
204
+ const outcomeInspection = { ...freshInspection, state: outcome.stateOverride ?? freshInspection.state };
205
+ const boundPayload = isRecord(outcome.evidencePayload)
206
+ ? { ...outcome.evidencePayload, planDigest: initialPlan.planDigest, savedPlanDigest: canonicalSha256(initialPlan) }
207
+ : outcome.evidencePayload;
208
+ const outcomeNow = clock();
2051
209
  let evidenceRecord;
2052
210
  let evidenceParts;
2053
211
  let evidenceReference;
2054
212
  if (resultState !== 'approved') {
2055
213
  const evidenceId = `${phase.id}-${safeTimestamp(now.toISOString())}`;
2056
214
  const header = evidenceHeaderFor({
2057
- inspection: freshInspection,
2058
- phase,
2059
- plan: initialPlan,
2060
- result: resultState,
2061
- now
215
+ inspection: outcomeInspection, phase, plan: initialPlan, result: resultState, now: outcomeNow,
216
+ payload: boundPayload, liveReadback: outcome.liveReadback
2062
217
  });
2063
218
  evidenceRecord = {
2064
- evidenceId,
2065
- header,
219
+ evidenceId, header,
2066
220
  ...(outcome.liveReadback ? { liveReadback: outcome.liveReadback } : {}),
2067
- ...(outcome.evidencePayload !== undefined ? { payload: outcome.evidencePayload } : {})
221
+ ...(boundPayload !== undefined ? { payload: boundPayload } : {})
2068
222
  };
2069
223
  evidenceParts = evidencePathParts(evidenceId);
2070
- evidenceReference = {
2071
- phaseId: phase.id,
2072
- evidenceId,
2073
- headerDigest: evidenceHeaderDigest(header),
2074
- result: header.result
2075
- };
224
+ evidenceReference = { phaseId: phase.id, evidenceId, headerDigest: evidenceHeaderDigest(header), result: header.result };
225
+ const validation = validateEvidenceFreshness(evidenceRecord, {
226
+ ...freshInspection.contexts[phase.id],
227
+ remoteBindingDigest: remoteBindingDigest(outcomeInspection.state.remoteBinding),
228
+ evidenceReferences: [evidenceReference], reviewedPlans: [initialPlan],
229
+ ...(phase.id === 'seed-archived' && postSnapshot.workflowSpecDigest ? { workflowSpecDigest: postSnapshot.workflowSpecDigest } : {}),
230
+ now: outcomeNow
231
+ });
232
+ if (!validation.valid) {
233
+ return executionBlockedResult(freshInspection, initialPlan, saved, `Completed outcome rejected: ${validation.issues.map((issue) => issue.message).join(' ')}`, completedOperations, freshInspection.loadedState?.contentHash ?? '', outcome.cleanupWarnings ?? [], false);
234
+ }
2076
235
  }
2077
236
  const nextState = nextStateForOutcome({
2078
- inspection: freshInspection,
2079
- phase,
2080
- plan: initialPlan,
2081
- resultState,
2082
- evidenceReference,
2083
- override: outcome.stateOverride,
2084
- now
237
+ inspection: freshInspection, phase, plan: initialPlan, resultState,
238
+ evidenceReference, override: outcome.stateOverride, now
2085
239
  });
240
+ await input.assertProtectedInputs?.();
2086
241
  const write = await writeOutcomeTransaction({
2087
- projectRoot: freshInspection.projectRoot,
2088
- plan: initialPlan,
2089
- nextState,
2090
- evidenceRecord,
2091
- evidencePathParts: evidenceParts,
2092
- fileMutations: outcome.fileMutations
242
+ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState, evidenceRecord,
243
+ evidencePathParts: evidenceParts, fileMutations: outcome.fileMutations, filePreconditions: outcome.filePreconditions
2093
244
  });
245
+ await input.assertProtectedInputs?.();
2094
246
  const rollbackPlan = rollbackPlanForPhase(phase, completedOperations);
2095
247
  return {
2096
- schemaVersion: 1,
2097
- command: 'governance apply-next',
2098
- projectRoot: freshInspection.projectRoot,
2099
- execute: true,
2100
- applied: true,
2101
- authorized: true,
2102
- reason: 'phase-executed',
2103
- message: `Executed one phase: ${phase.id}.`,
2104
- selectedPhase: phase.id,
2105
- executedPhase: phase.id,
2106
- nextReadyPhase: phase.id,
2107
- approval: initialPlan.approval,
2108
- proposedMutations: {
2109
- local: initialPlan.mutationClasses.local,
2110
- remote: initialPlan.mutationClasses.remote,
2111
- operations: initialPlan.operations
2112
- },
2113
- savedPlan: saved,
2114
- noWrites: false,
2115
- blockers: [],
2116
- executedOperations: [
2117
- ...completedOperations,
2118
- ...(evidenceParts ? [evidenceWriteOperation(phase, evidenceParts)] : []),
2119
- stateWriteOperation(phase)
2120
- ],
2121
- evidence: write.evidence,
2122
- stateHash: write.stateHash,
2123
- rollbackPlan,
248
+ schemaVersion: 1, command: 'governance apply-next', projectRoot: freshInspection.projectRoot,
249
+ execute: true, applied: true, authorized: true, reason: 'phase-executed',
250
+ message: `Executed one phase: ${phase.id}.`, selectedPhase: phase.id, executedPhase: phase.id,
251
+ nextReadyPhase: phase.id, approval: initialPlan.approval,
252
+ proposedMutations: { local: initialPlan.mutationClasses.local, remote: initialPlan.mutationClasses.remote, operations: initialPlan.operations },
253
+ savedPlan: saved, noWrites: false, blockers: [],
254
+ executedOperations: [...completedOperations, ...(evidenceParts ? [evidenceWriteOperation(phase, evidenceParts)] : []), stateWriteOperation(phase)],
255
+ evidence: write.evidence, stateHash: write.stateHash, rollbackPlan,
2124
256
  cleanupWarnings: [...rollbackPlan.cleanupWarnings, ...(outcome.cleanupWarnings ?? [])]
2125
257
  };
2126
258
  }
@@ -2128,33 +260,13 @@ function executionBlockedResult(inspection, plan, saved, blocker, completedOpera
2128
260
  const phase = phaseById(inspection.graph, plan.phaseId);
2129
261
  const rollbackPlan = rollbackPlanForPhase(phase, completedOperations);
2130
262
  return {
2131
- schemaVersion: 1,
2132
- command: 'governance apply-next',
2133
- projectRoot: inspection.projectRoot,
2134
- execute: true,
2135
- applied: false,
2136
- authorized: false,
2137
- reason: 'blocked',
2138
- message: blocker,
2139
- selectedPhase: plan.phaseId,
2140
- executedPhase: null,
2141
- nextReadyPhase: plan.phaseId,
2142
- approval: plan.approval,
2143
- proposedMutations: {
2144
- local: plan.mutationClasses.local,
2145
- remote: plan.mutationClasses.remote,
2146
- operations: plan.operations
2147
- },
2148
- savedPlan: saved,
2149
- noWrites: false,
2150
- blockers: [blocker],
2151
- executedOperations: [
2152
- ...completedOperations,
2153
- ...(stateWritten ? [stateWriteOperation(phase)] : [])
2154
- ],
2155
- evidence: null,
2156
- stateHash: stateHashValue,
2157
- rollbackPlan,
263
+ schemaVersion: 1, command: 'governance apply-next', projectRoot: inspection.projectRoot,
264
+ execute: true, applied: false, authorized: false, reason: 'blocked', message: blocker,
265
+ selectedPhase: plan.phaseId, executedPhase: null, nextReadyPhase: plan.phaseId, approval: plan.approval,
266
+ proposedMutations: { local: plan.mutationClasses.local, remote: plan.mutationClasses.remote, operations: plan.operations },
267
+ savedPlan: saved, noWrites: false, blockers: [blocker],
268
+ executedOperations: [...completedOperations, ...(stateWritten ? [stateWriteOperation(phase)] : [])],
269
+ evidence: null, stateHash: stateHashValue, rollbackPlan,
2158
270
  cleanupWarnings: [...rollbackPlan.cleanupWarnings, ...cleanupWarnings]
2159
271
  };
2160
272
  }