Skip to content

Commit 7288d30

Browse files
cut-release: regenerate candidate before freezing it, not after
script/cut-release.sh is what rainix-tag-release runs as its snapshot-generate-cmd, so this is the code that will cut this repo's first real release (rain.factory#46 step 3). It froze the numbered snapshot from the committed candidate and only then re-ran BuildPointers. That order is silently wrong whenever the committed candidate has drifted from the source. The copy freezes the stale bytes into src/generated/<tag>/, which the frozen-snapshots-append-only gate then protects forever, and the regeneration immediately moves candidate on to the real ones. The release publishes a LibCloneFactoryDeploy aliasing the new address while permanently recording the old one as what that version shipped. Nothing downstream catches it. testCandidateSelfConsistent compares the REGENERATED candidate to the source, so it is green either way, and no test compares a numbered dir to candidate. The append-only gate only sees an added dir. The failure is invisible until someone tries to reproduce the release. Reordered so the frozen dir is a copy of a candidate already known to match the source, with forge fmt also moved ahead of the copy so the two are byte-identical rather than differing by formatting. Added a diff -r assertion after the copy so the property is checked, not assumed. No behaviour change when candidate is already fresh, which is the normal case CI enforces on main. This is the abnormal case: tagging while main is red. Docs corrected to match — CLAUDE.md stated the old order in two places.
1 parent 5640894 commit 7288d30

2 files changed

Lines changed: 36 additions & 15 deletions

File tree

‎CLAUDE.md‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ as the `rain-factory` Soldeer dependency, so they are read under
8181
tag. Never regenerated.
8282
- `script/BuildPointers.sol` — Regenerates `candidate` and the
8383
`LibCloneFactoryDeploy` alias. Never writes a numbered snapshot.
84-
- `script/cut-release.sh` — Freezes `candidate` as `src/generated/<tag>/` at
85-
release time. The only thing that creates a numbered snapshot.
84+
- `script/cut-release.sh` — Regenerates `candidate`, then freezes it as
85+
`src/generated/<tag>/` at release time. The only thing that creates a numbered
86+
snapshot. Regenerating first is what makes the frozen record equal to the pins
87+
the release actually publishes.
8688
- `script/Deploy.sol` — The Zoltu deploy script.
8789

8890
## Solidity Conventions
@@ -125,10 +127,10 @@ uses the **rolling-candidate** model:
125127
`rain-factory-deploy` revisions.
126128
- A human pushes a `sol-v<version>` tag, which runs `rainix-tag-release`: it
127129
writes the version from the tag into `foundry.toml`, runs
128-
`bash script/cut-release.sh` (which copies `candidate` to
129-
`src/generated/<tag>/`, then regenerates), verifies the live chains match the
130-
pins with `forge test`, publishes `rain-factory-deploy` to Soldeer, and
131-
commits the new snapshot back to `main`.
130+
`bash script/cut-release.sh` (which regenerates `candidate`, then copies it to
131+
`src/generated/<tag>/`), verifies the live chains match the pins with
132+
`forge test`, publishes `rain-factory-deploy` to Soldeer, and commits the new
133+
snapshot back to `main`.
132134
- The on-chain deploy happens **before** tagging, via the manual dispatch above;
133135
`rainix-tag-release` never broadcasts, it only attests.
134136
- Existing `src/generated/<tag>/` snapshots are frozen: a release adds a new tag

‎script/cut-release.sh‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/env bash
22
# SPDX-License-Identifier: LicenseRef-DCL-1.0
33
# SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
4-
# Freeze the rolling `candidate` snapshot as a numbered release snapshot, then
5-
# regenerate the deploy pointer lib.
4+
# Regenerate the rolling `candidate` snapshot, then freeze it as a numbered
5+
# release snapshot.
66
#
77
# Invoked by rainix-tag-release as its `snapshot-generate-cmd`, AFTER the
88
# reusable has resolved the release version from the pushed `sol-vX.Y.Z` tag and
@@ -13,8 +13,8 @@
1313
# source compiles to (regenerated every BuildPointers run). A numbered snapshot
1414
# (`0_1_5/`, …) is a FROZEN copy of `candidate` taken at the instant a tag
1515
# releases it — it never changes again (the frozen-snapshots-append-only gate
16-
# enforces this). This script performs that copy, then re-runs BuildPointers so
17-
# the pointer lib is regenerated on top of the freshly cut tree.
16+
# enforces this). This script regenerates `candidate` from the current source and
17+
# then performs that copy.
1818
set -euo pipefail
1919

2020
VERSION="$(grep -m1 -E '^version = ' foundry.toml | sed -E 's/^version = "([^"]+)"/\1/')"
@@ -43,11 +43,30 @@ if [ -d "src/generated/${TAG}" ]; then
4343
exit 1
4444
fi
4545

46+
# Regenerate FIRST, freeze SECOND. The numbered dir must record what this release
47+
# actually publishes, and what it publishes is `candidate` — the pin lib aliases
48+
# it — so the copy has to come from a `candidate` already known to match the
49+
# current source.
50+
#
51+
# The other order is silently wrong whenever the committed `candidate` has
52+
# drifted from the source: the copy freezes the stale bytes into a dir the
53+
# append-only gate then protects forever, while this regeneration moves
54+
# `candidate` on to the real ones. Nothing downstream catches it —
55+
# `testCandidateSelfConsistent` checks the *regenerated* `candidate` against the
56+
# source, and no test compares a numbered dir to `candidate` — so the release
57+
# publishes one address and permanently records another.
58+
#
59+
# `forge fmt` also runs before the copy, so the frozen dir is byte-identical to
60+
# `candidate` rather than to its pre-format form.
61+
forge script ./script/BuildPointers.sol
62+
forge fmt
63+
4664
echo "cut-release: freezing candidate -> src/generated/${TAG}"
4765
cp -r src/generated/candidate "src/generated/${TAG}"
4866

49-
# Regenerate candidate (idempotent — source unchanged) and the pointer lib. The
50-
# lib keeps pointing at `candidate`: "current" never advances to the numbered
51-
# dir, which is only a historical record of what this release shipped.
52-
forge script ./script/BuildPointers.sol
53-
forge fmt
67+
# The point of the ordering above, asserted rather than assumed: the frozen
68+
# record and the published pin are the same bytes.
69+
if ! diff -r src/generated/candidate "src/generated/${TAG}"; then
70+
echo "cut-release: src/generated/${TAG} does not match src/generated/candidate after the copy" >&2
71+
exit 1
72+
fi

0 commit comments

Comments
 (0)