# `DpExchange.Core.AdapterContract`
[🔗](https://github.com/DistortionPoint/dp-exchange-core/blob/v0.3.120/lib/dp_exchange/core/adapter_contract.ex#L1)

The conformance suite every venue package runs in its own CI.

**This is the mechanism.** Prose in six `CLAUDE.md` files drifts; a suite that runs in
five CI pipelines cannot.

## Why this lives in `lib/` and not `test/support/`

It was in `test/support/` first, following the reference repo, and **that does not
work for a suite consumers run.** `elixirc_paths(:test)` governs *this* package's own
build; a dependency is not compiled in the `:test` environment, so the file shipped
inside the tarball and was never compiled into the consumer. A venue package's
`use DpExchange.Core.AdapterContract` failed with *module not loaded and could not be
found* — the suite present on disk and absent from the code path.

Shipping a file is not the same as shipping a module. A public testing API belongs in
`lib/`, which is why `Ecto.Adapters.SQL.Sandbox` and `Phoenix.ConnTest` live there.

`use ExUnit.Case` appears only inside the generated block, so nothing here needs
ExUnit at compile time and this compiles cleanly as a dependency.

    defmodule DpExchange.Coinbase.ContractTest do
      use DpExchange.Core.AdapterContract,
        venue: DpExchange.Coinbase,
        symbol_format: DpExchange.Coinbase.SymbolFormat,
        sample_pairs: ~w(BTC-USDC ETH-USDC),
        credentials: %{api_key: "test", api_secret: "test"}
    end

## Options of `use`

  * `:venue` - **required**, the facade module.
  * `:fake` and `:sample_pairs` - not optional in practice: group 1 fails without them.
  * `:symbol_format` - the `SymbolNormalizer` module; group 4 is generated only when named.
  * `:credentials` - the credentials map the calls carry (default `%{}`).
  * `:endpoint_opts` - `%{{name, arity} => keyword()}`, what the fake needs to answer.
  * `:endpoint_symbols` - `%{{name, arity} => symbol}` for an endpoint narrower than
    `:sample_pairs`.
  * `:order_args` - `%{order_request | order_requests | order_changes => value}`; an unknown
    key raises at compile time.
  * `:absent_feed_opts` - options pointing the streaming calls at a feed nothing registered
    (default `[feed: :dp_exchange_contract_absent_feed]`).
  * `:package_root` - where groups 16, 18 and 19 scan compiled modules (default `"lib"`).
  * `:unlisted_symbol` and `:padded_credentials` - the opt-in facts of group 41.
  * `:unknown_order_id` - group 49's order id nobody placed, in the venue's own id format
    (default: a text id; a venue with numeric ids names a number).
  * `:ambiguous_place_order` - opt-in for group 42: a 1-arity function (`fn request -> ...
    end` or `&Mod.fun/1`) that takes an order request map and runs the venue's **real**
    `place_order/3` path, with a transport fault injected after the request is sent (a Req
    plug that returns a transport error, or a 503 on every attempt). Group 42 asserts that
    answer is `{:error, {:order_unconfirmed, id, reason}}`, and that the same function
    given a request refused before sending (no `:order_type`) is a plain or refused error,
    never `:order_unconfirmed`. Absent, group 42 is skipped, like group 41's facts.
  * `:process_probe` - opt-in for group 51: a `{module, function}` naming a 1-arity function
    that starts the venue's own long-lived process or processes (its feed, its socket) **with
    no network** (the fake transport, a closed port, a stub) from a credentials map and
    answers `{:ok, pid}` or `{:ok, [pid]}`. Group 51 renders `:sys.get_status/2` of each,
    which is what OTP prints in a crash report, and fails if the secret it passed in is in
    it. Absent, group 51 is skipped, like group 41's facts, and a venue holding a
    credential in a process it never probed is a venue this suite has not examined.

## What it asserts, and what it deliberately does not

Fifty-four groups, listed in `assertions/0`. The load-bearing one is **capabilities and
behaviour agreeing in both directions**: over-declaring fails in a caller's hands at
runtime, and under-declaring hides working functionality. Holding both is what makes
`capabilities/0` trustworthy enough for a consumer to branch on instead of branching on
venue identity.

**Maturity is asserted for presence, not for truth.** Every active endpoint must declare
`:proven` or `:experimental`, because an absent value is the failure this prevents.
Whether a `:proven` claim is *true* is not machine-checkable, and a suite that pretended
otherwise would be worse than one that admits the limit.

**No assertion may name a socket, a channel string, a transport module or a polling
interval.** If one does, mechanism has leaked through the facade and the assertion is the
bug, not the venue.

## The fake satisfies the same suite as the real adapter

That is the ratchet. The suite's job is not to anticipate every way a fake can diverge —
it is to make each discovered divergence permanent. The reference implementation this
pattern comes from went from nothing to over a thousand lines by absorbing thirteen gaps
its first consumer found, and none reached an external user across thousands of downloads.

**Every gap found becomes a new assertion here.** A gap fixed only in one venue's fake is
a gap the next venue will reintroduce.

# `active_endpoint_fault`

```elixir
@spec active_endpoint_fault(term()) :: nil | :not_supported | :raised
```

What is wrong with a fake's answer to an endpoint its `capabilities/0` declares active,
or `nil` when nothing is.

  * `:not_supported` — the fake answered `{:error, :not_supported}`: the declaration and the
    behaviour disagree (assertion 12, over-declaring).
  * `:raised` — the call raised. `{:raised, error}` is how the suite's `call_on/3` reports
    it. A raise is not an answer, and treating it as one let a fake that crashed on every
    endpoint pass "does not answer :not_supported".

Any other answer, refusals and errors included, is not this check's business.

# `ambiguous_write_fault`

```elixir
@spec ambiguous_write_fault(term(), term(), String.t() | nil) ::
  nil | {atom(), term()}
```

What is wrong with a venue's answers to an ambiguous write, or `nil`. Assertion 42.

`ambiguous` is what `place_order/3` answered when a transport fault came **after** the
request was sent (a timeout, a dropped connection, a 503 on every attempt). It must be
`{:error, {:order_unconfirmed, id, reason}}`: the order may have been placed, so a plain
`{:error, _}` invites the caller to retry into a duplicate, and `{:refused, _}` claims
permanence the venue never gave. `id` is a client order id, a list of them, or `nil` when
the venue has none and none was generated. When the request carried a binary
`client_order_id` (`sent_id`) it is that id: `nil` there is `{:unconfirmed_id, nil}`.

`presend` is what the same path answered for a request refused **before** anything was
sent. It must be an `{:error, _}` or `{:refused, _}` that is not `:order_unconfirmed`:
nothing left the process, so claiming uncertainty would make a caller reconcile an order
that never existed.

`{:not_unconfirmed, answer}`, `{:unconfirmed_id, id}`, `{:presend_unconfirmed, answer}`
or `{:presend_not_refused, answer}`.

# `amount_position`

```elixir
@spec amount_position(atom(), non_neg_integer()) :: non_neg_integer() | nil
```

The position of the amount among `name/arity`'s arguments, from the suite's own argument
table (`arg_shape/3`), or `nil` when the call takes none.

# `amount_write_endpoints`

```elixir
@spec amount_write_endpoints() :: [{atom(), non_neg_integer()}]
```

The writes whose amount assertion 47 checks, when `capabilities/0` declares them active.

# `answer_type_fault`

```elixir
@spec answer_type_fault(term(), [module()]) :: nil | String.t()
```

What is wrong with the answer of an endpoint to assertion 5's question, or `nil`: does a
success hold the `Core.Types` struct (one of `modules`) its callback promises?

Returns a short description of the offence: a struct of another module, a raw map, a list
holding something else, a bare value (`{:ok, nil}`, `{:ok, 5}`; only `{:ok, :cancelled}` is
allowed, as `cancel_order/3` types it) or a raise. A refusal or an error is not a success and
is not judged.

# `arg_shape`

```elixir
@spec arg_shape(atom(), non_neg_integer(), boolean()) :: [atom()]
```

How a call to `name/arity` is shaped, position by position: `:credentials`, `:symbol`,
`:timeframe`, `:opts`, or one of the order arguments.

`credentialed?` is whether the callback takes its credential positionally — see
`credentialed_callbacks/0`. A per-callback override wins over the shape the arity implies.
Both the call assertions and assertion 17's credential-stripped call are built from this one
table; the stripped call used to rebuild its own and ignore the overrides, so for
`place_order/3` and `replace_order/4` it sent a malformed order, the fake raised, and
"no `{:ok, _}` with credentials stripped" held for a reason that had nothing to do with
credentials.

# `assertions`

```elixir
@spec assertions() :: [{pos_integer(), String.t()}]
```

The assertion groups, for documentation and for a venue package to report against.

# `asset_classes_fault`

```elixir
@spec asset_classes_fault(term()) :: nil | {:asset_classes, term()}
```

The identity fault of a venue's `asset_classes/0` answer, or `nil`. Assertion 3: a non-empty
list of the classes a host switches on.

# `balance_fault`

```elixir
@spec balance_fault(term()) ::
  nil | {:wrong_struct, term()} | {:currency | :timestamp | :provider, term()}
```

What is wrong with a `Balance` a fake returned, or `nil`. Assertion 24.

`{:currency, value}` unless it is non-blank text (an amount attributable to no asset cannot
be sized, booked or reconciled), `{:timestamp, value}` unless a `DateTime`, `{:provider,
value}` when empty. `:balance` is deliberately not judged: it may honestly be `nil`.

# `book_depth_fault`

```elixir
@spec book_depth_fault(term(), pos_integer()) ::
  nil
  | {:too_many_levels, :bids | :asks, non_neg_integer()}
  | {:answered, term()}
```

What is wrong with the answer to a `depth` request, or `nil`: `{:too_many_levels, side, n}`
when a side carries more than `depth` levels.

`{:error, {:invalid_option, :depth, _}}` is `nil`: the venue's own minimum, the family's
refusal for a value it cannot honour. **Any other non-success is `{:answered, answer}`.**
It was `nil` for every refusal, so a fake that refused `depth: 1` for an unrelated reason
(a missing option, a raise) passed an assertion documented as "the fake must honour
`:depth`" without a single book being examined. The ask is made with the same arguments as
the baseline book of group 25, which already succeeded.

# `book_limit_fault`

```elixir
@spec book_limit_fault(term(), term()) :: nil | :raised | {:answered, term()}
```

What is wrong with the answer to a request carrying `limit:`, given the answer `baseline`
without it, or `nil`. Only what the convention requires: Coinbase refuses `:limit`
(`{:unsupported_option, :limit}`) because its old option meant depth, while a venue whose
book has no `:limit` ignores it as Core's usage rules let it, and one with a native `:limit`
may refuse a value (`{:invalid_option, :limit, v}`). So a success and those two refusals are
fine; a raise (`:raised`) or any other answer (`{:answered, answer}`) is not. Nothing is
judged when the baseline itself was not a success.

# `book_ordering_fault`

```elixir
@spec book_ordering_fault(term(), :asc | :desc) ::
  nil
  | {:too_thin, non_neg_integer()}
  | {:unreadable_level, term()}
  | {:unsorted, [Decimal.t()]}
```

What is wrong with one side of an order book a fake returned, or `nil`. Assertion 25.

`direction` is `:desc` for bids and `:asc` for asks. `{:too_thin, n}`: fewer than two levels,
which no comparator can order wrongly, so the assertion would pass having checked nothing.
`{:unreadable_level, level}`: a level that is not `{Decimal, size}`. `{:unsorted, prices}`:
the prices are not best first.

# `book_probe_depth`

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

The `:depth` assertion 38 asks for: one level per side.

# `candle_ask_fault`

```elixir
@spec candle_ask_fault(term()) :: nil | {:no_candles, term()}
```

Whether the unbounded candle ask of assertion 37 gave the group anything to check, or
`{:no_candles, answer}`.

Only a non-empty `{:ok, candles}` is something to check. A refusal, a raise or an empty
list used to skip the whole group, which then reported green having examined no candle.

# `candle_range_probes`

```elixir
@spec candle_range_probes([struct()], String.t() | nil) :: [
  {keyword(), [DateTime.t()]}
]
```

The ranges assertion 37 re-asks with, as `{range, must_include}`, chosen from the fake's own
bars (oldest first). Two or more bars: `start` the first bar's `opened_at` and `end` the last
one's, which must keep the first and drop the last. One bar: `start` alone (keeps it) and
`end` alone (drops it). None: nothing to ask.

With `timeframe` given and a fixed width, the one-bar ask that must keep the bar is
`start:` the bar and `end:` one width later, not `start:` alone. A `start:` with no `end:` runs
to the venue's "now", and a venue that bounds a window by its width (Coinbase's 350 buckets)
refuses a fake's old fixed bar for being too far back, which is a refusal about the clock the
fake was written against and not about the range. A width Core has no rule for (`1w`) keeps
`start:` alone.

# `candle_series_fault`

```elixir
@spec candle_series_fault([struct()], String.t(), [atom()]) ::
  nil | {:wrong_timeframe | :unordered | :unaligned, term()}
```

What is wrong with the candles a fake returned for `timeframe`, or `nil`. Assertion 37.

  * `{:wrong_timeframe, label}` — a bar labelled with a width other than the one asked for.
    A bar stamped `"1d"` that was asked for as `"1h"` is a missing granularity becoming
    the closest one in the label, which is how every bar then gets joined to the wrong
    series; group 21 holds the refusal of an unserved width, and this holds the label of a
    served one. Judged first.
  * `{:unordered, opened_at}` — a bar that is not later than the one before it. Oldest
    first, strictly.
  * `{:unaligned, opened_at}` — a bar not on its width's grid. The grid is
    `Timeframe.aligned?/2` (strict, UTC), except that for a venue that is not crypto-only
    (`asset_classes`) a width wider than 30 minutes and shorter than a day is
    session-anchored (US equity sessions open at 09:30 ET) and only needs 30-minute
    alignment. Daily and longer bars are mapped to 00:00 UTC of the session date and stay
    strict. Widths the vocabulary does not model are not judged.

# `captured_notice_faults`

```elixir
@spec captured_notice_faults(
  module(),
  [String.t()],
  keyword(),
  String.t() | nil,
  timeout()
) ::
  {non_neg_integer(), list()}
```

Drives `fake` through `subscribe_notices/1`, `subscribe/2` of `pairs`, `subscribe/2` of
`unlisted` when given, and `unsubscribe/2`, then collects every `Core.Notice` delivered to
this process until none arrives for `wait` milliseconds. Assertion 45.

Returns `{judged, faults}`: how many notices were captured, and `notice_batch_faults/1` of
them. A call that raises is `{0, [{:raised, error}]}`. **`judged` of `0` means the fake
emitted nothing and the group verified nothing**; the fakes in this family register a notice
subscriber silently, so that is the usual case, and it must not be read as coverage.

# `carries_process?`

```elixir
@spec carries_process?(term()) :: boolean()
```

Whether a term holds a pid, port or reference anywhere inside it, structs and map keys
included. Assertion 11: a facade answer is data, never a process the caller must manage.

# `case_faults`

```elixir
@spec case_faults(module(), [{atom(), list(), term()}]) :: [
  {atom(), arity(), term(), :raised | {:answered, term()}}
]
```

Runs each `{name, args, expected}` of `cases` against `module` and returns the ones whose
answer is not `{:error, expected}`, as `{name, arity, expected, fault}` with `fault` as
`refusal_fault/2` names it. Empty means every case was refused as the convention says.

The arguments are not kept in the result: they carry the suite's credentials, and a failure
message prints what it is given.

# `catalog_access_fault`

```elixir
@spec catalog_access_fault(:enumerable | :query_only, term()) ::
  nil | :query_required | {:answered, term()}
```

What is wrong with `get_symbols/1`'s answer without a query, given the declared
`catalog_access`, or `nil`. Assertion 12.

`:query_only` means there is no list-everything call, so the answer must be exactly
`{:error, {:query_required, venue}}` (`usage-rules.md`). Any other answer is
`{:answered, answer}`: it was `{:error, _}`, so a fake refusing for a missing credential or
any unrelated reason satisfied a `:query_only` claim it never demonstrated. `:enumerable`
must not demand a query: `:query_required` otherwise.

# `child_spec_credential_fault`

```elixir
@spec child_spec_credential_fault(module()) :: nil | {:leaked, String.t()}
```

What is wrong with `venue.child_spec/1` where a secret is concerned, or `nil`. Assertion 22.

Hands `child_spec/1` a `:credentials` map holding one unique canary under every secret key
name this family's venues use, and looks for the canary in `inspect/2` of the answer, which
is what OTP prints on every child crash. `{:leaked, rendered}` when it is there. A
`child_spec/1` that drops the credentials altogether is not a fault, and one that raises is
not caught here: the group fails on the raise, as it should.

Public so that a teeth test can hand it a `child_spec/1` that stores the raw map, which the
inline version of this check could never be shown to catch.

# `coverage_by_kind_fault`

```elixir
@spec coverage_by_kind_fault(%{required(atom()) =&gt; map()}, map(), [atom()]) ::
  nil
  | {:union_mismatch, [String.t()], [String.t()]}
  | {:undeclared_kinds, [atom()]}
```

What is wrong with a venue's `coverage_by_kind/1` answer, given the `coverage/1` map of the
same moment and the declared `streamable` kinds, or `nil`. Assertion 15.

`{:union_mismatch, kind_symbols, coverage_symbols}` (both sorted) when the symbols across
every kind are not exactly the symbols `coverage/1` reports, the two being meant as one
fact; `{:undeclared_kinds, kinds}` when it reports a kind `streamable` does not declare. The
union is judged first.

# `credential_gate_fault`

```elixir
@spec credential_gate_fault(term()) :: nil | {:succeeded | :raised, term()}
```

What is wrong with a call's answer when its credentials were stripped, or `nil`. Assertion 17.

  * `{:succeeded, answer}` - `{:ok, _}`: the fake served a call it could not have signed.
  * `{:raised, answer}` - a raise, exit or throw (`{:raised, e}`, `{:exit, r}`, `{:throw, v}`
    from the suite's `call_on/3`). A raise is not a refusal: the family answers with tuples,
    and a fake that crashes on `%{}` credentials holds the gate for a reason that has
    nothing to do with credentials.

Any other answer is nil: the refusals (`{:error, _}`, `{:refused, _}`) and the callbacks that
return a bare value, which the group walks too and which cannot be `{:ok, _}`.

# `credential_gated_endpoints`

```elixir
@spec credential_gated_endpoints(DpExchange.Core.Capabilities.t(), [atom()]) :: [
  {atom(), arity()}
]
```

The endpoints assertion 17 asks a `credential_benefit: :required` venue's fake to refuse
when called with credentials stripped.

Every facade callback that is **answerable** (not `child_spec/1` or `start_link/1`, which
start something), **active** in `caps`, not one of the two whose own doc types the
credential `credentials() | nil`, not declared in `Capabilities.no_venue_contact`, and not
`market_status/1` on a venue whose `asset_classes` are exactly `[:crypto]` (crypto trades
continuously, so there is no session for a credential to gate).

This is public, and the assertion calls it, so that the selection can be shown to fail:
the previous tests of this decision rebuilt it by hand beside the fixtures and asserted
what the fixtures did, so no change to the decision could break them. The assertion also
refuses an **empty** result, because a loop over no endpoints reports green having
examined none.

# `credentialed_callbacks`

```elixir
@spec credentialed_callbacks() :: [atom()]
```

The names of the `Core.Venue` callbacks whose first argument is `credentials()`, read from
the behaviour's own `@callback` specs.

The suite shapes a call's arguments by whether the callback takes its credential
positionally. That used to be a hand-written list of eleven names, and it rotted in the
way a list does: `list_payment_methods/2`, `get_payment_method/3`, `get_notional_balances/3`,
`list_custody_fees/2`, `get_transactions/2`, `place_orders/3`, `cancel_all_orders/2`,
`preview_order/3`, `preview_replace/4`, `close_position/3` and `get_trade_volume/2` all take
`credentials()` first and none was on it. Each was therefore called with a symbol where the
credential belongs, a fake that matched on its credentials raised, and the raise was taken
for an answer by every assertion that tolerates one. A union such as
`credentials() | nil` (`test_connection/2`) counts.

# `eligible_cases`

```elixir
@spec eligible_cases(module(), DpExchange.Core.Capabilities.t(), [
  {atom(), list(), term()}
]) :: [
  {atom(), list(), term()}
]
```

The calls of `cases` that `module` can be asked, as `{name, args, expected}` triples:
exported at that arity and not declared `:unsupported` in `caps`.

A callback a venue legitimately declares `:unsupported` is skipped the way the suite's
other assertions skip it. It is not a pass: assertion 12 holds the declaration to the
behaviour.

# `family_reason_fault`

```elixir
@spec family_reason_fault(term(), term(), atom()) :: nil | {atom(), term()}
```

What is wrong with a fake's answers for the two opt-in family reasons, or `nil`. Assertion 41.

`unlisted` is a well-formed symbol the fake does not list and `price_answer` is what its
`get_price/2` said for it: anything but `{:refused, :not_listed}` is
`{:unlisted_symbol, answer}`. `padded_answer` is what a credentialed read said for a
credentials map with surrounding whitespace, from a venue whose `runtime_id/0` is `venue`:
anything but `{:error, {:invalid_credentials, venue}}` is `{:padded_credentials, answer}`.
A `nil` for either answer is a reason the venue did not opt in to, and is not judged.

# `foreign_modules`

```elixir
@spec foreign_modules([module()], MapSet.t(String.t()), Path.t()) :: [module()]
```

The modules in `modules` that belong to an application outside `permitted`.

`build_lib_dir` is `Path.join(Mix.Project.build_path(), "lib")`. A module found under it
belongs to the application named by its first path segment. A module found anywhere else is
OTP or the Elixir standard library and is permitted, and a module found nowhere is a
host-application module this package depends on without saying so, which is foreign.

**The previous check could not fire for a dependency.** It looked for `/deps/<name>/` in
the module's path, but Mix compiles a dependency into `_build/<env>/lib/<name>/ebin` and
loads it from there, never from `deps/`. Every dependency module therefore matched nothing
and was permitted, so a package linking an undeclared dependency passed. Only the
`:non_existing` branch ever rejected anything.

# `format_case_faults`

```elixir
@spec format_case_faults([{atom(), arity(), term(), term()}]) :: String.t()
```

A failure message for `case_faults/2`'s result, one line per call that was not refused.

# `half_implemented_optionals`

```elixir
@spec half_implemented_optionals(module()) :: [{atom(), arity(), [arity()]}]
```

The optional callbacks `module` implements at the wrong arity, as `{name, arity, arities}`:
the callback's name is exported at `arities` but not at the one the behaviour declares, so a
caller at the declared arity gets an `UndefinedFunctionError`. Assertion 1.

# `include_partial_refusal_cases`

```elixir
@spec include_partial_refusal_cases(String.t(), String.t(), keyword()) :: [
  {atom(), list(), term()}
]
```

The `{name, args, expected}` cases of assertion 36: `get_historical_prices/4` with an
`:include_partial` that is not a boolean must answer `{:error, {:invalid_option,
:include_partial, value}}`. `nil` is not tried: several venues read it as an absent option.

# `invalid_amount_fault`

```elixir
@spec invalid_amount_fault(term(), term()) ::
  nil | {:accepted | :wrong_error | :raised, term()}
```

What is wrong with a write's answer to the invalid `value` as its amount, or `nil`.
Assertion 47, venue rule W4.

The answer must be exactly `{:error, {:invalid_amount, value}}`, the value as given: a venue
that echoes a different term has rewritten what the caller sent. `{:accepted, answer}` is a
success, the dangerous one (a withdrawal of NaN was sent); `{:wrong_error, answer}` is any
other answer, which names another reason or another tag; `{:raised, answer}` is a raise,
exit or throw.

# `invalid_amounts`

```elixir
@spec invalid_amounts() :: [{atom(), term()}]
```

The invalid amounts assertion 47 hands every amount-taking write: `{label, value}`.

Venue rule W4 (`DpExchange.Core.Venue`): a float, zero, a negative, NaN and infinity are
each `{:error, {:invalid_amount, value}}`. NaN and infinity are `Decimal`s, which is how a
validator that compares with `Decimal.gt?/2` before checking `Decimal.nan?/1` raises.

# `invalid_order_ids`

```elixir
@spec invalid_order_ids() :: [term()]
```

Order ids `get_order/3` and `cancel_order/3` must refuse as `{:invalid_order_id, value}`:
`nil` and a whitespace-only string.

# `invalid_single_symbols`

```elixir
@spec invalid_single_symbols() :: [term()]
```

Single-symbol values `get_price/2` and `get_top_of_book/2` must refuse as
`{:invalid_symbol, value}`: `nil`, a number, and a whitespace-only string.

# `invalid_subscribers`

```elixir
@spec invalid_subscribers() :: [term()]
```

`:to` values a streaming call must refuse as `{:invalid_subscriber, value}`: neither a pid
nor an atom other than `nil`, `true` and `false`. `nil` is not here: it means the caller.

# `invalid_symbol_lists`

```elixir
@spec invalid_symbol_lists(String.t()) :: [term()]
```

Symbol lists the streaming calls must refuse as `{:invalid_symbols, value}`: not a list, a
list holding a non-string or a blank string (whitespace-only is blank), and an improper
list. `sample` is a symbol the venue serves, so the bad lists are bad for exactly one reason.

# `lower_case_delivery_fault`

```elixir
@spec lower_case_delivery_fault(module(), String.t(), keyword(), non_neg_integer()) ::
  nil | :not_delivered
```

What is wrong with a fake's `subscribe/2` given a lower-case pair, or `nil`. Assertion 29.

The real facade upper-cases subscribed symbols, so the payload arrives under `pair` (the
canonical form). `:not_delivered` when nothing arrived under it within `wait` milliseconds.

# `market_price_fault`

```elixir
@spec market_price_fault([term()]) :: nil | :price_never_refused | {:answered, term()}
```

What is wrong with the answers to `market_price_requests/2`, or `nil`. Assertion 34.

Every answer must be `{:error, {:unexpected_field, :price}}` or a refusal of the *duration*
that was tried (`{:unsupported_order_combination, type, tif}`,
`{:missing_field, :time_in_force}`, `{:unexpected_field, :time_in_force}`), and at least one
must be the price refusal.

  * `{:answered, answer}` — an answer that is neither: a success (the price was dropped
    and a different order placed than the one asked for), a raise, any other error.
  * `:price_never_refused` — every duration was refused for being the wrong duration, so
    nothing showed what the venue does with the price.

# `market_price_requests`

```elixir
@spec market_price_requests(map(), DpExchange.Core.Capabilities.t()) :: [map()]
```

The requests that put a `:price` on a market order, one for each time in force the venue
might want for it, or `[]` when `caps` does not support `:market`.

The time in force a venue takes on a market order is the venue's own (one wants `:ioc`, one
none at all), and the check order is time in force before price fields. So the request is
sent with no duration and with each duration `caps` names, and `market_price_fault/1`
judges the set.

# `market_value_fault`

```elixir
@spec market_value_fault(term()) :: nil | tuple()
```

What is wrong with a value (or list of values) a fake returned, or `nil`. Assertion 39.

A traded price (`Quote.price`, `Trade.price`, a candle's open, high, low and close) is above
zero: `{:non_positive, field, value}`. A `TopOfBook` bid or ask is `nil` or above zero. An
`OrderBook` level has a price and a size above zero and each side is best first
(`{:non_positive, {side, :price | :size}, value}`, `{:unreadable_level, side, level}`,
`{:unsorted, side}`). Any other value is not judged.

# `missing_fake_exports`

```elixir
@spec missing_fake_exports(module(), module()) :: [{atom(), arity()}]
```

The public functions of `venue` that `fake` does not export, as a sorted list of
`{name, arity}`. Assertion 44.

A function on the facade and not on the Fake is an `UndefinedFunctionError` in a consumer's
`async: true` suite and an answer in production: the consumer cannot fake what the Fake
does not have. `[]` is conforming. The Fake may export more (test helpers); that is not
judged here.

# `missing_required_callbacks`

```elixir
@spec missing_required_callbacks(module()) :: [{atom(), arity()}]
```

The callbacks `Core.Venue` requires that `module` does not export, as `{name, arity}`.
Assertion 1.

# `nil_recipient_fault`

```elixir
@spec nil_recipient_fault(module(), [String.t()], keyword(), non_neg_integer()) ::
  nil
  | {:raised, Exception.t()}
  | {:escaped, atom(), term()}
  | :nothing_delivered
```

What is wrong with a fake's `subscribe/2` given `to: nil`, or `nil`. Assertion 27.

A forwarded `nil` is a caller that said nothing, and nothing means the caller: it must not
raise and must deliver to the calling process. `wait` is how long to wait for the delivery.
`{:raised, error}` or `{:escaped, kind, reason}`; `:nothing_delivered`.

# `no_request_transport_fault`

```elixir
@spec no_request_transport_fault(term()) ::
  nil | {:ignored | :wrong_error | :raised, term()}
```

What is wrong with a no-request call's answer to `timeout: 5_000`, or `nil`. Assertion 46.

`subscribe_notices/1` sends no HTTP request, so a transport key there is
read by nothing: a caller who passed `timeout:` believed the call was bounded and it was
not. It must be `{:error, {:unknown_option, :timeout}}`. `{:ignored, answer}` is any
answer that carried on (`:ok`, `{:ok, _}`); `{:raised, answer}` a raise, exit or throw;
`{:wrong_error, answer}` anything else.

# `nothing_checked?`

```elixir
@spec nothing_checked?(module(), DpExchange.Core.Capabilities.t(), [
  {atom(), list(), term()}
]) ::
  boolean()
```

Whether a run of `cases` against `module` examined nothing it should have: no case is
eligible (`eligible_cases/3`) although `caps` declares at least one of the cases' calls
active, which means that call is not exported at its arity.

A venue that declares every call of `cases` `:unsupported` has nothing to hold and is
`false`; the declaration itself is held by assertion 12.

# `nothing_examined?`

```elixir
@spec nothing_examined?([term()], [boolean()]) :: boolean()
```

Whether assertion 39 examined nothing: `endpoints` were active but none of their answers
was checked (`examined` holds one boolean per endpoint, `true` when a value was inspected).

No active endpoint at all is not a failure (the venue declares them all `:unsupported`).

# `notice_batch_faults`

```elixir
@spec notice_batch_faults([term()]) :: [
  {atom() | nil, DpExchange.Core.Notice.fault()}
]
```

The rule violations in `notices`, as `{kind, fault}` pairs, judged by
`DpExchange.Core.Notice.conformance_faults/1`. `[]` is conforming, and so is an empty list:
judging nothing is not a fault, which is why group 45 also reports how many it judged.

# `order_id_refusal_cases`

```elixir
@spec order_id_refusal_cases((atom() -&gt; [keyword()])) :: [{atom(), list(), term()}]
```

The `{name, args, expected}` cases of assertion 35. `opts_for` gives, for a callback name,
the option lists to try; the suite passes the venue's own options with credentials stripped
and an empty list, because the id is checked before the account option and before
credentials.

# `order_request_cases`

```elixir
@spec order_request_cases(map()) :: [{term(), term()}]
```

The malformed `place_order/3` requests of assertion 34, as `{request, expected}`, built
from `base`, a request the venue accepts: a non-map (`{:invalid_order_request, request}`),
a request with no `:order_type` (`{:missing_field, :order_type}`) and one with a key the
builder does not read (`{:unsupported_order_field, key}`).

# `order_value_cases`

```elixir
@spec order_value_cases(map()) :: [{map(), term()}]
```

The requests of assertion 52, as `{request, reason}`, built from `base`, a request the venue
accepts: for each of `:quantity` and `:price` that `base` carries, one request per value of
`invalid_amounts/0` (a float, zero, a negative, NaN, infinity) in its place.

`reason` is `{:invalid_quantity, value}` or `{:invalid_price, value}`, the value as given
(`Core.Venue.place_order/3`, "Quantities and prices accept a `Decimal`, an integer or a
numeric string, and refuse a float, zero and negative values, and NaN and infinity"). Only
one field is bad in each request, so the refusal can only be for that field. A key `base`
does not carry is not probed: adding a `:price` to a market order would be refused for a
different reason (`{:unexpected_field, :price}`), and a venue whose sizing is a
`:quote_size` has no `:quantity` to spoil. `[]` therefore means `base` had neither, and the
group refuses to pass having examined nothing.

# `order_value_fault`

```elixir
@spec order_value_fault(term()) :: nil | {:order | :fill, String.t()}
```

What is wrong with an `Order` or `Fill` a fake answered, or `nil`. Assertion 54.

`answer` is a struct, a list of them, or `{:ok, either}`; anything else (a refusal, `:ok`, a
map) is not judged. Each struct is rebuilt through its own `new/1` (`Types.Order.new/1`,
`Types.Fill.new/1`), so a fake's order is held to everything the type refuses: a side, type,
status or time in force outside the vocabulary, a status beside a fill figure that
contradicts it, a negative size, a float, a leg that is not an `OrderLeg`. The fault is
`{:order, message}` or `{:fill, message}`, the constructor's own `ArgumentError` message.

Groups 5 and 31 judge only that a success is an `Order` and that its id is accepted back; a
fake that answered `%Order{status: :filled, filled_quantity: 0}` or `side: "buy"` passed
both. Every venue in the family builds its orders as struct literals, which skips the
constructor, so this is the only place the constructor's refusals reach a venue's Fake.

# `order_values_examined`

```elixir
@spec order_values_examined(term()) :: non_neg_integer()
```

How many `Order` and `Fill` structs `answer` holds (a struct, a list of them, or
`{:ok, either}`), the number assertion 54 actually judged.

A group over a fake that refused every call, or answered `{:ok, []}` everywhere, judged
nothing and must not read as a pass; assertion 54 requires at least one when
`place_order/3` is active.

# `order_write_discipline_cases`

```elixir
@spec order_write_discipline_cases(list(), list()) :: [{atom(), list(), term()}]
```

The `{name, args, expected}` cases of assertion 50, from the suite's own argument lists for
`preview_order/3` (`[credentials, request, opts]`) and `replace_order/4`
(`[credentials, id, changes, opts]`).

`preview_order/3` is handed each malformed request of `order_request_cases/1`, and must answer
the tuple `place_order/3` answers for it. `replace_order/4` is handed an empty change map and
values that are not maps, and must answer `{:invalid_order_request, value}`: a replace that
changes nothing is not a replace, and a non-map never raises.

# `permitted_apps`

```elixir
@spec permitted_apps(atom(), [tuple()]) :: MapSet.t(String.t())
```

The applications a package may link against: its own, every declared dependency, and what
those depend on at runtime, as strings.

`deps` is `Mix.Project.config()[:deps]` in any of Mix's three shapes. The purity assertion
read it with `for {dep, _rest} <- deps`, which matches a two-element tuple only, so every
dependency declared with a requirement AND options — `{:jason, "~> 1.4", optional: true}`,
`{:foo, "~> 1.0", only: :test}` — was silently dropped from the declared set.

# `probe_timeframe`

```elixir
@spec probe_timeframe(DpExchange.Core.Capabilities.t()) :: String.t() | nil
```

The width assertions 36 and 37 ask `get_historical_prices/4` for: `"1h"` when `caps` declares
it, else the first declared width the shared vocabulary gives a fixed length, else the first
declared width, or `nil` when none is declared.

A venue is asked for a width it serves: asking for one it does not would be refused for the
width before the option under test was ever read.

# `process_credential_fault`

```elixir
@spec process_credential_fault({module(), atom()} | nil) ::
  nil | {:leaked, pid(), String.t()} | {:probe_failed, term()}
```

What is wrong with a venue's running process where a secret is concerned, or `nil`.
Assertion 51.

`probe` is the `:process_probe` option: `{module, function}`, a 1-arity function that starts
the venue's own process or processes with no network from a credentials map. It is handed a
map holding one unique canary under every secret key name this family's venues use, and
must answer `{:ok, pid}` or `{:ok, [pid, ...]}`. Each process is asked for
`:sys.get_status/2`, which carries its process dictionary and the state it would print in a
crash report, rendered with `inspect/2`; the canary in it is `{:leaked, pid, excerpt}`. The
processes are stopped afterwards whatever the answer.

`{:probe_failed, answer}` when the probe starts nothing it can examine (an error, an empty
list, a non-pid, a process that does not answer to `:sys`): a probe that examined nothing
must not read as a pass. `nil` for no probe at all: the group is opt-in.

Public so that a teeth test can hand it a process that keeps the raw map, which the inline
version could never be shown to catch.

# `promised_types`

```elixir
@spec promised_types() :: %{required({atom(), non_neg_integer()}) =&gt; [module()]}
```

The `Core.Types` struct(s) each `Core.Venue` callback's success is typed as, read from the
behaviour's own `@callback` specs.

Returns `%{{name, arity} => [module]}` for every callback whose success type names a
`DpExchange.Core.Types.*` struct, and omits the rest. Read from the compiled specs rather
than written as a table, so it cannot drift from the contract it restates. Used by
assertion 5.

# `provider_name_fault`

```elixir
@spec provider_name_fault(term()) :: nil | {:provider_name, term()}
```

The identity fault of a venue's `provider_name/0` answer, or `nil`. Assertion 3.

# `range_fault`

```elixir
@spec range_fault(keyword(), term(), [DateTime.t()]) ::
  nil | {:outside_range | :missing | :not_answered, term()}
```

What is wrong with a ranged answer, or `nil`. `:start` is inclusive and `:end` exclusive, on
`opened_at`: `{:outside_range, opened_at}` for a bar outside `range`, `{:missing,
opened_at}` for one of `must_include` that was dropped.

**A range chosen to keep a bar must be answered.** The ranges come from the fake's own bars, so
a window around them is one the venue has data for, and an answer that is not `{:ok, list}` is
`{:not_answered, answer}`: a fake refusing every range it was asked passed this group for
having examined no bound. When `must_include` is empty (the ask that must drop the bar) a
refusal stays `nil`: a venue may decline a window with nothing in it, and a refusal there says
nothing about the bounds.

# `refusal_fault`

```elixir
@spec refusal_fault(term(), term()) :: nil | :raised | {:answered, term()}
```

What is wrong with a call's answer to an argument the family refuses locally, or `nil`.
Assertions 32 to 35.

`expected` is the whole error term, for example `{:invalid_symbols, nil}`; the answer must
be exactly `{:error, expected}`.

  * `:raised` — the call raised, exited or threw (`{:raised, error}`, `{:exit, reason}` or
    `{:throw, value}` from the suite's `call_on/3`). A raise is not a refusal.
  * `{:answered, answer}` — any other answer: `:ok` for a malformed list, `{:ok, _}`,
    `{:error, :feed_not_started}` where a symbol was wrong, a refusal after credentials.

# `runtime_id_fault`

```elixir
@spec runtime_id_fault(module(), term()) :: nil | {:runtime_id, term(), String.t()}
```

The identity fault of a venue's `runtime_id/0` answer, or `nil`. Assertion 3.

The id is an atom equal to the module's last segment, underscored: `DpExchange.Coinbase` is
`:coinbase`. `to_string/1` is compared, because `nil`, `true` and `false` stringify to words
no module segment is.

# `single_symbol_refusal_cases`

```elixir
@spec single_symbol_refusal_cases((atom() -&gt; keyword())) :: [{atom(), list(), term()}]
```

The `{name, args, expected}` cases of assertion 33. `opts_for` gives the options for a
callback name; the suite passes options with credentials stripped, because the refusal is
decided before credentials are read.

# `staking_return_fault`

```elixir
@spec staking_return_fault(term()) ::
  nil | {:not_a_staking_transaction | :unexamined | :raised, term()}
```

What is wrong with a staking write's answer to a valid call, or `nil`. Assertion 48, venue
rule W5.

`stake/3` and `unstake/3` are typed `result(Types.StakingTransaction.t())`, and the Fake
returns that struct as the real path does. `{:not_a_staking_transaction, answer}` is a
success of another kind (a raw map); `{:unexamined, answer}` is a refusal or error, which
proves nothing about the return and is how a fake that cannot be asked passes unseen (declare
what it needs in `endpoint_opts:`, or declare the endpoint `:unsupported`);
`{:raised, answer}` a raise, exit or throw.

# `streamed_but_absent`

```elixir
@spec streamed_but_absent([{atom(), arity()}], [atom()]) :: [
  {atom(), {atom(), arity()}}
]
```

The `{kind, endpoint}` pairs a package declares streamable while saying the venue does not
serve the matching pull endpoint at all (`absent`). Assertion 8: the two cannot both be
true.

# `streaming_call_fault`

```elixir
@spec streaming_call_fault((-&gt; term())) ::
  nil | {:escaped, atom(), term()} | {:not_a_value, term()}
```

What is wrong with a streaming call made while no feed runs, or `nil`. Assertion 28.

`call` is a zero-arity function making the call. A streaming callback returns a value
(`:ok`, `{:error, _}` or a map); `{:escaped, kind, reason}` when it exited, threw or raised
in the caller, `{:not_a_value, result}` for any other answer.

# `streaming_refusal_cases`

```elixir
@spec streaming_refusal_cases(String.t(), keyword()) :: [{atom(), list(), term()}]
```

The `{name, args, expected}` cases of assertion 32, for the streaming calls given `opts`.

Every malformed symbol list on `subscribe/2`, `unsubscribe/2` and `update_symbols/2`; every
bad `:to` on `subscribe/2` and `subscribe_notices/1`; and one call wrong in both ways, which
must answer for the symbols, because the symbols are checked first. `opts` decides whether
this drives a fake or the real facade with no feed running.

# `struct_answer?`

```elixir
@spec struct_answer?(term()) :: boolean()
```

Whether `result` is a success that gave assertion 5 a struct to check: `{:ok, struct}` or
`{:ok, [struct | _]}`.

Assertion 5 takes a refusal, an error and an empty list as "not a success, so nothing to
compare", which is right for each answer alone. Across a whole fake it is not: a fake that
refused every endpoint (a missing `endpoint_opts:`) or returned `{:ok, []}` everywhere
passed the group having compared nothing. The group requires at least one answer for which
this is `true`.

# `subscribed_push_fault`

```elixir
@spec subscribed_push_fault({:dp_exchange, term(), term()}, atom()) ::
  nil | :wrong_tag | :not_a_core_struct
```

What is wrong with a message `subscribe/2` pushed, or `nil` when nothing is. Assertion 20.

`message` is the `{:dp_exchange, runtime_id, payload}` the suite received and `runtime_id`
is the venue's own `c:DpExchange.Core.Venue.runtime_id/0`.

  * `:wrong_tag` — tagged with something other than the venue's `runtime_id/0`, so a
    process subscribed to several venues cannot tell them apart. Judged first.
  * `:not_a_core_struct` — the payload is not a `DpExchange.Core.Types.*` struct: raw venue
    data forwarded to subscribers, the shape with a wired decoder that never ran.

# `subscription_set_fault`

```elixir
@spec subscription_set_fault(module(), [String.t(), ...], keyword()) ::
  nil | {atom(), term()}
```

What is wrong with a fake's subscription set after four calls, or `nil`. Assertion 26.

`subscribe/2` adds to the live set, `update_symbols/2` replaces it, `unsubscribe/2` removes
only what it names; the set is read back through `coverage/1`. Needs two symbols the fake
serves. The fault names the first rule broken: `{:never_covered, symbol}` (nothing to compare
with), `{:subscribe_dropped, symbols}`, `{:subscribe_missed, symbol}`,
`{:unsubscribe_left, symbol}`, `{:unsubscribe_overreached, symbol}`,
`{:update_not_taken, symbol}`, `{:update_left, symbol}` or `{:state_not_restored, symbols}`.

The last step unsubscribes the one symbol the set still holds, and the set must then be empty
again: coverage that retains anything for a symbol no longer subscribed is state that grows
by one entry for every distinct symbol a venue is ever asked about.

# `time_fault`

```elixir
@spec time_fault(term(), module()) ::
  nil | {:wrong_struct, term()} | {:observed_at, term()} | {:venue_time, term()}
```

What is wrong with the times on a struct a fake returned, or `nil`. Assertions 14 and 23.

`module` is the struct the endpoint promises (`Quote`, `OrderBook` or `TopOfBook`).
`{:wrong_struct, value}`; `{:observed_at, value}` unless it is a `DateTime`; `{:venue_time,
value}` unless it is a `DateTime` or `nil`: the venue's own time, never an epoch integer, a
`NaiveDateTime` or a stand-in.

# `timeframe_fault`

```elixir
@spec timeframe_fault(term(), String.t()) ::
  nil | :substituted | {:wrong_answer, term()}
```

What is wrong with a call's answer to `width`, a timeframe the venue does not declare, or
`nil`. Assertion 21.

The answer must be exactly `{:error, {:unsupported_timeframe, width}}`, the width as given
(`DpExchange.Core.Venue.get_historical_prices/4`). `:substituted` is `{:ok, _}`: the venue
served the width it has in place of the one asked for, a missing granularity becoming the
closest one, which mislabels every candle it touches. `{:wrong_answer, answer}` is any other
answer: a different error, a refusal, a raise, or the tag with another value. It used to be
`nil` for all of them, so a fake that answered `{:error, :boom}` to every width it was asked
for passed a group documented as "refused, never served at the nearest width"; the refusal
was assumed, not seen.

# `unexported_calls`

```elixir
@spec unexported_calls(module(), DpExchange.Core.Capabilities.t(), [
  {atom(), list(), term()}
]) :: [
  {atom(), arity()}
]
```

The `{name, arity}` of each call in `cases` that `caps` declares active and `module` does not
export, in the order first met.

`eligible_cases/3` drops such a case without a word, so a fake missing one of two calls was
judged on the other and passed. Used by assertions 33 and 35, whose calls
(`get_price/2`, `get_top_of_book/2`, `get_order/3`, `cancel_order/3`) are required
`Core.Venue` callbacks, so a declaration of one active with no export behind it is a fake that
cannot be asked, not a call the venue does not serve.

# `unknown_id_endpoints`

```elixir
@spec unknown_id_endpoints() :: [{atom(), non_neg_integer()}]
```

The reads and cancels assertion 49 asks about an unknown id: the order callbacks `Core.Venue`
documents `{:refused, :not_found}` for. Other id-taking callbacks (`get_conversion/2`,
`get_payment_method/3`, `get_watchlist/2`) have no documented answer for an unknown id, so
none is asserted.

# `unknown_id_fault`

```elixir
@spec unknown_id_fault(term()) :: nil | {:invented | :wrong_answer | :raised, term()}
```

What is wrong with a fake's answer to an order id nobody placed, or `nil`. Assertion 49,
venue rule W6.

The real path answers a 404 `{:refused, :not_found}` (`c:DpExchange.Core.Venue.cancel_order/3`).
`{:invented, answer}` is a success: the fake made up an order for an id it never issued, so a
consumer's test of its not-found handling can never run. `{:wrong_answer, answer}` is any
other answer, including a refusal with another reason, or an error from a call the fake could
not place for lack of `endpoint_opts:`; `{:raised, answer}` a raise, exit or throw.

# `unknown_option_evidence?`

```elixir
@spec unknown_option_evidence?(:strict | term(), term()) :: boolean()
```

Whether an endpoint's answer to the probe is evidence the options were checked: it could be
judged (`unknown_option_judged?/1`), or it is the `{:unknown_option, key}` refusal itself.

A refusal of the probe is proof whatever the plain call did, so an endpoint the fake
refuses for a venue reason (an order id it has not placed) still counts when it refused the
option too. Assertion 43 requires at least one such answer.

# `unknown_option_exclusions`

```elixir
@spec unknown_option_exclusions() :: %{
  required({atom(), non_neg_integer()}) =&gt; String.t()
}
```

The callbacks assertion 43 cannot hand an unknown option, each with the written reason.

Everything else the facade declares is probed. An exclusion is a decision, so it is listed
here rather than skipped inside the group: a callback absent from both `unknown_option_probed/1`
and this map would be silently unprobed, and the teeth tests hold that the two cover the
whole behaviour.

# `unknown_option_fault`

```elixir
@spec unknown_option_fault(term()) ::
  nil | {:ignored | :wrong_error | :raised, term()}
```

What is wrong with a read's answer to a call carrying `unknown_option_probe/0`, or `nil`.
Assertion 43.

A facade call given an option it does not read must refuse it,
`{:error, {:unknown_option, :dp_contract_unknown_option}}`, not ignore it: an ignored
misspelling (`limt:`) is a plausible answer to a different question. `{:error,
:not_supported}` is also fine, since an endpoint the venue does not serve answers that
whatever the options. `{:ignored, answer}` is a success or a refusal that carried on as if
the key were absent; `{:wrong_error, answer}` is any other answer, which names a different
key or is not the family tag; `{:raised, answer}` is a raise, exit or throw.

# `unknown_option_fault`

```elixir
@spec unknown_option_fault(:strict | term(), term()) ::
  nil | {:ignored | :wrong_error | :raised, term()}
```

`unknown_option_fault/1`, for an endpoint whose plain call is the `baseline`.

With `:strict` or a baseline that succeeded, the answer is judged by `unknown_option_fault/1`.
With any other baseline the fake refused the generated call for its own reason, so the
options were never reached and only two things are faults: a raise, and a success (a probe
cannot turn a refusal into a success).

# `unknown_option_judged?`

```elixir
@spec unknown_option_judged?(:strict | term()) :: boolean()
```

Whether assertion 43 could judge an endpoint's answer to the probe.

`mode` is `:strict` (judged on the probe alone) or the answer the same call gave without the
probe. A write or a read the fake refuses for its own reasons before it reads the options (a
venue-specific refusal of the generated arguments) gives no evidence about the options, so it
is not judged; the group requires at least one judged answer.

# `unknown_option_probe`

```elixir
@spec unknown_option_probe() :: :dp_contract_unknown_option
```

The option key assertion 43 hands every read: one no venue reads and none can mistake for
its own.

# `unknown_option_probed`

```elixir
@spec unknown_option_probed([{atom(), non_neg_integer()}]) :: [
  {atom(), non_neg_integer()}
]
```

The callbacks of `callbacks` assertion 43 probes: all of them, less
`unknown_option_exclusions/0`.

# `unknown_option_strict`

```elixir
@spec unknown_option_strict() :: [{atom(), non_neg_integer()}]
```

The five reads assertion 43 judged before it covered every endpoint. They are judged on the
probe alone, with no baseline call: a refusal of the probe key there is a fault whatever the
plain call would have answered.

# `unknown_order_id`

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

The order id assertion 49 asks about: one no write of the suite ever minted.

# `unknown_stream_kinds`

```elixir
@spec unknown_stream_kinds(DpExchange.Core.Capabilities.t()) :: [term()]
```

The kinds in `streamable` or `authenticated_streamable` that this contract has no word for
(`Capabilities.data_kinds/0`). Assertion 8.

# `unrecorded_absences`

```elixir
@spec unrecorded_absences([{atom(), arity()}], DpExchange.Core.Capabilities.t()) :: [
  {atom(), arity()}
]
```

The endpoints `venue_does_not_serve/0` names that `caps` does not declare `:unsupported`.
Assertion 8: an endpoint the venue does not serve cannot also be one the package answers.

# `unserved_timeframe`

```elixir
@spec unserved_timeframe(DpExchange.Core.Capabilities.t()) :: String.t() | nil
```

The timeframe assertion 21 asks `get_historical_prices/4` for: the first width of
`Timeframe.nameable/0` that `caps.historical_timeframes` does not name, or `nil` when the
venue declares the whole vocabulary and there is no width left to prove a refusal with.

# `unsupported_fault`

```elixir
@spec unsupported_fault(term()) :: nil | :string_refusal | :raised | :served | :other
```

What is wrong with a call's answer to an endpoint `capabilities/0` declares `:unsupported`,
or `nil` when nothing is. Assertion 12's under-declaring direction.

  * `nil` — `{:error, :not_supported}`, the atom the contract promises.
  * `:string_refusal` — `{:error, "not_supported"}`. A caller matching the atom misses it
    and treats a refusal as an unrecognised error.
  * `:raised` — the call raised (`{:raised, error}` from the suite's `call_on/3`).
  * `:served` — `{:ok, _}`: declared unsupported but it works, which hides functionality a
    consumer could have used.
  * `:other` — any other answer, which is neither the atom nor a success.

# `use_options`

```elixir
@spec use_options() :: [atom()]
```

The options `use DpExchange.Core.AdapterContract` accepts.

Anything else is refused by `validate_use_opts!/1`: a misspelled option (`fkae:`,
`sample_pair:`) used to be ignored, and the group it was meant to switch on then ran against
its default or was skipped, which reads as a pass.

# `validate_order_args!`

```elixir
@spec validate_order_args!(term()) :: %{optional(atom()) =&gt; term()}
```

Checks the `order_args:` option of `use DpExchange.Core.AdapterContract`. It must be a map
whose keys are drawn from `:order_request`, `:order_requests` and `:order_changes`.

An unknown key raises at compile time instead of being ignored. A misspelt key that was
silently dropped would leave the suite's default order in place, and the assertion it
was meant to reach would go on failing, or passing, for a reason nobody declared.

# `validate_use_opts!`

```elixir
@spec validate_use_opts!(term()) :: keyword()
```

Validates the options given to `use DpExchange.Core.AdapterContract`, returning them.

Raises `ArgumentError` for a non-keyword list, or a key outside `use_options/0`.

# `value_examined?`

```elixir
@spec value_examined?(term()) :: boolean()
```

Whether `value`, the success of a price endpoint, gave assertion 39 anything to inspect.

A struct does, except an `OrderBook` with no level on either side and a `TopOfBook` with
neither a bid nor an ask (nothing there for `market_value_fault/1` to hold). A list does when
one of its elements does, so `{:ok, []}` from `get_trades/2` or an empty candle list does
not. Anything else, a bare map or a scalar, is not something `market_value_fault/1` judges.

`{:ok, _}` of any shape used to count as examined, so a fake answering `{:ok, []}` for every
price endpoint passed the group having looked at no price, size or level at all.

# `volume_window_fault`

```elixir
@spec volume_window_fault(term()) ::
  nil
  | {:wrong_struct, term()}
  | {:window_without_volume | :unstated_window, term()}
```

What is wrong with the volume window on a `Quote` a fake returned, or `nil`. Assertion 30.

A quote with no volume names no window: `{:window_without_volume, window}`. A quote with a
volume names which quantity it is (one of `Quote.volume_windows/0`): `{:unstated_window,
window}`.

# `wanted_fault`

```elixir
@spec wanted_fault(module(), [String.t()], keyword()) :: nil | {atom(), term()}
```

What is wrong with a fake's `wanted/1`, or `nil`. Assertion 40.

A fake that does not export `wanted/1` has not adopted the optional callback and is never
faulted. Otherwise, after `subscribe/2` of `pairs`, `wanted/1` is a list or a `MapSet`
(`{:not_a_collection, value}`), holds every symbol subscribed
(`{:subscribed_not_wanted, symbols}`) and holds every symbol `coverage/1` reports
(`{:covered_not_wanted, symbols}`), because what arrived was asked for. The set is cleared
with `update_symbols/2` first, so an earlier subscription cannot satisfy it.

---

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