@msn-control/liftoff 0.11.3 → 0.12.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 (332) hide show
  1. package/DEVELOPER.md +26 -35
  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 +44 -0
  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/state/azure-blob.d.ts +43 -0
  23. package/dist/adapters/state/azure-blob.js +335 -0
  24. package/dist/adapters/state/azure-blob.js.map +1 -0
  25. package/dist/adapters/state/darwin-capabilities.d.ts +33 -0
  26. package/dist/adapters/state/darwin-capabilities.js +146 -0
  27. package/dist/adapters/state/darwin-capabilities.js.map +1 -0
  28. package/dist/adapters/state/darwin-native-host.d.ts +32 -0
  29. package/dist/adapters/state/darwin-native-host.js +294 -0
  30. package/dist/adapters/state/darwin-native-host.js.map +1 -0
  31. package/dist/adapters/state/darwin-profile.d.ts +6 -0
  32. package/dist/adapters/state/darwin-profile.js +56 -0
  33. package/dist/adapters/state/darwin-profile.js.map +1 -0
  34. package/dist/adapters/state/darwin-system-program.d.ts +1 -0
  35. package/dist/adapters/state/darwin-system-program.js +94 -0
  36. package/dist/adapters/state/darwin-system-program.js.map +1 -0
  37. package/dist/adapters/state/local-qualification.d.ts +16 -0
  38. package/dist/adapters/state/local-qualification.js +299 -0
  39. package/dist/adapters/state/local-qualification.js.map +1 -0
  40. package/dist/adapters/state/local.d.ts +25 -0
  41. package/dist/adapters/state/local.js +145 -0
  42. package/dist/adapters/state/local.js.map +1 -0
  43. package/dist/adapters/state/native-command.d.ts +21 -0
  44. package/dist/adapters/state/native-command.js +82 -0
  45. package/dist/adapters/state/native-command.js.map +1 -0
  46. package/dist/adapters/state/native-files.d.ts +2 -0
  47. package/dist/adapters/state/native-files.js +47 -0
  48. package/dist/adapters/state/native-files.js.map +1 -0
  49. package/dist/adapters/state/native-system.d.ts +38 -0
  50. package/dist/adapters/state/native-system.js +86 -0
  51. package/dist/adapters/state/native-system.js.map +1 -0
  52. package/dist/adapters/state/opentofu.d.ts +35 -0
  53. package/dist/adapters/state/opentofu.js +350 -0
  54. package/dist/adapters/state/opentofu.js.map +1 -0
  55. package/dist/adapters/state/owned-process.d.ts +43 -0
  56. package/dist/adapters/state/owned-process.js +252 -0
  57. package/dist/adapters/state/owned-process.js.map +1 -0
  58. package/dist/adapters/state/posix-lock-program.d.ts +1 -0
  59. package/dist/adapters/state/posix-lock-program.js +185 -0
  60. package/dist/adapters/state/posix-lock-program.js.map +1 -0
  61. package/dist/adapters/state/posix-native-lock.d.ts +15 -0
  62. package/dist/adapters/state/posix-native-lock.js +145 -0
  63. package/dist/adapters/state/posix-native-lock.js.map +1 -0
  64. package/dist/adapters/state/protected-workspace.d.ts +57 -0
  65. package/dist/adapters/state/protected-workspace.js +412 -0
  66. package/dist/adapters/state/protected-workspace.js.map +1 -0
  67. package/dist/application/context.d.ts +3 -0
  68. package/dist/application/diagnose/doctor.d.ts +5 -0
  69. package/dist/application/diagnose/doctor.js +21 -5
  70. package/dist/application/diagnose/doctor.js.map +1 -1
  71. package/dist/application/diagnose/generated-project.js +2 -7
  72. package/dist/application/diagnose/generated-project.js.map +1 -1
  73. package/dist/application/initialize/fixture.js +8 -1
  74. package/dist/application/initialize/fixture.js.map +1 -1
  75. package/dist/application/initialize/use-case.js +70 -36
  76. package/dist/application/initialize/use-case.js.map +1 -1
  77. package/dist/application/project/manifest.js +5 -4
  78. package/dist/application/project/manifest.js.map +1 -1
  79. package/dist/application/state-migration/execution.d.ts +32 -0
  80. package/dist/application/state-migration/execution.js +440 -0
  81. package/dist/application/state-migration/execution.js.map +1 -0
  82. package/dist/application/state-migration/index.d.ts +22 -0
  83. package/dist/application/state-migration/index.js +17 -0
  84. package/dist/application/state-migration/index.js.map +1 -0
  85. package/dist/application/state-migration/lifecycle.d.ts +3 -0
  86. package/dist/application/state-migration/lifecycle.js +149 -0
  87. package/dist/application/state-migration/lifecycle.js.map +1 -0
  88. package/dist/application/state-migration/planning.d.ts +7 -0
  89. package/dist/application/state-migration/planning.js +150 -0
  90. package/dist/application/state-migration/planning.js.map +1 -0
  91. package/dist/application/state-migration/recovery.d.ts +5 -0
  92. package/dist/application/state-migration/recovery.js +145 -0
  93. package/dist/application/state-migration/recovery.js.map +1 -0
  94. package/dist/application/state-migration/runtime.d.ts +32 -0
  95. package/dist/application/state-migration/runtime.js +188 -0
  96. package/dist/application/state-migration/runtime.js.map +1 -0
  97. package/dist/application/state-migration/service.d.ts +2 -0
  98. package/dist/application/state-migration/service.js +20 -0
  99. package/dist/application/state-migration/service.js.map +1 -0
  100. package/dist/application/update/inspection.d.ts +16 -2
  101. package/dist/application/update/inspection.js +76 -23
  102. package/dist/application/update/inspection.js.map +1 -1
  103. package/dist/application/update/migration-runtime.js +24 -11
  104. package/dist/application/update/migration-runtime.js.map +1 -1
  105. package/dist/application/update/output.d.ts +2 -0
  106. package/dist/application/update/output.js +11 -1
  107. package/dist/application/update/output.js.map +1 -1
  108. package/dist/application/update/revalidation-plan.d.ts +1 -0
  109. package/dist/application/update/revalidation-plan.js +25 -12
  110. package/dist/application/update/revalidation-plan.js.map +1 -1
  111. package/dist/application/update/revalidation.d.ts +4 -1
  112. package/dist/application/update/revalidation.js +62 -21
  113. package/dist/application/update/revalidation.js.map +1 -1
  114. package/dist/application/update/use-case.js +25 -17
  115. package/dist/application/update/use-case.js.map +1 -1
  116. package/dist/application/upgrade/use-case.js +3 -0
  117. package/dist/application/upgrade/use-case.js.map +1 -1
  118. package/dist/cli/args/definitions.js +11 -3
  119. package/dist/cli/args/definitions.js.map +1 -1
  120. package/dist/cli/args/parser.js +29 -3
  121. package/dist/cli/args/parser.js.map +1 -1
  122. package/dist/domain/governance/activation/approvals.d.ts +264 -7
  123. package/dist/domain/governance/activation/approvals.js +166 -23
  124. package/dist/domain/governance/activation/approvals.js.map +1 -1
  125. package/dist/domain/governance/activation/capabilities.js +5 -2
  126. package/dist/domain/governance/activation/capabilities.js.map +1 -1
  127. package/dist/domain/governance/activation/evidence.d.ts +2 -1
  128. package/dist/domain/governance/activation/evidence.js +64 -11
  129. package/dist/domain/governance/activation/evidence.js.map +1 -1
  130. package/dist/domain/governance/activation/graph.d.ts +11 -384
  131. package/dist/domain/governance/activation/graph.js +424 -379
  132. package/dist/domain/governance/activation/graph.js.map +1 -1
  133. package/dist/domain/governance/activation/inputs.d.ts +3 -1
  134. package/dist/domain/governance/activation/inputs.js +47 -6
  135. package/dist/domain/governance/activation/inputs.js.map +1 -1
  136. package/dist/domain/governance/activation/operations.d.ts +4 -1
  137. package/dist/domain/governance/activation/operations.js +110 -22
  138. package/dist/domain/governance/activation/operations.js.map +1 -1
  139. package/dist/domain/governance/activation/readiness.d.ts +10 -0
  140. package/dist/domain/governance/activation/readiness.js +77 -38
  141. package/dist/domain/governance/activation/readiness.js.map +1 -1
  142. package/dist/domain/governance/activation/types.d.ts +151 -9
  143. package/dist/domain/governance/activation/types.js +23 -1
  144. package/dist/domain/governance/activation/types.js.map +1 -1
  145. package/dist/domain/governance/activation/validators.d.ts +4 -1
  146. package/dist/domain/governance/activation/validators.js +419 -26
  147. package/dist/domain/governance/activation/validators.js.map +1 -1
  148. package/dist/domain/governance/policy/identity.d.ts +55 -13
  149. package/dist/domain/governance/policy/identity.js +28 -8
  150. package/dist/domain/governance/policy/identity.js.map +1 -1
  151. package/dist/domain/project/artifact-lifecycle.d.ts +2 -1
  152. package/dist/domain/project/artifact-lifecycle.js +7 -1
  153. package/dist/domain/project/artifact-lifecycle.js.map +1 -1
  154. package/dist/domain/project/catalog.d.ts +50 -1
  155. package/dist/domain/project/catalog.js +104 -4
  156. package/dist/domain/project/catalog.js.map +1 -1
  157. package/dist/domain/project/contracts.d.ts +9 -1
  158. package/dist/domain/project/infrastructure-layout.d.ts +1 -1
  159. package/dist/domain/project/infrastructure-layout.js +3 -1
  160. package/dist/domain/project/infrastructure-layout.js.map +1 -1
  161. package/dist/domain/project/manifest/governance.d.ts +1 -1
  162. package/dist/domain/project/manifest/governance.js +9 -10
  163. package/dist/domain/project/manifest/governance.js.map +1 -1
  164. package/dist/domain/project/planning.js +4 -5
  165. package/dist/domain/project/planning.js.map +1 -1
  166. package/dist/domain/repair/stateful-bounded.d.ts +1 -0
  167. package/dist/domain/repair/stateful-bounded.js +37 -0
  168. package/dist/domain/repair/stateful-bounded.js.map +1 -0
  169. package/dist/domain/repair/stateful-invariants.d.ts +28 -0
  170. package/dist/domain/repair/stateful-invariants.js +278 -0
  171. package/dist/domain/repair/stateful-invariants.js.map +1 -0
  172. package/dist/domain/repair/stateful.d.ts +690 -0
  173. package/dist/domain/repair/stateful.js +26 -0
  174. package/dist/domain/repair/stateful.js.map +1 -0
  175. package/dist/domain/workstation/constraints.d.ts +1 -0
  176. package/dist/domain/workstation/constraints.js.map +1 -1
  177. package/dist/domain/workstation/contracts.d.ts +42 -0
  178. package/dist/domain/workstation/contracts.js +2 -0
  179. package/dist/domain/workstation/contracts.js.map +1 -0
  180. package/dist/domain/workstation/executables.d.ts +19 -0
  181. package/dist/domain/workstation/executables.js +68 -0
  182. package/dist/domain/workstation/executables.js.map +1 -0
  183. package/dist/domain/workstation/versions.d.ts +8 -0
  184. package/dist/domain/workstation/versions.js +145 -0
  185. package/dist/domain/workstation/versions.js.map +1 -0
  186. package/dist/framework-adapters.d.ts +2 -0
  187. package/dist/framework-adapters.js +148 -22
  188. package/dist/framework-adapters.js.map +1 -1
  189. package/dist/framework-validation.d.ts +6 -0
  190. package/dist/framework-validation.js +89 -24
  191. package/dist/framework-validation.js.map +1 -1
  192. package/dist/generators/common/base.js +57 -17
  193. package/dist/generators/common/base.js.map +1 -1
  194. package/dist/governance-activation/activation-state.js +39 -7
  195. package/dist/governance-activation/activation-state.js.map +1 -1
  196. package/dist/governance-activation/authority-records.d.ts +4 -0
  197. package/dist/governance-activation/authority-records.js +24 -0
  198. package/dist/governance-activation/authority-records.js.map +1 -0
  199. package/dist/governance-activation/commands.d.ts +9 -1
  200. package/dist/governance-activation/commands.js +392 -97
  201. package/dist/governance-activation/commands.js.map +1 -1
  202. package/dist/governance-activation/compatibility.d.ts +11 -8
  203. package/dist/governance-activation/compatibility.js +37 -23
  204. package/dist/governance-activation/compatibility.js.map +1 -1
  205. package/dist/governance-activation/doctor.js +40 -16
  206. package/dist/governance-activation/doctor.js.map +1 -1
  207. package/dist/governance-activation/github-config.d.ts +17 -0
  208. package/dist/governance-activation/github-config.js +88 -0
  209. package/dist/governance-activation/github-config.js.map +1 -0
  210. package/dist/governance-activation/github-discovery.d.ts +18 -0
  211. package/dist/governance-activation/github-discovery.js +229 -0
  212. package/dist/governance-activation/github-discovery.js.map +1 -0
  213. package/dist/governance-activation/github-ports.d.ts +12 -0
  214. package/dist/governance-activation/github-ports.js +4 -0
  215. package/dist/governance-activation/github-ports.js.map +1 -0
  216. package/dist/governance-activation/github-publication.d.ts +3 -0
  217. package/dist/governance-activation/github-publication.js +190 -0
  218. package/dist/governance-activation/github-publication.js.map +1 -0
  219. package/dist/governance-activation/historical-common.d.ts +98 -0
  220. package/dist/governance-activation/historical-common.js +269 -0
  221. package/dist/governance-activation/historical-common.js.map +1 -0
  222. package/dist/governance-activation/historical-credential-policy.d.ts +3 -0
  223. package/dist/governance-activation/historical-credential-policy.js +91 -0
  224. package/dist/governance-activation/historical-credential-policy.js.map +1 -0
  225. package/dist/governance-activation/historical-inputs.d.ts +6 -0
  226. package/dist/governance-activation/historical-inputs.js +57 -0
  227. package/dist/governance-activation/historical-inputs.js.map +1 -0
  228. package/dist/governance-activation/historical-safety.d.ts +9 -0
  229. package/dist/governance-activation/historical-safety.js +102 -0
  230. package/dist/governance-activation/historical-safety.js.map +1 -0
  231. package/dist/governance-activation/historical-source-metadata.d.ts +31 -0
  232. package/dist/governance-activation/historical-source-metadata.js +55 -0
  233. package/dist/governance-activation/historical-source-metadata.js.map +1 -0
  234. package/dist/governance-activation/historical-state.d.ts +12 -50
  235. package/dist/governance-activation/historical-state.js +195 -308
  236. package/dist/governance-activation/historical-state.js.map +1 -1
  237. package/dist/governance-activation/historical-v1-auxiliary.d.ts +1 -0
  238. package/dist/governance-activation/historical-v1-auxiliary.js +43 -0
  239. package/dist/governance-activation/historical-v1-auxiliary.js.map +1 -0
  240. package/dist/governance-activation/historical-v1-phase-contracts.d.ts +31 -0
  241. package/dist/governance-activation/historical-v1-phase-contracts.js +46 -0
  242. package/dist/governance-activation/historical-v1-phase-contracts.js.map +1 -0
  243. package/dist/governance-activation/historical-v2.d.ts +53 -0
  244. package/dist/governance-activation/historical-v2.js +313 -0
  245. package/dist/governance-activation/historical-v2.js.map +1 -0
  246. package/dist/governance-activation/history-contracts.d.ts +21 -12
  247. package/dist/governance-activation/history-contracts.js +99 -27
  248. package/dist/governance-activation/history-contracts.js.map +1 -1
  249. package/dist/governance-activation/inputs.d.ts +8 -2
  250. package/dist/governance-activation/inputs.js +41 -6
  251. package/dist/governance-activation/inputs.js.map +1 -1
  252. package/dist/governance-activation/migration-history.d.ts +43 -5
  253. package/dist/governance-activation/migration-history.js +231 -50
  254. package/dist/governance-activation/migration-history.js.map +1 -1
  255. package/dist/governance-activation/migration.d.ts +7 -4
  256. package/dist/governance-activation/migration.js +53 -252
  257. package/dist/governance-activation/migration.js.map +1 -1
  258. package/dist/governance-activation/phase-azure.d.ts +3 -0
  259. package/dist/governance-activation/phase-azure.js +205 -0
  260. package/dist/governance-activation/phase-azure.js.map +1 -0
  261. package/dist/governance-activation/phase-bootstrap-state.js +3 -0
  262. package/dist/governance-activation/phase-bootstrap-state.js.map +1 -1
  263. package/dist/governance-activation/phase-github.d.ts +3 -0
  264. package/dist/governance-activation/phase-github.js +192 -0
  265. package/dist/governance-activation/phase-github.js.map +1 -0
  266. package/dist/governance-activation/phase-governance.js +8 -2
  267. package/dist/governance-activation/phase-governance.js.map +1 -1
  268. package/dist/governance-activation/public-plans.d.ts +36 -0
  269. package/dist/governance-activation/public-plans.js +100 -0
  270. package/dist/governance-activation/public-plans.js.map +1 -0
  271. package/dist/governance-activation/read-only.d.ts +3 -1
  272. package/dist/governance-activation/read-only.js +7 -3
  273. package/dist/governance-activation/read-only.js.map +1 -1
  274. package/dist/governance-activation/reconciliation.js +10 -0
  275. package/dist/governance-activation/reconciliation.js.map +1 -1
  276. package/dist/governance-activation/seed-lifecycle.js +14 -25
  277. package/dist/governance-activation/seed-lifecycle.js.map +1 -1
  278. package/dist/governance-activation/source-of-truth.d.ts +21 -5
  279. package/dist/governance-activation/source-of-truth.js +102 -13
  280. package/dist/governance-activation/source-of-truth.js.map +1 -1
  281. package/dist/governance-activation/task-projection.d.ts +4 -1
  282. package/dist/governance-activation/task-projection.js +32 -3
  283. package/dist/governance-activation/task-projection.js.map +1 -1
  284. package/dist/governance-activation/task-writes.d.ts +42 -0
  285. package/dist/governance-activation/task-writes.js +250 -0
  286. package/dist/governance-activation/task-writes.js.map +1 -0
  287. package/dist/governance-activation/transition-files.d.ts +12 -0
  288. package/dist/governance-activation/transition-files.js +109 -0
  289. package/dist/governance-activation/transition-files.js.map +1 -0
  290. package/dist/governance-activation/transition-planning.d.ts +3 -1
  291. package/dist/governance-activation/transition-planning.js +158 -20
  292. package/dist/governance-activation/transition-planning.js.map +1 -1
  293. package/dist/governance-activation/transition-ports.d.ts +31 -3
  294. package/dist/governance-activation/transition-records.d.ts +11 -1
  295. package/dist/governance-activation/transition-records.js +52 -6
  296. package/dist/governance-activation/transition-records.js.map +1 -1
  297. package/dist/governance-activation/transitions.d.ts +5 -0
  298. package/dist/governance-activation/transitions.js +271 -37
  299. package/dist/governance-activation/transitions.js.map +1 -1
  300. package/dist/governance-assessment/engine.js +22 -15
  301. package/dist/governance-assessment/engine.js.map +1 -1
  302. package/dist/governance-assessment/project.d.ts +2 -0
  303. package/dist/governance-assessment/project.js +2 -1
  304. package/dist/governance-assessment/project.js.map +1 -1
  305. package/dist/interactive.d.ts +0 -1
  306. package/dist/interactive.js +3 -22
  307. package/dist/interactive.js.map +1 -1
  308. package/dist/openspec-profile.d.ts +2 -1
  309. package/dist/openspec-profile.js +11 -41
  310. package/dist/openspec-profile.js.map +1 -1
  311. package/dist/repository-governance.d.ts +10 -5
  312. package/dist/repository-governance.js +178 -92
  313. package/dist/repository-governance.js.map +1 -1
  314. package/dist/self-upgrade.d.ts +8 -2
  315. package/dist/self-upgrade.js +180 -47
  316. package/dist/self-upgrade.js.map +1 -1
  317. package/dist/telemetry/contract.d.ts +1 -1
  318. package/dist/telemetry/contract.js +3 -0
  319. package/dist/telemetry/contract.js.map +1 -1
  320. package/dist/workstation-catalog.d.ts +13 -1
  321. package/dist/workstation-catalog.js +122 -0
  322. package/dist/workstation-catalog.js.map +1 -1
  323. package/dist/workstation.d.ts +69 -14
  324. package/dist/workstation.js +600 -215
  325. package/dist/workstation.js.map +1 -1
  326. package/docs/cli-reference.md +69 -22
  327. package/docs/configuration-and-manifests.md +9 -8
  328. package/docs/prerequisites.md +93 -18
  329. package/docs/repository-governance.md +68 -40
  330. package/docs/spec-workflows-and-agents.md +132 -13
  331. package/docs/troubleshooting.md +32 -2
  332. package/package.json +1 -1
@@ -1,109 +1,29 @@
1
- import { lstat, readdir, readFile, realpath } from 'node:fs/promises';
1
+ import { constants } from 'node:fs';
2
+ import { lstat, open, readdir, realpath } from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import { canonicalJson, canonicalSha256, isRecord } from '../domain/governance/activation/canonical-json.js';
4
5
  import { currentActivationIdentity } from '../domain/governance/activation/graph.js';
6
+ import { historicalActivationIdentities, isHistoricalV2ActivationIdentity } from '../domain/governance/policy/identity.js';
5
7
  import { errorCode } from '../adapters/filesystem/errors.js';
6
8
  import { parseManifest } from '../application/project/manifest.js';
7
- import { ActivationHistoryError, historicalIdentity, historyArray, historyBoolean, historyCaseKey, historyDigest, historyEnum, historyExact, historyFail, historyLiteral, historyPathKey, historyPathParts, historyRecord, historyRecordId, historyString, historyStrings, historyTimestamp, historicalActivationIdentity, historicalActivationStatePathParts, historicalManifestPathParts, historicalMetadataPathParts, parseHistoryJson, rawHistoryDigest } from './history-contracts.js';
9
+ import { ActivationHistoryError, historicalIdentity, historicalV1Identity, historyArray, historyBoolean, historyCaseKey, historyDigest, historyEnum, historyExact, historyFail, historyLiteral, historyPathKey, historyPathParts, historyRecord, historyRecordId, historyString, historyStrings, historyTimestamp, historicalActivationIdentity, historicalActivationStatePathParts, historicalManifestPathParts, historicalMetadataPathParts, migrationStateFilePathParts, historicalSourceChangePathParts, parseHistoryJson, rawHistoryDigest, validateHistoricalV2SourceMigrationJournal } from './history-contracts.js';
8
10
  import { FileSystemError } from '../domain/project/errors.js';
9
11
  import { validateGovernanceCompatibilityMetadata } from './compatibility.js';
10
- // Frozen from 57eba97^:src/governance-activation/{types,validators,graph}.ts.
11
- // These schemas are historical readers, never current execution validators.
12
- export const historicalPhaseIds = [
13
- 'seed-valid', 'seed-verified', 'seed-archived', 'committed', 'pushed', 'phase-0-complete',
14
- 'activation-approved', 'credential-ready', 'provider-ready', 'state-path-selected',
15
- 'existing-private-path', 'bootstrap-local', 'runner-ready', 'private-backend-proof',
16
- 'remote-import-verified', 'remote-ready', 'application-foundation', 'workflow-source-ready',
17
- 'dev-proof', 'staging-qualified', 'production-rehearsed', 'green-red-proof',
18
- 'enforcement-approved', 'rulesets-applied', 'live-readback', 'bootstrap-state-disposed'
19
- ];
20
- const historicalPhaseStates = [
21
- 'pending', 'blocked', 'ready', 'approved', 'running', 'verified', 'failed', 'inapplicable', 'retained', 'disposed'
22
- ];
23
- const historicalResults = ['verified', 'failed', 'inapplicable', 'retained', 'disposed'];
24
- const historicalMutationClasses = [
25
- 'none', 'read-worktree', 'write-activation-state', 'write-evidence', 'write-openspec-seed',
26
- 'write-openspec-governance', 'write-local-state', 'delete-local-state', 'write-workflows',
27
- 'write-ruleset-source', 'git-commit', 'git-push', 'github-read', 'github-write',
28
- 'github-secret-write', 'azure-read', 'azure-provider-register', 'azure-network-provision',
29
- 'azure-state-import', 'azure-resource-provision', 'github-ruleset-write'
30
- ];
31
- const historicalGateKinds = [
32
- 'none', 'repository-publish', 'activation-plan', 'credential-enrollment', 'infrastructure-cost',
33
- 'enforcement', 'destructive-disposal', 'external-blocker'
34
- ];
35
- const historicalQuestionKinds = [
36
- 'repository-creation-initial-commit-push', 'credential-enrollment',
37
- 'billed-infrastructure-policy-exception-cost-ceiling', 'final-enforcement',
38
- 'destructive-operation', 'external-blocker'
39
- ];
40
- const destinationTypes = ['local', 'repository', 'subscription', 'environment', 'tenant', 'external'];
41
- const adapterIds = ['local-evidence', 'selected-spec-workflow', 'git', 'github', 'azure-opentofu', 'local-state'];
42
- const historicalPhaseGates = {
43
- 'seed-valid': 'none', 'seed-verified': 'none', 'seed-archived': 'none',
44
- committed: 'repository-publish', pushed: 'repository-publish', 'phase-0-complete': 'none',
45
- 'activation-approved': 'activation-plan', 'credential-ready': 'credential-enrollment',
46
- 'provider-ready': 'infrastructure-cost', 'state-path-selected': 'infrastructure-cost',
47
- 'existing-private-path': 'none', 'bootstrap-local': 'infrastructure-cost',
48
- 'runner-ready': 'infrastructure-cost', 'private-backend-proof': 'none',
49
- 'remote-import-verified': 'infrastructure-cost', 'remote-ready': 'none',
50
- 'application-foundation': 'infrastructure-cost', 'workflow-source-ready': 'none',
51
- 'dev-proof': 'none', 'staging-qualified': 'none', 'production-rehearsed': 'none',
52
- 'green-red-proof': 'none', 'enforcement-approved': 'enforcement', 'rulesets-applied': 'enforcement',
53
- 'live-readback': 'none', 'bootstrap-state-disposed': 'destructive-disposal'
54
- };
55
- function phaseId(value, label) {
56
- return historyEnum(value, historicalPhaseIds, label);
57
- }
58
- export function assertHistoricalPhasesComplete(phases) {
59
- for (const id of historicalPhaseIds) {
60
- if (phases[id] === undefined)
61
- historyFail(`phases.${id}`, 'is required.');
62
- }
63
- }
64
- function reference(value, label) {
65
- const item = historyExact(value, ['phaseId', 'evidenceId', 'headerDigest', 'result'], label);
66
- return {
67
- phaseId: phaseId(item.phaseId, `${label}.phaseId`),
68
- evidenceId: historyRecordId(item.evidenceId, `${label}.evidenceId`),
69
- headerDigest: historyDigest(item.headerDigest, `${label}.headerDigest`),
70
- result: historyEnum(item.result, historicalResults, `${label}.result`)
71
- };
72
- }
73
- function retention(value, label) {
74
- const item = historyExact(value, [
75
- 'status', 'remoteImportEvidenceId', 'remoteImportEvidenceDigest', 'retainedAt', 'disposeAfter',
76
- 'encryptedStatePathParts', 'encryptionKeyPathParts'
77
- ], label, ['disposedAt', 'deletionEvidenceId', 'incompleteCleanup']);
78
- const status = historyEnum(item.status, ['retained', 'disposed'], `${label}.status`);
79
- const retainedAt = historyTimestamp(item.retainedAt, `${label}.retainedAt`);
80
- const disposeAfter = historyTimestamp(item.disposeAfter, `${label}.disposeAfter`);
81
- if (Date.parse(disposeAfter) - Date.parse(retainedAt) !== 30 * 24 * 60 * 60 * 1000) {
82
- historyFail(label, 'disposeAfter must be exactly 30 days after retainedAt.');
83
- }
84
- if (status === 'disposed' && !Object.hasOwn(item, 'disposedAt'))
85
- historyFail(label, 'disposedAt is required for disposed state.');
86
- return {
87
- status,
88
- remoteImportEvidenceId: historyRecordId(item.remoteImportEvidenceId, `${label}.remoteImportEvidenceId`),
89
- remoteImportEvidenceDigest: historyDigest(item.remoteImportEvidenceDigest, `${label}.remoteImportEvidenceDigest`),
90
- retainedAt, disposeAfter,
91
- encryptedStatePathParts: historyArray(item.encryptedStatePathParts, `${label}.encryptedStatePathParts`)
92
- .map((entry) => historyPathParts(entry, `${label}.encryptedStatePathParts`)),
93
- encryptionKeyPathParts: historyArray(item.encryptionKeyPathParts, `${label}.encryptionKeyPathParts`)
94
- .map((entry) => historyPathParts(entry, `${label}.encryptionKeyPathParts`)),
95
- ...(Object.hasOwn(item, 'disposedAt') ? { disposedAt: historyTimestamp(item.disposedAt, `${label}.disposedAt`) } : {}),
96
- ...(Object.hasOwn(item, 'deletionEvidenceId') ? { deletionEvidenceId: historyRecordId(item.deletionEvidenceId, `${label}.deletionEvidenceId`) } : {}),
97
- ...(Object.hasOwn(item, 'incompleteCleanup') ? { incompleteCleanup: historyStrings(item.incompleteCleanup, `${label}.incompleteCleanup`) } : {})
98
- };
99
- }
12
+ import { assertSafeHistoricalBytes, assertSafeHistoricalRecord } from './historical-safety.js';
13
+ import { historicalV1PhaseContractDigests, historicalV1ResultAllowed } from './historical-v1-phase-contracts.js';
14
+ import { validateHistoricalV1AuxiliaryRecord } from './historical-v1-auxiliary.js';
15
+ import { validateHistoricalGovernanceChangeMetadata } from './historical-source-metadata.js';
16
+ import { validateHistoricalV2ActivationState, validateHistoricalV2EvidenceRecord, validateHistoricalV2ApprovalEnvelope, validateHistoricalV2SavedTransitionPlan, validateHistoricalV2Compatibility, historicalV2ApprovalEnvelopeHash, validateHistoricalV2AuxiliaryRecord } from './historical-v2.js';
17
+ export * from './historical-common.js';
18
+ import { historicalPhaseIds, historicalPhaseStates, historicalResults, phaseId, assertHistoricalPhasesComplete, readHistoricalEvidenceReference, validateHistoricalBootstrapState, readHistoricalEvidenceTransition, validateHistoricalEmbeddedPaths, readHistoricalApprovalFields, readHistoricalSavedTransitionPlan } from './historical-common.js';
100
19
  export function validateHistoricalActivationState(value) {
101
20
  const label = 'historicalActivationState';
21
+ assertSafeHistoricalRecord(value, label);
102
22
  const state = historyExact(value, [
103
23
  'schemaVersion', 'identity', 'repository', 'activeChange', 'applicability', 'phases', 'createdAt', 'updatedAt'
104
24
  ], label, ['bootstrapState']);
105
25
  historyLiteral(state.schemaVersion, 1, `${label}.schemaVersion`);
106
- const identity = historicalIdentity(state.identity, `${label}.identity`);
26
+ const identity = historicalV1Identity(state.identity, `${label}.identity`);
107
27
  const repository = historyExact(state.repository, ['id', 'name', 'defaultBranch'], `${label}.repository`);
108
28
  const applicability = historyExact(state.applicability, ['statePath', 'privateStagingDast', 'credentialRequired'], `${label}.applicability`);
109
29
  let activeChange = null;
@@ -119,7 +39,7 @@ export function validateHistoricalActivationState(value) {
119
39
  for (const id of historicalPhaseIds) {
120
40
  const at = `${label}.phases.${id}`;
121
41
  const phase = historyExact(rawPhases[id], ['state', 'updatedAt', 'evidence', 'approvals', 'blockers'], at);
122
- const evidence = historyArray(phase.evidence, `${at}.evidence`).map((entry, index) => reference(entry, `${at}.evidence[${index}]`));
42
+ const evidence = historyArray(phase.evidence, `${at}.evidence`).map((entry, index) => readHistoricalEvidenceReference(entry, `${at}.evidence[${index}]`));
123
43
  if (evidence.some((entry) => entry.phaseId !== id))
124
44
  historyFail(at, 'evidence reference names another phase.');
125
45
  const approvals = historyArray(phase.approvals, `${at}.approvals`).map((entry) => historyRecordId(entry, `${at}.approvals`));
@@ -146,29 +66,20 @@ export function validateHistoricalActivationState(value) {
146
66
  privateStagingDast: historyBoolean(applicability.privateStagingDast, `${label}.applicability.privateStagingDast`),
147
67
  credentialRequired: historyBoolean(applicability.credentialRequired, `${label}.applicability.credentialRequired`)
148
68
  },
149
- ...(Object.hasOwn(state, 'bootstrapState') ? { bootstrapState: retention(state.bootstrapState, `${label}.bootstrapState`) } : {}),
69
+ ...(Object.hasOwn(state, 'bootstrapState') ? { bootstrapState: validateHistoricalBootstrapState(state.bootstrapState, `${label}.bootstrapState`) } : {}),
150
70
  phases, createdAt: historyString(state.createdAt, `${label}.createdAt`),
151
71
  updatedAt: historyString(state.updatedAt, `${label}.updatedAt`)
152
72
  };
153
73
  }
154
- function transition(value, label) {
155
- const item = historyExact(value, ['phaseId', 'baselineSha', 'inputDigest', 'transitionDigest'], label);
156
- return {
157
- phaseId: phaseId(item.phaseId, `${label}.phaseId`),
158
- baselineSha: historyDigest(item.baselineSha, `${label}.baselineSha`),
159
- inputDigest: historyDigest(item.inputDigest, `${label}.inputDigest`),
160
- transitionDigest: historyDigest(item.transitionDigest, `${label}.transitionDigest`)
161
- };
162
- }
163
74
  function historicalProofIdentity(item, label) {
164
- const identity = historicalIdentity(item.identity, `${label}.identity`);
75
+ const identity = historicalV1Identity(item.identity, `${label}.identity`);
165
76
  const graphHash = historyDigest(item.phaseGraphHash, `${label}.phaseGraphHash`);
166
77
  if (graphHash !== identity.phaseGraphHash)
167
78
  historyFail(label, 'graph hash contradicts the historical identity.');
168
79
  const id = phaseId(item.phaseId, `${label}.phaseId`);
169
80
  const baselineSha = historyDigest(item.baselineSha, `${label}.baselineSha`);
170
81
  const inputDigest = historyDigest(item.inputDigest, `${label}.inputDigest`);
171
- const proofTransition = transition(item.transition, `${label}.transition`);
82
+ const proofTransition = readHistoricalEvidenceTransition(item.transition, `${label}.transition`);
172
83
  if (proofTransition.phaseId !== id || proofTransition.baselineSha !== baselineSha || proofTransition.inputDigest !== inputDigest) {
173
84
  historyFail(label, 'transition contradicts the proof phase, baseline or inputs.');
174
85
  }
@@ -180,20 +91,29 @@ function historicalProofIdentity(item, label) {
180
91
  }
181
92
  export function validateHistoricalEvidenceHeader(value) {
182
93
  const label = 'historicalEvidenceHeader';
94
+ assertSafeHistoricalRecord(value, label);
183
95
  const item = historyExact(value, [
184
96
  'schemaVersion', 'repositoryId', 'identity', 'phaseGraphHash', 'phaseId', 'phaseContractDigest',
185
97
  'inputDigest', 'baselineSha', 'transition', 'producedAt', 'producer', 'result'
186
98
  ], label);
99
+ const proof = historicalProofIdentity(item, label);
100
+ const phaseContractDigest = historyDigest(item.phaseContractDigest, `${label}.phaseContractDigest`);
101
+ if (phaseContractDigest !== historicalV1PhaseContractDigests[proof.phaseId]) {
102
+ historyFail(label, 'phase contract digest is not the published v1 behavior.');
103
+ }
104
+ const result = historyEnum(item.result, historicalResults, `${label}.result`);
105
+ if (!historicalV1ResultAllowed(proof.phaseId, result))
106
+ historyFail(label, 'terminal result is not permitted by the published v1 phase.');
187
107
  return {
188
- ...historicalProofIdentity(item, label),
189
- phaseContractDigest: historyDigest(item.phaseContractDigest, `${label}.phaseContractDigest`),
108
+ ...proof, phaseContractDigest,
190
109
  producedAt: historyTimestamp(item.producedAt, `${label}.producedAt`),
191
110
  producer: historyString(item.producer, `${label}.producer`),
192
- result: historyEnum(item.result, historicalResults, `${label}.result`)
111
+ result
193
112
  };
194
113
  }
195
114
  export function validateHistoricalLiveReadback(value) {
196
115
  const label = 'historicalLiveReadback';
116
+ assertSafeHistoricalRecord(value, label);
197
117
  const item = historyExact(value, [
198
118
  'schemaVersion', 'repositoryId', 'identity', 'phaseGraphHash', 'phaseId', 'baselineSha', 'inputDigest',
199
119
  'transition', 'observedAt', 'provider', 'resourceType', 'resourceId', 'sourceDigest', 'readbackDigest', 'matches'
@@ -209,28 +129,9 @@ export function validateHistoricalLiveReadback(value) {
209
129
  matches: historyBoolean(item.matches, `${label}.matches`)
210
130
  };
211
131
  }
212
- function validateEmbeddedPaths(value, label) {
213
- if (Array.isArray(value)) {
214
- value.forEach((entry, index) => validateEmbeddedPaths(entry, `${label}[${index}]`));
215
- }
216
- else if (isRecord(value)) {
217
- for (const [key, entry] of Object.entries(value)) {
218
- if (key === 'pathParts' || key.endsWith('PathParts')) {
219
- const parts = historyArray(entry, `${label}.${key}`);
220
- if (parts.length === 0 && ['cwdPathParts', 'encryptedStatePathParts', 'encryptionKeyPathParts', 'deletedPathParts'].includes(key))
221
- continue;
222
- if (parts.length > 0 && Array.isArray(parts[0]))
223
- parts.forEach((item) => historyPathParts(item, `${label}.${key}`));
224
- else
225
- historyPathParts(parts, `${label}.${key}`);
226
- }
227
- else
228
- validateEmbeddedPaths(entry, `${label}.${key}`);
229
- }
230
- }
231
- }
232
132
  export function validateHistoricalEvidenceRecord(value, headerOnlyEvidenceId) {
233
133
  const label = 'historicalEvidenceRecord';
134
+ assertSafeHistoricalRecord(value, label);
234
135
  const item = historyRecord(value, label);
235
136
  // Header-only JSON was an explicit v1 format in commands.ts, not an unversioned import.
236
137
  if (!Object.hasOwn(item, 'header')) {
@@ -241,7 +142,7 @@ export function validateHistoricalEvidenceRecord(value, headerOnlyEvidenceId) {
241
142
  }
242
143
  historyExact(item, ['evidenceId', 'header'], label, ['payload', 'liveReadback']);
243
144
  if (Object.hasOwn(item, 'payload'))
244
- validateEmbeddedPaths(item.payload, `${label}.payload`);
145
+ validateHistoricalEmbeddedPaths(item.payload, `${label}.payload`);
245
146
  const header = validateHistoricalEvidenceHeader(item.header);
246
147
  const liveReadback = Object.hasOwn(item, 'liveReadback')
247
148
  ? historyArray(item.liveReadback, `${label}.liveReadback`).map(validateHistoricalLiveReadback) : undefined;
@@ -255,169 +156,23 @@ export function validateHistoricalEvidenceRecord(value, headerOnlyEvidenceId) {
255
156
  ...(liveReadback === undefined ? {} : { liveReadback })
256
157
  };
257
158
  }
258
- function uniqueSorted(values, key, label) {
259
- if (new Set(values.map(key)).size !== values.length)
260
- historyFail(label, 'contains duplicate scope entries.');
261
- return values.sort((a, b) => key(a).localeCompare(key(b), 'en'));
262
- }
263
- function nullableString(value, label) {
264
- return value === null ? null : historyString(value, label);
265
- }
266
159
  export function validateHistoricalApprovalEnvelope(value) {
267
- const label = 'historicalApprovalEnvelope';
268
- const item = historyExact(value, [
269
- 'schemaVersion', 'id', 'phaseId', 'gateKind', 'identity', 'baselineSha', 'planDigest', 'resources',
270
- 'destinations', 'permissions', 'costCeiling', 'policyExceptions', 'destructiveScope', 'expiresAt', 'approvedAt', 'approver'
271
- ], label);
272
- const id = phaseId(item.phaseId, `${label}.phaseId`);
273
- const gateKind = historyEnum(item.gateKind, historicalGateKinds, `${label}.gateKind`);
274
- if (gateKind !== historicalPhaseGates[id])
275
- historyFail(label, 'gate does not match its historical phase.');
276
- const resources = historyArray(item.resources, `${label}.resources`).map((entry, index) => {
277
- const at = `${label}.resources[${index}]`;
278
- const resource = historyExact(entry, ['type', 'identity'], at);
279
- return { type: historyString(resource.type, `${at}.type`).trim().toLowerCase(), identity: historyString(resource.identity, `${at}.identity`).trim() };
280
- });
281
- const destinations = historyArray(item.destinations, `${label}.destinations`).map((entry, index) => {
282
- const at = `${label}.destinations[${index}]`;
283
- const destination = historyExact(entry, ['type', 'identity', 'repository', 'subscriptionId'], at);
284
- return {
285
- type: historyEnum(destination.type, destinationTypes, `${at}.type`),
286
- identity: historyString(destination.identity, `${at}.identity`).trim(),
287
- repository: nullableString(destination.repository, `${at}.repository`)?.trim() ?? null,
288
- subscriptionId: nullableString(destination.subscriptionId, `${at}.subscriptionId`)?.trim() ?? null
289
- };
290
- });
291
- const cost = historyExact(item.costCeiling, ['currency', 'fixedMonthlyCents', 'usageMonthlyCents'], `${label}.costCeiling`);
292
- const currency = historyString(cost.currency, `${label}.costCeiling.currency`).trim();
293
- if (!/^[A-Z]{3}$/u.test(currency))
294
- historyFail(label, 'cost currency must be a three-letter uppercase ISO currency.');
295
- const cents = (value, at) => {
296
- if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)
297
- historyFail(at, 'must be non-negative safe integer cents.');
298
- return value;
299
- };
300
- const scopeStrings = (value, at, lower = false) => uniqueSorted(historyStrings(value, at).map((entry) => lower ? entry.trim().toLowerCase() : entry.trim()), (entry) => entry, at);
160
+ const item = historyRecord(value, 'historicalApprovalEnvelope');
161
+ const fields = readHistoricalApprovalFields(value);
162
+ if (Date.parse(fields.approvedAt) >= Date.parse(fields.expiresAt))
163
+ historyFail('historicalApprovalEnvelope', 'requires approvedAt before expiresAt.');
301
164
  return {
302
- schemaVersion: historyLiteral(item.schemaVersion, 1, `${label}.schemaVersion`),
303
- id: historyRecordId(item.id, `${label}.id`), phaseId: id, gateKind,
304
- identity: historicalIdentity(item.identity, `${label}.identity`),
305
- baselineSha: historyDigest(item.baselineSha, `${label}.baselineSha`),
306
- planDigest: historyDigest(item.planDigest, `${label}.planDigest`),
307
- resources: uniqueSorted(resources, canonicalJson, `${label}.resources`),
308
- destinations: uniqueSorted(destinations, canonicalJson, `${label}.destinations`),
309
- permissions: scopeStrings(item.permissions, `${label}.permissions`, true),
310
- costCeiling: {
311
- currency, fixedMonthlyCents: cents(cost.fixedMonthlyCents, `${label}.costCeiling.fixedMonthlyCents`),
312
- usageMonthlyCents: cents(cost.usageMonthlyCents, `${label}.costCeiling.usageMonthlyCents`)
313
- },
314
- policyExceptions: scopeStrings(item.policyExceptions, `${label}.policyExceptions`),
315
- destructiveScope: scopeStrings(item.destructiveScope, `${label}.destructiveScope`),
316
- expiresAt: historyTimestamp(item.expiresAt, `${label}.expiresAt`),
317
- approvedAt: historyTimestamp(item.approvedAt, `${label}.approvedAt`),
318
- approver: historyString(item.approver, `${label}.approver`)
165
+ ...fields,
166
+ schemaVersion: historyLiteral(item.schemaVersion, 1, 'historicalApprovalEnvelope.schemaVersion'),
167
+ identity: historicalV1Identity(item.identity, 'historicalApprovalEnvelope.identity')
319
168
  };
320
169
  }
321
170
  export function historicalApprovalEnvelopeHash(envelope) {
322
171
  const { id: _id, approvedAt: _approvedAt, approver: _approver, ...scope } = validateHistoricalApprovalEnvelope(envelope);
323
172
  return canonicalSha256(scope);
324
173
  }
325
- function operationDestination(value, label) {
326
- const item = historyExact(value, ['type', 'identity'], label, ['pathParts', 'repository', 'subscriptionId', 'ref']);
327
- return {
328
- type: historyEnum(item.type, destinationTypes, `${label}.type`),
329
- identity: historyString(item.identity, `${label}.identity`),
330
- ...(Object.hasOwn(item, 'pathParts') ? { pathParts: historyPathParts(item.pathParts, `${label}.pathParts`) } : {}),
331
- ...(Object.hasOwn(item, 'repository') ? { repository: historyString(item.repository, `${label}.repository`) } : {}),
332
- ...(Object.hasOwn(item, 'subscriptionId') ? { subscriptionId: historyString(item.subscriptionId, `${label}.subscriptionId`) } : {}),
333
- ...(Object.hasOwn(item, 'ref') ? { ref: historyString(item.ref, `${label}.ref`) } : {})
334
- };
335
- }
336
- function operation(value, label) {
337
- const item = historyExact(value, ['adapter', 'actionId', 'mutationClass', 'phaseId', 'inputs', 'destination', 'remote', 'destructive'], label);
338
- const inputs = historyRecord(item.inputs, `${label}.inputs`);
339
- validateEmbeddedPaths(inputs, `${label}.inputs`);
340
- return {
341
- adapter: historyEnum(item.adapter, adapterIds, `${label}.adapter`),
342
- actionId: historyString(item.actionId, `${label}.actionId`),
343
- mutationClass: historyEnum(item.mutationClass, historicalMutationClasses, `${label}.mutationClass`),
344
- phaseId: phaseId(item.phaseId, `${label}.phaseId`), inputs,
345
- destination: operationDestination(item.destination, `${label}.destination`),
346
- remote: historyBoolean(item.remote, `${label}.remote`), destructive: historyBoolean(item.destructive, `${label}.destructive`)
347
- };
348
- }
349
- function evaluation(value, label) {
350
- const item = historyExact(value, [
351
- 'phaseId', 'gateKind', 'questionKind', 'approvalRequired', 'status', 'envelopeId', 'envelopeHash', 'reasons', 'expansionReasons'
352
- ], label);
353
- return {
354
- phaseId: phaseId(item.phaseId, `${label}.phaseId`),
355
- gateKind: historyEnum(item.gateKind, historicalGateKinds, `${label}.gateKind`),
356
- questionKind: item.questionKind === null ? null : historyEnum(item.questionKind, historicalQuestionKinds, `${label}.questionKind`),
357
- approvalRequired: historyBoolean(item.approvalRequired, `${label}.approvalRequired`),
358
- status: historyEnum(item.status, ['not-required', 'approval-required', 'reused', 'expired', 'invalidated'], `${label}.status`),
359
- envelopeId: item.envelopeId === null ? null : historyRecordId(item.envelopeId, `${label}.envelopeId`),
360
- envelopeHash: item.envelopeHash === null ? null : historyDigest(item.envelopeHash, `${label}.envelopeHash`),
361
- reasons: historyStrings(item.reasons, `${label}.reasons`), expansionReasons: historyStrings(item.expansionReasons, `${label}.expansionReasons`)
362
- };
363
- }
364
- function rollbackPlan(value, label) {
365
- const item = historyExact(value, ['phaseId', 'strategy', 'target', 'operations', 'retained', 'cleanupWarnings'], label);
366
- return {
367
- phaseId: phaseId(item.phaseId, `${label}.phaseId`),
368
- strategy: historyEnum(item.strategy, ['none', 'retain', 'reverse-to', 'dispose'], `${label}.strategy`),
369
- target: item.target === null ? null : phaseId(item.target, `${label}.target`),
370
- operations: historyArray(item.operations, `${label}.operations`).map((entry, index) => operation(entry, `${label}.operations[${index}]`)),
371
- retained: historyStrings(item.retained, `${label}.retained`), cleanupWarnings: historyStrings(item.cleanupWarnings, `${label}.cleanupWarnings`)
372
- };
373
- }
374
174
  export function validateHistoricalSavedTransitionPlan(value) {
375
- const label = 'historicalTransitionPlan';
376
- const item = historyExact(value, [
377
- 'schemaVersion', 'phaseId', 'createdAt', 'expiresAt', 'identity', 'graphHash', 'stateHash',
378
- 'baselineDigest', 'inputDigest', 'transitionDigest', 'planDigest', 'mutationClasses', 'operations',
379
- 'approval', 'rollbackPlan', 'noSecrets'
380
- ], label);
381
- const identity = historicalIdentity(item.identity, `${label}.identity`);
382
- const graphHash = historyDigest(item.graphHash, `${label}.graphHash`);
383
- if (graphHash !== identity.phaseGraphHash)
384
- historyFail(label, 'graphHash contradicts the historical identity.');
385
- const id = phaseId(item.phaseId, `${label}.phaseId`);
386
- const operations = historyArray(item.operations, `${label}.operations`).map((entry, index) => operation(entry, `${label}.operations[${index}]`));
387
- if (operations.some((entry) => entry.phaseId !== id))
388
- historyFail(label, 'operation phase differs from its plan.');
389
- const mutations = historyExact(item.mutationClasses, ['local', 'remote'], `${label}.mutationClasses`);
390
- const approval = historyExact(item.approval, ['gateKind', 'required', 'evaluation', 'envelopeId', 'envelopeHash'], `${label}.approval`);
391
- const approvalEvaluation = evaluation(approval.evaluation, `${label}.approval.evaluation`);
392
- const gateKind = historyEnum(approval.gateKind, historicalGateKinds, `${label}.approval.gateKind`);
393
- const required = historyBoolean(approval.required, `${label}.approval.required`);
394
- const envelopeId = approval.envelopeId === null ? null : historyRecordId(approval.envelopeId, `${label}.approval.envelopeId`);
395
- const envelopeHash = approval.envelopeHash === null ? null : historyDigest(approval.envelopeHash, `${label}.approval.envelopeHash`);
396
- if (gateKind !== historicalPhaseGates[id] || required !== (gateKind !== 'none') ||
397
- approvalEvaluation.phaseId !== id || approvalEvaluation.gateKind !== gateKind ||
398
- approvalEvaluation.envelopeId !== envelopeId || approvalEvaluation.envelopeHash !== envelopeHash ||
399
- (envelopeId === null) !== (envelopeHash === null)) {
400
- historyFail(label, 'approval references or gate contradict the historical phase.');
401
- }
402
- const rollback = rollbackPlan(item.rollbackPlan, `${label}.rollbackPlan`);
403
- if (rollback.phaseId !== id)
404
- historyFail(label, 'rollback names another phase.');
405
- return {
406
- schemaVersion: historyLiteral(item.schemaVersion, 1, `${label}.schemaVersion`), phaseId: id,
407
- createdAt: historyTimestamp(item.createdAt, `${label}.createdAt`),
408
- expiresAt: historyTimestamp(item.expiresAt, `${label}.expiresAt`), identity, graphHash,
409
- stateHash: item.stateHash === null ? null : historyDigest(item.stateHash, `${label}.stateHash`),
410
- baselineDigest: historyDigest(item.baselineDigest, `${label}.baselineDigest`),
411
- inputDigest: historyDigest(item.inputDigest, `${label}.inputDigest`),
412
- transitionDigest: historyDigest(item.transitionDigest, `${label}.transitionDigest`),
413
- planDigest: historyDigest(item.planDigest, `${label}.planDigest`),
414
- mutationClasses: {
415
- local: historyArray(mutations.local, `${label}.mutationClasses.local`).map((entry) => historyEnum(entry, historicalMutationClasses, `${label}.mutationClasses.local`)),
416
- remote: historyArray(mutations.remote, `${label}.mutationClasses.remote`).map((entry) => historyEnum(entry, historicalMutationClasses, `${label}.mutationClasses.remote`))
417
- },
418
- operations, approval: { gateKind, required, evaluation: approvalEvaluation, envelopeId, envelopeHash },
419
- rollbackPlan: rollback, noSecrets: historyLiteral(item.noSecrets, true, `${label}.noSecrets`)
420
- };
175
+ return readHistoricalSavedTransitionPlan(value, historicalV1Identity);
421
176
  }
422
177
  export function historicalTransitionPlanPathParts(plan) {
423
178
  return historyPathParts([
@@ -481,16 +236,32 @@ export async function captureHistoryFile(projectRoot, parts) {
481
236
  if (!details.isFile() || details.isSymbolicLink() || details.nlink !== 1) {
482
237
  historyFail(historyPathKey(pathParts), 'must be a regular unlinked file.', 'unsafe-history-path');
483
238
  }
484
- const content = await readFile(target);
485
- const after = await lstat(target);
486
- if (after.ino !== details.ino || after.dev !== details.dev || after.mode !== details.mode ||
487
- after.size !== details.size || after.mtimeMs !== details.mtimeMs || after.nlink !== 1) {
488
- historyFail(historyPathKey(pathParts), 'changed while being read; obtain a fresh preview.', 'historical-source-changed');
239
+ if (details.size > 8 * 1024 * 1024) {
240
+ historyFail(historyPathKey(pathParts), 'exceeds the bounded control-record size; no contents were read.', 'unsafe-historical-payload');
241
+ }
242
+ const handle = await open(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
243
+ try {
244
+ const opened = await handle.stat();
245
+ if (!opened.isFile() || opened.ino !== details.ino || opened.dev !== details.dev || opened.nlink !== 1 ||
246
+ opened.mode !== details.mode || opened.size !== details.size || opened.mtimeMs !== details.mtimeMs) {
247
+ historyFail(historyPathKey(pathParts), 'changed before the bounded read.', 'historical-source-changed');
248
+ }
249
+ await resolveHistoryProjectPath(projectRoot, pathParts);
250
+ const content = await handle.readFile();
251
+ const after = await lstat(await resolveHistoryProjectPath(projectRoot, pathParts));
252
+ if (after.ino !== details.ino || after.dev !== details.dev || after.mode !== details.mode ||
253
+ after.size !== details.size || after.mtimeMs !== details.mtimeMs || after.nlink !== 1) {
254
+ historyFail(historyPathKey(pathParts), 'changed while being read; obtain a fresh preview.', 'historical-source-changed');
255
+ }
256
+ return { pathParts, content, mode: details.mode & 0o7777 };
257
+ }
258
+ finally {
259
+ await handle.close();
489
260
  }
490
- return { pathParts, content, mode: details.mode & 0o7777 };
491
261
  }
492
262
  export function validateHistoricalSourceManifest(value) {
493
263
  const label = 'historicalSourceManifest';
264
+ assertSafeHistoricalRecord(value, label);
494
265
  const raw = historyRecord(value, label);
495
266
  historyLiteral(raw.artifactVersion, 7, `${label}.artifactVersion`);
496
267
  const governance = historyRecord(raw.governance, `${label}.governance`);
@@ -525,7 +296,7 @@ function validateHistoricalCompatibility(value, label) {
525
296
  const tuples = historyArray(activation.currentCompatibleTuples, `${label}.activation.currentCompatibleTuples`);
526
297
  if (tuples.length !== 1)
527
298
  historyFail(label, 'requires the single registered historical activation tuple.');
528
- historicalIdentity(tuples[0], `${label}.activation.currentCompatibleTuples[0]`);
299
+ historicalV1Identity(tuples[0], `${label}.activation.currentCompatibleTuples[0]`);
529
300
  if (canonicalJson(activation.recognizedGraphHashes) !== canonicalJson([historicalActivationIdentity.phaseGraphHash]) ||
530
301
  canonicalJson(activation.graphMappings) !== '[]\n' || canonicalJson(activation.historicalStateMigrations) !== '[]\n') {
531
302
  historyFail(label, 'contains an unknown historical graph or migration declaration.');
@@ -557,6 +328,7 @@ function validateHistoricalCompatibility(value, label) {
557
328
  }
558
329
  function validateHistoricalMetadata(file) {
559
330
  const label = historyPathKey(file.pathParts);
331
+ assertSafeHistoricalBytes(file.content, label);
560
332
  if (!label.endsWith('.json'))
561
333
  return;
562
334
  const value = parseHistoryJson(file.content, label);
@@ -564,7 +336,8 @@ function validateHistoricalMetadata(file) {
564
336
  // It is preserved as source bytes, never used to authorize a migration lane.
565
337
  if (label === '.liftoff/governance/phase-graph.json') {
566
338
  const digest = canonicalSha256(value);
567
- if (digest !== historicalActivationIdentity.phaseGraphHash && digest !== currentActivationIdentity.phaseGraphHash) {
339
+ if (!historicalActivationIdentities.some((identity) => identity.phaseGraphHash === digest) &&
340
+ digest !== currentActivationIdentity.phaseGraphHash) {
568
341
  historyFail(label, 'is neither the registered historical nor the installed current managed graph.', 'unsupported-historical-graph');
569
342
  }
570
343
  }
@@ -572,6 +345,9 @@ function validateHistoricalMetadata(file) {
572
345
  if (isRecord(value) && value.schemaVersion === 1) {
573
346
  validateHistoricalCompatibility(value, label);
574
347
  }
348
+ else if (isRecord(value) && (value.schemaVersion === 2 || value.schemaVersion === 3)) {
349
+ validateHistoricalV2Compatibility(value, label);
350
+ }
575
351
  else {
576
352
  try {
577
353
  const metadata = validateGovernanceCompatibilityMetadata(value);
@@ -601,7 +377,7 @@ function validateHistoricalMetadata(file) {
601
377
  if (historyArray(context.commands, `${label}.commands`).length === 0)
602
378
  historyFail(label, 'context must declare its generated commands.');
603
379
  historyRecord(context.generatedBoundaries, `${label}.generatedBoundaries`);
604
- validateEmbeddedPaths(context, label);
380
+ validateHistoricalEmbeddedPaths(context, label);
605
381
  }
606
382
  else if (label === '.liftoff/governance/credential-policy.schema.json') {
607
383
  const schema = historyRecord(value, label);
@@ -614,7 +390,8 @@ function validateHistoricalMetadata(file) {
614
390
  return [key, declaration.const];
615
391
  }));
616
392
  const identityDigest = canonicalSha256(declaredIdentity);
617
- if (identityDigest !== canonicalSha256(historicalActivationIdentity) && identityDigest !== canonicalSha256(currentActivationIdentity)) {
393
+ if (!historicalActivationIdentities.some((identity) => identityDigest === canonicalSha256(identity)) &&
394
+ identityDigest !== canonicalSha256(currentActivationIdentity)) {
618
395
  historyFail(label, 'credential schema must declare the exact historical or installed current activation identity.', 'invalid-managed-source-metadata');
619
396
  }
620
397
  }
@@ -626,6 +403,7 @@ function sourceFile(snapshot, kind) {
626
403
  if (snapshot.content === undefined || snapshot.mode === undefined) {
627
404
  historyFail(historyPathKey(snapshot.pathParts), 'required historical source is missing.', 'missing-historical-record');
628
405
  }
406
+ assertSafeHistoricalBytes(snapshot.content, historyPathKey(snapshot.pathParts));
629
407
  return {
630
408
  kind, pathParts: [...snapshot.pathParts], content: snapshot.content,
631
409
  digest: rawHistoryDigest(snapshot.content), mode: snapshot.mode
@@ -657,21 +435,86 @@ function validateAt(file, validator) {
657
435
  return historyFail(historyPathKey(file.pathParts), error.message, error.code);
658
436
  }
659
437
  }
438
+ export function validateReadableHistoricalActivationState(value) {
439
+ const item = historyRecord(value, 'historicalActivationState');
440
+ return isHistoricalV2ActivationIdentity(item.identity)
441
+ ? validateHistoricalV2ActivationState(value) : validateHistoricalActivationState(value);
442
+ }
660
443
  export async function readHistoricalActivationInventory(projectRoot, options = {}) {
444
+ return readHistoricalInventory((parts) => captureHistoryFile(projectRoot, parts), (directory) => historicalActiveRecordPaths(projectRoot, directory), options);
445
+ }
446
+ /** Read only the exact stored copies in an index; never substitute current active files. */
447
+ export async function readHistoricalSnapshotInventory(projectRoot, index) {
448
+ const byPath = new Map(index.files.map((file) => [historyPathKey(file.originalPathParts), file]));
449
+ const inventory = await readHistoricalInventory(async (parts) => {
450
+ const file = byPath.get(historyPathKey(parts));
451
+ if (!file)
452
+ return { pathParts: [...parts] };
453
+ const copy = await captureHistoryFile(projectRoot, file.copyPathParts);
454
+ if (!copy.content || rawHistoryDigest(copy.content) !== file.digest) {
455
+ historyFail(historyPathKey(file.copyPathParts), 'declared copy is missing or differs from its recorded raw digest.', 'history-digest-mismatch');
456
+ }
457
+ return { pathParts: [...parts], content: copy.content, mode: file.mode };
458
+ }, async (directory) => index.files
459
+ .filter((file) => file.originalPathParts.length === 3 && file.originalPathParts[0] === 'governance' &&
460
+ file.originalPathParts[1] === directory).map((file) => [...file.originalPathParts]), {
461
+ reviewedUnreferencedPathParts: index.files
462
+ .filter((file) => ['evidence', 'plan', 'approval'].includes(file.kind)).map((file) => file.originalPathParts)
463
+ }, false);
464
+ if (canonicalSha256(inventory.state.identity) !== canonicalSha256(index.sourceIdentity) ||
465
+ canonicalSha256(inventory.files.map((file) => historyPathKey(file.pathParts)).sort()) !==
466
+ canonicalSha256(index.files.map((file) => historyPathKey(file.originalPathParts)).sort())) {
467
+ historyFail('activationHistoryIndex', 'source contract or complete record inventory contradicts its snapshot.', 'invalid-historical-reference');
468
+ }
469
+ return inventory;
470
+ }
471
+ async function readHistoricalInventory(captureSource, activeRecordPaths, options, requireSourceChange = true) {
661
472
  const preconditions = [];
662
473
  const capture = async (parts) => {
663
- const snapshot = await captureHistoryFile(projectRoot, parts);
474
+ const snapshot = await captureSource(parts);
664
475
  preconditions.push(snapshot);
665
476
  return snapshot;
666
477
  };
667
478
  const manifestFile = sourceFile(await capture(historicalManifestPathParts), 'manifest');
668
479
  const stateFile = sourceFile(await capture(historicalActivationStatePathParts), 'state');
669
480
  const manifest = validateAt(manifestFile, validateHistoricalSourceManifest);
670
- const state = validateAt(stateFile, validateHistoricalActivationState);
481
+ const state = validateAt(stateFile, validateReadableHistoricalActivationState);
482
+ if (!('activationIdentity' in manifest.governance) ||
483
+ canonicalSha256(manifest.governance.activationIdentity) !== canonicalSha256(state.identity)) {
484
+ historyFail(historyPathKey(stateFile.pathParts), 'active state and manifest declare different source contracts.', 'mixed-active-identity');
485
+ }
671
486
  if (state.activeChange !== null && state.activeChange.kind !== manifest.project.specWorkflow) {
672
487
  historyFail(historyPathKey(stateFile.pathParts), 'active change belongs to a different spec workflow.', 'historical-spec-ownership-conflict');
673
488
  }
674
489
  const files = [manifestFile, stateFile];
490
+ let sourceMigration;
491
+ const migrationSnapshot = await capture(migrationStateFilePathParts);
492
+ if (migrationSnapshot.content !== undefined) {
493
+ if (state.schemaVersion !== 2)
494
+ historyFail(historyPathKey(migrationStateFilePathParts), 'v1 active state cannot have a committed v2 source migration.', 'mixed-active-identity');
495
+ const file = sourceFile(migrationSnapshot, 'migration');
496
+ sourceMigration = validateAt(file, validateHistoricalV2SourceMigrationJournal);
497
+ if (sourceMigration.successor.repositoryId !== state.repository.id || sourceMigration.successor.createdAt !== state.createdAt) {
498
+ historyFail(historyPathKey(file.pathParts), 'source migration does not identify this active v2 successor.', 'invalid-historical-reference');
499
+ }
500
+ files.push(file);
501
+ }
502
+ let sourceChangeMetadata;
503
+ if (state.activeChange) {
504
+ const base = historicalSourceChangePathParts(state.activeChange);
505
+ const metadataSnapshot = await capture([...base, 'liftoff-governance.json']);
506
+ const tasksSnapshot = await capture([...base, 'tasks.md']);
507
+ if (requireSourceChange || metadataSnapshot.content !== undefined || tasksSnapshot.content !== undefined) {
508
+ const metadataFile = sourceFile(metadataSnapshot, 'source-metadata');
509
+ const tasksFile = sourceFile(tasksSnapshot, 'source-tasks');
510
+ sourceChangeMetadata = validateAt(metadataFile, validateHistoricalGovernanceChangeMetadata);
511
+ if (sourceChangeMetadata.changeId !== state.activeChange.id || sourceChangeMetadata.workflowKind !== state.activeChange.kind ||
512
+ ![state.identity, ...(sourceMigration ? [sourceMigration.sourceIdentity] : [])].some((identity) => canonicalSha256(identity) === canonicalSha256(sourceChangeMetadata.activationIdentity))) {
513
+ historyFail(historyPathKey(metadataFile.pathParts), 'metadata does not match the recorded historical source change.', 'invalid-historical-reference');
514
+ }
515
+ files.push(metadataFile, tasksFile);
516
+ }
517
+ }
675
518
  for (const parts of historicalMetadataPathParts) {
676
519
  const snapshot = await capture(parts);
677
520
  if (snapshot.content === undefined)
@@ -684,12 +527,13 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
684
527
  const plans = [];
685
528
  const approvals = new Map();
686
529
  for (const [directory, kind] of [['evidence', 'evidence'], ['plans', 'plan'], ['approvals', 'approval']]) {
687
- for (const parts of await historicalActiveRecordPaths(projectRoot, directory)) {
530
+ for (const parts of await activeRecordPaths(directory)) {
688
531
  const file = sourceFile(await capture(parts), kind);
689
532
  if (!parts[2].endsWith('.json'))
690
533
  historyFail(historyPathKey(parts), 'active record extension is not the registered lowercase .json layout.');
691
534
  if (kind === 'evidence') {
692
- const record = validateAt(file, (value) => validateHistoricalEvidenceRecord(value, parts[2].slice(0, -5)));
535
+ const record = validateAt(file, (value) => state.schemaVersion === 2
536
+ ? validateHistoricalV2EvidenceRecord(value) : validateHistoricalEvidenceRecord(value, parts[2].slice(0, -5)));
693
537
  if (record.header.repositoryId !== state.repository.id)
694
538
  historyFail(historyPathKey(parts), 'historical evidence belongs to another repository.', 'invalid-historical-reference');
695
539
  if (evidence.has(record.evidenceId))
@@ -697,24 +541,44 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
697
541
  evidence.set(record.evidenceId, { file, record });
698
542
  }
699
543
  else if (kind === 'plan') {
700
- plans.push({ file, record: validateAt(file, validateHistoricalSavedTransitionPlan) });
544
+ plans.push({ file, record: validateAt(file, (value) => state.schemaVersion === 2
545
+ ? validateHistoricalV2SavedTransitionPlan(value) : validateHistoricalSavedTransitionPlan(value)) });
701
546
  }
702
547
  else {
703
- const record = validateAt(file, validateHistoricalApprovalEnvelope);
548
+ const record = validateAt(file, (value) => state.schemaVersion === 2
549
+ ? validateHistoricalV2ApprovalEnvelope(value) : validateHistoricalApprovalEnvelope(value));
704
550
  if (approvals.has(record.id))
705
551
  historyFail(historyPathKey(parts), 'duplicates an approval identity.');
706
552
  approvals.set(record.id, { file, record });
707
553
  }
708
554
  }
709
555
  }
710
- for (const directory of ['supersessions', 'reconciliation']) {
711
- const records = await historicalActiveRecordPaths(projectRoot, directory);
712
- if (records.length > 0)
713
- historyFail(historyPathKey(records[0]), 'active historical auxiliary proof has no registered successor retirement contract; preserve it for explicit reconciliation.', 'unsupported-active-record');
556
+ for (const [directory, kind] of [['supersessions', 'supersession'], ['reconciliation', 'reconciliation']]) {
557
+ const records = await activeRecordPaths(directory);
558
+ for (const parts of records) {
559
+ const file = sourceFile(await capture(parts), kind);
560
+ try {
561
+ validateAt(file, (value) => state.schemaVersion === 1
562
+ ? validateHistoricalV1AuxiliaryRecord(value, kind) : validateHistoricalV2AuxiliaryRecord(value, kind));
563
+ }
564
+ catch (error) {
565
+ if (!(error instanceof ActivationHistoryError) || error.code !== 'invalid-history-record')
566
+ throw error;
567
+ historyFail(historyPathKey(parts), error.message, 'unsupported-active-record');
568
+ }
569
+ files.push(file);
570
+ }
714
571
  }
715
572
  for (const parts of [['governance', 'credentials', 'preflight-policy.json'], ['governance', 'activation-baseline.json']]) {
716
- if ((await capture(parts)).content !== undefined) {
717
- historyFail(historyPathKey(parts), 'active auxiliary proof has no registered v1 retirement contract; it cannot be silently carried forward.', 'unsupported-active-record');
573
+ const snapshot = await capture(parts);
574
+ if (snapshot.content !== undefined) {
575
+ if (parts[1] === 'activation-baseline.json') {
576
+ historyFail(historyPathKey(parts), 'active auxiliary proof has no registered source retirement contract; it cannot be silently carried forward.', 'unsupported-active-record');
577
+ }
578
+ const file = sourceFile(snapshot, 'credential-policy');
579
+ validateAt(file, (value) => state.schemaVersion === 1
580
+ ? validateHistoricalV1AuxiliaryRecord(value, 'credential-policy') : validateHistoricalV2AuxiliaryRecord(value, 'credential-policy'));
581
+ files.push(file);
718
582
  }
719
583
  }
720
584
  const selected = new Set();
@@ -723,7 +587,9 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
723
587
  const found = approvals.get(id);
724
588
  if (!found)
725
589
  historyFail(`governance/approvals/${id}`, 'referenced historical approval is missing.', 'missing-historical-record');
726
- if (found.record.phaseId !== expectedPhase || expectedHash !== undefined && historicalApprovalEnvelopeHash(found.record) !== expectedHash) {
590
+ const scopeHash = found.record.schemaVersion === 1
591
+ ? historicalApprovalEnvelopeHash(found.record) : historicalV2ApprovalEnvelopeHash(found.record);
592
+ if (found.record.phaseId !== expectedPhase || expectedHash !== undefined && scopeHash !== expectedHash) {
727
593
  historyFail(historyPathKey(found.file.pathParts), 'approval phase or scope hash contradicts its reference.', 'invalid-historical-reference');
728
594
  }
729
595
  select(found.file);
@@ -758,6 +624,10 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
758
624
  phase.state === 'approved' && phase.approvals.length === 0) {
759
625
  historyFail(`governance/activation-state.json#phases.${id}`, 'terminal historical state has no required record references.', 'missing-historical-record');
760
626
  }
627
+ if (['verified', 'inapplicable', 'retained', 'disposed'].includes(phase.state) &&
628
+ !phase.evidence.some((reference) => reference.result === phase.state)) {
629
+ historyFail(`governance/activation-state.json#phases.${id}`, 'terminal historical state has no matching result reference.', 'invalid-historical-reference');
630
+ }
761
631
  for (const ref of phase.evidence) {
762
632
  const found = evidence.get(ref.evidenceId);
763
633
  if (!found)
@@ -777,6 +647,20 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
777
647
  canonicalSha256(retained.record.header) !== state.bootstrapState.remoteImportEvidenceDigest) {
778
648
  historyFail('governance/activation-state.json#bootstrapState', 'remote import evidence reference is missing or inconsistent.', 'invalid-historical-reference');
779
649
  }
650
+ if (sourceMigration) {
651
+ for (const phase of sourceMigration.revalidation.phases) {
652
+ if (phase.status !== 'complete')
653
+ continue;
654
+ for (const id of phase.evidenceIds) {
655
+ const found = evidence.get(id);
656
+ if (!found || found.record.header.phaseId !== phase.phaseId || found.record.header.result !== 'verified' ||
657
+ !state.phases[phase.phaseId].evidence.some((reference) => reference.evidenceId === id &&
658
+ reference.headerDigest === canonicalSha256(found.record.header))) {
659
+ historyFail(historyPathKey(migrationStateFilePathParts), 'completed source revalidation has a missing or contradictory historical proof link.', 'invalid-historical-reference');
660
+ }
661
+ }
662
+ }
663
+ }
780
664
  selectEvidence(retained);
781
665
  if (state.bootstrapState.deletionEvidenceId !== undefined) {
782
666
  const deletion = evidence.get(state.bootstrapState.deletionEvidenceId);
@@ -812,6 +696,9 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
812
696
  unreviewedRecords.push(entry.file);
813
697
  }
814
698
  files.sort((a, b) => historyPathKey(a.pathParts) < historyPathKey(b.pathParts) ? -1 : 1);
815
- return { manifest, state, files, unreviewedRecords, preconditions };
699
+ return {
700
+ manifest, state, ...(sourceMigration ? { sourceMigration } : {}),
701
+ ...(sourceChangeMetadata ? { sourceChangeMetadata } : {}), files, unreviewedRecords, preconditions
702
+ };
816
703
  }
817
704
  //# sourceMappingURL=historical-state.js.map