gate-evm-tools 1.0.26 → 1.0.28
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.
- package/README.md +14 -0
- package/bin/workflow.cjs +19 -0
- package/dist/core/BytecodeChecker.d.ts.map +1 -1
- package/dist/core/BytecodeChecker.js +16 -1
- package/dist/core/BytecodeChecker.js.map +1 -1
- package/dist/core/legacyProxyArtifact.d.ts +6 -0
- package/dist/core/legacyProxyArtifact.d.ts.map +1 -0
- package/dist/core/legacyProxyArtifact.js +36 -0
- package/dist/core/legacyProxyArtifact.js.map +1 -0
- package/dist-runtime/config/hardhatAdapters.d.ts +7 -0
- package/dist-runtime/config/hardhatAdapters.js +61 -0
- package/dist-runtime/config/hardhatAdapters.js.map +1 -0
- package/dist-runtime/config/networks.d.ts +32 -0
- package/dist-runtime/config/networks.js +275 -0
- package/dist-runtime/config/networks.js.map +1 -0
- package/dist-runtime/config/openzeppelin.d.ts +28 -0
- package/dist-runtime/config/openzeppelin.js +144 -0
- package/dist-runtime/config/openzeppelin.js.map +1 -0
- package/dist-runtime/config/rpc.d.ts +7 -0
- package/dist-runtime/config/rpc.js +19 -0
- package/dist-runtime/config/rpc.js.map +1 -0
- package/dist-runtime/config/zksyncSolcPath.d.ts +1 -0
- package/dist-runtime/config/zksyncSolcPath.js +61 -0
- package/dist-runtime/config/zksyncSolcPath.js.map +1 -0
- package/dist-runtime/migrate.d.ts +13 -0
- package/dist-runtime/migrate.js +208 -0
- package/dist-runtime/migrate.js.map +1 -0
- package/dist-runtime/package.json +1 -0
- package/dist-runtime/projectHardhat.d.ts +4 -0
- package/dist-runtime/projectHardhat.js +16 -0
- package/dist-runtime/projectHardhat.js.map +1 -0
- package/dist-runtime/runtime-identity.json +4 -0
- package/dist-runtime/template-migration.json +293 -0
- package/dist-runtime/workflow/checkCode.d.ts +1 -0
- package/dist-runtime/workflow/checkCode.js +16 -0
- package/dist-runtime/workflow/checkCode.js.map +1 -0
- package/dist-runtime/workflow/checkCodeFinalize.d.ts +1 -0
- package/dist-runtime/workflow/checkCodeFinalize.js +11 -0
- package/dist-runtime/workflow/checkCodeFinalize.js.map +1 -0
- package/dist-runtime/workflow/checkCodePlan.d.ts +1 -0
- package/dist-runtime/workflow/checkCodePlan.js +11 -0
- package/dist-runtime/workflow/checkCodePlan.js.map +1 -0
- package/dist-runtime/workflow/cli.d.ts +1 -0
- package/dist-runtime/workflow/cli.js +302 -0
- package/dist-runtime/workflow/cli.js.map +1 -0
- package/dist-runtime/workflow/commitContractInfo.d.ts +1 -0
- package/dist-runtime/workflow/commitContractInfo.js +64 -0
- package/dist-runtime/workflow/commitContractInfo.js.map +1 -0
- package/dist-runtime/workflow/create2Hashes.d.ts +1 -0
- package/dist-runtime/workflow/create2Hashes.js +76 -0
- package/dist-runtime/workflow/create2Hashes.js.map +1 -0
- package/dist-runtime/workflow/forkSourceProxy.d.ts +1 -0
- package/dist-runtime/workflow/forkSourceProxy.js +151 -0
- package/dist-runtime/workflow/forkSourceProxy.js.map +1 -0
- package/dist-runtime/workflow/importState.d.ts +1 -0
- package/dist-runtime/workflow/importState.js +193 -0
- package/dist-runtime/workflow/importState.js.map +1 -0
- package/dist-runtime/workflow/lib/authority.d.ts +28 -0
- package/dist-runtime/workflow/lib/authority.js +122 -0
- package/dist-runtime/workflow/lib/authority.js.map +1 -0
- package/dist-runtime/workflow/lib/calldata.d.ts +2 -0
- package/dist-runtime/workflow/lib/calldata.js +8 -0
- package/dist-runtime/workflow/lib/calldata.js.map +1 -0
- package/dist-runtime/workflow/lib/calls.d.ts +23 -0
- package/dist-runtime/workflow/lib/calls.js +316 -0
- package/dist-runtime/workflow/lib/calls.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/args.d.ts +2 -0
- package/dist-runtime/workflow/lib/checkCode/args.js +68 -0
- package/dist-runtime/workflow/lib/checkCode/args.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/command.d.ts +5 -0
- package/dist-runtime/workflow/lib/checkCode/command.js +35 -0
- package/dist-runtime/workflow/lib/checkCode/command.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/dependencies.d.ts +6 -0
- package/dist-runtime/workflow/lib/checkCode/dependencies.js +125 -0
- package/dist-runtime/workflow/lib/checkCode/dependencies.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/finalize.d.ts +17 -0
- package/dist-runtime/workflow/lib/checkCode/finalize.js +174 -0
- package/dist-runtime/workflow/lib/checkCode/finalize.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/index.d.ts +3 -0
- package/dist-runtime/workflow/lib/checkCode/index.js +20 -0
- package/dist-runtime/workflow/lib/checkCode/index.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/plan.d.ts +22 -0
- package/dist-runtime/workflow/lib/checkCode/plan.js +145 -0
- package/dist-runtime/workflow/lib/checkCode/plan.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCodeReport.d.ts +25 -0
- package/dist-runtime/workflow/lib/checkCodeReport.js +102 -0
- package/dist-runtime/workflow/lib/checkCodeReport.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCodeRunner.d.ts +87 -0
- package/dist-runtime/workflow/lib/checkCodeRunner.js +964 -0
- package/dist-runtime/workflow/lib/checkCodeRunner.js.map +1 -0
- package/dist-runtime/workflow/lib/cliTool.d.ts +10 -0
- package/dist-runtime/workflow/lib/cliTool.js +25 -0
- package/dist-runtime/workflow/lib/cliTool.js.map +1 -0
- package/dist-runtime/workflow/lib/config.d.ts +33 -0
- package/dist-runtime/workflow/lib/config.js +100 -0
- package/dist-runtime/workflow/lib/config.js.map +1 -0
- package/dist-runtime/workflow/lib/contractInfoCandidates.d.ts +82 -0
- package/dist-runtime/workflow/lib/contractInfoCandidates.js +441 -0
- package/dist-runtime/workflow/lib/contractInfoCandidates.js.map +1 -0
- package/dist-runtime/workflow/lib/controlledMulticallAbi.d.ts +2 -0
- package/dist-runtime/workflow/lib/controlledMulticallAbi.js +10 -0
- package/dist-runtime/workflow/lib/controlledMulticallAbi.js.map +1 -0
- package/dist-runtime/workflow/lib/create2.d.ts +49 -0
- package/dist-runtime/workflow/lib/create2.js +194 -0
- package/dist-runtime/workflow/lib/create2.js.map +1 -0
- package/dist-runtime/workflow/lib/create2Hashes.d.ts +22 -0
- package/dist-runtime/workflow/lib/create2Hashes.js +57 -0
- package/dist-runtime/workflow/lib/create2Hashes.js.map +1 -0
- package/dist-runtime/workflow/lib/deploymentInfo.d.ts +40 -0
- package/dist-runtime/workflow/lib/deploymentInfo.js +240 -0
- package/dist-runtime/workflow/lib/deploymentInfo.js.map +1 -0
- package/dist-runtime/workflow/lib/deploymentRecords.d.ts +137 -0
- package/dist-runtime/workflow/lib/deploymentRecords.js +374 -0
- package/dist-runtime/workflow/lib/deploymentRecords.js.map +1 -0
- package/dist-runtime/workflow/lib/deployments.d.ts +21 -0
- package/dist-runtime/workflow/lib/deployments.js +240 -0
- package/dist-runtime/workflow/lib/deployments.js.map +1 -0
- package/dist-runtime/workflow/lib/erc1967.d.ts +5 -0
- package/dist-runtime/workflow/lib/erc1967.js +29 -0
- package/dist-runtime/workflow/lib/erc1967.js.map +1 -0
- package/dist-runtime/workflow/lib/errors.d.ts +6 -0
- package/dist-runtime/workflow/lib/errors.js +77 -0
- package/dist-runtime/workflow/lib/errors.js.map +1 -0
- package/dist-runtime/workflow/lib/executionId.d.ts +7 -0
- package/dist-runtime/workflow/lib/executionId.js +26 -0
- package/dist-runtime/workflow/lib/executionId.js.map +1 -0
- package/dist-runtime/workflow/lib/executionPlan.d.ts +85 -0
- package/dist-runtime/workflow/lib/executionPlan.js +450 -0
- package/dist-runtime/workflow/lib/executionPlan.js.map +1 -0
- package/dist-runtime/workflow/lib/executionProgress.d.ts +5 -0
- package/dist-runtime/workflow/lib/executionProgress.js +100 -0
- package/dist-runtime/workflow/lib/executionProgress.js.map +1 -0
- package/dist-runtime/workflow/lib/external.d.ts +62 -0
- package/dist-runtime/workflow/lib/external.js +282 -0
- package/dist-runtime/workflow/lib/external.js.map +1 -0
- package/dist-runtime/workflow/lib/externalExecutionScanner.d.ts +64 -0
- package/dist-runtime/workflow/lib/externalExecutionScanner.js +638 -0
- package/dist-runtime/workflow/lib/externalExecutionScanner.js.map +1 -0
- package/dist-runtime/workflow/lib/forkSnapshot.d.ts +34 -0
- package/dist-runtime/workflow/lib/forkSnapshot.js +167 -0
- package/dist-runtime/workflow/lib/forkSnapshot.js.map +1 -0
- package/dist-runtime/workflow/lib/gateTool.d.ts +16 -0
- package/dist-runtime/workflow/lib/gateTool.js +177 -0
- package/dist-runtime/workflow/lib/gateTool.js.map +1 -0
- package/dist-runtime/workflow/lib/gateToolSync.d.ts +22 -0
- package/dist-runtime/workflow/lib/gateToolSync.js +253 -0
- package/dist-runtime/workflow/lib/gateToolSync.js.map +1 -0
- package/dist-runtime/workflow/lib/lock.d.ts +52 -0
- package/dist-runtime/workflow/lib/lock.js +269 -0
- package/dist-runtime/workflow/lib/lock.js.map +1 -0
- package/dist-runtime/workflow/lib/migrationState.d.ts +103 -0
- package/dist-runtime/workflow/lib/migrationState.js +920 -0
- package/dist-runtime/workflow/lib/migrationState.js.map +1 -0
- package/dist-runtime/workflow/lib/operatorExecution.d.ts +28 -0
- package/dist-runtime/workflow/lib/operatorExecution.js +134 -0
- package/dist-runtime/workflow/lib/operatorExecution.js.map +1 -0
- package/dist-runtime/workflow/lib/parameters.d.ts +58 -0
- package/dist-runtime/workflow/lib/parameters.js +323 -0
- package/dist-runtime/workflow/lib/parameters.js.map +1 -0
- package/dist-runtime/workflow/lib/pendingDeploymentReuse.d.ts +29 -0
- package/dist-runtime/workflow/lib/pendingDeploymentReuse.js +277 -0
- package/dist-runtime/workflow/lib/pendingDeploymentReuse.js.map +1 -0
- package/dist-runtime/workflow/lib/proxy.d.ts +18 -0
- package/dist-runtime/workflow/lib/proxy.js +19 -0
- package/dist-runtime/workflow/lib/proxy.js.map +1 -0
- package/dist-runtime/workflow/lib/registryCommit.d.ts +33 -0
- package/dist-runtime/workflow/lib/registryCommit.js +319 -0
- package/dist-runtime/workflow/lib/registryCommit.js.map +1 -0
- package/dist-runtime/workflow/lib/release.d.ts +57 -0
- package/dist-runtime/workflow/lib/release.js +256 -0
- package/dist-runtime/workflow/lib/release.js.map +1 -0
- package/dist-runtime/workflow/lib/releaseFinalize.d.ts +14 -0
- package/dist-runtime/workflow/lib/releaseFinalize.js +571 -0
- package/dist-runtime/workflow/lib/releaseFinalize.js.map +1 -0
- package/dist-runtime/workflow/lib/releaseParameters.d.ts +43 -0
- package/dist-runtime/workflow/lib/releaseParameters.js +184 -0
- package/dist-runtime/workflow/lib/releaseParameters.js.map +1 -0
- package/dist-runtime/workflow/lib/reporter.d.ts +39 -0
- package/dist-runtime/workflow/lib/reporter.js +452 -0
- package/dist-runtime/workflow/lib/reporter.js.map +1 -0
- package/dist-runtime/workflow/lib/resultPaths.d.ts +17 -0
- package/dist-runtime/workflow/lib/resultPaths.js +29 -0
- package/dist-runtime/workflow/lib/resultPaths.js.map +1 -0
- package/dist-runtime/workflow/lib/runtimeVersion.d.ts +10 -0
- package/dist-runtime/workflow/lib/runtimeVersion.js +46 -0
- package/dist-runtime/workflow/lib/runtimeVersion.js.map +1 -0
- package/dist-runtime/workflow/lib/signer.d.ts +10 -0
- package/dist-runtime/workflow/lib/signer.js +105 -0
- package/dist-runtime/workflow/lib/signer.js.map +1 -0
- package/dist-runtime/workflow/lib/signerIdentity.d.ts +1 -0
- package/dist-runtime/workflow/lib/signerIdentity.js +23 -0
- package/dist-runtime/workflow/lib/signerIdentity.js.map +1 -0
- package/dist-runtime/workflow/lib/simulation.d.ts +35 -0
- package/dist-runtime/workflow/lib/simulation.js +282 -0
- package/dist-runtime/workflow/lib/simulation.js.map +1 -0
- package/dist-runtime/workflow/lib/simulationCallers.d.ts +44 -0
- package/dist-runtime/workflow/lib/simulationCallers.js +260 -0
- package/dist-runtime/workflow/lib/simulationCallers.js.map +1 -0
- package/dist-runtime/workflow/lib/sourceRpcPolicy.d.ts +1 -0
- package/dist-runtime/workflow/lib/sourceRpcPolicy.js +43 -0
- package/dist-runtime/workflow/lib/sourceRpcPolicy.js.map +1 -0
- package/dist-runtime/workflow/lib/state.d.ts +6 -0
- package/dist-runtime/workflow/lib/state.js +25 -0
- package/dist-runtime/workflow/lib/state.js.map +1 -0
- package/dist-runtime/workflow/lib/staticValidation.d.ts +22 -0
- package/dist-runtime/workflow/lib/staticValidation.js +124 -0
- package/dist-runtime/workflow/lib/staticValidation.js.map +1 -0
- package/dist-runtime/workflow/lib/zksyncForkCapability.d.ts +27 -0
- package/dist-runtime/workflow/lib/zksyncForkCapability.js +52 -0
- package/dist-runtime/workflow/lib/zksyncForkCapability.js.map +1 -0
- package/dist-runtime/workflow/lib/zksyncForkRunner.d.ts +57 -0
- package/dist-runtime/workflow/lib/zksyncForkRunner.js +344 -0
- package/dist-runtime/workflow/lib/zksyncForkRunner.js.map +1 -0
- package/dist-runtime/workflow/reconcileExternal.d.ts +1 -0
- package/dist-runtime/workflow/reconcileExternal.js +87 -0
- package/dist-runtime/workflow/reconcileExternal.js.map +1 -0
- package/dist-runtime/workflow/reconcileExternalRun.d.ts +1 -0
- package/dist-runtime/workflow/reconcileExternalRun.js +77 -0
- package/dist-runtime/workflow/reconcileExternalRun.js.map +1 -0
- package/dist-runtime/workflow/releaseFinalize.d.ts +1 -0
- package/dist-runtime/workflow/releaseFinalize.js +11 -0
- package/dist-runtime/workflow/releaseFinalize.js.map +1 -0
- package/dist-runtime/workflow/run.d.ts +40 -0
- package/dist-runtime/workflow/run.js +858 -0
- package/dist-runtime/workflow/run.js.map +1 -0
- package/dist-runtime/workflow/staticValidate.d.ts +1 -0
- package/dist-runtime/workflow/staticValidate.js +26 -0
- package/dist-runtime/workflow/staticValidate.js.map +1 -0
- package/dist-runtime/workflow/steps/call.d.ts +3 -0
- package/dist-runtime/workflow/steps/call.js +9 -0
- package/dist-runtime/workflow/steps/call.js.map +1 -0
- package/dist-runtime/workflow/steps/check.d.ts +3 -0
- package/dist-runtime/workflow/steps/check.js +9 -0
- package/dist-runtime/workflow/steps/check.js.map +1 -0
- package/dist-runtime/workflow/steps/custom.d.ts +3 -0
- package/dist-runtime/workflow/steps/custom.js +33 -0
- package/dist-runtime/workflow/steps/custom.js.map +1 -0
- package/dist-runtime/workflow/steps/deploy.d.ts +3 -0
- package/dist-runtime/workflow/steps/deploy.js +596 -0
- package/dist-runtime/workflow/steps/deploy.js.map +1 -0
- package/dist-runtime/workflow/steps/upgrade.d.ts +3 -0
- package/dist-runtime/workflow/steps/upgrade.js +466 -0
- package/dist-runtime/workflow/steps/upgrade.js.map +1 -0
- package/dist-runtime/workflow/steps/view.d.ts +3 -0
- package/dist-runtime/workflow/steps/view.js +9 -0
- package/dist-runtime/workflow/steps/view.js.map +1 -0
- package/docs/direct-package-acceptance.md +20 -0
- package/docs/external-infrastructure-acceptance.md +33 -0
- package/docs/network-runtime-acceptance.md +18 -0
- package/docs/plans/2026-09-09-workflow-extraction.md +16 -0
- package/docs/superpowers/plans/2026-07-22-manifest-bootstrap-transaction.md +50 -0
- package/docs/superpowers/plans/2026-07-22-manifest-review-remediation.md +49 -0
- package/docs/superpowers/plans/2026-08-17-custody-hardhat-signers.md +227 -0
- package/docs/superpowers/plans/2026-09-09-remove-template-infrastructure-contracts.md +11 -0
- package/docs/workflow-reference.md +1181 -0
- package/docs/workflow-runtime-acceptance.md +17 -0
- package/docs/workflow-runtime.md +56 -0
- package/package.json +26 -5
- package/runtime/config/hardhatAdapters.ts +58 -0
- package/runtime/config/networks.ts +305 -0
- package/runtime/config/openzeppelin.ts +173 -0
- package/runtime/config/rpc.ts +27 -0
- package/runtime/config/zksyncSolcPath.ts +59 -0
- package/runtime/hardhat-types.d.ts +2 -0
- package/runtime/migrate.ts +154 -0
- package/runtime/projectHardhat.ts +10 -0
- package/runtime/template-migration.json +293 -0
- package/runtime/workflow/checkCode.ts +13 -0
- package/runtime/workflow/checkCodeFinalize.ts +8 -0
- package/runtime/workflow/checkCodePlan.ts +8 -0
- package/runtime/workflow/cli.ts +356 -0
- package/runtime/workflow/commitContractInfo.ts +63 -0
- package/runtime/workflow/create2Hashes.ts +79 -0
- package/runtime/workflow/forkSourceProxy.ts +193 -0
- package/runtime/workflow/importState.ts +190 -0
- package/runtime/workflow/lib/authority.ts +170 -0
- package/runtime/workflow/lib/calldata.ts +9 -0
- package/runtime/workflow/lib/calls.ts +407 -0
- package/runtime/workflow/lib/checkCode/args.ts +76 -0
- package/runtime/workflow/lib/checkCode/command.ts +36 -0
- package/runtime/workflow/lib/checkCode/dependencies.ts +112 -0
- package/runtime/workflow/lib/checkCode/finalize.ts +198 -0
- package/runtime/workflow/lib/checkCode/index.ts +18 -0
- package/runtime/workflow/lib/checkCode/plan.ts +163 -0
- package/runtime/workflow/lib/checkCodeReport.ts +133 -0
- package/runtime/workflow/lib/checkCodeRunner.ts +1225 -0
- package/runtime/workflow/lib/cliTool.ts +31 -0
- package/runtime/workflow/lib/config.ts +130 -0
- package/runtime/workflow/lib/contractInfoCandidates.ts +690 -0
- package/runtime/workflow/lib/controlledMulticallAbi.ts +6 -0
- package/runtime/workflow/lib/create2.ts +278 -0
- package/runtime/workflow/lib/create2Hashes.ts +83 -0
- package/runtime/workflow/lib/deploymentInfo.ts +286 -0
- package/runtime/workflow/lib/deploymentRecords.ts +576 -0
- package/runtime/workflow/lib/deployments.ts +276 -0
- package/runtime/workflow/lib/erc1967.ts +57 -0
- package/runtime/workflow/lib/errors.ts +95 -0
- package/runtime/workflow/lib/executionId.ts +35 -0
- package/runtime/workflow/lib/executionPlan.ts +550 -0
- package/runtime/workflow/lib/executionProgress.ts +127 -0
- package/runtime/workflow/lib/external.ts +372 -0
- package/runtime/workflow/lib/externalExecutionScanner.ts +892 -0
- package/runtime/workflow/lib/forkSnapshot.ts +231 -0
- package/runtime/workflow/lib/gateTool.ts +261 -0
- package/runtime/workflow/lib/gateToolSync.ts +398 -0
- package/runtime/workflow/lib/lock.ts +388 -0
- package/runtime/workflow/lib/migrationState.ts +1246 -0
- package/runtime/workflow/lib/operatorExecution.ts +174 -0
- package/runtime/workflow/lib/parameters.ts +438 -0
- package/runtime/workflow/lib/pendingDeploymentReuse.ts +428 -0
- package/runtime/workflow/lib/proxy.ts +45 -0
- package/runtime/workflow/lib/registryCommit.ts +455 -0
- package/runtime/workflow/lib/release.ts +329 -0
- package/runtime/workflow/lib/releaseFinalize.ts +711 -0
- package/runtime/workflow/lib/releaseParameters.ts +248 -0
- package/runtime/workflow/lib/reporter.ts +502 -0
- package/runtime/workflow/lib/resultPaths.ts +49 -0
- package/runtime/workflow/lib/runtimeVersion.ts +28 -0
- package/runtime/workflow/lib/signer.ts +124 -0
- package/runtime/workflow/lib/signerIdentity.ts +22 -0
- package/runtime/workflow/lib/simulation.ts +415 -0
- package/runtime/workflow/lib/simulationCallers.ts +335 -0
- package/runtime/workflow/lib/sourceRpcPolicy.ts +41 -0
- package/runtime/workflow/lib/state.ts +25 -0
- package/runtime/workflow/lib/staticValidation.ts +142 -0
- package/runtime/workflow/lib/zksyncForkCapability.ts +80 -0
- package/runtime/workflow/lib/zksyncForkRunner.ts +505 -0
- package/runtime/workflow/reconcileExternal.ts +86 -0
- package/runtime/workflow/reconcileExternalRun.ts +88 -0
- package/runtime/workflow/releaseFinalize.ts +8 -0
- package/runtime/workflow/run.ts +1072 -0
- package/runtime/workflow/staticValidate.ts +23 -0
- package/runtime/workflow/steps/call.ts +10 -0
- package/runtime/workflow/steps/check.ts +10 -0
- package/runtime/workflow/steps/custom.ts +43 -0
- package/runtime/workflow/steps/deploy.ts +772 -0
- package/runtime/workflow/steps/upgrade.ts +599 -0
- package/runtime/workflow/steps/view.ts +10 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import {
|
|
5
|
+
loadSimulationCallerNetwork,
|
|
6
|
+
type SimulationCallerNetworkBinding,
|
|
7
|
+
} from "./simulationCallers";
|
|
8
|
+
|
|
9
|
+
export interface ReleaseFinalizeInput {
|
|
10
|
+
root: string;
|
|
11
|
+
deploymentId: string;
|
|
12
|
+
targetNetworks: string[];
|
|
13
|
+
now?: () => string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ReleaseFinalizeResult {
|
|
17
|
+
releasePath: string;
|
|
18
|
+
statusPath: string;
|
|
19
|
+
handoffPath: string;
|
|
20
|
+
reportPath: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ManifestStep {
|
|
24
|
+
id: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SimulationEvidence {
|
|
29
|
+
executionId: string;
|
|
30
|
+
resultPath: string;
|
|
31
|
+
callerNetworkHash: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ResultEnvelope {
|
|
35
|
+
executionId: string;
|
|
36
|
+
steps: Record<string, unknown>[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PendingWrite {
|
|
40
|
+
filePath: string;
|
|
41
|
+
contents: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface StagedWrite extends PendingWrite {
|
|
45
|
+
temporaryPath: string;
|
|
46
|
+
backupPath: string;
|
|
47
|
+
originalMoved: boolean;
|
|
48
|
+
replacementMoved: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/;
|
|
52
|
+
const SAFE_NETWORK = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
|
|
53
|
+
const HASH = /^[0-9a-f]{64}$/;
|
|
54
|
+
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
55
|
+
const ZERO_ADDRESS = /^0x0{40}$/i;
|
|
56
|
+
const RESULT_NAMES = ["execution", "view", "verification"] as const;
|
|
57
|
+
|
|
58
|
+
const object = (value: unknown, label: string): Record<string, unknown> => {
|
|
59
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
60
|
+
throw new Error(`${label} must be an object`);
|
|
61
|
+
}
|
|
62
|
+
return value as Record<string, unknown>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const optionalObject = (value: unknown): Record<string, unknown> => (
|
|
66
|
+
value && typeof value === "object" && !Array.isArray(value)
|
|
67
|
+
? value as Record<string, unknown>
|
|
68
|
+
: {}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const requiredString = (
|
|
72
|
+
source: Record<string, unknown>,
|
|
73
|
+
key: string,
|
|
74
|
+
label: string,
|
|
75
|
+
): string => {
|
|
76
|
+
const value = source[key];
|
|
77
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
78
|
+
throw new Error(`${label}.${key} must be a non-empty string`);
|
|
79
|
+
}
|
|
80
|
+
return value.trim();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const safeId = (value: string, label: string): string => {
|
|
84
|
+
if (!SAFE_ID.test(value)) throw new Error(`${label} is invalid`);
|
|
85
|
+
return value;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const safeNetwork = (value: string, label: string): string => {
|
|
89
|
+
if (!SAFE_NETWORK.test(value) || ["__proto__", "prototype", "constructor"].includes(value)) {
|
|
90
|
+
throw new Error(`${label} is invalid`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const normalizedAddress = (value: unknown, label: string): string => {
|
|
96
|
+
if (typeof value !== "string") {
|
|
97
|
+
throw new Error(`${label} must be a valid non-zero EVM address`);
|
|
98
|
+
}
|
|
99
|
+
const address = value.trim();
|
|
100
|
+
if (!ADDRESS.test(address) || ZERO_ADDRESS.test(address)) {
|
|
101
|
+
throw new Error(`${label} must be a valid non-zero EVM address`);
|
|
102
|
+
}
|
|
103
|
+
return address;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const normalizedTargets = (values: string[], label: string): string[] => {
|
|
107
|
+
if (!Array.isArray(values)) throw new Error(`${label} must be a unique non-empty list`);
|
|
108
|
+
const targets = values.map((value) => (
|
|
109
|
+
safeNetwork(typeof value === "string" ? value.trim() : "", label)
|
|
110
|
+
));
|
|
111
|
+
if (targets.length === 0 || new Set(targets).size !== targets.length) {
|
|
112
|
+
throw new Error(`${label} must be a unique non-empty list`);
|
|
113
|
+
}
|
|
114
|
+
return targets.sort();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const stringArray = (value: unknown, label: string): string[] => {
|
|
118
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
119
|
+
throw new Error(`${label} must be an array of strings`);
|
|
120
|
+
}
|
|
121
|
+
return normalizedTargets(value as string[], label);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const orderedStringArray = (value: unknown, label: string): string[] => {
|
|
125
|
+
if (!Array.isArray(value) || value.some((item) => (
|
|
126
|
+
typeof item !== "string" || !item.trim() || item !== item.trim()
|
|
127
|
+
))) {
|
|
128
|
+
throw new Error(`${label} must be an ordered array of non-empty strings`);
|
|
129
|
+
}
|
|
130
|
+
return value as string[];
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const sameStrings = (left: string[], right: string[]): boolean => (
|
|
134
|
+
JSON.stringify(left) === JSON.stringify(right)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const readJsonObject = (filePath: string, label: string): Record<string, unknown> => {
|
|
138
|
+
if (!fs.existsSync(filePath)) throw new Error(`${label} is missing`);
|
|
139
|
+
try {
|
|
140
|
+
return object(JSON.parse(fs.readFileSync(filePath, "utf8")) as unknown, label);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof SyntaxError) {
|
|
143
|
+
throw new Error(`${label} must contain valid JSON: ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const validateHash = (value: unknown, label: string): string => {
|
|
150
|
+
if (typeof value !== "string" || !HASH.test(value)) {
|
|
151
|
+
throw new Error(`${label} must be a lowercase sha256 hash`);
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const parseManifestSteps = (manifestText: string, deploymentId: string): ManifestStep[] => {
|
|
157
|
+
let parsed: unknown;
|
|
158
|
+
try {
|
|
159
|
+
parsed = YAML.parse(manifestText) as unknown;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`manifest.yaml must contain valid YAML: ${error instanceof Error ? error.message : String(error)}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const manifest = object(parsed, "manifest.yaml");
|
|
166
|
+
if (manifest.id !== deploymentId) {
|
|
167
|
+
throw new Error("Manifest deployment identity does not match the release task");
|
|
168
|
+
}
|
|
169
|
+
if (!Array.isArray(manifest.steps) || manifest.steps.length === 0) {
|
|
170
|
+
throw new Error("Manifest must contain a non-empty steps array");
|
|
171
|
+
}
|
|
172
|
+
const seen = new Set<string>();
|
|
173
|
+
return manifest.steps.map((value, index) => {
|
|
174
|
+
const step = object(value, `Manifest step ${index}`);
|
|
175
|
+
const id = requiredString(step, "id", `Manifest step ${index}`);
|
|
176
|
+
const kind = requiredString(step, "kind", `Manifest step ${id}`);
|
|
177
|
+
if (step.id !== id || step.kind !== kind) {
|
|
178
|
+
throw new Error(`Manifest step ${id} fields must not contain surrounding whitespace`);
|
|
179
|
+
}
|
|
180
|
+
if (seen.has(id)) throw new Error(`Duplicate Manifest step ${id}`);
|
|
181
|
+
seen.add(id);
|
|
182
|
+
return { id, kind };
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const confinedRegularFile = (input: {
|
|
187
|
+
taskDir: string;
|
|
188
|
+
filePath: string;
|
|
189
|
+
label: string;
|
|
190
|
+
}): string => {
|
|
191
|
+
if (!fs.existsSync(input.filePath)) throw new Error(`${input.label} is missing`);
|
|
192
|
+
const stat = fs.lstatSync(input.filePath);
|
|
193
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
194
|
+
throw new Error(`${input.label} must be a regular non-symlink file`);
|
|
195
|
+
}
|
|
196
|
+
const taskRoot = fs.realpathSync(input.taskDir);
|
|
197
|
+
const realFilePath = fs.realpathSync(input.filePath);
|
|
198
|
+
if (!realFilePath.startsWith(`${taskRoot}${path.sep}`)) {
|
|
199
|
+
throw new Error(`${input.label} escapes the deployment task directory`);
|
|
200
|
+
}
|
|
201
|
+
return realFilePath;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const resolveExecutionResult = (input: {
|
|
205
|
+
taskDir: string;
|
|
206
|
+
network: string;
|
|
207
|
+
resultPath: string;
|
|
208
|
+
}): { absolutePath: string; normalizedPath: string; pathExecutionId: string } => {
|
|
209
|
+
const normalizedPath = input.resultPath.replaceAll("\\", "/");
|
|
210
|
+
const parts = normalizedPath.split("/");
|
|
211
|
+
if (
|
|
212
|
+
path.isAbsolute(input.resultPath)
|
|
213
|
+
|| parts.length !== 5
|
|
214
|
+
|| parts[0] !== "results"
|
|
215
|
+
|| parts[1] !== input.network
|
|
216
|
+
|| parts[3] !== "simulation"
|
|
217
|
+
|| parts[4] !== "execution.json"
|
|
218
|
+
|| parts.some((part) => !part || part === "." || part === "..")
|
|
219
|
+
) {
|
|
220
|
+
throw new Error(`Simulation result path is invalid for ${input.network}`);
|
|
221
|
+
}
|
|
222
|
+
const pathExecutionId = safeId(parts[2], `${input.network} simulation executionId`);
|
|
223
|
+
const filePath = path.resolve(input.taskDir, ...parts);
|
|
224
|
+
return {
|
|
225
|
+
absolutePath: confinedRegularFile({
|
|
226
|
+
taskDir: input.taskDir,
|
|
227
|
+
filePath,
|
|
228
|
+
label: `${input.network} simulation execution result`,
|
|
229
|
+
}),
|
|
230
|
+
normalizedPath,
|
|
231
|
+
pathExecutionId,
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const readResultEnvelope = (input: {
|
|
236
|
+
taskDir: string;
|
|
237
|
+
deploymentId: string;
|
|
238
|
+
network: string;
|
|
239
|
+
filePath: string;
|
|
240
|
+
resultName: typeof RESULT_NAMES[number];
|
|
241
|
+
expectedExecutionId?: string;
|
|
242
|
+
}): ResultEnvelope => {
|
|
243
|
+
const label = `${input.network} simulation ${input.resultName} result`;
|
|
244
|
+
const filePath = confinedRegularFile({
|
|
245
|
+
taskDir: input.taskDir,
|
|
246
|
+
filePath: input.filePath,
|
|
247
|
+
label,
|
|
248
|
+
});
|
|
249
|
+
const result = readJsonObject(filePath, label);
|
|
250
|
+
const executionId = safeId(requiredString(result, "executionId", label), "executionId");
|
|
251
|
+
if (
|
|
252
|
+
result.taskId !== input.deploymentId
|
|
253
|
+
|| result.target !== input.network
|
|
254
|
+
|| result.mode !== "simulate"
|
|
255
|
+
|| (input.expectedExecutionId !== undefined && executionId !== input.expectedExecutionId)
|
|
256
|
+
|| !Array.isArray(result.steps)
|
|
257
|
+
) {
|
|
258
|
+
throw new Error(`Canonical simulation ${input.resultName} evidence is invalid for ${input.network}`);
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
executionId,
|
|
262
|
+
steps: result.steps.map((step, index) => object(step, `${label}.steps[${index}]`)),
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const validateTerminalCallerEvidence = (input: {
|
|
267
|
+
network: string;
|
|
268
|
+
terminal: Record<string, unknown>;
|
|
269
|
+
binding: SimulationCallerNetworkBinding;
|
|
270
|
+
manifestSteps: ManifestStep[];
|
|
271
|
+
}): void => {
|
|
272
|
+
const selectedStepIds = orderedStringArray(
|
|
273
|
+
input.terminal.selectedStepIds,
|
|
274
|
+
`${input.network} simulation selectedStepIds`,
|
|
275
|
+
);
|
|
276
|
+
const manifestStepIds = input.manifestSteps.map((step) => step.id);
|
|
277
|
+
const callers = input.terminal.simulationCallers;
|
|
278
|
+
if (
|
|
279
|
+
input.terminal.status !== "succeeded"
|
|
280
|
+
|| input.terminal.signerAddress !== undefined
|
|
281
|
+
|| !sameStrings(selectedStepIds, manifestStepIds)
|
|
282
|
+
|| input.terminal.simulationCallerNetworkHash !== input.binding.networkHash
|
|
283
|
+
|| !Array.isArray(callers)
|
|
284
|
+
|| callers.length !== input.binding.resolved.length
|
|
285
|
+
) {
|
|
286
|
+
throw new Error(`Canonical simulation caller completion evidence is invalid for ${input.network}`);
|
|
287
|
+
}
|
|
288
|
+
for (const [index, expected] of input.binding.resolved.entries()) {
|
|
289
|
+
const actual = object(callers[index], `${input.network} simulation caller ${index}`);
|
|
290
|
+
const callerAddress = normalizedAddress(
|
|
291
|
+
actual.callerAddress,
|
|
292
|
+
`${input.network} simulation caller ${expected.stepId}`,
|
|
293
|
+
);
|
|
294
|
+
if (
|
|
295
|
+
actual.stepId !== expected.stepId
|
|
296
|
+
|| callerAddress.toLowerCase() !== expected.callerAddress.toLowerCase()
|
|
297
|
+
) {
|
|
298
|
+
throw new Error(`Ordered simulation caller evidence is invalid for ${input.network}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const validateStepCallerAnnotations = (input: {
|
|
304
|
+
network: string;
|
|
305
|
+
binding: SimulationCallerNetworkBinding;
|
|
306
|
+
manifestSteps: ManifestStep[];
|
|
307
|
+
results: Record<typeof RESULT_NAMES[number], ResultEnvelope>;
|
|
308
|
+
}): void => {
|
|
309
|
+
const expectedByStep = new Map(input.binding.resolved.map((caller) => [
|
|
310
|
+
caller.stepId,
|
|
311
|
+
caller.callerAddress,
|
|
312
|
+
]));
|
|
313
|
+
const kindByStep = new Map(input.manifestSteps.map((step) => [step.id, step.kind]));
|
|
314
|
+
const seen = new Set<string>();
|
|
315
|
+
for (const resultName of RESULT_NAMES) {
|
|
316
|
+
for (const record of input.results[resultName].steps) {
|
|
317
|
+
if (record.kind === "workflow-simulation-completed") {
|
|
318
|
+
if (resultName !== "execution") {
|
|
319
|
+
throw new Error(`Simulation completion evidence is misplaced for ${input.network}`);
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const stepId = requiredString(
|
|
324
|
+
record,
|
|
325
|
+
"stepId",
|
|
326
|
+
`${input.network} simulation ${resultName} annotation`,
|
|
327
|
+
);
|
|
328
|
+
const expectedCaller = expectedByStep.get(stepId);
|
|
329
|
+
if (!expectedCaller || record.kind !== kindByStep.get(stepId)) {
|
|
330
|
+
throw new Error(`Simulation caller annotation references an unknown Manifest step ${stepId}`);
|
|
331
|
+
}
|
|
332
|
+
const actualCaller = normalizedAddress(
|
|
333
|
+
record.simulationCallerAddress,
|
|
334
|
+
`${input.network} simulation caller annotation for ${stepId}`,
|
|
335
|
+
);
|
|
336
|
+
if (actualCaller.toLowerCase() !== expectedCaller.toLowerCase()) {
|
|
337
|
+
throw new Error(`Simulation caller annotation changed for ${input.network} step ${stepId}`);
|
|
338
|
+
}
|
|
339
|
+
seen.add(stepId);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const missing = input.manifestSteps.find((step) => !seen.has(step.id));
|
|
343
|
+
if (missing) {
|
|
344
|
+
throw new Error(`Missing simulation caller annotation for ${input.network} step ${missing.id}`);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const validateSimulationEvidence = (input: {
|
|
349
|
+
taskDir: string;
|
|
350
|
+
deploymentId: string;
|
|
351
|
+
network: string;
|
|
352
|
+
resultPath: string;
|
|
353
|
+
manifestSteps: ManifestStep[];
|
|
354
|
+
binding: SimulationCallerNetworkBinding;
|
|
355
|
+
}): SimulationEvidence => {
|
|
356
|
+
const executionResult = resolveExecutionResult({
|
|
357
|
+
taskDir: input.taskDir,
|
|
358
|
+
network: input.network,
|
|
359
|
+
resultPath: input.resultPath,
|
|
360
|
+
});
|
|
361
|
+
const resultDirectory = path.dirname(executionResult.absolutePath);
|
|
362
|
+
const execution = readResultEnvelope({
|
|
363
|
+
taskDir: input.taskDir,
|
|
364
|
+
deploymentId: input.deploymentId,
|
|
365
|
+
network: input.network,
|
|
366
|
+
filePath: executionResult.absolutePath,
|
|
367
|
+
resultName: "execution",
|
|
368
|
+
});
|
|
369
|
+
if (execution.executionId !== executionResult.pathExecutionId) {
|
|
370
|
+
throw new Error(`Simulation executionId path does not match evidence for ${input.network}`);
|
|
371
|
+
}
|
|
372
|
+
const results: Record<typeof RESULT_NAMES[number], ResultEnvelope> = {
|
|
373
|
+
execution,
|
|
374
|
+
view: readResultEnvelope({
|
|
375
|
+
taskDir: input.taskDir,
|
|
376
|
+
deploymentId: input.deploymentId,
|
|
377
|
+
network: input.network,
|
|
378
|
+
filePath: path.join(resultDirectory, "view.json"),
|
|
379
|
+
resultName: "view",
|
|
380
|
+
expectedExecutionId: execution.executionId,
|
|
381
|
+
}),
|
|
382
|
+
verification: readResultEnvelope({
|
|
383
|
+
taskDir: input.taskDir,
|
|
384
|
+
deploymentId: input.deploymentId,
|
|
385
|
+
network: input.network,
|
|
386
|
+
filePath: path.join(resultDirectory, "verification.json"),
|
|
387
|
+
resultName: "verification",
|
|
388
|
+
expectedExecutionId: execution.executionId,
|
|
389
|
+
}),
|
|
390
|
+
};
|
|
391
|
+
const terminalRecords = execution.steps.filter((step) => (
|
|
392
|
+
step.kind === "workflow-simulation-completed"
|
|
393
|
+
));
|
|
394
|
+
const terminal = execution.steps.at(-1);
|
|
395
|
+
if (
|
|
396
|
+
terminalRecords.length !== 1
|
|
397
|
+
|| terminal !== terminalRecords[0]
|
|
398
|
+
|| terminal?.kind !== "workflow-simulation-completed"
|
|
399
|
+
) {
|
|
400
|
+
throw new Error(`Canonical simulation completion must be terminal for ${input.network}`);
|
|
401
|
+
}
|
|
402
|
+
validateTerminalCallerEvidence({
|
|
403
|
+
network: input.network,
|
|
404
|
+
terminal,
|
|
405
|
+
binding: input.binding,
|
|
406
|
+
manifestSteps: input.manifestSteps,
|
|
407
|
+
});
|
|
408
|
+
validateStepCallerAnnotations({
|
|
409
|
+
network: input.network,
|
|
410
|
+
binding: input.binding,
|
|
411
|
+
manifestSteps: input.manifestSteps,
|
|
412
|
+
results,
|
|
413
|
+
});
|
|
414
|
+
return {
|
|
415
|
+
executionId: execution.executionId,
|
|
416
|
+
resultPath: executionResult.normalizedPath,
|
|
417
|
+
callerNetworkHash: input.binding.networkHash,
|
|
418
|
+
};
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const writeFilesAtomically = (writes: PendingWrite[]): void => {
|
|
422
|
+
const suffix = `${process.pid}.${Date.now()}`;
|
|
423
|
+
const staged: StagedWrite[] = writes.map((write, index) => ({
|
|
424
|
+
...write,
|
|
425
|
+
temporaryPath: `${write.filePath}.${suffix}.${index}.tmp`,
|
|
426
|
+
backupPath: `${write.filePath}.${suffix}.${index}.bak`,
|
|
427
|
+
originalMoved: false,
|
|
428
|
+
replacementMoved: false,
|
|
429
|
+
}));
|
|
430
|
+
try {
|
|
431
|
+
for (const item of staged) {
|
|
432
|
+
fs.mkdirSync(path.dirname(item.filePath), { recursive: true });
|
|
433
|
+
fs.writeFileSync(item.temporaryPath, item.contents, { flag: "wx" });
|
|
434
|
+
}
|
|
435
|
+
for (const item of staged) {
|
|
436
|
+
if (fs.existsSync(item.filePath)) {
|
|
437
|
+
fs.renameSync(item.filePath, item.backupPath);
|
|
438
|
+
item.originalMoved = true;
|
|
439
|
+
}
|
|
440
|
+
fs.renameSync(item.temporaryPath, item.filePath);
|
|
441
|
+
item.replacementMoved = true;
|
|
442
|
+
}
|
|
443
|
+
} catch (error) {
|
|
444
|
+
for (const item of [...staged].reverse()) {
|
|
445
|
+
try {
|
|
446
|
+
if (item.replacementMoved && fs.existsSync(item.filePath)) fs.unlinkSync(item.filePath);
|
|
447
|
+
if (item.originalMoved && fs.existsSync(item.backupPath)) {
|
|
448
|
+
fs.renameSync(item.backupPath, item.filePath);
|
|
449
|
+
}
|
|
450
|
+
} catch {
|
|
451
|
+
// Preserve the original transaction error after best-effort rollback.
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (const item of staged) {
|
|
455
|
+
try {
|
|
456
|
+
if (fs.existsSync(item.temporaryPath)) fs.unlinkSync(item.temporaryPath);
|
|
457
|
+
if (fs.existsSync(item.backupPath)) fs.unlinkSync(item.backupPath);
|
|
458
|
+
} catch {
|
|
459
|
+
// Preserve the original transaction error after best-effort cleanup.
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
for (const item of staged) {
|
|
465
|
+
try {
|
|
466
|
+
if (fs.existsSync(item.backupPath)) fs.unlinkSync(item.backupPath);
|
|
467
|
+
} catch {
|
|
468
|
+
// The committed formal files are authoritative; a backup cleanup failure is non-fatal.
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const jsonContents = (value: unknown): string => `${JSON.stringify(value, null, 2)}\n`;
|
|
474
|
+
|
|
475
|
+
export const finalizeReleaseVerification = (
|
|
476
|
+
input: ReleaseFinalizeInput,
|
|
477
|
+
): ReleaseFinalizeResult => {
|
|
478
|
+
const deploymentId = safeId(input.deploymentId, "deploymentId");
|
|
479
|
+
const targets = normalizedTargets(input.targetNetworks, "target network");
|
|
480
|
+
const updatedAt = (input.now || (() => new Date().toISOString()))();
|
|
481
|
+
if (!Number.isFinite(Date.parse(updatedAt))) throw new Error("Finalize timestamp is invalid");
|
|
482
|
+
|
|
483
|
+
const taskBase = `scripts/tasks/${deploymentId}`;
|
|
484
|
+
const releasePath = `${taskBase}/release.json`;
|
|
485
|
+
const statusPath = `${taskBase}/status.json`;
|
|
486
|
+
const handoffPath = `${taskBase}/handoff.json`;
|
|
487
|
+
const reportPath = `${taskBase}/docs/release-verification.md`;
|
|
488
|
+
const absolute = (relative: string): string => path.join(input.root, ...relative.split("/"));
|
|
489
|
+
const taskDir = absolute(taskBase);
|
|
490
|
+
|
|
491
|
+
const task = object(
|
|
492
|
+
YAML.parse(fs.readFileSync(path.join(taskDir, "task.yaml"), "utf8")) as unknown,
|
|
493
|
+
"task.yaml",
|
|
494
|
+
);
|
|
495
|
+
const sourceSuperTaskId = safeId(
|
|
496
|
+
requiredString(task, "sourceSuperTaskId", "task.yaml"),
|
|
497
|
+
"sourceSuperTaskId",
|
|
498
|
+
);
|
|
499
|
+
const developmentNetwork = safeNetwork(
|
|
500
|
+
requiredString(task, "developmentNetwork", "task.yaml"),
|
|
501
|
+
"developmentNetwork",
|
|
502
|
+
);
|
|
503
|
+
if (
|
|
504
|
+
task.id !== deploymentId
|
|
505
|
+
|| targets.includes(developmentNetwork)
|
|
506
|
+
|| !sameStrings(stringArray(task.releaseTargetNetworks, "task releaseTargetNetworks"), targets)
|
|
507
|
+
) {
|
|
508
|
+
throw new Error("Release task identity or target networks are invalid");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const manifestText = fs.readFileSync(path.join(taskDir, "manifest.yaml"), "utf8");
|
|
512
|
+
const manifestSteps = parseManifestSteps(manifestText, deploymentId);
|
|
513
|
+
const release = readJsonObject(absolute(releasePath), "release.json");
|
|
514
|
+
const releaseNetworks = object(release.networks, "release.json.networks");
|
|
515
|
+
if (
|
|
516
|
+
release.version !== 1
|
|
517
|
+
|| release.sourceSuperTaskId !== sourceSuperTaskId
|
|
518
|
+
|| release.developmentNetwork !== developmentNetwork
|
|
519
|
+
|| !sameStrings(Object.keys(releaseNetworks).sort(), targets)
|
|
520
|
+
) {
|
|
521
|
+
throw new Error("Release identity or target networks are invalid");
|
|
522
|
+
}
|
|
523
|
+
const releaseManifestHash = validateHash(release.manifestHash, "release.json.manifestHash");
|
|
524
|
+
validateHash(release.artifactHash, "release.json.artifactHash");
|
|
525
|
+
|
|
526
|
+
const evidenceByNetwork: Record<string, SimulationEvidence> = {};
|
|
527
|
+
const nextReleaseNetworks: Record<string, unknown> = {};
|
|
528
|
+
for (const network of targets) {
|
|
529
|
+
const binding = loadSimulationCallerNetwork({
|
|
530
|
+
taskDir,
|
|
531
|
+
targetNetworks: targets,
|
|
532
|
+
network,
|
|
533
|
+
manifestText,
|
|
534
|
+
manifestSteps,
|
|
535
|
+
});
|
|
536
|
+
if (binding.manifestHash !== releaseManifestHash) {
|
|
537
|
+
throw new Error(`Manifest hash changed after release simulation for ${network}`);
|
|
538
|
+
}
|
|
539
|
+
const entry = object(releaseNetworks[network], `release.json.networks.${network}`);
|
|
540
|
+
const resultPath = requiredString(entry, "simulationResult", `release.json.networks.${network}`);
|
|
541
|
+
const simulatedAt = requiredString(entry, "simulatedAt", `release.json.networks.${network}`);
|
|
542
|
+
if (!Number.isFinite(Date.parse(simulatedAt))) {
|
|
543
|
+
throw new Error(`release.json.networks.${network}.simulatedAt is invalid`);
|
|
544
|
+
}
|
|
545
|
+
for (const key of ["manifestHash", "artifactHash", "parametersHash", "configHash"]) {
|
|
546
|
+
validateHash(entry[key], `release.json.networks.${network}.${key}`);
|
|
547
|
+
}
|
|
548
|
+
if (entry.manifestHash !== binding.manifestHash) {
|
|
549
|
+
throw new Error(`Manifest fingerprint changed after release simulation for ${network}`);
|
|
550
|
+
}
|
|
551
|
+
const evidence = validateSimulationEvidence({
|
|
552
|
+
taskDir,
|
|
553
|
+
deploymentId,
|
|
554
|
+
network,
|
|
555
|
+
resultPath,
|
|
556
|
+
manifestSteps,
|
|
557
|
+
binding,
|
|
558
|
+
});
|
|
559
|
+
evidenceByNetwork[network] = evidence;
|
|
560
|
+
nextReleaseNetworks[network] = {
|
|
561
|
+
...entry,
|
|
562
|
+
status: "ready",
|
|
563
|
+
simulationCallerNetworkHash: evidence.callerNetworkHash,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const status = readJsonObject(absolute(statusPath), "status.json");
|
|
568
|
+
if (
|
|
569
|
+
status.sourceSuperTaskId !== sourceSuperTaskId
|
|
570
|
+
|| status.developmentNetwork !== developmentNetwork
|
|
571
|
+
) {
|
|
572
|
+
throw new Error("Release status identity is invalid");
|
|
573
|
+
}
|
|
574
|
+
const phases = optionalObject(status.phases);
|
|
575
|
+
const networks = optionalObject(status.networks);
|
|
576
|
+
const simulationResults = Object.fromEntries(
|
|
577
|
+
targets.map((network) => [network, evidenceByNetwork[network].resultPath]),
|
|
578
|
+
);
|
|
579
|
+
const simulationCallerNetworkHashes = Object.fromEntries(
|
|
580
|
+
targets.map((network) => [network, evidenceByNetwork[network].callerNetworkHash]),
|
|
581
|
+
);
|
|
582
|
+
const nextNetworks = { ...networks };
|
|
583
|
+
for (const network of targets) {
|
|
584
|
+
nextNetworks[network] = {
|
|
585
|
+
...optionalObject(networks[network]),
|
|
586
|
+
parameterStatus: "complete",
|
|
587
|
+
simulationStatus: "succeeded",
|
|
588
|
+
releaseStatus: "ready",
|
|
589
|
+
deployStatus: "pending",
|
|
590
|
+
simulationCallerNetworkHash: evidenceByNetwork[network].callerNetworkHash,
|
|
591
|
+
lastExecutionId: evidenceByNetwork[network].executionId,
|
|
592
|
+
lastExecutionMode: "simulate",
|
|
593
|
+
updatedAt,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const nextStatus = {
|
|
597
|
+
...status,
|
|
598
|
+
status: "release_ready",
|
|
599
|
+
releaseTargetNetworks: targets,
|
|
600
|
+
phases: {
|
|
601
|
+
...phases,
|
|
602
|
+
releaseSimulation: {
|
|
603
|
+
status: "succeeded",
|
|
604
|
+
networks: targets,
|
|
605
|
+
simulationCallerNetworkHashes,
|
|
606
|
+
simulationResults,
|
|
607
|
+
updatedAt,
|
|
608
|
+
},
|
|
609
|
+
releaseVerification: {
|
|
610
|
+
status: "succeeded",
|
|
611
|
+
networks: targets,
|
|
612
|
+
simulationCallerNetworkHashes,
|
|
613
|
+
simulationResults,
|
|
614
|
+
report: reportPath,
|
|
615
|
+
updatedAt,
|
|
616
|
+
},
|
|
617
|
+
deploy: { status: "pending", networks: targets },
|
|
618
|
+
},
|
|
619
|
+
networks: nextNetworks,
|
|
620
|
+
updatedAt,
|
|
621
|
+
};
|
|
622
|
+
const evidence = targets.map((network) => evidenceByNetwork[network].resultPath);
|
|
623
|
+
const nextHandoff = {
|
|
624
|
+
version: 1,
|
|
625
|
+
stageKind: "release_verify",
|
|
626
|
+
status: "release_ready",
|
|
627
|
+
sourceSuperTaskId,
|
|
628
|
+
deploymentId,
|
|
629
|
+
developmentNetwork,
|
|
630
|
+
releaseTargetNetworks: targets,
|
|
631
|
+
readyReleaseNetworks: targets,
|
|
632
|
+
simulationCallerNetworkHashes,
|
|
633
|
+
evidence,
|
|
634
|
+
evidenceDetails: {
|
|
635
|
+
statusPath,
|
|
636
|
+
taskPath: `${taskBase}/task.yaml`,
|
|
637
|
+
releasePath,
|
|
638
|
+
releaseVerificationReport: reportPath,
|
|
639
|
+
simulationResults,
|
|
640
|
+
simulationCallerNetworkHashes,
|
|
641
|
+
},
|
|
642
|
+
updatedAt,
|
|
643
|
+
};
|
|
644
|
+
const report = [
|
|
645
|
+
"# Release Verification Report",
|
|
646
|
+
"",
|
|
647
|
+
"stageKind: release_verify",
|
|
648
|
+
"status: release_ready",
|
|
649
|
+
`deploymentId: ${deploymentId}`,
|
|
650
|
+
`sourceSuperTaskId: ${sourceSuperTaskId}`,
|
|
651
|
+
`developmentNetwork: ${developmentNetwork}`,
|
|
652
|
+
`generatedAt: ${updatedAt}`,
|
|
653
|
+
"",
|
|
654
|
+
"## Ready Networks",
|
|
655
|
+
...targets.flatMap((network) => [
|
|
656
|
+
`- network: ${network}`,
|
|
657
|
+
` simulationResult: ${evidenceByNetwork[network].resultPath}`,
|
|
658
|
+
` executionId: ${evidenceByNetwork[network].executionId}`,
|
|
659
|
+
` simulationCallerNetworkHash: ${evidenceByNetwork[network].callerNetworkHash}`,
|
|
660
|
+
]),
|
|
661
|
+
"",
|
|
662
|
+
].join("\n");
|
|
663
|
+
|
|
664
|
+
writeFilesAtomically([
|
|
665
|
+
{
|
|
666
|
+
filePath: absolute(releasePath),
|
|
667
|
+
contents: jsonContents({ ...release, networks: nextReleaseNetworks }),
|
|
668
|
+
},
|
|
669
|
+
{ filePath: absolute(statusPath), contents: jsonContents(nextStatus) },
|
|
670
|
+
{ filePath: absolute(handoffPath), contents: jsonContents(nextHandoff) },
|
|
671
|
+
{ filePath: absolute(reportPath), contents: report },
|
|
672
|
+
]);
|
|
673
|
+
return { releasePath, statusPath, handoffPath, reportPath };
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
export const runReleaseFinalizeCli = (
|
|
677
|
+
argv: string[],
|
|
678
|
+
defaultRoot: string = process.cwd(),
|
|
679
|
+
): ReleaseFinalizeResult => {
|
|
680
|
+
const values = new Map<string, string[]>();
|
|
681
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
682
|
+
const item = argv[index];
|
|
683
|
+
if (!item.startsWith("--")) continue;
|
|
684
|
+
const equalAt = item.indexOf("=");
|
|
685
|
+
const key = item.slice(2, equalAt > 2 ? equalAt : undefined);
|
|
686
|
+
const next = argv[index + 1];
|
|
687
|
+
const value = equalAt > 2 ? item.slice(equalAt + 1) : next;
|
|
688
|
+
if (!value || value.startsWith("--")) throw new Error(`--${key} requires a value`);
|
|
689
|
+
if (equalAt < 0) index += 1;
|
|
690
|
+
values.set(key, [...(values.get(key) || []), value.trim()]);
|
|
691
|
+
}
|
|
692
|
+
for (const key of values.keys()) {
|
|
693
|
+
if (!new Set(["root", "task", "network"]).has(key)) {
|
|
694
|
+
throw new Error(`Unsupported release finalizer argument --${key}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const one = (key: string, required = false): string | undefined => {
|
|
698
|
+
const items = values.get(key) || [];
|
|
699
|
+
if (items.length > 1) throw new Error(`--${key} may be provided only once`);
|
|
700
|
+
if (required && !items[0]) throw new Error(`--${key} is required`);
|
|
701
|
+
return items[0] || undefined;
|
|
702
|
+
};
|
|
703
|
+
return finalizeReleaseVerification({
|
|
704
|
+
root: path.resolve(one("root") || defaultRoot),
|
|
705
|
+
deploymentId: one("task", true)!,
|
|
706
|
+
targetNetworks: (values.get("network") || [])
|
|
707
|
+
.flatMap((item) => item.split(","))
|
|
708
|
+
.map((item) => item.trim())
|
|
709
|
+
.filter(Boolean),
|
|
710
|
+
});
|
|
711
|
+
};
|