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_hexover 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.pyrefuses 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:
StrEnumClosed 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:
StrEnumClosed 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_atis 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:
- Parameters:
bucket_id (str)
event_type (BucketEventType)
occurred_at (datetime)
actor (str)
object_type (BucketEventObjectType)
object_id (str)
- class BucketEvent(**data)[source]¶
Bases:
BaseModelOne 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:
- Parameters:
event (BucketEvent)
- bucket_event_order_key(event)[source]¶
Return the canonical chronological sort key for one bucket event.
Ordering by
occurred_atalone 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 samenow()), 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_idbreaks 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:
- Parameters:
event (BucketEvent)
- class BucketEventHistoryCatalogue(**data)[source]¶
Bases:
BaseModelImmutable catalogue of every bucket event in storage.
- Parameters:
data (Any)
- events: Mapping[str, BucketEvent]¶
- get(event_id)[source]¶
Return the
BucketEventforevent_id, orNoneif absent.- Return type:
- Parameters:
event_id (str)
- for_bucket(bucket_id, *, event_types=None)[source]¶
Return every
BucketEventrecorded againstbucket_idin chronological order.Events are ordered by
bucket_event_order_key()and optionally filtered to one or more event types.- Return type:
- Parameters:
bucket_id (str)
event_types (tuple[BucketEventType, ...] | None)
- for_object(*, object_type, object_id)[source]¶
Return every event recorded against one object, in canonical chronological order.
- Return type:
- Returns:
Tuple of
BucketEventrecords ordered bybucket_event_order_key().- Parameters:
object_type (BucketEventObjectType)
object_id (str)
- values()[source]¶
Return a live view over every
BucketEventin the catalogue.- Return type: