# `Sidereon.GNSS.Distribution`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/gnss/distribution.ex#L1)

Exact GNSS product acquisition from explicit public distributors.

Product identity is independent from transport. An ordered source list may
contain the cataloged direct archive, NASA CDDIS/Earthdata, a local file, or
caller-provided bytes. Switching source never changes center, solution class,
issue, cadence, date, family, or official filename.

This module owns authenticated HTTP and cache IO. The Rust core and NIF remain
network-free and continue to own catalog derivation and SP3/IONEX parsing.
`Sidereon.GNSS.Data` delegates `identity/1`, `request/2`, `cddis_url/1`, and
`acquire/2` here.

# `error_reason`

```elixir
@type error_reason() ::
  :offline_cache_miss
  | {:invalid_option, :max_archive_bytes | :max_product_bytes}
  | {:unsupported_distribution, atom(), String.t()}
  | {:authentication_required, integer(), String.t()}
  | {:authentication_failed, integer(), String.t()}
  | {:authorization_denied, integer(), String.t()}
  | {:product_not_published, integer(), String.t()}
  | {:retired_endpoint, integer(), String.t()}
  | {:redirect_policy_failure, integer(), String.t()}
  | {:malformed_url, String.t()}
  | {:transport, atom(), String.t()}
  | {:http_client_failure, atom(), String.t()}
  | {:http_status, integer(), String.t()}
  | {:invalid_content_type, String.t(), String.t()}
  | {:error_document, String.t()}
  | {:content_length_mismatch, term(), non_neg_integer(), String.t()}
  | {:download_size_exceeded, non_neg_integer()}
  | {:decompression_failed, term()}
  | {:checksum_mismatch, String.t(), String.t()}
  | {:product_validation_failed, term()}
  | {:cache_read_failed, term()}
  | {:cache_write_failed, term()}
  | {:all_distributors_failed, [Sidereon.GNSS.Distribution.SourceFailure.t()]}
```

# `acquire`

```elixir
@spec acquire(
  Sidereon.GNSS.Distribution.Request.t(),
  keyword()
) ::
  {:ok, Sidereon.GNSS.Distribution.Result.t()}
  | {:error, error_reason() | term()}
```

Acquire an exact product from only its ordered, caller-selected sources.

# `cddis_url`

```elixir
@spec cddis_url(Sidereon.GNSS.Distribution.ProductIdentity.t()) ::
  {:ok, String.t()} | {:error, error_reason() | term()}
```

Build the official CDDIS URL for an exact SP3 or IONEX identity.

# `direct`

```elixir
@spec direct() :: Sidereon.GNSS.Distribution.Source.t()
```

Cataloged direct analysis-center distribution.

# `identity`

```elixir
@spec identity(Sidereon.GNSS.Data.Product.t()) ::
  {:ok, Sidereon.GNSS.Distribution.ProductIdentity.t()}
  | {:error, error_reason() | term()}
```

Resolve an existing data product to exact distributor-independent identity.

# `identity_key`

```elixir
@spec identity_key(Sidereon.GNSS.Distribution.ProductIdentity.t()) :: String.t()
```

Returns the stable cache key derived from every exact identity field.

# `in_memory`

```elixir
@spec in_memory(
  binary(),
  keyword()
) :: Sidereon.GNSS.Distribution.Source.t()
```

Caller-provided in-memory product bytes.

# `local_file`

```elixir
@spec local_file(
  String.t(),
  keyword()
) :: Sidereon.GNSS.Distribution.Source.t()
```

Caller-provided local file source.

# `location`

```elixir
@spec location(
  Sidereon.GNSS.Distribution.ProductIdentity.t(),
  Sidereon.GNSS.Distribution.Source.source_type() | String.t()
) ::
  {:ok,
   %{
     source: Sidereon.GNSS.Distribution.Source.source_type(),
     original_url: String.t() | nil,
     archive_filename: String.t(),
     compression: :none | :gzip | :unix_compress
   }}
  | {:error, error_reason() | term()}
```

Resolve an official distribution location for a complete exact identity.

# `nasa_cddis`

```elixir
@spec nasa_cddis() :: Sidereon.GNSS.Distribution.Source.t()
```

Official NASA CDDIS/Earthdata HTTPS distribution.

# `request`

```elixir
@spec request(Sidereon.GNSS.Data.Product.t(), [
  Sidereon.GNSS.Distribution.Source.t()
  | Sidereon.GNSS.Distribution.Source.source_type()
]) ::
  {:ok, Sidereon.GNSS.Distribution.Request.t()}
  | {:error, error_reason() | term()}
```

Build an exact request from an ordered list of acceptable distributors.

# `validate_exact_product_set`

```elixir
@spec validate_exact_product_set([Sidereon.GNSS.Distribution.ProductIdentity.t()], [
  Sidereon.GNSS.Distribution.ProductIdentity.t()
]) :: :ok | {:error, error_reason() | term()}
```

Require available identities to be exactly the declared product set.

The expected set must be non-empty. Both lists reject duplicates; missing and
undeclared identities fail. Comparison includes every identity field, not
only the official filename. For SP3 observed/predicted timing, use
`Sidereon.GNSS.SP3.prediction_summary/1`; catalog fields and issue times are
not substitutes for product record flags.

---

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