Skip to content

symfonic.services.privacy.legacy

legacy

Lifting the shipped erasure participants into the row-20 registry.

T2.3.7's recording store already registers with symfonic.services.shadow.privacy.PRIVACY_DELETION_PARTICIPANTS, and SEC-PRIV-5 makes its coverage release-blocking. Asking every existing participant to be rewritten against the new port before the new port has a consumer would be the wrong order: the migration-window answer is an adapter, so the registry PRIV-1 wants is the union of both contracts and no store falls between them.

The adapter is honest about what it cannot do. The legacy contract has an erase and a count and no export, so :meth:LegacyParticipantAdapter.export_subject returns a fragment marked exportable=False rather than an empty one — an empty fragment would tell an operator the store held nothing about the subject, which is a different and much worse claim.

LegacyParticipantAdapter

LegacyParticipantAdapter(participant: _LegacyParticipant)

A TenantErasureParticipant seen through the row-20 port.

The legacy contract is keyed by tenant_id alone, so the adapter passes the subject's tenant segment and documents the consequence: erasing a sub-tenant through a legacy participant erases the whole tenant's rows in that store. That is the conservative direction — over-erasure of the caller's own subtree, never under-erasure — and it is recorded here rather than discovered later.

Source code in src/symfonic/services/privacy/legacy.py
def __init__(self, participant: _LegacyParticipant) -> None:
    self._participant = participant

adapt_legacy_registry

adapt_legacy_registry(legacy: object, *, into: SubjectDataStoreRegistry) -> tuple[str, ...]

Register every legacy participant with into; return the ids added.

Takes the registry structurally (participants()) so this module does not import the shadow package — the erasure path must not acquire a dependency on the cutover machinery in order to sweep it.

Source code in src/symfonic/services/privacy/legacy.py
def adapt_legacy_registry(legacy: object, *, into: SubjectDataStoreRegistry) -> tuple[str, ...]:
    """Register every legacy participant with ``into``; return the ids added.

    Takes the registry structurally (``participants()``) so this module does not
    import the shadow package — the erasure path must not acquire a dependency
    on the cutover machinery in order to sweep it.
    """
    added: list[str] = []
    participants = getattr(legacy, "participants", None)
    if not callable(participants):
        return ()
    for participant in participants():
        adapter = LegacyParticipantAdapter(participant)
        participant_id = adapter.describe().participant_id
        if into.holds(participant_id):
            continue
        into.register(adapter)
        added.append(participant_id)
    return tuple(added)