Skip to content

symfonic.services.effects.fence

fence

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