<!-- docs/ADR/2026-07-4.0-capability-registry.md -->

# ADR: Unified BLE 4.0 capability registry and evidence binding

**Status:** Accepted design baseline
**Date:** 2026-07-25

## Decision

Runtime backend-reported capabilities are the only capability authority. A
capability registration binds a stable namespaced ID, exact state, typed local
implementation, machine-readable limits and limitations, immutable evidence
reference, negotiated capability-schema range, and required TCK profile in one
record. The executable production shape is
[`capabilities.ts`](../../src/backend-contract/capabilities.ts) and
[`backend-sdk-authoring.ts`](../../src/backend-sdk-authoring.ts).

The four states are exactly `supported`, `limited`, `unsupported`, and
`unavailable`:

- `supported` means all stated guarantees are implemented and carry the required
  proof level.
- `limited` means the operation exists but names its missing guarantee and
  applicable bounds.
- `unsupported` means the implementation cannot provide the operation.
- `unavailable` means it might exist but is not usable or safely measurable now.

Neither a state nor an evidence label is encoded as an empty result, a boolean,
or a host-name inference.

## Registration, negotiation, and skew

Feature IDs are namespaced strings. Built-in IDs have one registry authority;
third-party authors register their own namespace without editing a closed
platform union. A descriptor can cross a native or IPC boundary, but an
implementation cannot: a remote descriptor becomes invocable only when its
negotiated local typed proxy binding exists.

Capability-schema version is independent from backend-contract, event-schema,
trace-format, native-protocol, and IPC-protocol versions. Negotiation occurs
before radio admission. An unknown required feature, required field, or
incompatible range fails `protocol.incompatible`; an unknown optional field is
ignored only under the negotiated schema's declared rule. There is no implicit
downgrade or support promotion.

Every descriptor reports bounded values for the feature's relevant byte,
queue, subscription, scan, cancellation, database-change, background,
security, MTU, RSSI, restoration, and transport constraints. A missing safe
bound makes the feature unavailable rather than infinite.

## Lifecycle, ownership, streams, and errors

Feature invocation uses the same manager ownership, attachment/generation,
`AbortSignal`, deadline, opaque backend-correlation, resource-counter, and
bounded-stream rules as core GATT operations. A feature may not cancel another
client's work, extend a lease, reopen a stale resource, or retain an unbounded
listener. Its errors use the shared dotted taxonomy, especially
`capability.unsupported`, `capability.unavailable`, and `capability.limited`.

Feature implementations receive owned/borrowed bytes only through the boundary
rules, expose redacted diagnostics, and cannot smuggle host handles or product
policy into their limits. A provider never assumes that a feature is present
because an operating-system family normally has it.

## Evidence and security

An evidence reference identifies immutable receipt, source/artifact binding,
implementation version, scenario IDs, proof label, and limitations. A mock,
benchmark, compile result, fixed-function peripheral, or historical receipt
proves only its recorded scope. It cannot promote another backend/platform or a
new binary to `supported`. Missing proof leaves the exact feature unavailable
or limited with the missing guarantee named.

The registry is a security boundary: third-party descriptors, limits, evidence,
and TCK claims are validated as untrusted input. A backend author cannot gain a
support badge by returning a shell-command success, fake result, arbitrary
receipt, or static matrix. Sensitive details remain redacted from capability
diagnostics and evidence exports.

## Export and native-artifact impact

The public root may describe a feature result but cannot import a host-specific
implementation. Typed implementations and TCK registration belong to the
explicit backend SDK; serializable descriptors cross native/IPC only after
schema negotiation. A packaged native artifact, successful load, or compile is
evidence of neither a feature state nor a lifecycle-safe implementation.

## Rejected alternatives

- Static capabilities by host, import path, device name, or implementation
  label: rejected because they lose runtime truth and encourage fake support.
- Optional methods with casts, placeholder implementations, or declared
  capability without a TCK-bound implementation: rejected because callers
  cannot distinguish omission from a failed safety guarantee.
- Free-form notes as the only limitation language: rejected because policy,
  validation, and documentation need machine-readable codes.

## Consequences and gates

The capability reference and TCK profile list are generated or mechanically
checked from the registry authority. Contract tests must reject a registration
without implementation/TCK/evidence, invalid range, unknown required feature,
unbounded limit, or false promotion. Public examples inspect runtime state and
handle each non-supported state honestly.

The draft registration declarations were deleted at G1 promotion; production
code has one registry authority. Before a release claim, scenario receipts and
artifact provenance must validate at the declared evidence level. Hardware
absence blocks only the affected evidence label, never the deterministic
contract or unrelated backend work.

## Related decisions

- [Backend contract ADR](2026-07-4.0-backend-contract.md)
- [Boundary ADR](2026-07-4.0-boundary.md)
- [Open-source governance ADR](2026-07-4.0-open-source-governance.md)
