<!-- docs/ADR/2026-07-4.0-public-api.md -->

# ADR: Unified BLE 4.0 public API

**Status:** Accepted design baseline
**Date:** 2026-07-25
**Supersedes:** the public/API portions of the deleted transitional host, byte, and native ADRs

## Decision

`unified-ble-manager@4.0.0` is a new package and a clean API baseline. It does
not adapt, mirror, retain, or silently emulate the 3.x manager, port, byte,
transaction, or capability surface. The public root is framework-neutral and
contains only portable manager, handle, stream, error, capability, byte, and
diagnostic contracts. A host is selected only through an explicit subpath and
explicit provider/backend construction.

The Phase 0 declaration fixture was deleted at G1 promotion. The production
root is [`src/index.ts`](../../src/index.ts), which is a package export rather
than a second declaration authority. Normative behavior is
[`UNIFIED_SEMANTICS.md`](../UNIFIED_SEMANTICS.md); this ADR freezes the
public-boundary decisions needed to implement it.

## Public surface and ownership

The root exposes one `BleManager` created from a selected backend, `ScanSession`,
`Connection`, `GattDatabase`, occurrence-safe attributes, `Subscription`,
`BoundedAsyncStream`, normalized errors, runtime capabilities, and explicit
cleanup. The production declarations are generated from
[`src/manager`](../../src/manager),
[`gatt.ts`](../../src/backend-contract/gatt.ts), and
[`streams.ts`](../../src/backend-contract/streams.ts).

- A provider enumerates adapters and requires a selection when selection is
  ambiguous. Importing the root never creates a global manager, radio owner, or
  host dependency.
- An owning manager destroys its backend only when no registered borrowers
  remain. With borrowers, it admits no new work, awaits resource settlement,
  then completes either settled revocation or an atomic, verified ownership
  transfer; a borrower only releases its own leases. Borrowing never grants
  scan multiplexing, connection ownership, or another client's cancellation
  authority.
- A normal second scan fails `scan.already-active`. Joining is explicit, uses an
  authorized share token, requires identical scan semantics, and gives each
  consumer a separately bounded stream.
- A connection, database, service, characteristic, descriptor, and subscription
  are attachment- and generation-bound. Disconnect, rediscovery, Services
  Changed, adapter reset, backend replacement, and destroy invalidate old
  paths. A stale path fails `gatt.stale-handle` before backend dispatch.
- Public cancellation is `AbortSignal`; public deadlines are absolute monotonic
  deadlines. Operation correlations belong to core/backend/boundary machinery,
  not caller-supplied transaction identifiers.
- Scan and notification streams declare item and byte capacities, overflow
  policy, counters, terminal notices, ordering, and idempotent cleanup. No
  public callback or async stream is unbounded.
- Public bytes are `Uint8Array` snapshots with documented ownership. Normal BLE
  payloads are never text encoded, and neither numeric native handles nor
  public transaction identifiers exist.

Product device choice, reconnect policy, profile protocol, UI state, storage,
telemetry, and framework bindings remain outside this package. The public API
does not expose a product name, vendor profile, or health-domain policy.

## Version, capabilities, and errors

Package semantic versioning is distinct from negotiated backend-contract,
capability-schema, event-schema, trace-format, native-protocol where applicable,
and IPC-protocol where applicable axes. The peer ranges must overlap before
radio work; unknown required fields or events fail `protocol.incompatible`; an
optional additive field may be ignored only when the negotiated schema says so.
There is no silent downgrade. Exact production types are in
[`primitives.ts`](../../src/backend-contract/primitives.ts).

Capabilities are runtime backend reports, not a static host matrix. Every
claim carries a state, typed implementation binding, limitations, limits, TCK
binding, and evidence reference as decided by the capability ADR. All failures
use the stable dotted taxonomy in Semantics Section 15; in particular cancellation
is `operation.aborted`, deadline expiry is `operation.timed-out`, and a stale
resource is not converted to an empty result or busy error.

## Security and privacy invariants

The public API treats peers, advertisements, GATT values, adapter callbacks,
and backend events as untrusted. It reveals owned byte snapshots only through
bounded streams and redacts identifiers, payloads, and platform messages from
diagnostics by default. A BLE identifier is never authentication. No network,
telemetry, or diagnostic export occurs without an explicit caller action.

## Export and native-artifact impact

The root exports no host implementation or native binary. Explicit host
subpaths own their loading and may not change public manager/handle, generation,
stream, cancellation, or error semantics. Native and IPC artifacts must be
provenance-checked, ABI-bound, and fail closed; package inventory excludes the
draft fixture, legacy path, mock success path, and unapproved binary.

## Rejected alternatives

- Retaining or wrapping the old manager/port API, parallel byte methods, or a
  compatibility facade: rejected because the package has no production users and
  each path would split semantics and TCK coverage.
- Root-level host auto-detection or a singleton manager: rejected because it
  leaks host dependencies and hides physical-radio ownership.
- Static capability guesses, fake results, or automatic reconnect: rejected
  because they misstate runtime truth or impose product policy.
- UUID-first lookup or global numeric handles: rejected because repeated UUIDs
  and generation changes make them unsafe.

## Consequences and gates

The public examples must compile under strict Bundler, Node16, and NodeNext
resolution using the declaration fixture now and the actual exports at G2.
The deterministic vertical slice proves scan, connect, discovery, read,
notification, cancellation, deadline, overflow, generation invalidation,
two-client arbitration, and zero cleanup counters. Public helpers may only
compose these primitives; they may not weaken ownership, error, cancellation,
or cleanup rules.

At G1, `spikes/draft-contract/` was deleted. The package validator proves that
no draft source is exported, packaged, or selected by production TypeScript
configuration. The deleted ADRs are historical source characterization only and
cannot be cited as an API or migration authority.

## Related decisions

- [Backend contract ADR](2026-07-4.0-backend-contract.md)
- [Capability registry ADR](2026-07-4.0-capability-registry.md)
- [Boundary ADR](2026-07-4.0-boundary.md)
- [Packaging ADR](2026-07-4.0-packaging.md)
