Skip to content
Back to skills

Finish Rollout

ASecurity

Finishes a production multisig rollout after its timelock ops have executed — verifies execution (MongoDB timelock queue + on-chain isOperationDone), closes the #dev-sc-multisig-proposals Slack thread ("Executed" reply + rocket reaction), syncs the diamond logs for the impacted chains onto the rollout PR, and takes that PR through /pr-ready → ready-for-review → /post-pr-for-review. If the ops are executable but the cron hasn't run yet, it dispatches the Timelock Auto Execution workflow and wa...

  • 215 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 1, 2026
blockchaingobashreactrailsgitdatabase

Works with

  • mcp

Security analysis

A100/100

Scanned September 1, 2026

npx -y skills add lifinance/contracts --skill finish-rollout --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Finish Rollout?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Finish Rollout
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lifinance-finish-rollout/badge)](https://www.skillsdirectory.com/skills/lifinance-finish-rollout)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: finish-rollout
description: Finishes a production multisig rollout after its timelock ops have executed — verifies execution (MongoDB timelock queue + on-chain isOperationDone), closes the #dev-sc-multisig-proposals Slack thread ("Executed" reply + rocket reaction), syncs the diamond logs for the impacted chains onto the rollout PR, and takes that PR through /pr-ready → ready-for-review → /post-pr-for-review. If the ops are executable but the cron hasn't run yet, it dispatches the Timelock Auto Execution workflow and waits before proceeding. Use when the user says "finish the rollout", "the timelock executed, wrap it up", "close out the deployment", or supplies a #dev-sc-multisig-proposals thread link with finishing intent. This is the tail of `multisig-rollout` (which ends at "timelock ops execute via the scheduled pipeline"); it never executes or cancels timelock ops itself — direct execution stays with the workflow / `execute-pending-timelock-tx.ts`. Requires gh and the Slack MCP server. No VPN needed.
usage: /finish-rollout <slack thread link>
---

# Finish Rollout (LI.FI Contracts)

Input: a `#dev-sc-multisig-proposals` thread link, e.g.
`https://lifi-protocol.slack.com/archives/C09DKGYQ1GC/p1783082088092039`.
Channel id is the `C…` path segment; the parent message `ts` comes from the `p<digits>` segment
by inserting a `.` before the last 6 digits (`p1783082088092039` → `1783082088.092039`).

## Hard rails

- **Strict finisher.** Never execute, cancel, or reschedule timelock ops directly. The one
  allowed nudge: when this rollout's ops are ready-but-unexecuted, dispatch the Timelock Auto
  Execution workflow (Phase 2b) — the same pipeline the cron uses — and wait for it. Every
  other stuck state (missing signatures, delay still running) is report-and-stop.
- **Nothing outward-facing before the gate passes.** No Slack reply, no reaction, no PR
  mutation until Phase 2 verifies every op of THIS rollout as executed (all-or-nothing).
- **Op-level scope.** The gate covers only ops correlated to this rollout. Unrelated
  queued/unexecuted ops on the same networks are never stoppers (mention as FYI at most).
- **No VPN.** Verification uses the non-gated `MONGODB_URI` timelock queue plus public RPCs.
  Do not call `list-pending-proposals.ts` (VPN-gated `SC_MONGODB_URI`) as part of this skill.
- The thread must live in `#dev-sc-multisig-proposals` (`C09DKGYQ1GC`). Anything else: stop
  and ask.

## Phase 0 — Preflight

- Slack MCP connected (read thread, send reply, add reaction). If missing, stop — the Slack
  closure is half the job.
- `gh auth status` OK (deploy mode edits the rollout PR).
- `MONGODB_URI` present in `.env` (the verification script exits `2` if not).

## Phase 1 — Parse the thread

Read the thread. Extract:

- **Mode** from the top-level message: `<N>x <Contract> v<version> deployment` → deploy mode;
  `<N>x whitelist sync — …` → whitelist mode. Neither → stop and ask.
- **Networks** from the reply's `Safe proposals live on:` bullets (ignore trailing
  `(nonce N)` annotations).
- **PR URL** from the reply (`PR with deployed addresses:` / `Whitelist PR:`). A thread posted by
  `multisig-rollout` always carries this link; if it's absent (thread posted by hand, or edited),
  stop and ask the user for the rollout PR URL rather than guessing from recent merges.
- **Parent `ts`** for the reaction, thread `ts` for the reply.

## Phase 2 — Verification gate (all-or-nothing, op-level)

Correlate queue rows to this rollout by **deployed address in the scheduled payload**:

1. Per network, get the rollout's contract address:
   - deploy mode: the PR's `| Chain | Contract address | … |` table, or the
     `deployments/<net>.json` diff in the PR.
   - whitelist mode: the added/removed addresses from the whitelist PR's
     `config/whitelist.json` diff.
2. Run:

   ```bash
   bunx tsx script/deploy/safe/list-timelock-queue.ts \
     --network <csv> --payloadContains <address csv> --checkOnChain --json
   ```

   Exit codes: `0` ok, `1` real error (stop, report), `2` `MONGODB_URI` missing/unreachable
   (tell the user, stop).
3. **Gate**: every target network has ≥1 correlated row, and every correlated row is
   `status: "executed"` with `onChainDone: true`. A diamond-called periphery rollout expects
   **two** rows per network (registration + whitelist).
4. When the gate fails, branch on the per-row state:
   - row `queued`, `onChainReady: true` → executable now, the cron just hasn't run →
     **Phase 2b** (trigger execution), then continue;
   - anything else → print a per-network stuck-state table and STOP:
     - no correlated row → the Safe tx was never executed on-chain (queue rows are written by
       `confirm-safe-tx.ts` when it mines) → still gathering signatures;
     - row `queued`, `onChainReady: false` → timelock delay still running;
     - row `cancelled`/`failed` → surface `failureReason` and ask the user how to proceed.

### Phase 2b — Trigger execution (ready-but-unexecuted ops only)

Dispatch the same pipeline the cron uses (requires repo write access; a dispatched run always
executes, even when scheduled runs are disabled):

```bash
gh workflow run runPendingTimelockTXs.yml --repo lifinance/contracts
```

The new run takes a few seconds to appear; poll until the newest `workflow_dispatch` run's
`createdAt` is after your dispatch, then watch it (it may first queue behind an in-flight cron
run — the concurrency group serializes them):

```bash
RUN_ID=$(gh run list --repo lifinance/contracts --workflow runPendingTimelockTXs.yml \
  --event workflow_dispatch --limit 1 --json databaseId,createdAt --jq '.[0].databaseId')
gh run watch "$RUN_ID" --repo lifinance/contracts --exit-status
```

Afterwards re-run the Phase 2 gate **once**. Green → continue to Phase 3. Still not executed →
`gh run view "$RUN_ID" --repo lifinance/contracts --log-failed`, report, STOP — never loop
dispatches.

## Phase 3 — Slack closure

Reply in the thread with exactly `Executed`, then add the `rocket` reaction to the parent
message. Both via the Slack MCP tools; if unavailable, hand the user the exact reply text and
emoji to post manually — do not use webhooks.

## Phase 4 — Diamond log sync (deploy mode only)

The production `deployments/<net>.diamond.json` registry only updates when the cut executes —
that's now. Regenerate it from on-chain state:

```bash
gh pr checkout <N>
source script/helperFunctions.sh
updateDiamondLogs "production" "<network>"   # once per impacted network
```

The diff MUST show the new contract/version in each impacted `<net>.diamond.json` — this is a
second, independent execution proof. If it doesn't, that contradicts Phase 2: stop, flag,
investigate; commit nothing. Otherwise commit the diamond-log diff to the PR branch and push.

**Removals in the same cut.** `updateDiamondLogs` rebuilds `Facets` from the loupe, so a facet
the cut removed (a drained deferred-cleanup task rides along as extra `scheduleBatch` elements)
disappears from `<net>.diamond.json` on its own — but the flat `deployments/<net>.json` is
append-only. Delete each removed facet's entry there by hand in the same commit; both logs track
current on-chain registration, not history
([docs/DeploymentLogs.md](../../docs/DeploymentLogs.md)). Keep the entry when the facet is still
routed by the loupe and no open parked task covers it (removal not executed, or the parked task
retired as cancelled — cancellation proves nothing about routing). An entry already pruned at
park time stays pruned while its task is still `queued`/`proposed` — the queue-aware
invariants track the still-routed facet as expected-pending. Restore it if the task retired as
cancelled and the loupe still routes the facet. `superseded` is loupe-verified gone, so
delete that entry with the rest of the cut.

## Phase 5 — PR finish (deploy mode only)

1. `/pr-ready` (mandatory local review gate — resolve findings first).
2. `gh pr ready <N>` (draft → ready for review).
3. `/post-pr-for-review` (posts to `#dev-sc-review`, tags the team, enables auto-merge).

Whitelist mode skips Phases 4–5: its PR was merged before the rollout started.

## Phase 6 — Report

Networks with their `executionTxHash`es, Slack reply + reaction confirmation, PR state
(commit pushed, ready, posted for review), and any FYI (e.g. unrelated ops still queued on the
same networks).

## Failure modes

- `list-timelock-queue.ts` exit `2` → `MONGODB_URI` missing or cluster unreachable — relay,
  stop.
- A correlated row is `status: "blocked"` → the pre-execute guard refused the batch and the
  timelock op is still un-executed. The gate fails; do not treat it as done. **Report and
  stop** — clearing a block is outside the finisher's authority (Hard rails: the workflow
  dispatch is the only allowed nudge). Re-run the lister with `--attention`, relay
  `statusReason` and whether the op is still executable on-chain, and hand it to an approved
  operator: they clear the cause and run `requeue-timelock-op.ts` themselves, or — for a
  fully-obsolete folded removal — cancel the op and re-propose the primary cut alone (see
  `docs/DeferredDiamondCleanupQueue.md`).
- Thread doesn't parse (format drift, missing PR link) → show what was extracted, ask.
- PR already merged/closed in deploy mode → nothing to finish there; report and continue with
  the Slack closure only if the gate passed.
- Diamond log diff missing the expected version → contradiction with the queue; stop and
  investigate (wrong address correlated, or the cut executed a different payload).
- `gh workflow run` fails in Phase 2b → the user's `gh` account lacks write access to
  `lifinance/contracts`; relay and stop (the cron will pick the ops up on its own).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.