symfonic.services.effects.accounting¶
accounting ¶
What a revert is entitled to say, derived from what the fence reached.
This is the pessimistic half of revert.py, separated from it because the two
halves answer different questions and fail differently. revert.py acts: it
raises the fence, revokes leases, requests cancellation, drives the drain.
Nothing here acts on the system at all — every method turns the set of
invocations the fence covered into a record, and the only mutation any of them
performs is the trust demotion OpaqueExposureAccountant owns.
Keeping them apart matters because the accounting is the part that must never overstate. A change to the drain or the linearization point cannot silently change what the incident claims, and a reviewer auditing the claims does not have to read the concurrency to do it.
RevertAccounting ¶
RevertAccounting(*, accountant: OpaqueExposureAccountant, containment: ContainmentCoordinator, ports: FencedPortRegistry)
Turns the fenced set into exposures, claims, and blocked cutovers.
Source code in src/symfonic/services/effects/accounting.py
blocked ¶
Ports whose exposure has no approved rule, so cutover cannot proceed.
Source code in src/symfonic/services/effects/accounting.py
claims ¶
claims(invocations: tuple[str, ...], exposures: tuple[ExposureRecord, ...]) -> tuple[FencingClaim, ...]
One claim per affected invocation, over both axes of exposure.
The accountant only knows extensions. An invocation that ran none but whose irreversible effect had already left the process is still exposed, and printing SUPPRESSED beside its own exposure record would be precisely the overstatement this package exists to prevent — so the port-crossing exposures override a clean extension verdict here.
Source code in src/symfonic/services/effects/accounting.py
contain_all ¶
Run the approved compensation for each exposure, or record its absence.
exposures ¶
exposures(dispatched: list[EffectTicket], invocations: tuple[str, ...]) -> tuple[ExposureRecord, ...]
Irreversible effects that already left, plus every opaque extension.
Source code in src/symfonic/services/effects/accounting.py
invocations
staticmethod
¶
invocations(covered: tuple[EffectLease, ...], affected: tuple[EffectTicket, ...]) -> tuple[str, ...]
Every invocation the fence reached, whether or not it crossed a port.
Effect tickets are the wrong sole key here. An opaque in-process extension performs its effects without crossing a classified port, so an invocation can hold a fenced lease and own no ticket at all — exactly the invocation a revert must never leave out of the record. An invocation is accounted for because it was authorized under the rejected generation, not because it happened to cross a port the framework can see.