Skip to content

Store

wealthbraid.store.records

Record envelopes, canonical JSON, and content-derived identifiers.

Every fact in a book is a :class:Record: an immutable envelope carrying a kind, the actor who caused it, the operation it belongs to (if any), and a kind-specific data payload. Records form a single hash chain: each record's prev is the identifier of the record before it, and its id is derived from the SHA-256 digest of its canonical JSON form (everything except id). Rewriting or reordering any earlier record therefore changes every later identifier, which :func:wealthbraid.book.verify.verify_book detects.

Record dataclass

An immutable, content-addressed record in a book's log.

Attributes:

Name Type Description
id str

The content-derived identifier.

seq int

The 1-based position of the record in the log.

prev str | None

The identifier of the previous record, or None for the first.

kind RecordKind

The record kind.

recorded_at str

UTC timestamp (ISO-8601, seconds precision) of the write.

actor str

Who caused the record, e.g. "human:alice" or "agent:claude".

operation str | None

The operation this record resulted from, if any.

data Mapping[str, Any]

The kind-specific payload.

v int

The record schema version.

body()

Return the hashed envelope: every field except id.

Returns:

Type Description
dict[str, Any]

A JSON-compatible dictionary.

create(*, seq, prev, kind, recorded_at, actor, operation, data) classmethod

Build a record and derive its identifier.

Parameters:

Name Type Description Default
seq int

The log position.

required
prev str | None

The previous record's identifier.

required
kind RecordKind

The record kind.

required
recorded_at str

The UTC write timestamp.

required
actor str

The actor string.

required
operation str | None

The originating operation, if any.

required
data Mapping[str, Any]

The kind-specific payload.

required

Returns:

Type Description
Record

The new :class:Record.

expected_id()

Recompute the identifier this record's content implies.

Returns:

Type Description
str

The identifier derived from :meth:body.

from_json(raw) classmethod

Parse a record from its on-disk representation.

Parameters:

Name Type Description Default
raw Mapping[str, Any]

The decoded JSON object.

required

Returns:

Type Description
Record

The parsed :class:Record (its identifier is not verified here).

Raises:

Type Description
ValueError

If required fields are missing or the kind is unknown.

to_json()

Return the full on-disk representation, id first.

Returns:

Type Description
dict[str, Any]

A JSON-compatible dictionary.

to_line()

Serialise the record as one canonical JSON line (without newline).

Returns:

Type Description
str

The canonical JSON text.

RecordKind

Bases: Enum

The kinds of record a book can contain.

The enum value is the stable string stored on disk.

prefix property

Return the identifier prefix for this kind.

Returns:

Type Description
str

A three-letter prefix such as "ent".

canonical_json(value)

Serialise a JSON-compatible value canonically.

Keys are sorted, separators carry no whitespace, and non-ASCII text is kept verbatim, so equal values always produce byte-identical output.

Parameters:

Name Type Description Default
value Any

A JSON-compatible value (no floats should appear in records).

required

Returns:

Type Description
str

The canonical JSON text.

compute_id(kind, body)

Derive a record identifier from its kind and envelope body.

Parameters:

Name Type Description Default
kind RecordKind

The record kind, which supplies the prefix.

required
body Mapping[str, Any]

The envelope without its id field.

required

Returns:

Type Description
str

An identifier such as "ent_3f2a…".

wealthbraid.store.store

The on-disk, append-only record log and content-addressed evidence store.

Layout of a book directory::

wealthbraid.toml                 settings (see wealthbraid.book.config)
records/2026/09.jsonl            append-only log segments, one record per line
records/HEAD                     anchor: sequence number and id of the newest record
evidence/sha256/ab/abcdef…       immutable evidence blobs named by digest
.wealthbraid/                    local, regenerable state (lock file); git-ignored

Records are only ever appended. A write takes an exclusive lock, computes the new records' identifiers against the current head of the chain, and appends all of them in a single write followed by fsync, then updates the head anchor. The anchor lets verification notice records deleted from the end of the log, which the hash chain alone cannot. Nothing in this module can modify or delete an existing record or evidence blob.

PendingAppend

Records staged for a single atomic append to the log.

__init__(store, head, recorded_at)

Bind a pending append to the head it extends.

Parameters:

Name Type Description Default
store RecordStore

The owning store.

required
head Record | None

The current last record, or None.

required
recorded_at str

The timestamp shared by every record in this append.

required

add(kind, data, *, actor, operation=None)

Stage a record; its identifier is available immediately.

Parameters:

Name Type Description Default
kind RecordKind

The record kind.

required
data Mapping[str, Any]

The kind-specific payload.

required
actor str

The actor string.

required
operation str | None

The originating operation, if any.

None

Returns:

Type Description
Record

The staged :class:Record.

commit()

Append every staged record in one write and fsync.

Returns:

Type Description
list[Record]

The records written.

RecordStore

Append-only access to a book's record log and evidence blobs.

anchor_path property

Return the path of the head anchor file.

Returns:

Type Description
Path

records/HEAD.

evidence_dir property

Return the directory holding evidence blobs.

Returns:

Type Description
Path

The evidence directory path.

records_dir property

Return the directory holding log segments.

Returns:

Type Description
Path

The records directory path.

__init__(root, *, clock=utc_now)

Open the store rooted at a book directory.

Parameters:

Name Type Description Default
root Path

The book directory.

required
clock Callable[[], datetime]

Source of write timestamps; injectable for deterministic tests.

utc_now

begin(*, recorded_at=None)

Start collecting records to append against the current head.

The caller must hold :meth:lock until :meth:PendingAppend.commit.

Parameters:

Name Type Description Default
recorded_at str | None

The timestamp for the staged records; defaults to now.

None

Returns:

Name Type Description
A PendingAppend

class:PendingAppend bound to the current head.

evidence_path(sha256)

Return where the blob with a given digest is stored.

Parameters:

Name Type Description Default
sha256 str

The lowercase hex SHA-256 digest.

required

Returns:

Type Description
Path

The blob path (which may not exist).

head()

Return the last record in the log, if any.

Returns:

Type Description
Record | None

The most recent record, or None for an empty log.

iter_records()

Yield every record in log order.

Yields:

Type Description
Record

Parsed records, without identifier or chain verification.

Raises:

Type Description
IntegrityError

If a line is not valid JSON or not a valid record.

lock()

Hold the book's exclusive write lock.

Yields:

Type Description
None

Nothing; the lock is held for the duration of the block.

A lock left behind by a process that no longer exists is recovered automatically. On release, the lock file is removed only if it still names this process.

Raises:

Type Description
ConflictError

If another live process holds the lock.

put_evidence(content)

Store an evidence blob by digest; storing the same bytes twice is a no-op.

Parameters:

Name Type Description Default
content bytes

The raw file content.

required

Returns:

Type Description
str

The SHA-256 digest of content.

Raises:

Type Description
IntegrityError

If a different blob already occupies the digest's path.

read_anchor()

Read the head anchor.

Returns:

Type Description
dict[str, Any] | None

{"seq": int, "id": str}, or None if no anchor has been written.

Raises:

Type Description
IntegrityError

If the anchor exists but is unreadable.

read_evidence(sha256)

Read an evidence blob.

Parameters:

Name Type Description Default
sha256 str

The blob digest.

required

Returns:

Type Description
bytes

The blob content.

Raises:

Type Description
NotFoundError

If no blob with that digest is stored.

read_records()

Return every record in log order.

Returns:

Type Description
list[Record]

The full list of records.

segments()

Return the log segment files in chain order.

Returns:

Type Description
list[Path]

Segment paths sorted by YYYY/MM name.

stray_files()

Return files under records/ that are neither segments nor the anchor.

Returns:

Type Description
list[Path]

Sorted paths relative to the book root.

format_timestamp(moment)

Format a datetime as the canonical UTC timestamp string.

Parameters:

Name Type Description Default
moment datetime

A timezone-aware datetime.

required

Returns:

Type Description
str

A string such as "2026-09-16T12:00:00Z".

utc_now()

Return the current UTC time truncated to whole seconds.

Returns:

Type Description
datetime

A timezone-aware UTC datetime.