Agent-instruction and architecture docs had drifted from the code: - CLAUDE.md: slim to commands + pointers; fix wrong claim that `make pre-commit` is the full pre-PR gate (that is `make pre-pr`); drop stale pre-#3929 file paths and merged bug narratives - AGENTS.md: drop dead `rust-refactor-helper` skill rule and the hand-maintained (already stale) scoped-AGENTS index; link architecture docs from Sources of Truth - .github/AGENTS.md: replace the outdated copied CI command matrix with a pointer to ci.yml - crates/AGENTS.md: merge duplicated Testing sections - ARCHITECTURE.md: resolve the utils->config contradiction (edges are removed), mark volatile counts as snapshots, fix a bad path - docs/architecture: add README router; move one-shot plans/trackers (rebalance-decommission phases, migration-progress ledger, PR template) to docs/superpowers/plans with archive headers; fix stale source paths in kept inventories (core/sets.rs, core/pools.rs, store/mod.rs, startup_* split from #3671) - docs/operations/tier-ilm-debugging.md: extracted tier debugging playbook with corrected paths - scripts/check_doc_paths.sh: new guard failing pre-commit/pre-pr when instruction/architecture docs reference nonexistent file paths - .claude/skills: add tier-debug and arch-checks repo skills; .gitignore now keeps .claude/skills and docs/operations committable Verification: ./scripts/check_doc_paths.sh, ./scripts/check_architecture_migration_rules.sh (both pass) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
17 KiB
Archived implementation plan/tracker — moved from
docs/architecture/(2026-07). Kept for history; not maintained. File paths inside may reflect the pre-#3929 module layout (e.g.crates/ecstore/src/rebalance.rsis nowcrates/ecstore/src/store/rebalance.rs;set_disk.rsis nowset_disk/).
Rebalance and Decommission Phase 1 Safety Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Fix the highest-risk rebalance/decommission safety defects so object-version semantics and distributed operation state cannot silently diverge.
Architecture: Phase 1 keeps the existing ECStore/Rebalance/PoolMeta architecture and introduces narrowly scoped behavior changes. The plan favors fail-closed semantics for unsafe migration, peer propagation, and startup conflicts. Each fix block should be implemented as a separate PR unless a shared helper is explicitly called out.
Tech Stack: Rust, Tokio, ECStore, Axum admin handlers, tonic peer RPC, MessagePack metadata, existing RustFS test modules.
Scope
This plan covers:
- F01: rebalance delete marker and remote tiered version migration semantics.
- F02: decommission
DataMovementOverwriteErrcleanup safety. - F03: decommission
pool_metareload as a start barrier. - F04: store startup recovery ordering for pool meta and rebalance.
- F05: rebalance distributed start/stop semantics and
stoppingvisibility.
This plan does not cover multipart streaming, checksum preservation, lifecycle-expired cleanup, overwrite convergence, cleanup warning UX, audit fields, or metadata decoding hardening. Those belong to later phase plans.
Shared Principles
- Treat unknown target equivalence as unsafe.
- Do not clean a source entry unless the target state is proven complete or the version is intentionally skipped by a documented policy.
- Do not report distributed admin success when required peers failed.
- Do not auto-run rebalance and decommission together after restart.
- Prefer small local helpers near existing logic over broad refactors.
F01: Rebalance Delete Marker and Remote Tiered Version Safety
Decision Needed
Remote tiered versions need a product decision before coding:
- Recommended short-term behavior: Match MinIO and skip remote tiered versions during rebalance. This avoids changing remote-tier metadata during a balancing operation and removes the immediate source-cleanup data-loss risk.
- Later behavior: Implement explicit cross-pool target metadata movement for remote tiered versions only after tests prove remote references, lifecycle state, and cleanup semantics.
Delete markers should not be skipped. They preserve versioned delete semantics and must be copied to the selected target pool before source cleanup.
Files
- Modify:
crates/ecstore/src/rebalance.rs - Possibly modify:
crates/ecstore/src/data_movement.rs - Avoid unless necessary:
crates/ecstore/src/set_disk.rs - Test: existing rebalance unit tests in
crates/ecstore/src/rebalance.rs
Design
-
Add an ECStore-level helper for rebalance delete marker movement.
- Input: source pool index, bucket, object name, source
FileInfo, version ID. - Behavior: choose a target pool using the same placement rules as normal data movement, excluding source/rebalancing/decommissioned pools.
- Write the delete marker metadata to the target pool with the original version ID, mod time, delete marker flag, and replication state.
- Return success only when the target write succeeds or an equivalent target delete marker is confirmed.
- Input: source pool index, bucket, object name, source
-
Change the delete marker branch in
migrate_entry_version_with_retry_wait.- Do not call
SetDisks::delete_object_for_migrationfor rebalance delete markers. - Call the ECStore-level helper via the
transfer/backend abstraction or split delete marker handling out of theSetDisksbackend path. - Do not set
moved = trueunless the target helper succeeds.
- Do not call
-
Change remote tiered branch.
- Short term: return
ignored = true,cleanup_ignored = false,moved = falsefor remote tiered versions so they do not contribute to full source cleanup. - If skipping a remote tiered version prevents source cleanup, record a clear status reason so operators understand why the entry remains.
- Short term: return
-
Source cleanup must remain blocked if any version was skipped without cleanup permission.
Implementation Tasks
- Write a failing test proving a rebalance delete marker currently does not require target-pool metadata before counting complete.
- Write a failing test proving remote tiered versions do not allow source cleanup under the short-term skip policy.
- Implement the delete marker target write helper.
- Replace the rebalance delete marker source-set branch with the target write helper.
- Replace the remote tiered branch with the skip-without-cleanup policy.
- Run focused rebalance tests.
Focused Test Commands
cargo test -p rustfs-ecstore rebalance_delete_marker --lib
cargo test -p rustfs-ecstore remote_tier --lib
cargo test -p rustfs-ecstore rebalance_entry --lib
Acceptance Criteria
- Rebalance does not clean a source entry when a delete marker target write fails.
- Rebalance preserves versioned delete behavior after source cleanup.
- Remote tiered versions are either safely skipped without source cleanup or explicitly moved with verified target metadata.
- Existing non-delete-marker rebalance behavior remains unchanged.
Risks
- The existing
MigrationBackendtrait is source-set oriented. If adapting it becomes invasive, prefer an ECStore-specific path inrebalance_entryrather than widening the trait for one branch. - Skipping remote tiered versions may leave more source entries behind. That is safer than deleting unverified metadata and can be improved in a later PR.
F02: Decommission DataMovementOverwriteErr Cleanup Safety
Decision
DataMovementOverwriteErr must not be cleanup-safe by default. It can count as complete only after explicit target equivalence verification.
Files
- Modify:
crates/ecstore/src/pools.rs - Possibly modify:
crates/ecstore/src/data_movement.rs - Test: decommission tests in
crates/ecstore/src/pools.rs
Design
-
Update delete marker and remote tiered decommission error handling.
- Keep object-not-found and version-not-found handling as cleanup-safe only when existing semantics justify it.
- Remove
is_err_data_movement_overwrite(&err)from branches that setcleanup_ignored = true.
-
Add a narrow equivalence helper only if an existing target version can be inspected cheaply.
- Compare bucket, object, version ID, delete marker state, ETag where applicable, mod time, and key metadata.
- If comparison cannot be done reliably in the current code path, treat overwrite as failure for Phase 1.
-
Preserve source entry on unsafe overwrite.
- Set
failure = true. - Include a clear error stage in logs/status.
- Do not increment the decommissioned count.
- Set
Implementation Tasks
- Write a failing unit test for delete marker decommission where
DataMovementOverwriteErrmust not count complete. - Write a failing unit test for remote tiered decommission where
DataMovementOverwriteErrmust not count complete. - Remove
is_err_data_movement_overwritefrom cleanup-safe conditions in those branches. - Add an explicit helper or comment documenting why overwrite is unsafe without equivalence.
- Run focused decommission tests.
Focused Test Commands
cargo test -p rustfs-ecstore decommission --lib
cargo test -p rustfs-ecstore DataMovementOverwriteErr --lib
Acceptance Criteria
DataMovementOverwriteErrdoes not setcleanup_ignored = trueby default.- Source cleanup does not run when overwrite equivalence is unknown.
- Logs make unsafe overwrite distinguishable from not-found cleanup-safe cases.
Risks
- Tightening this behavior may cause more decommission retries/failures in clusters that previously masked unsafe state. That is intended; status must make the failure actionable.
F03: Decommission Pool Meta Reload Barrier
Decision
A successful decommission start must mean all required peers have acknowledged the updated pool meta or the API must return an explicit failure/degraded result.
Files
- Modify:
crates/ecstore/src/pools.rs - Modify:
crates/ecstore/src/notification_sys.rs - Modify:
rustfs/src/admin/handlers/pools.rs - Possibly modify:
crates/ecstore/src/rpc/peer_rest_client.rs - Possibly modify:
rustfs/src/storage/rpc/node_service.rs
Design
-
Change
start_decommissionpropagation behavior.- After
pool_meta.save, callreload_pool_meta. - If peer reload returns an aggregate error, return that error to the admin handler.
- Do not silently proceed.
- After
-
Decide rollback behavior.
- Preferred minimal Phase 1: fail the API and leave persisted
pool_metain decommission state only if rollback is unsafe or unavailable; status must show reload failure. - Stronger option: persist a reverted pool meta before returning failure. This needs careful lock and persistence review and should not be attempted without tests.
- Preferred minimal Phase 1: fail the API and leave persisted
-
Ensure decommission workers do not spawn after reload failure.
- If the caller starts workers after
start_decommissionreturns, returningErris sufficient. - If any background path can resume from persisted metadata after a failed reload, status must clearly show pending/degraded state.
- If the caller starts workers after
Implementation Tasks
- Write a failing test for
start_decommissionwith peer reload failure. - Change reload failure from
warnto returned error. - Update admin handler error mapping so clients do not receive success.
- Add status/log context for peer reload failure.
- Run focused pool/decommission tests.
Focused Test Commands
cargo test -p rustfs-ecstore start_decommission --lib
cargo test -p rustfs-ecstore reload_pool_meta --lib
cargo test -p rustfs decommission --lib
Acceptance Criteria
- Admin start decommission does not return success when peer reload fails.
- Decommission workers do not start after reload failure through the admin path.
- Failure output includes enough peer context for operators.
Risks
- Persisted pool meta may already be updated before reload failure is detected. Avoid adding rollback until the exact persistence safety is reviewed. Fail-closed reporting is the minimum safe fix.
F04: Store Init Recovery Ordering
Decision
Store init must load and install pool meta before deciding whether rebalance can auto-start.
Files
- Modify:
crates/ecstore/src/store/init.rs - Possibly modify:
crates/ecstore/src/rebalance.rs - Possibly modify:
crates/ecstore/src/pools.rs
Design
-
Reorder init sequence.
- Initialize boot time.
- Load pool meta.
- Validate and install pool meta.
- Resolve resumable decommission pools.
- Load rebalance meta.
- Start rebalance only if no decommission is active or resumable.
-
Define conflict behavior.
- If active decommission and started rebalance metadata coexist, decommission wins for Phase 1.
- Rebalance should not start.
- Record or expose a clear deferred/conflict reason if existing status structures can carry it without broad schema changes.
-
Preserve existing happy path.
- Clusters with only rebalance metadata should still auto-start rebalance.
- Clusters with only decommission metadata should still resume decommission after the existing delay.
Implementation Tasks
- Write a failing init test for active decommission plus rebalance metadata.
- Move pool meta load/validate/install before
load_rebalance_metaandstart_rebalance. - Add a helper such as
should_auto_start_rebalance_after_init. - Ensure decommission resume still uses the installed pool meta.
- Run focused init tests.
Focused Test Commands
cargo test -p rustfs-ecstore init --lib
cargo test -p rustfs-ecstore rebalance_meta --lib
cargo test -p rustfs-ecstore decommission --lib
Acceptance Criteria
- Rebalance does not start when active decommission is loaded from pool meta.
- Rebalance still starts when no decommission is active.
- Decommission resume behavior is unchanged except that it no longer races with an already-started rebalance.
Risks
- Init ordering can affect existing startup failure behavior. Keep error handling and stage names explicit so operators can identify which stage failed.
F05: Rebalance Distributed Start/Stop Semantics
Decision
Admin start/stop must not report plain success for partial cluster state. If stop remains asynchronous, status must expose that distinction.
Files
- Modify:
rustfs/src/admin/handlers/rebalance.rs - Modify:
rustfs/src/storage/rpc/node_service.rs - Modify:
crates/ecstore/src/notification_sys.rs - Modify:
crates/ecstore/src/rpc/peer_rest_client.rs - Possibly modify:
crates/ecstore/src/rebalance.rs
Design
-
Fix peer RPC stop first.
node_service::stop_rebalancemust returnsuccess=falseor gRPC error whenstore.stop_rebalance().awaitfails.notification_sys.stop_rebalancemust propagate aggregate peer failure instead of warning and returningOk.
-
Fix start propagation.
- Avoid returning success from peer
load_rebalance_meta(start=true)before local start validation has completed. - If fully synchronous worker startup is too invasive, split semantics:
- metadata loaded;
- start accepted;
- worker running observable through status.
- Admin start should fail when any required peer fails load/start acceptance.
- Avoid returning success from peer
-
Define rollback/degraded semantics.
- Preferred Phase 1: return failure on propagation failure and attempt local stop if local rebalance was already started.
- If rollback fails, return a degraded error and expose status.
-
Add status language for stop.
- If cancellation is requested but workers may still be running, status should not claim fully stopped.
- Minimal approach: add a
stoppingor equivalent field if existing response types allow it.
Implementation Tasks
- Write a failing test for peer stop returning success despite local stop error.
- Update
node_service::stop_rebalanceto return failure details. - Write a failing test for
notification_sys.stop_rebalanceaggregate peer failure. - Update
notification_sys.stop_rebalanceto return aggregate error. - Write a failing admin start propagation test.
- Update admin start to fail or rollback on propagation failure.
- Add status distinction for requested stop versus completed stop if response schema permits.
- Run focused admin/RPC/rebalance tests.
Focused Test Commands
cargo test -p rustfs-ecstore stop_rebalance --lib
cargo test -p rustfs-ecstore load_rebalance_meta --lib
cargo test -p rustfs rebalance --lib
Acceptance Criteria
- Peer stop failure is visible to callers.
- Admin stop fails or reports degraded state on peer failure.
- Admin start fails or reports degraded state on peer load/start failure.
- Stop status does not mislead operators while workers are still winding down.
Risks
- API response schema changes may affect clients. Prefer backward-compatible additions where possible, and keep error responses compatible with existing admin error handling.
Phase 1 Test Matrix
Run these once the individual fix tests pass:
cargo test -p rustfs-ecstore rebalance --lib
cargo test -p rustfs-ecstore decommission --lib
cargo test -p rustfs-ecstore pools --lib
cargo test -p rustfs rebalance --lib
cargo test -p rustfs pools --lib
cargo fmt --all --check
Before opening a PR with code changes:
cargo fmt --all
cargo fmt --all --check
make pre-commit
After build-based verification, clean generated build artifacts to avoid unnecessary disk usage.
Suggested PR Split
- PR 1: F02 only. Smallest fail-closed fix.
- PR 2: F04 only. Startup ordering and conflict tests.
- PR 3: F03 only. Decommission peer reload barrier.
- PR 4: F05 stop semantics first, then start semantics if review scope remains manageable.
- PR 5: F01 delete marker fix and remote tiered skip policy.
F01 is highest risk, but it may need the most design review. F02 and F04 are good first implementation candidates because they reduce safety risk with narrower code changes.
Open Questions Before Coding
- For F01 remote tiered versions, should Phase 1 match MinIO and skip them during rebalance, or should RustFS implement cross-pool remote metadata movement now?
- For F03 reload failure, should the implementation attempt to rollback persisted pool meta, or fail closed and expose degraded metadata state?
- For F05 start propagation, is a backward-compatible degraded error response acceptable, or must the API stay strictly compatible with current success/error shapes?