EFX-F-1 โ the fences a revert raises, and what they cover.
Two kinds, because they answer two different questions and are keyed
differently. An epoch fence says "this bundle's generation is rejected"; a
subject fence says "this tenant scope is barred" โ the erasure tombstone of
EFX-ER. Both are append-only: a fence that could be lowered would let a
revoked invocation mint its own permission by waiting.
Fence
dataclass
Fence(fence_id: str, kind: FenceKind, reason: str, raised_at: float, raised_by: str = '', bundle_id: str | None = None, epoch_ceiling: int | None = None, rejected_vector_hash: str | None = None, tenant_scope_hash: str | None = None)
One barrier. Keyed narrowly on purpose: a revert is not an outage.
covers
covers(lease: EffectLease) -> bool
Does this fence bar the generation (or subject) the lease captured?
Source code in src/symfonic/services/effects/fence.py
| def covers(self, lease: EffectLease) -> bool:
"""Does this fence bar the generation (or subject) the lease captured?"""
if self.kind is FenceKind.SUBJECT:
return self.tenant_scope_hash == lease.tenant_scope_hash
if self.bundle_id is not None and self.bundle_id != lease.bundle_id:
return False
if self.rejected_vector_hash is not None:
return self.rejected_vector_hash == lease.generation_vector_hash
if self.epoch_ceiling is not None:
return lease.admitted_epoch < self.epoch_ceiling
return False
|
FenceLedger
dataclass
FenceLedger(_fences: list[Fence] = list())
Append-only. Nothing here lowers a fence, and that is the point.
covering
covering(lease: EffectLease) -> Fence | None
The first fence that bars this lease, or None.
Source code in src/symfonic/services/effects/fence.py
| def covering(self, lease: EffectLease) -> Fence | None:
"""The first fence that bars this lease, or ``None``."""
for fence in self._fences:
if fence.covers(lease):
return fence
return None
|