# `AgentDb.Application.DataTransfer.Manifest`
[🔗](https://github.com/manhvu/agent_db/blob/v0.1.0/lib/agent_db/application/data_transfer/manifest.ex#L1)

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.

# `limits`

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

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

# `check_bytes`

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

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

# `check_counts`

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

Refuses a payload holding more entries than the transfer accepts.

# `check_members`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@spec format_version() :: pos_integer()
```

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

# `members`

```elixir
@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`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
