You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Host a BuildScript abstract base here, with run() and cutRelease() concrete on it, and move the other deploy repos onto it.
Toward rainlanguage/rainix#273, which is still open. rain.deploy already solved this for itself; the other consumers have not.
The state today, verified
rainix-tag-release is adopted by three of the four .deploy repos. The Solidity half is adopted by one:
repo
CI lifecycle
script
entry points
rainlanguage/rain.deploy
rainix-tag-release
script/Build.sol
run()andcutRelease()
rainlanguage/rain.factory.deploy
rainix-tag-release
script/BuildPointers.sol
run() only
S01-Issuer/st0x.deploy
rainix-tag-release
script/BuildPointers.sol
run() only
rainlanguage/dvin.deploy
no package-release.yaml
—
—
So two repos have the manual-release workflow pointed at a script with a single entry point. The workflow chooses which entry point to call; it cannot stop a run() that freezes from freezing. st0x.deploy is the repo whose incident produced rainix#273, and it is one of the two.
Why a base rather than three copies
The split has to be structural or it decays. From the design that rainlanguage/rain.sol.codegen#32 argued and this repo then proved out:
abstractcontractBuildScriptisScript {
function build() internalvirtual; // consumer implementsfunction snapshotContractNames() internalviewvirtualreturns (string[] memory);
function run() external { build(); } // CI: regenerate, cuts nothingfunction cutRelease() external { build(); freeze(); } // the only path that cuts
}
Both entry points concrete on the base means a consumer implements two hooks and has nowhere to cut from the path CI runs. A consumer cannot re-merge them by forgetting.
cutRelease() regenerates before freezing, because freezing stale output records a lie — which is what script/Build.sol here already does.
An env-var gate was tried first and discarded: forge runs test contracts concurrently, the env is process-global, and the opt-in raced — one test setting true flipped another test's false mid-run. A gate that cannot be tested in isolation is not a gate worth shipping. The type system carries the decision instead.
Why here and not rain.sol.codegen
rain.sol.codegen#32 placed it there, arguing that hosting it in rain.deploy would force rain-deploy -> rain-sol-codegen on everyone wanting deployZoltu. That argument no longer holds, and the placement is now impossible:
The dependency it was avoiding already exists. This repo's foundry.toml declares rain-sol-codegen = "0.1.6".
codegen cannot host it.cutRelease() calls freeze, and freeze lives in LibRainDeploySnapshot here. LibSnapshot was deleted from codegen on 2026-08-13 (49ffe9fd, "chore: remove dead LibSnapshot"). A base in codegen calling freeze would need codegen to depend on rain.deploy, which already depends on codegen — a cycle.
The dependency graph makes it free for the consumers.rain.factory.deploy and st0x.deploy both already declare rain-deploy AND rain-sol-codegen, so a base here costs them no new dependency.
It matches what each repo is. codegen emits source text and knows nothing of releases, tags or chains. Release lifecycle — the append-only record, deployTag(), cutRelease(), the frozen-snapshot checks — is this repo's whole subject.
Script comes into the dependency surface with the base, which is already paid: RainDeployBroadcast inherits it.
Scope
src/abstract/BuildScript.sol here, with run()/cutRelease() concrete and the two hooks virtual.
script/Build.sol here inherits it, so this repo's bespoke split becomes the shared one and the base has a live consumer from the first commit.
Publish, then move rain.factory.deploy and st0x.deploy onto it.
Decide whether dvin.deploy is in scope — it has no package-release.yaml at all.
Both consumers are behind on versions and will need bumping regardless: rain.factory.deploy is on rain-deploy 0.1.3 / rain-sol-codegen 0.1.0, st0x.deploy on 0.1.4 / 0.1.3.
Replaces
rainlanguage/rain.sol.codegen#32, closed. Its diagnosis and design stand; its branch patches src/lib/LibSnapshot.sol, which no longer exists, so the 163 lines are not salvageable.
Ask
Host a
BuildScriptabstract base here, withrun()andcutRelease()concrete on it, and move the other deploy repos onto it.Toward rainlanguage/rainix#273, which is still open.
rain.deployalready solved this for itself; the other consumers have not.The state today, verified
rainix-tag-releaseis adopted by three of the four.deployrepos. The Solidity half is adopted by one:rainlanguage/rain.deployrainix-tag-releasescript/Build.solrun()andcutRelease()rainlanguage/rain.factory.deployrainix-tag-releasescript/BuildPointers.solrun()onlyS01-Issuer/st0x.deployrainix-tag-releasescript/BuildPointers.solrun()onlyrainlanguage/dvin.deploypackage-release.yamlSo two repos have the manual-release workflow pointed at a script with a single entry point. The workflow chooses which entry point to call; it cannot stop a
run()that freezes from freezing.st0x.deployis the repo whose incident produced rainix#273, and it is one of the two.Why a base rather than three copies
The split has to be structural or it decays. From the design that rainlanguage/rain.sol.codegen#32 argued and this repo then proved out:
Both entry points concrete on the base means a consumer implements two hooks and has nowhere to cut from the path CI runs. A consumer cannot re-merge them by forgetting.
cutRelease()regenerates before freezing, because freezing stale output records a lie — which is whatscript/Build.solhere already does.An env-var gate was tried first and discarded: forge runs test contracts concurrently, the env is process-global, and the opt-in raced — one test setting
trueflipped another test'sfalsemid-run. A gate that cannot be tested in isolation is not a gate worth shipping. The type system carries the decision instead.Why here and not rain.sol.codegen
rain.sol.codegen#32 placed it there, arguing that hosting it in rain.deploy would force
rain-deploy -> rain-sol-codegenon everyone wantingdeployZoltu. That argument no longer holds, and the placement is now impossible:foundry.tomldeclaresrain-sol-codegen = "0.1.6".cutRelease()calls freeze, and freeze lives inLibRainDeploySnapshothere.LibSnapshotwas deleted from codegen on 2026-08-13 (49ffe9fd, "chore: remove dead LibSnapshot"). A base in codegen calling freeze would need codegen to depend on rain.deploy, which already depends on codegen — a cycle.rain.factory.deployandst0x.deployboth already declarerain-deployANDrain-sol-codegen, so a base here costs them no new dependency.deployTag(),cutRelease(), the frozen-snapshot checks — is this repo's whole subject.Scriptcomes into the dependency surface with the base, which is already paid:RainDeployBroadcastinherits it.Scope
src/abstract/BuildScript.solhere, withrun()/cutRelease()concrete and the two hooks virtual.script/Build.solhere inherits it, so this repo's bespoke split becomes the shared one and the base has a live consumer from the first commit.rain.factory.deployandst0x.deployonto it.script/BuildPointers.soltoscript/Build.solin the same change. rainix'srainix-copy-artifactsmatchesscript/Build.solexactly and hard-errors otherwise, so their committed-artifact currency check is not running today. See README tells consumers to write script/BuildPointers.sol, which rainix CI rejects outright — plus three more stale claims rain.sol.codegen#42.dvin.deployis in scope — it has nopackage-release.yamlat all.Both consumers are behind on versions and will need bumping regardless:
rain.factory.deployis onrain-deploy 0.1.3/rain-sol-codegen 0.1.0,st0x.deployon0.1.4/0.1.3.Replaces
rainlanguage/rain.sol.codegen#32, closed. Its diagnosis and design stand; its branch patches
src/lib/LibSnapshot.sol, which no longer exists, so the 163 lines are not salvageable.