# `Bourse.Spec`
[🔗](https://github.com/ZenHive/bourse/blob/main/lib/bourse/spec.ex#L1)

Compile-time JSON spec loader for exchange specifications.

Reads the closed runtime-support manifest and its complete owned venue specs
for use by the generator macro (`Bourse.Exchange`). CCXT-derived documents are
a separate authoring corpus and are never read by this module.

## Usage

    # In generator macro (compile time):
    spec = Bourse.Spec.load!("bybit")

    # List available exchanges:
    Bourse.Spec.exchanges()
    #=> ["alpaca", "binance", "binancecoinm", "binanceusdm", "bybit", "coinbaseexchange", "deribit", "derive", "hyperliquid", "lighter", "okx"]

## Schema Version

The returned documents carry this project's schema version. It changes only
when `bourse` deliberately migrates its compile-time contract; upstream
extractor versions and provenance metadata are not part of that contract.

Every supported venue loads one complete hand-owned document from
`priv/specs/json/output/authored/`. Meaningful `null` and empty values are
preserved byte-for-byte from that document.

# `authored_spec_path`

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

Returns the hand-authored document path for an authored venue, otherwise `nil`.

# `decode_file!`

```elixir
@spec decode_file!(String.t()) :: map()
```

Decodes a spec document after rejecting duplicate JSON object keys.

# `exchanges`

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

Returns the complete, invariant runtime-support inventory.

## Examples

    Bourse.Spec.exchanges()
    #=> ["alpaca", "binance", ..., "okx"]

# `load!`

```elixir
@spec load!(String.t()) :: map()
```

Loads and decodes an exchange spec by ID.

Reads and validates the complete owned JSON document. Returns a map with
string keys.

Raises `File.Error` if the spec file doesn't exist, or `Jason.DecodeError`
on invalid JSON.

## Examples

    spec = Bourse.Spec.load!("bybit")
    spec["exchange"]["id"]
    #=> "bybit"

# `load_manifest!`

```elixir
@spec load_manifest!() :: map()
```

Loads and decodes the manifest file.

Returns the closed runtime-support contract. This manifest is structurally
distinct from the authoring reference-corpus manifest.

## Examples

    manifest = Bourse.Spec.load_manifest!()
    manifest["venue_count"] #=> 11

# `manifest_path`

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

Returns the absolute path to the manifest file.

## Examples

    Bourse.Spec.manifest_path()
    #=> "/path/to/priv/specs/json/runtime_support.json"

# `oracle_venues`

```elixir
@spec oracle_venues(String.t() | atom()) :: [String.t()]
```

Returns supported venues graded by the named authored oracle.

# `owned_spec_path`

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

Returns the complete owned runtime-document path for a first-class venue.

# `schema_version`

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

Returns the owned local spec schema version.

# `spec_path`

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

Returns the absolute path to the runtime spec file for the given exchange ID.

Supported venues resolve to their complete owned document. Unsupported
reference-only venues raise immediately.

## Examples

    Bourse.Spec.spec_path("bybit")
    #=> "/path/to/priv/specs/json/output/authored/bybit.json"

# `supported?`

```elixir
@spec supported?(String.t() | atom()) :: boolean()
```

Returns whether the exchange is part of the closed runtime-support inventory.

# `validate_all_documents!`

```elixir
@spec validate_all_documents!() :: :ok
```

Validates the runtime manifest and every supported owned document.

Reference-corpus validation belongs to the authoring-only reference-corpus
boundary, which lives in the source repository's Mix tooling and is never
reachable at runtime.

# `validate_authored_contract!`

```elixir
@spec validate_authored_contract!(map(), String.t()) :: :ok
```

Validates every required interpretive slot when a spec is marked authored.

# `validate_manifest_schema!`

```elixir
@spec validate_manifest_schema!(map()) :: map()
```

Validates a decoded runtime-support manifest.

# `validate_schema!`

```elixir
@spec validate_schema!(map(), String.t()) :: map()
```

Validates a decoded per-exchange spec map against the owned schema contract.

Returns the spec unchanged on success. Called internally from `load!/1`;
exposed publicly so test suites can exercise every branch against in-memory
maps without writing fixture files into the live spec directory.

---

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