AgentDb.Application.DataTransfer.Manifest (agent_db v0.1.0)

Copy Markdown View Source

The on-the-wire shape of a transfer archive: its members, its manifest, and the validation every entry must pass.

A transfer archive is four JSON members -- manifest.json plus the documents, memories, and sessions it carries -- and this module is everything about those bytes that is not the container itself. Encoding a payload into members and turning read members back into a validated payload are the same question asked in two directions, and both belong here rather than in the workflow that walks the store.

Two rules run through all of it:

  • Nothing is written until everything is accepted. Every function returns either a value or a reason for refusing the whole archive, so a caller that writes only on success leaves the store untouched.
  • The bounds are passed in, not declared here. The transfer module's limits/0 is where they are defined and documented; this module is handed them, so the format cannot drift from the transfer that uses it.

AgentDb.Archive owns the container this content is packed into, in both directions. The transfer module owns the walk over the store and the writes that restore it.

Summary

Types

The bounds one transfer accepts: entries, and expanded bytes.

Functions

Refuses encoded members larger than the transfer's byte bound.

Refuses a payload holding more entries than the transfer accepts.

Refuses an archive holding more members than the bound allows, a name twice, a name that is not one of the four, or a name that escapes the archive root.

Refuses a binary larger than the transfer's byte bound.

Decodes the members into terms.

The manifest: format version, the scope the export covered, when it ran, what it carried, and a checksum per member.

Encodes the three collections as the JSON members an archive carries.

The format version this module writes, and the only one it reads.

The archive's members, in the order they are written.

Validates a decoded archive whole: the manifest against what it claims, then every document, memory, and session, then the bounds again on what they hold.

Types

limits()

@type limits() :: %{max_entries: pos_integer(), max_bytes: pos_integer()}

The bounds one transfer accepts: entries, and expanded bytes.

Functions

check_bytes(docs_json, mems_json, sess_json, limits)

@spec check_bytes(binary(), binary(), binary(), limits()) :: :ok | {:error, term()}

Refuses encoded members larger than the transfer's byte bound.

check_counts(documents, memories, sessions, limits)

@spec check_counts([map()], [map()], [map()], limits()) :: :ok | {:error, term()}

Refuses a payload holding more entries than the transfer accepts.

check_members(members, limits)

@spec check_members([{String.t(), binary()}], limits()) :: :ok | {:error, term()}

Refuses an archive holding more members than the bound allows, a name twice, a name that is not one of the four, or a name that escapes the archive root.

check_size(bytes, limits)

@spec check_size(non_neg_integer(), limits()) :: :ok | {:error, term()}

Refuses a binary larger than the transfer's byte bound.

Checked against the archive as it arrived, before it is expanded, and again against what its members hold once they are decoded.

decode(members)

@spec decode([{String.t(), binary()}]) :: {:ok, map()} | {:error, term()}

Decodes the members into terms.

The raw JSON of each member is kept alongside, because the manifest's checksums are over the bytes as they arrived rather than over the decoded terms.

encode(scope, documents, memories, sessions, docs_json, mems_json, sess_json)

@spec encode(
  String.t() | nil,
  [map()],
  [map()],
  [map()],
  binary(),
  binary(),
  binary()
) ::
  {:ok, binary()} | {:error, term()}

The manifest: format version, the scope the export covered, when it ran, what it carried, and a checksum per member.

encode_payload(documents, memories, sessions)

@spec encode_payload([map()], [map()], [map()]) ::
  {:ok, {binary(), binary(), binary()}} | {:error, term()}

Encodes the three collections as the JSON members an archive carries.

format_version()

@spec format_version() :: pos_integer()

The format version this module writes, and the only one it reads.

members(manifest_json, documents_json, memories_json, sessions_json)

@spec members(binary(), binary(), binary(), binary()) :: [{String.t(), binary()}]

The archive's members, in the order they are written.

Both directions of a transfer need this list, so only one place decides what a transfer archive contains.

validate_payload(map, limits)

@spec validate_payload(map(), limits()) :: {:ok, map()} | {:error, term()}

Validates a decoded archive whole: the manifest against what it claims, then every document, memory, and session, then the bounds again on what they hold.

Nothing is written by any of it, so a caller that writes only on success leaves the store exactly as it was.