Skip to content

symfonic.services.privacy.saga_values

saga_values

PRIV-3 — the durable state that makes erasure resumable instead of hopeful.

The shape is deliberately per-participant. A single "erasure in progress" flag would tell a resumed run nothing about which of nine stores already went, so either it redoes everything (and a store whose erase is not idempotent is now a new problem) or it skips everything (and the subject's data survives).

ParticipantProgress dataclass

ParticipantProgress(participant_id: str, confirmed: bool = False, exhausted: bool = False, attempts: int = 0, erased: int = 0, last_error: str = '')

One store's place in the saga.

confirmed means absence was verified (SEC-PRIV-3), not that an erase call returned. exhausted means the bounded retry ran out — recorded rather than raised, because one unreachable backend must not abort the other eight.

SagaState dataclass

SagaState(scope_key: str, started_at: float, progress: dict[str, ParticipantProgress] = dict(), generation_advanced: bool = False)

Everything a resumed saga needs, and nothing a transport would add.

exhausted

exhausted() -> tuple[str, ...]

Participants whose bounded retry ran out.

Source code in src/symfonic/services/privacy/saga_values.py
def exhausted(self) -> tuple[str, ...]:
    """Participants whose bounded retry ran out."""
    return tuple(
        pid for pid in sorted(self.progress) if self.progress[pid].exhausted
    )

pending

pending() -> tuple[str, ...]

Participants that still owe a confirmed absence.

Source code in src/symfonic/services/privacy/saga_values.py
def pending(self) -> tuple[str, ...]:
    """Participants that still owe a confirmed absence."""
    return tuple(
        pid
        for pid in sorted(self.progress)
        if not self.progress[pid].confirmed and not self.progress[pid].exhausted
    )

unconfirmed

unconfirmed() -> tuple[str, ...]

Everything not yet proven absent — the read-suppression set.

Source code in src/symfonic/services/privacy/saga_values.py
def unconfirmed(self) -> tuple[str, ...]:
    """Everything not yet proven absent — the read-suppression set."""
    return tuple(
        pid for pid in sorted(self.progress) if not self.progress[pid].confirmed
    )