Skip to content

symfonic.services.privacy.registry

registry

PRIV-1 — coverage is a registry, not a list somebody remembers to update.

SEC-PRIV-1 defines coverage as "keyed to tenant". The only way that can be checked is if the set of subject-bearing stores is declared where a check can read it — which is what makes "the erasure path MUST be extended before the store accepts tenant data" an architecture rule (T4.4.1) instead of a habit.

SubjectDataStoreRegistry

SubjectDataStoreRegistry(registry_id: str = 'default')

The set of stores the erasure saga enumerates.

An instance, not a module global: HOST-3 forbids process-global mutable state for per-deployment facts, and two hosts in one process (a test suite, a sidecar, an adopter mounting two agents) must not share an erasure surface neither of them declared.

Source code in src/symfonic/services/privacy/registry.py
def __init__(self, registry_id: str = "default") -> None:
    self._registry_id = registry_id
    self._stores: dict[str, SubjectDataStore] = {}

participants

participants() -> tuple[SubjectDataStore, ...]

Stable order, so a resumed saga sweeps in the same sequence.

Source code in src/symfonic/services/privacy/registry.py
def participants(self) -> tuple[SubjectDataStore, ...]:
    """Stable order, so a resumed saga sweeps in the same sequence."""
    return tuple(self._stores[key] for key in sorted(self._stores))

register

register(store: SubjectDataStore) -> None

Add a participant. A duplicate id is an error, not an overwrite.

Silently replacing would drop a live store out of the erasure path and leave a registry that looks complete — the worst of both.

Source code in src/symfonic/services/privacy/registry.py
def register(self, store: SubjectDataStore) -> None:
    """Add a participant. A duplicate id is an error, not an overwrite.

    Silently replacing would drop a live store out of the erasure path and
    leave a registry that *looks* complete — the worst of both.
    """
    participant_id = store.describe().participant_id
    if not participant_id:
        raise ParticipantRegistrationError(
            "a subject-data store must declare a participant id; an unnamed "
            "participant cannot be reported as covered or missing"
        )
    if participant_id in self._stores:
        raise ParticipantRegistrationError(
            f"{participant_id!r} is already registered with the "
            f"{self._registry_id!r} erasure registry; overwriting it would "
            "silently remove a store from the erasure path"
        )
    self._stores[participant_id] = store