gate-evm-tools 1.0.31 → 1.2.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 (349) hide show
  1. package/README.md +34 -20
  2. package/bin/cli.js +3 -0
  3. package/bin/delivery.cjs +8 -7
  4. package/dist/commands/check.d.ts.map +1 -1
  5. package/dist/commands/check.js +26 -2
  6. package/dist/commands/check.js.map +1 -1
  7. package/dist/commands/plan/index.d.ts.map +1 -1
  8. package/dist/commands/plan/index.js +1 -1
  9. package/dist/commands/plan/index.js.map +1 -1
  10. package/dist/core/preparation/batch.d.ts +26 -0
  11. package/dist/core/preparation/batch.d.ts.map +1 -0
  12. package/dist/core/preparation/batch.js +72 -0
  13. package/dist/core/preparation/batch.js.map +1 -0
  14. package/dist/core/preparation/evm.d.ts +23 -4
  15. package/dist/core/preparation/evm.d.ts.map +1 -1
  16. package/dist/core/preparation/evm.js +19 -3
  17. package/dist/core/preparation/evm.js.map +1 -1
  18. package/dist/core/preparation/evmManifestSync.d.ts +5 -0
  19. package/dist/core/preparation/evmManifestSync.d.ts.map +1 -1
  20. package/dist/core/preparation/evmManifestSync.js +15 -7
  21. package/dist/core/preparation/evmManifestSync.js.map +1 -1
  22. package/dist/core/preparation/index.d.ts +2 -2
  23. package/dist/core/preparation/index.d.ts.map +1 -1
  24. package/dist/core/preparation/manifest.d.ts +2 -0
  25. package/dist/core/preparation/manifest.d.ts.map +1 -1
  26. package/dist/core/preparation/manifest.js +2 -2
  27. package/dist/core/preparation/manifest.js.map +1 -1
  28. package/dist/core/preparation/normalize.d.ts.map +1 -1
  29. package/dist/core/preparation/normalize.js +53 -11
  30. package/dist/core/preparation/normalize.js.map +1 -1
  31. package/dist/core/preparation/transparent.d.ts +23 -0
  32. package/dist/core/preparation/transparent.d.ts.map +1 -0
  33. package/dist/core/preparation/transparent.js +103 -0
  34. package/dist/core/preparation/transparent.js.map +1 -0
  35. package/dist/core/preparation/types.d.ts +47 -5
  36. package/dist/core/preparation/types.d.ts.map +1 -1
  37. package/dist/core/preparation/zksync.d.ts.map +1 -1
  38. package/dist/core/preparation/zksync.js +2 -0
  39. package/dist/core/preparation/zksync.js.map +1 -1
  40. package/dist/custody/credential.d.ts.map +1 -1
  41. package/dist/custody/credential.js +1 -6
  42. package/dist/custody/credential.js.map +1 -1
  43. package/dist/deployment-evm/index.d.ts +4 -0
  44. package/dist/deployment-evm/index.d.ts.map +1 -0
  45. package/dist/deployment-evm/index.js +3 -0
  46. package/dist/deployment-evm/index.js.map +1 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +4 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/sync/index.d.ts.map +1 -1
  51. package/dist/sync/index.js +5 -0
  52. package/dist/sync/index.js.map +1 -1
  53. package/dist/types.d.ts +3 -0
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/workflow/index.d.ts +4 -1
  56. package/dist/workflow/index.d.ts.map +1 -1
  57. package/dist/workflow/index.js +2 -0
  58. package/dist/workflow/index.js.map +1 -1
  59. package/dist-cjs/core/preparation/batch.js +76 -0
  60. package/dist-cjs/core/preparation/batch.js.map +1 -0
  61. package/dist-cjs/core/preparation/evm.js +25 -7
  62. package/dist-cjs/core/preparation/evm.js.map +1 -1
  63. package/dist-cjs/core/preparation/evmManifestSync.js +15 -7
  64. package/dist-cjs/core/preparation/evmManifestSync.js.map +1 -1
  65. package/dist-cjs/core/preparation/manifest.js +2 -2
  66. package/dist-cjs/core/preparation/manifest.js.map +1 -1
  67. package/dist-cjs/core/preparation/normalize.js +53 -11
  68. package/dist-cjs/core/preparation/normalize.js.map +1 -1
  69. package/dist-cjs/core/preparation/transparent.js +110 -0
  70. package/dist-cjs/core/preparation/transparent.js.map +1 -0
  71. package/dist-cjs/core/preparation/zksync.js +2 -0
  72. package/dist-cjs/core/preparation/zksync.js.map +1 -1
  73. package/dist-cjs/custody/credential.js +1 -6
  74. package/dist-cjs/custody/credential.js.map +1 -1
  75. package/dist-cjs/deployment-evm/index.js +10 -0
  76. package/dist-cjs/deployment-evm/index.js.map +1 -0
  77. package/dist-cjs/sync/index.js +5 -0
  78. package/dist-cjs/sync/index.js.map +1 -1
  79. package/dist-cjs/workflow/index.js +11 -1
  80. package/dist-cjs/workflow/index.js.map +1 -1
  81. package/dist-runtime/config/hardhatAdapters.js +2 -1
  82. package/dist-runtime/config/hardhatAdapters.js.map +1 -1
  83. package/dist-runtime/delivery/aggregate-bootstrap.js +3 -1
  84. package/dist-runtime/delivery/aggregate-bootstrap.js.map +1 -1
  85. package/dist-runtime/delivery/aggregate-channel.js +7 -3
  86. package/dist-runtime/delivery/aggregate-channel.js.map +1 -1
  87. package/dist-runtime/delivery/aggregate-diagnostics.d.ts +14 -0
  88. package/dist-runtime/delivery/aggregate-diagnostics.js +73 -0
  89. package/dist-runtime/delivery/aggregate-diagnostics.js.map +1 -0
  90. package/dist-runtime/delivery/aggregate-inputs.d.ts +13 -9
  91. package/dist-runtime/delivery/aggregate-source.js +11 -3
  92. package/dist-runtime/delivery/aggregate-source.js.map +1 -1
  93. package/dist-runtime/delivery/aggregate-worker.js +12 -4
  94. package/dist-runtime/delivery/aggregate-worker.js.map +1 -1
  95. package/dist-runtime/delivery/aggregate.js +2 -2
  96. package/dist-runtime/delivery/aggregate.js.map +1 -1
  97. package/dist-runtime/delivery/business-extension.d.ts +10 -0
  98. package/dist-runtime/delivery/business-extension.js +38 -0
  99. package/dist-runtime/delivery/business-extension.js.map +1 -0
  100. package/dist-runtime/delivery/calldata-archive.d.ts +18 -0
  101. package/dist-runtime/delivery/calldata-archive.js +31 -0
  102. package/dist-runtime/delivery/calldata-archive.js.map +1 -0
  103. package/dist-runtime/delivery/calldata-details.d.ts +47 -0
  104. package/dist-runtime/delivery/calldata-details.js +94 -0
  105. package/dist-runtime/delivery/calldata-details.js.map +1 -0
  106. package/dist-runtime/delivery/calldata-format.d.ts +4 -0
  107. package/dist-runtime/delivery/calldata-format.js +17 -0
  108. package/dist-runtime/delivery/calldata-format.js.map +1 -0
  109. package/dist-runtime/delivery/calldata-preparations.d.ts +13 -0
  110. package/dist-runtime/delivery/calldata-preparations.js +85 -0
  111. package/dist-runtime/delivery/calldata-preparations.js.map +1 -0
  112. package/dist-runtime/delivery/calldata-review.d.ts +44 -0
  113. package/dist-runtime/delivery/calldata-review.js +131 -0
  114. package/dist-runtime/delivery/calldata-review.js.map +1 -0
  115. package/dist-runtime/delivery/codec-pin.d.ts +2 -2
  116. package/dist-runtime/delivery/codec-pin.js +4 -4
  117. package/dist-runtime/delivery/codec-pin.js.map +1 -1
  118. package/dist-runtime/delivery/compiler-files.js +3 -1
  119. package/dist-runtime/delivery/compiler-files.js.map +1 -1
  120. package/dist-runtime/delivery/compiler-pins.js +1 -1
  121. package/dist-runtime/delivery/compiler.js +17 -16
  122. package/dist-runtime/delivery/compiler.js.map +1 -1
  123. package/dist-runtime/delivery/config.d.ts +1 -1
  124. package/dist-runtime/delivery/config.js +27 -2
  125. package/dist-runtime/delivery/config.js.map +1 -1
  126. package/dist-runtime/delivery/configuration-definition.d.ts +9 -0
  127. package/dist-runtime/delivery/configuration-definition.js +33 -6
  128. package/dist-runtime/delivery/configuration-definition.js.map +1 -1
  129. package/dist-runtime/delivery/configuration.js +7 -4
  130. package/dist-runtime/delivery/configuration.js.map +1 -1
  131. package/dist-runtime/delivery/contract-source.d.ts +14 -0
  132. package/dist-runtime/delivery/contract-source.js +138 -0
  133. package/dist-runtime/delivery/contract-source.js.map +1 -0
  134. package/dist-runtime/delivery/custom-format.js +51 -24
  135. package/dist-runtime/delivery/custom-format.js.map +1 -1
  136. package/dist-runtime/delivery/development-inputs.d.ts +14 -0
  137. package/dist-runtime/delivery/development-inputs.js +76 -0
  138. package/dist-runtime/delivery/development-inputs.js.map +1 -0
  139. package/dist-runtime/delivery/development.d.ts +3 -1
  140. package/dist-runtime/delivery/development.js +24 -10
  141. package/dist-runtime/delivery/development.js.map +1 -1
  142. package/dist-runtime/delivery/eoa.d.ts +1 -0
  143. package/dist-runtime/delivery/execution-identity.d.ts +15 -0
  144. package/dist-runtime/delivery/execution-identity.js +33 -0
  145. package/dist-runtime/delivery/execution-identity.js.map +1 -0
  146. package/dist-runtime/delivery/existing-code.js +14 -6
  147. package/dist-runtime/delivery/existing-code.js.map +1 -1
  148. package/dist-runtime/delivery/history-package.d.ts +2 -0
  149. package/dist-runtime/delivery/history-package.js +70 -0
  150. package/dist-runtime/delivery/history-package.js.map +1 -0
  151. package/dist-runtime/delivery/history-registration.d.ts +1 -1
  152. package/dist-runtime/delivery/inputs.js +22 -15
  153. package/dist-runtime/delivery/inputs.js.map +1 -1
  154. package/dist-runtime/delivery/journal.js +18 -10
  155. package/dist-runtime/delivery/journal.js.map +1 -1
  156. package/dist-runtime/delivery/parameter-defaults.d.ts +10 -0
  157. package/dist-runtime/delivery/parameter-defaults.js +86 -0
  158. package/dist-runtime/delivery/parameter-defaults.js.map +1 -0
  159. package/dist-runtime/delivery/parameters.d.ts +2 -0
  160. package/dist-runtime/delivery/parameters.js +22 -0
  161. package/dist-runtime/delivery/parameters.js.map +1 -1
  162. package/dist-runtime/delivery/planning.d.ts +4 -1
  163. package/dist-runtime/delivery/planning.js +19 -3
  164. package/dist-runtime/delivery/planning.js.map +1 -1
  165. package/dist-runtime/delivery/preparation.js +43 -23
  166. package/dist-runtime/delivery/preparation.js.map +1 -1
  167. package/dist-runtime/delivery/production-environment.js +2 -1
  168. package/dist-runtime/delivery/production-environment.js.map +1 -1
  169. package/dist-runtime/delivery/production-finalization.js +23 -9
  170. package/dist-runtime/delivery/production-finalization.js.map +1 -1
  171. package/dist-runtime/delivery/production-intent.d.ts +2 -0
  172. package/dist-runtime/delivery/production-intent.js +16 -0
  173. package/dist-runtime/delivery/production-intent.js.map +1 -0
  174. package/dist-runtime/delivery/production.d.ts +129 -8
  175. package/dist-runtime/delivery/production.js +60 -11
  176. package/dist-runtime/delivery/production.js.map +1 -1
  177. package/dist-runtime/delivery/project-files.d.ts +3 -3
  178. package/dist-runtime/delivery/project-files.js +19 -14
  179. package/dist-runtime/delivery/project-files.js.map +1 -1
  180. package/dist-runtime/delivery/project-rpc.d.ts +19 -0
  181. package/dist-runtime/delivery/project-rpc.js +138 -0
  182. package/dist-runtime/delivery/project-rpc.js.map +1 -0
  183. package/dist-runtime/delivery/project.d.ts +3 -1
  184. package/dist-runtime/delivery/project.js +21 -8
  185. package/dist-runtime/delivery/project.js.map +1 -1
  186. package/dist-runtime/delivery/public-calldata.d.ts +25 -0
  187. package/dist-runtime/delivery/public-calldata.js +97 -0
  188. package/dist-runtime/delivery/public-calldata.js.map +1 -0
  189. package/dist-runtime/delivery/release-graph.d.ts +7 -3
  190. package/dist-runtime/delivery/release-graph.js +58 -25
  191. package/dist-runtime/delivery/release-graph.js.map +1 -1
  192. package/dist-runtime/delivery/release-history-capture.js +22 -26
  193. package/dist-runtime/delivery/release-history-capture.js.map +1 -1
  194. package/dist-runtime/delivery/release-history.d.ts +3 -2
  195. package/dist-runtime/delivery/release-history.js +97 -50
  196. package/dist-runtime/delivery/release-history.js.map +1 -1
  197. package/dist-runtime/delivery/release-inputs.d.ts +33 -0
  198. package/dist-runtime/delivery/release-inputs.js +129 -0
  199. package/dist-runtime/delivery/release-inputs.js.map +1 -0
  200. package/dist-runtime/delivery/release.d.ts +38 -14
  201. package/dist-runtime/delivery/release.js +81 -29
  202. package/dist-runtime/delivery/release.js.map +1 -1
  203. package/dist-runtime/delivery/safe.d.ts +1 -0
  204. package/dist-runtime/delivery/safe.js +13 -2
  205. package/dist-runtime/delivery/safe.js.map +1 -1
  206. package/dist-runtime/delivery/same-fork-baseline.d.ts +1 -1
  207. package/dist-runtime/delivery/same-fork-baseline.js +5 -4
  208. package/dist-runtime/delivery/same-fork-baseline.js.map +1 -1
  209. package/dist-runtime/delivery/setup.d.ts +1 -1
  210. package/dist-runtime/delivery/setup.js +21 -6
  211. package/dist-runtime/delivery/setup.js.map +1 -1
  212. package/dist-runtime/delivery/source.d.ts +1 -0
  213. package/dist-runtime/delivery/source.js +12 -10
  214. package/dist-runtime/delivery/source.js.map +1 -1
  215. package/dist-runtime/delivery/task-identity.d.ts +23 -0
  216. package/dist-runtime/delivery/task-identity.js +109 -0
  217. package/dist-runtime/delivery/task-identity.js.map +1 -0
  218. package/dist-runtime/delivery/toolchain.d.ts +3 -0
  219. package/dist-runtime/delivery/toolchain.js +52 -0
  220. package/dist-runtime/delivery/toolchain.js.map +1 -0
  221. package/dist-runtime/delivery/validation.js +7 -2
  222. package/dist-runtime/delivery/validation.js.map +1 -1
  223. package/dist-runtime/runtime-identity.json +2 -2
  224. package/dist-runtime/vendor/plan-codec/index.cjs +97 -20
  225. package/dist-runtime/vendor/plan-codec/index.d.cts +6 -0
  226. package/dist-runtime/vendor/plan-codec/provenance.json +9 -8
  227. package/dist-runtime/workflow/lib/calls.js +1 -0
  228. package/dist-runtime/workflow/lib/calls.js.map +1 -1
  229. package/dist-runtime/workflow/lib/checkCodeRunner.js +7 -4
  230. package/dist-runtime/workflow/lib/checkCodeRunner.js.map +1 -1
  231. package/dist-runtime/workflow/lib/context.d.ts +1 -0
  232. package/dist-runtime/workflow/lib/create2.d.ts +4 -26
  233. package/dist-runtime/workflow/lib/create2.js +2 -67
  234. package/dist-runtime/workflow/lib/create2.js.map +1 -1
  235. package/dist-runtime/workflow/lib/deploymentInfo.d.ts +2 -0
  236. package/dist-runtime/workflow/lib/deploymentInfo.js +6 -0
  237. package/dist-runtime/workflow/lib/deploymentInfo.js.map +1 -1
  238. package/dist-runtime/workflow/lib/deploymentRecords.d.ts +2 -0
  239. package/dist-runtime/workflow/lib/deploymentRecords.js +1 -1
  240. package/dist-runtime/workflow/lib/deploymentRecords.js.map +1 -1
  241. package/dist-runtime/workflow/lib/externalExecutionScanner.js +2 -1
  242. package/dist-runtime/workflow/lib/externalExecutionScanner.js.map +1 -1
  243. package/dist-runtime/workflow/lib/gateTool.js +2 -2
  244. package/dist-runtime/workflow/lib/gateTool.js.map +1 -1
  245. package/dist-runtime/workflow/lib/gateToolSync.d.ts +3 -0
  246. package/dist-runtime/workflow/lib/gateToolSync.js +5 -0
  247. package/dist-runtime/workflow/lib/gateToolSync.js.map +1 -1
  248. package/dist-runtime/workflow/lib/parameters.js +5 -0
  249. package/dist-runtime/workflow/lib/parameters.js.map +1 -1
  250. package/dist-runtime/workflow/lib/proxy.d.ts +1 -0
  251. package/dist-runtime/workflow/lib/proxy.js.map +1 -1
  252. package/dist-runtime/workflow/run.js +1 -3
  253. package/dist-runtime/workflow/run.js.map +1 -1
  254. package/dist-runtime/workflow/steps/deploy.js +53 -0
  255. package/dist-runtime/workflow/steps/deploy.js.map +1 -1
  256. package/dist-runtime/workflow/steps/upgrade.js +24 -10
  257. package/dist-runtime/workflow/steps/upgrade.js.map +1 -1
  258. package/docs/compiler-qualification.md +8 -2
  259. package/docs/delivery-aggregate.md +2 -0
  260. package/docs/delivery-calldata.md +27 -0
  261. package/docs/delivery-development.md +6 -0
  262. package/docs/delivery-execution-store.md +1 -1
  263. package/docs/delivery-inputs.md +55 -6
  264. package/docs/delivery-parameters.md +1 -1
  265. package/docs/delivery-release.md +77 -7
  266. package/docs/delivery-safe.md +7 -4
  267. package/docs/delivery-setup.md +72 -20
  268. package/docs/delivery-source.md +8 -8
  269. package/docs/direct-package-acceptance.md +2 -0
  270. package/docs/openzeppelin-acceptance.md +86 -0
  271. package/docs/openzeppelin-baseline.json +27 -0
  272. package/docs/openzeppelin-integration.md +115 -0
  273. package/docs/plans/2026-10-28-openzeppelin-toolkit-consolidation.md +396 -0
  274. package/docs/plans/2026-11-02-transparent-proxy-delivery.md +78 -0
  275. package/docs/superpowers/plans/2026-08-17-custody-hardhat-signers.md +1 -1
  276. package/docs/workflow-reference.md +12 -4
  277. package/docs/workflow-runtime.md +6 -0
  278. package/package.json +19 -5
  279. package/runtime/config/hardhatAdapters.ts +2 -1
  280. package/runtime/delivery/aggregate-bootstrap.ts +3 -1
  281. package/runtime/delivery/aggregate-channel.ts +6 -3
  282. package/runtime/delivery/aggregate-diagnostics.ts +63 -0
  283. package/runtime/delivery/aggregate-source.ts +6 -2
  284. package/runtime/delivery/aggregate-worker.ts +15 -5
  285. package/runtime/delivery/aggregate.ts +2 -2
  286. package/runtime/delivery/business-extension.ts +33 -0
  287. package/runtime/delivery/calldata-archive.ts +25 -0
  288. package/runtime/delivery/calldata-details.ts +90 -0
  289. package/runtime/delivery/calldata-format.ts +14 -0
  290. package/runtime/delivery/calldata-preparations.ts +76 -0
  291. package/runtime/delivery/calldata-review.ts +126 -0
  292. package/runtime/delivery/codec-pin.ts +3 -3
  293. package/runtime/delivery/compiler-files.ts +3 -1
  294. package/runtime/delivery/compiler-pins.ts +1 -1
  295. package/runtime/delivery/compiler.ts +18 -17
  296. package/runtime/delivery/config.ts +25 -2
  297. package/runtime/delivery/configuration-definition.ts +34 -7
  298. package/runtime/delivery/configuration.ts +8 -5
  299. package/runtime/delivery/contract-source.ts +112 -0
  300. package/runtime/delivery/custom-format.ts +20 -5
  301. package/runtime/delivery/development-inputs.ts +42 -0
  302. package/runtime/delivery/development.ts +14 -11
  303. package/runtime/delivery/execution-identity.ts +32 -0
  304. package/runtime/delivery/existing-code.ts +7 -6
  305. package/runtime/delivery/history-package.ts +50 -0
  306. package/runtime/delivery/inputs.ts +18 -11
  307. package/runtime/delivery/journal.ts +14 -10
  308. package/runtime/delivery/parameter-defaults.ts +75 -0
  309. package/runtime/delivery/parameters.ts +18 -0
  310. package/runtime/delivery/planning.ts +19 -5
  311. package/runtime/delivery/preparation.ts +17 -1
  312. package/runtime/delivery/production-environment.ts +2 -1
  313. package/runtime/delivery/production-finalization.ts +19 -10
  314. package/runtime/delivery/production-intent.ts +14 -0
  315. package/runtime/delivery/production.ts +51 -11
  316. package/runtime/delivery/project-files.ts +16 -11
  317. package/runtime/delivery/project-rpc.ts +116 -0
  318. package/runtime/delivery/project.ts +19 -7
  319. package/runtime/delivery/public-calldata.ts +74 -0
  320. package/runtime/delivery/release-graph.ts +44 -14
  321. package/runtime/delivery/release-history-capture.ts +19 -9
  322. package/runtime/delivery/release-history.ts +76 -22
  323. package/runtime/delivery/release-inputs.ts +107 -0
  324. package/runtime/delivery/release.ts +56 -18
  325. package/runtime/delivery/safe.ts +14 -3
  326. package/runtime/delivery/same-fork-baseline.ts +5 -4
  327. package/runtime/delivery/setup.ts +20 -8
  328. package/runtime/delivery/source.ts +10 -10
  329. package/runtime/delivery/task-identity.ts +85 -0
  330. package/runtime/delivery/toolchain.ts +48 -0
  331. package/runtime/delivery/validation.ts +7 -2
  332. package/runtime/vendor/plan-codec/index.cjs +97 -20
  333. package/runtime/vendor/plan-codec/index.d.cts +6 -0
  334. package/runtime/vendor/plan-codec/provenance.json +9 -8
  335. package/runtime/workflow/lib/calls.ts +1 -0
  336. package/runtime/workflow/lib/checkCodeRunner.ts +7 -3
  337. package/runtime/workflow/lib/context.ts +1 -0
  338. package/runtime/workflow/lib/create2.ts +7 -108
  339. package/runtime/workflow/lib/deploymentInfo.ts +8 -0
  340. package/runtime/workflow/lib/deploymentRecords.ts +3 -1
  341. package/runtime/workflow/lib/externalExecutionScanner.ts +2 -1
  342. package/runtime/workflow/lib/gateTool.ts +2 -2
  343. package/runtime/workflow/lib/gateToolSync.ts +8 -0
  344. package/runtime/workflow/lib/parameters.ts +4 -0
  345. package/runtime/workflow/lib/proxy.ts +1 -0
  346. package/runtime/workflow/run.ts +1 -2
  347. package/runtime/workflow/steps/deploy.ts +42 -0
  348. package/runtime/workflow/steps/upgrade.ts +19 -11
  349. package/bin/workflow.cjs +0 -19
@@ -1,24 +1,33 @@
1
1
  /** Actual-file release graph classification. No caller-selected roots or target subsets. */
2
- import {parseCanonicalPlan,projectCanonicalDefinitions} from './source';
2
+ import {parseCanonicalPlan,projectCanonicalDefinitions,deploymentPurpose} from './source';
3
3
  import {projectRoot} from './project-files';
4
4
  import {readInputBytes,parseInputDocument,assertUnambiguousHandoff} from './input-files';
5
5
  import {canonicalJson,deepFreeze,semanticHash,exactObject} from '../workflow/lib/canonicalData';
6
- import {safeId,portablePath} from './protocol';
6
+ import {safeId,portablePath,object} from './protocol';
7
7
  export interface ReleaseGraphOptions {workspaceRoot:string;projectRoot:string;handoff:string;taskId:string;deploymentId:string;releaseRef:{path:string;releaseId:string;inputsHash:string}}
8
8
  export const sameReleaseData=(a:unknown,b:unknown,label:string):void=>{if(canonicalJson(a)!==canonicalJson(b))throw Error(`${label} mismatch`);};
9
- const stages=['release-prepare','release-execute','release-verify'];
9
+ /** A completed simulation-only script task supplies reports, never on-chain acceptance. */
10
+ export function isDevelopmentAcceptancePrerequisite(task:Parameters<typeof deploymentPurpose>[0]){
11
+ const d=task.scope?.deployment as any;
12
+ return deploymentPurpose(task)==='dev-execution'&&Array.isArray(d?.allowedActions)&&d.allowedActions.some((action:string)=>action!=='simulate');
13
+ }
10
14
  export function readReleaseUniverse(input:Omit<ReleaseGraphOptions,'releaseRef'>){
11
15
  const workspace=projectRoot(input.workspaceRoot,'.'),root=projectRoot(workspace,input.projectRoot);
12
16
  safeId(input.deploymentId,'deploymentId');portablePath(input.handoff,'handoff');
13
17
  const text=readInputBytes(workspace,input.handoff).toString();assertUnambiguousHandoff(text);
14
18
  const plan=parseCanonicalPlan(text);if(plan.taskId!==input.taskId)throw Error('Current task identity mismatch');
15
- const task=exactObject(parseInputDocument(readInputBytes(root,`scripts/tasks/${input.deploymentId}/task.yaml`),'yaml'),['version','id','developmentNetwork','releaseTargetNetworks'],'task.yaml');
19
+ const task=object(parseInputDocument(readInputBytes(root,`scripts/tasks/${input.deploymentId}/task.yaml`),'yaml'),'task.yaml',['version','id','developmentNetwork','releaseTargetNetworks','requirementRef','productionRequirementRef','referenceDeploymentId']);
16
20
  if(task.version!==1||task.id!==input.deploymentId||!Array.isArray(task.releaseTargetNetworks)||!task.releaseTargetNetworks.length)throw Error('Invalid release target scope');
17
21
  const targets=task.releaseTargetNetworks as string[];if(new Set(targets).size!==targets.length)throw Error('Duplicate release target');
18
22
  const entries=Object.entries(plan.tasks).flatMap(([lane,tasks])=>tasks.map(task=>({lane,task}))),relevant=entries.filter(({task:t})=>{const d=t.scope?.deployment as any;return d?.projectRoot===input.projectRoot&&d?.deploymentId===input.deploymentId;});
19
- for(const {task:t} of relevant){const d=t.scope!.deployment as any;sameReleaseData(d.developmentNetwork,task.developmentNetwork,'Current development target');sameReleaseData(d.releaseTargetNetworks,targets,'Current selected targets');}
20
- const qualifications=targets.map(target=>{const rows=relevant.filter(({task:t})=>t.step==='deploy-test'&&(t.scope!.deployment as any).targetNetwork===target);if(rows.length!==1)throw Error('Missing or ambiguous qualification target');const row=rows[0],d=row.task.scope!.deployment as any;if(plan.lanes[row.lane].state!=='enabled'||row.task.status==='cancelled'||d.environment!=='prod'||d.releaseRef!==undefined)throw Error('Ineligible qualification task');sameReleaseData(d.allowedActions,['simulate','verify'],'Qualification allowedActions');return row.task;});
21
- if(relevant.some(({task:t})=>t.step==='deploy-test'&&!targets.includes((t.scope!.deployment as any).targetNetwork)))throw Error('Unknown qualification target');
23
+ for(const {task:t} of relevant){const d=t.scope!.deployment as any;sameReleaseData(d.developmentNetwork,task.developmentNetwork,'Current development target');
24
+ if(['prod-qualification','prod-execution'].includes(deploymentPurpose(t)!)){
25
+ sameReleaseData(d.releaseTargetNetworks,targets,'Current selected targets');
26
+ sameReleaseData(d.productionRequirementRef??null,task.productionRequirementRef??null,'Current production confirmation');
27
+ }else if((d.releaseTargetNetworks??[]).some((network:string)=>!targets.includes(network)))throw Error('Current selected targets removed a development-declared target');
28
+ }
29
+ const qualifications=targets.map(target=>{const rows=relevant.filter(({task:t})=>deploymentPurpose(t)==='prod-qualification'&&(t.scope!.deployment as any).targetNetwork===target);if(rows.length!==1)throw Error('Missing or ambiguous qualification target');const row=rows[0],d=row.task.scope!.deployment as any;if(plan.lanes[row.lane].state!=='enabled'||row.task.status==='cancelled'||d.environment!=='prod'||d.releaseRef!==undefined)throw Error('Ineligible qualification task');sameReleaseData([...d.allowedActions].sort(),['simulate','verify'],'Qualification allowedActions');return row.task;});
30
+ if(relevant.some(({task:t})=>deploymentPurpose(t)==='prod-qualification'&&!targets.includes((t.scope!.deployment as any).targetNetwork)))throw Error('Unknown qualification target');
22
31
  const upstream=projectCanonicalDefinitions(text,{roots:qualifications.map(t=>t.planKey),mode:'upstream'});
23
32
  return {workspace,root,text,plan,entries,relevant,targets,qualifications,upstream,developmentNetwork:task.developmentNetwork as string};
24
33
  }
@@ -26,12 +35,31 @@ export function readReleaseGraph(input:ReleaseGraphOptions){
26
35
  const u=readReleaseUniverse(input);safeId(input.releaseRef.releaseId,'releaseId');portablePath(input.releaseRef.path,'releaseRef.path');if(!/^[a-f0-9]{64}$/.test(input.releaseRef.inputsHash))throw Error('Invalid release input hash');
27
36
  // Classify ALL relevant production tasks first. Filtering a good ref first conceals split/wrong bindings.
28
37
  const groups=new Map<string,typeof u.relevant>();
29
- for(const row of u.relevant.filter(({task})=>stages.includes(task.step??''))){const d=row.task.scope!.deployment as any,ref=d.releaseRef;if(!ref)throw Error('Missing release binding');const key=canonicalJson(ref),rows=groups.get(key)??[];rows.push(row);groups.set(key,rows);}
38
+ for(const row of u.relevant.filter(({task})=>deploymentPurpose(task)==='prod-execution')){const d=row.task.scope!.deployment as any,ref=d.releaseRef;if(!ref)throw Error('Missing release binding');const key=canonicalJson(ref),rows=groups.get(key)??[];rows.push(row);groups.set(key,rows);}
30
39
  const selectedKey=canonicalJson(input.releaseRef);
31
40
  for(const [key,rows] of groups){const ref=(rows[0].task.scope!.deployment as any).releaseRef;if(ref.releaseId===input.releaseRef.releaseId&&key!==selectedKey)throw Error('Split release binding');
32
- if(rows.length!==u.targets.length*3)throw Error('Incomplete or duplicate production stage graph');
33
- for(const target of u.targets){const chain=stages.map(step=>{const matches=rows.filter(({task})=>task.step===step&&(task.scope!.deployment as any).targetNetwork===target);if(matches.length!==1)throw Error('Missing or duplicate release stage');return matches[0];});
34
- for(const [index,{lane,task}] of chain.entries()){const d=task.scope!.deployment as any;if(u.plan.lanes[lane].state!=='enabled'||task.status==='cancelled'||d.environment!=='prod')throw Error('Ineligible production task');const required=index===0?u.qualifications.find(t=>(t.scope!.deployment as any).targetNetwork===target)!.planKey:chain[index-1].task.planKey;if(!task.dependsOn.includes(required))throw Error('Split release dependency graph');const allowed=index===0?['simulate']:index===1?['execute','export-calldata','reconcile']:['verify'];sameReleaseData(d.allowedActions,allowed,'Release allowedActions');}
41
+ if(rows.some(({task})=>!u.targets.includes((task.scope!.deployment as any).targetNetwork)))throw Error('Unknown production target');
42
+ const byKey=new Map(u.entries.map(row=>[row.task.planKey,row.task]));
43
+ const ancestors=(task:typeof rows[number]['task'],seen=new Set<string>()):Set<string>=>{for(const id of task.dependsOn){if(seen.has(id))continue;seen.add(id);const prior=byKey.get(id);if(prior)ancestors(prior,seen);}return seen;};
44
+ for(const target of u.targets){
45
+ const tasks=rows.filter(({task})=>(task.scope!.deployment as any).targetNetwork===target).sort((a,b)=>a.task.no-b.task.no);
46
+ if(!tasks.length)throw Error('Missing complete release stage graph');
47
+ const qualification=u.qualifications.find(t=>(t.scope!.deployment as any).targetNetwork===target)!;
48
+ const actions=(task:typeof rows[number]['task'])=>(task.scope!.deployment as any).allowedActions as string[];
49
+ const prepares=tasks.filter(({task})=>actions(task).includes('simulate'));
50
+ const effects=tasks.filter(({task})=>actions(task).some(a=>['execute','export-calldata'].includes(a)));
51
+ const verifies=tasks.filter(({task})=>actions(task).includes('verify'));
52
+ if(!prepares.length||!effects.length||!verifies.length)throw Error('Incomplete production stage graph: simulate, execute/export and verify required');
53
+ for(let index=1;index<tasks.length;index++)if(!ancestors(tasks[index].task).has(tasks[index-1].task.planKey))throw Error('Split or duplicate release dependency graph');
54
+ if(!actions(tasks.at(-1)!.task).includes('verify'))throw Error('Production graph must finish with verification');
55
+ for(const {lane,task} of tasks){
56
+ const d=task.scope!.deployment as any,upstream=ancestors(task);
57
+ if(u.plan.lanes[lane].state!=='enabled'||task.status==='cancelled'||d.environment!=='prod')throw Error('Ineligible production task');
58
+ if(actions(task).some(a=>['execute','export-calldata'].includes(a))&&!actions(task).some(a=>['reconcile','verify'].includes(a)))throw Error('Production effect task requires reconcile or verify to observe completion');
59
+ if(!upstream.has(qualification.planKey))throw Error('Split release dependency graph');
60
+ if(actions(task).some(a=>['execute','export-calldata'].includes(a))&&!prepares.some(p=>p.task.planKey===task.planKey||upstream.has(p.task.planKey)))throw Error('Missing production preparation dependency');
61
+ if(actions(task).includes('verify')&&effects.some(e=>e.task.planKey!==task.planKey&&!upstream.has(e.task.planKey)))throw Error('Missing production verification dependency');
62
+ }
35
63
  }
36
64
  }
37
65
  const selected=groups.get(selectedKey);if(!selected)throw Error('Missing complete release graph');
@@ -41,9 +69,11 @@ export function readReleaseGraph(input:ReleaseGraphOptions){
41
69
  /** Progress is deliberately outside sourceDefinitionHash: eligibility is checked anew. */
42
70
  export function currentReleasePrerequisites(input:Omit<ReleaseGraphOptions,'releaseRef'>){
43
71
  const u=readReleaseUniverse(input),keys=new Set(u.upstream.snapshot.tasks.map(t=>t.planKey)),qual=new Set(u.qualifications.map(t=>t.planKey));
44
- const required=u.entries.filter(({lane,task})=>keys.has(task.planKey)&&!qual.has(task.planKey)&&u.plan.lanes[lane].state==='enabled');
45
- for(const row of u.entries){if(['development','audit'].includes(row.lane)&&u.plan.lanes[row.lane].state==='enabled'&&!keys.has(row.task.planKey))throw Error('Mandatory prerequisite missing from current closure');}
46
- for(const step of ['deploy','check-code'])if(!required.some(r=>r.task.step===step))throw Error(`Missing development acceptance prerequisite: ${step}`);
72
+ const requirementKeys=new Set(u.relevant.map(({task})=>(task.scope?.deployment as any)?.requirementRef?.planKey).filter(Boolean));
73
+ const required=u.entries.filter(({lane,task})=>keys.has(task.planKey)&&!qual.has(task.planKey)&&!requirementKeys.has(task.planKey)&&u.plan.lanes[lane].state==='enabled');
74
+ // Explicit dependencies (including the requirement's prerequisites) define this
75
+ // round. Unrelated development/audit work appended later is not a prerequisite.
76
+ if(!required.some(({task})=>{const d=task.scope?.deployment as any;return isDevelopmentAcceptancePrerequisite(task)&&d.deploymentId===input.deploymentId&&d.projectRoot===input.projectRoot&&d.environment==='dev'&&d.targetNetwork===u.developmentNetwork&&d.allowedActions.includes('verify');}))throw Error('Missing development acceptance prerequisite: verify');
47
77
  for(const {task} of required)if(task.status!=='completed'||task.result?.kind!=='success'||task.adoptedRev!==task.rev)throw Error(`Current prerequisite is incomplete or stale: ${task.planKey}`);
48
78
  return {...u,required};
49
79
  }
@@ -1,32 +1,42 @@
1
- /** Narrow R1→R2 handoff: capture the currently installed producer and changing containers BEFORE prod edits. */
1
+ /** Preserve accepted development inputs and one shared, compressed producer snapshot. */
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import {gzipSync} from 'node:zlib';
5
5
  import {readInputBytes} from './input-files';
6
6
  import {confinedFile,projectRoot} from './project-files';
7
- import {atomicDevelopmentWrite,byteHash,developmentPath} from './materialization';
7
+ import {byteHash,developmentPath} from './materialization';
8
8
  import {canonicalJson,semanticHash} from '../workflow/lib/canonicalData';
9
9
  import {parseDeliveryPlan} from './plan';
10
10
  import {safeId} from './protocol';
11
+ import {persistDevelopmentInputs,writeImmutableDeliveryFile} from './development-inputs';
11
12
  import type {DeliveryAggregate,DeliveryAggregateOptions} from './aggregate';
12
13
  import type {HistoricalDevelopmentFiles} from './release-history';
13
14
  export function captureDevelopmentReleaseHistory(input:{root:string;source:DeliveryAggregateOptions;aggregate:DeliveryAggregate}):HistoricalDevelopmentFiles {
14
15
  const root=fs.realpathSync(input.root),a=input.aggregate,plan=parseDeliveryPlan(a.collections[0]?.plan);
15
16
  if(input.source.selection!=='development'||a.collections.length!==1||plan.environment!=='dev'||projectRoot(input.source.workspaceRoot,input.source.projectRoot)!==root)throw Error('Historical capture requires original development source');
16
17
  safeId(plan.executionId,'executionId');
17
- const base=`.gate-delivery/history/${plan.executionId}`,readyPath=`${base}/READY.json`;
18
+ const base=`.gate-delivery/history/${plan.executionId}`,readyFile=`${base}/READY.json`;
18
19
  const acceptanceFile=`.gate-delivery/development/${plan.executionId}.acceptance.json`,acceptance=JSON.parse(readInputBytes(root,acceptanceFile).toString());
19
20
  if(acceptance.status!=='accepted'||acceptance.planHash!==plan.planHash||acceptance.inputsHash!==a.inputsHash||semanticHash(acceptance.runtimeIdentity)!==semanticHash(plan.runtimeIdentity))throw Error('Historical capture requires actual accepted development');
20
21
  const original=a.inputs.runtime.package;if(original.name!=='gate-evm-tools'||original.lockLocation!=='node_modules/gate-evm-tools')throw Error('Unsupported historical producer location');
22
+ // A completed capture can be reused after the installed producer changes.
23
+ if(fs.existsSync(developmentPath(root,readyFile))){
24
+ const ready=JSON.parse(readInputBytes(root,readyFile).toString());
25
+ if(ready.schema!=='gate-delivery-history-ready'||ready.version!==2||ready.generation!=='complete'||ready.planHash!==plan.planHash||ready.aggregateHash!==semanticHash(a)||ready.sourceHash!==semanticHash(input.source)||ready.descriptor?.version!==2||ready.descriptor.readyFile!==readyFile||!Array.isArray(ready.files)||!ready.files.length)throw Error('Conflicting historical capture');
26
+ for(const f of ready.files)if(byteHash(readInputBytes(root,f.path))!==f.sha256)throw Error('Captured historical file drift');
27
+ return ready.descriptor;
28
+ }
21
29
  const files:Array<{path:string;digest:string;base64:string}>=[];
22
30
  const visit=(relative:string)=>{const dir=confinedFile(root,`${original.lockLocation}/${relative}`.replace(/\/$/,''),'directory');for(const name of fs.readdirSync(dir).sort()){const rel=relative?`${relative}/${name}`:name;const stat=fs.lstatSync(path.join(dir,name));if(stat.isDirectory()){if(name==='node_modules')continue;visit(rel);}else{const bytes=readInputBytes(root,`${original.lockLocation}/${rel}`);files.push({path:rel,digest:byteHash(bytes),base64:bytes.toString('base64')});}}};visit('');files.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);
23
31
  if(semanticHash(files.map(({path,digest})=>({path,digest})))!==semanticHash(original.payload))throw Error('Historical capture must precede producer/runtime replacement');
24
- const descriptor:HistoricalDevelopmentFiles={aggregateFile:`${base}/aggregate.json`,sourceFile:`${base}/source.json`,acceptanceFile,packageRoot:`${base}/package`,inputsRoot:`${base}/inputs`,archiveFile:`${base}/package.snapshot.json.gz`};
25
- if(fs.existsSync(developmentPath(root,readyPath))){const ready=JSON.parse(readInputBytes(root,readyPath).toString());if(ready.schema!=='gate-delivery-history-ready'||ready.generation!=='complete'||ready.planHash!==plan.planHash||ready.aggregateHash!==semanticHash(a))throw Error('Conflicting historical capture');for(const f of ready.files)if(byteHash(readInputBytes(root,f.path))!==f.sha256)throw Error('Captured historical file drift');return descriptor;}
32
+ const archive=gzipSync(Buffer.from(canonicalJson({schema:'gate-delivery-package-snapshot',version:2,runtimeIdentity:plan.runtimeIdentity,observedPackage:original,files})));
33
+ const archiveFile=`.gate-delivery/packages/${byteHash(archive)}.json.gz`;
26
34
  const saved:Array<{path:string;sha256:string}>=[];
27
- const write=(relative:string,bytes:Buffer)=>{const file=developmentPath(root,relative);fs.mkdirSync(path.dirname(file),{recursive:true,mode:0o700});if(fs.existsSync(file)&&!readInputBytes(root,relative).equals(bytes))throw Error('Partial historical capture conflict');const fd=fs.openSync(file,'w',0o600);try{fs.writeFileSync(fd,bytes);fs.fsyncSync(fd);}finally{fs.closeSync(fd);}saved.push({path:relative,sha256:byteHash(bytes)});};
28
- for(const file of files)write(`${base}/package/${file.path}`,Buffer.from(file.base64,'base64'));
35
+ const write=(relative:string,bytes:Buffer)=>saved.push(writeImmutableDeliveryFile(root,relative,bytes));
36
+ const {sourcePath:sourceFile,aggregatePath:aggregateFile}=persistDevelopmentInputs(input);
37
+ const descriptor:HistoricalDevelopmentFiles={version:2,aggregateFile,sourceFile,acceptanceFile,readyFile,inputsRoot:`${base}/inputs`,archiveFile};
38
+ for(const relative of [sourceFile,aggregateFile,acceptanceFile])saved.push({path:relative,sha256:byteHash(readInputBytes(root,relative))});
29
39
  for(const name of [`${a.inputs.business.businessRoot}/task.yaml`,`${a.inputs.business.businessRoot}/parameters.yaml`,`${a.inputs.business.businessRoot}/parameters.provenance.json`,'config.yaml','hardhat.config.ts','package-lock.json','contractInfo.json','deploymentInfo.json'])write(`${base}/inputs/${name}`,readInputBytes(root,name));
30
- write(descriptor.aggregateFile,Buffer.from(canonicalJson(a)+'\n'));write(descriptor.sourceFile,Buffer.from(canonicalJson(input.source)+'\n'));write(descriptor.archiveFile,gzipSync(Buffer.from(canonicalJson({schema:'gate-delivery-package-snapshot',version:1,runtimeIdentity:plan.runtimeIdentity,observedPackage:original,files}))));
31
- atomicDevelopmentWrite(root,readyPath,canonicalJson({schema:'gate-delivery-history-ready',version:1,generation:'complete',planHash:plan.planHash,aggregateHash:semanticHash(a),descriptor,files:saved})+'\n');return descriptor;
40
+ write(archiveFile,archive);
41
+ writeImmutableDeliveryFile(root,readyFile,Buffer.from(canonicalJson({schema:'gate-delivery-history-ready',version:2,generation:'complete',planHash:plan.planHash,aggregateHash:semanticHash(a),sourceHash:semanticHash(input.source),descriptor,files:saved})+'\n'));return descriptor;
32
42
  }
@@ -1,15 +1,18 @@
1
1
  /** Read-only consumption of R1 evidence. Historical runtime is verified, never executed or relabelled. */
2
2
  import {existingCodeCoverage,requiredExistingAddresses} from './existing-code';
3
3
  import fs from 'node:fs';
4
+ import {aggregateInputHash} from './execution-identity';
5
+ export {aggregateInputHash} from './execution-identity';
4
6
  import path from 'node:path';
5
- import {createHash} from 'node:crypto';
6
- import {gzipSync,gunzipSync} from 'node:zlib';
7
7
  import {Contract,JsonRpcProvider} from 'ethers';
8
+ import {BytecodeChecker,TRANSPARENT_PROXY_ARTIFACT,PROXY_ADMIN_ARTIFACT,ERC1967_ADMIN_SLOT,PROXY_ADMIN_INTERFACE,transparentAdminAddress,assertTransparentAdminImmutable} from 'gate-evm-tools/workflow';
8
9
  import {readInputBytes,inputDigest,parseInputDocument} from './input-files';
9
- import {confinedFile,projectRoot} from './project-files';
10
+ import {projectRoot} from './project-files';
10
11
  import {parseCanonicalPlan,projectCanonicalDefinitions} from './source';
11
12
  import {parseConfigurationDefinition} from './configuration-definition';
12
13
  import {parseDeliveryPublicTargets} from './config';
14
+ import {object,safeId} from './protocol';
15
+ import {readHistoricalPackage} from './history-package';
13
16
  import {readProjectNetworkSnapshots} from './project';
14
17
  import {parseDeliveryPlan} from './plan';
15
18
  import {parsePreparationJournal} from './journal';
@@ -25,48 +28,79 @@ import {validateDevelopmentCheckReport,type ExpectedDevelopmentCode} from './che
25
28
  import {sameReleaseData as same} from './release-graph';
26
29
  import type {DeliveryAggregate,DeliveryAggregateOptions} from './aggregate';
27
30
  import {historicalRegistrationFile,validateHistoricalRegistration} from './history-registration';
28
- export interface HistoricalDevelopmentFiles {aggregateFile:string;sourceFile:string;acceptanceFile:string;packageRoot:string;inputsRoot:string;archiveFile:string}
29
- export function aggregateInputHash(a:DeliveryAggregate){return semanticHash({version:1,business:a.inputs.business,sourceDefinitions:a.inputs.sourceDefinitions,runtime:a.inputs.runtime,files:a.inputs.files,targets:a.inputs.targets.map(r=>({target:r.target,configuration:r.configuration.configurationRuntimeHash,sourceConfiguration:r.sourceConfiguration.configurationRuntimeHash,compiler:r.compiler.stableDigest,custom:r.custom}))});}
31
+ import {resolveParameterTargets} from './parameter-defaults';
32
+ import {validateHistoricalParameterExtension,type ParameterInputs} from './parameters';
33
+ import {validateHistoricalBusinessExtension} from './business-extension';
34
+ import {parseNativeOperation} from './custom';
35
+ export interface HistoricalDevelopmentFiles {version:2;aggregateFile:string;sourceFile:string;acceptanceFile:string;readyFile:string;inputsRoot:string;archiveFile:string}
30
36
  function decode(raw:any):any{const v=parseTypedValue(raw);switch(v.type){case 'bigint':return BigInt(v.value);case 'number':return Number(v.value);case 'array':return v.value.map(decode);case 'object':return Object.fromEntries(Object.entries(v.value).map(([k,x])=>[k,decode(x)]));default:return v.value;}}
31
37
  /** Every mutable historical container is retained with its ORIGINAL digest; allowed deltas are explicit. */
32
38
  export function validateHistoricalDevelopmentFiles(input:{root:string;workspaceRoot:string;projectRoot:string;history:HistoricalDevelopmentFiles}){
33
39
  const workspace=projectRoot(input.workspaceRoot,'.'),root=projectRoot(workspace,input.projectRoot),{history}=input,files=new Map<string,string>();
34
40
  if(fs.realpathSync(input.root)!==root)throw Error('Historical current project root mismatch');
41
+ object(history,'historical descriptor',['version','aggregateFile','sourceFile','acceptanceFile','readyFile','inputsRoot','archiveFile']);
42
+ if(history.version!==2)throw Error('Unsupported historical descriptor version');
35
43
  const read=(relative:string)=>{const bytes=readInputBytes(root,relative);files.set(relative,inputDigest(bytes));return bytes;};
36
44
  const aggregate=JSON.parse(read(history.aggregateFile).toString()) as DeliveryAggregate,source=JSON.parse(read(history.sourceFile).toString()) as DeliveryAggregateOptions,acceptance=JSON.parse(read(history.acceptanceFile).toString());
37
45
  if(source.projectRoot!==input.projectRoot)throw Error('Historical relative project scope mismatch');
38
46
  if(source.selection!=='development'||source.targets.length!==1||aggregate.collections.length!==1||aggregate.inputs.business.targets.length!==1)throw Error('Historical development scope mismatch');
39
47
  same(aggregateInputHash(aggregate),aggregate.inputsHash,'Historical aggregate inputs');same(semanticHash(aggregate.inputs.sourceDefinitions),aggregate.sourceDefinitionHash,'Historical source hash');
40
48
  const collection=aggregate.collections[0],plan=parseDeliveryPlan(collection.plan),target=aggregate.inputs.business.targets[0];
49
+ const id=safeId(plan.executionId,'executionId'),base=`.gate-delivery/history/${id}`;
50
+ if(history.aggregateFile!==`.gate-delivery/development/${id}.aggregate.json`||history.sourceFile!==`.gate-delivery/development/${id}.source.json`||history.acceptanceFile!==`.gate-delivery/development/${id}.acceptance.json`||history.readyFile!==`${base}/READY.json`||history.inputsRoot!==`${base}/inputs`||!/^\.gate-delivery\/packages\/[a-f0-9]{64}\.json\.gz$/.test(history.archiveFile))throw Error('Noncanonical historical descriptor');
41
51
  if(plan.environment!=='dev'||plan.target!==target.id||source.targets[0].request.network!==plan.target||source.targets[0].executionId!==plan.executionId)throw Error('Historical development identity mismatch');
42
52
  for(const key of ['executionId','planHash','target','inputsHash','sourceDefinitionHash','runtimeIdentity'] as const)same(acceptance[key],plan[key],`Historical acceptance ${key}`);
43
53
  if(acceptance.version!==1||acceptance.status!=='accepted')throw Error('Missing historical acceptance');
44
- const readyPath=path.posix.dirname(history.aggregateFile)+'/READY.json',ready=JSON.parse(read(readyPath).toString());
45
- if(ready.schema!=='gate-delivery-history-ready'||ready.version!==1||ready.generation!=='complete'||ready.planHash!==plan.planHash||ready.aggregateHash!==semanticHash(aggregate)||!Array.isArray(ready.files)||!ready.files.length)throw Error('Historical snapshot generation is not READY');
54
+ const ready=JSON.parse(read(history.readyFile).toString());
55
+ if(ready.schema!=='gate-delivery-history-ready'||ready.version!==2||ready.generation!=='complete'||ready.planHash!==plan.planHash||ready.aggregateHash!==semanticHash(aggregate)||ready.sourceHash!==semanticHash(source)||!Array.isArray(ready.files)||!ready.files.length)throw Error('Historical snapshot generation is not READY');
46
56
  same(ready.descriptor,history,'Historical READY descriptor');for(const file of ready.files)if(byteHash(read(file.path))!==file.sha256)throw Error('Historical READY file binding mismatch');
47
57
  const original=aggregate.inputs.runtime.package;
48
58
  same(aggregate.inputs.runtime.declared,plan.runtimeIdentity,'Historical runtime identity');
49
59
  if(original.name!=='gate-evm-tools'||original.version!==plan.runtimeIdentity.version||original.source.identity!==original.integrity)throw Error('Historical producer provenance mismatch');
50
- const archive=read(history.archiveFile),snapshot=JSON.parse(gunzipSync(archive).toString());
51
- if(snapshot.schema!=='gate-delivery-package-snapshot'||snapshot.version!==1||!Array.isArray(snapshot.files))throw Error('Invalid historical package snapshot archive');
52
- const archived=snapshot.files.map((f:any)=>{const bytes=Buffer.from(f.base64,'base64');if(bytes.toString('base64')!==f.base64||byteHash(bytes)!==f.digest)throw Error('Historical snapshot archive bytes mismatch');return {path:f.path,digest:f.digest};});
53
- same(archived,original.payload,'Historical archive complete payload');same(snapshot.observedPackage,original,'Historical snapshot provenance');same(snapshot.runtimeIdentity,plan.runtimeIdentity,'Historical snapshot runtime');
54
- const payload:Array<{path:string;digest:string}>=[];
55
- const visit=(relative:string)=>{const dir=confinedFile(root,`${history.packageRoot}/${relative}`.replace(/\/$/,''),'directory');for(const name of fs.readdirSync(dir).sort()){if(name==='node_modules')throw Error('Historical producer nested package unsupported');const rel=relative?`${relative}/${name}`:name,p=confinedFile(root,`${history.packageRoot}/${rel}`,fs.lstatSync(path.join(dir,name)).isDirectory()?'directory':'file');if(fs.lstatSync(p).isDirectory())visit(rel);else payload.push({path:rel,digest:inputDigest(read(`${history.packageRoot}/${rel}`))});}};
56
- visit('');payload.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);same(payload,original.payload,'Historical producer actual payload');same(byteHash(payload.map(f=>`${f.path}\0${f.digest}\n`).join('')),original.payloadDigest,'Historical payload digest');
57
- const digest=createHash('sha256');const hashDir=(dir:string)=>{for(const name of fs.readdirSync(confinedFile(root,`${history.packageRoot}/${dir}`,'directory')).sort()){const rel=`${dir}/${name}`,file=confinedFile(root,`${history.packageRoot}/${rel}`,fs.lstatSync(path.join(root,history.packageRoot,rel)).isDirectory()?'directory':'file');if(fs.lstatSync(file).isDirectory())hashDir(rel);else if(/\.(?:c?js)$/.test(rel)){digest.update(rel);digest.update(read(`${history.packageRoot}/${rel}`));}}};for(const dir of ['dist-runtime','dist','dist-cjs','bin'])hashDir(dir);same(digest.digest('hex'),plan.runtimeIdentity.digest,'Historical executable runtime digest');
60
+ const archive=read(history.archiveFile),packageFiles=readHistoricalPackage(archive,history.archiveFile,aggregate);
58
61
  const business=aggregate.inputs.business.businessRoot,mutable=new Set([`${business}/task.yaml`,`${business}/parameters.yaml`,`${business}/parameters.provenance.json`,'config.yaml','hardhat.config.ts']);
59
62
  const old=(file:string)=>read(`${history.inputsRoot}/${file}`),now=(file:string)=>read(file);
63
+ const evolving=new Set([`${business}/manifest.yaml`,`${business}/parameters.schema.json`].filter(file=>{
64
+ const originalFile=aggregate.inputs.files.find(entry=>entry.path===file);
65
+ return originalFile!==undefined&&inputDigest(now(file))!==originalFile.digest;
66
+ }));
67
+ if(evolving.size){
68
+ const previous=aggregate.inputs.business.parameters.find(entry=>entry.target===plan.target)?.resolved;
69
+ if(!previous)throw Error('Historical shared-step evolution requires complete accepted parameter evidence');
70
+ const metadata=parseInputDocument(now(`${business}/task.yaml`),'yaml') as any;
71
+ const resolved=resolveParameterTargets(parseInputDocument(now(`${business}/parameters.yaml`),'yaml'),parseInputDocument(now(`${business}/parameters.provenance.json`),'json'),[plan.target,...metadata.releaseTargetNetworks]);
72
+ const current=validateHistoricalParameterExtension(previous,{schema:parseInputDocument(now(`${business}/parameters.schema.json`),'json') as any,
73
+ target:plan.target,values:resolved.params[plan.target],sources:resolved.params,...resolved.observations[plan.target]} as ParameterInputs);
74
+ const manifest=object(parseInputDocument(now(`${business}/manifest.yaml`),'yaml'),'manifest.yaml',['id','description','steps']);
75
+ if(manifest.id!==plan.deploymentId||!Array.isArray(manifest.steps))throw Error('Historical shared manifest identity changed');
76
+ validateHistoricalBusinessExtension({before:aggregate.inputs.business.steps,after:manifest.steps.map(parseNativeOperation),previous,current,target:target as unknown as Record<string,unknown>});
77
+ }
60
78
  const ownPayload=new Set(original.payload.map(file=>`${original.lockLocation}/${file.path}`));
61
79
  // The entire old frozen file union is verified. Only owned producer bytes and bounded containers have historical locations.
62
- for(const file of aggregate.inputs.files){const bytes=ownPayload.has(file.path)?read(`${history.packageRoot}/${file.path.slice(original.lockLocation.length+1)}`):mutable.has(file.path)?old(file.path):now(file.path);if(inputDigest(bytes)!==file.digest)throw Error(`Historical immutable input drift: ${file.path}`);}
80
+ for(const file of aggregate.inputs.files){if(evolving.has(file.path))continue;const bytes=ownPayload.has(file.path)?packageFiles.get(file.path.slice(original.lockLocation.length+1))!:mutable.has(file.path)?old(file.path):now(file.path);if(inputDigest(bytes)!==file.digest)throw Error(`Historical immutable input drift: ${file.path}`);}
63
81
  const oldTask=parseInputDocument(old(`${business}/task.yaml`),'yaml') as any,newTask=parseInputDocument(now(`${business}/task.yaml`),'yaml') as any;
64
- same({...newTask,releaseTargetNetworks:oldTask.releaseTargetNetworks},oldTask,'Historical development task container');
82
+ const {productionRequirementRef:_production,...newBase}=newTask;
83
+ same({...newBase,releaseTargetNetworks:oldTask.releaseTargetNetworks,...(oldTask.productionRequirementRef===undefined?{}:{productionRequirementRef:oldTask.productionRequirementRef})},oldTask,'Historical development task container');
65
84
  if(!Array.isArray(newTask.releaseTargetNetworks)||oldTask.releaseTargetNetworks.some((x:string)=>!newTask.releaseTargetNetworks.includes(x)))throw Error('Historical production target removal');
66
85
  const prod=new Set<string>(newTask.releaseTargetNetworks);
67
- for(const file of ['parameters.yaml','parameters.provenance.json']){const before=parseInputDocument(old(`${business}/${file}`),file.endsWith('.json')?'json':'yaml') as any,after=parseInputDocument(now(`${business}/${file}`),file.endsWith('.json')?'json':'yaml') as any;const container=file==='parameters.yaml'?'params':'targets';const projection=(value:any)=>({...value,[container]:Object.fromEntries(Object.entries(value[container]).filter(([k])=>!prod.has(k)))});same(projection(after),projection(before),'Historical dev parameter values/provenance');}
86
+ for(const file of ['parameters.yaml','parameters.provenance.json']){
87
+ const before=parseInputDocument(old(`${business}/${file}`),file.endsWith('.json')?'json':'yaml') as any,after=parseInputDocument(now(`${business}/${file}`),file.endsWith('.json')?'json':'yaml') as any;
88
+ const container=file==='parameters.yaml'?'params':'targets';
89
+ if(file.endsWith('.json'))for(const [id,definition] of Object.entries(before.producers))same(after.producers[id],definition,'Historical existing producer definition');
90
+ const projection=(value:any)=>({...value,...(file.endsWith('.json')?{producers:before.producers}:{}),[container]:Object.fromEntries(Object.entries(value[container]).filter(([k])=>!prod.has(k)&&!(evolving.size&&k===plan.target)))});
91
+ same(projection(after),projection(before),'Historical dev parameter values/provenance');
92
+ }
68
93
  const oldConfig=parseInputDocument(old('config.yaml'),'yaml') as any,newConfig=parseInputDocument(now('config.yaml'),'yaml') as any;
69
- same(Object.fromEntries(Object.entries(newConfig).filter(([k])=>!prod.has(k))),Object.fromEntries(Object.entries(oldConfig).filter(([k])=>!prod.has(k))),'Historical development configuration');
94
+ // Prod defaults do not participate in dev inheritance. Keep shared/dev defaults and network overrides bound.
95
+ const devConfig=(value:unknown)=>{
96
+ const config=object(value,'config.yaml'),projected=Object.fromEntries(Object.entries(config).filter(([k])=>k!=='_profiles'&&!prod.has(k)));
97
+ if(config._profiles!==undefined){
98
+ const {prod:_prod,...profiles}=object(config._profiles,'_profiles',['baseProfile','dev','prod']);
99
+ if(Object.keys(profiles).length)projected._profiles=profiles;
100
+ }
101
+ return projected;
102
+ };
103
+ same(devConfig(newConfig),devConfig(oldConfig),'Historical development configuration');
70
104
  const previous=parseConfigurationDefinition(old('hardhat.config.ts'),plan.target).definition,current=parseConfigurationDefinition(now('hardhat.config.ts'),plan.target).definition;
71
105
  // Existing Hardhat networks remain identical; only newly selected production aliases may be added.
72
106
  for(const [name,value] of Object.entries(previous.networks))same(current.networks[name],value,'Historical effective network');
@@ -78,7 +112,7 @@ export function validateHistoricalDevelopmentFiles(input:{root:string;workspaceR
78
112
  for(const file of dependencyFiles)if(inputDigest(now(file.path))!==file.digest)throw Error('Historical custom/producer dependency changed');
79
113
  const handoff=source.targets[0].request.sourceRef.path,text=readInputBytes(workspace,handoff).toString(),actual=parseCanonicalPlan(text);if(actual.taskId!==source.targets[0].request.sourceRef.taskId)throw Error('Historical task source identity mismatch');
80
114
  const projection=projectCanonicalDefinitions(text,{roots:[source.targets[0].request.sourceRef.planKey],mode:'upstream'}).snapshot;
81
- const normalize=(v:any)=>{const copy=JSON.parse(canonicalJson(v));for(const t of copy.tasks)if(t.scope?.deployment)t.scope.deployment.releaseTargetNetworks=oldTask.releaseTargetNetworks;return copy;};
115
+ const normalize=(v:any)=>{const copy=JSON.parse(canonicalJson(v));for(const t of copy.tasks)if(t.scope?.deployment&&Object.hasOwn(t.scope.deployment,'releaseTargetNetworks'))t.scope.deployment.releaseTargetNetworks=oldTask.releaseTargetNetworks;return copy;};
82
116
  const historical=(aggregate.inputs.sourceDefinitions as any[]);if(historical.length!==1||historical[0].path!==handoff)throw Error('Historical source document mismatch');same(normalize(projection),historical[0].definition,'Historical upstream definition with bounded prod expansion');
83
117
  const expected=expectedDevelopmentAssertions(collection,aggregate.inputs.business.steps);
84
118
  if(!Array.isArray(acceptance.assertions)||acceptance.assertions.length!==expected.length)throw Error('Missing development business assertions');
@@ -101,13 +135,13 @@ export function validateHistoricalDevelopmentFiles(input:{root:string;workspaceR
101
135
  for(const [key,value] of Object.entries({identity:acceptance.identity,executionId:plan.executionId,planHash:plan.planHash,inputsHash:plan.inputsHash,sourceDefinitionHash:plan.sourceDefinitionHash,runtimeIdentity:plan.runtimeIdentity,preparations,finalized,assertions:acceptance.assertions}))same(record[key],value,`Historical deployment record ${key}`);
102
136
  if(!Array.isArray(record.changes))throw Error('Missing development registry changes');
103
137
  const expectedCode:ExpectedDevelopmentCode[]=[],effective:any={[plan.target]:{}};
104
- for(const change of record.changes){effective[plan.target][change.contractInfoKey]=change.address;const key=change.contractInfoKey.split(':')[0];expectedCode.push({key,contract:change.actualContract,address:change.implementationAddress??change.address});if(change.proxyKind==='uups')expectedCode.push({key:`${key}_Proxy`,contract:'ERC1967Proxy',address:change.address});}
138
+ for(const change of record.changes){effective[plan.target][change.contractInfoKey]=change.address;const key=change.contractInfoKey.split(':')[0];expectedCode.push({key,contract:change.actualContract,address:change.implementationAddress??change.address});if(change.proxyKind)expectedCode.push({key:`${key}_Proxy`,contract:change.proxyKind==='transparent'?'TransparentUpgradeableProxy':'ERC1967Proxy',address:change.address});if(change.proxyKind==='transparent')expectedCode.push({key:`${key}_ProxyAdmin`,contract:'ProxyAdmin',address:change.proxyAdminAddress});}
105
139
  const pure=!preparations.length?existingCodeCoverage({target:plan.target,addresses:requiredExistingAddresses(collection),registry:JSON.parse(historicalRegistration('contractInfo.json').toString()),metadata:JSON.parse(historicalRegistration('deploymentInfo.json').toString()),compiler:aggregate.inputs.targets.find(t=>t.target===plan.target)!.compiler,manifests:(acceptance.materialization.manifests??[]).map((m:any)=>({path:m.file,contents:now(m.file).toString()}))}):null;
106
140
  if(pure){if(record.changes.length)throw Error('Pure-call history cannot invent deployment changes');Object.assign(effective,pure.effective);expectedCode.push(...pure.expected);same(acceptance.materialization.manifests,pure.manifests,'Historical existing manifests');}
107
141
  const code=acceptance.checkCode;if(code?.version!==1||code.executionId!==plan.executionId||code.planHash!==plan.planHash||code.target!==plan.target||typeof code.rawFile!=='string'||!code.rawFile.startsWith(`.gate-delivery/check-code/${plan.executionId}/`))throw Error('Missing real CheckCode identity');
108
142
  same(byteHash(historicalRegistration('contractInfo.json')),code.registryHash,'Historical registry');same(byteHash(historicalRegistration('deploymentInfo.json')),code.metadataHash,'Historical deployment metadata');same(byteHash(canonicalJson(effective)+'\n'),code.effectiveHash,'Historical CheckCode effective registry');
109
143
  const raw=now(code.rawFile);same(byteHash(raw),code.rawHash,'Historical real raw CheckCode');validateDevelopmentCheckReport(JSON.parse(raw.toString()),plan.target,expectedCode);same(code.artifacts,pure?.artifacts??preparations.map(p=>p.prepared.plan.artifact),'Historical CheckCode artifacts');
110
- const manifests=acceptance.materialization.manifests;if(!Array.isArray(manifests)||record.changes.some((c:any)=>c.proxyKind==='uups')&&!manifests.length)throw Error('Missing official manifest evidence');for(const m of manifests)same(byteHash(now(m.file)),m.hash,'Historical official manifest');
144
+ const manifests=acceptance.materialization.manifests;if(!Array.isArray(manifests)||record.changes.some((c:any)=>(c.proxyKind==='uups'||c.proxyKind==='transparent'))&&!manifests.length)throw Error('Missing official manifest evidence');for(const m of manifests)same(byteHash(now(m.file)),m.hash,'Historical official manifest');
111
145
  return deepFreeze({aggregate,source,acceptance,plan,files:[...files].map(([path,digest])=>({path,digest})),association:{version:1,historicalInputsHash:aggregate.inputsHash,historicalRuntime:plan.runtimeIdentity,historicalSnapshotSha256:byteHash(archive),observedLockIntegrity:original.integrity,installation:'owned-installed-payload-snapshot@1',developmentTarget:plan.target,oldProductionTargets:oldTask.releaseTargetNetworks,currentProductionTargets:newTask.releaseTargetNetworks,rule:'dev-preserving-prod-container-extension@1'}});
112
146
  }
113
147
  /** Actual read-only receipts and business state. No signing, compilation, mutation or old-runtime execution. */
@@ -115,6 +149,26 @@ export async function verifyHistoricalDevelopmentChain(result:ReturnType<typeof
115
149
  const {plan,acceptance,aggregate}=result,snapshot=readProjectNetworkSnapshots(root,[plan.target])[0],provider=new JsonRpcProvider(snapshot.url,undefined,{staticNetwork:false});
116
150
  try{const rpc={request:(method:string,params:readonly unknown[])=>provider.send(method,[...params])};const context={executionId:plan.executionId,planHash:plan.planHash,releaseHash:plan.inputsHash,operator:plan.operator,policyHash:semanticHash({historical:true}),chainId:plan.chainId,target:plan.target,finality:{kind:'confirmations' as const,depth:String(Math.max(1,aggregate.inputs.business.targets[0].finality.confirmations))}};
117
151
  for(const [index,observation] of acceptance.finalized.entries())same(await observeEoaTransaction({plan,context,provider:rpc,index,transactionHash:observation.transactionHash}),observation,'Current historical finalized receipt');
152
+ // Recheck exercised Transparent administration from the accepted metadata and sealed
153
+ // compiler output. No project code or historical runtime is loaded.
154
+ const journal=readDevelopmentJournal(root,plan.executionId),metadataFile=journal?.files.find((f:{path:string;after:string})=>f.path==='deploymentInfo.json');
155
+ if(!metadataFile)throw Error('Historical deployment metadata missing');
156
+ const metadata=JSON.parse(metadataFile.after).networks?.[plan.target]??{},raw=JSON.parse(readInputBytes(root,acceptance.checkCode.rawFile).toString())[plan.target];
157
+ const compiler=aggregate.inputs.targets.find(t=>t.target===plan.target)!.compiler,checker=new BytecodeChecker(undefined as never,{},'evm');
158
+ for(const [alias,m] of Object.entries(metadata) as Array<[string,any]>)if(m.proxyKind==='transparent'&&Object.hasOwn(raw,`${alias}_ProxyAdmin`)){
159
+ const admin=m.proxyAdminAddress?.toLowerCase();if(!admin||admin!==transparentAdminAddress(m.address)||!m.proxyAdminOwner)throw Error('Historical Transparent admin identity mismatch');
160
+ const slot=await provider.getStorage(m.address,ERC1967_ADMIN_SLOT);if(!/^0x0{24}[a-fA-F0-9]{40}$/.test(slot)||'0x'+slot.slice(-40).toLowerCase()!==admin)throw Error('Historical Transparent admin slot mismatch');
161
+ for(const name of [TRANSPARENT_PROXY_ARTIFACT,PROXY_ADMIN_ARTIFACT]){
162
+ const matches=compiler.artifacts.filter(a=>a.fqn===name);if(matches.length!==1)throw Error('Historical Transparent artifact missing');
163
+ const entry=matches[0],artifact=entry.artifact,build=compiler.builds.find(b=>b.digest===entry.buildInfo),refs=build&&JSON.parse(build.contents).output?.contracts?.[artifact.sourceName]?.[artifact.contractName]?.evm?.deployedBytecode?.immutableReferences;
164
+ if(!refs)throw Error('Historical Transparent compiler output missing');
165
+ const code=await provider.getCode(name===PROXY_ADMIN_ARTIFACT?admin:m.address);if(code==='0x'||!checker.compareBytecode(code,artifact.deployedBytecode!,artifact.abi as any[],refs).matches)throw Error('Historical Transparent runtime mismatch');
166
+ if(name===TRANSPARENT_PROXY_ARTIFACT)assertTransparentAdminImmutable(code,refs,admin);
167
+ }
168
+ const owner=PROXY_ADMIN_INTERFACE.decodeFunctionResult('owner',await provider.call({to:admin,data:PROXY_ADMIN_INTERFACE.encodeFunctionData('owner')}))[0];
169
+ const version=PROXY_ADMIN_INTERFACE.decodeFunctionResult('UPGRADE_INTERFACE_VERSION',await provider.call({to:admin,data:PROXY_ADMIN_INTERFACE.encodeFunctionData('UPGRADE_INTERFACE_VERSION')}))[0];
170
+ if(owner.toLowerCase()!==m.proxyAdminOwner.toLowerCase()||version!=='5.0.0')throw Error('Historical ProxyAdmin owner/version mismatch');
171
+ }
118
172
  for(const assertion of acceptance.assertions){const contract=new Contract(assertion.address,assertion.abi,provider),actual=await contract.getFunction(assertion.method).staticCall(...decode(assertion.args)),comparison=compareAbiResults(assertion.abi,assertion.method,actual,decode(assertion.expected));if(comparison.actual!==comparison.expected)throw Error('Current historical development business assertion failed');}
119
173
  }finally{provider.destroy();}
120
174
  }
@@ -0,0 +1,107 @@
1
+ /** Exact deployment input bytes, independent of whether deployment scripts were committed. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { readInputBytes, inputDigest } from './input-files';
5
+ import { confinedFile } from './project-files';
6
+ import { portablePath } from './protocol';
7
+ import { strictData } from './data';
8
+ import { canonicalJson, deepFreeze, exactObject, semanticHash } from '../workflow/lib/canonicalData';
9
+ import type { ContractSourceIdentity } from './contract-source';
10
+ import type { DeliveryAggregate } from './aggregate';
11
+
12
+ export interface ReleaseInputSnapshot {
13
+ schema: 'gate-delivery-input-snapshot';
14
+ version: 1;
15
+ contractSource: ContractSourceIdentity;
16
+ files: Array<{ path: string; digest: string; base64: string }>;
17
+ snapshotHash: string;
18
+ }
19
+ export interface ReleaseInputSnapshotRef { path: string; hash: string; sha256: string }
20
+ const encode = (value: unknown) => canonicalJson(value) + '\n';
21
+ function inputPath(raw: unknown): string {
22
+ const file=portablePath(raw, 'release input path');
23
+ if (file.split('/').some(part => /^(?:node_modules|\.git|\.gate-delivery|rpc\.env|\.env(?:\..*)?|\.npmrc|\.ssh)$/.test(part))
24
+ || /^scripts\/tasks\/[^/]+\/(?:releases|results)(?:\/|$)/.test(file)) throw Error('Unsupported release snapshot input path');
25
+ return file;
26
+ }
27
+ export function captureReleaseInputs(root: string, aggregate: DeliveryAggregate, contractSource: ContractSourceIdentity): Readonly<ReleaseInputSnapshot> {
28
+ // Config snapshots retain source expressions, never evaluated signing credentials.
29
+ const literalCredential=(value:any):boolean=>value&&typeof value==='object'&&(value.kind==='literal'
30
+ ? value.definitionHash!==inputDigest('gate-delivery-secret-definition@1\0')
31
+ : Object.values(value).some(literalCredential));
32
+ for(const target of aggregate.inputs.targets)for(const network of Object.values(target.configuration.configuration.definition?.networks??{}) as any[])
33
+ if(literalCredential(network.accounts))throw Error('Signing credentials must use environment references before deployment inputs are archived');
34
+ const wanted = new Map<string, string | undefined>();
35
+ for (const file of [...aggregate.inputs.files, ...contractSource.files]) {
36
+ if (file.path.split('/').includes('node_modules')) continue;
37
+ inputPath(file.path);
38
+ if (wanted.has(file.path) && wanted.get(file.path) !== file.digest) throw Error('Conflicting release snapshot input');
39
+ wanted.set(file.path, file.digest);
40
+ }
41
+ for (const file of ['package.json', 'package-lock.json', 'tsconfig.json']) {
42
+ if (!wanted.has(file) && fs.existsSync(confinedFile(root, file))) wanted.set(file, undefined);
43
+ }
44
+ const files = [...wanted].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([file, expected]) => {
45
+ const bytes = readInputBytes(root, file), digest = inputDigest(bytes);
46
+ if (expected !== undefined && expected !== digest) throw Error(`Release snapshot input changed: ${file}`);
47
+ return { path: file, digest, base64: bytes.toString('base64') };
48
+ });
49
+ const body = { schema: 'gate-delivery-input-snapshot' as const, version: 1 as const, contractSource, files };
50
+ return deepFreeze({ ...body, snapshotHash: semanticHash(body) });
51
+ }
52
+ /** Validate the entire payload before inspecting current inputs or writing any restored files. */
53
+ export function parseReleaseInputSnapshot(raw: unknown): Readonly<ReleaseInputSnapshot> {
54
+ const data = exactObject(strictData(raw), ['schema', 'version', 'contractSource', 'files', 'snapshotHash'], 'release input snapshot');
55
+ const { snapshotHash, ...body } = data;
56
+ if (data.schema !== 'gate-delivery-input-snapshot' || data.version !== 1 || semanticHash(body) !== snapshotHash || !Array.isArray(data.files) || !data.files.length)
57
+ throw Error('Invalid release input snapshot identity');
58
+ const source = exactObject(data.contractSource, ['version', 'commit', 'projectPath', 'sourcesDirectory', 'files', 'sourceHash'], 'contract source identity');
59
+ if (source.version !== 1 || typeof source.commit!=='string' || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(source.commit) || !Array.isArray(source.files) || !source.files.length
60
+ || source.sourceHash !== semanticHash({ sourcesDirectory: source.sourcesDirectory, files: source.files })) throw Error('Invalid release contract source identity');
61
+ for (const directory of [source.projectPath, source.sourcesDirectory]) if (directory !== '.') portablePath(directory, 'contract source directory');
62
+ const files = new Map<string, string>();
63
+ let previous = '';
64
+ for (const rawFile of data.files) {
65
+ const file = exactObject(rawFile, ['path', 'digest', 'base64'], 'snapshot file');
66
+ const filePath=inputPath(file.path);
67
+ if (typeof file.base64 !== 'string' || typeof file.digest!=='string' || !/^[a-f0-9]{64}$/.test(file.digest) || filePath <= previous) throw Error('Invalid or duplicate release snapshot file');
68
+ const bytes = Buffer.from(file.base64, 'base64');
69
+ if (bytes.toString('base64') !== file.base64 || inputDigest(bytes) !== file.digest) throw Error('Release snapshot file bytes changed');
70
+ previous = filePath; files.set(filePath, file.digest);
71
+ }
72
+ const seen = new Set<string>();
73
+ for (const rawFile of source.files) {
74
+ const file = exactObject(rawFile, ['path', 'digest'], 'contract source file');
75
+ const filePath=inputPath(file.path);
76
+ if (seen.has(filePath) || files.get(filePath) !== file.digest) throw Error('Missing or conflicting snapshotted contract source');
77
+ seen.add(filePath);
78
+ }
79
+ return deepFreeze(data as unknown as ReleaseInputSnapshot);
80
+ }
81
+ export function readReleaseInputSnapshot(root: string, ref: ReleaseInputSnapshotRef): Readonly<ReleaseInputSnapshot> {
82
+ exactObject(ref, ['path', 'hash', 'sha256'], 'release input snapshot reference');
83
+ const bytes = readInputBytes(root, ref.path);
84
+ if (inputDigest(bytes) !== ref.sha256) throw Error('Release input snapshot bytes changed');
85
+ const snapshot = parseReleaseInputSnapshot(JSON.parse(bytes.toString()));
86
+ if (snapshot.snapshotHash !== ref.hash || bytes.toString() !== encode(snapshot)) throw Error('Release input snapshot reference mismatch');
87
+ return snapshot;
88
+ }
89
+ /** Restore into a new external directory only. This grants no execution/readiness authority. */
90
+ export function restoreReleaseInputs(root: string, ref: ReleaseInputSnapshotRef, destination: string) {
91
+ const snapshot = readReleaseInputSnapshot(root, ref);
92
+ if (typeof destination !== 'string' || !path.isAbsolute(destination)) throw Error('Release restore destination must be absolute');
93
+ const output = path.join(fs.realpathSync(path.dirname(destination)), path.basename(destination));
94
+ const source = fs.realpathSync(root);
95
+ if (output === source || output.startsWith(source + path.sep)) throw Error('Release inputs must be restored outside the current project');
96
+ if (fs.existsSync(output)) throw Error('Release restore destination already exists');
97
+ fs.mkdirSync(output, { mode: 0o700 });
98
+ try {
99
+ for (const file of snapshot.files) {
100
+ const target = confinedFile(output, file.path);
101
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
102
+ fs.writeFileSync(target, Buffer.from(file.base64, 'base64'), { flag: 'wx', mode: 0o600 });
103
+ }
104
+ return deepFreeze({ status: 'inputs-restored' as const, destination: output, contractSource: snapshot.contractSource,
105
+ snapshotHash: snapshot.snapshotHash, files: snapshot.files.map(({ path, digest }) => ({ path, digest })) });
106
+ } catch (error) { fs.rmSync(output, { recursive: true, force: true }); throw error; }
107
+ }