Packages
reckon_db
2.3.5
5.11.0
5.10.4
5.10.3
5.10.1
5.10.0
5.9.1
5.9.0
5.8.3
5.8.2
5.8.1
5.8.0
5.7.0
5.6.1
5.6.0
5.5.5
5.5.4
5.5.3
5.5.2
5.5.1
5.5.0
5.4.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.0.0
3.1.2
3.1.1
3.0.0
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.2
2.2.0
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.0
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.3
1.3.2
1.3.1
1.3.0
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
BEAM-native Event Store built on Khepri/Ra with Raft consensus. Event sourcing, persistent subscriptions, snapshots, and automatic cluster formation via UDP multicast discovery. Ships embedded Rust NIFs for 3-15x acceleration of crypto, hashing, compression, aggregation, filter matching, and grap...
Current section
Files
Jump to
Current section
Files
src/reckon_db_chain_watermark.erl
%% @doc Per-stream chain-start watermark for the tamper-resistance
%% migration story.
%%
%% Each stream that has ever held integrity-bearing events records the
%% version at which integrity began. Events with version below the
%% watermark are pre-integrity legacy; events at or above must carry
%% prev_event_hash + mac.
%%
%% == Storage ==
%%
%% Watermark lives in Khepri under
%% [metadata, integrity, chain_start, StreamId] (the macro
%% ?INTEGRITY_CHAIN_START_PATH from reckon_db.hrl, plus the stream ID).
%% Value is a single non_neg_integer().
%%
%% == Lazy enablement ==
%%
%% The watermark is created on the first integrity-bearing append to a
%% stream. There is no explicit "enable integrity on this stream"
%% operation - the act of appending under a store with integrity
%% enabled implicitly sets the watermark if it does not already exist.
%%
%% For a fresh stream (never had any events) this means the watermark
%% is set to 0 when the first event is written.
%%
%% For an existing legacy stream (events 0..N-1 already stored) being
%% appended to for the first time AFTER integrity was enabled on the
%% store, the watermark is set to N - the version that the new event
%% is about to take. The first N legacy events stay legacy.
%%
%% == Read-side use ==
%%
%% The read path consults the watermark to decide whether to verify
%% integrity on an event. This module's responsibility ends at
%% read/write of the watermark itself; verification logic lives in
%% reckon_gater_integrity.
%%
%% @end
-module(reckon_db_chain_watermark).
-include("reckon_db.hrl").
-export([
lookup/2,
set_if_absent/3,
delete/2
]).
%%====================================================================
%% Public API
%%====================================================================
%% @doc Read the chain-start version for a stream.
%%
%% Returns the version at which integrity began for the stream, or
%% 'undefined' if no watermark has been recorded (i.e., the stream
%% has never had an integrity-bearing append).
-spec lookup(StoreId :: atom(), StreamId :: binary()) ->
{ok, non_neg_integer() | undefined}.
lookup(StoreId, StreamId) ->
Path = ?INTEGRITY_CHAIN_START_PATH ++ [StreamId],
case khepri:get(StoreId, Path) of
{ok, Version} when is_integer(Version), Version >= 0 ->
{ok, Version};
_ ->
{ok, undefined}
end.
%% @doc Set the chain-start watermark for a stream if not already set.
%%
%% Returns the version that ended up recorded - either the value
%% passed in (this call set it), or the value that was already
%% present (a concurrent call set it first).
%%
%% This function intentionally tolerates write-write races: multiple
%% writers attempting to set the same watermark all converge on the
%% same outcome, because the writer's "next version about to write"
%% is itself serialized by reckon-db's per-stream append ordering.
%% If two concurrent writers somehow both reach this code path with
%% the same NextVersion, the watermark ends up equal to NextVersion
%% regardless of who won.
-spec set_if_absent(
StoreId :: atom(),
StreamId :: binary(),
Version :: non_neg_integer()
) -> {ok, RecordedVersion :: non_neg_integer()}.
set_if_absent(StoreId, StreamId, Version)
when is_integer(Version), Version >= 0 ->
case lookup(StoreId, StreamId) of
{ok, Existing} when is_integer(Existing) ->
{ok, Existing};
{ok, undefined} ->
Path = ?INTEGRITY_CHAIN_START_PATH ++ [StreamId],
ok = khepri:put(StoreId, Path, Version),
%% Re-read to handle the rare race where two writers both
%% saw 'undefined' and both wrote. Whoever's write
%% Khepri/Ra serialised last wins; we return that value.
{ok, Recorded} = lookup(StoreId, StreamId),
case Recorded of
undefined -> {ok, Version}; %% defensive; should not happen
_ -> {ok, Recorded}
end
end.
%% @doc Delete a stream's watermark.
%%
%% Used only when deleting the entire stream - the watermark is part
%% of the stream's metadata and should not outlive it. Best-effort:
%% any error is swallowed since this should never block stream
%% deletion itself.
-spec delete(StoreId :: atom(), StreamId :: binary()) -> ok.
delete(StoreId, StreamId) ->
Path = ?INTEGRITY_CHAIN_START_PATH ++ [StreamId],
_ = khepri:delete(StoreId, Path),
ok.