reckon_db_dcb (reckon_db v3.1.1)

View Source

DCB conditional-append primitive.

Implements append_if_no_tag_matches/4 as a khepri:transaction/2 body. The transaction:

1. Verifies the tag-filter context ({context_changed, _} on conflict). 2. For integrity-enabled stores: verifies the seq counter and chain-tip still match what the caller observed when pre-stamping MACs. On mismatch aborts with {dcb_state_changed, _} and the outer loop retries with a fresh snapshot. 3. Writes events under ?DCB_STREAM_PATH ++ [SeqKey] plus one tag-index entry per tag at ?BY_TAG_PATH ++ [Tag, SeqKey]. 4. Updates the seq counter (and chain-tip if integrity is enabled).

The whole sequence happens inside one Ra log entry; atomic across the cluster. Either everything commits, or an abort comes back and nothing changed.

Integrity (reckon-db 3.2.0+)

On integrity-enabled stores, DCB events carry prev_event_hash + mac like any other integrity-bearing event. Because Khepri's Horus extractor rejects code containing crypto:* calls (even on unreachable branches; extraction sees the whole function), MAC chains are pre-computed OUTSIDE the transaction. Inside the transaction we verify the chain-tip + counter are still what we saw, then write the pre-stamped records.

Two retry vectors: - {context_changed, _}: tag-filter conflict. Callers (e.g., evoq_decision_runtime) retry with fresh context. - {dcb_state_changed, _}: counter or chain-tip moved between our pre-stamp read and the transaction. The retry loop in this module handles it transparently, bounded by ?INTEGRITY_RETRY_BUDGET.

Summary

Functions

append_if_no_tag_matches(StoreId, TagFilter, SeqCutoff, Events)

-spec append_if_no_tag_matches(StoreId :: atom() | binary(),
                               TagFilter :: reckon_gater_types:tag_filter(),
                               SeqCutoff :: reckon_gater_types:seq_cutoff(),
                               Events :: [map()]) ->
                                  {ok, LastSeq :: non_neg_integer()} |
                                  {error, {context_changed, non_neg_integer()}} |
                                  {error, no_events} |
                                  {error, dcb_concurrent_writer_exhausted} |
                                  {error, term()}.