# Local profile contract — draft 0.3

This is **inside the owner's credential system**, not a replacement for NFC, an
Internet API or a protocol a legacy organisation must implement. The contract can
be function calls in one app or authenticated commands between an owner management
app and a programmable token. Use a separate reviewed management-channel binding
when it crosses devices; no transport/framing/encryption is prescribed here.

## Command vocabulary

| Command | Arguments | Result/effect |
|---|---|---|
| `PROFILE.IMPORT` | local profile reference, validated descriptor | `configured`; not remotely registered |
| `PROFILE.SELECT` | profile reference | selected for the next presentation; no implied provider identity verification |
| `PROFILE.PRESENT` | selected profile reference | exact approved representation or a driver-managed presentation handle |
| `PROFILE.SUSPEND` | profile reference | local use stopped; lost media/copies are unaffected |
| `PROFILE.REMOVE` | profile reference | local profile removed; provider state is unchanged/unknown |

Local errors include `OWNER_REQUIRED`, `UNSUPPORTED_DRIVER`, `UNSUPPORTED_FORMAT`,
`UNKNOWN_PROFILE`, `SELECT_FIRST`, `SUSPENDED`, `NOT_APPROVED` and `LIMIT_EXCEEDED`.
Implementations must fail closed. Names are semantic; the reference raises errors
rather than assigning numeric error codes or pretending these are NFC status words.

The reference `ProfileManager` classes in `examples/python/legacy.py` and
`examples/javascript/legacy.mjs` exercise the five local operations. They store
state only in memory and return a representation to a hypothetical renderer/driver.
They deliberately never mark remote state active/revoked on their own.

## Local state is not remote state

```
configured --select/present--> configured (with a current selection)
configured --suspend---------> suspended
configured or suspended --remove--> removed
```

Actual presentation is an event, not a new remote lifecycle state. These transitions
say nothing about whether the organisation has registered the value or disabled it.
Maintain a separate remote journal: not-confirmed, registration-confirmed,
revocation-pending, revocation-confirmed, retraction-pending, retraction-confirmed.
Only a verified native result or an independently recorded legacy confirmation can
advance that journal. Provider receipts are not inferred from local command success.
The minimal local reference returns `not-confirmed`; it is not a complete journal.

## Profile contents and constraints

Store the selected provider label, mode, carrier capability, allowed format, exact
accepted value (or protected key handle), provenance, selection rule and local
policy. For native profiles also store the authenticated provider binding,
relationship reference, per-device credential ID and key reference. Never send the
whole local profile to an unaware reader. Never share a global root identifier as
an implicit side effect of selecting a relationship.

A profile can choose Code 128 and the string `00481720`; the display renderer owns
barcode generation. An NFC profile can select a legitimately provisioned existing
application; its driver owns the standard/proprietary state machine. A profile
cannot supply missing issuer secrets or tell an ordinary card to rewrite its UID.
Hardware UID should be treated as an observed capability/constraint, not a mutable
configuration field. Only an approved driver can declare an operation available.

Authenticate the owner management session before accepting imports, activation,
key changes or deletion. Do not accept a claimed `ownerVerified` bit from a request.
The references take that decision as an explicitly trusted local adapter input.
Bound profile sizes, allowed drivers, formats and key operations. Imported JSON is
data, not executable code. Existing private keys stay behind their key handles.

## The selection problem

Ordinary optical scanners and many legacy card readers do not supply authenticated
provider identity. The owner usually chooses the intended profile. A shared AID,
reader label or an unauthenticated `provider=library` string is not adequate proof.
Authenticated native context can select a relationship; UID-only legacy use may
necessarily expose one shared physical identifier.

## What does the organisation see?

For C0/C1, only what its existing standard/application already expects: a barcode
value, QR payload, UID or authorised NFC application exchange. There is no new
BringPass `REGISTER`/`REVOKE` command. Registration/revocation still happen through
the provider's existing desk/portal controls, if they exist. That is the precise
boundary between token-side adaptation and provider integration.

See `protocol.md` for native commands, required checks and all lifecycle ceremonies.
