# Architecture

## Crate layout

```
akira-billing
├── client          // Client — HMAC-signed transport over reqwest
├── signature       // canonical / sign / new_nonce / HEADER_* constants
├── error           // Error enum (Transport / Api / Decode)
├── types           // request + response data shapes (serde)
├── oauth           // PKCE primitives + init-URL builder
├── license         // decode + verify + remaining math + grace + updates-window
├── gate            // Gate runtime — feature checks
├── usage           // UsageTracker / MemoryBuffer / UsageBuffer trait
├── lifecycle       // compute_state / trial_days_left / LicenseState
├── loopback*       // desktop loopback OAuth (feature = "loopback")
└── desktop*        // keystore, cipher, fingerprint, session (feature = "desktop")
```

`*` feature-gated. The default build (`default = ["tokio"]`) pulls only the first nine modules.

## Trust model

The crate is **server / desktop only**. The `productSecret` is the trust root — leaking it allows an attacker to impersonate the product against the billing API. Do not embed a `Client` in a public artifact:

- Web frontends (WASM bundles): use the [TypeScript SDK](https://github.com/akira-io/billing-sdk-js) storefront helpers instead. The Rust crate has no browser surface.
- Public CLIs: ship a thin command that calls a backend you control; do not bake the secret into the binary.
- Desktop apps: the binary is signed and shipped to end users. The secret in the binary is recoverable via `strings` or any disassembler. Use the `desktop` feature's `keystore`/`cipher` for per-customer tokens, but accept that the **product** secret is effectively public once the binary leaves your build pipeline.

For desktop apps you have two practical options:

1. **Per-product secret + accept exposure.** The billing app rate-limits + monitors per-product traffic. The damage from a leaked secret is bounded because it cannot impersonate other products.
2. **Per-build rotation.** Rotate `hmac_secret` on every release. Old binaries fall off the cliff after the 24-hour grace window (see [60-protocol](60-protocol.md)).

## Request signing

Every signed request carries four headers and an HMAC-SHA256 signature computed over a deterministic canonical string. See [11-signature](11-signature.md) for the byte-exact format.

```
X-Akira-Product:   <product_slug>
X-Akira-Timestamp: <unix seconds>
X-Akira-Nonce:     <random 32 hex chars>
X-Akira-Signature: <hex-encoded HMAC-SHA256>
```

The canonical bytes are joined with `\n` and the body is SHA-256'd to a hex prefix. The Go and Rust SDKs share `tests/fixtures/signature-vectors.json` — any change to the canonical form requires both crates to roll together.

## Async runtime

Methods that touch the network are `async`. The default feature pulls Tokio:

```toml
[features]
default = ["tokio"]
tokio = ["dep:tokio"]
```

`tokio` is `optional = true` so consumers running on alternative runtimes can opt out by disabling default features. The `Client` itself is runtime-agnostic — it relies only on `reqwest::Client` (which is `Send + Sync` and works on any executor).

The `loopback` feature mandates Tokio because the HTTP server uses `tokio::net::TcpListener`.

## Connection pooling

`Client` wraps a single `reqwest::Client`. Reuse the same `Client` across requests — `reqwest::Client` is `Clone` and internally `Arc`-wrapped, so cloning is cheap and shares the connection pool.

For per-request token isolation (e.g. routing different customer bearers through one process), construct a fresh `Client` per request — the connection pool is still shared via the underlying `reqwest::Client::new()`, but the bearer is per-instance.

## Concurrency

`Client` is `Clone + Send + Sync`. The internal `reqwest::Client` is also `Send + Sync` so the value is safe to share across tasks via `Arc<Client>` or by cloning.

The mutating methods (`set_customer_token`, `clear_customer_token`) take `&mut self`. To swap the bearer across tasks, wrap in a `Mutex` or `RwLock`, or construct a per-request clone:

```rust
let mut clone = base_client.clone();
clone.set_customer_token(req_token);
// run the request on `clone`
```

## Error model

A single `Error` enum captures every failure mode (`Transport`, `Api { status, code }`, `Decode`). See [12-errors](12-errors.md) for the branching pattern.

## Module boundaries

```
                   storefront    auth bridge     signed runtime
                   ───────────   ───────────     ──────────────
                   oauth         loopback*       client (Client)
                                                 license
                                                 gate
                                                 usage
                                                 lifecycle
                                                 desktop*
```

The `oauth` module is browser-safe — it builds the URL and PKCE values without touching the secret. `loopback` orchestrates the desktop side of OAuth. Everything else is server-only or desktop-only.

---

Navigation: [← Quickstart](02-quickstart.md) · **Architecture** · [Client →](10-client.md)
