diff --git a/.github/workflows/release-metadata.yml b/.github/workflows/release-metadata.yml index d0ff5577d..837cc133a 100644 --- a/.github/workflows/release-metadata.yml +++ b/.github/workflows/release-metadata.yml @@ -7,6 +7,13 @@ name: Release Metadata # resolved against sources. on: pull_request: + # Includes labeled and unlabeled so adding `deployment-record` re-runs the manifest guard + # below. Listing types at all replaces the default set, so the defaults are repeated here. + types: [opened, edited, synchronize, reopened, labeled, unlabeled, ready_for_review] + # A pull request touching none of the paths below never starts this workflow, so no job in + # it reports a conclusion on that pull request. Harmless while these are optional checks, + # and the thing to settle before marking any of them required: a required check that never + # reports blocks the merge. paths: - "deployments/**" - ".github/abi-contracts.txt" @@ -29,3 +36,39 @@ jobs: - name: Validate deployment manifests and the contract list run: bun scripts/js/release-metadata.mjs validate + + # Live network manifests record what is deployed on a real chain and ship verbatim into the + # deployments.json release asset, so they are written by a deploy and never by a code change + # (CONTRIBUTING.md). The CREATE3 parity gates compare deployments/expected.json rather than + # these files, so this job is where that rule is enforced. A pull request that records a real + # deploy carries the `deployment-record` label and is exempt. + # Runs on every pull request rather than being skipped for a labelled one: a skipped job + # reports no conclusion, so making this a required check would leave a correctly labelled + # deploy pull request unable to merge. The label is read inside the step instead. + live-manifests-unchanged: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Reject edits to a live network manifest + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + IS_DEPLOY_RECORD: ${{ contains(github.event.pull_request.labels.*.name, 'deployment-record') }} + run: | + changed=$(git diff --name-only "$BASE_SHA"...HEAD -- 'deployments/*/*.json') + if [ -z "$changed" ]; then + echo "No live network manifest was edited." + exit 0 + fi + if [ "$IS_DEPLOY_RECORD" = "true" ]; then + echo "Live network manifest edited under the 'deployment-record' label:" + printf ' %s\n' $changed + exit 0 + fi + echo "::error::A code pull request must not edit a live network manifest." + printf ' %s\n' $changed + echo "Update deployments/expected.json instead; the live file is written by a deploy." + echo "If this pull request records a real deploy, add the 'deployment-record' label." + exit 1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 150ae8b2d..12c1b6897 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,7 +135,7 @@ Any new contract address that other contracts need to read must be looked up thr If you are adding a new contract category, add a `bytes32` key for it in `DotnsConstants.sol`, wire it up in `WireDeployments.s.sol` (including its entry in `_registryEntries`, so its code identity is declared and verified with the rest), and list the contract and its interface in `.github/abi-contracts.txt` so their ABIs ship in the release artifact. Read it the same way every existing contract does. Give the contract the standard `version()` mirror — a `view` that returns `protocolRegistry.protocolVersion()`, copied from any existing contract — and never a hardcoded version constant: what a network runs is declared once, on the protocol registry, every contract reports that one value, and per-contract identity is the declared codehash the deploy pipeline writes, not a self-report compiled into the bytecode. -A change that moves or adds an address (a new salt, a new contract, a contract restructured behind a proxy) must update `deployments/expected.json` in the same PR — that diff is where review sees the move — and must NOT touch any `deployments//.json`. Those are records of live networks, updated only by a real deploy on that network; editing one from a code PR publishes an address nothing is deployed at. The expected set diverging from a network's manifest is normal and means a redeploy or migration is owed on that network — see "Network manifests and the expected set" in `DEPLOYMENTS.md`. +A change that moves or adds an address (a new salt, a new contract, a contract restructured behind a proxy) must update `deployments/expected.json` in the same PR — that diff is where review sees the move — and must NOT touch any `deployments//.json`. Those are records of live networks, updated only by a real deploy on that network; editing one from a code PR publishes an address nothing is deployed at. The expected set diverging from a network's manifest is normal and means a redeploy or migration is owed on that network — see "Network manifests and the expected set" in `DEPLOYMENTS.md`. CI enforces this: `release-metadata.yml` fails a pull request that edits a live manifest unless the pull request carries the `deployment-record` label, which is how a real deploy records its addresses. Bad — the registrar address is frozen at construction, so rotating it needs an upgrade: @@ -277,7 +277,7 @@ forge test --no-match-path 'test/fork/**' 2. Delete the paired fork test under `test/fork/`. 3. Delete every `*Old.sol` and `I*Old.sol` referenced only by the upgrade script. 4. Delete temporary forge artefacts: `broadcast/