Phase 2.5 of the decentralization roadmap — make CONCURRENT masters safe where Stage A (functionland/pinning-service#46) made fenced failover possible. Gate for Stage B (vetted third-party storage-only operators).
Ships
- FM-1 — bucket-root CAS: the per-bucket root pointer is currently guarded only by IN-PROCESS locks (
fula-core/src/bucket.rs:428 per-bucket DashMap mutex, :432 registry persist lock) — two concurrent gateways writing one bucket = silent lost updates. Add compare-and-swap on the expected root CID (reject + bounded retry on mismatch) with pre-commit pinning of the new root blocks BEFORE the pointer flips, over a registry store readable by all masters. Flag-gated, default OFF (single-master behavior byte-identical when dark).
- FM-4 — portable identity: EIP-712 wallet-signature auth as an ADDITIVE auth path (existing JWT/session auth untouched) so one identity works on any master without master-local secrets.
- Auto-failover graduation: with CAS live, a wrongly-presumed-dead master becomes harmless — the Stage A runbook's fenced manual flip can graduate to health-based automatic failover.
join-as-master.sh storage-only profile (cross-repo, pinning-service): follower + ingest, no cluster write authority, no DB.
Verify (e2e, test infra)
Two gateways against the same stack: concurrent same-bucket writes -> both commit or one cleanly rejects+retries, NEVER silently lost (CAS race drill); unfenced failover during live writes loses nothing; EIP-712 login works against both masters with the same wallet; storage-only profile runs end-to-end on a third box/container.
Data-safety
CAS behind a flag; no auth removal; no migration rewrites; mixed-fleet invariant holds.
🤖 Generated with Claude Code
Phase 2.5 of the decentralization roadmap — make CONCURRENT masters safe where Stage A (functionland/pinning-service#46) made fenced failover possible. Gate for Stage B (vetted third-party storage-only operators).
Ships
fula-core/src/bucket.rs:428per-bucket DashMap mutex,:432registry persist lock) — two concurrent gateways writing one bucket = silent lost updates. Add compare-and-swap on the expected root CID (reject + bounded retry on mismatch) with pre-commit pinning of the new root blocks BEFORE the pointer flips, over a registry store readable by all masters. Flag-gated, default OFF (single-master behavior byte-identical when dark).join-as-master.shstorage-only profile (cross-repo, pinning-service): follower + ingest, no cluster write authority, no DB.Verify (e2e, test infra)
Two gateways against the same stack: concurrent same-bucket writes -> both commit or one cleanly rejects+retries, NEVER silently lost (CAS race drill); unfenced failover during live writes loses nothing; EIP-712 login works against both masters with the same wallet; storage-only profile runs end-to-end on a third box/container.
Data-safety
CAS behind a flag; no auth removal; no migration rewrites; mixed-fleet invariant holds.
🤖 Generated with Claude Code