From e196abfd710344792fa8fa51380ae1b71b875218 Mon Sep 17 00:00:00 2001 From: ms-lead-reviewer Date: Sun, 19 Jul 2026 17:40:09 -0500 Subject: [PATCH] fix(#832): retain revoke fence on rejected cycles --- .../832-receipt-challenge-protocol.md | 2 +- .../framework/tools/lease-broker/daemon.py | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/scratchpads/832-receipt-challenge-protocol.md b/docs/scratchpads/832-receipt-challenge-protocol.md index 1b8ada9a..9bc36483 100644 --- a/docs/scratchpads/832-receipt-challenge-protocol.md +++ b/docs/scratchpads/832-receipt-challenge-protocol.md @@ -10,4 +10,4 @@ 3. Implement the broker-minted receipt challenge and exact receipt observation/consume/promote path. 4. Run unit, framework-shell, compile, lint, and type checks; push after the required queue guard; report to `mosaic-100`. - **Risks:** The standalone harness must drive the real daemon without a divergent fixture. If that is impossible, stop and flag Mos. -- **Evidence:** Initial RED recorded in `/home/hermes/agent-work/reviews/832-wi5-red-receipt-challenge.log`; initial green checks passed. Remediation RED recorded in `/home/hermes/agent-work/reviews/832-wi5-remediation-red.log` before observer/payload implementation: forged `h_source`/`h_payload` was accepted and the observer module was absent. Remediation GREEN: observer/payload/T26/T29 unittest (4 tests), normative-fragments unittest (5), state-store regression (10), full mutator-gate acceptance (20, including the real begin → observer → consume → promote path), `py_compile`, Mosaic package lint/typecheck, and targeted Prettier check. The P5 harness was updated for the test-observer seam but was not fired. Coverage tooling remains unavailable (`python3 -m coverage`: module not installed). Push pending. +- **Evidence:** Initial RED recorded in `/home/hermes/agent-work/reviews/832-wi5-red-receipt-challenge.log`; initial green checks passed. Remediation RED recorded in `/home/hermes/agent-work/reviews/832-wi5-remediation-red.log` before observer/payload implementation; remediation green passed. Remediation-2 RED recorded in `/home/hermes/agent-work/reviews/832-wi5-remediation2-red.log`: each rejected begin restored prior VERIFIED authority. Remediation-2 GREEN: receipt unittest (5: all `INVALID_CONSTRUCTION`, `PAYLOAD_CONSTRUCTION_REFUSED`, and `PAYLOAD_BINDING_MISMATCH` cases preserve UNVERIFIED and deny the next mutator), normative-fragments unittest (5), state-store regression (10), full mutator-gate acceptance (20, including the real begin → observer → consume → promote path), `py_compile`, Mosaic package lint/typecheck, and targeted Prettier check. The P5 harness remains unfired. Coverage tooling remains unavailable (`python3 -m coverage`: module not installed). Push pending. diff --git a/packages/mosaic/framework/tools/lease-broker/daemon.py b/packages/mosaic/framework/tools/lease-broker/daemon.py index 7af3a61c..8df693ff 100644 --- a/packages/mosaic/framework/tools/lease-broker/daemon.py +++ b/packages/mosaic/framework/tools/lease-broker/daemon.py @@ -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: