cadrumo.adapters.persistence.profile.calculation_revision_override_migration module

Forward migration rekeying persisted relation overrides onto binding ids.

A CalculationRevision persists relation_overrides keyed by the id of the relation the operator overrode, and that mapping feeds the content-addressed revision id. Relations have since been absorbed into the binding providers that replaced them, so the runtime override channel is keyed by the TARGET BINDING id: an override still stored under a retired relation id matches no binding, contributes nothing, and does so without saying anything – the operator’s explicit figure silently stops applying to a filing-bound value.

This module is the forward, deterministic, idempotent stored-data migration that closes that gap. It rekeys every stored override through the frozen relation-to-binding join shipped beside it (relation_binding_join), recomputes each affected revision id through the canonical identity function so the content address still describes its contents, and reports the old-to-new revision id pairs it produced.

Properties the migration holds, in the order they matter:

Forward. It reads the retired vocabulary and writes the current one. There is no reverse hop and no dual-read: once a catalogue is migrated, the retired keys are gone from storage.

Deterministic. The join is frozen product data and the id recomputation is the same pure function the write path uses, so the same stored catalogue always yields the same migrated catalogue and the same revision ids.

Idempotent. A key that is already a binding id the join resolves onto is left exactly as it is, so a second run rekeys nothing, recomputes nothing, and returns the catalogue unchanged. Re-running after a partial failure is safe because the write is a single compare-and-swap of the whole singleton.

Non-destructive under doubt. A key that is neither a known relation id nor a known binding id is REFUSED, loudly, naming the key. It is never dropped and never guessed at: an unrecognised override key means either a corpus the frozen join does not describe or a corrupted catalogue, and both are conditions an operator must see rather than a zero a filing can quietly inherit.

The join is many-to-one, so two retired relation ids in one revision can fold onto the same binding. That collapse is accepted only when both stored values are identical; a genuine disagreement is refused for the same reason, since picking a winner would discard an operator-entered figure.

Stored override VALUES are taxpayer figures and appear in no result field and no log record here. The migration reports identifiers only.

exception OrphanedRelationOverrideError(message=None, *, context=None, translated_message=None)[source]

Bases: CalculationRevisionPersistenceError

Raised when a stored override key resolves to neither a relation nor a binding.

Carries the offending keys and the owning revision id so an operator can find the catalogue entry. It does NOT carry the override values: they are taxpayer figures, and an error context is one of the surfaces that ends up in a log.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • translated_message (str | None)

Return type:

None

code
class CalculationRevisionIdRemap(**data)[source]

Bases: BaseModel

One revision’s content address before and after its overrides were rekeyed.

Parameters:
  • previous_calculation_revision_id (str)

  • calculation_revision_id (str)

  • work_unit_id (str)

previous_calculation_revision_id: str
calculation_revision_id: str
work_unit_id: str
class RelationOverrideRekey(**data)[source]

Bases: BaseModel

One override key’s move from its retired relation id onto its binding id.

Parameters:
  • calculation_revision_id (str)

  • relation_id (RelationId)

  • binding_id (BindingId)

calculation_revision_id: str

The id the owning revision carried BEFORE the rekey, so the pair can be traced back to the catalogue entry the migration read.

relation_id: RelationId
binding_id: BindingId
class CalculationRevisionOverrideMigrationResult(**data)[source]

Bases: BaseModel

What one migration run changed, reported as identifiers only.

rekeyed_revisions is empty on an already-migrated catalogue, which is how a caller distinguishes a no-op re-run from a first run.

Parameters:
catalogue: CalculationRevisionCatalogue
rekeyed_revisions: tuple[CalculationRevisionIdRemap, ...]
rekeyed_override_keys: tuple[RelationOverrideRekey, ...]
unchanged_revision_ids: tuple[str, ...]
property changed: bool

Report whether this run rekeyed anything at all.

rekey_calculation_revision_overrides(catalogue, *, operation)[source]

Rekey every stored relation override onto its binding id, in memory.

The pure half of the migration: no storage is touched, so this is the function tests and the repository-facing entry point share. A revision whose overrides are already binding-keyed – including every revision with no overrides at all – is carried through untouched and its content address is not recomputed, which is what makes a second run a no-op.

Parameters:
Return type:

CalculationRevisionOverrideMigrationResult

Returns:

The migrated catalogue plus the identifier-only record of what moved.

Raises:

OrphanedRelationOverrideError – A stored override key could not be resolved, or two keys folded onto one binding with different values.

migrate_stored_relation_overrides_to_binding_ids(repository, *, operation)[source]

Rekey one profile’s stored overrides and persist the result atomically.

The read is revision-guarded and the write is the singleton’s own compare-and-swap, so a calculate run committing between the two makes this call fail rather than discard that run’s revision. A failed run leaves the stored catalogue exactly as it was and can simply be re-run.

An already-migrated catalogue is not written back at all: the migration is idempotent in storage as well as in memory, so re-running it does not churn the row’s revision lineage.

Parameters:
Return type:

CalculationRevisionOverrideMigrationResult

Returns:

The identifier-only record of what moved, carrying the migrated catalogue.

Raises:

OrphanedRelationOverrideError – A stored override key could not be resolved to a binding.