Skip to content

feat(deploy): harden deferred-cleanup queue — resolve-by-address drain, queue-aware stale-facet invariant, safe-to-prune report [EXSC-723] - #2157

Draft
0xDEnYO wants to merge 2 commits into
mainfrom
feature/exsc-723-parked-queue-drain-hardening
Draft

feat(deploy): harden deferred-cleanup queue — resolve-by-address drain, queue-aware stale-facet invariant, safe-to-prune report [EXSC-723]#2157
0xDEnYO wants to merge 2 commits into
mainfrom
feature/exsc-723-parked-queue-drain-hardening

Conversation

@0xDEnYO

@0xDEnYO 0xDEnYO commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Which Linear task belongs to this PR?

EXSC-723

Why did I implement it this way?

Backfilling the #2046 deprecations into the deferred-cleanup queue (112 tasks parked 2026-07-30) exposed three gaps in the parked-removal lifecycle. This PR closes them without touching governance (ts-only; the minted removal proposal is byte-for-byte unchanged):

  1. Drain resolves a pruned deploy-log entry by stored address (diffNamedFacets hint path, spec §14 Q5). "Remove every selector currently routed to the parked task's stored facetAddress" is never stale by construction — selectors still come from the live loupe at drain time, so this does NOT reintroduce the frozen-calldata staleness class the design rejected. Resolution happens only when the hint is unambiguous: an address the log maps to a different name, or one claimed by two tasks, stays in prunedButRouted (keep + alert) exactly as before. The deploy log stops being load-bearing for the drain.

  2. Queue-aware stale-facet health-check invariant (no-stale-registered-facets, warning, production-only, EVM + Tron). Today a deprecated-but-still-registered facet is invisible: facets-registered only flags missing facets, no-unexpected-facets only flags unknown addresses. The new invariant warns on any on-chain facet whose deploy-log name is absent from _targetState.json and has no open parked task — open tasks are expected-pending (no alarm flood for the 112 queued removals). Concrete instance it would have caught: GenericSwapFacet on tron (EXSC-724), which fell out of the queue because the enqueue CLI rejects base58. Mongo is only touched when the cheap pre-pass finds something stale; an unreachable queue degrades to a visible "coverage skipped" warning, never false alarms. MONGODB_URI is already in the health-check workflow env.

  3. Reconcile reports safe-to-prune deploy-log entries. Nothing pruned deployments/*.json after a removal executed. The weekly reconcile job now names entries whose parked work is fully terminal (≥1 executed/superseded, none open, entry still in the log; cancelled-only groups are never safe). Report-only — pruning stays a reviewed PR.

Pure decisions (computeStaleRegisteredFacets, computeSafeToPrune, formatSafeToPruneReport, the diffNamedFacets hint resolution) are fully unit-tested; live wiring follows the existing unit-test-exempt adapter convention. Executable check: reconcile-parked-tasks.ts --network arbitrum dry-run against the live queue (5 open tasks seen, all keep, prune report empty as expected).

Checklist before requesting a review

Checklist for reviewer (DO NOT DEPLOY and contracts BEFORE CHECKING THIS!!!)

  • I have checked that any arbitrary calls to external contracts are validated and or restricted
  • I have checked that any privileged calls (i.e. storage modifications) are validated and or restricted
  • I have ensured that any new contracts have had AT A MINIMUM 1 preliminary audit conducted on by <company/auditor>

…n, queue-aware stale-facet invariant, safe-to-prune report [EXSC-723]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change adds address-based recovery for pruned deployment-log entries, a queue-aware stale-facet health invariant, environment-filtered parked-task queries, and reconciliation reporting for entries safe to prune.

Changes

Deferred cleanup safeguards

Layer / File(s) Summary
Pruned-log facet resolution
script/deploy/safe/diamondRemovalDiff.ts, script/deploy/safe/diamondRemovalDiff.test.ts, script/deploy/safe/drain-parked-tasks.ts, script/deploy/safe/parked-tasks.ts, script/deploy/safe/parked-tasks.test.ts, docs/DeferredDiamondCleanupQueue.md
diffNamedFacets resolves unambiguous stored address hints using live loupe selectors, while protected, ambiguous, and conflicting cases remain routed and alertable. Parked-task draining and queries now filter by environment.
Stale registered facet invariant
script/deploy/healthCheckInvariants.ts, script/deploy/healthCheckInvariants.test.ts
Adds computeStaleRegisteredFacets and the production no-stale-registered-facets warning invariant, classifying deprecated on-chain facets by open parked-task coverage.
Safe-to-prune reconciliation
script/deploy/safe/reconcile-parked-tasks.ts, script/deploy/safe/reconcile-parked-tasks.test.ts, .agents/commands/deprecate-contract.md
Adds terminal-task and deploy-log checks, grouped safe-to-prune reporting, cron wiring, tests, and updated pruning guidance.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: AuditNotRequired

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: deferred-cleanup queue hardening, address-based drain resolution, a stale-facet invariant, and a safe-to-prune report.
Description check ✅ Passed The description matches the template well, including the Linear task, rationale, before-review checklist, and reviewer checklist.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/exsc-723-parked-queue-drain-hardening

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
script/deploy/safe/reconcile-parked-tasks.test.ts (1)

193-255: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use descriptive result variable names.

Rename repeated r variables to prunableEntries so these tests follow the TypeScript naming guideline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/safe/reconcile-parked-tasks.test.ts` around lines 193 - 255,
Rename each repeated result variable `r` within the `computeSafeToPrune` tests
to `prunableEntries`, updating the corresponding assertions while preserving all
test behavior.

Source: Coding guidelines

script/deploy/healthCheckInvariants.test.ts (1)

231-305: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

LGTM on the added computeStaleRegisteredFacets unit tests — verified against the implementation, all correct.

Consider also covering no-stale-registered-facets's run() behavior (mocking the dynamic ./safe/parked-tasks import) for the queue-unreachable fallback and environment-scoped queries — this is exactly the code path with the environment-filter gap flagged in healthCheckInvariants.ts, and it's currently untested.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/healthCheckInvariants.test.ts` around lines 231 - 305, The
tests cover computeStaleRegisteredFacets but not the no-stale-registered-facets
invariant's run() behavior. Add run() tests that mock the dynamic
./safe/parked-tasks import, covering the queue-unreachable fallback and
verifying environment-scoped queries use the correct production scope, including
the environment-filter gap in healthCheckInvariants.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@script/deploy/healthCheckInvariants.ts`:
- Around line 1698-1705: The missing targetFacets condition in the stale-facet
check should record a non-fatal warning in IHealthCheckNetworkResult.warnings
instead of calling consola.info and returning silently. Update the surrounding
health-check result flow to append an actionable warning for the missing
targetState[network].production.LiFiDiamond, while preserving the check’s
non-failing skip behavior.
- Around line 1720-1741: The parked-task lookup used to build
openParkedFacetNames must be scoped to ctx.environment. Extend
IListParkedTasksFilter and listParkedTasks to accept an optional environment:
EnvironmentEnum filter, apply it to queued/proposed task queries, and pass
ctx.environment from the stale-facet check so tasks from other environments
cannot mask the required warning.

---

Nitpick comments:
In `@script/deploy/healthCheckInvariants.test.ts`:
- Around line 231-305: The tests cover computeStaleRegisteredFacets but not the
no-stale-registered-facets invariant's run() behavior. Add run() tests that mock
the dynamic ./safe/parked-tasks import, covering the queue-unreachable fallback
and verifying environment-scoped queries use the correct production scope,
including the environment-filter gap in healthCheckInvariants.ts.

In `@script/deploy/safe/reconcile-parked-tasks.test.ts`:
- Around line 193-255: Rename each repeated result variable `r` within the
`computeSafeToPrune` tests to `prunableEntries`, updating the corresponding
assertions while preserving all test behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 73ccab99-78e7-4b5f-b41f-df8fbe3f31d4

📥 Commits

Reviewing files that changed from the base of the PR and between fb5edec and 0fdfb8a.

📒 Files selected for processing (9)
  • .agents/commands/deprecate-contract.md
  • docs/DeferredDiamondCleanupQueue.md
  • script/deploy/healthCheckInvariants.test.ts
  • script/deploy/healthCheckInvariants.ts
  • script/deploy/safe/diamondRemovalDiff.test.ts
  • script/deploy/safe/diamondRemovalDiff.ts
  • script/deploy/safe/drain-parked-tasks.ts
  • script/deploy/safe/reconcile-parked-tasks.test.ts
  • script/deploy/safe/reconcile-parked-tasks.ts

Comment thread script/deploy/healthCheckInvariants.ts
Comment thread script/deploy/healthCheckInvariants.ts
An open staging task for a same-named facet could otherwise mask a
production stale facet from the no-stale-registered-facets warning, and
the drain's listQueued could pick up cross-environment rows despite
minting production-only removals. Theoretical in v1 (enqueue rejects
non-production), enforced at the query now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
script/deploy/healthCheckInvariants.ts (1)

377-382: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve ambiguous deploy-log address mappings.

Object.fromEntries overwrites earlier names when multiple deploy-log entries share the same lowercased address. This can hide a stale facet when the surviving name is expected, or report the wrong facet depending on insertion order. Treat duplicate address mappings as ambiguous/unresolved instead of selecting one arbitrarily, and add a regression test for two names mapping to the same address.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/deploy/healthCheckInvariants.ts` around lines 377 - 382, Update the
nameByAddress construction in the deployedContracts mapping to detect duplicate
lowercased addresses and mark them ambiguous/unresolved instead of allowing
Object.fromEntries to overwrite an earlier name. Ensure downstream health-check
logic does not select either name for an ambiguous address, and add a regression
test covering two deploy-log names sharing one address.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@script/deploy/healthCheckInvariants.ts`:
- Around line 377-382: Update the nameByAddress construction in the
deployedContracts mapping to detect duplicate lowercased addresses and mark them
ambiguous/unresolved instead of allowing Object.fromEntries to overwrite an
earlier name. Ensure downstream health-check logic does not select either name
for an ambiguous address, and add a regression test covering two deploy-log
names sharing one address.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d72a70c-f4f2-4e6f-9d5a-849a4722ca52

📥 Commits

Reviewing files that changed from the base of the PR and between 0fdfb8a and 983aad5.

📒 Files selected for processing (4)
  • script/deploy/healthCheckInvariants.ts
  • script/deploy/safe/drain-parked-tasks.ts
  • script/deploy/safe/parked-tasks.test.ts
  • script/deploy/safe/parked-tasks.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant