# Changelog

All notable changes to `tibet-iddrop` are documented here.

## [0.3.0] — 2026-05-17 (middag)

### Added — live AINS coupling + family-recovery demo

- **`/api/ains/identity/{aint}` endpoint** (brain-api, public):
  returns only the Ed25519 `verifying_key` for an aint — narrower
  surface than `/resolve`, no metadata leakage. Public by design:
  verifying keys are public by definition.
- **`HttpAinsResolver` now uses `/identity` first** with fallback to
  `/resolve`. Verified live against `jasper.aint`, `root_idd.aint`,
  `storm.vandemeent.aint`.
- **`normalize_pubkey` helper** accepts both hex and base64 (with or
  without padding, URL-safe or standard), normalises to lowercase hex.
  Storm's birth-key was base64, root_idd's is hex — both now work.

### Added — i-poll auto-register

- `IpollTransport.try_register()` posts to `/api/ipoll/register` with
  the local `from_agent` identifier and returns the brain-api response.
- `IpollTransport.send()` retries once after self-register when the
  initial push returns 401/403. Sandbox-tier agents can immediately
  reach `echo / ping / help`; broader reach needs human approval.

### Added — Storm.aint family-recovery demo

`examples/storm_recovery_demo.sh` — end-to-end script demonstrating the
full L1+L2+L3 stack for parent → child sub-identity recovery on a fresh
device. Seven steps, all green:
1. Keypairs (Papa + Storm-new-device)
2. Generate `storm.vandemeent.aint` sub-identity
3. Build payload (`identity.json` + key material)
4. TBZ v2 seal `--type identity` to Storm-new-device pubkey
5. Drop in iddrop inbox via HTTP
6. Storm pulls + unseals on new device
7. Negative: attacker key correctly rejected (AEAD fail)

Family hierarchy: `vandemeent.aint` (koepel) →
`{jasper, storm}.vandemeent.aint` → Storm's own
`{claude, knuffel}.storm.aint` sub-claims.

### Added — QR offer helpers (proximity without NFC)

- **`iddrop offer-qr`** — emits a compact `iddrop://<base64>` URL of
  an Offer JSON. ~427 chars, scannable as QR by any reader.
  `--format raw` pipes into `qrencode`; `--format json` for debug.
- **`iddrop offer-decode`** — reverse: takes an `iddrop://...` URL or
  bare base64, returns the Offer JSON. Verified roundtrip identical.

### Compatibility / breaking changes

- **`ReceiverContext.ains_resolve` was a closure in v0.1 → now `ains_resolver: &dyn AinsResolver` (v0.2+)**. Same context struct can
  hold live or stubbed resolvers via trait-object dispatch. Existing
  callers updated; integration tests unchanged in behaviour.

### Tests

- 8 validator integration tests (unchanged from 0.2) all green
- 3 ains unit tests including base64↔hex normalisation

## [0.2.0] — 2026-05-17

### Added — wire layer + AINS coupling

- **Brain-api inbox endpoints** (Python, `brain_api/iddrop_endpoints.py`):
  - `POST /api/iddrop/inbox/{aint}` — accept opaque sealed envelope
  - `GET  /api/iddrop/inbox/{aint}/pull` — fetch + delete oldest (204 if empty)
  - `GET  /api/iddrop/inbox/{aint}/peek` — list pending without consuming
  - `GET  /api/iddrop/health` — service health
  - File-backed FIFO queue under `/var/lib/tibet/iddrop` (configurable
    via `$IDDROP_INBOX_ROOT`). Bytes are opaque to server; TBZ v2
    seals end-to-end.
  - Whitelisted as `public` in Fort Knox: TBZ v2 self-authenticates.

- **AINS resolver** Rust client (`ains.rs`):
  - `AinsResolver` trait — pluggable lookup interface
  - `HttpAinsResolver` — live calls to `GET /api/ains/resolve/{aint}`
    on brain-api
  - `StubAinsResolver` — in-memory map for tests + offline demos

- **Validator now uses `AinsResolver` trait** instead of a closure
  (= same context type can hold live or stubbed resolvers; trait-object
  dispatch).

- **CLI subcommands**:
  - `iddrop send --to <aint> <bundle> [--url ... | --via-ipoll]`
  - `iddrop pull --as <aint> [--output ... | --url ... | --via-ipoll]`

- **Transport adapters**:
  - `HttpTransport` — direct brain-api inbox (working)
  - `IpollTransport` — wraps i-poll push/pull as alternative wire path
    (= code-complete; live use requires agent registration via
    `/api/ipoll/register` in v0.3 polish)

### Verified end-to-end

```
[secret.txt]
  ↓ L1+L2: tibet-zip pack --seal --to <bob_pub> --type identity
  → sealed.aint (1437 bytes, AES-256-GCM, identity-class)
  ↓ L3: iddrop send → POST /api/iddrop/inbox/bob
  [brain-api inbox queue]
  ↓ L3: iddrop pull → GET /api/iddrop/inbox/bob/pull
  → pulled.aint (byte-identical sha256)
  ↓ L1+L2: tibet-zip unpack --as bob.priv
[secret.txt] ✓ identical
```

### Tests

- 8 validator integration tests (= unchanged from 0.1) all green
- 3 new unit tests in `ains` module (canonical, stub hit/miss, lowercase)

## [0.1.0] — 2026-05-17 (earlier today)

### Added — protocol skeleton

- Types `Offer` (§5.1), `Request` (§5.2), `IdClaim` (§5.3) matching
  `draft-vandemeent-iddrop-00` Appendix A
- `ChallengeNonce` 16-byte nonce
- `SemanticType` enum (identity / capability / credential / sub-identity)
- Six-rule validator (§8.1-§8.6) as one pipeline
- Transport trait + HTTP stub + NFC/QR placeholder
- CLI: `spec`, `offer`, `request`, `inspect`, `validate`, `accept`
- 8/8 integration tests pass
- Use case #1 framed: trust-kernel handoff (KIT app)
