# `Ankusa.Envelope`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/envelope.ex#L1)

The claim-checked record that flows through every layer.

The envelope is the *only* thing components share. Compaction, dispatch, and
the dashboard read committed envelopes from the store; they never call the edge.

`body` is the exact raw bytes as received — signature checks need it verbatim.
Keep it as a refcounted binary and pass it by reference.

# `t`

```elixir
@type t() :: %Ankusa.Envelope{
  body: binary(),
  content_type: String.t() | nil,
  dedupe_key: String.t() | nil,
  headers: [{String.t(), String.t()}],
  id: String.t(),
  method: String.t(),
  path: String.t(),
  received_at: integer(),
  seq: non_neg_integer() | nil,
  size: non_neg_integer(),
  source_id: String.t(),
  tenant_id: String.t() | nil,
  trace: Ankusa.Trace.t() | nil,
  verification: Ankusa.Verification.t() | nil
}
```

# `from_binary`

```elixir
@spec from_binary(binary()) ::
  {:ok, t()} | {:error, :unsupported_envelope_version | :undecodable}
```

Inverse of `to_binary/1,2`: reads the unprefixed legacy form (v0) and v1.

Keys the struct does not know (a field added by a newer node) are ignored,
so a row written by N+1 decodes on N. A version this node does not know is
`{:error, :unsupported_envelope_version}`; bytes that are no envelope at all
are `{:error, :undecodable}`. Never raises: callers dead-letter or log.

# `header`

```elixir
@spec header(t(), String.t()) :: String.t() | nil
```

Fetch the first value of a request header (case-insensitive).

# `header_downcased`

```elixir
@spec header_downcased(t(), String.t()) :: String.t() | nil
```

`header/2` for a `name` that is already lowercase (`String.downcase/1`): a
compiled signature template, a dedupe or ordering spec, which lower the name
once when they are built, not on every request.

# `idempotency_key`

```elixir
@spec idempotency_key(t()) :: String.t()
```

The tenant-scoped key a consumer dedupes this hook on; see docs/delivery.md.

# `to_binary`

```elixir
@spec to_binary(t(), keyword()) :: binary()
```

Serialize an envelope to a compact binary for the store.

`body` is kept verbatim; the term is `:erlang.term_to_binary/2` with
`:deterministic` so re-encoding a replayed record is byte-stable.

## Versions

* `0` (default): the bare term, as every release up to 0.6 wrote it.
* `1` (`version: 1`): `<<"AE", 1, term::binary>>`. Nothing writes it by
  default yet: this release reads both, the next one flips the default, so a
  rollback from this release to the previous one still reads every row it
  wrote (see `docs/deployment.md`, "Rolling back a minor").

---

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