symfonic.services.conversation.registry¶
registry ¶
The authoritative checkpoint registry.
"Authoritative" in the strong sense: a checkpoint the registry has never seen is not resumable, however healthy it looks in the backend. That inversion is the point — without it, "which checkpoints exist?" is answered by whichever storage engine happens to be attached, and no drain, freeze, or horizon can ever reach a fixed point.
Three governance verbs live here and nowhere else:
- issuance freeze — closes new issuance while leaving reads and idempotent replays open, so a retirement drain can finish;
- idempotent safe-boundary markers — derived ids, so marking the same boundary from a retry or a second process yields one marker;
- crash-expiry reconciliation — state that was issued but never finalized is expired explicitly, with a reason, rather than lingering as a resumable frame nobody can vouch for.
CheckpointRegistry ¶
The single source of truth for which checkpoints exist and may resume.
Source code in src/symfonic/services/conversation/registry.py
expire ¶
Mark a ref expired. Recorded, never deleted.
Source code in src/symfonic/services/conversation/registry.py
finalize ¶
Close the crash window on a ref. Unknown refs are refused.
Source code in src/symfonic/services/conversation/registry.py
freeze_issuance ¶
Close new issuance. The first reason is the one that is kept.
A second freeze does not overwrite the first: the operator who declared the drain is the one whose reason belongs in the record.
Source code in src/symfonic/services/conversation/registry.py
mark_safe_boundary ¶
mark_safe_boundary(thread_id: str, *, sequence: int, digest: str, writer_line: WriterLine, boundary_id: str | None = None) -> SafeBoundaryMarker
Record a replayable boundary. Idempotent by derived id.
boundary_id re-adopts a boundary under the id its writer derived,
which is what makes the marker idempotent across a restart as well as
within one process: the (sequence, digest) it was hashed from are not
recoverable from a bare listing, so re-deriving mints a second id.
Source code in src/symfonic/services/conversation/registry.py
migration_lock ¶
The lock every migrator must hold while replaying this legacy ref.
It lives here, not on the migrator, for the same reason
:class:MigrationLink does: the window it closes spans an awaited
replay whose side effects the port commits before any link exists, so
a lock scoped to one migrator instance serialises nothing once two
migrators share this registry. record_migration is first-wins, but
by then the loser has already replayed. Cross-process exclusion is
out of scope for an in-memory registry; a durable implementation
supplies it by making this lock durable.
Source code in src/symfonic/services/conversation/registry.py
migration_of ¶
The replay this legacy ref already produced, if any.
reconcile_crash_expiry ¶
Expire issued-but-never-finalized state older than grace.
Idempotent: an already-expired ref is not reported a second time, so a reconciler on a timer does not manufacture a rising expiry count.
Source code in src/symfonic/services/conversation/registry.py
record_migration ¶
record_migration(thread_id: str, legacy_checkpoint_id: str, *, checkpoint_id: str, boundary_id: str) -> MigrationLink
Link a legacy ref to the checkpoint its replay produced.
Idempotent, and the first link wins: if two racing replays somehow both landed, the one already recorded is the one every later reader resolves to, so a legacy ref never resolves to two different migrated checkpoints depending on who asks.
Source code in src/symfonic/services/conversation/registry.py
refs_for ¶
Every known ref for a thread, oldest first.
Source code in src/symfonic/services/conversation/registry.py
register ¶
Record a checkpoint. Idempotent; refuses a changed writer line.
Re-registering an identical ref while frozen is explicitly allowed: an idempotent replay of state that was already issued is not issuance, and refusing it would make a retry during a drain look like a new checkpoint.
issuance=False says "this ref records durable state that already
exists; registering it is bookkeeping catch-up", so the freeze does not
apply. Two callers may say it: :meth:register_replay (which keys the
claim to a legacy ref already accounted for) and restart adoption
(whose rows were read out of the backend, so refusing them prevents
no state from existing — it only leaves the thread quarantined).
Source code in src/symfonic/services/conversation/registry.py
register_replay ¶
Register the checkpoint a safe-boundary replay produced.
This is the write a retirement drain is made of, so it survives an issuance freeze — otherwise freezing issuance to drain legacy state would guarantee nothing could ever be drained, and the freeze's stated purpose ("leaving reads and idempotent replays open, so a retirement drain can finish") would be unreachable.
The exemption is keyed, not blanket: while frozen, the legacy ref must already be authoritative here. A replay of something this registry has never seen is new state wearing the word "migration".
Source code in src/symfonic/services/conversation/registry.py
IssuanceFreeze
dataclass
¶
Why and when new issuance was closed.
MigrationLink
dataclass
¶
MigrationLink(thread_id: str, legacy_checkpoint_id: str, checkpoint_id: str, boundary_id: str, at: datetime)
Which migrated checkpoint a legacy ref was replayed into, and from where.
The link lives in the registry rather than on the migrator because the question "has this legacy frame already been replayed?" outlives any one migrator instance. Answering it from an instance attribute means a second migrator — a second worker, a retry after a restart — replays a thread the first one already replayed, with whatever side effects the replay port committed.
ReconciliationReport
dataclass
¶
ReconciliationReport(at: datetime, grace_seconds: float, expired: tuple[CheckpointRef, ...] = (), inspected: int = 0)
What a crash-expiry pass expired, and on what grounds.