@msn-control/liftoff 0.10.3 → 0.11.1

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 (656) hide show
  1. package/DEVELOPER.md +364 -20
  2. package/README.md +29 -29
  3. package/assets/governance/single-maintainer-gitflow/assessment-controls.json +39 -0
  4. package/assets/supported-stack.json +7 -138
  5. package/dist/adapters/azure/governance-assessment.d.ts +2 -0
  6. package/dist/adapters/azure/governance-assessment.js +21 -0
  7. package/dist/adapters/azure/governance-assessment.js.map +1 -0
  8. package/dist/adapters/filesystem/atomic-write.d.ts +2 -0
  9. package/dist/adapters/filesystem/atomic-write.js +74 -0
  10. package/dist/adapters/filesystem/atomic-write.js.map +1 -0
  11. package/dist/adapters/filesystem/errors.d.ts +2 -0
  12. package/dist/adapters/filesystem/errors.js +8 -0
  13. package/dist/adapters/filesystem/errors.js.map +1 -0
  14. package/dist/adapters/filesystem/manifest-file.d.ts +1 -0
  15. package/dist/adapters/filesystem/manifest-file.js +21 -0
  16. package/dist/adapters/filesystem/manifest-file.js.map +1 -0
  17. package/dist/adapters/filesystem/project-config.d.ts +4 -0
  18. package/dist/adapters/filesystem/project-config.js +157 -0
  19. package/dist/adapters/filesystem/project-config.js.map +1 -0
  20. package/dist/adapters/filesystem/project-discovery.d.ts +2 -0
  21. package/dist/adapters/filesystem/project-discovery.js +30 -0
  22. package/dist/adapters/filesystem/project-discovery.js.map +1 -0
  23. package/dist/adapters/filesystem/project-files.d.ts +6 -0
  24. package/dist/adapters/filesystem/project-files.js +68 -0
  25. package/dist/adapters/filesystem/project-files.js.map +1 -0
  26. package/dist/adapters/filesystem/project-lock.d.ts +8 -0
  27. package/dist/adapters/filesystem/project-lock.js +164 -0
  28. package/dist/adapters/filesystem/project-lock.js.map +1 -0
  29. package/dist/adapters/filesystem/project-paths.d.ts +2 -0
  30. package/dist/adapters/filesystem/project-paths.js +60 -0
  31. package/dist/adapters/filesystem/project-paths.js.map +1 -0
  32. package/dist/adapters/filesystem/project-transaction.d.ts +23 -0
  33. package/dist/adapters/filesystem/project-transaction.js +158 -0
  34. package/dist/adapters/filesystem/project-transaction.js.map +1 -0
  35. package/dist/adapters/git/governance-assessment.d.ts +22 -0
  36. package/dist/adapters/git/governance-assessment.js +161 -0
  37. package/dist/adapters/git/governance-assessment.js.map +1 -0
  38. package/dist/adapters/github/governance-assessment.d.ts +2 -0
  39. package/dist/adapters/github/governance-assessment.js +18 -0
  40. package/dist/adapters/github/governance-assessment.js.map +1 -0
  41. package/dist/adapters/packaged-assets/package-root.d.ts +3 -0
  42. package/dist/adapters/packaged-assets/package-root.js +21 -0
  43. package/dist/adapters/packaged-assets/package-root.js.map +1 -0
  44. package/dist/adapters/packaged-assets/supported-stack.d.ts +4 -0
  45. package/dist/adapters/packaged-assets/supported-stack.js +18 -0
  46. package/dist/adapters/packaged-assets/supported-stack.js.map +1 -0
  47. package/dist/adapters/packaged-assets/template-assets.d.ts +23 -0
  48. package/dist/adapters/packaged-assets/template-assets.js +36 -0
  49. package/dist/adapters/packaged-assets/template-assets.js.map +1 -0
  50. package/dist/adapters/process/shell-command.d.ts +5 -0
  51. package/dist/adapters/process/shell-command.js +21 -0
  52. package/dist/adapters/process/shell-command.js.map +1 -0
  53. package/dist/application/context.d.ts +20 -0
  54. package/dist/application/context.js +2 -0
  55. package/dist/application/context.js.map +1 -0
  56. package/dist/application/diagnose/doctor.d.ts +21 -0
  57. package/dist/application/diagnose/doctor.js +586 -0
  58. package/dist/application/diagnose/doctor.js.map +1 -0
  59. package/dist/application/diagnose/generated-project.d.ts +1 -0
  60. package/dist/application/diagnose/generated-project.js +105 -0
  61. package/dist/application/diagnose/generated-project.js.map +1 -0
  62. package/dist/application/diagnose/validate.d.ts +6 -0
  63. package/dist/application/diagnose/validate.js +27 -0
  64. package/dist/application/diagnose/validate.js.map +1 -0
  65. package/dist/application/initialize/fixture.d.ts +2 -0
  66. package/dist/application/initialize/fixture.js +39 -0
  67. package/dist/application/initialize/fixture.js.map +1 -0
  68. package/dist/application/initialize/inputs.d.ts +3 -0
  69. package/dist/application/initialize/inputs.js +35 -0
  70. package/dist/application/initialize/inputs.js.map +1 -0
  71. package/dist/application/initialize/use-case.d.ts +23 -0
  72. package/dist/application/initialize/use-case.js +360 -0
  73. package/dist/application/initialize/use-case.js.map +1 -0
  74. package/dist/application/migrate/use-case.d.ts +7 -0
  75. package/dist/application/migrate/use-case.js +378 -0
  76. package/dist/application/migrate/use-case.js.map +1 -0
  77. package/dist/application/project/catalog.d.ts +46 -0
  78. package/dist/application/project/catalog.js +12 -0
  79. package/dist/application/project/catalog.js.map +1 -0
  80. package/dist/application/project/manifest.d.ts +2 -0
  81. package/dist/application/project/manifest.js +29 -0
  82. package/dist/application/project/manifest.js.map +1 -0
  83. package/dist/application/project/planning.d.ts +6 -0
  84. package/dist/application/project/planning.js +12 -0
  85. package/dist/application/project/planning.js.map +1 -0
  86. package/dist/application/update/planning.d.ts +40 -0
  87. package/dist/application/update/planning.js +293 -0
  88. package/dist/application/update/planning.js.map +1 -0
  89. package/dist/application/update/reporting.d.ts +42 -0
  90. package/dist/application/update/reporting.js +325 -0
  91. package/dist/application/update/reporting.js.map +1 -0
  92. package/dist/application/update/use-case.d.ts +8 -0
  93. package/dist/application/update/use-case.js +600 -0
  94. package/dist/application/update/use-case.js.map +1 -0
  95. package/dist/application/upgrade/use-case.d.ts +6 -0
  96. package/dist/application/upgrade/use-case.js +82 -0
  97. package/dist/application/upgrade/use-case.js.map +1 -0
  98. package/dist/args.d.ts +5 -65
  99. package/dist/args.js +4 -441
  100. package/dist/args.js.map +1 -1
  101. package/dist/artifact-lifecycle.d.ts +1 -21
  102. package/dist/artifact-lifecycle.js +1 -73
  103. package/dist/artifact-lifecycle.js.map +1 -1
  104. package/dist/catalogs.d.ts +1 -50
  105. package/dist/catalogs.js +1 -472
  106. package/dist/catalogs.js.map +1 -1
  107. package/dist/cli/args/contracts.d.ts +51 -0
  108. package/dist/cli/args/contracts.js +2 -0
  109. package/dist/cli/args/contracts.js.map +1 -0
  110. package/dist/cli/args/definitions.d.ts +6 -0
  111. package/dist/cli/args/definitions.js +187 -0
  112. package/dist/cli/args/definitions.js.map +1 -0
  113. package/dist/cli/args/help.d.ts +5 -0
  114. package/dist/cli/args/help.js +130 -0
  115. package/dist/cli/args/help.js.map +1 -0
  116. package/dist/cli/args/parser.d.ts +5 -0
  117. package/dist/cli/args/parser.js +145 -0
  118. package/dist/cli/args/parser.js.map +1 -0
  119. package/dist/cli/args/readers.d.ts +4 -0
  120. package/dist/cli/args/readers.js +19 -0
  121. package/dist/cli/args/readers.js.map +1 -0
  122. package/dist/cli/commands/diagnose.d.ts +4 -0
  123. package/dist/cli/commands/diagnose.js +12 -0
  124. package/dist/cli/commands/diagnose.js.map +1 -0
  125. package/dist/cli/commands/dispatch.d.ts +3 -0
  126. package/dist/cli/commands/dispatch.js +94 -0
  127. package/dist/cli/commands/dispatch.js.map +1 -0
  128. package/dist/cli/commands/help.d.ts +3 -0
  129. package/dist/cli/commands/help.js +32 -0
  130. package/dist/cli/commands/help.js.map +1 -0
  131. package/dist/cli/commands/helpers.d.ts +8 -0
  132. package/dist/cli/commands/helpers.js +97 -0
  133. package/dist/cli/commands/helpers.js.map +1 -0
  134. package/dist/cli/commands/initialize.d.ts +3 -0
  135. package/dist/cli/commands/initialize.js +7 -0
  136. package/dist/cli/commands/initialize.js.map +1 -0
  137. package/dist/cli/commands/migrate.d.ts +3 -0
  138. package/dist/cli/commands/migrate.js +8 -0
  139. package/dist/cli/commands/migrate.js.map +1 -0
  140. package/dist/cli/commands/plan.d.ts +3 -0
  141. package/dist/cli/commands/plan.js +34 -0
  142. package/dist/cli/commands/plan.js.map +1 -0
  143. package/dist/cli/commands/reference.d.ts +5 -0
  144. package/dist/cli/commands/reference.js +43 -0
  145. package/dist/cli/commands/reference.js.map +1 -0
  146. package/dist/cli/commands/update.d.ts +3 -0
  147. package/dist/cli/commands/update.js +9 -0
  148. package/dist/cli/commands/update.js.map +1 -0
  149. package/dist/cli/commands/upgrade.d.ts +3 -0
  150. package/dist/cli/commands/upgrade.js +7 -0
  151. package/dist/cli/commands/upgrade.js.map +1 -0
  152. package/dist/cli/context.d.ts +1 -0
  153. package/dist/cli/context.js +2 -0
  154. package/dist/cli/context.js.map +1 -0
  155. package/dist/cli/project-options.d.ts +3 -0
  156. package/dist/cli/project-options.js +41 -0
  157. package/dist/cli/project-options.js.map +1 -0
  158. package/dist/cli.d.ts +3 -3
  159. package/dist/cli.js +4 -3
  160. package/dist/cli.js.map +1 -1
  161. package/dist/commands.d.ts +4 -35
  162. package/dist/commands.js +3 -2812
  163. package/dist/commands.js.map +1 -1
  164. package/dist/container-validation.d.ts +1 -1
  165. package/dist/domain/governance/activation/approvals.d.ts +351 -0
  166. package/dist/domain/governance/activation/approvals.js +528 -0
  167. package/dist/domain/governance/activation/approvals.js.map +1 -0
  168. package/dist/domain/governance/activation/canonical-json.d.ts +4 -0
  169. package/dist/domain/governance/activation/canonical-json.js +39 -0
  170. package/dist/domain/governance/activation/canonical-json.js.map +1 -0
  171. package/dist/domain/governance/activation/capabilities.d.ts +7 -0
  172. package/dist/domain/governance/activation/capabilities.js +33 -0
  173. package/dist/domain/governance/activation/capabilities.js.map +1 -0
  174. package/dist/domain/governance/activation/evidence.d.ts +63 -0
  175. package/dist/domain/governance/activation/evidence.js +444 -0
  176. package/dist/domain/governance/activation/evidence.js.map +1 -0
  177. package/dist/domain/governance/activation/graph.d.ts +397 -0
  178. package/dist/domain/governance/activation/graph.js +441 -0
  179. package/dist/domain/governance/activation/graph.js.map +1 -0
  180. package/dist/domain/governance/activation/inputs.d.ts +25 -0
  181. package/dist/domain/governance/activation/inputs.js +63 -0
  182. package/dist/domain/governance/activation/inputs.js.map +1 -0
  183. package/dist/domain/governance/activation/operations.d.ts +19 -0
  184. package/dist/domain/governance/activation/operations.js +181 -0
  185. package/dist/domain/governance/activation/operations.js.map +1 -0
  186. package/dist/domain/governance/activation/readiness.d.ts +27 -0
  187. package/dist/domain/governance/activation/readiness.js +269 -0
  188. package/dist/domain/governance/activation/readiness.js.map +1 -0
  189. package/dist/domain/governance/activation/types.d.ts +399 -0
  190. package/dist/domain/governance/activation/types.js +109 -0
  191. package/dist/domain/governance/activation/types.js.map +1 -0
  192. package/dist/domain/governance/activation/validators.d.ts +22 -0
  193. package/dist/domain/governance/activation/validators.js +1347 -0
  194. package/dist/domain/governance/activation/validators.js.map +1 -0
  195. package/dist/domain/governance/assessment/catalog.d.ts +14 -0
  196. package/dist/domain/governance/assessment/catalog.js +105 -0
  197. package/dist/domain/governance/assessment/catalog.js.map +1 -0
  198. package/dist/domain/governance/assessment/errors.d.ts +10 -0
  199. package/dist/domain/governance/assessment/errors.js +22 -0
  200. package/dist/domain/governance/assessment/errors.js.map +1 -0
  201. package/dist/domain/governance/assessment/index.d.ts +8 -0
  202. package/dist/domain/governance/assessment/index.js +9 -0
  203. package/dist/domain/governance/assessment/index.js.map +1 -0
  204. package/dist/domain/governance/assessment/live-normalize.d.ts +19 -0
  205. package/dist/domain/governance/assessment/live-normalize.js +403 -0
  206. package/dist/domain/governance/assessment/live-normalize.js.map +1 -0
  207. package/dist/domain/governance/assessment/predicates.d.ts +36 -0
  208. package/dist/domain/governance/assessment/predicates.js +545 -0
  209. package/dist/domain/governance/assessment/predicates.js.map +1 -0
  210. package/dist/domain/governance/assessment/report.d.ts +23 -0
  211. package/dist/domain/governance/assessment/report.js +266 -0
  212. package/dist/domain/governance/assessment/report.js.map +1 -0
  213. package/dist/domain/governance/assessment/sanitize.d.ts +8 -0
  214. package/dist/domain/governance/assessment/sanitize.js +60 -0
  215. package/dist/domain/governance/assessment/sanitize.js.map +1 -0
  216. package/dist/domain/governance/assessment/types.d.ts +174 -0
  217. package/dist/domain/governance/assessment/types.js +13 -0
  218. package/dist/domain/governance/assessment/types.js.map +1 -0
  219. package/dist/domain/governance/assessment/yaml.d.ts +6 -0
  220. package/dist/domain/governance/assessment/yaml.js +30 -0
  221. package/dist/domain/governance/assessment/yaml.js.map +1 -0
  222. package/dist/domain/governance/policy/identity.d.ts +64 -0
  223. package/dist/domain/governance/policy/identity.js +112 -0
  224. package/dist/domain/governance/policy/identity.js.map +1 -0
  225. package/dist/domain/migration/dependencies.d.ts +7 -0
  226. package/dist/domain/migration/dependencies.js +221 -0
  227. package/dist/domain/migration/dependencies.js.map +1 -0
  228. package/dist/domain/migration/inventory.d.ts +3 -0
  229. package/dist/domain/migration/inventory.js +15 -0
  230. package/dist/domain/migration/inventory.js.map +1 -0
  231. package/dist/domain/project/artifact-lifecycle.d.ts +69 -0
  232. package/dist/domain/project/artifact-lifecycle.js +137 -0
  233. package/dist/domain/project/artifact-lifecycle.js.map +1 -0
  234. package/dist/domain/project/catalog.d.ts +48 -0
  235. package/dist/domain/project/catalog.js +507 -0
  236. package/dist/domain/project/catalog.js.map +1 -0
  237. package/dist/domain/project/contracts.d.ts +247 -0
  238. package/dist/domain/project/contracts.js +2 -0
  239. package/dist/domain/project/contracts.js.map +1 -0
  240. package/dist/domain/project/errors.d.ts +3 -0
  241. package/dist/domain/project/errors.js +7 -0
  242. package/dist/domain/project/errors.js.map +1 -0
  243. package/dist/domain/project/infrastructure-layout.d.ts +26 -0
  244. package/dist/domain/project/infrastructure-layout.js +124 -0
  245. package/dist/domain/project/infrastructure-layout.js.map +1 -0
  246. package/dist/domain/project/inputs.d.ts +21 -0
  247. package/dist/domain/project/inputs.js +41 -0
  248. package/dist/domain/project/inputs.js.map +1 -0
  249. package/dist/domain/project/manifest/artifacts.d.ts +8 -0
  250. package/dist/domain/project/manifest/artifacts.js +150 -0
  251. package/dist/domain/project/manifest/artifacts.js.map +1 -0
  252. package/dist/domain/project/manifest/context.d.ts +13 -0
  253. package/dist/domain/project/manifest/context.js +2 -0
  254. package/dist/domain/project/manifest/context.js.map +1 -0
  255. package/dist/domain/project/manifest/fields.d.ts +6 -0
  256. package/dist/domain/project/manifest/fields.js +36 -0
  257. package/dist/domain/project/manifest/fields.js.map +1 -0
  258. package/dist/domain/project/manifest/governance.d.ts +8 -0
  259. package/dist/domain/project/manifest/governance.js +161 -0
  260. package/dist/domain/project/manifest/governance.js.map +1 -0
  261. package/dist/domain/project/manifest/project-identity.d.ts +6 -0
  262. package/dist/domain/project/manifest/project-identity.js +273 -0
  263. package/dist/domain/project/manifest/project-identity.js.map +1 -0
  264. package/dist/domain/project/manifest/reader.d.ts +9 -0
  265. package/dist/domain/project/manifest/reader.js +141 -0
  266. package/dist/domain/project/manifest/reader.js.map +1 -0
  267. package/dist/domain/project/paths.d.ts +2 -0
  268. package/dist/domain/project/paths.js +29 -0
  269. package/dist/domain/project/paths.js.map +1 -0
  270. package/dist/domain/project/planning.d.ts +19 -0
  271. package/dist/domain/project/planning.js +329 -0
  272. package/dist/domain/project/planning.js.map +1 -0
  273. package/dist/domain/project/retired-workload.d.ts +4 -0
  274. package/dist/domain/project/retired-workload.js +18 -0
  275. package/dist/domain/project/retired-workload.js.map +1 -0
  276. package/dist/domain/project/supported-stack.d.ts +113 -0
  277. package/dist/domain/project/supported-stack.js +426 -0
  278. package/dist/domain/project/supported-stack.js.map +1 -0
  279. package/dist/domain/workstation/constraints.d.ts +6 -0
  280. package/dist/domain/workstation/constraints.js +8 -0
  281. package/dist/domain/workstation/constraints.js.map +1 -0
  282. package/dist/file-system.d.ts +10 -41
  283. package/dist/file-system.js +9 -1204
  284. package/dist/file-system.js.map +1 -1
  285. package/dist/framework-adapters.d.ts +1 -1
  286. package/dist/framework-validation.d.ts +1 -1
  287. package/dist/framework-validation.js +1 -1
  288. package/dist/framework-validation.js.map +1 -1
  289. package/dist/genai-templates.d.ts +2 -9
  290. package/dist/genai-templates.js +4 -5
  291. package/dist/genai-templates.js.map +1 -1
  292. package/dist/generators/common/artifacts.d.ts +6 -0
  293. package/dist/generators/common/artifacts.js +30 -0
  294. package/dist/generators/common/artifacts.js.map +1 -0
  295. package/dist/generators/common/base.d.ts +17 -0
  296. package/dist/generators/common/base.js +421 -0
  297. package/dist/generators/common/base.js.map +1 -0
  298. package/dist/generators/common/dotenv-validation.d.ts +2 -0
  299. package/dist/generators/common/dotenv-validation.js +80 -0
  300. package/dist/generators/common/dotenv-validation.js.map +1 -0
  301. package/dist/generators/common/environments.d.ts +3 -0
  302. package/dist/generators/common/environments.js +15 -0
  303. package/dist/generators/common/environments.js.map +1 -0
  304. package/dist/generators/common/frontend.d.ts +15 -0
  305. package/dist/generators/common/frontend.js +166 -0
  306. package/dist/generators/common/frontend.js.map +1 -0
  307. package/dist/generators/common/python-settings.d.ts +2 -0
  308. package/dist/generators/common/python-settings.js +70 -0
  309. package/dist/generators/common/python-settings.js.map +1 -0
  310. package/dist/generators/common/spec-workflow.d.ts +28 -0
  311. package/dist/generators/common/spec-workflow.js +567 -0
  312. package/dist/generators/common/spec-workflow.js.map +1 -0
  313. package/dist/generators/common/values.d.ts +13 -0
  314. package/dist/generators/common/values.js +16 -0
  315. package/dist/generators/common/values.js.map +1 -0
  316. package/dist/generators/containers/compose.d.ts +7 -0
  317. package/dist/generators/containers/compose.js +196 -0
  318. package/dist/generators/containers/compose.js.map +1 -0
  319. package/dist/generators/containers/context.d.ts +1 -0
  320. package/dist/generators/containers/context.js +43 -0
  321. package/dist/generators/containers/context.js.map +1 -0
  322. package/dist/generators/containers/images.d.ts +7 -0
  323. package/dist/generators/containers/images.js +119 -0
  324. package/dist/generators/containers/images.js.map +1 -0
  325. package/dist/generators/context.d.ts +42 -0
  326. package/dist/generators/context.js +38 -0
  327. package/dist/generators/context.js.map +1 -0
  328. package/dist/generators/genai/backend.d.ts +20 -0
  329. package/dist/generators/genai/backend.js +667 -0
  330. package/dist/generators/genai/backend.js.map +1 -0
  331. package/dist/generators/genai/configuration.d.ts +3 -0
  332. package/dist/generators/genai/configuration.js +43 -0
  333. package/dist/generators/genai/configuration.js.map +1 -0
  334. package/dist/generators/genai/database.d.ts +7 -0
  335. package/dist/generators/genai/database.js +71 -0
  336. package/dist/generators/genai/database.js.map +1 -0
  337. package/dist/generators/genai/functions.d.ts +15 -0
  338. package/dist/generators/genai/functions.js +156 -0
  339. package/dist/generators/genai/functions.js.map +1 -0
  340. package/dist/generators/genai/index.d.ts +8 -0
  341. package/dist/generators/genai/index.js +11 -0
  342. package/dist/generators/genai/index.js.map +1 -0
  343. package/dist/generators/genai/patterns.d.ts +9 -0
  344. package/dist/generators/genai/patterns.js +309 -0
  345. package/dist/generators/genai/patterns.js.map +1 -0
  346. package/dist/generators/infrastructure/azure.d.ts +18 -0
  347. package/dist/generators/infrastructure/azure.js +655 -0
  348. package/dist/generators/infrastructure/azure.js.map +1 -0
  349. package/dist/generators/infrastructure/names.d.ts +23 -0
  350. package/dist/generators/infrastructure/names.js +51 -0
  351. package/dist/generators/infrastructure/names.js.map +1 -0
  352. package/dist/generators/standard/configuration.d.ts +3 -0
  353. package/dist/generators/standard/configuration.js +26 -0
  354. package/dist/generators/standard/configuration.js.map +1 -0
  355. package/dist/generators/standard/go.d.ts +17 -0
  356. package/dist/generators/standard/go.js +338 -0
  357. package/dist/generators/standard/go.js.map +1 -0
  358. package/dist/generators/standard/index.d.ts +12 -0
  359. package/dist/generators/standard/index.js +12 -0
  360. package/dist/generators/standard/index.js.map +1 -0
  361. package/dist/generators/standard/node.d.ts +20 -0
  362. package/dist/generators/standard/node.js +314 -0
  363. package/dist/generators/standard/node.js.map +1 -0
  364. package/dist/generators/standard/python.d.ts +18 -0
  365. package/dist/generators/standard/python.js +191 -0
  366. package/dist/generators/standard/python.js.map +1 -0
  367. package/dist/go-template-assets.js +3 -3
  368. package/dist/go-template-assets.js.map +1 -1
  369. package/dist/governance-activation/activation-state.d.ts +1 -1
  370. package/dist/governance-activation/activation-state.js +46 -9
  371. package/dist/governance-activation/activation-state.js.map +1 -1
  372. package/dist/governance-activation/approvals.d.ts +1 -188
  373. package/dist/governance-activation/approvals.js +1 -507
  374. package/dist/governance-activation/approvals.js.map +1 -1
  375. package/dist/governance-activation/canonical-json.d.ts +1 -3
  376. package/dist/governance-activation/canonical-json.js +1 -35
  377. package/dist/governance-activation/canonical-json.js.map +1 -1
  378. package/dist/governance-activation/commands.d.ts +1 -1
  379. package/dist/governance-activation/commands.js +107 -103
  380. package/dist/governance-activation/commands.js.map +1 -1
  381. package/dist/governance-activation/compatibility.d.ts +11 -3
  382. package/dist/governance-activation/compatibility.js +31 -4
  383. package/dist/governance-activation/compatibility.js.map +1 -1
  384. package/dist/governance-activation/credentials.d.ts +3 -3
  385. package/dist/governance-activation/credentials.js +5 -5
  386. package/dist/governance-activation/credentials.js.map +1 -1
  387. package/dist/governance-activation/doctor.d.ts +1 -1
  388. package/dist/governance-activation/doctor.js +28 -96
  389. package/dist/governance-activation/doctor.js.map +1 -1
  390. package/dist/governance-activation/evidence.d.ts +1 -49
  391. package/dist/governance-activation/evidence.js +1 -245
  392. package/dist/governance-activation/evidence.js.map +1 -1
  393. package/dist/governance-activation/git-ignore.d.ts +9 -0
  394. package/dist/governance-activation/git-ignore.js +76 -0
  395. package/dist/governance-activation/git-ignore.js.map +1 -0
  396. package/dist/governance-activation/graph.d.ts +1 -397
  397. package/dist/governance-activation/graph.js +1 -440
  398. package/dist/governance-activation/graph.js.map +1 -1
  399. package/dist/governance-activation/identity.d.ts +1 -35
  400. package/dist/governance-activation/identity.js +1 -88
  401. package/dist/governance-activation/identity.js.map +1 -1
  402. package/dist/governance-activation/index.d.ts +3 -0
  403. package/dist/governance-activation/index.js +3 -0
  404. package/dist/governance-activation/index.js.map +1 -1
  405. package/dist/governance-activation/inputs.d.ts +8 -0
  406. package/dist/governance-activation/inputs.js +146 -0
  407. package/dist/governance-activation/inputs.js.map +1 -0
  408. package/dist/governance-activation/migration.d.ts +4 -3
  409. package/dist/governance-activation/migration.js +14 -6
  410. package/dist/governance-activation/migration.js.map +1 -1
  411. package/dist/governance-activation/phase-bootstrap-state.d.ts +3 -0
  412. package/dist/governance-activation/phase-bootstrap-state.js +128 -0
  413. package/dist/governance-activation/phase-bootstrap-state.js.map +1 -0
  414. package/dist/governance-activation/phase-discovery.d.ts +2 -0
  415. package/dist/governance-activation/phase-discovery.js +88 -0
  416. package/dist/governance-activation/phase-discovery.js.map +1 -0
  417. package/dist/governance-activation/phase-governance.d.ts +4 -0
  418. package/dist/governance-activation/phase-governance.js +141 -0
  419. package/dist/governance-activation/phase-governance.js.map +1 -0
  420. package/dist/governance-activation/phase-publication.d.ts +8 -0
  421. package/dist/governance-activation/phase-publication.js +318 -0
  422. package/dist/governance-activation/phase-publication.js.map +1 -0
  423. package/dist/governance-activation/read-only.d.ts +30 -0
  424. package/dist/governance-activation/read-only.js +93 -0
  425. package/dist/governance-activation/read-only.js.map +1 -0
  426. package/dist/governance-activation/readiness.d.ts +1 -24
  427. package/dist/governance-activation/readiness.js +1 -220
  428. package/dist/governance-activation/readiness.js.map +1 -1
  429. package/dist/governance-activation/reconciliation.d.ts +1 -1
  430. package/dist/governance-activation/reconciliation.js +3 -3
  431. package/dist/governance-activation/reconciliation.js.map +1 -1
  432. package/dist/governance-activation/release-integrity.d.ts +1 -1
  433. package/dist/governance-activation/release-integrity.js +2 -2
  434. package/dist/governance-activation/release-integrity.js.map +1 -1
  435. package/dist/governance-activation/seed-lifecycle.d.ts +10 -2
  436. package/dist/governance-activation/seed-lifecycle.js +244 -66
  437. package/dist/governance-activation/seed-lifecycle.js.map +1 -1
  438. package/dist/governance-activation/source-of-truth.d.ts +5 -3
  439. package/dist/governance-activation/source-of-truth.js +32 -52
  440. package/dist/governance-activation/source-of-truth.js.map +1 -1
  441. package/dist/governance-activation/spec-kit-seed.d.ts +9 -0
  442. package/dist/governance-activation/spec-kit-seed.js +43 -0
  443. package/dist/governance-activation/spec-kit-seed.js.map +1 -0
  444. package/dist/governance-activation/task-projection.d.ts +1 -1
  445. package/dist/governance-activation/transition-planning.d.ts +15 -0
  446. package/dist/governance-activation/transition-planning.js +250 -0
  447. package/dist/governance-activation/transition-planning.js.map +1 -0
  448. package/dist/governance-activation/transition-ports.d.ts +150 -0
  449. package/dist/governance-activation/transition-ports.js +2 -0
  450. package/dist/governance-activation/transition-ports.js.map +1 -0
  451. package/dist/governance-activation/transition-process.d.ts +7 -0
  452. package/dist/governance-activation/transition-process.js +21 -0
  453. package/dist/governance-activation/transition-process.js.map +1 -0
  454. package/dist/governance-activation/transition-records.d.ts +56 -0
  455. package/dist/governance-activation/transition-records.js +208 -0
  456. package/dist/governance-activation/transition-records.js.map +1 -0
  457. package/dist/governance-activation/transitions.d.ts +6 -157
  458. package/dist/governance-activation/transitions.js +143 -2045
  459. package/dist/governance-activation/transitions.js.map +1 -1
  460. package/dist/governance-activation/types.d.ts +1 -390
  461. package/dist/governance-activation/types.js +1 -107
  462. package/dist/governance-activation/types.js.map +1 -1
  463. package/dist/governance-activation/validators.d.ts +1 -17
  464. package/dist/governance-activation/validators.js +1 -1304
  465. package/dist/governance-activation/validators.js.map +1 -1
  466. package/dist/governance-assessment/catalog.d.ts +7 -0
  467. package/dist/governance-assessment/catalog.js +31 -0
  468. package/dist/governance-assessment/catalog.js.map +1 -0
  469. package/dist/governance-assessment/command.d.ts +10 -0
  470. package/dist/governance-assessment/command.js +77 -0
  471. package/dist/governance-assessment/command.js.map +1 -0
  472. package/dist/governance-assessment/engine.d.ts +14 -0
  473. package/dist/governance-assessment/engine.js +935 -0
  474. package/dist/governance-assessment/engine.js.map +1 -0
  475. package/dist/governance-assessment/index.d.ts +5 -0
  476. package/dist/governance-assessment/index.js +6 -0
  477. package/dist/governance-assessment/index.js.map +1 -0
  478. package/dist/governance-assessment/live-normalize.d.ts +1 -0
  479. package/dist/governance-assessment/live-normalize.js +2 -0
  480. package/dist/governance-assessment/live-normalize.js.map +1 -0
  481. package/dist/governance-assessment/live-transport.d.ts +34 -0
  482. package/dist/governance-assessment/live-transport.js +455 -0
  483. package/dist/governance-assessment/live-transport.js.map +1 -0
  484. package/dist/governance-assessment/live.d.ts +2 -0
  485. package/dist/governance-assessment/live.js +689 -0
  486. package/dist/governance-assessment/live.js.map +1 -0
  487. package/dist/governance-assessment/predicates.d.ts +1 -0
  488. package/dist/governance-assessment/predicates.js +2 -0
  489. package/dist/governance-assessment/predicates.js.map +1 -0
  490. package/dist/governance-assessment/project.d.ts +33 -0
  491. package/dist/governance-assessment/project.js +178 -0
  492. package/dist/governance-assessment/project.js.map +1 -0
  493. package/dist/governance-assessment/readers.d.ts +14 -0
  494. package/dist/governance-assessment/readers.js +122 -0
  495. package/dist/governance-assessment/readers.js.map +1 -0
  496. package/dist/governance-assessment/report.d.ts +1 -0
  497. package/dist/governance-assessment/report.js +2 -0
  498. package/dist/governance-assessment/report.js.map +1 -0
  499. package/dist/governance-assessment/sanitize.d.ts +1 -0
  500. package/dist/governance-assessment/sanitize.js +2 -0
  501. package/dist/governance-assessment/sanitize.js.map +1 -0
  502. package/dist/governance-assessment/types.d.ts +6 -0
  503. package/dist/governance-assessment/types.js +2 -0
  504. package/dist/governance-assessment/types.js.map +1 -0
  505. package/dist/governance-assessment/yaml.d.ts +1 -0
  506. package/dist/governance-assessment/yaml.js +2 -0
  507. package/dist/governance-assessment/yaml.js.map +1 -0
  508. package/dist/init-filesystem.d.ts +1 -1
  509. package/dist/init-filesystem.js +126 -174
  510. package/dist/init-filesystem.js.map +1 -1
  511. package/dist/interactive.d.ts +2 -2
  512. package/dist/interactive.js +42 -41
  513. package/dist/interactive.js.map +1 -1
  514. package/dist/migrate-plan.d.ts +5 -1
  515. package/dist/migrate-plan.js +87 -10
  516. package/dist/migrate-plan.js.map +1 -1
  517. package/dist/npm-template-assets.js +5 -4
  518. package/dist/npm-template-assets.js.map +1 -1
  519. package/dist/openspec-profile.d.ts +1 -1
  520. package/dist/opentofu-template-assets.js +3 -3
  521. package/dist/opentofu-template-assets.js.map +1 -1
  522. package/dist/package-identity.d.ts +3 -0
  523. package/dist/package-identity.js +13 -2
  524. package/dist/package-identity.js.map +1 -1
  525. package/dist/planner.d.ts +1 -19
  526. package/dist/planner.js +1 -565
  527. package/dist/planner.js.map +1 -1
  528. package/dist/process-runner.d.ts +5 -1
  529. package/dist/process-runner.js +164 -8
  530. package/dist/process-runner.js.map +1 -1
  531. package/dist/project-dependencies.d.ts +5 -3
  532. package/dist/project-dependencies.js +74 -63
  533. package/dist/project-dependencies.js.map +1 -1
  534. package/dist/published-verifier.js +67 -30
  535. package/dist/published-verifier.js.map +1 -1
  536. package/dist/python-template-assets.js +6 -9
  537. package/dist/python-template-assets.js.map +1 -1
  538. package/dist/reconcile.d.ts +1 -1
  539. package/dist/reconcile.js +2 -2
  540. package/dist/reconcile.js.map +1 -1
  541. package/dist/release-identity.js +19 -0
  542. package/dist/release-identity.js.map +1 -1
  543. package/dist/repository-governance.d.ts +10 -2
  544. package/dist/repository-governance.js +240 -99
  545. package/dist/repository-governance.js.map +1 -1
  546. package/dist/runtime.js +1 -1
  547. package/dist/runtime.js.map +1 -1
  548. package/dist/scan.d.ts +6 -2
  549. package/dist/scan.js +78 -30
  550. package/dist/scan.js.map +1 -1
  551. package/dist/self-upgrade.d.ts +1 -1
  552. package/dist/self-upgrade.js +20 -7
  553. package/dist/self-upgrade.js.map +1 -1
  554. package/dist/stable-release.js +7 -1
  555. package/dist/stable-release.js.map +1 -1
  556. package/dist/standard-templates.d.ts +2 -2
  557. package/dist/standard-templates.js +6 -841
  558. package/dist/standard-templates.js.map +1 -1
  559. package/dist/supported-stack.d.ts +3 -115
  560. package/dist/supported-stack.js +4 -438
  561. package/dist/supported-stack.js.map +1 -1
  562. package/dist/telemetry/contract.d.ts +2 -0
  563. package/dist/telemetry/contract.js +7 -0
  564. package/dist/telemetry/contract.js.map +1 -1
  565. package/dist/telemetry/index.d.ts +1 -1
  566. package/dist/templates.d.ts +10 -20
  567. package/dist/templates.js +52 -3238
  568. package/dist/templates.js.map +1 -1
  569. package/dist/types.d.ts +1 -263
  570. package/dist/types.js +1 -1
  571. package/dist/types.js.map +1 -1
  572. package/dist/version.js +2 -6
  573. package/dist/version.js.map +1 -1
  574. package/dist/workstation-catalog.d.ts +5 -2
  575. package/dist/workstation-catalog.js +33 -2
  576. package/dist/workstation-catalog.js.map +1 -1
  577. package/dist/workstation.d.ts +4 -3
  578. package/dist/workstation.js +86 -32
  579. package/dist/workstation.js.map +1 -1
  580. package/docs/assets/liftoff-terminal.svg +3 -3
  581. package/docs/azure-deployment.md +107 -10
  582. package/docs/cli-reference.md +127 -11
  583. package/docs/configuration-and-manifests.md +150 -35
  584. package/docs/existing-repositories.md +33 -6
  585. package/docs/getting-started.md +44 -31
  586. package/docs/prerequisites.md +34 -35
  587. package/docs/project-structure.md +45 -47
  588. package/docs/repository-governance.md +207 -24
  589. package/docs/safety-and-consent.md +65 -12
  590. package/docs/spec-workflows-and-agents.md +31 -28
  591. package/docs/supported-stack.md +11 -9
  592. package/docs/telemetry.md +9 -3
  593. package/docs/troubleshooting.md +107 -49
  594. package/docs/workloads.md +53 -62
  595. package/package.json +6 -8
  596. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/UPSTREAM_LICENSE.txt +0 -21
  597. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/catalog.json +0 -520
  598. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/packaged/gitignore +0 -27
  599. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/README.md +0 -25
  600. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/components.json +0 -22
  601. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/eslint.config.js +0 -23
  602. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/index.html +0 -13
  603. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package-lock.json +0 -6156
  604. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package.json +0 -60
  605. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/public/power-apps.svg +0 -55
  606. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/App.tsx +0 -17
  607. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/assets/react.svg +0 -1
  608. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/mode-toggle.tsx +0 -37
  609. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/badge.tsx +0 -46
  610. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/button.tsx +0 -60
  611. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/calendar.tsx +0 -211
  612. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/card.tsx +0 -92
  613. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/chart.tsx +0 -355
  614. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/checkbox.tsx +0 -32
  615. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/command.tsx +0 -184
  616. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/dialog.tsx +0 -141
  617. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/dropdown-menu.tsx +0 -257
  618. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/input.tsx +0 -21
  619. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/label.tsx +0 -22
  620. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/popover.tsx +0 -46
  621. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/progress.tsx +0 -29
  622. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/select.tsx +0 -185
  623. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/separator.tsx +0 -26
  624. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/skeleton.tsx +0 -13
  625. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/table.tsx +0 -114
  626. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/tabs.tsx +0 -66
  627. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/textarea.tsx +0 -18
  628. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/components/ui/tooltip.tsx +0 -61
  629. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/hooks/use-theme.ts +0 -11
  630. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/index.css +0 -131
  631. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/lib/utils.ts +0 -6
  632. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/main.tsx +0 -10
  633. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/_layout.tsx +0 -31
  634. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/home.tsx +0 -59
  635. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/pages/not-found.tsx +0 -14
  636. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/query-provider.tsx +0 -26
  637. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/sonner-provider.tsx +0 -23
  638. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/providers/theme-provider.tsx +0 -64
  639. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/src/router.tsx +0 -24
  640. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.app.json +0 -34
  641. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.json +0 -13
  642. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/tsconfig.node.json +0 -26
  643. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/vite.config.ts +0 -19
  644. package/assets/power-apps-code-app/README.md +0 -27
  645. package/dist/code-apps-plugin.d.ts +0 -13
  646. package/dist/code-apps-plugin.js +0 -69
  647. package/dist/code-apps-plugin.js.map +0 -1
  648. package/dist/power-apps-assets.d.ts +0 -30
  649. package/dist/power-apps-assets.js +0 -131
  650. package/dist/power-apps-assets.js.map +0 -1
  651. package/dist/power-apps-templates.d.ts +0 -3
  652. package/dist/power-apps-templates.js +0 -173
  653. package/dist/power-apps-templates.js.map +0 -1
  654. package/dist/power-apps-validation.d.ts +0 -1
  655. package/dist/power-apps-validation.js +0 -79
  656. package/dist/power-apps-validation.js.map +0 -1
@@ -1,1938 +1,72 @@
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
- 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
- nextReadyPhase,
1145
- approval: plan?.approval ?? null,
1146
- proposedMutations: {
1147
- local: plan?.mutationClasses.local ?? ['none'],
1148
- remote: plan?.mutationClasses.remote ?? ['none'],
1149
- operations: plan?.operations ?? []
1150
- },
1151
- savedPlan: null,
1152
- noWrites: true,
1153
- blockers: blockers.length > 0 ? blockers : ['No phase is ready for execution.']
1154
- };
1155
- }
1156
- if (plan.approval.evaluation.approvalRequired) {
1157
- return {
1158
- schemaVersion: 1,
1159
- command: 'governance apply-next',
1160
- projectRoot: input.inspection.projectRoot,
1161
- execute: input.execute,
1162
- applied: false,
1163
- authorized: false,
1164
- reason: 'approval-required',
1165
- message: plan.approval.evaluation.reasons.join('; '),
1166
- nextReadyPhase,
1167
- approval: plan.approval,
1168
- proposedMutations: {
1169
- local: plan.mutationClasses.local,
1170
- remote: plan.mutationClasses.remote,
1171
- operations: plan.operations
1172
- },
1173
- savedPlan: null,
1174
- noWrites: true,
1175
- blockers: plan.approval.evaluation.reasons
1176
- };
1177
- }
1178
- return {
1179
- schemaVersion: 1,
1180
- command: 'governance apply-next',
1181
- projectRoot: input.inspection.projectRoot,
1182
- execute: input.execute,
1183
- applied: false,
1184
- authorized: true,
1185
- reason: input.execute ? 'execute-requested' : 'execute-required',
1186
- message: input.execute
1187
- ? 'Execution requested; the plan must be saved and revalidated before any mutation.'
1188
- : 'Preview only. Rerun with --execute to save this plan and execute at most one phase.',
1189
- nextReadyPhase,
1190
- approval: plan.approval,
1191
- proposedMutations: {
1192
- local: plan.mutationClasses.local,
1193
- remote: plan.mutationClasses.remote,
1194
- operations: plan.operations
1195
- },
1196
- savedPlan: null,
1197
- noWrites: true,
1198
- blockers: []
1199
- };
1200
- }
1201
- async function saveTransitionPlan(projectRoot, plan) {
1202
- const validated = validateSavedTransitionPlan(plan);
1203
- assertNoSecrets(validated);
1204
- const pathParts = transitionPlanPathParts(validated);
1205
- const existing = await readProjectFile(projectRoot, pathParts);
1206
- if (existing !== undefined) {
1207
- throw new Error(`Refusing to overwrite existing governance transition plan ${pathParts.join('/')}.`);
1208
- }
1209
- const content = `${canonicalJson(validated)}\n`;
1210
- await applyProjectFileTransaction(projectRoot, [{
1211
- type: 'write',
1212
- pathParts,
1213
- content
1214
- }]);
1215
- return { pathParts, digest: canonicalSha256(validated) };
1216
- }
1217
- function comparePlanFreshness(saved, fresh) {
1218
- if (!fresh) {
1219
- return ['No phase remained ready after saving the transition plan.'];
1220
- }
1221
- const issues = [];
1222
- if (saved.phaseId !== fresh.phaseId) {
1223
- issues.push(`Ready phase changed from ${saved.phaseId} to ${fresh.phaseId}.`);
1224
- }
1225
- for (const field of ['stateHash', 'baselineDigest', 'inputDigest', 'transitionDigest']) {
1226
- if (saved[field] !== fresh[field]) {
1227
- issues.push(`${field} changed after plan save.`);
1228
- }
1229
- }
1230
- if (canonicalSha256(saved.operations) !== canonicalSha256(fresh.operations)) {
1231
- issues.push('Proposed operations changed after plan save.');
1232
- }
1233
- if (saved.approval.evaluation.approvalRequired || fresh.approval.evaluation.approvalRequired) {
1234
- issues.push('Approval was not valid immediately before execution.');
1235
- }
1236
- return issues;
1237
- }
1238
- function evidenceHeaderFor(input) {
1239
- const context = input.inspection.contexts[input.phase.id];
1240
- return validateEvidenceHeader({
1241
- schemaVersion: input.inspection.state.identity.evidenceHeaderSchemaVersion,
1242
- repositoryId: input.inspection.state.repository.id,
1243
- identity: input.inspection.state.identity,
1244
- phaseGraphHash: input.inspection.state.identity.phaseGraphHash,
1245
- phaseId: input.phase.id,
1246
- phaseContractDigest: phaseContractDigests(input.inspection.graph)[input.phase.id],
1247
- inputDigest: input.plan.inputDigest,
1248
- baselineSha: input.plan.baselineDigest,
1249
- transition: context.transition,
1250
- producedAt: input.now.toISOString(),
1251
- producer: engineProducer,
1252
- result: input.result
1253
- });
1254
- }
1255
- function appendUnique(values, value) {
1256
- if (value === null || values.includes(value)) {
1257
- return [...values];
1258
- }
1259
- return [...values, value];
1260
- }
1261
- function nextStateForOutcome(input) {
1262
- const base = cloneState(input.override ?? input.inspection.state);
1263
- if (input.inspection.sourceOfTruth.status === 'selected' &&
1264
- input.inspection.sourceOfTruth.recordActiveChangeOnNextMutation) {
1265
- const selected = stateWithSelectedActiveChange(base, input.inspection.sourceOfTruth.selected);
1266
- base.activeChange = selected.activeChange;
1267
- }
1268
- const phaseState = input.resultState === 'approved' ? 'approved' : input.resultState;
1269
- base.phases[input.phase.id] = {
1270
- state: phaseState,
1271
- updatedAt: input.now.toISOString(),
1272
- evidence: input.evidenceReference
1273
- ? [...base.phases[input.phase.id].evidence, input.evidenceReference]
1274
- : base.phases[input.phase.id].evidence,
1275
- approvals: appendUnique(base.phases[input.phase.id].approvals, input.plan.approval.envelopeId),
1276
- blockers: input.blocker ? [input.blocker] : []
1277
- };
1278
- base.updatedAt = input.now.toISOString();
1279
- return validateUserActivationState(base);
1280
- }
1281
- function blockedState(input) {
1282
- const base = cloneState(input.inspection.state);
1283
- base.phases[input.phase.id] = {
1284
- state: 'blocked',
1285
- updatedAt: input.now.toISOString(),
1286
- evidence: base.phases[input.phase.id].evidence,
1287
- approvals: appendUnique(base.phases[input.phase.id].approvals, input.plan.approval.envelopeId),
1288
- blockers: [input.blocker]
1289
- };
1290
- base.updatedAt = input.now.toISOString();
1291
- return validateUserActivationState(base);
1292
- }
1293
- async function assertLoadedStateHash(projectRoot, expectedHash) {
1294
- const bytes = await readProjectFile(projectRoot, activationStatePathParts());
1295
- const currentHash = bytes === undefined ? null : activationStateContentHash(bytes);
1296
- if (currentHash !== expectedHash) {
1297
- throw new Error(`Activation state changed after inspection: expected ${expectedHash ?? 'absent'}, found ${currentHash ?? 'absent'}.`);
1298
- }
1299
- }
1300
- async function writeOutcomeTransaction(input) {
1301
- await assertLoadedStateHash(input.projectRoot, input.plan.stateHash);
1302
- const stateContent = `${canonicalJson(validateUserActivationState(input.nextState))}\n`;
1303
- const mutations = [
1304
- ...(input.fileMutations ?? []).map((mutation) => ({
1305
- ...mutation,
1306
- pathParts: validateArtifactPathParts([...mutation.pathParts], 'Governance transition file mutation path')
1307
- })),
1308
- ...(input.evidenceRecord && input.evidencePathParts
1309
- ? [{
1310
- type: 'write',
1311
- pathParts: [...input.evidencePathParts],
1312
- content: `${canonicalJson(input.evidenceRecord)}\n`
1313
- }]
1314
- : []),
1315
- {
1316
- type: 'write',
1317
- pathParts: activationStatePathParts(),
1318
- content: stateContent
1319
- }
1320
- ];
1321
- if (input.evidencePathParts) {
1322
- const existing = await readProjectFile(input.projectRoot, [...input.evidencePathParts]);
1323
- if (existing !== undefined) {
1324
- throw new Error(`Refusing to overwrite existing governance evidence ${input.evidencePathParts.join('/')}.`);
1325
- }
1326
- }
1327
- await applyProjectFileTransaction(input.projectRoot, mutations);
1328
- return {
1329
- stateHash: activationStateContentHash(stateContent),
1330
- evidence: input.evidenceRecord && input.evidencePathParts
1331
- ? {
1332
- evidenceId: input.evidenceRecord.evidenceId,
1333
- pathParts: input.evidencePathParts,
1334
- headerDigest: evidenceHeaderDigest(input.evidenceRecord.header),
1335
- result: input.evidenceRecord.header.result
1336
- }
1337
- : null
1338
- };
1339
- }
1340
- async function executeGitOperations(input, operations) {
1341
- if (input.phase.id !== 'committed' && input.phase.id !== 'pushed') {
1342
- return null;
1343
- }
1344
- const completed = [];
1345
- for (const op of operations.filter((entry) => entry.adapter === 'git')) {
1346
- if (op.actionId === 'git.verify-existing-commit' || op.actionId === 'git.verify-existing-push') {
1347
- completed.push(op);
1348
- continue;
1349
- }
1350
- if (op.actionId === 'git.init') {
1351
- const result = await runGit(input.runner, input.inspection.projectRoot, ['init', '-b', String(op.inputs.branch)]);
1352
- if (!commandSucceeded(result)) {
1353
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1354
- }
1355
- completed.push(op);
1356
- }
1357
- else if (op.actionId === 'git.add-reviewed') {
1358
- const paths = Array.isArray(op.inputs.paths) ? op.inputs.paths.map(String) : [];
1359
- if (paths.length === 0) {
1360
- completed.push(op);
1361
- continue;
1362
- }
1363
- const result = await runGit(input.runner, input.inspection.projectRoot, ['add', '--', ...paths]);
1364
- if (!commandSucceeded(result)) {
1365
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1366
- }
1367
- completed.push(op);
1368
- }
1369
- else if (op.actionId === 'git.commit-reviewed') {
1370
- const message = String(op.inputs.message);
1371
- const result = await runGit(input.runner, input.inspection.projectRoot, ['commit', '-m', message]);
1372
- if (!commandSucceeded(result)) {
1373
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1374
- }
1375
- completed.push(op);
1376
- }
1377
- else if (op.actionId === 'git.push-approved-ref') {
1378
- const branch = String(op.inputs.branch);
1379
- const result = await runGit(input.runner, input.inspection.projectRoot, ['push', 'origin', `HEAD:refs/heads/${branch}`]);
1380
- if (!commandSucceeded(result)) {
1381
- return { status: 'blocked', blocker: commandFailure(result), completedOperations: completed };
1382
- }
1383
- completed.push(op);
1384
- }
1385
- }
1386
- return {
1387
- status: 'completed',
1388
- resultState: 'verified',
1389
- completedOperations: completed
1390
- };
1391
- }
1392
- async function executeSeedOperations(input) {
1393
- if (!input.phase.id.startsWith('seed-')) {
1394
- return null;
1395
- }
1396
- if (input.phase.id === 'seed-valid') {
1397
- const result = await validateGeneratedSeedForPhase(input.inspection.projectRoot, input.runner);
1398
- if (result.status === 'blocked') {
1399
- return { status: 'blocked', blocker: result.issues[0] ?? 'Seed validation failed.', completedOperations: [] };
1400
- }
1401
- return { status: 'completed', resultState: 'verified', completedOperations: input.plan.operations.filter((op) => op.actionId === 'openspec.seed.validate') };
1402
- }
1403
- if (input.phase.id === 'seed-verified') {
1404
- const result = await verifyGeneratedSeedBaselineForPhase(input.inspection.projectRoot, input.runner);
1405
- if (result.status === 'blocked') {
1406
- return { status: 'blocked', blocker: result.issues[0] ?? 'Seed baseline verification failed.', completedOperations: [] };
1407
- }
1408
- return {
1409
- status: 'completed',
1410
- resultState: 'verified',
1411
- evidencePayload: {
1412
- kind: 'seed-verified.v1',
1413
- checks: result.checks.map((check) => ({
1414
- id: check.id,
1415
- taskId: check.taskId,
1416
- status: check.status
1417
- }))
1418
- },
1419
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'openspec.seed.baseline-verify')
1420
- };
1421
- }
1422
- const result = await archiveGeneratedSeedForPhase(input.inspection.projectRoot, input.runner);
1423
- if (result.status === 'blocked') {
1424
- return {
1425
- status: 'blocked',
1426
- blocker: result.issues[0] ?? 'Seed archive failed.',
1427
- completedOperations: result.archiveCompleted
1428
- ? input.plan.operations.filter((op) => op.actionId === 'openspec.seed.archive')
1429
- : [],
1430
- retryableWithoutStateMutation: result.retryableAfterRepair
1431
- };
1432
- }
1433
- return {
1434
- status: 'completed',
1435
- resultState: 'verified',
1436
- evidencePayload: {
1437
- kind: 'seed-archived.v1',
1438
- archiveSyncBehavior: result.archiveSyncBehavior ?? null
1439
- },
1440
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'openspec.seed.archive')
1441
- };
1442
- }
1443
- async function discoverPhase0(input) {
1444
- if (input.phase.id !== 'phase-0-complete') {
1445
- return null;
1446
- }
1447
- const gh = await runCommand(input.runner, {
1448
- executable: 'gh',
1449
- args: ['repo', 'view', '--json', 'id,nameWithOwner,defaultBranchRef,isPrivate']
1450
- }, input.inspection.projectRoot);
1451
- if (!commandSucceeded(gh)) {
1452
- return {
1453
- status: 'blocked',
1454
- blocker: `Phase 0 GitHub read-only discovery failed: ${commandFailure(gh)}`,
1455
- completedOperations: []
1456
- };
1457
- }
1458
- let repo;
1459
- try {
1460
- repo = JSON.parse(gh.stdout);
1461
- }
1462
- catch (error) {
1463
- return {
1464
- status: 'blocked',
1465
- blocker: `Phase 0 GitHub discovery returned invalid JSON: ${errorMessage(error)}`,
1466
- completedOperations: []
1467
- };
1468
- }
1469
- if (!repo.id || !repo.nameWithOwner || !repo.defaultBranchRef?.name) {
1470
- return {
1471
- status: 'blocked',
1472
- blocker: 'Phase 0 GitHub discovery did not return repository id, nameWithOwner, and default branch.',
1473
- completedOperations: []
1474
- };
1475
- }
1476
- const az = await runCommand(input.runner, {
1477
- executable: 'az',
1478
- args: ['account', 'show', '--output', 'json']
1479
- }, input.inspection.projectRoot);
1480
- const azureAccount = commandSucceeded(az) ? safeJson(az.stdout) : null;
1481
- const git = await inspectGitRepository(input.inspection.projectRoot, input.runner);
1482
- const baselineDigest = canonicalSha256({
1483
- gitHead: git.head,
1484
- repositoryId: repo.id,
1485
- repositoryName: repo.nameWithOwner,
1486
- defaultBranch: repo.defaultBranchRef.name
1487
- });
1488
- const facts = {
1489
- repositoryId: repo.id,
1490
- repositoryName: repo.nameWithOwner,
1491
- defaultBranch: repo.defaultBranchRef.name,
1492
- baselineDigest,
1493
- privateStagingDast: false,
1494
- credentialRequired: false,
1495
- statePath: 'none',
1496
- approvedFacts: [
1497
- { id: 'repository.id', value: repo.id },
1498
- { id: 'repository.nameWithOwner', value: repo.nameWithOwner },
1499
- { id: 'repository.defaultBranch', value: repo.defaultBranchRef.name },
1500
- { id: 'repository.isPrivate', value: repo.isPrivate ?? null },
1501
- { id: 'azure.accountReadable', value: azureAccount !== null }
1502
- ]
1503
- };
1504
- const nextState = cloneState(input.inspection.state);
1505
- nextState.repository = {
1506
- id: facts.repositoryId,
1507
- name: facts.repositoryName,
1508
- defaultBranch: facts.defaultBranch
1509
- };
1510
- nextState.applicability = {
1511
- statePath: facts.statePath,
1512
- privateStagingDast: facts.privateStagingDast,
1513
- credentialRequired: facts.credentialRequired
1514
- };
1515
- return {
1516
- status: 'completed',
1517
- resultState: 'verified',
1518
- evidencePayload: {
1519
- kind: 'phase-0-discovery.v1',
1520
- facts: facts.approvedFacts,
1521
- activationCreateChangePlan: {
1522
- deterministic: true,
1523
- graphHash: input.inspection.graphHash
1524
- }
1525
- },
1526
- stateOverride: nextState,
1527
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'github.phase0.discover' || op.actionId === 'azure.phase0.discover')
1528
- };
1529
- }
1530
- function safeJson(value) {
1531
- try {
1532
- return JSON.parse(value);
1533
- }
1534
- catch {
1535
- return null;
1536
- }
1537
- }
1538
- async function executeActivationApproval(input) {
1539
- if (input.phase.id !== 'activation-approved') {
1540
- return null;
1541
- }
1542
- const state = cloneState(input.inspection.state);
1543
- const fileMutations = [];
1544
- if (input.inspection.sourceOfTruth.status === 'none') {
1545
- const facts = buildApprovedPhase0FactsFromState(input.inspection.manifest, input.inspection.state, input.inspection.evidence);
1546
- if (!facts || input.inspection.sourceOfTruth.createPlan.status !== 'ready') {
1547
- return {
1548
- status: 'blocked',
1549
- blocker: input.inspection.sourceOfTruth.createPlan.reason,
1550
- completedOperations: []
1551
- };
1552
- }
1553
- const writePlan = renderGovernanceChangeWritePlan(facts);
1554
- for (const file of writePlan.files) {
1555
- const existing = await readProjectFile(input.inspection.projectRoot, [...file.pathParts]);
1556
- if (existing !== undefined) {
1557
- return {
1558
- status: 'blocked',
1559
- blocker: `Refusing to overwrite existing governance artifact ${file.pathParts.join('/')}.`,
1560
- completedOperations: []
1561
- };
1562
- }
1563
- fileMutations.push({
1564
- type: 'write',
1565
- pathParts: [...file.pathParts],
1566
- content: file.content
1567
- });
1568
- }
1569
- state.activeChange = {
1570
- id: writePlan.changeId,
1571
- kind: writePlan.workflowKind === 'openspec' ? 'openspec' : 'spec-kit'
1572
- };
1573
- }
1574
- else if (input.inspection.sourceOfTruth.status === 'selected') {
1575
- state.activeChange = {
1576
- id: input.inspection.sourceOfTruth.selected.changeId,
1577
- kind: input.inspection.sourceOfTruth.selected.workflowKind === 'openspec' ? 'openspec' : 'spec-kit'
1578
- };
1579
- }
1580
- else {
1581
- return {
1582
- status: 'blocked',
1583
- blocker: 'A compatible governance source of truth is required before activation approval can be recorded.',
1584
- completedOperations: []
1585
- };
1586
- }
1587
- return {
1588
- status: 'completed',
1589
- resultState: 'approved',
1590
- stateOverride: state,
1591
- fileMutations,
1592
- completedOperations: input.plan.operations.filter((op) => op.actionId.endsWith('governance.create-change'))
1593
- };
1594
- }
1595
- async function executeCredentialReady(input) {
1596
- if (input.phase.id !== 'credential-ready') {
1597
- return null;
1598
- }
1599
- const policy = await readProjectFile(input.inspection.projectRoot, [...credentialPolicyPathParts]);
1600
- if (policy === undefined) {
1601
- return {
1602
- status: 'blocked',
1603
- blocker: 'Credential enrollment requires a secure masked input channel or an existing selected-repository App; automated noninteractive apply cannot collect a PAT.',
1604
- completedOperations: []
1605
- };
1606
- }
1607
- const scan = detectCredentialLeaks([{
1608
- source: 'imported-evidence',
1609
- label: credentialPolicyPathParts.join('/'),
1610
- text: policy.toString('utf8')
1611
- }]);
1612
- if (scan.status === 'compromised') {
1613
- return {
1614
- status: 'blocked',
1615
- blocker: scan.guidance.join(' '),
1616
- completedOperations: []
1617
- };
1618
- }
1619
- return {
1620
- status: 'completed',
1621
- resultState: 'verified',
1622
- evidencePayload: {
1623
- kind: 'credential-ready.v1',
1624
- policyPathParts: credentialPolicyPathParts,
1625
- payloadFree: true,
1626
- policyDigest: canonicalSha256(policy.toString('utf8'))
1627
- },
1628
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'github.credential.verify-policy')
1629
- };
1630
- }
1631
- function remoteImportRetention(input) {
1632
- if (input.phase.id !== 'remote-ready') {
1633
- return null;
1634
- }
1635
- if (input.inspection.state.applicability.statePath !== 'bootstrap-local') {
1636
- return {
1637
- status: 'completed',
1638
- resultState: 'verified',
1639
- evidencePayload: { kind: 'remote-ready.v1', retention: 'not-applicable' },
1640
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'azure.remote-ready.verify')
1641
- };
1642
- }
1643
- const importRecord = latestRecordWithPayload(input.inspection, 'remote-import-verified');
1644
- if (!importRecord || !isRecord(importRecord.payload) || importRecord.payload.kind !== remoteImportPayloadKind) {
1645
- return {
1646
- status: 'blocked',
1647
- blocker: 'Remote import retention requires immutable remote-import evidence with state and key identifiers.',
1648
- completedOperations: []
1649
- };
1650
- }
1651
- const encryptedStatePathParts = pathPartLists(importRecord.payload.encryptedStatePathParts, 'encryptedStatePathParts');
1652
- const encryptionKeyPathParts = pathPartLists(importRecord.payload.encryptionKeyPathParts, 'encryptionKeyPathParts');
1653
- const retainedAt = input.now.toISOString();
1654
- const disposeAfter = new Date(input.now.getTime() + 30 * dayMs).toISOString();
1655
- const state = cloneState(input.inspection.state);
1656
- state.bootstrapState = {
1657
- status: 'retained',
1658
- remoteImportEvidenceId: importRecord.evidenceId,
1659
- remoteImportEvidenceDigest: evidenceHeaderDigest(importRecord.header),
1660
- retainedAt,
1661
- disposeAfter,
1662
- encryptedStatePathParts,
1663
- encryptionKeyPathParts
1664
- };
1665
- return {
1666
- status: 'completed',
1667
- resultState: 'retained',
1668
- stateOverride: state,
1669
- evidencePayload: {
1670
- kind: 'remote-ready.v1',
1671
- retention: state.bootstrapState
1672
- },
1673
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'azure.remote-ready.verify')
1674
- };
1675
- }
1676
- function pathPartLists(value, label) {
1677
- if (!Array.isArray(value)) {
1678
- throw new Error(`${label} must be an array of path-part arrays.`);
1679
- }
1680
- return value.map((entry, index) => validateArtifactPathParts(entry, `${label}[${index}]`));
1681
- }
1682
- async function executeBootstrapStateDisposal(input) {
1683
- if (input.phase.id !== 'bootstrap-state-disposed') {
1684
- return null;
1685
- }
1686
- const retention = input.inspection.state.bootstrapState;
1687
- if (!retention || input.inspection.state.applicability.statePath !== 'bootstrap-local') {
1688
- return {
1689
- status: 'completed',
1690
- resultState: 'inapplicable',
1691
- evidencePayload: { kind: bootstrapStateDisposedPayloadKind, reason: 'no retained bootstrap state' },
1692
- completedOperations: []
1693
- };
1694
- }
1695
- if (retention.status === 'disposed') {
1696
- return {
1697
- status: 'completed',
1698
- resultState: 'disposed',
1699
- evidencePayload: { kind: bootstrapStateDisposedPayloadKind, reason: 'already disposed', deletionEvidenceId: retention.deletionEvidenceId ?? null },
1700
- completedOperations: []
1701
- };
1702
- }
1703
- if (Date.parse(retention.disposeAfter) > input.now.getTime()) {
1704
- return {
1705
- status: 'blocked',
1706
- blocker: `Retained bootstrap state is not disposable until ${retention.disposeAfter}.`,
1707
- completedOperations: []
1708
- };
1709
- }
1710
- const importRecord = input.inspection.evidence.find((record) => record.evidenceId === retention.remoteImportEvidenceId);
1711
- if (!importRecord || evidenceHeaderDigest(importRecord.header) !== retention.remoteImportEvidenceDigest) {
1712
- return {
1713
- status: 'blocked',
1714
- blocker: 'Destructive disposal requires the immutable remote-import evidence referenced by retained bootstrap state.',
1715
- completedOperations: []
1716
- };
1717
- }
1718
- if (!isRecord(importRecord.payload) || importRecord.payload.kind !== remoteImportPayloadKind) {
1719
- return {
1720
- status: 'blocked',
1721
- blocker: 'Destructive disposal requires verified remote backend and no-change evidence.',
1722
- completedOperations: []
1723
- };
1724
- }
1725
- const remoteBackendDigest = importRecord.payload.remoteBackendDigest;
1726
- const noChangePlanDigest = importRecord.payload.noChangePlanDigest;
1727
- if (typeof remoteBackendDigest !== 'string' ||
1728
- !/^[a-f0-9]{64}$/u.test(remoteBackendDigest) ||
1729
- typeof noChangePlanDigest !== 'string' ||
1730
- !/^[a-f0-9]{64}$/u.test(noChangePlanDigest)) {
1731
- return {
1732
- status: 'blocked',
1733
- blocker: 'Destructive disposal requires payload-free remote backend and no-change plan digests.',
1734
- completedOperations: []
1735
- };
1736
- }
1737
- const allPaths = [...retention.encryptedStatePathParts, ...retention.encryptionKeyPathParts]
1738
- .map((parts) => validateArtifactPathParts([...parts], 'Bootstrap disposal path'));
1739
- const incompleteCleanup = [];
1740
- for (const parts of allPaths) {
1741
- try {
1742
- const target = await resolveProjectPath(input.inspection.projectRoot, parts);
1743
- const details = await stat(target);
1744
- if (!details.isFile()) {
1745
- incompleteCleanup.push(`${parts.join('/')} is not a regular file.`);
1746
- }
1747
- }
1748
- catch (error) {
1749
- incompleteCleanup.push(`${parts.join('/')} was already absent before disposal: ${errorMessage(error)}`);
1750
- }
1751
- }
1752
- const state = cloneState(input.inspection.state);
1753
- state.bootstrapState = {
1754
- ...retention,
1755
- status: 'disposed',
1756
- disposedAt: input.now.toISOString(),
1757
- deletionEvidenceId: `${input.phase.id}-${safeTimestamp(input.now.toISOString())}`,
1758
- incompleteCleanup
1759
- };
1760
- return {
1761
- status: 'completed',
1762
- resultState: 'disposed',
1763
- stateOverride: state,
1764
- evidencePayload: {
1765
- kind: bootstrapStateDisposedPayloadKind,
1766
- deletedPathParts: allPaths,
1767
- remoteBackendDigest,
1768
- noChangePlanDigest,
1769
- incompleteCleanup,
1770
- payloadFree: true
1771
- },
1772
- fileMutations: allPaths.map((parts) => ({ type: 'delete', pathParts: parts })),
1773
- completedOperations: input.plan.operations.filter((op) => op.actionId === 'local.bootstrap-state.dispose'),
1774
- cleanupWarnings: incompleteCleanup
1775
- };
1776
- }
1777
- function assertGreenRedProof(input) {
1778
- const record = latestRecordWithPayload(input.inspection, 'green-red-proof');
1779
- if (!record || !isRecord(record.payload) || record.payload.kind !== greenRedPayloadKind) {
1780
- throw new Error('Ruleset enforcement requires current green-red-proof evidence with typed proof payload.');
1781
- }
1782
- const green = isRecord(record.payload.green) ? record.payload.green : null;
1783
- const red = isRecord(record.payload.deliberateRed) ? record.payload.deliberateRed : null;
1784
- const greenConclusion = green?.conclusion;
1785
- const redConclusion = red?.conclusion;
1786
- const badConclusions = [greenConclusion, redConclusion].filter((value) => value === 'skipped' || value === 'cancelled' || value === 'neutral');
1787
- if (badConclusions.length > 0) {
1788
- throw new Error('Skipped, cancelled, and neutral checks cannot satisfy green/red proof.');
1789
- }
1790
- if (!red || greenConclusion !== 'success' || redConclusion !== 'failure' || red.deliberate !== true) {
1791
- throw new Error('Ruleset enforcement requires green success and deliberate red failure proof.');
1792
- }
1793
- return record;
1794
- }
1795
- async function executeRulesetPhase(input) {
1796
- if (input.phase.id !== 'rulesets-applied' && input.phase.id !== 'live-readback') {
1797
- return null;
1798
- }
1799
- try {
1800
- assertGreenRedProof(input);
1801
- }
1802
- catch (error) {
1803
- return { status: 'blocked', blocker: errorMessage(error), completedOperations: [] };
1804
- }
1805
- const sourceDigest = rulesetSourceDigestFromEvidence(input.inspection);
1806
- if (!sourceDigest) {
1807
- return {
1808
- status: 'blocked',
1809
- blocker: 'Ruleset enforcement requires a saved ruleset source digest from workflow-source-ready evidence.',
1810
- completedOperations: []
1811
- };
1812
- }
1813
- const adapter = input.adapters.githubRulesets;
1814
- if (!adapter) {
1815
- return {
1816
- status: 'blocked',
1817
- blocker: 'No injected GitHub ruleset adapter is configured; refusing live ruleset calls in this execution context.',
1818
- completedOperations: []
1819
- };
1820
- }
1821
- const envelopeId = input.plan.approval.envelopeId;
1822
- if (input.phase.id === 'rulesets-applied' && !envelopeId) {
1823
- return {
1824
- status: 'blocked',
1825
- blocker: 'Ruleset enforcement requires a persisted enforcement approval envelope.',
1826
- completedOperations: []
1827
- };
1828
- }
1829
- const completed = [];
1830
- const write = input.phase.id === 'rulesets-applied'
1831
- ? await adapter.applyRuleset({
1832
- repository: input.inspection.state.repository.name,
1833
- sourceDigest,
1834
- approvalEnvelopeId: envelopeId ?? 'ungated-readback'
1835
- })
1836
- : await adapter.readRuleset({
1837
- repository: input.inspection.state.repository.name,
1838
- sourceDigest
1839
- });
1840
- completed.push(...input.plan.operations.filter((op) => input.phase.id === 'rulesets-applied'
1841
- ? op.actionId === 'github.ruleset.apply' || op.actionId === 'github.ruleset.readback'
1842
- : op.actionId === 'github.ruleset.readback'));
1843
- if (write.sourceDigest !== sourceDigest || write.readbackDigest !== sourceDigest) {
1844
- return {
1845
- status: 'blocked',
1846
- blocker: 'Post-write live ruleset readback did not match the saved source digest.',
1847
- completedOperations: completed
1848
- };
1849
- }
1850
- const header = evidenceHeaderFor({
1851
- inspection: input.inspection,
1852
- phase: input.phase,
1853
- plan: input.plan,
1854
- result: 'verified',
1855
- now: input.now
1856
- });
1857
- const liveReadback = {
1858
- schemaVersion: input.inspection.state.identity.evidenceHeaderSchemaVersion,
1859
- repositoryId: header.repositoryId,
1860
- identity: header.identity,
1861
- phaseGraphHash: header.phaseGraphHash,
1862
- phaseId: header.phaseId,
1863
- baselineSha: header.baselineSha,
1864
- inputDigest: header.inputDigest,
1865
- transition: header.transition,
1866
- observedAt: input.now.toISOString(),
1867
- provider: 'github',
1868
- resourceType: 'ruleset',
1869
- resourceId: write.resourceId,
1870
- sourceDigest,
1871
- readbackDigest: write.readbackDigest,
1872
- matches: true
1873
- };
1874
- return {
1875
- status: 'completed',
1876
- resultState: 'verified',
1877
- liveReadback: [liveReadback],
1878
- evidencePayload: {
1879
- kind: `${input.phase.id}.v1`,
1880
- sourceDigest,
1881
- resourceId: write.resourceId,
1882
- readbackDigest: write.readbackDigest
1883
- },
1884
- completedOperations: completed
1885
- };
1886
- }
1887
33
  async function executeBuiltInPhase(input) {
1888
34
  const custom = input.adapters.phases?.[input.phase.id];
1889
- if (custom) {
35
+ if (custom)
1890
36
  return await custom.execute(input);
1891
- }
1892
- for (const attempt of [
1893
- executeSeedOperations,
1894
- async (execution) => executeGitOperations(execution, execution.plan.operations),
1895
- discoverPhase0,
1896
- executeActivationApproval,
1897
- executeCredentialReady,
1898
- async (execution) => remoteImportRetention(execution),
1899
- executeBootstrapStateDisposal,
1900
- executeRulesetPhase
1901
- ]) {
1902
- const outcome = await attempt(input);
1903
- if (outcome) {
1904
- return outcome;
1905
- }
1906
- }
1907
- return {
37
+ return await builtInExecutors[input.phase.id]?.(input) ?? {
1908
38
  status: 'blocked',
1909
39
  blocker: `No production adapter is configured for ${input.phase.id}; refusing success-shaped fallback.`,
1910
40
  completedOperations: []
1911
41
  };
1912
42
  }
1913
43
  function sourceOfTruthAllowsPhase(inspection, phaseId) {
1914
- if (phaseOrder(phaseId) <= phaseOrder('phase-0-complete')) {
44
+ if (phaseOrder(phaseId) <= phaseOrder('phase-0-complete'))
1915
45
  return null;
1916
- }
1917
46
  const source = inspection.sourceOfTruth;
1918
- if (phaseId === 'activation-approved' && (source.status === 'none' || source.status === 'selected')) {
47
+ if (phaseId === 'activation-approved' && (source.status === 'none' || source.status === 'selected'))
1919
48
  return null;
1920
- }
1921
- if (source.status === 'selected' && source.reconciliation.status === 'not-required') {
49
+ if (source.status === 'selected' && source.reconciliation.status === 'not-required')
1922
50
  return null;
1923
- }
1924
- if (source.status === 'seed-blocked') {
1925
- return source.blockers.join('; ');
1926
- }
1927
- if (source.status === 'ambiguous' || source.status === 'incompatible') {
51
+ if (source.status === 'seed-blocked' || source.status === 'ambiguous' || source.status === 'incompatible')
1928
52
  return source.blockers.join('; ');
1929
- }
1930
- if (source.status === 'none') {
53
+ if (source.status === 'none')
1931
54
  return source.createPlan.reason;
1932
- }
1933
55
  return 'Active governance source of truth is not ready.';
1934
56
  }
1935
57
  export async function executeApplyNext(input) {
58
+ validateManifestActivationForExecution(input.inspection.manifest);
59
+ return withProjectMutationLock(input.inspection.projectRoot, async (lease) => {
60
+ await lease.assertHeld();
61
+ if (!input.inspection.loadedState && input.inspection.state.repository.id === 'unbound' &&
62
+ input.inspection.readiness.nextReadyPhase?.startsWith('seed-')) {
63
+ await initializeExecutionAnchor(input.inspection, input.now ?? new Date());
64
+ input = { ...input, inspection: await input.reinspect() };
65
+ }
66
+ return executeApplyNextLocked(input, lease);
67
+ });
68
+ }
69
+ async function executeApplyNextLocked(input, lease) {
1936
70
  const runner = input.runner ?? new NodeCommandRunner();
1937
71
  const adapters = input.adapters ?? {};
1938
72
  const now = input.now ?? new Date();
@@ -1942,6 +76,7 @@ export async function executeApplyNext(input) {
1942
76
  return {
1943
77
  ...preview,
1944
78
  applied: false,
79
+ executedPhase: null,
1945
80
  noWrites: false,
1946
81
  executedOperations: [],
1947
82
  evidence: null,
@@ -1955,14 +90,8 @@ export async function executeApplyNext(input) {
1955
90
  if (initialPlan.approval.evaluation.approvalRequired) {
1956
91
  const preview = await previewApplyNext({ inspection: input.inspection, runner, now, execute: true });
1957
92
  return {
1958
- ...preview,
1959
- applied: false,
1960
- noWrites: false,
1961
- executedOperations: [],
1962
- evidence: null,
1963
- stateHash: null,
1964
- rollbackPlan: initialPlan.rollbackPlan,
1965
- cleanupWarnings: []
93
+ ...preview, applied: false, executedPhase: null, noWrites: false,
94
+ executedOperations: [], evidence: null, stateHash: null, rollbackPlan: initialPlan.rollbackPlan, cleanupWarnings: []
1966
95
  };
1967
96
  }
1968
97
  const saved = await saveTransitionPlan(input.inspection.projectRoot, initialPlan);
@@ -1971,147 +100,134 @@ export async function executeApplyNext(input) {
1971
100
  const freshnessIssues = comparePlanFreshness(initialPlan, freshPlan);
1972
101
  if (freshnessIssues.length > 0) {
1973
102
  return {
1974
- schemaVersion: 1,
1975
- command: 'governance apply-next',
1976
- projectRoot: input.inspection.projectRoot,
1977
- execute: true,
1978
- applied: false,
1979
- authorized: false,
1980
- reason: 'stale-after-plan-save',
1981
- message: freshnessIssues.join(' '),
1982
- nextReadyPhase: freshInspection.readiness.nextReadyPhase,
103
+ schemaVersion: 1, command: 'governance apply-next', projectRoot: input.inspection.projectRoot,
104
+ execute: true, applied: false, authorized: false, reason: 'stale-after-plan-save', message: freshnessIssues.join(' '),
105
+ selectedPhase: initialPlan.phaseId, executedPhase: null, nextReadyPhase: freshInspection.readiness.nextReadyPhase,
1983
106
  approval: initialPlan.approval,
1984
- proposedMutations: {
1985
- local: initialPlan.mutationClasses.local,
1986
- remote: initialPlan.mutationClasses.remote,
1987
- operations: initialPlan.operations
1988
- },
1989
- savedPlan: saved,
1990
- noWrites: false,
1991
- blockers: freshnessIssues,
1992
- executedOperations: [],
1993
- evidence: null,
1994
- stateHash: null,
1995
- rollbackPlan: initialPlan.rollbackPlan,
1996
- cleanupWarnings: []
107
+ proposedMutations: { local: initialPlan.mutationClasses.local, remote: initialPlan.mutationClasses.remote, operations: initialPlan.operations },
108
+ savedPlan: saved, noWrites: false, blockers: freshnessIssues, executedOperations: [],
109
+ evidence: null, stateHash: null, rollbackPlan: initialPlan.rollbackPlan, cleanupWarnings: []
1997
110
  };
1998
111
  }
1999
112
  const sourceBlocker = sourceOfTruthAllowsPhase(freshInspection, initialPlan.phaseId);
2000
113
  if (sourceBlocker) {
2001
114
  const nextState = blockedState({ inspection: freshInspection, phase, plan: initialPlan, blocker: sourceBlocker, now });
2002
- const write = await writeOutcomeTransaction({
2003
- projectRoot: freshInspection.projectRoot,
2004
- plan: initialPlan,
2005
- nextState
2006
- });
115
+ const write = await writeOutcomeTransaction({ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState });
2007
116
  return executionBlockedResult(freshInspection, initialPlan, saved, sourceBlocker, [], write.stateHash);
2008
117
  }
118
+ await lease.assertHeld();
2009
119
  const outcome = await executeBuiltInPhase({
2010
- inspection: freshInspection,
2011
- plan: initialPlan,
2012
- phase,
2013
- runner,
2014
- adapters,
2015
- now
120
+ inspection: freshInspection, plan: initialPlan, phase, runner, adapters, now,
121
+ clock: () => input.now ?? new Date(), lease
2016
122
  });
2017
123
  const completedOperations = outcome.completedOperations ?? [];
124
+ for (const completed of completedOperations) {
125
+ assertOperationAllowed(phase, completed);
126
+ if (!initialPlan.operations.some((planned) => canonicalSha256(planned) === canonicalSha256(completed))) {
127
+ throw new Error(`Completed operation ${completed.actionId} was not in the reviewed plan.`);
128
+ }
129
+ }
130
+ for (const mutation of outcome.fileMutations ?? []) {
131
+ const allowed = initialPlan.operations.some((operation) => {
132
+ const prefix = operation.destination.pathParts;
133
+ return !operation.remote && prefix && prefix.every((part, index) => mutation.pathParts[index] === part) &&
134
+ ['write-seed-tasks', 'write-openspec-governance', 'write-workflows', 'write-ruleset-source', 'delete-local-state'].includes(operation.mutationClass) &&
135
+ (mutation.type !== 'delete' || operation.destructive && prefix.length === mutation.pathParts.length);
136
+ });
137
+ if (!allowed)
138
+ throw new Error(`Outcome file mutation ${mutation.pathParts.join('/')} is outside the reviewed plan destinations.`);
139
+ }
140
+ const postSnapshot = await readActivationInputSnapshot(freshInspection.projectRoot, freshInspection.manifest, runner);
141
+ if (postSnapshot.baselineSha !== initialPlan.baselineDigest || phaseInputDigest(phase.id, postSnapshot) !== initialPlan.inputDigest) {
142
+ return executionBlockedResult(freshInspection, initialPlan, saved, 'Relevant project inputs changed during execution; no outcome was persisted.', completedOperations, freshInspection.loadedState?.contentHash ?? '', outcome.cleanupWarnings ?? [], false);
143
+ }
2018
144
  if (outcome.status === 'blocked') {
2019
145
  const blocker = outcome.blocker ?? `Phase ${phase.id} blocked.`;
2020
146
  if (outcome.retryableWithoutStateMutation) {
2021
- const stateHash = freshInspection.loadedState?.contentHash ??
2022
- activationStateContentHash(canonicalJson(freshInspection.state));
147
+ const stateHash = freshInspection.loadedState?.contentHash ?? activationStateContentHash(canonicalJson(freshInspection.state));
2023
148
  return executionBlockedResult(freshInspection, initialPlan, saved, blocker, completedOperations, stateHash, outcome.cleanupWarnings ?? [], false);
2024
149
  }
2025
150
  const nextState = blockedState({ inspection: freshInspection, phase, plan: initialPlan, blocker, now });
2026
- const write = await writeOutcomeTransaction({
2027
- projectRoot: freshInspection.projectRoot,
2028
- plan: initialPlan,
2029
- nextState
2030
- });
151
+ const write = await writeOutcomeTransaction({ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState });
2031
152
  return executionBlockedResult(freshInspection, initialPlan, saved, blocker, completedOperations, write.stateHash, outcome.cleanupWarnings ?? []);
2032
153
  }
2033
154
  const resultState = outcome.resultState ?? 'verified';
2034
155
  if (!phase.terminalStates.includes(resultState)) {
2035
156
  const blocker = `Phase adapter returned ${resultState}, which is not an allowed terminal state for ${phase.id}.`;
2036
157
  const nextState = blockedState({ inspection: freshInspection, phase, plan: initialPlan, blocker, now });
2037
- const write = await writeOutcomeTransaction({
2038
- projectRoot: freshInspection.projectRoot,
2039
- plan: initialPlan,
2040
- nextState
2041
- });
158
+ const write = await writeOutcomeTransaction({ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState });
2042
159
  return executionBlockedResult(freshInspection, initialPlan, saved, blocker, completedOperations, write.stateHash, outcome.cleanupWarnings ?? []);
2043
160
  }
161
+ if (outcome.stateOverride && outcome.stateOverride.repository.id !== freshInspection.state.repository.id) {
162
+ throw new Error('A phase outcome must not replace the immutable local execution anchor.');
163
+ }
164
+ if (outcome.stateOverride) {
165
+ if (canonicalSha256(outcome.stateOverride.phases) !== canonicalSha256(freshInspection.state.phases)) {
166
+ throw new Error('A phase adapter cannot rewrite authoritative phase history or unrelated phase states.');
167
+ }
168
+ if (phase.id !== 'phase-0-complete' &&
169
+ canonicalSha256(outcome.stateOverride.remoteBinding ?? null) !== canonicalSha256(freshInspection.state.remoteBinding ?? null)) {
170
+ throw new Error('Only verified Phase 0 discovery may establish a remote repository binding.');
171
+ }
172
+ }
173
+ if (phase.approvalGate.required) {
174
+ const finalInspection = await input.reinspect();
175
+ const finalPlan = await buildSavedTransitionPlan({ inspection: finalInspection, runner, now: input.now ?? new Date() });
176
+ if (!finalPlan || finalPlan.phaseId !== phase.id || finalPlan.approval.evaluation.approvalRequired ||
177
+ finalPlan.approval.envelopeHash !== initialPlan.approval.envelopeHash) {
178
+ return executionBlockedResult(freshInspection, initialPlan, saved, 'Approval changed or expired before outcome persistence; no successful outcome was recorded.', completedOperations, freshInspection.loadedState?.contentHash ?? '', outcome.cleanupWarnings ?? [], false);
179
+ }
180
+ }
181
+ const outcomeInspection = { ...freshInspection, state: outcome.stateOverride ?? freshInspection.state };
182
+ const boundPayload = isRecord(outcome.evidencePayload)
183
+ ? { ...outcome.evidencePayload, planDigest: initialPlan.planDigest, savedPlanDigest: canonicalSha256(initialPlan) }
184
+ : outcome.evidencePayload;
185
+ const outcomeNow = input.now ?? new Date();
2044
186
  let evidenceRecord;
2045
187
  let evidenceParts;
2046
188
  let evidenceReference;
2047
189
  if (resultState !== 'approved') {
2048
190
  const evidenceId = `${phase.id}-${safeTimestamp(now.toISOString())}`;
2049
191
  const header = evidenceHeaderFor({
2050
- inspection: freshInspection,
2051
- phase,
2052
- plan: initialPlan,
2053
- result: resultState,
2054
- now
192
+ inspection: outcomeInspection, phase, plan: initialPlan, result: resultState, now: outcomeNow,
193
+ payload: boundPayload, liveReadback: outcome.liveReadback
2055
194
  });
2056
195
  evidenceRecord = {
2057
- evidenceId,
2058
- header,
196
+ evidenceId, header,
2059
197
  ...(outcome.liveReadback ? { liveReadback: outcome.liveReadback } : {}),
2060
- ...(outcome.evidencePayload !== undefined ? { payload: outcome.evidencePayload } : {})
198
+ ...(boundPayload !== undefined ? { payload: boundPayload } : {})
2061
199
  };
2062
200
  evidenceParts = evidencePathParts(evidenceId);
2063
- evidenceReference = {
2064
- phaseId: phase.id,
2065
- evidenceId,
2066
- headerDigest: evidenceHeaderDigest(header),
2067
- result: header.result
2068
- };
201
+ evidenceReference = { phaseId: phase.id, evidenceId, headerDigest: evidenceHeaderDigest(header), result: header.result };
202
+ const validation = validateEvidenceFreshness(evidenceRecord, {
203
+ ...freshInspection.contexts[phase.id],
204
+ remoteBindingDigest: remoteBindingDigest(outcomeInspection.state.remoteBinding),
205
+ evidenceReferences: [evidenceReference], reviewedPlans: [initialPlan],
206
+ ...(phase.id === 'seed-archived' && postSnapshot.workflowSpecDigest ? { workflowSpecDigest: postSnapshot.workflowSpecDigest } : {}),
207
+ now: outcomeNow
208
+ });
209
+ if (!validation.valid) {
210
+ return executionBlockedResult(freshInspection, initialPlan, saved, `Completed outcome rejected: ${validation.issues.map((issue) => issue.message).join(' ')}`, completedOperations, freshInspection.loadedState?.contentHash ?? '', outcome.cleanupWarnings ?? [], false);
211
+ }
2069
212
  }
2070
213
  const nextState = nextStateForOutcome({
2071
- inspection: freshInspection,
2072
- phase,
2073
- plan: initialPlan,
2074
- resultState,
2075
- evidenceReference,
2076
- override: outcome.stateOverride,
2077
- now
214
+ inspection: freshInspection, phase, plan: initialPlan, resultState,
215
+ evidenceReference, override: outcome.stateOverride, now
2078
216
  });
2079
217
  const write = await writeOutcomeTransaction({
2080
- projectRoot: freshInspection.projectRoot,
2081
- plan: initialPlan,
2082
- nextState,
2083
- evidenceRecord,
2084
- evidencePathParts: evidenceParts,
2085
- fileMutations: outcome.fileMutations
218
+ projectRoot: freshInspection.projectRoot, plan: initialPlan, nextState, evidenceRecord,
219
+ evidencePathParts: evidenceParts, fileMutations: outcome.fileMutations, filePreconditions: outcome.filePreconditions
2086
220
  });
2087
221
  const rollbackPlan = rollbackPlanForPhase(phase, completedOperations);
2088
222
  return {
2089
- schemaVersion: 1,
2090
- command: 'governance apply-next',
2091
- projectRoot: freshInspection.projectRoot,
2092
- execute: true,
2093
- applied: true,
2094
- authorized: true,
2095
- reason: 'phase-executed',
2096
- message: `Executed one phase: ${phase.id}.`,
2097
- nextReadyPhase: phase.id,
2098
- approval: initialPlan.approval,
2099
- proposedMutations: {
2100
- local: initialPlan.mutationClasses.local,
2101
- remote: initialPlan.mutationClasses.remote,
2102
- operations: initialPlan.operations
2103
- },
2104
- savedPlan: saved,
2105
- noWrites: false,
2106
- blockers: [],
2107
- executedOperations: [
2108
- ...completedOperations,
2109
- ...(evidenceParts ? [evidenceWriteOperation(phase, evidenceParts)] : []),
2110
- stateWriteOperation(phase)
2111
- ],
2112
- evidence: write.evidence,
2113
- stateHash: write.stateHash,
2114
- rollbackPlan,
223
+ schemaVersion: 1, command: 'governance apply-next', projectRoot: freshInspection.projectRoot,
224
+ execute: true, applied: true, authorized: true, reason: 'phase-executed',
225
+ message: `Executed one phase: ${phase.id}.`, selectedPhase: phase.id, executedPhase: phase.id,
226
+ nextReadyPhase: phase.id, approval: initialPlan.approval,
227
+ proposedMutations: { local: initialPlan.mutationClasses.local, remote: initialPlan.mutationClasses.remote, operations: initialPlan.operations },
228
+ savedPlan: saved, noWrites: false, blockers: [],
229
+ executedOperations: [...completedOperations, ...(evidenceParts ? [evidenceWriteOperation(phase, evidenceParts)] : []), stateWriteOperation(phase)],
230
+ evidence: write.evidence, stateHash: write.stateHash, rollbackPlan,
2115
231
  cleanupWarnings: [...rollbackPlan.cleanupWarnings, ...(outcome.cleanupWarnings ?? [])]
2116
232
  };
2117
233
  }
@@ -2119,31 +235,13 @@ function executionBlockedResult(inspection, plan, saved, blocker, completedOpera
2119
235
  const phase = phaseById(inspection.graph, plan.phaseId);
2120
236
  const rollbackPlan = rollbackPlanForPhase(phase, completedOperations);
2121
237
  return {
2122
- schemaVersion: 1,
2123
- command: 'governance apply-next',
2124
- projectRoot: inspection.projectRoot,
2125
- execute: true,
2126
- applied: false,
2127
- authorized: false,
2128
- reason: 'blocked',
2129
- message: blocker,
2130
- nextReadyPhase: plan.phaseId,
2131
- approval: plan.approval,
2132
- proposedMutations: {
2133
- local: plan.mutationClasses.local,
2134
- remote: plan.mutationClasses.remote,
2135
- operations: plan.operations
2136
- },
2137
- savedPlan: saved,
2138
- noWrites: false,
2139
- blockers: [blocker],
2140
- executedOperations: [
2141
- ...completedOperations,
2142
- ...(stateWritten ? [stateWriteOperation(phase)] : [])
2143
- ],
2144
- evidence: null,
2145
- stateHash: stateHashValue,
2146
- rollbackPlan,
238
+ schemaVersion: 1, command: 'governance apply-next', projectRoot: inspection.projectRoot,
239
+ execute: true, applied: false, authorized: false, reason: 'blocked', message: blocker,
240
+ selectedPhase: plan.phaseId, executedPhase: null, nextReadyPhase: plan.phaseId, approval: plan.approval,
241
+ proposedMutations: { local: plan.mutationClasses.local, remote: plan.mutationClasses.remote, operations: plan.operations },
242
+ savedPlan: saved, noWrites: false, blockers: [blocker],
243
+ executedOperations: [...completedOperations, ...(stateWritten ? [stateWriteOperation(phase)] : [])],
244
+ evidence: null, stateHash: stateHashValue, rollbackPlan,
2147
245
  cleanupWarnings: [...rollbackPlan.cleanupWarnings, ...cleanupWarnings]
2148
246
  };
2149
247
  }