@msn-control/liftoff 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/DEVELOPER.md +26 -24
  2. package/README.md +18 -18
  3. package/assets/governance/single-maintainer-gitflow/activation-v2-graph.json +1583 -0
  4. package/dist/adapters/credentials/github-enrollment.d.ts +51 -0
  5. package/dist/adapters/credentials/github-enrollment.js +226 -0
  6. package/dist/adapters/credentials/github-enrollment.js.map +1 -0
  7. package/dist/adapters/credentials/protected-input.d.ts +7 -0
  8. package/dist/adapters/credentials/protected-input.js +68 -0
  9. package/dist/adapters/credentials/protected-input.js.map +1 -0
  10. package/dist/adapters/filesystem/executables.d.ts +2 -0
  11. package/dist/adapters/filesystem/executables.js +70 -0
  12. package/dist/adapters/filesystem/executables.js.map +1 -0
  13. package/dist/adapters/filesystem/update-previews.d.ts +9 -0
  14. package/dist/adapters/filesystem/update-previews.js +45 -2
  15. package/dist/adapters/filesystem/update-previews.js.map +1 -1
  16. package/dist/adapters/filesystem/workstation-attempts.d.ts +13 -0
  17. package/dist/adapters/filesystem/workstation-attempts.js +78 -0
  18. package/dist/adapters/filesystem/workstation-attempts.js.map +1 -0
  19. package/dist/adapters/github/activation-rest.d.ts +40 -0
  20. package/dist/adapters/github/activation-rest.js +272 -0
  21. package/dist/adapters/github/activation-rest.js.map +1 -0
  22. package/dist/adapters/process/shell-command.d.ts +1 -0
  23. package/dist/adapters/process/shell-command.js +5 -2
  24. package/dist/adapters/process/shell-command.js.map +1 -1
  25. package/dist/adapters/state/azure-blob.d.ts +43 -0
  26. package/dist/adapters/state/azure-blob.js +335 -0
  27. package/dist/adapters/state/azure-blob.js.map +1 -0
  28. package/dist/adapters/state/darwin-capabilities.d.ts +33 -0
  29. package/dist/adapters/state/darwin-capabilities.js +146 -0
  30. package/dist/adapters/state/darwin-capabilities.js.map +1 -0
  31. package/dist/adapters/state/darwin-native-host.d.ts +32 -0
  32. package/dist/adapters/state/darwin-native-host.js +294 -0
  33. package/dist/adapters/state/darwin-native-host.js.map +1 -0
  34. package/dist/adapters/state/darwin-profile.d.ts +6 -0
  35. package/dist/adapters/state/darwin-profile.js +56 -0
  36. package/dist/adapters/state/darwin-profile.js.map +1 -0
  37. package/dist/adapters/state/darwin-system-program.d.ts +1 -0
  38. package/dist/adapters/state/darwin-system-program.js +94 -0
  39. package/dist/adapters/state/darwin-system-program.js.map +1 -0
  40. package/dist/adapters/state/local-qualification.d.ts +16 -0
  41. package/dist/adapters/state/local-qualification.js +299 -0
  42. package/dist/adapters/state/local-qualification.js.map +1 -0
  43. package/dist/adapters/state/local.d.ts +25 -0
  44. package/dist/adapters/state/local.js +145 -0
  45. package/dist/adapters/state/local.js.map +1 -0
  46. package/dist/adapters/state/native-command.d.ts +21 -0
  47. package/dist/adapters/state/native-command.js +82 -0
  48. package/dist/adapters/state/native-command.js.map +1 -0
  49. package/dist/adapters/state/native-files.d.ts +2 -0
  50. package/dist/adapters/state/native-files.js +47 -0
  51. package/dist/adapters/state/native-files.js.map +1 -0
  52. package/dist/adapters/state/native-system.d.ts +38 -0
  53. package/dist/adapters/state/native-system.js +86 -0
  54. package/dist/adapters/state/native-system.js.map +1 -0
  55. package/dist/adapters/state/opentofu.d.ts +35 -0
  56. package/dist/adapters/state/opentofu.js +350 -0
  57. package/dist/adapters/state/opentofu.js.map +1 -0
  58. package/dist/adapters/state/owned-process.d.ts +43 -0
  59. package/dist/adapters/state/owned-process.js +252 -0
  60. package/dist/adapters/state/owned-process.js.map +1 -0
  61. package/dist/adapters/state/posix-lock-program.d.ts +1 -0
  62. package/dist/adapters/state/posix-lock-program.js +185 -0
  63. package/dist/adapters/state/posix-lock-program.js.map +1 -0
  64. package/dist/adapters/state/posix-native-lock.d.ts +15 -0
  65. package/dist/adapters/state/posix-native-lock.js +145 -0
  66. package/dist/adapters/state/posix-native-lock.js.map +1 -0
  67. package/dist/adapters/state/protected-workspace.d.ts +57 -0
  68. package/dist/adapters/state/protected-workspace.js +412 -0
  69. package/dist/adapters/state/protected-workspace.js.map +1 -0
  70. package/dist/application/context.d.ts +3 -0
  71. package/dist/application/diagnose/doctor.d.ts +5 -0
  72. package/dist/application/diagnose/doctor.js +21 -5
  73. package/dist/application/diagnose/doctor.js.map +1 -1
  74. package/dist/application/diagnose/generated-project.js +2 -7
  75. package/dist/application/diagnose/generated-project.js.map +1 -1
  76. package/dist/application/initialize/fixture.js +8 -1
  77. package/dist/application/initialize/fixture.js.map +1 -1
  78. package/dist/application/initialize/use-case.js +70 -36
  79. package/dist/application/initialize/use-case.js.map +1 -1
  80. package/dist/application/project/manifest.js +5 -4
  81. package/dist/application/project/manifest.js.map +1 -1
  82. package/dist/application/state-migration/execution.d.ts +32 -0
  83. package/dist/application/state-migration/execution.js +440 -0
  84. package/dist/application/state-migration/execution.js.map +1 -0
  85. package/dist/application/state-migration/index.d.ts +22 -0
  86. package/dist/application/state-migration/index.js +17 -0
  87. package/dist/application/state-migration/index.js.map +1 -0
  88. package/dist/application/state-migration/lifecycle.d.ts +3 -0
  89. package/dist/application/state-migration/lifecycle.js +149 -0
  90. package/dist/application/state-migration/lifecycle.js.map +1 -0
  91. package/dist/application/state-migration/planning.d.ts +7 -0
  92. package/dist/application/state-migration/planning.js +150 -0
  93. package/dist/application/state-migration/planning.js.map +1 -0
  94. package/dist/application/state-migration/recovery.d.ts +5 -0
  95. package/dist/application/state-migration/recovery.js +145 -0
  96. package/dist/application/state-migration/recovery.js.map +1 -0
  97. package/dist/application/state-migration/runtime.d.ts +32 -0
  98. package/dist/application/state-migration/runtime.js +188 -0
  99. package/dist/application/state-migration/runtime.js.map +1 -0
  100. package/dist/application/state-migration/service.d.ts +2 -0
  101. package/dist/application/state-migration/service.js +20 -0
  102. package/dist/application/state-migration/service.js.map +1 -0
  103. package/dist/application/update/command-guidance.d.ts +19 -2
  104. package/dist/application/update/command-guidance.js +20 -6
  105. package/dist/application/update/command-guidance.js.map +1 -1
  106. package/dist/application/update/guidance-context.d.ts +2 -0
  107. package/dist/application/update/guidance-context.js +35 -0
  108. package/dist/application/update/guidance-context.js.map +1 -0
  109. package/dist/application/update/inspection.d.ts +21 -4
  110. package/dist/application/update/inspection.js +88 -29
  111. package/dist/application/update/inspection.js.map +1 -1
  112. package/dist/application/update/migration-runtime.js +25 -13
  113. package/dist/application/update/migration-runtime.js.map +1 -1
  114. package/dist/application/update/output.d.ts +4 -1
  115. package/dist/application/update/output.js +14 -4
  116. package/dist/application/update/output.js.map +1 -1
  117. package/dist/application/update/preview.d.ts +10 -1
  118. package/dist/application/update/preview.js +26 -4
  119. package/dist/application/update/preview.js.map +1 -1
  120. package/dist/application/update/revalidation-plan.d.ts +1 -0
  121. package/dist/application/update/revalidation-plan.js +27 -15
  122. package/dist/application/update/revalidation-plan.js.map +1 -1
  123. package/dist/application/update/revalidation.d.ts +4 -1
  124. package/dist/application/update/revalidation.js +64 -23
  125. package/dist/application/update/revalidation.js.map +1 -1
  126. package/dist/application/update/use-case.js +50 -29
  127. package/dist/application/update/use-case.js.map +1 -1
  128. package/dist/cli/args/definitions.js +11 -3
  129. package/dist/cli/args/definitions.js.map +1 -1
  130. package/dist/cli/args/parser.js +29 -3
  131. package/dist/cli/args/parser.js.map +1 -1
  132. package/dist/domain/governance/activation/approvals.d.ts +264 -7
  133. package/dist/domain/governance/activation/approvals.js +166 -23
  134. package/dist/domain/governance/activation/approvals.js.map +1 -1
  135. package/dist/domain/governance/activation/capabilities.js +5 -2
  136. package/dist/domain/governance/activation/capabilities.js.map +1 -1
  137. package/dist/domain/governance/activation/evidence.d.ts +2 -1
  138. package/dist/domain/governance/activation/evidence.js +64 -11
  139. package/dist/domain/governance/activation/evidence.js.map +1 -1
  140. package/dist/domain/governance/activation/graph.d.ts +11 -384
  141. package/dist/domain/governance/activation/graph.js +424 -379
  142. package/dist/domain/governance/activation/graph.js.map +1 -1
  143. package/dist/domain/governance/activation/inputs.d.ts +3 -1
  144. package/dist/domain/governance/activation/inputs.js +47 -6
  145. package/dist/domain/governance/activation/inputs.js.map +1 -1
  146. package/dist/domain/governance/activation/operations.d.ts +4 -1
  147. package/dist/domain/governance/activation/operations.js +110 -22
  148. package/dist/domain/governance/activation/operations.js.map +1 -1
  149. package/dist/domain/governance/activation/readiness.d.ts +10 -0
  150. package/dist/domain/governance/activation/readiness.js +77 -38
  151. package/dist/domain/governance/activation/readiness.js.map +1 -1
  152. package/dist/domain/governance/activation/types.d.ts +151 -9
  153. package/dist/domain/governance/activation/types.js +23 -1
  154. package/dist/domain/governance/activation/types.js.map +1 -1
  155. package/dist/domain/governance/activation/validators.d.ts +4 -1
  156. package/dist/domain/governance/activation/validators.js +419 -26
  157. package/dist/domain/governance/activation/validators.js.map +1 -1
  158. package/dist/domain/governance/policy/identity.d.ts +55 -13
  159. package/dist/domain/governance/policy/identity.js +28 -8
  160. package/dist/domain/governance/policy/identity.js.map +1 -1
  161. package/dist/domain/project/artifact-lifecycle.d.ts +2 -1
  162. package/dist/domain/project/artifact-lifecycle.js +7 -1
  163. package/dist/domain/project/artifact-lifecycle.js.map +1 -1
  164. package/dist/domain/project/catalog.d.ts +50 -1
  165. package/dist/domain/project/catalog.js +104 -4
  166. package/dist/domain/project/catalog.js.map +1 -1
  167. package/dist/domain/project/contracts.d.ts +9 -1
  168. package/dist/domain/project/infrastructure-layout.d.ts +1 -1
  169. package/dist/domain/project/infrastructure-layout.js +3 -1
  170. package/dist/domain/project/infrastructure-layout.js.map +1 -1
  171. package/dist/domain/project/manifest/governance.d.ts +1 -1
  172. package/dist/domain/project/manifest/governance.js +9 -10
  173. package/dist/domain/project/manifest/governance.js.map +1 -1
  174. package/dist/domain/project/planning.js +4 -5
  175. package/dist/domain/project/planning.js.map +1 -1
  176. package/dist/domain/repair/stateful-bounded.d.ts +1 -0
  177. package/dist/domain/repair/stateful-bounded.js +37 -0
  178. package/dist/domain/repair/stateful-bounded.js.map +1 -0
  179. package/dist/domain/repair/stateful-invariants.d.ts +28 -0
  180. package/dist/domain/repair/stateful-invariants.js +278 -0
  181. package/dist/domain/repair/stateful-invariants.js.map +1 -0
  182. package/dist/domain/repair/stateful.d.ts +690 -0
  183. package/dist/domain/repair/stateful.js +26 -0
  184. package/dist/domain/repair/stateful.js.map +1 -0
  185. package/dist/domain/workstation/constraints.d.ts +1 -0
  186. package/dist/domain/workstation/constraints.js.map +1 -1
  187. package/dist/domain/workstation/contracts.d.ts +42 -0
  188. package/dist/domain/workstation/contracts.js +2 -0
  189. package/dist/domain/workstation/contracts.js.map +1 -0
  190. package/dist/domain/workstation/executables.d.ts +19 -0
  191. package/dist/domain/workstation/executables.js +68 -0
  192. package/dist/domain/workstation/executables.js.map +1 -0
  193. package/dist/domain/workstation/versions.d.ts +8 -0
  194. package/dist/domain/workstation/versions.js +145 -0
  195. package/dist/domain/workstation/versions.js.map +1 -0
  196. package/dist/framework-adapters.d.ts +2 -0
  197. package/dist/framework-adapters.js +148 -22
  198. package/dist/framework-adapters.js.map +1 -1
  199. package/dist/framework-validation.d.ts +6 -0
  200. package/dist/framework-validation.js +89 -24
  201. package/dist/framework-validation.js.map +1 -1
  202. package/dist/generators/common/base.js +57 -17
  203. package/dist/generators/common/base.js.map +1 -1
  204. package/dist/governance-activation/activation-state.js +39 -7
  205. package/dist/governance-activation/activation-state.js.map +1 -1
  206. package/dist/governance-activation/authority-records.d.ts +4 -0
  207. package/dist/governance-activation/authority-records.js +24 -0
  208. package/dist/governance-activation/authority-records.js.map +1 -0
  209. package/dist/governance-activation/commands.d.ts +9 -1
  210. package/dist/governance-activation/commands.js +392 -97
  211. package/dist/governance-activation/commands.js.map +1 -1
  212. package/dist/governance-activation/compatibility.d.ts +11 -8
  213. package/dist/governance-activation/compatibility.js +37 -23
  214. package/dist/governance-activation/compatibility.js.map +1 -1
  215. package/dist/governance-activation/doctor.js +40 -16
  216. package/dist/governance-activation/doctor.js.map +1 -1
  217. package/dist/governance-activation/github-config.d.ts +17 -0
  218. package/dist/governance-activation/github-config.js +88 -0
  219. package/dist/governance-activation/github-config.js.map +1 -0
  220. package/dist/governance-activation/github-discovery.d.ts +18 -0
  221. package/dist/governance-activation/github-discovery.js +229 -0
  222. package/dist/governance-activation/github-discovery.js.map +1 -0
  223. package/dist/governance-activation/github-ports.d.ts +12 -0
  224. package/dist/governance-activation/github-ports.js +4 -0
  225. package/dist/governance-activation/github-ports.js.map +1 -0
  226. package/dist/governance-activation/github-publication.d.ts +3 -0
  227. package/dist/governance-activation/github-publication.js +190 -0
  228. package/dist/governance-activation/github-publication.js.map +1 -0
  229. package/dist/governance-activation/historical-common.d.ts +98 -0
  230. package/dist/governance-activation/historical-common.js +269 -0
  231. package/dist/governance-activation/historical-common.js.map +1 -0
  232. package/dist/governance-activation/historical-credential-policy.d.ts +3 -0
  233. package/dist/governance-activation/historical-credential-policy.js +91 -0
  234. package/dist/governance-activation/historical-credential-policy.js.map +1 -0
  235. package/dist/governance-activation/historical-inputs.d.ts +6 -0
  236. package/dist/governance-activation/historical-inputs.js +57 -0
  237. package/dist/governance-activation/historical-inputs.js.map +1 -0
  238. package/dist/governance-activation/historical-safety.d.ts +9 -0
  239. package/dist/governance-activation/historical-safety.js +102 -0
  240. package/dist/governance-activation/historical-safety.js.map +1 -0
  241. package/dist/governance-activation/historical-source-metadata.d.ts +31 -0
  242. package/dist/governance-activation/historical-source-metadata.js +55 -0
  243. package/dist/governance-activation/historical-source-metadata.js.map +1 -0
  244. package/dist/governance-activation/historical-state.d.ts +12 -50
  245. package/dist/governance-activation/historical-state.js +195 -308
  246. package/dist/governance-activation/historical-state.js.map +1 -1
  247. package/dist/governance-activation/historical-v1-auxiliary.d.ts +1 -0
  248. package/dist/governance-activation/historical-v1-auxiliary.js +43 -0
  249. package/dist/governance-activation/historical-v1-auxiliary.js.map +1 -0
  250. package/dist/governance-activation/historical-v1-phase-contracts.d.ts +31 -0
  251. package/dist/governance-activation/historical-v1-phase-contracts.js +46 -0
  252. package/dist/governance-activation/historical-v1-phase-contracts.js.map +1 -0
  253. package/dist/governance-activation/historical-v2.d.ts +53 -0
  254. package/dist/governance-activation/historical-v2.js +313 -0
  255. package/dist/governance-activation/historical-v2.js.map +1 -0
  256. package/dist/governance-activation/history-contracts.d.ts +21 -12
  257. package/dist/governance-activation/history-contracts.js +99 -27
  258. package/dist/governance-activation/history-contracts.js.map +1 -1
  259. package/dist/governance-activation/inputs.d.ts +8 -2
  260. package/dist/governance-activation/inputs.js +41 -6
  261. package/dist/governance-activation/inputs.js.map +1 -1
  262. package/dist/governance-activation/migration-history.d.ts +43 -5
  263. package/dist/governance-activation/migration-history.js +231 -50
  264. package/dist/governance-activation/migration-history.js.map +1 -1
  265. package/dist/governance-activation/migration.d.ts +7 -4
  266. package/dist/governance-activation/migration.js +53 -252
  267. package/dist/governance-activation/migration.js.map +1 -1
  268. package/dist/governance-activation/phase-azure.d.ts +3 -0
  269. package/dist/governance-activation/phase-azure.js +205 -0
  270. package/dist/governance-activation/phase-azure.js.map +1 -0
  271. package/dist/governance-activation/phase-bootstrap-state.js +3 -0
  272. package/dist/governance-activation/phase-bootstrap-state.js.map +1 -1
  273. package/dist/governance-activation/phase-github.d.ts +3 -0
  274. package/dist/governance-activation/phase-github.js +192 -0
  275. package/dist/governance-activation/phase-github.js.map +1 -0
  276. package/dist/governance-activation/phase-governance.js +8 -2
  277. package/dist/governance-activation/phase-governance.js.map +1 -1
  278. package/dist/governance-activation/public-plans.d.ts +36 -0
  279. package/dist/governance-activation/public-plans.js +100 -0
  280. package/dist/governance-activation/public-plans.js.map +1 -0
  281. package/dist/governance-activation/read-only.d.ts +3 -1
  282. package/dist/governance-activation/read-only.js +7 -3
  283. package/dist/governance-activation/read-only.js.map +1 -1
  284. package/dist/governance-activation/reconciliation.js +10 -0
  285. package/dist/governance-activation/reconciliation.js.map +1 -1
  286. package/dist/governance-activation/seed-lifecycle.js +14 -25
  287. package/dist/governance-activation/seed-lifecycle.js.map +1 -1
  288. package/dist/governance-activation/source-of-truth.d.ts +21 -5
  289. package/dist/governance-activation/source-of-truth.js +102 -13
  290. package/dist/governance-activation/source-of-truth.js.map +1 -1
  291. package/dist/governance-activation/task-projection.d.ts +4 -1
  292. package/dist/governance-activation/task-projection.js +32 -3
  293. package/dist/governance-activation/task-projection.js.map +1 -1
  294. package/dist/governance-activation/task-writes.d.ts +42 -0
  295. package/dist/governance-activation/task-writes.js +250 -0
  296. package/dist/governance-activation/task-writes.js.map +1 -0
  297. package/dist/governance-activation/transition-files.d.ts +12 -0
  298. package/dist/governance-activation/transition-files.js +109 -0
  299. package/dist/governance-activation/transition-files.js.map +1 -0
  300. package/dist/governance-activation/transition-planning.d.ts +3 -1
  301. package/dist/governance-activation/transition-planning.js +158 -20
  302. package/dist/governance-activation/transition-planning.js.map +1 -1
  303. package/dist/governance-activation/transition-ports.d.ts +31 -3
  304. package/dist/governance-activation/transition-records.d.ts +11 -1
  305. package/dist/governance-activation/transition-records.js +52 -6
  306. package/dist/governance-activation/transition-records.js.map +1 -1
  307. package/dist/governance-activation/transitions.d.ts +5 -0
  308. package/dist/governance-activation/transitions.js +271 -37
  309. package/dist/governance-activation/transitions.js.map +1 -1
  310. package/dist/governance-assessment/engine.js +22 -15
  311. package/dist/governance-assessment/engine.js.map +1 -1
  312. package/dist/governance-assessment/project.d.ts +2 -0
  313. package/dist/governance-assessment/project.js +2 -1
  314. package/dist/governance-assessment/project.js.map +1 -1
  315. package/dist/interactive.d.ts +0 -1
  316. package/dist/interactive.js +3 -22
  317. package/dist/interactive.js.map +1 -1
  318. package/dist/openspec-profile.d.ts +2 -1
  319. package/dist/openspec-profile.js +11 -41
  320. package/dist/openspec-profile.js.map +1 -1
  321. package/dist/repository-governance.d.ts +10 -5
  322. package/dist/repository-governance.js +178 -92
  323. package/dist/repository-governance.js.map +1 -1
  324. package/dist/telemetry/contract.d.ts +1 -1
  325. package/dist/telemetry/contract.js +3 -0
  326. package/dist/telemetry/contract.js.map +1 -1
  327. package/dist/workstation-catalog.d.ts +13 -1
  328. package/dist/workstation-catalog.js +122 -0
  329. package/dist/workstation-catalog.js.map +1 -1
  330. package/dist/workstation.d.ts +69 -14
  331. package/dist/workstation.js +600 -215
  332. package/dist/workstation.js.map +1 -1
  333. package/docs/cli-reference.md +94 -23
  334. package/docs/configuration-and-manifests.md +11 -10
  335. package/docs/prerequisites.md +93 -18
  336. package/docs/repository-governance.md +68 -40
  337. package/docs/spec-workflows-and-agents.md +132 -13
  338. package/docs/troubleshooting.md +28 -0
  339. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
- import { access, readdir } from 'node:fs/promises';
1
+ import { access, open, readdir } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { readBooleanFlag, readStringFlag } from '../cli/args/readers.js';
4
5
  import { findProjectRoot } from '../adapters/filesystem/project-discovery.js';
@@ -11,27 +12,35 @@ import { governanceArtifactPaths, validateGovernancePolicy } from '../repository
11
12
  import { governanceAssessmentCommand } from '../governance-assessment/command.js';
12
13
  import { loadActivationState } from './activation-state.js';
13
14
  import { canonicalSha256 } from '../domain/governance/activation/canonical-json.js';
14
- import { canonicalApprovalEnvelopeHash, evaluateApprovalForTransitionPlan, transitionPlanForPhase } from '../domain/governance/activation/approvals.js';
15
+ import { approvalRequestForSavedPlan, canonicalApprovalEnvelopeHash, evaluateApprovalForTransitionPlan, transitionPlanForPhase } from '../domain/governance/activation/approvals.js';
15
16
  import { buildPatEnrollmentGuidance, canonicalCredentialRepository, credentialPolicyPathParts, detectCredentialLeaks, runnerPreflightPermissions, validateCredentialPolicyUsage } from './credentials.js';
16
- import { selectLatestPhaseEvidence } from '../domain/governance/activation/evidence.js';
17
+ import { assertPhaseOutputsBound, selectLatestPhaseEvidence } from '../domain/governance/activation/evidence.js';
17
18
  import { canonicalPhaseGraph, canonicalPhaseGraphHash, currentActivationIdentity } from '../domain/governance/activation/graph.js';
18
19
  import { governanceActivationPolicyVersion } from '../domain/governance/policy/identity.js';
19
20
  import { calculatePhaseReadiness } from '../domain/governance/activation/readiness.js';
20
- import { projectOpenSpecTaskCheckboxes } from './task-projection.js';
21
+ import { projectGovernanceChangeTasks, projectOpenSpecTaskCheckboxes } from './task-projection.js';
21
22
  import { inspectGovernanceSourceOfTruth } from './source-of-truth.js';
22
23
  import { discoverGeneratedSeed, inspectArchivedSeedIntegrity, seedInfrastructureBaselineBlocker } from './seed-lifecycle.js';
23
24
  import { executeApplyNext, previewApplyNext } from './transitions.js';
24
- import { phaseIds } from '../domain/governance/activation/types.js';
25
- import { activationEvidenceContexts, readActivationInputSnapshot } from './inputs.js';
25
+ import { phaseIds, phaseScope } from '../domain/governance/activation/types.js';
26
+ import { activationEvidenceContexts, activationSensitivePathExclusions, protectedLocalInputBlockers, readActivationInputSnapshot } from './inputs.js';
26
27
  import { phaseCapabilities } from '../domain/governance/activation/capabilities.js';
27
28
  import { readActivationEvidence, readReviewedTransitionPlans } from './read-only.js';
28
- import { inspectActivationMigrationHistory } from './migration-history.js';
29
+ import { inspectActivationMigrationHistory, historicalLifecyclePhaseBlockers } from './migration-history.js';
29
30
  import { migrationRevalidationPhaseIds } from './history-contracts.js';
30
31
  import { validateApprovalEnvelope, validateManagedPhaseGraph, validateCredentialPolicy, validateManifestActivationForExecution } from '../domain/governance/activation/validators.js';
32
+ import { validateActivationConfiguration } from '../domain/governance/activation/validators.js';
33
+ import { remoteRepository } from '../domain/governance/activation/inputs.js';
34
+ import { isRecord } from '../domain/governance/activation/canonical-json.js';
35
+ import { approveGovernancePreview, loadGovernancePreview, saveGovernancePreview } from './public-plans.js';
36
+ import { parseArgs } from '../cli/args/parser.js';
31
37
  const governanceSubcommands = new Set([
32
38
  'status',
33
39
  'plan',
40
+ 'approve',
34
41
  'apply-next',
42
+ 'credential-enroll',
43
+ 'recover',
35
44
  'resume',
36
45
  'verify'
37
46
  ]);
@@ -66,7 +75,12 @@ function errorCode(error) {
66
75
  return typeof code === 'string' ? code : undefined;
67
76
  }
68
77
  function json(presentation, value) {
69
- presentation.rawStdout(`${JSON.stringify(value, null, 2)}\n`);
78
+ const text = `${JSON.stringify(value, null, 2)}\n`;
79
+ const scan = detectCredentialLeaks([{ source: 'generated-artifact', label: 'governance command output', text }]);
80
+ if (scan.status === 'compromised') {
81
+ throw new Error('Governance output contained credential-shaped data and was withheld; inspect the protected execution checkpoint.');
82
+ }
83
+ presentation.rawStdout(text);
70
84
  }
71
85
  function parseGovernanceSubcommand(parsed) {
72
86
  if (!parsed.subcommand || !governanceSubcommands.has(parsed.subcommand)) {
@@ -117,7 +131,9 @@ function notStartedState(manifest) {
117
131
  applicability: {
118
132
  statePath: 'none',
119
133
  privateStagingDast: 'unknown',
120
- credentialRequired: 'unknown'
134
+ credentialRequired: 'unknown',
135
+ cloudStateRequired: 'unknown',
136
+ privateRunnerRequired: 'unknown'
121
137
  },
122
138
  phases: emptyPhaseState(now),
123
139
  createdAt: now,
@@ -193,14 +209,15 @@ async function loadEvidence(projectRoot) {
193
209
  return readActivationEvidence(projectRoot);
194
210
  }
195
211
  function repositoryFromState(state) {
196
- const repositoryName = state.repository.name.includes('/')
197
- ? state.repository.name.split('/').at(-1)
198
- : state.repository.name;
199
- const owner = state.repository.name.includes('/')
200
- ? state.repository.name.split('/')[0]
212
+ const remote = remoteRepository(state);
213
+ const repositoryName = remote.name.includes('/')
214
+ ? remote.name.split('/').at(-1)
215
+ : remote.name;
216
+ const owner = remote.name.includes('/')
217
+ ? remote.name.split('/')[0]
201
218
  : 'local';
202
219
  return canonicalCredentialRepository({
203
- id: state.repository.id,
220
+ id: remote.id,
204
221
  owner,
205
222
  name: repositoryName
206
223
  });
@@ -235,7 +252,7 @@ async function inspectCredentialPolicy(projectRoot, state) {
235
252
  ready: false,
236
253
  guidance,
237
254
  policy: null,
238
- issues: [`${pathLabel} is missing. Public credential enrollment and independent credential readback are unavailable; do not hand-create credential proof or supply a token through setup.`]
255
+ issues: [`${pathLabel} is missing. Preview credential-ready and use governance credential-enroll with the approved plan and a private input channel.`]
239
256
  };
240
257
  }
241
258
  const text = bytes.toString('utf8');
@@ -285,7 +302,7 @@ async function inspectCredentialPolicy(projectRoot, state) {
285
302
  ready: false,
286
303
  guidance: null,
287
304
  policy,
288
- issues: [...usage.issues, 'Independent credential readback and public credential enrollment are unavailable; a policy file alone is not proof.']
305
+ issues: [...usage.issues]
289
306
  };
290
307
  }
291
308
  catch (error) {
@@ -322,14 +339,17 @@ function buildEvidenceFreshness(graph, evidence, contexts) {
322
339
  return [phase.id, freshnessEntry(phase, selection)];
323
340
  }));
324
341
  }
325
- async function inspectGovernance(projectRoot, runner, now = new Date()) {
342
+ async function inspectGovernance(projectRoot, runner, now = new Date(), options = {}) {
326
343
  const manifest = await loadManifest(projectRoot);
327
344
  validateManifestActivationForExecution(manifest);
328
345
  const graph = await loadGovernanceGraph(projectRoot);
329
346
  await assertPolicyIdentity(projectRoot, manifest);
330
347
  const loadedState = await loadActivationState(projectRoot);
331
348
  const migration = await inspectActivationMigrationHistory(projectRoot);
332
- const state = loadedState?.state ?? notStartedState(manifest);
349
+ const state = { ...(loadedState?.state ?? notStartedState(manifest)),
350
+ ...(options.activationInputs ? { activationInputs: options.activationInputs } : {}) };
351
+ const isStatusOrVerify = options.command === 'status' || options.command === 'verify';
352
+ const scope = options.scope ?? (isStatusOrVerify ? 'activation' : 'local');
333
353
  if (state.identity.phaseGraphHash !== graph.hash) {
334
354
  throw new Error(`Activation state graph hash ${state.identity.phaseGraphHash} does not match loaded graph hash ${graph.hash}.`);
335
355
  }
@@ -338,9 +358,19 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
338
358
  }
339
359
  const approvals = await loadApprovals(projectRoot, state.identity);
340
360
  const evidence = await loadEvidence(projectRoot);
341
- const snapshot = await readActivationInputSnapshot(projectRoot, manifest, runner);
342
- const contexts = activationEvidenceContexts(graph.graph, state, snapshot, now);
361
+ assertPhaseOutputsBound(state, evidence);
343
362
  const reviewedPlans = await readReviewedTransitionPlans(projectRoot);
363
+ if (!options.activationInputs) {
364
+ const approvedConfiguration = reviewedPlans.filter((plan) => plan.configuration &&
365
+ approvals.some((approval) => approval.id === plan.approval.envelopeId &&
366
+ canonicalApprovalEnvelopeHash(approval) === plan.approval.envelopeHash))
367
+ .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0]?.configuration;
368
+ if (approvedConfiguration)
369
+ state.activationInputs = approvedConfiguration;
370
+ }
371
+ const sensitivePathExclusions = activationSensitivePathExclusions(state, migration.status === 'committed' ? migration.lifecycleObligations.map((obligation) => obligation.retention) : []);
372
+ const snapshot = await readActivationInputSnapshot(projectRoot, manifest, runner, { sensitivePathExclusions });
373
+ const contexts = activationEvidenceContexts(graph.graph, state, snapshot, now);
344
374
  for (const phase of phaseIds)
345
375
  contexts[phase].reviewedPlans = reviewedPlans;
346
376
  const sourceOfTruth = await inspectGovernanceSourceOfTruth({
@@ -364,7 +394,19 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
364
394
  !evidence.some((record) => archiveAndLaterPhases.includes(record.header.phaseId));
365
395
  const credential = await inspectCredentialPolicy(projectRoot, state);
366
396
  const evidenceFreshness = buildEvidenceFreshness(graph.graph, evidence, contexts);
397
+ if (credential.policy && credential.status === 'valid') {
398
+ const proof = selectLatestPhaseEvidence(evidence.filter((entry) => entry.header.phaseId === 'credential-ready'), contexts['credential-ready']).selected;
399
+ credential.ready = !!proof && isRecord(proof.payload) && proof.payload.policyDigest === canonicalSha256(credential.policy);
400
+ if (!credential.ready)
401
+ credential.issues = ['Independent credential readback and current verification are required; a policy file alone is not proof.'];
402
+ }
367
403
  const infrastructureBlocker = seedInfrastructureBaselineBlocker(manifest);
404
+ const localInputBlockers = [
405
+ ...(infrastructureBlocker ? [infrastructureBlocker] : []),
406
+ ...protectedLocalInputBlockers(sensitivePathExclusions)
407
+ ];
408
+ const historicalLifecycleObligations = migration.status === 'committed' ? migration.lifecycleObligations : [];
409
+ const historicalProtection = historicalLifecyclePhaseBlockers(historicalLifecycleObligations);
368
410
  const readiness = calculatePhaseReadiness({
369
411
  graph: graph.graph,
370
412
  state,
@@ -372,15 +414,43 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
372
414
  evidence,
373
415
  transitionContexts: contexts,
374
416
  retryArchivedSeedBaseline,
417
+ scope,
418
+ recoverPhase: options.recoverPhase,
419
+ historicalLifecycleBlockers: historicalProtection['bootstrap-state-disposed'],
375
420
  phaseBlockers: {
376
421
  ...Object.fromEntries(Object.entries(phaseCapabilities).filter(([, capability]) => capability.blocker)
377
422
  .map(([id, capability]) => [id, [capability.blocker]])),
378
423
  ...(archivedSeedIntegrity.status === 'invalid' ? { 'seed-archived': archivedSeedIntegrity.issues } : {}),
379
424
  ...(seedDiscovery.state === 'blocked' ? { 'seed-valid': seedDiscovery.issues } : {}),
380
- ...(infrastructureBlocker ? { 'seed-verified': [infrastructureBlocker] } : {})
425
+ ...(localInputBlockers.length ? { 'seed-verified': localInputBlockers } : {}),
426
+ ...historicalProtection
381
427
  },
382
428
  now
383
429
  });
430
+ let resolvedScope = options.scope ?? (isStatusOrVerify ? 'activation' : (readiness.completion.local ? 'activation' : 'local'));
431
+ let finalReadiness = readiness;
432
+ if (resolvedScope !== scope) {
433
+ finalReadiness = calculatePhaseReadiness({
434
+ graph: graph.graph,
435
+ state,
436
+ approvals,
437
+ evidence,
438
+ transitionContexts: contexts,
439
+ retryArchivedSeedBaseline,
440
+ scope: resolvedScope,
441
+ recoverPhase: options.recoverPhase,
442
+ historicalLifecycleBlockers: historicalProtection['bootstrap-state-disposed'],
443
+ phaseBlockers: {
444
+ ...Object.fromEntries(Object.entries(phaseCapabilities).filter(([, capability]) => capability.blocker)
445
+ .map(([id, capability]) => [id, [capability.blocker]])),
446
+ ...(archivedSeedIntegrity.status === 'invalid' ? { 'seed-archived': archivedSeedIntegrity.issues } : {}),
447
+ ...(seedDiscovery.state === 'blocked' ? { 'seed-valid': seedDiscovery.issues } : {}),
448
+ ...(localInputBlockers.length ? { 'seed-verified': localInputBlockers } : {}),
449
+ ...historicalProtection
450
+ },
451
+ now
452
+ });
453
+ }
384
454
  return {
385
455
  projectRoot,
386
456
  manifest,
@@ -392,13 +462,18 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
392
462
  evidence,
393
463
  contexts,
394
464
  evidenceFreshness,
395
- readiness,
465
+ readiness: finalReadiness,
396
466
  sourceOfTruth,
397
467
  credential,
398
468
  archivedSeedIntegrity,
399
469
  retryArchivedSeedBaseline,
400
470
  expectedActiveSeed,
401
- migration: migration.status === 'committed' ? migration.journal : null
471
+ migration: migration.status === 'committed' ? migration.journal : null,
472
+ scope: resolvedScope,
473
+ ...(state.activationInputs ? { activationInputs: state.activationInputs } : {}),
474
+ ...(options.recoverPhase ? { recoverPhase: options.recoverPhase } : {}),
475
+ sensitivePathExclusions,
476
+ historicalLifecycleObligations
402
477
  };
403
478
  }
404
479
  async function assertPolicyIdentity(projectRoot, manifest) {
@@ -418,7 +493,10 @@ function approvalStatus(approval, now = new Date()) {
418
493
  return Date.parse(approval.expiresAt) > now.getTime() ? 'valid' : 'expired';
419
494
  }
420
495
  function approvalEvaluationForPhase(phase, inspection) {
421
- const plan = transitionPlanForPhase(phase, inspection.state, inspection.contexts[phase.id].transition, inspection.projectRoot, inspection.contexts[phase.id].publicationDestination);
496
+ const reviewed = inspection.contexts[phase.id].reviewedPlans?.filter((plan) => plan.phaseId === phase.id &&
497
+ plan.inputDigest === inspection.contexts[phase.id].inputDigest && plan.baselineDigest === inspection.contexts[phase.id].baselineSha)
498
+ .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
499
+ const plan = reviewed ? approvalRequestForSavedPlan(reviewed, phase, inspection.state) : transitionPlanForPhase(phase, inspection.state, inspection.contexts[phase.id].transition, inspection.projectRoot, inspection.contexts[phase.id].publicationDestination);
422
500
  return evaluateApprovalForTransitionPlan(plan, inspection.approvals);
423
501
  }
424
502
  function approvalEvaluationJson(evaluation) {
@@ -453,7 +531,7 @@ function summarizeMigration(inspection) {
453
531
  nextRecordedPhase: phases.find((phase) => phase.status !== 'complete')?.phaseId ?? null,
454
532
  currentProofRequired: true,
455
533
  remedy: needsRevalidation
456
- ? `Keep the committed v2 successor and preserved v1 history. Repair the named blockers or stale current proof, run ${checkCommand} for a fresh preview, then explicitly approve the exact remaining local plan before retrying. Prior migration approval does not authorize new work.`
534
+ ? `Keep the committed v3 successor and preserved v1/v2 history. Repair the named blockers or stale current proof, run ${checkCommand} for a fresh preview, then explicitly approve the exact remaining local plan before retrying. Prior migration approval does not authorize new work.`
457
535
  : null
458
536
  };
459
537
  }
@@ -477,21 +555,34 @@ function renderMigrationHuman(summary, presentation) {
477
555
  if (summary.revalidation.nextAction) {
478
556
  presentation.status('pending', 'Recorded next action', summary.revalidation.nextAction);
479
557
  }
480
- presentation.status('info', 'Migration scope', 'Journal progress is audit information, not current proof, governance completion, approval, or provider authority. Current readiness is evaluated separately from v2 evidence.');
558
+ presentation.status('info', 'Migration scope', 'Journal progress is audit information, not current proof, governance completion, approval, or provider authority. Current readiness is evaluated separately from v3 evidence.');
481
559
  if (summary.remedy)
482
560
  presentation.remedy(summary.remedy);
483
561
  }
484
562
  function statusJson(inspection, command) {
485
- const blockers = phaseIds.flatMap((phaseId) => inspection.readiness.phases[phaseId].blockers.map((message) => ({ phaseId, message })));
563
+ const blockers = verificationPhaseIds(inspection).flatMap((phaseId) => inspection.readiness.phases[phaseId].blockers.map((message) => ({ phaseId, message })));
486
564
  return {
487
- schemaVersion: 1,
565
+ schemaVersion: 2,
566
+ scope: inspection.scope,
488
567
  command: `governance ${command}`,
489
568
  projectRoot: inspection.projectRoot,
490
569
  readOnly: command !== 'apply-next',
491
570
  stateSource: inspection.stateSource,
571
+ activationDisabled: inspection.manifest.governance.profile === 'none',
572
+ progress: inspection.readiness.completion,
573
+ localComplete: inspection.readiness.completion.local,
574
+ activationComplete: inspection.readiness.completion.activation,
575
+ lifecycleComplete: inspection.readiness.completion.lifecycle,
576
+ nextActions: governanceNextActions(inspection),
577
+ blockerFingerprint: canonicalSha256({
578
+ scope: inspection.scope, stateHash: inspection.loadedState?.contentHash ?? null,
579
+ inputs: verificationPhaseIds(inspection).map((id) => [id, inspection.contexts[id].inputDigest]), blockers
580
+ }),
492
581
  activationIdentity: inspection.state.identity,
493
582
  migration: inspection.migration,
494
583
  migrationSummary: summarizeMigration(inspection),
584
+ taskProjectionAudit: inspection.state.taskProjection ?? null,
585
+ historicalLifecycleObligations: inspection.historicalLifecycleObligations,
495
586
  executionAnchor: inspection.state.repository.id === 'unbound' ? null : inspection.state.repository.id,
496
587
  remoteBinding: inspection.state.remoteBinding ?? null,
497
588
  graphHash: inspection.graph.hash,
@@ -508,6 +599,10 @@ function statusJson(inspection, command) {
508
599
  label: phase.label,
509
600
  state: inspection.readiness.phases[phase.id].state,
510
601
  storedState: inspection.state.phases[phase.id].state,
602
+ scope: phaseScope(phase.id),
603
+ plannable: inspection.readiness.phases[phase.id].plannable ?? false,
604
+ externalOperation: inspection.state.phases[phase.id].operation ?? null,
605
+ executionPlanDigest: inspection.state.phases[phase.id].executionPlanDigest ?? null,
511
606
  storedBlockers: inspection.state.phases[phase.id].blockers,
512
607
  retryable: phaseCapabilities[phase.id].retry === 'explicit-local' &&
513
608
  ['failed', 'blocked'].includes(inspection.state.phases[phase.id].state),
@@ -523,6 +618,7 @@ function statusJson(inspection, command) {
523
618
  allowedMutations: phase.allowedMutations
524
619
  })),
525
620
  nextReadyPhase: inspection.readiness.nextReadyPhase,
621
+ nextPlannablePhase: inspection.readiness.nextPlannablePhase,
526
622
  blockers,
527
623
  approvals: inspection.approvals.map((approval) => ({
528
624
  id: approval.id,
@@ -535,11 +631,51 @@ function statusJson(inspection, command) {
535
631
  evidenceFreshness: phaseIds.map((phaseId) => inspection.evidenceFreshness[phaseId])
536
632
  };
537
633
  }
634
+ function governanceAction(inspection, subcommand, scope, extras = [], approvalRequired = false) {
635
+ const args = ['governance', subcommand, '--project', inspection.projectRoot, '--scope', scope, ...extras, '--json'];
636
+ parseArgs(args);
637
+ return {
638
+ id: `governance-${subcommand}-${scope}`, label: `${subcommand} ${scope} governance`,
639
+ command: { executable: 'liftoff', args }, cwd: inspection.projectRoot, scope, approvalRequired
640
+ };
641
+ }
642
+ function governanceNextActions(inspection, preview) {
643
+ if (inspection.manifest.governance.profile === 'none' && inspection.scope === 'activation')
644
+ return [];
645
+ if (inspection.readiness.completion[inspection.scope]) {
646
+ if (inspection.scope === 'local')
647
+ return [governanceAction(inspection, 'plan', 'activation')];
648
+ if (inspection.scope === 'activation' && !inspection.readiness.completion.lifecycle) {
649
+ return [governanceAction(inspection, 'status', 'lifecycle')];
650
+ }
651
+ return [];
652
+ }
653
+ if (preview) {
654
+ const planArgs = ['--plan', preview.fingerprint];
655
+ if (preview.plan.approval.evaluation.approvalRequired) {
656
+ return [governanceAction(inspection, 'approve', preview.plan.scope, planArgs, true)];
657
+ }
658
+ const subcommand = preview.plan.recovery ? 'recover' : 'apply-next';
659
+ return [governanceAction(inspection, subcommand, preview.plan.scope, [...(inspection.stateSource === 'not-started' ? [] : planArgs), '--execute'])];
660
+ }
661
+ const interrupted = phaseIds.find((id) => phaseScope(id) === inspection.scope && inspection.readiness.phases[id].recoveryRequired);
662
+ if (interrupted)
663
+ return [governanceAction(inspection, 'plan', inspection.scope, ['--recover-phase', interrupted])];
664
+ if (inspection.scope !== 'local' && !inspection.readiness.completion.local) {
665
+ return [governanceAction(inspection, 'plan', 'local')];
666
+ }
667
+ return [governanceAction(inspection, 'plan', inspection.scope)];
668
+ }
669
+ function verificationPhaseIds(inspection) {
670
+ return phaseIds.filter((id) => phaseScope(id) === inspection.scope ||
671
+ inspection.scope === 'activation' && phaseScope(id) === 'local');
672
+ }
538
673
  function renderStatusHuman(inspection, command) {
539
674
  const presentation = inspectionPresentation(inspection);
540
675
  presentation.commandIdentity(`governance ${command}`, 'Deterministic activation status');
541
676
  presentation.definitions('Activation identity', [
542
677
  { label: 'Project', value: inspection.projectRoot },
678
+ { label: 'Scope', value: inspection.scope },
543
679
  { label: 'State', value: inspection.stateSource },
544
680
  { label: 'Policy', value: inspection.state.identity.policyVersion },
545
681
  { label: 'Contract', value: String(inspection.state.identity.activationContractVersion) },
@@ -596,17 +732,23 @@ function inspectionPresentation(inspection) {
596
732
  return inspection.presentation;
597
733
  }
598
734
  function planJson(inspection) {
599
- const ready = inspection.graph.graph.phases
735
+ const scopedPhases = inspection.scope === 'local'
736
+ ? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'local')
737
+ : inspection.scope === 'lifecycle'
738
+ ? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'lifecycle')
739
+ : inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) !== 'lifecycle');
740
+ const ready = scopedPhases
600
741
  .filter((phase) => inspection.readiness.phases[phase.id].state === 'ready')
601
742
  .map((phase) => planPhase(phase, inspection));
602
- const blocked = inspection.graph.graph.phases
743
+ const blocked = scopedPhases
603
744
  .filter((phase) => inspection.readiness.phases[phase.id].state === 'blocked')
604
745
  .map((phase) => ({
605
746
  ...planPhase(phase, inspection),
606
747
  blockers: inspection.readiness.phases[phase.id].blockers
607
748
  }));
608
749
  return {
609
- schemaVersion: 1,
750
+ schemaVersion: 2,
751
+ scope: inspection.scope,
610
752
  command: 'governance plan',
611
753
  projectRoot: inspection.projectRoot,
612
754
  readOnly: true,
@@ -616,6 +758,10 @@ function planJson(inspection) {
616
758
  activeChange: inspection.state.activeChange,
617
759
  activeSourceOfTruth: inspection.sourceOfTruth,
618
760
  credential: inspection.credential,
761
+ progress: inspection.readiness.completion,
762
+ nextReadyPhase: inspection.readiness.nextReadyPhase,
763
+ nextPlannablePhase: inspection.readiness.nextPlannablePhase,
764
+ nextActions: governanceNextActions(inspection),
619
765
  readyPhases: ready,
620
766
  blockedPhases: blocked
621
767
  };
@@ -651,10 +797,15 @@ function costEnvelope(phase) {
651
797
  };
652
798
  }
653
799
  function renderPlanHuman(inspection, presentation) {
654
- presentation.commandIdentity('governance plan', 'Read-only activation transition plan');
655
- const ready = inspection.graph.graph.phases.filter((phase) => inspection.readiness.phases[phase.id].state === 'ready');
656
- const blocked = inspection.graph.graph.phases.filter((phase) => inspection.readiness.phases[phase.id].state === 'blocked');
657
- presentation.status('info', 'Read-only', 'No files, remotes, credentials, or cloud resources are changed.');
800
+ presentation.commandIdentity('governance plan', `Project-read-only ${inspection.scope} transition plan`);
801
+ const scopedPhases = inspection.scope === 'local'
802
+ ? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'local')
803
+ : inspection.scope === 'lifecycle'
804
+ ? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'lifecycle')
805
+ : inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) !== 'lifecycle');
806
+ const ready = scopedPhases.filter((phase) => inspection.readiness.phases[phase.id].state === 'ready');
807
+ const blocked = scopedPhases.filter((phase) => inspection.readiness.phases[phase.id].state === 'blocked');
808
+ presentation.status('info', 'Project-read-only', 'No project or provider data is changed; any external preview receipt is disclosed separately.');
658
809
  presentation.table('Ready phases', ['Phase', 'Evidence', 'Question', 'Approval', 'Mutations'], ready.map((phase) => {
659
810
  const approval = approvalEvaluationForPhase(phase, inspection);
660
811
  return [
@@ -703,7 +854,7 @@ function validateStateEvidence(inspection) {
703
854
  if (inspection.stateSource === 'not-started') {
704
855
  return { id: 'state-evidence', status: 'passed', issues };
705
856
  }
706
- for (const phaseId of phaseIds) {
857
+ for (const phaseId of verificationPhaseIds(inspection)) {
707
858
  const stored = inspection.state.phases[phaseId];
708
859
  if (!terminalEvidenceStates.has(stored.state)) {
709
860
  continue;
@@ -718,7 +869,7 @@ function validateStateEvidence(inspection) {
718
869
  }
719
870
  function validatePhaseTerminalStates(inspection) {
720
871
  const issues = [];
721
- for (const phase of inspection.graph.graph.phases) {
872
+ for (const phase of inspection.graph.graph.phases.filter((phase) => verificationPhaseIds(inspection).includes(phase.id))) {
722
873
  const allowed = phase.terminalStates;
723
874
  const stored = inspection.state.phases[phase.id].state;
724
875
  const selected = selectLatestPhaseEvidence(inspection.evidence.filter((record) => record.header.phaseId === phase.id), inspection.contexts[phase.id]).selected;
@@ -743,7 +894,7 @@ function validatePhaseTerminalStates(inspection) {
743
894
  };
744
895
  }
745
896
  function validateEvidenceFreshnessCheck(inspection) {
746
- const issues = phaseIds.flatMap((phaseId) => {
897
+ const issues = verificationPhaseIds(inspection).flatMap((phaseId) => {
747
898
  const freshness = inspection.evidenceFreshness[phaseId];
748
899
  return freshness.status === 'fresh'
749
900
  ? []
@@ -775,44 +926,14 @@ function validateReadinessCheck(inspection) {
775
926
  issues: [inspection.readiness.identityBlocker ?? 'Activation identity is incompatible.']
776
927
  };
777
928
  }
778
- async function activeTaskProjectionCheck(inspection) {
779
- if (inspection.manifest.project.specWorkflow === 'spec-kit') {
780
- const bytes = await readProjectFile(inspection.projectRoot, ['specs', '000-liftoff-bootstrap', 'tasks.md']);
781
- if (!bytes)
782
- return { id: 'task-projection', status: 'failed', issues: ['Spec Kit seed-adoption-required: real bootstrap tasks are missing.'] };
783
- const expected = inspection.readiness.phases['seed-verified'].state === 'verified';
784
- const tasks = [...bytes.toString('utf8').matchAll(/^\s*- \[([ xX])\] (B00[1-6]) /gm)];
785
- const issues = tasks.filter((match) => (match[1].toLowerCase() === 'x') !== expected)
786
- .map((match) => `Spec Kit task ${match[2]} differs from the authoritative local baseline projection. Verification did not edit it.`);
787
- return { id: 'task-projection', status: issues.length ? 'failed' : 'passed', issues };
788
- }
789
- const activeChange = inspection.state.activeChange;
790
- if (!activeChange || activeChange.kind !== 'openspec') {
791
- return { id: 'task-projection', status: 'skipped', issues: [] };
792
- }
793
- const pathParts = validateArtifactPathParts(['openspec', 'changes', activeChange.id, 'tasks.md'], 'Active OpenSpec task path');
794
- const bytes = await readProjectFile(inspection.projectRoot, pathParts);
795
- if (bytes === undefined) {
796
- return { id: 'task-projection', status: 'skipped', issues: [] };
797
- }
798
- const markdown = bytes.toString('utf8');
799
- const mappings = extractPhaseTaskMappings(markdown);
800
- if (mappings.length === 0) {
801
- return { id: 'task-projection', status: 'skipped', issues: [] };
802
- }
803
- const projection = projectOpenSpecTaskCheckboxes(markdown, mappings, inspection.readiness.phases);
804
- const issues = projection.changes.map((change) => `Task ${change.taskId} for ${change.phaseId} is ${change.fromChecked ? 'checked' : 'unchecked'} but authoritative phase state is ${change.state}.`);
805
- return { id: 'task-projection', status: issues.length === 0 ? 'passed' : 'failed', issues };
806
- }
807
929
  function extractPhaseTaskMappings(markdown) {
808
930
  const phaseIdSet = new Set(phaseIds);
809
931
  const mappings = [];
810
932
  const pattern = /^\s*[-*]\s+\[[ xX]\]\s+(\S+).*<!--\s*liftoff-phase:\s*([a-z0-9-]+)\s*-->/u;
811
933
  for (const line of markdown.split(/\r?\n/u)) {
812
934
  const match = line.match(pattern);
813
- if (!match) {
935
+ if (!match)
814
936
  continue;
815
- }
816
937
  const phaseId = match[2];
817
938
  if (!phaseIdSet.has(phaseId)) {
818
939
  throw new Error(`Task projection references unknown phase ${phaseId}.`);
@@ -821,6 +942,52 @@ function extractPhaseTaskMappings(markdown) {
821
942
  }
822
943
  return mappings;
823
944
  }
945
+ async function activeTaskProjectionCheck(inspection) {
946
+ const issues = [];
947
+ let inspected = false;
948
+ if (inspection.scope !== 'lifecycle' && inspection.manifest.project.specWorkflow === 'spec-kit') {
949
+ inspected = true;
950
+ const bytes = await readProjectFile(inspection.projectRoot, ['specs', '000-liftoff-bootstrap', 'tasks.md']);
951
+ if (!bytes)
952
+ return { id: 'task-projection', status: 'failed', issues: ['Spec Kit seed-adoption-required: real bootstrap tasks are missing.'] };
953
+ const expected = inspection.readiness.phases['seed-verified'].state === 'verified';
954
+ const tasks = [...bytes.toString('utf8').matchAll(/^\s*- \[([ xX])\] (B00[1-6]) /gm)];
955
+ issues.push(...tasks.filter((match) => (match[1].toLowerCase() === 'x') !== expected)
956
+ .map((match) => `Spec Kit task ${match[2]} differs from the authoritative local baseline projection. Verification did not edit it.`));
957
+ }
958
+ const activeChange = inspection.state.activeChange;
959
+ if (activeChange && activeChange.kind === 'openspec') {
960
+ const pathParts = validateArtifactPathParts(['openspec', 'changes', activeChange.id, 'tasks.md'], 'Active OpenSpec task path');
961
+ const bytes = await readProjectFile(inspection.projectRoot, pathParts);
962
+ if (bytes !== undefined) {
963
+ inspected = true;
964
+ const markdown = bytes.toString('utf8');
965
+ const mappings = extractPhaseTaskMappings(markdown);
966
+ if (mappings.length > 0) {
967
+ const projection = projectOpenSpecTaskCheckboxes(markdown, mappings, inspection.readiness.phases);
968
+ issues.push(...projection.changes.filter((change) => verificationPhaseIds(inspection).includes(change.phaseId)).map((change) => `Task ${change.taskId} for ${change.phaseId} is ${change.fromChecked ? 'checked' : 'unchecked'} but authoritative phase state is ${change.state}.`));
969
+ }
970
+ }
971
+ }
972
+ const source = inspection.sourceOfTruth;
973
+ if (inspection.scope !== 'local' && source.status === 'selected') {
974
+ inspected = true;
975
+ if (!source.selected.metadata) {
976
+ issues.push('The selected current governance source has no validated metadata.');
977
+ }
978
+ else {
979
+ const pathParts = validateArtifactPathParts([...source.selected.pathParts, 'tasks.md'], 'Current governance task path');
980
+ const bytes = await readProjectFile(inspection.projectRoot, pathParts);
981
+ if (!bytes)
982
+ issues.push(`Current governance tasks ${pathParts.join('/')} are missing.`);
983
+ else {
984
+ const projection = projectGovernanceChangeTasks(bytes.toString('utf8'), source.selected.metadata, inspection.readiness.phases);
985
+ issues.push(...projection.changes.filter((change) => verificationPhaseIds(inspection).includes(change.phaseId)).map((change) => `Task ${change.taskId} for ${change.phaseId} is ${change.fromChecked ? 'checked' : 'unchecked'} but current authoritative phase state is ${change.state}.`));
986
+ }
987
+ }
988
+ }
989
+ return { id: 'task-projection', status: issues.length ? 'failed' : inspected ? 'passed' : 'skipped', issues };
990
+ }
824
991
  function activeChangeIdentityCheck(inspection) {
825
992
  const activeChange = inspection.state.activeChange;
826
993
  if (!activeChange) {
@@ -838,7 +1005,7 @@ function activeChangeIdentityCheck(inspection) {
838
1005
  return { id: 'active-change-identity', status: issues.length === 0 ? 'passed' : 'failed', issues };
839
1006
  }
840
1007
  function liveReadbackCheck(inspection) {
841
- const issues = inspection.graph.graph.phases.flatMap((phase) => {
1008
+ const issues = inspection.graph.graph.phases.filter((phase) => verificationPhaseIds(inspection).includes(phase.id)).flatMap((phase) => {
842
1009
  if (phase.evidence.liveReadbackProviders.length === 0) {
843
1010
  return [];
844
1011
  }
@@ -850,6 +1017,8 @@ function liveReadbackCheck(inspection) {
850
1017
  return { id: 'live-readback', status: issues.length === 0 ? 'passed' : 'failed', issues };
851
1018
  }
852
1019
  function credentialPolicyCheck(inspection) {
1020
+ if (inspection.scope !== 'activation')
1021
+ return { id: 'credential-policy', status: 'skipped', issues: [] };
853
1022
  if (!inspection.credential.applicable) {
854
1023
  return { id: 'credential-policy', status: 'skipped', issues: [] };
855
1024
  }
@@ -865,6 +1034,12 @@ function credentialPolicyCheck(inspection) {
865
1034
  }
866
1035
  function activeSourceOfTruthCheck(inspection) {
867
1036
  const source = inspection.sourceOfTruth;
1037
+ if (inspection.scope === 'local') {
1038
+ if (source.status === 'seed-blocked' && !inspection.expectedActiveSeed) {
1039
+ return { id: 'active-source-of-truth', status: 'failed', issues: source.blockers };
1040
+ }
1041
+ return { id: 'active-source-of-truth', status: 'skipped', issues: [] };
1042
+ }
868
1043
  if (inspection.expectedActiveSeed) {
869
1044
  return {
870
1045
  id: 'active-source-of-truth',
@@ -948,19 +1123,15 @@ function setupCompletion(inspection) {
948
1123
  summary: `Verification is consistent, but setup has not started. Next ready phase: ${inspection.readiness.nextReadyPhase ?? 'none'}.`
949
1124
  };
950
1125
  }
951
- const terminal = inspection.readiness.phases['bootstrap-state-disposed'].state;
952
- const everyPhaseComplete = inspection.graph.graph.phases.every((phase) => {
953
- const phaseId = phase.id;
954
- const state = inspection.readiness.phases[phaseId].state;
955
- return state !== 'identity-incompatible' &&
956
- successfulSetupStates.has(state) &&
957
- phase.terminalStates.includes(state);
958
- });
959
- if (everyPhaseComplete && (terminal === 'disposed' || terminal === 'inapplicable')) {
1126
+ if (inspection.readiness.completion[inspection.scope]) {
960
1127
  return {
961
1128
  status: 'complete',
962
1129
  complete: true,
963
- summary: 'Verification is consistent and deterministic setup is complete.'
1130
+ summary: inspection.scope === 'local'
1131
+ ? 'Local setup is complete. Repository publication, cloud deployment, and governance activation are separate approved work.'
1132
+ : inspection.scope === 'activation'
1133
+ ? 'Governance activation is complete. Delayed retained-state disposal is tracked separately.'
1134
+ : 'Lifecycle work is complete.'
964
1135
  };
965
1136
  }
966
1137
  return {
@@ -982,7 +1153,8 @@ async function verifyJson(inspection) {
982
1153
  ? completion.status
983
1154
  : 'in-progress';
984
1155
  return {
985
- schemaVersion: 1,
1156
+ schemaVersion: 2,
1157
+ scope: inspection.scope,
986
1158
  command: 'governance verify',
987
1159
  projectRoot: inspection.projectRoot,
988
1160
  readOnly: true,
@@ -1000,7 +1172,11 @@ async function verifyJson(inspection) {
1000
1172
  activeChange: inspection.state.activeChange,
1001
1173
  activeSourceOfTruth: inspection.sourceOfTruth,
1002
1174
  nextReadyPhase: inspection.readiness.nextReadyPhase,
1003
- checks
1175
+ progress: inspection.readiness.completion,
1176
+ nextActions: governanceNextActions(inspection),
1177
+ checks,
1178
+ historicalLifecycleObligations: inspection.historicalLifecycleObligations,
1179
+ taskProjectionAudit: inspection.state.taskProjection ?? null
1004
1180
  };
1005
1181
  }
1006
1182
  async function renderVerifyHuman(inspection, presentation) {
@@ -1014,13 +1190,15 @@ async function renderVerifyHuman(inspection, presentation) {
1014
1190
  }
1015
1191
  return result.ok === true ? 0 : 1;
1016
1192
  }
1017
- function renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode) {
1193
+ function renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode, scope = 'activation') {
1018
1194
  const result = {
1019
- schemaVersion: 1,
1195
+ schemaVersion: 2,
1196
+ scope,
1020
1197
  command: `governance ${subcommand}`,
1021
1198
  projectRoot,
1022
1199
  readOnly: true,
1023
1200
  ok: false,
1201
+ nextActions: [],
1024
1202
  ...(subcommand === 'verify'
1025
1203
  ? {
1026
1204
  consistent: false,
@@ -1055,6 +1233,11 @@ function transitionInspection(inspection) {
1055
1233
  manifest: inspection.manifest,
1056
1234
  graph: inspection.graph.graph,
1057
1235
  graphHash: inspection.graph.hash,
1236
+ scope: inspection.scope,
1237
+ ...(inspection.activationInputs ? { activationInputs: inspection.activationInputs } : {}),
1238
+ ...(inspection.recoverPhase ? { recoverPhase: inspection.recoverPhase } : {}),
1239
+ sensitivePathExclusions: inspection.sensitivePathExclusions,
1240
+ historicalLifecycleObligations: inspection.historicalLifecycleObligations,
1058
1241
  ...(inspection.loadedState ? { loadedState: inspection.loadedState } : {}),
1059
1242
  state: inspection.state,
1060
1243
  approvals: inspection.approvals,
@@ -1065,7 +1248,7 @@ function transitionInspection(inspection) {
1065
1248
  };
1066
1249
  }
1067
1250
  export async function inspectGovernanceTransition(projectRoot, options = {}) {
1068
- return transitionInspection(await inspectGovernance(projectRoot, options.runner, options.now));
1251
+ return transitionInspection(await inspectGovernance(projectRoot, options.runner, options.now, options));
1069
1252
  }
1070
1253
  function renderApplyNextHuman(result, presentation) {
1071
1254
  presentation.commandIdentity('governance apply-next', 'Controlled activation transition');
@@ -1103,6 +1286,33 @@ function renderApplyNextHuman(result, presentation) {
1103
1286
  presentation.status('info', 'Preview only', 'No writes occurred; rerun with --execute to execute at most one phase.');
1104
1287
  }
1105
1288
  }
1289
+ function governanceScopeFlag(parsed) {
1290
+ const value = readStringFlag(parsed.flags, 'scope') ?? 'activation';
1291
+ if (value !== 'local' && value !== 'activation' && value !== 'lifecycle')
1292
+ throw new Error('Governance scope must be local, activation, or lifecycle.');
1293
+ return value;
1294
+ }
1295
+ async function publicActivationInputs(filePath) {
1296
+ const handle = await open(filePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
1297
+ try {
1298
+ const stat = await handle.stat();
1299
+ if (!stat.isFile() || stat.nlink !== 1 || stat.size > 64 * 1024) {
1300
+ throw new Error('Activation inputs must be a singly linked regular public JSON file no larger than 64 KiB.');
1301
+ }
1302
+ const text = await handle.readFile('utf8');
1303
+ let value;
1304
+ try {
1305
+ value = JSON.parse(text);
1306
+ }
1307
+ catch {
1308
+ throw new Error('Activation inputs are not valid JSON; credential or state content must not be supplied here.');
1309
+ }
1310
+ return validateActivationConfiguration(value);
1311
+ }
1312
+ finally {
1313
+ await handle.close();
1314
+ }
1315
+ }
1106
1316
  export async function governanceCommand(parsed, context) {
1107
1317
  if (parsed.subcommand === 'assess') {
1108
1318
  return governanceAssessmentCommand(parsed, context);
@@ -1111,16 +1321,20 @@ export async function governanceCommand(parsed, context) {
1111
1321
  const jsonMode = readBooleanFlag(parsed.flags, 'json') ?? false;
1112
1322
  const subcommand = parseGovernanceSubcommand(parsed);
1113
1323
  if (!subcommand) {
1114
- presentation.error('Missing governance subcommand.', 'Run `liftoff governance --help` and choose one of: status, plan, apply-next, resume, verify.');
1324
+ presentation.error('Missing governance subcommand.', 'Run `liftoff governance --help` to choose a supported planning, approval, execution, or inspection command.');
1115
1325
  return 1;
1116
1326
  }
1327
+ const rawScope = readStringFlag(parsed.flags, 'scope');
1328
+ if (rawScope && rawScope !== 'local' && rawScope !== 'activation' && rawScope !== 'lifecycle') {
1329
+ throw new Error('Governance scope must be local, activation, or lifecycle.');
1330
+ }
1331
+ const scope = rawScope;
1117
1332
  let projectRoot;
1118
1333
  try {
1119
1334
  projectRoot = await resolveGovernanceProjectRoot(parsed, context);
1120
1335
  }
1121
1336
  catch (error) {
1122
- presentation.error(errorMessage(error), 'Run `liftoff governance --help` to review accepted project arguments.');
1123
- return 1;
1337
+ throw new Error(`${errorMessage(error)} Run liftoff governance --help to review accepted project arguments.`);
1124
1338
  }
1125
1339
  if (!projectRoot) {
1126
1340
  const start = parsed.positional[0] ?? readStringFlag(parsed.flags, 'project') ?? context.cwd;
@@ -1128,13 +1342,34 @@ export async function governanceCommand(parsed, context) {
1128
1342
  presentation.error(failure.message, failure.remedy);
1129
1343
  return 1;
1130
1344
  }
1345
+ const inputsFile = readStringFlag(parsed.flags, 'inputs');
1346
+ const activationInputs = inputsFile ? await publicActivationInputs(path.resolve(context.cwd, inputsFile)) : undefined;
1347
+ const fingerprint = readStringFlag(parsed.flags, 'plan');
1348
+ const reviewed = fingerprint ? await loadGovernancePreview(projectRoot, fingerprint) : undefined;
1349
+ if (reviewed && scope && reviewed.plan.scope !== scope)
1350
+ throw new Error('The selected scope does not match the reviewed plan.');
1351
+ if (subcommand === 'recover' && !reviewed?.plan.recovery)
1352
+ throw new Error('Recovery requires a preview explicitly created with governance plan --recover-phase.');
1353
+ if (subcommand === 'apply-next' && reviewed?.plan.recovery)
1354
+ throw new Error('A recovery preview can be executed only with governance recover.');
1355
+ if (subcommand === 'credential-enroll' && reviewed?.plan.phaseId !== 'credential-ready') {
1356
+ throw new Error('Credential enrollment requires a credential-ready preview, not another activation or repair plan.');
1357
+ }
1358
+ const requestedRecovery = readStringFlag(parsed.flags, 'recover-phase');
1359
+ const recoverPhase = reviewed?.plan.recovery ? reviewed.plan.phaseId : phaseIds.find((id) => id === requestedRecovery);
1360
+ const options = {
1361
+ ...(scope ? { scope } : {}),
1362
+ command: subcommand,
1363
+ ...(activationInputs ?? reviewed?.plan.configuration ? { activationInputs: activationInputs ?? reviewed?.plan.configuration } : {}),
1364
+ ...(recoverPhase ? { recoverPhase } : {})
1365
+ };
1131
1366
  let inspection;
1132
1367
  try {
1133
- inspection = attachPresentation(await inspectGovernance(projectRoot, context.runner), presentation);
1368
+ inspection = attachPresentation(await inspectGovernance(projectRoot, context.runner, new Date(), options), presentation);
1134
1369
  }
1135
1370
  catch (error) {
1136
1371
  if (subcommand === 'verify') {
1137
- return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode);
1372
+ return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode, scope);
1138
1373
  }
1139
1374
  throw error;
1140
1375
  }
@@ -1148,14 +1383,62 @@ export async function governanceCommand(parsed, context) {
1148
1383
  return 0;
1149
1384
  }
1150
1385
  if (subcommand === 'plan') {
1386
+ let saved;
1387
+ try {
1388
+ saved = await saveGovernancePreview(transitionInspection(inspection), { runner: context.runner });
1389
+ }
1390
+ catch (error) {
1391
+ if (jsonMode)
1392
+ json(presentation, {
1393
+ ...planJson(inspection), ready: false, reason: 'planning-blocked',
1394
+ blockers: [errorMessage(error)], preview: null
1395
+ });
1396
+ else
1397
+ presentation.error(errorMessage(error), 'Supply the named supported inputs or resolve the prerequisite, then request a fresh plan.');
1398
+ return 1;
1399
+ }
1151
1400
  if (jsonMode) {
1152
- json(presentation, planJson(inspection));
1401
+ json(presentation, {
1402
+ ...planJson(inspection),
1403
+ preview: saved ? { fingerprint: saved.preview.fingerprint, path: saved.path, expiresAt: saved.preview.plan.expiresAt } : null,
1404
+ plan: saved?.preview.plan ?? null,
1405
+ projectWrites: false,
1406
+ providerWrites: false,
1407
+ noWrites: true,
1408
+ externalPreviewWritten: saved !== null,
1409
+ nextActions: governanceNextActions(inspection, saved?.preview)
1410
+ });
1153
1411
  }
1154
1412
  else {
1155
1413
  renderPlanHuman(inspection, presentation);
1414
+ if (saved) {
1415
+ presentation.status('info', 'External preview', `${saved.path}; fingerprint ${saved.preview.fingerprint}. This is not approval.`);
1416
+ const next = governanceNextActions(inspection, saved.preview)[0];
1417
+ if (next)
1418
+ presentation.remedy([next.command.executable, ...next.command.args.map((arg) => /\s/u.test(arg) ? JSON.stringify(arg) : arg)].join(' '));
1419
+ }
1156
1420
  }
1157
1421
  return 0;
1158
1422
  }
1423
+ if (subcommand === 'approve') {
1424
+ const approved = await approveGovernancePreview({
1425
+ projectRoot, fingerprint: fingerprint,
1426
+ inspect: async () => transitionInspection(await inspectGovernance(projectRoot, context.runner, new Date(), options)),
1427
+ runner: context.runner
1428
+ });
1429
+ const refreshed = attachPresentation(await inspectGovernance(projectRoot, context.runner, new Date(), options), presentation);
1430
+ const result = {
1431
+ schemaVersion: 2, command: 'governance approve', projectRoot, scope,
1432
+ approved: true, executed: false, envelopeId: approved.envelope.id,
1433
+ envelopeHash: canonicalApprovalEnvelopeHash(approved.envelope), expiresAt: approved.envelope.expiresAt,
1434
+ nextActions: governanceNextActions(refreshed, { fingerprint: fingerprint, plan: approved.plan })
1435
+ };
1436
+ if (jsonMode)
1437
+ json(presentation, result);
1438
+ else
1439
+ presentation.status('success', 'Plan approved', `Approval ${approved.envelope.id} was saved without executing its operations.`);
1440
+ return 0;
1441
+ }
1159
1442
  if (subcommand === 'resume') {
1160
1443
  const result = {
1161
1444
  ...statusJson(inspection, 'resume'),
@@ -1182,16 +1465,21 @@ export async function governanceCommand(parsed, context) {
1182
1465
  return await renderVerifyHuman(inspection, presentation);
1183
1466
  }
1184
1467
  catch (error) {
1185
- return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode);
1468
+ return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode, scope);
1186
1469
  }
1187
1470
  }
1188
- const execute = readBooleanFlag(parsed.flags, 'execute') ?? false;
1471
+ const execute = subcommand === 'credential-enroll' || (readBooleanFlag(parsed.flags, 'execute') ?? false);
1189
1472
  const transitionInput = transitionInspection(inspection);
1190
1473
  const result = execute
1191
1474
  ? await executeApplyNext({
1192
1475
  inspection: transitionInput,
1193
1476
  runner: context.runner,
1194
- reinspect: async () => transitionInspection(attachPresentation(await inspectGovernance(projectRoot, context.runner), presentation))
1477
+ reviewedPlan: reviewed?.plan,
1478
+ recovery: subcommand === 'recover',
1479
+ ...(subcommand === 'credential-enroll' ? {
1480
+ credentialEnrollment: { protectedStdin: readBooleanFlag(parsed.flags, 'protected-stdin') ?? false }
1481
+ } : {}),
1482
+ reinspect: async () => transitionInspection(attachPresentation(await inspectGovernance(projectRoot, context.runner, new Date(), options), presentation))
1195
1483
  })
1196
1484
  : await previewApplyNext({
1197
1485
  inspection: transitionInput,
@@ -1199,11 +1487,18 @@ export async function governanceCommand(parsed, context) {
1199
1487
  execute: false
1200
1488
  });
1201
1489
  if (jsonMode) {
1202
- json(presentation, result);
1490
+ const refreshed = execute
1491
+ ? await inspectGovernance(projectRoot, context.runner, new Date(), { ...options, recoverPhase: undefined })
1492
+ : inspection;
1493
+ json(presentation, {
1494
+ ...result, command: `governance ${subcommand}`,
1495
+ progress: refreshed.readiness.completion,
1496
+ nextActions: governanceNextActions(refreshed)
1497
+ });
1203
1498
  }
1204
1499
  else {
1205
1500
  renderApplyNextHuman(result, presentation);
1206
1501
  }
1207
- return result.applied || result.reason === 'execute-required' ? 0 : 1;
1502
+ return result.applied || ['execute-required', 'external-operation-pending'].includes(result.reason) ? 0 : 1;
1208
1503
  }
1209
1504
  //# sourceMappingURL=commands.js.map