symfonic.core.prompt.blocks.protocol_conflict¶
protocol_conflict ¶
Optimistic-concurrency vocabulary for writable block sources.
Split out of :mod:symfonic.core.prompt.blocks.protocol (441 lines against the
300-line budget). protocol describes what a block source can do -- three
widening capability Protocols, checked structurally. This module is the one
thing a writable source must agree with its caller about beyond those method
signatures: what happens when the head moved underneath an append.
RevisionConflictError carries the two heads, is picklable across a process
boundary through _rebuild_revision_conflict_error, and
ensure_expected_head is the single place the comparison is spelled out so
two adapters cannot disagree about it.
protocol re-exports both names.
RevisionConflictError ¶
RevisionConflictError(*, scope_path: str, block_id: str, expected_head: str | None, actual_head: str | None)
Bases: ConflictError
An append was attempted against a head the caller no longer holds.
Raised by :meth:WritableBlockSource.append_revision when
expected_head does not match the block's current head revision --
another writer appended in between. The write is rejected; it is
never applied on top of the newer revision, because doing so would
silently discard the concurrent edit while leaving the history
looking linear.
It derives from :class:~symfonic.core.protocols.ConflictError (and
so from StorageError) deliberately: an optimistic-lock failure is
the same concept whether it is detected by
:func:ensure_expected_head or by the backing store's own unique
constraint. A caller mapping except ConflictError to a 409 must
catch both paths, or the pre-check path would surface as a 500.
Source code in src/symfonic/core/prompt/blocks/protocol_conflict.py
ensure_expected_head ¶
ensure_expected_head(scope: TenantScope, block_id: str, *, expected_head: str | None, actual_head: str | None) -> None
Raise :class:RevisionConflictError unless the heads agree.
The shared optimistic-concurrency check for
:meth:WritableBlockSource.append_revision implementations, so every
adapter rejects a stale write the same way instead of each inventing
its own (or, worse, overwriting). It sits on top of the backing
store's own unique-sequence constraint, it does not replace it.