fix(#832): retain revoke fence on rejected cycles
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
ms-lead-reviewer
2026-07-19 17:40:09 -05:00
parent b6e1461952
commit e196abfd71
2 changed files with 22 additions and 3 deletions

View File

@@ -303,6 +303,10 @@ class Broker:
def __init__(self, store: StateStore, observer: ReceiptObserver | None = None) -> None:
self.store = store
self.observer: ReceiptObserver = observer if observer is not None else UnavailableReceiptObserver()
# Set only by begin_verification after its mandatory revoke-first fence.
# It is preserved if later cycle admission is refused; all other broker
# actions retain the normal snapshot rollback behavior.
self._rejected_cycle_fence: tuple[dict[str, object], dict[str, dict[str, object]]] | None = None
# VERIFIED authority is deliberately volatile: broker restart revokes all
# leases while preserving WI-1 identity and pending-token integrity.
self.leases: dict[str, dict[str, object]] = {}
@@ -391,6 +395,7 @@ class Broker:
raise StateCommitUncertain()
previous = copy.deepcopy(self.store.value)
previous_leases = copy.deepcopy(self.leases)
self._rejected_cycle_fence = None
try:
response = self._handle(peer, request)
if self.store.value != previous:
@@ -405,9 +410,18 @@ class Broker:
except StateCommitUncertain:
raise
except Exception:
self.store.value = previous
self.leases = previous_leases
fence = self._rejected_cycle_fence
if fence is None:
self.store.value = previous
self.leases = previous_leases
else:
# A refused re-verification must never resurrect the preceding
# VERIFIED authority. Preserve only this post-revoke fence;
# every unrelated partial-write failure still rolls back.
self.store.value, self.leases = fence
raise
finally:
self._rejected_cycle_fence = None
def _handle(self, peer: tuple[int, int, int], request: dict[str, object]) -> dict[str, object]:
peer_pid, peer_uid, peer_gid = peer
@@ -473,6 +487,11 @@ class Broker:
raise BrokerFailure("INVALID_LEASE_TTL")
# Revoke-first is a broker operation, not advisory adapter order.
self.revoke_session_authority(session_id)
# If subsequent construction admission rejects, handle() restores
# this fence rather than the pre-cycle VERIFIED snapshot.
self._rejected_cycle_fence = (
copy.deepcopy(self.store.value), copy.deepcopy(self.leases)
)
try:
constructed = build_payload_from_wire(construction)
except ValueError as exc: