Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/validate-task-status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Validate task status

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
validate-task-status:
runs-on: ubuntu-latest
steps:
- name: Harden the runner
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Check task statuses
run: python3 -B scripts/task-status.py check
- name: Test task status and scaffolding
run: python3 -B -m unittest scripts.tests.test_task_status -v
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ This is a repo of onchain operational tasks. Pending one-off EVM operations live

Standby EVM task lifecycle:

- Canonical standby files contain no real network config, addresses, validations, signatures, or execution records. Invoke by adding only `config/<network>/.env` and a concise signer README with `Status: READY TO SIGN`. Generate invocation artifacts there; keep Forge records task-scoped under `records/`.
- Canonical standby files contain no real network config, addresses, validations, signatures, execution records, or status file. Scaffold an invocation from the repository root with `make new-task STANDBY_OP=<operation> TASK_NETWORK=<network>`, adding only `config/<network>/.env`, a concise signer README with `Status: READY TO SIGN`, and task-root `status.json`. Generate invocation artifacts there; keep Forge records task-scoped under `records/`.
- Run directly from standby. Do not copy the operation into active or modify its canonical Makefile, Solidity, or root `FACILITATOR.md` for an incident. Every standby Make command requires command-line `TASK_NETWORK=<network>`, even for one network; exported values and defaults are forbidden.
- Keep task-root `status.json` current as the invocation progresses, including signature counts and final execution or cancellation. Never put status under `config/<network>/`, which task-origin signing covers. Finalize status and the signer README together; run `make check-task-status` from the repository root. Archival snapshots status and removes it from standby.
- Re-review canonical code when the target ABI, ownership or responder role, base/contracts pin, or signing workflow changes. Pre-review does not replace incident-specific state and transaction validation.
- Archive only when every present network invocation is executed or canceled and all final artifacts are committed. Use `make TASK_NETWORK=<network> archive-task TASK_DATE=<YYYY-MM-DD>` after fetching main. This snapshots all networks and task-scoped records into a dated, self-contained archive and removes only invocation files from standby.
- Keep the operational PR unmerged until all intended invocations are executed or canceled and the dated archive, final artifacts, and standby invocation removals are committed. On `main`, canonical standby must remain unchanged and contain no invocation-specific data before and after merge.
Expand All @@ -12,6 +13,8 @@ Standby EVM task lifecycle:
Active EVM task lifecycle:

- A task directory represents one logical operation and carries its shared Makefile, scripts, and facilitator guide across network rollouts. Add another `config/<network>/` to the existing task instead of creating a duplicate task solely to change networks.
- Scaffold from the repository root with `make new-task TASK_SLUG=<slug> TASK_NETWORK=<network> [TASK_DATE=YYYY-MM-DD]`, or add a network with `make new-task TASK_ID=<task-id> TASK_NETWORK=<network>`. These commands require command-line `TASK_NETWORK` and refuse overwrites.
- Keep task-root `status.json` current as each network progresses, including signature counts and final execution or cancellation. Never put status under `config/<network>/`, which task-origin signing covers. Finalize status and the signer README together; run `make check-task-status` from the repository root.
- A task with multiple network configurations must require `TASK_NETWORK` on each `make` command line. Do not set a mutable default or rely on an exported environment value; use `make TASK_NETWORK=<network> <target>` so the selected network is visible at every invocation.
- Keep the task-root `FACILITATOR.md` authoritative and network-agnostic, using explicit placeholders such as `TASK_NETWORK=<network>` and paths under `config/<network>/`. Per-network facilitator files are discouraged because they duplicate configuration and drift from the shared procedure. Only consider one as a last resort when a network has a materially different procedure that cannot be expressed in the root guide, `.env`, or signer README. If unavoidable, place only the procedural delta in `config/<network>/FACILITATOR.md` and link to it from the root guide.
- Keep a task under `active/evm/tasks/` while any currently intended network rollout remains pending. Do not run `make archive-task` between network rollouts; archive only after every intended network configuration is executed or canceled and its final artifacts are committed.
Expand Down
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ archive-task:
@python3 $(REPO_ROOT)/scripts/standby-task.py archive --repo "$(REPO_ROOT)" --task "$(STANDBY_TASK_DIR)" --network "$(TASK_NETWORK)" --date "$(TASK_DATE)"
endif

ifneq ($(filter new-task,$(MAKECMDGOALS)),)
ifneq ($(origin TASK_NETWORK),command line)
$(error TASK_NETWORK is required on the command line, e.g. make new-task TASK_SLUG=<slug> TASK_NETWORK=<network>)
endif
endif

.PHONY: new-task check-task-status
new-task: export TASK_SLUG := $(TASK_SLUG)
new-task: export TASK_ID := $(TASK_ID)
new-task: export STANDBY_OP := $(STANDBY_OP)
new-task: export TASK_NETWORK := $(TASK_NETWORK)
new-task: export TASK_DATE := $(TASK_DATE)
new-task:
@python3 -B "$(REPO_ROOT)/scripts/new-task.py" --repo "$(REPO_ROOT)"

check-task-status:
@python3 -B "$(REPO_ROOT)/scripts/task-status.py" check --repo "$(REPO_ROOT)"

##
# Toolchain bootstrap (mise)
##
Expand Down
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ active/evm/
└── <YYYY-MM-DD-task-name>/
├── Makefile # task dependencies, validation, approvals, and execution
├── FACILITATOR.md # network-agnostic facilitator procedure
├── status.json # task progress across network invocations
├── script/ # optional one-off task scripts
├── config/
│ └── <network>/
Expand Down Expand Up @@ -147,7 +148,7 @@ make deps PROJECT_DIR="$PWD/active/evm" BASE_CONTRACTS_COMMIT=<commit>

### Standby operations

Canonical standby tasks are dateless, pre-reviewed operations with no real network configuration or artifacts. Invoke directly from `standby/evm/tasks/<operation>/` by adding `config/<network>/.env` and a concise signer README with `Status: READY TO SIGN`. Do not copy the task into active or edit its canonical Makefile, Solidity, or root `FACILITATOR.md` for an incident. Every Make command requires explicit command-line `TASK_NETWORK=<network>`, not an exported value or default.
Canonical standby tasks are dateless, pre-reviewed operations with no real network configuration or artifacts. Invoke directly from `standby/evm/tasks/<operation>/` by adding `config/<network>/.env`, a concise signer README with `Status: READY TO SIGN`, and a task-root `status.json`. Do not copy the task into active or edit its canonical Makefile, Solidity, or root `FACILITATOR.md` for an incident. Every Make command requires explicit command-line `TASK_NETWORK=<network>`, not an exported value or default.

Use the [upgrade-delay guide](standby/evm/tasks/delay-upgrade-activation/FACILITATOR.md) or [verifier hash rotation guide](standby/evm/tasks/update-verifier-hashes/FACILITATOR.md) for exact commands. The verifier operation supports AggregateVerifier 0.3.0 at its reviewed contracts pin and replaces the former common verifier scripts. Standby tasks use the shared `active/evm` Foundry project, but each operation's dependency pin and build settings must be respected. Signer discovery includes an invocation only after JSON validations exist under `config/<network>/validations/`. The pinned signer checkout receives the deterministic repo-owned patch in `scripts/patches/signer-standby.patch`; changing its upstream pin or content without reviewing the patch fails closed.

Expand All @@ -166,7 +167,7 @@ standby/evm/tasks/<operation>/
└── <operation>.s.sol
```

Invocation-only `config/`, `signatures/`, and `records/` directories are added during an incident and removed by archival. Later incidents reuse the canonical standby directory. Historical archives must not be edited or executed.
Invocation-only `config/`, `signatures/`, and `records/` directories and the task-root `status.json` are added during an incident and removed by archival. Later incidents reuse the canonical standby directory. Historical archives must not be edited or executed.

### Legacy tasks

Expand All @@ -177,6 +178,44 @@ Each legacy task (under a network directory, or `archive/legacy/`) has a directo
- **src/** place to store any one-off smart contracts (long-lived contracts should go in [base/contracts](https://github.com/base/contracts))
- **.env** place to store task-specific environment variables (contract addresses are inherited from the network-level `.env`)

## Task status and scaffolding

Every active task and every standby operation with a network invocation has one task-root `status.json`. Humans and the automation bot read and update the same file. Never put it under `config/<network>/`, because task-origin signing covers that directory and later status updates must not change the signed payload. A canonical standby operation without invocations must not have a status file; archival carries invocation status into the dated archive and removes it from standby.

[`scripts/task-status.schema.json`](scripts/task-status.schema.json) defines version 1. All fields are required and unknown fields are rejected. `schema_version` is `1`, `task` matches the task directory name, `kind` is `active` or `standby`, and `last_updated` is a UTC timestamp in `YYYY-MM-DDTHH:MM:SSZ` form. `networks` contains exactly the directories under the task's `config/` and must not be empty. Each network has:

- `step`, one of the lifecycle steps below.
- `pr`, the task's pull request URL in this repository, or `null` before one exists.
- `blocksec_ticket`, an HTTPS ticket URL, or `null` if none applies.
- `signatures`, an object keyed by validation file stem, such as `base-signer`, with nonnegative integer `collected` and positive integer `required` counts. Use `{}` until the required signer roles are known.
- `executed_tx`, a 32-byte hex transaction hash only when the step is `executed`; otherwise `null`.

| Step | Meaning |
| --- | --- |
| `authoring` | The facilitator is writing scripts and configuration. |
| `validation` | The facilitator is generating and checking simulation artifacts. |
| `review` | Reviewers are checking the task and validation files. |
| `ready_to_sign` | Reviewed validation files are ready for signers. |
| `collecting` | Signers are submitting signatures. |
| `ready_to_execute` | Required signatures and approvals are ready for execution. |
| `executed` | Execution succeeded and its transaction hash is recorded. |
| `canceled` | The invocation will not execute. |

The facilitator or bot updates the network step, links, signature counts, and `last_updated` when progress changes. The initial machine step is `authoring` even though the signer README starts with `Status: READY TO SIGN`; do not ask signers to sign until validations are reviewed. Finalize the signer README as `EXECUTED` or `CANCELED` and update `status.json` in the same change. The checker requires both final states to match and accepts only uppercase `EXECUTED` or `CANCELED`, optionally followed by an HTTP(S) URL, or `[EXECUTED](<url>)` or `[CANCELED](<url>)` with an HTTP(S) URL. Other final-looking formats are rejected.

Run these commands from the repository root. Provide exactly one selector and a command-line `TASK_NETWORK` with a matching global `config/<network>.env`. The date defaults to today; identifiers use lowercase letters, digits, and single hyphens, with slugs, operations, and networks starting with a letter.

```sh
make new-task TASK_SLUG=<slug> TASK_NETWORK=<network> TASK_DATE=YYYY-MM-DD
make new-task TASK_ID=<existing-active-task-id> TASK_NETWORK=<network>
make new-task STANDBY_OP=<operation> TASK_NETWORK=<network>
make check-task-status
```

The first mode creates `active/evm/tasks/<date>-<slug>/` with a shared Makefile skeleton, facilitator guide, tracked `script/` directory, network `.env`, signer README, and initial `status.json`. Implement the operation and fill in the empty contracts pin before using it. The second mode adds only a network config and status entry to an existing active task. The third adds a standby invocation without changing canonical files; follow the operation's facilitator guide to fill in its reviewed pin and required inputs. All modes refuse overwrites and leave generated validations, addresses, signatures, and records to the facilitator.

To adopt an existing task, run `python3 -B scripts/task-status.py add-network <task-dir> <network>` once for each existing network, then update its progress and final artifacts. The CLI refuses duplicate entries. `make check-task-status` validates all active tasks and invoked standby operations, including network directories, execution hashes, and finalized README consistency. The [task status workflow](.github/workflows/validate-task-status.yml) runs the checker and scaffolder tests on pull requests and pushes to main.

## CI script validation

The GitHub Actions workflow validates shared active scripts and canonical standby Solidity on every pull request and push to `main`.
Expand Down
15 changes: 15 additions & 0 deletions active/evm/tasks/2026-10-07-denim-upgrade/status.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"schema_version": 1,
"task": "2026-10-07-denim-upgrade",
"kind": "active",
"last_updated": "2026-10-17T18:10:50Z",
"networks": {
"zeronet": {
"step": "executed",
"pr": "https://github.com/base/contract-deployments/pull/806",
"blocksec_ticket": null,
"signatures": {},
"executed_tx": "0x9468085edf0a0d5e3dbeea7ab84f13400a496aafc6c21ca8420e4f136c106034"
}
}
}
15 changes: 15 additions & 0 deletions active/evm/tasks/2026-10-15-upgrade-l2-predeploys/status.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"schema_version": 1,
"task": "2026-10-15-upgrade-l2-predeploys",
"kind": "active",
"last_updated": "2026-10-17T18:10:50Z",
"networks": {
"zeronet": {
"step": "executed",
"pr": "https://github.com/base/contract-deployments/pull/817",
"blocksec_ticket": null,
"signatures": {},
"executed_tx": "0x50c7bd1ee23ffa011dae50eebe1ea32ebf30a64e92e55214688afcbc6e2368a0"
}
}
}
Loading
Loading