Docket.Backend.GraphStore behaviour (docket v0.1.0-dev)

Copy Markdown View Source

Persistence contract for tenant-owned effective graph versions.

Graph documents are immutable and addressed within one explicit owner scope by {graph_id, graph_hash}. Publication materializes node configuration defaults before hashing, so the stored document carries its effective configuration. Compiled runtime graphs remain node-local and are not part of this storage contract.

An owner scope is either :tenantless or {:tenant, tenant_id}. :system is deliberately not accepted: even trusted runtime work must carry the owning run's scope when it loads a graph. A Docket.GraphRef is therefore a scope-relative content address rather than an authorization credential.

Graph publication is explicit and precedes run creation. start_run accepts a saved graph reference and reads through this capability; lifecycle transactions never publish graph documents.

Summary

Types

Stable newest-first graph-version cursor: {published_at, graph_hash}.

Trusted, normalized options for one graph-version collection read.

Callbacks

Reads an effective graph by {owner_scope, graph_id, graph_hash}.

Reads the latest saved version reference for a graph ID in one owner scope.

Lists retained version metadata for one graph ID in newest-first order.

Saves an effective graph under {owner_scope, graph_id, graph_hash}.

Types

ctx()

@type ctx() :: Docket.Backend.ctx()

list_cursor()

@type list_cursor() :: Docket.GraphVersionPage.cursor()

Stable newest-first graph-version cursor: {published_at, graph_hash}.

list_query()

@type list_query() :: %{limit: pos_integer(), before: list_cursor() | nil}

Trusted, normalized options for one graph-version collection read.

owner_scope()

@type owner_scope() :: Docket.Backend.owner_scope()

Callbacks

fetch_graph(ctx, owner_scope, graph_id, graph_hash)

@callback fetch_graph(
  ctx(),
  owner_scope(),
  graph_id :: String.t(),
  graph_hash :: String.t()
) :: {:ok, Docket.Graph.t()} | {:error, :not_found | :corrupt_graph}

Reads an effective graph by {owner_scope, graph_id, graph_hash}.

A wrong owner scope and an absent key both return :not_found. Backend or infrastructure failure is not collapsed into absence; implementations raise when the substrate cannot complete the read.

fetch_latest_graph_ref(ctx, owner_scope, graph_id)

@callback fetch_latest_graph_ref(
  ctx(),
  owner_scope(),
  graph_id :: String.t()
) :: {:ok, Docket.GraphRef.t()} | {:error, :not_found | :corrupt_graph}

Reads the latest saved version reference for a graph ID in one owner scope.

Latest is defined by the same immutable order as list_graph_versions/4: descending publication time, then descending graph hash. This is a metadata read and does not load or decode the effective graph document.

:not_found is reserved for an owner scope with no saved version under that graph ID. A wrong owner scope is indistinguishable from absence.

list_graph_versions(ctx, owner_scope, graph_id, list_query)

@callback list_graph_versions(
  ctx(),
  owner_scope(),
  graph_id :: String.t(),
  list_query()
) :: {:ok, Docket.GraphVersionPage.t()} | {:error, :corrupt_graph}

Lists retained version metadata for one graph ID in newest-first order.

query.before is an exclusive {published_at, graph_hash} cursor and query.limit bounds the returned versions. Implementations read at most one lookahead row and construct a Docket.GraphVersionPage, which centralizes trimming and next-cursor derivation without revalidating the backend's order. The metadata read never loads or decodes effective graph documents. An unknown graph ID returns an empty page.

save_graph(ctx, owner_scope, graph_id, graph_hash, graph)

@callback save_graph(
  ctx(),
  owner_scope(),
  graph_id :: String.t(),
  graph_hash :: String.t(),
  graph :: Docket.Graph.t()
) :: :ok | {:error, :graph_content_conflict | term()}

Saves an effective graph under {owner_scope, graph_id, graph_hash}.

Saving the same document again is idempotent. Reusing the key for different content is an error; a backend must not silently accept a hash/document mismatch. An idempotent save preserves the original publication timestamp and must not move that version to the front of version ordering. Durable backends assign that timestamp from their storage substrate rather than an application-node clock. The facade compiles and validates a graph before calling this function. Storage receives the durable Docket.Graph directly; public map serialization is not part of this contract.