The pre-fix swallowed-VM.Accept bug let a node's durable decided-floor
(vote-guard finalizedThrough) advance past every VM's last-accepted head. On
reboot WithVoteGuard re-seeds that phantom floor, the sign gate treats the
never-applied heights as decided, and the round-scoped view-change can never
re-finalize them — the chain is wedged even after the liveness fix.
Add a DELIBERATE, bounded recovery:
- VoteGuardStore.Reconcile: the ONE writer permitted to LOWER finalizedThrough
(Persist stays monotonic). DRY atomic write shared via writeSnapshot.
- Transitive.ReconcilePhantomFloor(target): lowers decidedFloor to
max(target, localVM) — an intrinsic guard that can never abandon a block THIS
node applied — drops committedSlot/lockRounds/recoveredLocks above it, and
durably rewrites the lowered floor. Fail-closed (nothing changes on a store
error); idempotent; a zero target is a no-op.
Safety (no observable double-cert): reconciling only heights strictly above the
fleet-max VM-applied height abandons ONLY internal-only finalizations — no VM
applied them (never externalized over RPC/bridge/light-client) and their cert
material was in-memory and is gone post-restart, so no party can present a cert;
re-finalizing fresh yields exactly one presentable cert. Committed heights
at/below the floor are recovered by frontier-trust bootstrap, never re-finalized.
Tests: floor lowered + phantom dropped + committed retained; durable monotonic
bypass; intrinsic guard vs wrong-low target; behind-node target-governs;
phantom-becomes-signable/committed-stays-decided; no-ops; idempotent;
fail-closed. Full engine/chain suite green under -race.
Co-authored-by: Hanzo Dev <[email protected]>