cadrumo.domain.buckets.event module

Bucket-scoped append-only event records.

A BucketEvent captures one material workflow transition inside a bucket. Events are immutable, content-addressed by their (bucket_id, event_type, occurred_at, actor, object_type, object_id, payload) tuple, and grouped into a frozen catalogue.

The closed BucketEventType enum fixes the emission scope mandated by the bucket event history specification. New event kinds enter the codebase as enum additions, never as ad-hoc strings.

BUCKET_ACTOR_LABEL_MAX_LENGTH: Final[int]

Maximum length of a bucket-event actor label.

Named rather than inlined because a CLI surface accepting an operator-supplied actor has to refuse an over-long value at the boundary, quoting the accepted bound. Reaching the bound only when the event is constructed makes the refusal arrive after the work it records has already been done, and leaves the operator with no accepted-value set to correct against.

BUCKET_EVENT_PAYLOAD_VALUE_MAX_LENGTH: Final[int]

Maximum length of a single bucket-event payload value.

The bound keeps the append-only event log bounded per event: the log is a substrate every service in the application writes to, so one unbounded producer degrades a shared surface rather than only its own.

A payload value is a fixed-width fact, never a variable-length join. The bound is enforced by refusal, not truncation — a silently shortened audit value is worse than a refused write — so a producer that joins a collection into one value stops being able to record its own event once the collection grows. Six occurrences of that shape have been found, each independently: joined attachment ids on transaction removal, joined child ids on ledger split and on merge, an unbounded evidence path, an unbounded export row set, and the reconcile diff detail. Identifiers are commonly 64-char SHA-256 digests, so the ceiling is reached at eight joined ids (519 characters) — low enough to be reachable by ordinary use.

A producer holding a collection has exactly three sanctioned options:

  • emit a count (*_count) when the members are recoverable from their own catalogue or from sibling events in the same batch;

  • emit a digest (sha256_hex over the joined members) when the set must stay verifiable but need not be enumerable from the log;

  • give the detail a durable home of its own when the event log is its only copy, and leave the event carrying verdict plus count.

The static gate domain/buckets/tests/test_payload_value_bounding.py refuses a new join into a payload value at authoring time, which is where every one of the six occurrences should have been caught.

class BucketEventType(*values)[source]

Bases: StrEnum

Closed catalogue of bucket-event kinds.

The enum mirrors the per-service emission scope declared by the bucket event history specification. Emitters land incrementally with their owning implementations; new kinds are added here only when a corresponding specification sanctions the emission.

MODELO_CALCULATION_CREATED
MODELO_VERIFICATION_PASSED
MODELO_VERIFICATION_REFUSED
MODELO_FILED
MODELO_FILED_SUPERSEDED
MODELO_AMENDED
MODELO_FILING_IMPORTED
MODELO_FILING_RECONCILED
MODELO_OBSERVATION_OVERRIDDEN
MODELO_OBSERVATION_OVERRIDE_CLEARED
MODELO_LIVE_EVIDENCE_STAMPED
MODELO_LEDGER_EVIDENCE_RECAPTURED
MODELO_RECONCILED
MODELO_EXPORTED
MODELO_IVA_WALLET_CORRECTED
MODELO_IVA_WALLET_OVERRIDE_RECORDED
MODELO_145_COMMUNICATION_CREATED
MODELO_145_COMMUNICATION_EXPORTED
MODELO_145_COMMUNICATION_DELIVERED_TO_PAYER
MODELO_145_COMMUNICATION_LOCALLY_COMPLETED
MODELO_WORK_UNIT_CREATED
MODELO_WORK_UNIT_DISCARDED
MODELO_WORK_UNIT_RENAMED
PROFILE_BUCKET_CREATED
PROFILE_SELECTED
PROFILE_VALUES_UPDATED
PROFILE_VALUES_CLEARED
PROFILE_DUPLICATED
PROFILE_RENAMED
PROFILE_EXPORTED
PROFILE_IMPORTED
PROFILE_ACTIVATED
PROFILE_SETUP_COMPLETED
PROFILE_PASSPHRASE_ROTATED
CENSO_APPLIED
CENSO_DECLARATION_ALTA
CENSO_DECLARATION_MODIFICACION
CENSO_DECLARATION_BAJA
BUCKET_EXPORTED
BUCKET_IMPORTED
BUCKET_RENAMED
BUCKET_DELETED
BUCKET_ARCHIVED
BUCKET_RESTORED
BUCKET_MERGED
LEDGER_RATIOS_SET
LEDGER_RATIOS_UNSET
LEDGER_RATIOS_CENSO_OVERRIDE_WARNING
AUTH_PROVIDER_CONFIGURED
AUTH_PROVIDER_CLEARED
AUTH_SESSION_VERIFIED
AUTH_SESSION_CLEARED
AUTH_LOCK_CLEARED
CONFIG_ENV_UPDATED
AUTH_CERTIFICATE_SOURCE_REGISTERED
AUTH_CERTIFICATE_SOURCE_SELECTED
AUTH_CERTIFICATE_SOURCE_REMOVED
AUTH_CERTIFICATE_SOURCE_SECRET_SET
AUTH_CERTIFICATE_SOURCE_SECRET_ROTATED
AUTH_CERTIFICATE_SOURCE_SECRET_REMOVED
CUSTODY_PASSPHRASE_CHANGED
CUSTODY_RECOVERY_CODE_CREATED
CUSTODY_RECOVERY_CODE_ROTATED
CUSTODY_SECRET_STORE_RECOVERED
LEDGER_TRANSACTION_CREATED
LEDGER_TRANSACTION_IMPORTED
LEDGER_IMPORT_DIAGNOSTIC_RECORDED
LEDGER_TRANSACTION_UPDATED
LEDGER_TRANSACTION_CLASSIFIED
LEDGER_TRANSACTION_LLM_SUGGESTION_REJECTED
LEDGER_TRANSACTION_ALLOCATED
LEDGER_TRANSACTION_REMOVED
LEDGER_TRANSACTION_ARCHIVED
LEDGER_TRANSACTION_STASHED
LEDGER_TRANSACTION_RESTORED
LEDGER_TRANSACTION_REVIEWED_EXCLUDED
LEDGER_TRANSACTION_EXPORTED
LEDGER_TRANSACTION_SPLIT
LEDGER_TRANSACTION_MERGED
LEDGER_TRANSACTION_INVOICE_LINKED
LEDGER_CATALOGUE_RESET
LEDGER_SANITIZATION_COMPLETED
PURCHASE_INVOICE_EVIDENCE_ATTACHED
PURCHASE_INVOICE_EVIDENCE_REPLACED
PURCHASE_INVOICE_EVIDENCE_DETACHED
PURCHASE_INVOICE_EVIDENCE_DRAFT_DECLINED
PAYABLE_INVOICE_CREATED
PAYABLE_INVOICE_UPDATED
PAYABLE_INVOICE_REMOVED
COLLECTIBLE_INVOICE_CREATED
COLLECTIBLE_INVOICE_UPDATED
COLLECTIBLE_INVOICE_REMOVED
LEDGER_INVENTORY_CREATED
LEDGER_INVENTORY_MOVEMENT_ADDED
LEDGER_INVENTORY_VALUATION_PREVIEWED
LEDGER_INVENTORY_REMOVED
MODELO_AUDIT_VERIFIED
MODELO_AUDIT_EXPORTED
LIVE_NOTIFICATIONS_SNAPSHOT_CAPTURED
LIVE_EXPEDIENTES_SNAPSHOT_CAPTURED
LIVE_VERIFY_NIF_IVA_CHECKED
LIVE_VERIFY_TGVI_CHECKED
LIVE_BORRADOR100_SNAPSHOT_CAPTURED
SYNC_RUN_FILED_DECLARATIONS_COMPLETED
SYNC_RUN_CALC_SHEETS_EXPORT_COMPLETED
ATTACHMENT_LINKED
ATTACHMENT_REMOVED
WORKFLOW_STATE_RESET
LEDGER_TRANSACTION_CORRECTION_APPLIED
LEDGER_PURCHASE_INVOICE_EVIDENCE_CORRECTION_APPLIED
LEDGER_PAYABLE_INVOICE_CORRECTION_APPLIED
LEDGER_COLLECTIBLE_INVOICE_CORRECTION_APPLIED
LEDGER_RENTAL_INCOME_CORRECTION_APPLIED
LEDGER_RENTAL_EXPENSE_CORRECTION_APPLIED
COLLAB_RECIPIENT_REGISTERED
COLLAB_RECIPIENT_REMOVED
COLLAB_PACKAGE_ENCRYPTED_FOR_RECIPIENT
COLLAB_PACKAGE_DECRYPTED
COLLAB_PACKAGE_COUNTER_SIGNED
class BucketEventObjectType(*values)[source]

Bases: StrEnum

Closed catalogue of object types a bucket event can reference.

WORK_UNIT
CALCULATION_REVISION
VERIFICATION_REPORT
FILING_RECORD
COMMUNICATION_RECORD
PROFILE
BUCKET
LEDGER_TRANSACTION
LEDGER_IMPORT_BATCH
LEDGER_CATALOGUE
LEDGER_EXPORT
PURCHASE_INVOICE_EVIDENCE
PAYABLE_INVOICE
COLLECTIBLE_INVOICE
ATTACHMENT
WORKFLOW_STATE
RECIPIENT
derive_bucket_event_id(*, bucket_id, event_type, occurred_at, actor, object_type, object_id, payload)[source]

Return the deterministic SHA-256 id for a bucket event.

occurred_at is held to the canonical UTC contract before it reaches the digest. The instant is hashed as text, so each spelling of one moment – naive, +01:00, UTC – produced a different id, and the append path assigns by id: the same event submitted twice under two spellings became two immutable history rows rather than collapsing, and the content-addressing the catalogue documents held only for callers who happened to agree on a timezone.

Return type:

str

Parameters:
class BucketEvent(**data)[source]

Bases: BaseModel

One append-only bucket event.

Variables:
  • event_id – Lowercase 64-char SHA-256 derived from the full event body. Content-addressed: structurally identical emissions collapse to the same id, making append naturally idempotent.

  • bucket_id – Owning bucket identifier.

  • event_type – One of BucketEventType.

  • occurred_at – UTC timestamp when the event was emitted.

  • actor – Actor label (free text up to 64 chars).

  • object_type – Type of the affected domain object.

  • object_id – Stable identifier of the affected object (e.g. a 64-char SHA-256 work-unit / revision / filing-record id).

  • payload_version – Integer schema version of the payload mapping. Bumped when the payload contract changes.

  • payload – Free-form structured details. Keys and values are short strings; secrets / credentials must not appear.

Parameters:

data (Any)

event_id: BucketEventId
bucket_id: BucketId
event_type: BucketEventType
occurred_at: UtcInstant
actor: BucketActorLabel
object_type: BucketEventObjectType
object_id: BucketObjectId
payload_version: int
payload: Mapping[_PayloadKey, _PayloadValue]
verify_bucket_event_id(event)[source]

Refuse an event whose declared id is not the derivation of its body.

Validation runs this, and so does every path that admits an event instance without re-validating it: a copied or constructed instance never passed the model validator, so its id is checked where it enters a catalogue.

Return type:

None

Parameters:

event (BucketEvent)

bucket_event_order_key(event)[source]

Return the canonical chronological sort key for one bucket event.

Ordering by occurred_at alone is not a total order: emissions inside one operation share an instant by design (a rename emits the lifecycle event and the maintenance event at the same now()), so ties fell through to whatever order the catalogue mapping happened to hold. Two processes reading the same persisted events could therefore render the operator different timelines, and the same reader could change its mind after a reload – with no validation error anywhere, because nothing was wrong with the data.

event_id breaks the tie because it is content-addressed: it is derived from the event body, so the tie-break is a property of the events themselves rather than of how they were stored or merged. The reconciliation history already ordered on this pair; this is the same rule, declared once for every projection that reads bucket events.

Return type:

tuple[datetime, str]

Parameters:

event (BucketEvent)

class BucketEventHistoryCatalogue(**data)[source]

Bases: BaseModel

Immutable catalogue of every bucket event in storage.

Parameters:

data (Any)

events: Mapping[str, BucketEvent]
get(event_id)[source]

Return the BucketEvent for event_id, or None if absent.

Return type:

BucketEvent | None

Parameters:

event_id (str)

for_bucket(bucket_id, *, event_types=None)[source]

Return every BucketEvent recorded against bucket_id in chronological order.

Events are ordered by bucket_event_order_key() and optionally filtered to one or more event types.

Return type:

tuple[BucketEvent, ...]

Parameters:
for_object(*, object_type, object_id)[source]

Return every event recorded against one object, in canonical chronological order.

Return type:

tuple[BucketEvent, ...]

Returns:

Tuple of BucketEvent records ordered by bucket_event_order_key().

Parameters:
values()[source]

Return a live view over every BucketEvent in the catalogue.

Return type:

ValuesView[BucketEvent]