# Changelog

All notable changes to `@aithos/sdk` will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Starting with `0.2.0`, this project follows semantic versioning on the `0.x`
line: a breaking change bumps the minor version (e.g. `0.2.0` → `0.3.0`) and
non-breaking additions bump the patch. The earlier `0.1.0-alpha.*` releases
were pre-releases and could break public API between any two alphas.

## [0.2.1] — 2026-07-02

Corrective patch release — no API changes.

- **fix:** ship `@aithos/assets-crypto` as a runtime **dependency** (was a
  `peerDependency`, so `npm i @aithos/sdk` alone crashed at runtime with
  `ERR_MODULE_NOT_FOUND … @aithos/assets-crypto imported from
  @aithos/sdk/dist/src/assets.js`). `@aithos/protocol-client` stays dual-declared
  (dependency + peer) so a standalone install works while an app that depends on
  it directly still dedupes to one copy. `npm i @aithos/sdk` now runs standalone.
- **fix:** `auth.importMandate` parses the cumulative v2 bundle pack
  (`"aithos-mandate-pack": "2"`) emitted by `sdk.mandates.createBundle` — it
  registers every mandate in the pack so the grantee can act on all covered
  zones. The legacy `aithos_delegate_version: "0.1.0"` single-mandate bundle is
  still accepted unchanged.
- **fix:** publish only `dist/src` via `files[]` — the tarball no longer ships
  `dist/test/**` (WIP e2e tests + a harness referencing the removed
  `setEthosV04OptIn`).

## [0.2.0] — 2026-07-01

**BREAKING — the SDK is now latest-format (v0.4) only.**

- Authoring is always v0.4: `EthosClient.publish()` / `ensureInitialized()` create
  a subject's first edition directly in the content-addressed v0.4 format
  (`createEditionV04Owner`). Brand-new subjects are born v0.4, never v0.3.
- Legacy formats are refused: any read or write on a v0.3/v0.2 Ethos throws a new
  `EthosMigrationRequiredError` (`code: "ethos_migration_required"`) whose message
  points to `https://app.aithos.be` to migrate. The migration engine
  (`migrateToV04()`) is the only reader still allowed to touch the legacy source.
- v0.2 is a hard error — it is no longer auto-migrated to v0.3.
- Key rotation refuses a legacy subject when migration is explicitly disabled
  (`migrateToV04: false`) instead of re-authoring v0.3.
- Removed the deprecated no-op `setEthosV04OptIn` export.
- Bumped `@aithos/protocol-client` to `^0.1.0-alpha.41` (ships `createEditionV04Owner`).

## [0.1.0-alpha.99] — 2026-06-29

- FIX `rotateEthos` — the POST-ROTATION re-seal write now retries through the
  did.json propagation window. After `rotate_sphere_key` commits the new did.json,
  the re-seal (signed with the NEW keys) is verified against it — but the rotated
  did.json takes up to the CDN TTL (~60s) to reach the write-verification path, so
  an immediate re-seal could fail with -32011 "Ed25519 signature invalid". When it
  did, the account was left HALF-ROTATED (did.json on new keys, edition still
  sealed to the old keys) and the caller never received the new recovery → the
  account became unreadable with either key set. The re-seal now retries on -32011
  (and -32030) for ~75s with progress callbacks, so it lands before `rotateEthos`
  returns — the account is never left inconsistent and the new recovery is always
  delivered. Affects both the v0.4 re-seal and the v0.3 republish paths.

## [0.1.0-alpha.98] — 2026-06-29

- NEW `rotateEthos` **migrates a non-v0.4 subject to v0.4 by default** as part of
  the key rotation (`RotateEthosOptions.migrateToV04`, default `true`;
  `RotateEthosResult.migratedToV04`). The migration is authored UNDER THE OLD
  keys (a height N+1 v0.4 edition) BEFORE `rotate_sphere_key`, then the rotation
  re-seals the zone keys to the NEW keys — reusing only tested primitives and
  sidestepping the (removed) auto-migrate-on-publish path. Pass
  `migrateToV04: false` to keep the legacy format-preserving behaviour. Publishes
  during rotation now retry once on a height conflict (-32030).
  NOTE: authenticated v0.4 reads can transiently fail with -32011 for up to the
  did.json CDN TTL (~60 s) right after rotation; callers should retry reads.

## [0.1.0-alpha.97] — 2026-06-29

- CHANGE decouple the v0.3→v0.4 migration from `publish()`. `publish()` never
  migrates a subject; migration is an explicit, owner-only action
  (`ethos.migrateToV04()`). `setEthosV04OptIn` / `AithosSDKConfig.ethosV04` are
  now deprecated no-ops. A profile save can no longer be blocked by — or lost to
  — a migration.

## [0.1.0-alpha.96] — 2026-06-18

- FIX `rotateEthos` ordering — read + decrypt the ethos edition under the OLD
  keys BEFORE rotating the did.json, then republish under the NEW keys after.
  The authenticated v0.4 read (`get_ethos_objects` with a sphere-key envelope)
  only verifies against the currently-published did.json; doing it after
  `rotate_sphere_key` failed with -32011 `Ed25519 signature invalid`. The data
  re-key is root-signed (root never rotates) so its ordering is unchanged.

## [0.1.0-alpha.95] — 2026-06-17

- FIX `rotateEthos` — the circle/self zone recopy now uses the v0.4 authenticated
  read/write path (`loadEthosIndexV04` opens the zone keys under the OLD owner via
  a signed `_envelope`; `patchEditionV04Owner({ rotate })` seals a fresh zone key
  to the NEW sphere keys and rewraps every section DEK). The old v0.3 reader did
  an UNSIGNED encrypted read which the live v0.4 platform rejects with
  `encrypted read requires a signed envelope`, so rotating an account with
  circle/self content failed. Active whole-zone delegates are carried forward
  (best-effort). Legacy v0.3 editions keep the previous path.

## [0.1.0-alpha.94] — 2026-06-17

- NEW `auth.signEnvelopeAsDelegate({ aud, method, params, mandateId|subjectDid, ttlSeconds? })`
  — sign a delegate-path envelope for an EXTERNAL endpoint with no owner session
  (broker / custody hosts). Resolves the delegate from the imported-mandate
  registry; attaches the SignedMandate; `iss` = subject DID. 4 unit tests.

## [Unreleased]

## [0.1.0-alpha.93] — 2026-06-16

Cumulative multi-zone mandates.

### Added

- **`sdk.mandates.createBundle(...)`** — grant a cumulative multi-zone mandate
  in one call. Every granted zone is sealed to a single shared grantee key, so
  the grantee holds one key that opens all of the zones covered by the bundle
  (instead of one mandate/key pair per zone).
- **`sdk.mandates.revokeByGrantee(...)`** — one-shot revocation of a cumulative
  bundle by its shared grantee, cutting off the whole grant at once.

## [0.1.0-alpha.92] — 2026-06-11

Supersedes the never-published alpha.91 (its release tag was misplaced on
the alpha.90 commit). Same PDS data fixes:

PDS (data sub-protocol) correctness fixes — surfaced by the new live
data/assets e2e suites (`test/e2e/*-data-*`, `*-assets-*`, `_data.ts`):

- **Tag filters now narrow.** `list({ filter: { tagsAny | tagsAll } })`
  is mapped to the server's snake_case `tags_any` / `tags_all`. Before,
  the camelCase keys were forwarded verbatim and silently ignored by the
  PDS, so a tag filter returned EVERY record. `equals`/`contains`/`range`
  were unaffected.
- **Delegate `listCollections()` works.** Synthesized client-side from
  the mandate scopes (the server's `list_collections` is owner-only).
  Returns exactly the scope-covered collections; throws on a
  wildcard-only `data.*.*` mandate.
- **Delegate `listGammaEntries()` fails fast.** Owner-only; a delegate
  now gets a clear client-side error (`data_delegate_owner_only`) instead
  of a bare server `-32042`.

No wire-format change; owner CRUD/assets paths unchanged.

## [0.1.0-alpha.88] — 2026-06-11

P6 incarnation (@aithos/agent-tools ^0.5.0, @aithos/mcp ^0.13.0):

- `sdk.agent.run` now serves `ethos_introduce` (third-party narration,
  public-only structurally) and `agent_briefing` (mandate + voice +
  context pack in one call) — both added to HOST_TOOLS.
- `SdkStorage` declares the `selfSigningWrites` capability marker
  (formalized in @aithos/mcp 0.13): identity-less self-signed writes are
  now an explicit contract instead of a convention.
- Fixed stale `VERSION` export (was alpha.57) and added a guard test so
  it can never drift from package.json again.

## [0.1.0-alpha.87] — 2026-06-10

Phase P4 of PLAN-MCP-UNIFICATION-2026-06: **the living mandate** in-process.
Details: `HANDOFF-MCP-P4.md`.

### Added

- **`mandate_describe` / `ethos_preflight_write`** served by
  `sdk.agent.run()` on every session: the agent learns its authority
  (mandate id, scopes, validity window, live status, the EXACT served tool
  set) and pre-flights writes without executing. Delegate sessions pass the
  FULL signed mandate document to the in-process server (new internal
  `ComputeNamespace._delegateMandateForSubject`).
- **Session-mandate liveness (T6)** — even without a host delegate key (the
  SDK self-signing shape), an expired/revoked session mandate refuses at
  stage time AND at commit; zero writes (server-side check, SDK-level test).

### Dependencies

- `@aithos/mcp` ^0.12.0 (living mandate + mandate pack),
  `@aithos/agent-tools` ^0.4.0.

## [0.1.0-alpha.86] — 2026-06-10

Phase P3 of PLAN-MCP-UNIFICATION-2026-06: **contextualization** — the M2
pitch ("Q&A contextualisée à coût minimal"). Details: `HANDOFF-MCP-P3.md`.

### Added

- **`ethos_search`** and **`ethos_context_pack`** served by `sdk.agent.run()`
  (read family, kept under `readOnly`): keyword search over readable
  sections and a budgeted, zero-inference context pack (guidance → pinned →
  task matches, deduplicated, truncated to `budget_tokens`). Both bounded
  by the mandate's read scopes — explicit zone requests cannot escape them
  (T12/T14 at SDK level).
- `ethos_list_sections` rows carry `approx_size_bytes` / `est_tokens` when
  the backend can provide them (filesystem hosts; the platform index does
  not yet).
- `ethos_diff_since` is exposure-allowed but registers only once SdkStorage
  gains the platform edition-history capability (`readManifestAt`, P5).

### Dependencies

- `@aithos/mcp` ^0.11.0, `@aithos/agent-tools` ^0.3.0; `@aithos/protocol-core`
  0.10.3 resolves in-range.

## [0.1.0-alpha.85] — 2026-06-10

Phase P2 of PLAN-MCP-UNIFICATION-2026-06: **transactional editing (D3).**
Details: `HANDOFF-MCP-P2.md`.

### Changed (breaking)

- **`sdk.agent.run()` is transactional by default.** Writes stage on the
  EthosClient buffer through the in-process MCP server (`@aithos/mcp`
  0.10.0); the model seals them with `ethos_commit` — ONE edition, one
  batched gamma anchor, one debit-relevant publish (T13). `ethos_discard`
  (or a run ending without commit) publishes NOTHING (T13b). Reads stay
  persisted-only while staging. Opt out with `autoCommit: true` (per-write
  editions, the trio not served). `runConversationLocal` keeps its
  historical per-write contract by forcing `autoCommit: true`.

### Added

- **`SdkStorage.applyEdits`** — the `AithosStorage` transactional
  capability: net-composes the batch (modify-after-add composes,
  delete-after-add cancels, last upsert wins) onto the EthosClient staging
  buffer and publishes ONCE.
- **`ethos_append_section`** served in both modes (journal pattern;
  composes over staged state).

### Dependencies

- `@aithos/mcp` ^0.10.0 (transactional server), `@aithos/agent-tools`
  ^0.2.0 (the trio's canonical specs); `@aithos/protocol-core` 0.10.2
  resolves in-range (`applyEdits` types).

## [0.1.0-alpha.84] — 2026-06-10

Phase P1 of PLAN-MCP-UNIFICATION-2026-06: **the SDK becomes an MCP host.**
The client-side agent loop now gets its tools from a REAL Aithos MCP server
running in-process (`@aithos/mcp` isomorphic core over `InMemoryTransport`),
with the canonical `@aithos/agent-tools` catalogue — one tool surface across
CLI, SDK, and platform. Details: `HANDOFF-MCP-P1.md`.

### Added

- **`sdk.agent.run()`** (P1.2) — agentic conversation with per-turn proxy
  inference (signing/billing/idempotency unchanged) and MCP-native tool
  dispatch: `tools/list` → Bedrock specs (passthrough JSON Schema),
  `tool_use` → `client.callTool()`, `CallToolResult.isError` →
  `tool_result.is_error`. Supports `readOnly`, tool filtering, a
  `dataProvider` (serves the canonical `data_query` on demand), and
  **additional MCP servers** (`mcpServers: [{ name, client }]`, U13) whose
  tools join the same loop (first-registered wins name collisions).
- **`SdkStorage`** (P1.1) — `AithosStorage` adapter over `EthosClient`:
  reads = lazy index snapshot + per-section fetch decrypted with the
  session keys; writes = stage + publish (one edition per write tool, same
  granularity as before — transactional `ethos_commit` lands in P2). The
  narrow `SdkStorageHost` slice keeps it fake-able (T11 runs the real MCP
  server with zero network/crypto).
- **Delegate exposure** — `createServer({ mandate: { scopes } })`: a
  delegate session's `tools/list` is filtered by its mandate scopes
  (`toolsForScopes`), on top of the EthosClient's per-operation
  enforcement at staging/publish (defense in depth preserved).
- `check:browser` gate — esbuild `--platform=browser` bundle of the agent
  surface must stay free of node builtins (measured 2026-06-10: 212 KB gz
  for AgentNamespace + MCP server/client + EthosClient path).

### Changed

- **`ComputeNamespace` shares the SDK's `EthosNamespace`** (P1.3) — single
  per-subject staging buffer across the whole SDK; the compute-private
  duplicate is gone (deps accept `ethos`; `AithosSDK` injects it).
- **`compute.runConversationLocal()` is deprecated** (P1.4) — now a thin
  wrapper over `sdk.agent.run()` (stderr notice once per process). The tool
  surface it forwards is the canonical MCP host surface: `identity_list`,
  `identity_describe`, `ethos_list_sections`, `ethos_read_section`,
  `ethos_read_sections` + the three writes; `data_query` only when a
  `dataProvider` is wired.

### Removed (breaking)

- **`dispatchAgentToolLocal` / `AgentDispatchContext`** (`agent-dispatch.ts`)
  — the in-process MCP server (SdkStorage handlers) IS the dispatch now.
- The SDK-owned tool catalogue: `agent-tools.ts` is a deprecated shim
  DERIVED from `@aithos/agent-tools` (same six legacy names, canonical
  normative descriptions); removal in 0.2.0. `DataProvider` moved to
  `compute.ts`.

### Dependencies

- `@aithos/agent-tools` ^0.1.0, `@aithos/mcp` ^0.9.1 (isomorphic core +
  exports fix), `@modelcontextprotocol/sdk` ^1.29.0, `zod` ^3.25.0 (new);
  `esbuild` ^0.28.0 (dev).

## [0.1.0-alpha.71] — 2026-06-09

### Fixed

- **Owner `#data` sphere publish self-heal.** Owner data/asset writes are signed
  under the dedicated `#data` sphere, but the PDS resolves the issuer DID
  document from the registry's published `did.json`. An account whose published
  doc predates the `#data` sphere (created/published before it landed) lists only
  `#root/#public/#circle/#self`, so every `#data`-signed write failed with
  `-32011 proof.verificationMethod <did>#data not found in issuer DID document`.
  A plain `publish_identity` republish can't fix it (the server rejects a
  "different did.json"), which is why a simple re-sign-in didn't help. The SDK
  now detects a published doc missing `#data` and additively re-publishes it via
  the idempotent `aithos.augment_identity` primitive:
  - New public `auth.ensureOwnerDataPublished()` — ensures the owner's published
    `did.json` carries `#data` (no-op when already present; throws
    `auth_no_data_sphere` for legacy owners without the seed). Idempotent and
    cached per session.
  - Runs automatically (best-effort, never blocks auth) on `resume` and the
    existing-account sign-in flows (`signIn`, `signInCustodial`,
    `signInWithRecovery`). New accounts already publish `#data` at sign-up, so
    this is a no-op for them.

## [0.1.0-alpha.64] — 2026-06-08

### Added

- **Transparent owner migration of legacy v0.2 ethoses to v0.3.** When an
  owner's `EthosClient` loads a subject still on the v0.2 monolithic edition,
  the SDK now reads the v0.2 content with the owner keys and re-publishes it as a
  fresh v0.3 edition under the hood, then continues — no app call required. Runs
  once (the next load succeeds as v0.3). A delegate or anonymous reader that hits
  a still-v0.2 ethos gets a clear `ethos_v02_needs_owner_migration` error (they
  hold no write keys, so only the owner can migrate).

### Changed

- Requires `@aithos/protocol-client` `^0.1.0-alpha.23` (peer + dev): onboarding
  now mints v0.3, and `loadEthosV03` tags its legacy-edition error so the SDK can
  detect + migrate.

## [0.1.0-alpha.63] — 2026-06-08

### Fixed

- **Export `SectionIndexEntry` from the package root.** The type was added to
  `ethos.ts` in alpha.62 (the return shape of `EthosZone.index()`) but not
  re-exported from `index.ts`, so consumers couldn't name it. Now exported
  alongside the other ethos types.

## [0.1.0-alpha.62] — 2026-06-08

### Added

- **`EthosZone.index()` — capability-annotated section index.** Returns every
  persisted section in a zone (including ones the actor can't decrypt) as
  `SectionIndexEntry { id, title?, tags?, readable, writable }`, so a UI can show
  inaccessible sections (e.g. greyed/locked) rather than silently dropping them
  the way `sections()` does. `readable` = the section appears in `sections()`
  (decryptable); `writable` = owner always, delegate iff its verb-scopes cover
  the `edit` op on `{id, tags}` (via `coversOperation`). For `self`, sealed
  sections the actor can't open come back without a `title` (the `self` index is
  encrypted); `public`/`circle` always carry the clear title. Reads the cached
  v0.3 snapshot — no extra network, no protocol-client change.

## [0.1.0-alpha.61] — 2026-06-08

### Changed

- **`EthosClient` now rides the v0.3 per-section transport.** The namespace
  switched off the legacy v0.2 monolithic path (`loadEditSnapshot` /
  `publishZoneEdit` / `publishPublic|PrivateZoneAsDelegate`) onto `loadEthosV03`
  + `publishEthosEditionV03Owner` / `publishEthosEditionV03Delegate`. The public
  DX is unchanged (`me()`/`of()`, `zone().addSection`/`updateSection`/
  `deleteSection`, `sections()`, `publish()`, `ensureInitialized()`,
  `findSectionsByTitle` …) — only the transport under the hood moved, and the
  first edition is now authored through the same v0.3 path.
- **Delegate publish collapses to a single edition.** A delegate authors exactly
  its mandate's `actor_sphere` (`circle`/`self`) via one
  `publishEthosEditionV03Delegate`; every other section/zone carries forward
  verbatim, so a delegate can no longer overwrite content it can't read (the old
  `ethos_delegate_cannot_overwrite_unreadable` guard is now structural). The SDK
  keeps a coarse same-sphere check (`ethos_delegate_zone_forbidden`,
  `ethos_delegate_unsupported_sphere`); per-section verb-scopes are enforced
  server-side and surfaced via `data.code` (`-32014`). A delegate snapshot
  decrypts only the sections sealed to it.
- Deps: `@aithos/protocol-client` → `^0.1.0-alpha.22` (peer + dev); dev
  `@aithos/protocol-core` aligned to `>=0.9.0 <0.10.0` (matches the peer range).

## [0.1.0-alpha.60] — 2026-06-07

### Changed

- **Ethos verb-scopes (draft `bundle-v0.3-section-verb-scopes.md`).** The
  `Scope` type widens to the verb grammar so a caller can mint section-scoped
  mandates: `EthosScope = ` `` `ethos.${EthosVerb}.${EthosZone}` `` (+ a
  `#${selector}` form for `#id=` / `#prefix=` / `#tag=`, plus `ethos.read.all`),
  with `EthosVerb = read | edit | append | delete | write`. Existing
  whole-zone scopes remain a subset, so current callers are unaffected. The
  runtime `compute.invoke` guard is unchanged.
- Deps: `@aithos/protocol-client` → `^0.1.0-alpha.21`, `@aithos/protocol-core`
  → `>=0.9.0 <0.10.0`.

## [0.1.0-alpha.59] — 2026-06-06

### Added

- **Dev-account endpoint config.** `AithosSdkEndpoints` gains `api` + `cdn`; new
  `DEV_SDK_ENDPOINTS` preset points every service (incl. the ethos api/cdn) at
  `*.dev.aithos.be`. The `AithosSDK` constructor now calls protocol-client's
  `configureEndpoints({ api, cdn })`, so a single `endpoints` config redirects
  the ethos reads/writes (previously hard-pinned to prod inside protocol-client).
  Usage: `new AithosSDK({ auth, appDid, endpoints: DEV_SDK_ENDPOINTS })`.

### Changed

- Requires `@aithos/protocol-client` `^0.1.0-alpha.20` (v0.3 per-section + public
  endpoint config) and `@aithos/protocol-core` `>=0.8.0 <0.9.0`.


## [0.1.0-alpha.57] — 2026-06-02

### Added

- **`auth.ownerDataClient({ pdsUrl?, schemas? })` (Phase D).** A ready-made
  owner data client bound to the signed-in account that signs + seals under the
  dedicated **`#data`** sphere. This is the ergonomic factory apps should use
  instead of hand-rolling `createDataClient` with a raw seed — hand-rolling with
  `#root` (as linkedone/delie did) is exactly what left collections sealed to
  the wrong sphere. Throws a clear error when no owner is signed in, or when the
  account has no `#data` sphere (legacy / 4-seed recovery → add one via
  `rotateEthos` first). Apps: replace `createDataClient({ sphereSeed: rootSeed,
  verificationMethod: ${did}#root })` with `auth.ownerDataClient({ schemas })`.

## [0.1.0-alpha.56] — 2026-06-02

### Changed

- **Reads no longer require a known schema (`data.ts`).** `_ensureCollection`
  used to throw when a collection's schema was neither bundled nor published —
  which blocked reading a migrated/rotated collection from any app that didn't
  ship that vendor schema. But `get`/`list` decrypt purely from the CMK + the
  stored metadata/payload; the schema is only needed to SPLIT on write. So a
  collection with an unknown schema is now fully READABLE (records decrypt),
  and only `insert`/`update` raise a precise "schema required to write" error.
  This is what makes a migrated collection directly usable under `#data` by any
  client, with no schema-publish step.
- The vendor-schema auto-resolve from alpha.55 still applies (it lets WRITES
  proceed without bundling the lite when the owner published the schema).

### Added

- `scripts/publish-schema.mjs` — publish a vendor JSON Schema to the PDS so any
  client can auto-resolve it for WRITES (reads never need it).

## [0.1.0-alpha.55] — 2026-06-02

### Added

- **Legacy `#data`-sphere migration (`src/migrate.ts`).** Onboard a pre-`#data`
  identity onto the dedicated `#data` sphere and re-key its collections:
  - `ensureDataSphere(recovery)` — generate a `#data` keypair (if absent) and
    re-publish the did.json with the appended `#data` + `#data-kex` entries
    (root-signed) via the additive `aithos.augment_identity` primitive.
  - `rekeyLegacyCollections(recovery, { mode })` — re-key each collection's CMK
    from its (auto-discovered) legacy sphere to `#data`. `mode: "replace"`
    swaps the owner wrap (clean cutover); `mode: "add"` keeps the legacy wrap
    and appends a `#data` wrap (dual-read, non-destructive).
  - `addDataSphereWrap(recovery)` — convenience wrapper for `mode: "add"`.
  - `migrateLegacyEthosToDataSphere(recovery)` — ensure + rekey in one call.
  - Guardrails: dry-run, `cmk_envelope` backup in the report, local round-trip
    verification before any server write, idempotence.
  - Operator scripts: `scripts/migrate-legacy-ethos.mjs`,
    `scripts/verify-data-sphere.mjs`, `scripts/diagnose-collection-keys.mjs`.

### Changed

- **Owner CMK reader tolerates multiple same-label wraps (`data.ts`).** A
  collection can carry more than one wrap under `${did}#data-kex` (e.g. after a
  dual-read migration: a legacy-sphere-sealed wrap kept for the old app plus a
  `#data`-sealed wrap). `_ensureCollection` now tries every matching wrap and
  keeps the one its key decrypts, instead of taking the first. Behaviour is
  unchanged for the common single-wrap case — this is what lets a `#data`
  client and a legacy (e.g. `#root`) client read the same collection during the
  strangler transition.

## [0.1.0-alpha.54] — 2026-06-01

### Added

- **`compute.runConversation` — agentic Bedrock loop with Aithos MCP tools.**
  A multi-turn tool-calling conversation that runs server-side in a single
  `aithos.compute_converse` POST and is billed once for the cumulative token
  usage. The model is given the Aithos MCP tools (`ethos_list_sections`,
  `ethos_read_section`, `data_query`) and reads the user's data from a
  client-decrypted working-set — the proxy never holds a standing decryption
  key. Same owner/delegate signer paths as `invokeBedrock`. New public types:
  `RunConversationArgs`, `RunConversationResult`, `ComputeWorkingSet`,
  `WorkingSetSection`, `ConverseToolCall`, `ConverseStopReason`.
- **`AithosSDK.buildWorkingSet(did, { zones })`** — decrypts the granted ethos
  zones client-side into the working-set shape consumed by `runConversation`.
  Zones the session can't read are skipped, so the set is naturally bounded by
  the mandate.

  Pairs with the compute-proxy `aithos.compute_converse` handler. Existing
  `invokeBedrock` tests stay green; 4 new wire-contract tests added
  (`test/converse.test.ts`).

## [0.1.0-alpha.53] — 2026-06-01

### Added

- **Owner `#data` sphere through the key material** (spec/data/02-key-hierarchy.md).
  Self-custody owners (`signUp` / `signIn`) now carry the dedicated `#data` sphere
  seed end-to-end so they can sign data/asset PDS envelopes under `#data` (root
  stays cold). Specifically:
  - The encrypted vault and the keystore (`StoredOwnerKeys.seedsHex`) persist an
    optional `data` seed; `signIn` rehydrates it from the decrypted vault.
  - The recovery file (`serializeRecoveryFile` / `parseRecoveryFile`) carries an
    optional `data` seed.
  - `OwnerSigners` holds an optional `#data` signer; crucially the vault
    re-encryption projection (`_unsafeStoredIdentity`) **preserves** it, so adding
    a delegate no longer risks dropping the data sphere. `signEnvelope({ sphere:
    "data" })` is now accepted.
  - Floors bumped: `@aithos/protocol-client` `^0.1.0-alpha.18` → `^0.1.0-alpha.19`
    (which generates `#data` in `createBrowserIdentity` + the DID document).

  **Backward compatible**: `#data` is optional everywhere — legacy owners, vaults,
  and recovery files (root/public/circle/self only) still load and sign data ops
  under `#root`. 212 existing + 5 new `#data` tests pass.

  **Not yet covered**: custodial accounts (`signUpCustodial` / `signInCustodial`)
  receive their seeds as a server-issued 128-byte bundle from `aithos-auth`; the
  `#data` sphere for custodial owners requires `aithos-auth` to issue a 5th seed
  (tracked separately).

## [0.1.0-alpha.52] — 2026-06-01

### Changed

- Raise dependency floors so consumers get a browser-safe protocol stack:
  `@aithos/protocol-core` `>=0.6.3 <0.7.0` → `>=0.6.5 <0.7.0`, and
  `@aithos/protocol-client` `^0.1.0-alpha.17` → `^0.1.0-alpha.18`. protocol-core
  0.6.5 makes the envelope verify/sign path bundleable and `Buffer`-free in the
  browser; protocol-client alpha.18 carries the matching floor. This prevents a
  browser app on this SDK from resolving a protocol-core that throws
  `node:`/`Buffer` errors at build or runtime. No SDK code change.

## [0.1.0-alpha.51] — 2026-06-01

### Fixed

- **Separate `assets` endpoint (the data and assets PDS are different API
  Gateways).** alpha.50 incorrectly defaulted `createAssetsClient` to the data
  PDS (`pds.aithos.be`). The assets sub-protocol is a distinct API Gateway with
  the same `/mcp/primitives/...` paths, so it needs its own host:
  `AithosSdkEndpoints.assets` (`DEFAULT_SDK_ENDPOINTS.assets =
  "https://assets.aithos.be"`). `createAssetsClient` now defaults to it.
  `createDataClient` still defaults to `pds.aithos.be`. Override either with an
  explicit `pdsUrl`.

  > Requires the `pds.aithos.be` and `assets.aithos.be` CloudFront distributions
  > to be live. Until then, keep passing the raw `execute-api` URLs explicitly.

## [0.1.0-alpha.50] — 2026-06-01

### Changed

- **`pdsUrl` is now centralized and optional.** The data/assets sub-protocol
  endpoint moves into the SDK endpoint config: `AithosSdkEndpoints.pds`
  (`DEFAULT_SDK_ENDPOINTS.pds = "https://pds.aithos.be"`). `createDataClient`,
  `createDelegateDataClient`, `createAppendDataClient` and `createAssetsClient`
  now take `pdsUrl?` and default to the production vanity domain
  (CloudFront-fronted) when it is omitted. Apps no longer need to hardcode the
  raw `execute-api` URL. Pass `pdsUrl` explicitly to override (self-host/staging).

  > Requires the `pds.aithos.be` CloudFront distribution to be live (fronting
  > the PDS API Gateway). Until then, keep passing the raw `pdsUrl` explicitly.

## [0.1.0-alpha.49] — 2026-05-31

### Changed

- **Consumes `@aithos/protocol-client@^0.1.0-alpha.17`**, which signs envelopes
  in the **with-proof** convention. As a result the SDK paths that sign via
  protocol-client's `buildSignedEnvelope` (compute, wallet, ethos, mandates,
  web) now emit with-proof envelopes — matching the data/assets PDS path, which
  already did. This is the client side of the envelope-proof convergence
  (étape 2). It is safe against production only because the Aithos servers run
  the dual-verify EXPAND step (accept with-proof OR without-proof); see
  `PLAN-ENVELOPE-PROOF-CONVERGENCE.md`.
- protocol-client also unifies canonicalization onto `@aithos/protocol-core`,
  so every SDK signing path now shares a single `canonicalize` and a single
  envelope convention.

## [0.1.0-alpha.48] — 2026-05-31

### Changed

- **Single canonicalization + envelope-signing source of truth.** The SDK no
  longer carries its own JSON-canonicalization or envelope assembly: it now
  routes through `@aithos/protocol-core`. Concretely:
  - `signOwnerEnvelope` (the data/assets PDS signing path) delegates to core's
    new pluggable async signer `signEnvelopeWith`, **preserving** the
    WebCrypto-ready `EnvelopeSigner` abstraction (no raw seed leaks).
  - The three hand-rolled `jcsCanonicalize` copies (`internal/envelope.ts`,
    `data.ts`, `apps.ts`) are gone — all canonicalization is now
    `@aithos/protocol-core#canonicalize`. This includes the **pre-encryption**
    record canonicalization in `data.ts` (the AAD path), so eliminating drift
    here is a data-integrity guarantee, and the sponsorship-mandate hashing in
    `apps.ts`.
  - **Byte-for-byte identical wire output** — proven by new conformance +
    snapshot regression tests (`canonical-conformance`, `envelope-core-conformance`)
    plus the existing envelope regression locks; the full suite stays green
    (212/212). Nothing changes on the wire, so no server-visible impact.

### Added

- Direct dependency on `@aithos/protocol-core` (`>= 0.6.3`), peer + dev.

### Notes

- This unifies the SDK's **own** canonicalization. The paths that sign via
  `@aithos/protocol-client`'s `buildSignedEnvelope` (compute / wallet / ethos /
  mandates / web) still use protocol-client's separate ported copy; converging
  those onto core is tracked as the envelope-proof-convergence effort and is
  not part of this release.

## [0.1.0-alpha.47] — 2026-05-30

Adds **invitation magic links that carry a mandate** to the custodial auth
flow — the generic mechanism behind "a practitioner emails a patient a link
that already grants a mandate". Mandate-agnostic: works with any scope
(read/write/append/ethos/compute). The mandate (and its delegate seed) never
ride the email URL — the auth backend stores it bound to a single-use token
and returns it only when the invitee redeems the link.

### Added

- `auth.inviteCustodial({ apiKey|publicKey, email, mandateBundle, ttlSeconds?, displayName? })`
  → sends an invitation magic link. `mandateBundle` accepts a Blob
  (`MintedMandate.bundle`), a JSON string, or a plain object. Types
  `InviteCustodialInput` / `InviteCustodialResult`.
- `auth.acceptInvite({ email, token, password? })` → consumes the token, signs
  the invitee in (creates the account with their password, or authenticates an
  existing one), and **auto-imports** the invited mandate. Returns
  `{ session, delegate: DelegateInfo, accountCreated }` — `delegate.subjectDid`
  identifies the issuer. Types `AcceptInviteInput` / `AcceptInviteResult`.
- `auth-api`: `custodialInvite` / `custodialAccept` (raw `POST
  /auth/custodial/invite` and `/auth/custodial/accept`).

### Changed

- Internal: extracted the custodial seed-bundle hydration shared by
  `verifyEmail` and `acceptInvite` into one private path (no behavior change;
  the 128-byte-seed error message is now generic).

## [0.1.0-alpha.46] — 2026-05-30

Adds **append-only delegated writes** to `sdk.data` via `createAppendDataClient`:
a holder of a `data.<collection>.append` mandate can `insert` into another
subject's collection **without any read capability** — it seals each record's
DEK to the owner's `#data-kex` public key (never the CMK), so it cannot decrypt
anything in the collection, not even its own deposit. This is the "deposit
without read" primitive behind Délie's magic-link flow (a patient drops a
long-lived mandate into the practitioner's collection without seeing other
patients' deposits). The owner reads deposits transparently; a read/write
delegate (CMK-holder) simply skips deposit records it cannot open.

### Added

- `createAppendDataClient(args) → AppendOnlyDataClient` — insert-only client
  bound to a `data.<collection>.append` mandate. Requires the owner's `#data`
  pubkey (`ownerDataPubkeyMultibase`) and the collection schema (append clients
  cannot read collection metadata). Types `AppendOnlyDataClient`,
  `AppendOnlyDataCollection`, `CreateAppendDataClientArgs`.
- Lateral scope type `DataLateralAction = "append"`; `data.<col>.append` is now
  a well-formed scope. It sits OUTSIDE the `read ⊂ write ⊂ admin` hierarchy.
- `RecordPayload` (server view) now carries either `dek_wrapped_for_cmk`
  (owner / read-write-delegate path) or `dek_wrapped_for_owner` (deposit path).
  The owner decrypt path handles both; the deposit wrap mirrors
  `@aithos/data-crypto` `wrapDEKForRecipient`.

## [0.1.0-alpha.45] — 2026-05-30

Adds **mandate-scoped delegate data read** to `sdk.data`: a mandate holder can
now read another subject's encrypted PDS collections, which was previously
owner-only. This unblocks apps like Délie (a psychologist reading a patient's
durable deposits under a mandate). The data access axis is the **collection**,
not a sphere — scopes are `data.<collection>.<action>` (mirrors
`aithos-protocol` `spec/data/04-mandates.md`).

### Added

- **`createDelegateDataClient({ pdsUrl, subjectDid, mandate, delegateSeed })`**
  → a read-only `ReadonlyDataClient`. Signs every request as the delegate
  (bare-multibase verificationMethod + the mandate attached to the §11.2
  envelope) and decrypts records via the CMK the owner re-wrapped for this
  delegate. Exposes `collection(name).get/list` and `listCollections`; mutating
  verbs are absent from the type and throw `-32042` if forced.
- **`DataClient.authorizeDelegate({ collectionName, mandate })`** (owner) —
  re-wraps the collection CMK to the grantee's key (X25519 derived from
  `mandate.grantee.pubkey`) and posts `aithos.data.authorize_app`. One wrap per
  grantee covers every record (O(1) authorization; the CMK is stable). The
  platform never sees the CMK in clear.
- **`DataClient.revokeDelegate({ collectionName, mandateId, reason? })`**
  (owner) — `aithos.data.revoke_app`. Forward-only: blocks the delegate's
  future reads.
- **`DataClient.ensureCollection({ name, schema })`** — get-or-create. Absorbs
  the `-32073 AITHOS_DATA_COLLECTION_EXISTS` conflict (and the concurrent-create
  race) so `collection(name).insert(…)` no longer needs a manual
  "create-if-missing" dance.
- **Data scopes** in `sdk.mandates`: `Scope` now accepts
  `data.<collection>.<action>` and the wildcard `data.*.<action>` (action =
  `read | write | admin`). Data mandates are sphere-neutral — minted under the
  owner's `self` sphere by default, and combinable with Ethos scopes in one
  mandate (the actor_sphere is decided by the Ethos scopes; `ethos.read.public
  + data.X.read` mints under `public`). Malformed data scopes are rejected at
  the SDK boundary.

### Notes

- No PDS redeployment is required: the data-backend already authorizes delegate
  reads (`requireScope`) and rejects delegate writes; this release is the
  client-side counterpart. The companion `data.*` allowance in the public
  sphere lives in `@aithos/protocol-core` and `@aithos/protocol-client`
  (`validateScopesAgainstSphere`).

## [0.1.0-alpha.44] — 2026-05-29

Adds **audio transcription** (AWS Transcribe Batch) to `sdk.compute`. Single
responsibility: audio → text. The SDK stores nothing — it returns the
transcript and the consumer decides what to do with it (write to an ethos, a
PDS, a database, email, or discard).

### Added

- **`sdk.compute.invokeTranscribe({ audio, … })`** — one-call high-level API:
  prepare → direct pre-signed S3 upload (with progress) → start → polling.
  Isomorphic (Node + browser): depends only on `Blob`, `fetch`/`XMLHttpRequest`
  and timers. On a backend pass `durationSecOverride`; in a browser the
  duration is probed automatically. Returns `{ text, segments, words,
  durationSec, languageCode, creditsCharged, walletBalance, auditId, … }`.
- **Low-level (advanced) API** for callers that want to own the upload/poll
  loop: `prepareTranscribe`, `startTranscribe`, `getTranscribeStatus`,
  `listPendingTranscribes`.
- **Framework-agnostic browser resilience** (vanilla JS — React, Vue, Svelte
  or none):
  - `sdk.compute.transcribeDraft` — IndexedDB queue of recorded audio Blobs
    (`save`/`list`/`get`/`delete`/`upload`), so a recording is never lost
    before upload.
  - `sdk.compute.listLocalPendingTranscribes()` /
    `subscribeLocalPendingTranscribes(listener)` / `resumeTranscribe(jobId)` —
    a localStorage-backed registry of in-flight jobs with a
    `subscribe`/`getSnapshot` API any framework can wrap, to recover jobs
    across reloads.
  - `LocalPendingTranscribeTracker` / `TranscribeDraftStore` exported for
    direct use.
- **`@aithos/sdk/react`**: `useAithosTranscribePendingJobs(sdk.compute)` — a
  thin React adapter over the vanilla tracker (React 17/18/19 compatible).
- New types: `TranscribeModelId`, `InvokeTranscribeArgs`,
  `InvokeTranscribeResult`, `TranscribeProgressState`, `TranscribeSegment`,
  `TranscribeWord`, plus the low-level arg/result shapes.

Models: `transcribe:aws-fr-standard` (default) and `transcribe:aws-en-standard`.
Pricing reuses the standard compute margin formula (AWS pass-through 0.4 mc/s,
15s minimum). No new mandate scope — reuses `compute.invoke`.

## [0.1.0-alpha.43] — 2026-05-28

Removes the "first edition is public-zone only" restriction. Apps that
write straight to `circle` / `self` on a fresh Ethos (Linkedone is the
trigger; Mail and Interview are latent beneficiaries) no longer have to
manually call `ensureInitialized()` first.

### Fixed

- **`EthosClient.publish()` no longer throws `ethos_first_edition_public_only`
  when a fresh Ethos has only circle / self mutations staged.** Previously,
  any first publish that staged a circle or self section threw because the
  underlying `buildSignedFirstEditionFromSections` helper only accepted a
  public-zone section list. Now the SDK:
  1. Buckets staged ADD mutations by zone (`public` / `circle` / `self`).
  2. If no public section was staged, auto-injects an `aithos-init`
     sentinel — identical in shape to what `ensureInitialized()` would
     produce if called explicitly — preserving the invariant that every
     Ethos has a non-empty public zone at height=1.
  3. Calls the upgraded `buildSignedFirstEditionFromSections` (now in
     `@aithos/protocol-client@>=0.1.0-alpha.14`) with the public, circle,
     and self section lists. Private zones are sealed with a fresh DEK +
     HKDF wrap for the owner using the same machinery as the height>=2
     path — so the resulting manifest is verifiable by the existing
     reader path with no special-casing.
  4. POSTs `aithos.publish_ethos_edition` with all three zone byte
     payloads when applicable.

  Behaviour for callers that staged only public adds is byte-identical
  to before — the manifest produced, the envelope signed, the wire shape
  POSTed all match the pre-alpha.43 output exactly.

### Removed

- **`ethos_first_edition_public_only` error code is no longer emitted.**
  The code remains a valid `AithosSDKError.code` string (the type is
  open), but no SDK callsite throws it anymore. Any catch block keyed on
  this specific code becomes dead-but-harmless; migrate to a generic
  `ethos_first_edition_*` catch when convenient.

### Internal

- `#publishFirstEditionOwner` updated docstring + bucket-by-zone loop +
  sentinel auto-injection + multi-zone payload assembly. `PublishResult.zonesPublished`
  now reflects the actual set of zones written (instead of a literal
  `["public"]`).
- `ensureInitialized()` is untouched — it still publishes a public-only
  height=1 sentinel when called explicitly. Apps that were already
  calling it followed by `publish()` for circle/self continue to work
  exactly as before (the second publish goes through the height>=2 path
  as it always did).

### Migration

None required. Existing call sites — including those calling
`ensureInitialized()` explicitly — continue to work without change. New
capability is opt-in (just stage circle/self mutations directly).

### Peer-dep bump

- `@aithos/protocol-client` peer range bumped from
  `>=0.1.0-alpha.13 <0.2.0` to `^0.1.0-alpha.14`. The new behaviour
  (first-edition circle/self) requires alpha.14's extended
  `buildSignedFirstEditionFromSections` helper at runtime, so it's
  cleaner to make that requirement explicit at install time than to
  silently break on consumers still on alpha.13.

## [0.1.0-alpha.42] — 2026-05-28

Adds the **publish/read RPC methods** for sponsorship, so the SDK now
covers the full lifecycle (sign + publish + read + revoke) instead of
sign-only. Pairs with the matching `aithos.sponsorship_*` and
`aithos.app_wallet_get_balance` methods on the compute proxy.

### Added

- **`sdk.apps.publishSponsorship(args)`** — signs a `SponsorshipMandate`
  with the owner's `#public` sphere AND posts it to the authority's
  `aithos-app-sponsorships` table via the new `aithos.sponsorship_create`
  JSON-RPC method. Server-side enforces that the calling owner is the
  registered `owner_did` of `args.audience.appDid`.
- **`sdk.apps.getSponsorship({appDid})`** — open read, no envelope
  required. Returns `{ exists: false }` if no row, or the full mandate +
  status + counters otherwise.
- **`sdk.apps.revokeSponsorship({appDid, sponsorshipId, reason})`** —
  marks an active sponsorship as revoked server-side. Requires owner
  signature.
- **`sdk.apps.getAppWalletBalance({appDid})`** — read the sponsor pool
  balance. Requires owner signature.

### Notes

- The previous V0.1 methods are kept untouched for backward compat:
  `createSponsorshipMandate` still only signs locally (use it when you
  want manual control over upload), `revokeSponsorshipMandate` still
  only signs the revocation document locally,
  `createAppTopupSession` is unchanged.
- The new methods are the recommended path for builder consoles and
  app dashboards — they handle envelope signing + transport + error
  mapping in one call.
- This version requires the compute-proxy Lambda to have the
  sponsorship RPC handlers deployed (= the matching commit on
  `innoestate/feature/sponsorship-phase-2-3`).

## [0.1.0-alpha.41] — 2026-05-28

Adds the **sponsorship primitive** (draft §13) on the client side. App
developers can now sign `SponsorshipMandate`s with a single SDK call,
top up an app-credit wallet via Stripe, and read sponsorship attribution
from `InvokeBedrockResult` (`fundedBy`, `sponsoredBy`, `receiptId`).

Composes with the existing `compute.invoke` scope of mandate envelope
v0.4.0 — the per-mandate `constraints.compute` caps bound a single
delegate's spend, the new `SponsorshipMandate` caps bound an app's
spend across many users.

### Added

- **`sdk.apps` namespace.** New `AppsNamespace` with three V0.1 methods:
  - `createSponsorshipMandate(args)` — builds + signs a SponsorshipMandate
    with the owner's `#public` sphere key. Returns the signed JSON, ready
    to be seeded into the authority's `aithos-app-sponsorships` table.
  - `revokeSponsorshipMandate(mandate, reason)` — builds + signs a §4.6
    revocation document with `mandate_kind: "sponsorship-mandate"`.
  - `createAppTopupSession({packId, successUrl, cancelUrl})` — Stripe
    Checkout for an `app-credits-10k/50k/200k` pack. Reuses the existing
    wallet-topup endpoint with the app's DID as the funded subject
    (Option A unified wallet).
- **Public types exported from `index.ts`**: `AppsNamespace`,
  `SignedSponsorshipMandate`, `SignedSponsorshipRevocation`,
  `CreateSponsorshipMandateArgs`, `SponsorshipBudgetInput`,
  `SponsorshipAudienceInput`, `SponsorshipAccountingAuthorityInput`,
  `AudienceSet`, `AppCreditPackId`,
  `CreateAppTopupSessionArgs`, `CreateAppTopupSessionResult`.

### Changed (back-compatible, additive only)

- **`InvokeBedrockResult` extended** with four optional fields populated
  by the new server-side routing (compute-proxy V0.1):
  - `fundedBy`: `"sponsored" | "grant" | "purchase"`.
  - `sponsoredBy`: sponsor DID when funded by sponsorship.
  - `receiptId`: signed ConsumptionReceipt id (`rcpt_…`) the authority
    issued for this debit.
  - `sponsoredRemainingForUser`: conservative 0 in V0.1 (real value via
    `getSponsorshipStatusForUser` in V0.2).
  All four are absent on legacy server responses (pre-2026-05-28).

### Notes

- The new surface is **experimental** until §13 is promoted to a
  normative spec chapter; expect minor breaks on sponsorship-specific
  types before v1.0.
- V0.1 has no server endpoint for uploading a freshly-signed mandate.
  The caller writes the row to `aithos-app-sponsorships` via
  `aws dynamodb put-item` or an ops script. V0.2 will add
  `aithos.sponsorship_create`.
- Receipts are stored canonically at the authority in V0.1. V0.2 will
  introduce gamma-log anchoring once `gamma-v0.3-per-entry-envelopes`
  lands and a `SponsorshipAcceptance` draft grants the authority
  `gamma.write` on each party's log.

## [0.1.0-alpha.40] — 2026-05-26

### Added

- **`@aithos/sdk/react` sub-module — React bindings for the assets
  sub-protocol.** Drop-in component and hook for displaying private
  assets without manual blob lifecycle:

  ```tsx
  // Once at app root
  <AssetsClientProvider client={sdk.assets}>
    <App />
  </AssetsClientProvider>

  // Anywhere in the tree
  <AithosAsset urn={cv.urn} alt="CV" className="w-full" />
  <AithosAsset urn={video.urn} as="video" controls />
  <AithosAsset urn={pdf.urn} as="download" filename="cv.pdf">
    Download my CV (PDF)
  </AithosAsset>
  ```

  Lower-level hook also exposed:

  ```tsx
  const { url, bytes, mediaType, loading, error } = useAithosAsset(urn);
  ```

  The component handles fetch + decrypt + blob URL lifecycle (revoke
  on unmount, race-cancel on URN change). The hook supports a
  `keepPreviousOnUrnChange` option to smooth out URL transitions.

  Imported via the new `./react` package export. React 18+ is a
  peer dependency, marked optional — apps that don't use the React
  bindings carry no React dep.

- **`sdk.assets` — Aithos assets sub-protocol PDS client.** Upload,
  fetch, list, ref/unref binary content (images, PDFs, audio, video)
  owned by a subject. AEAD-encrypted client-side per-asset under AMKs
  (Asset Master Keys) wrapped with X25519-HKDF-AEAD for the subject's
  sphere keys.

  Public API:
  - `createAssetsClient({pdsUrl, did, sphereSeed, verificationMethod})`
  - `AssetsClient.upload({bytes, mediaType, attachTo})` — routes
    automatically between public regime (CloudFront-served, no AMK)
    and private regime (AEAD, presigned GET) based on the attaching
    zone.
  - `AssetsClient.uploadWithThumbnails({bytes, sizes, downscale})` —
    primary asset + N thumbnails uploaded in one call (Deep use case).
  - `AssetsClient.fetch(urn)` — decrypts client-side, verifies SHA-256.
  - `AssetsClient.list(opts)`, `head(urn)`, `delete(urn)`, `ref(urn, ref)`,
    `unref(urn, ref)`, `listReferences(urn)`.
  - In-memory AMK cache; `reset()` zeroes it at logout.
  - `RecipientResolver` injection for custom recipient mapping
    (self-only default in v0.1).

  Spec ref: `spec/assets/` in the aithos-protocol repo. Owner-only auth
  in v0.1; mandate-grantee auth lands in v0.2.

  Requires the `@aithos/assets-crypto` peer dependency at install time.

## [0.1.0-alpha.39] - 2026-05-24

### Added

- **`DataClient.registerSchema(schemaDoc)` — vendor schema self-registration (A2b).**
  Apps can now publish their `aithos.x.<vendor>.<name>.v<N>` JSON Schema
  document to the subject's PDS, where it gets enforced server-side on
  subsequent record writes. Closes the security gap A2a left open : a
  buggy or malicious client could previously store arbitrary fields in
  vendor record metadata, bypassing `additionalProperties: false`.
  ```ts
  const { schemaId, docHash, created } = await client.registerSchema({
    "aithos:schema":  "aithos.x.linkedone.post.v1",
    "aithos:version": "1.0.0",
    type: "object",
    properties: { title: { type: "string", "aithos:indexable": true } },
    additionalProperties: false,
    required: ["title"],
  });
  ```
  Idempotent on the canonical document hash — safe to call on every app
  boot. A different document for the same `aithos:schema` id is rejected
  with `-32082 AITHOS_DATA_SCHEMA_IMMUTABLE` ; bump the version segment
  to evolve.
- **`DataClient.getSchema(schemaId, { subjectDid? })`** — fetch a
  published schema doc from the PDS. Returns null when the lookup
  misses. Core schemas (`aithos.<name>.v<N>`) ignore `subjectDid` ;
  vendor schemas use `subjectDid` (default : the client's own DID) to
  address the right per-owner registry.

- **`createDataClient({ schemas })` — app-defined schema overrides.**
  Apps using vendor-namespace schemas (`aithos.x.<vendor>.<name>.v<N>`)
  or any non-`aithos.*` namespace can now pass their schema definitions
  at client construction:
  ```ts
  const client = createDataClient({
    pdsUrl, did, sphereSeed, verificationMethod,
    schemas: [linkedonePostSchema],
  });
  ```
  The supplied schemas are stored per-client (no global side effects)
  and take precedence over the SDK-bundled core registry on lookup.
  Without this, `_ensureCollection` would throw `schema "..." not known
  to the SDK` for any collection whose schema isn't `aithos.contacts.v1`.

  Companion change: aithos-protocol PDS now accepts `aithos.x.*` and any
  non-`aithos.*` schema at face value (no server-side metadata
  validation pending A2b — see
  `aithos-protocol/PLAN-A2b-schema-self-registration.md`). Together
  these unblock vendor apps from defining their own data shapes without
  waiting for a full schema-registration RPC.

### Changed

- Error message on unknown schema is more actionable — points to
  `createDataClient({ schemas: [...] })` as the resolution path for
  vendor schemas, and to upgrading the SDK for core schemas added in a
  later release.

## [0.1.0-alpha.36] - 2026-05-23

### Fixed

- **`AithosAuth.#publishIdentity()` — backward-compat for republish on
  legacy backends.** Since alpha.33, `signInCustodial()` and
  `verifyEmail()` both call `#publishIdentity()` to ensure the user's
  Ethos is provisioned on every custodial sign-in. The implementation
  assumed server-side idempotence, but `protocol-client`'s
  `signedDidDocument()` regenerates a fresh `aithos.created_at` (and
  `proof.created`) on every call, breaking the strict byte-equal
  `deepEqualJson` check in `platform/primitives-write/handlers/
  publish-identity.ts`. Result: the second custodial sign-in for any
  given DID failed with `ethos_bootstrap_failed: publish_identity
  rejected: different did.json already published for this DID`
  (JSON-RPC code `-32022`). Confirmed in production on Switchia
  (2026-05-23) — every returning user got bricked at sign-in.
  - Fix here: catch the `-32022` "different did.json already published"
    response and treat it as a no-op success. For an honest signer
    (same root key, same DID) the only way to hit this code path is
    the timestamp-drift case, which is semantically a no-op — the
    Ethos is published, the crypto material matches, nothing to do.
  - The proper fix lives server-side (semantic equality on
    cryptographically significant fields only, ignoring
    `aithos.created_at` and `proof.created`). It ships in the
    `platform/primitives-write` deployment that accompanies this SDK
    release. The SDK-side shim is kept as defense-in-depth for
    consumers pointing at older backends.
  - No behavioral change for the genuine "tried to overwrite with
    different keys" case — that still raises `ethos_bootstrap_failed`
    with the original error data preserved.

### Tests

- New cases in `test/signup-bootstrap.test.ts` covering the `-32022`
  republish path: ensures `signInCustodial` succeeds when
  publish-identity returns the timestamp-drift conflict, and ensures
  any other `-32022` (e.g. a fabricated "different verificationMethod"
  case) still throws.

## [0.1.0-alpha.35] - 2026-05-23

### Added

- **`AithosAuth.signEnvelope({ aud, method, params, sphere?, ttlSeconds? })`**
  — generic envelope-signing primitive (spec §11.2) for apps calling a
  third-party Aithos-aware backend (any service that verifies a
  `SignedEnvelope` per `@aithos/protocol-core/envelope`). Same primitive
  that SDK namespaces (`sdk.data`, `sdk.ethos`, `sdk.mandates`, ...) use
  internally to sign their writes to `api.aithos.be`. Default sphere is
  `"public"`, default TTL is 60 seconds. Throws
  `AithosSDKError("auth_not_signed_in")` if no owner is loaded, or
  `AithosSDKError("auth_invalid_sphere")` on an unknown sphere.
- Exported public type `SignedEnvelope` from the package root so apps
  can type the payload they post to their backend.

### Changed (internal)

- Envelope-signing logic extracted from `src/data.ts` into a new
  SDK-internal module `src/internal/envelope.ts` (`signOwnerEnvelope`)
  for reuse by both `sdk.data` and the new `AithosAuth.signEnvelope`
  public surface. **No behavioral change for existing namespaces.** The
  envelope produced for the same inputs is byte-for-byte identical to
  alpha.34, locked by the new deterministic regression test in
  `test/envelope.test.ts`. Helpers (`jcsCanonicalize`, `sha256Hex`,
  `base64url`, `base64Std`, ULID) are intentionally duplicated between
  `data.ts` and `internal/envelope.ts` for isolation; consolidation
  into a shared `internal/canonical.ts` is planned for a follow-up
  release.

### Tests

- New `test/envelope.test.ts` — 14 dedicated cases covering: shape
  conformance to spec §11.2, signature verification round-trip,
  canonical `params_hash` stability across JS object key order, TTL
  default and override, nonce uniqueness, deterministic output under
  fixed inputs (byte-for-byte regression lock), default and explicit
  sphere selection, and the two throw paths
  (`auth_not_signed_in`, `auth_invalid_sphere`).

### Context

This unblocks apps that need to sign envelopes for their own backend —
previously the only options were to (a) re-implement the signing logic
locally, or (b) import `@aithos/protocol-client`'s `buildSignedEnvelope`
directly, bypassing the SDK. Both were anti-patterns; the SDK is meant
to be the single entry point for consumers. The new method makes it
possible for downstream apps to stay strictly on `@aithos/sdk` even when
they speak the Aithos envelope protocol with their own services.

## [0.1.0-alpha.30] - 2026-05-20

### Added

- **`AithosAuth.applyPasswordReset({ email, token, newPassword })`** —
  high-level wrapper around the `POST /auth/custodial/reset/finalize`
  endpoint. Closes the custodial reset loop: the inbox magic-link page
  reads `email` + `token` from its query string, prompts a new password,
  and calls this method. Returns a fresh `AithosSession` (JWT persisted
  to the session store) but does NOT hydrate the keystore — callers
  follow up with `signInCustodial(email, newPassword)` to materialise
  the seed bundle locally. The wire helper `custodialResetFinalize`
  already shipped in alpha.27; this release exposes it on the public
  class surface so apps don't have to reach into `auth-api.ts`.
- Exported types `ApplyPasswordResetInput` and `ApplyPasswordResetResult`
  from the package root.

### Context

This unblocks the magic-link reset flow end-to-end in `aithos-app-example`
and downstream consumers (Switchia) now that SES production access has
been granted (2026-05-20). Previously, apps had to either bypass the
class and call `auth-api.ts` directly (private surface, no stability
guarantees) or roll their own fetch against `auth.aithos.be`.

## [0.1.0-alpha.29] - 2026-05-19

### Notes

- Version bump only — no public API change. Sync with internal builder
  pipeline.

## [0.1.0-alpha.28] - 2026-05-17

### Fixed

- **`sdk.data._ensureCollection`** — defensive structural validation
  on `aithos.data.get_collection` response. The PDS sometimes returns
  a meta object lacking `cmk_envelope` for missing collections instead
  of the documented `-32020` JSON-RPC error. The previous code crashed
  with `Cannot read properties of undefined (reading 'wraps')` which
  bypassed the upper-layer's missing-collection handling.
  - When `cmk_envelope`, `urn`, or `schema` is missing on the meta, we
    now throw a typed `Error` with `code = -32020` so callers (e.g.
    `prospect-store.ensureCollection()` in switchia) can detect
    "collection not found" uniformly and trigger `createCollection()`
    as a recovery path.
  - Diagnosed in production 2026-05-17 — fresh DIDs that just signed
    up via Switchia post-wipe couldn't load the prospects page until
    they manually created a collection elsewhere.

## [0.1.0-alpha.27] - 2026-05-17

### Added

- **`EthosClient.ensureInitialized()`** — idempotent bootstrap of a
  subject's first edition. Required for the delegate-write flow: a
  delegate cannot sign a first edition (the height=1 manifest is
  signed with the owner's public-sphere key), so without an initial
  owner publish, delegate writes throw `not found: edition for did:…`.
  - Semantics:
    - If an edition already exists → no-op,
      `{ alreadyInitialized: true }`.
    - If no edition AND actor is owner → publishes height=1 with a
      single sentinel section `aithos-init` in the `public` zone,
      `{ alreadyInitialized: false, editionHeight, manifestHash }`.
    - If no edition AND actor is not owner →
      `ethos_bootstrap_not_owner`.
  - Routes through the existing `#publishFirstEditionOwner` path; the
   exposed primitive removes the need for callers to stage an explicit
    mutation just to trigger first-edition logic.
  - Typical call site: owner's dashboard, right after sign-in, before
    any delegate-mode write (e.g. before triggering a backend worker
    that holds a mandate). Idempotent ⇒ safe on every mount.

### Fixed

- Resolves the production-blocking issue where a freshly-signed-up
  user could not run any delegate-mediated workload (e.g. a backend
  worker writing to their Ethos) until they had manually published
  something on `app.aithos.be` first. Now the dashboard can call
  `ensureInitialized()` once at mount and the delegate path Just Works.

## [0.1.0-alpha.26] - 2026-05-14

### Fixed

- Re-export `Section` type from `@aithos/protocol-client`. Consumers
  (e.g. switchia-draft's Profile route) import `Section` alongside
  `EthosClient` and `ZoneName` from `@aithos/sdk`; the re-export had
  been dropped during the alpha.24 refactor. Restored to unbreak
  ethos-editor UIs.

## [0.1.0-alpha.25] - 2026-05-14

### Added

- **`sdk.data` namespace** — Aithos data sub-protocol client. Manages
  the lifecycle of subject-owned, encrypted, schema-validated records
  on an Aithos PDS deployment.
  - `createDataClient({ pdsUrl, did, sphereSeed, verificationMethod })`
  - `client.createCollection({ name, schema })`
  - `client.collection(name).insert(record)` / `.get(id)` / `.list(opts)`
    / `.update(id, record)` / `.delete(id)`
  - `client.listCollections()` / `client.listGammaEntries({ verify: true })`
  - Records are accepted as flat objects mixing indexable and encrypted
    fields; the SDK splits them per the bundled
    `aithos.contacts.v1` schema.
  - All envelope signing, CMK / DEK lifecycle, JCS canonicalization,
    and AEAD encryption is encapsulated. Developer sees idiomatic
    JavaScript objects in / idiomatic JavaScript objects out.
- Bundled schema `aithos.contacts.v1` (mirrors
  `spec/data/schemas/aithos.contacts.v1.json` in the protocol repo).

### Tested end-to-end against the live PDS

`test/data-e2e.test.mjs` — 4 tests passing:
- Full CRUD round-trip (insert, list, get, update, filter, delete, gamma)
- `listCollections` includes new collections
- Invalid record (missing required `name`) → `-32072` propagated
- Second client with same identity reads previously-encrypted records
  (CMK reconstructed via sphere-key unwrap → multi-device works)

### Notes / limitations

- Only owner-mode signing is in this iteration. Mandate-delegate
  signing (an app acting under another subject's mandate) is wired
  end-to-end on the server but not yet surfaced in the SDK's
  high-level API. Will be added in a subsequent alpha.
- The SDK currently bundles `aithos.contacts.v1` only. Other schemas
  arrive as they land in the protocol.
- The PDS endpoint URL is currently passed at construction; future
  versions will integrate with `DEFAULT_SDK_ENDPOINTS` once the data
  PDS has a canonical hosted URL.

## [0.1.0-alpha.24] - 2026-05-14

### Removed (BREAKING)

- **`sdk.compute.invokeUrlFetch` is removed.** With it, the types
  `InvokeUrlFetchArgs`, `InvokeUrlFetchResult`, `UrlFetchCitation`,
  `UrlFetchMetadata`, and `UrlFetchModelId`. Callers must migrate to
  `sdk.web.extract` (introduced in alpha.22 / extended in alpha.23),
  which routes through the deterministic Playwright-based web-extractor
  Lambda instead of the Anthropic API direct + `web_fetch` server-side
  tool. The new path is ~10× cheaper, ~5× faster, and avoids holding an
  Anthropic API key in the platform's SSM Parameter Store.

  Migration shape:

  ```ts
  // Before (alpha.23):
  const r = await sdk.compute.invokeUrlFetch({
    prompt: "Voici l'URL https://tata.com — résume…",
    model: "claude-sonnet-4-6",
    citations: true,
  });
  console.log(r.content);

  // After (alpha.24):
  const r = await sdk.web.extract({ url: "https://tata.com" });
  // r.data is a structured snapshot (meta, structure, content, styles,
  // visual_signature, logo). Hand it to a Bedrock call (or any model
  // call) if you need narrative output:
  const b = await sdk.compute.invokeBedrock({
    model: "claude-sonnet-4-6",
    system: "Résume ce snapshot…",
    messages: [{ role: "user", content: JSON.stringify(r.data) }],
  });
  ```

  Wallet contract is unchanged: `web.extract` debits 1 mc per call;
  the Bedrock follow-up is priced per token as before.

## [0.1.0-alpha.23] - 2026-05-13

### Added

- **`sdk.web.extract` now embeds the brand logo server-side.**
  The lambda resolves the best symbol-only logo for the page —
  declared `<link rel="icon"|"apple-touch-icon"|...>` declarations
  combined with conventional well-known paths
  (`/apple-touch-icon.png`, `/favicon.svg`, `/favicon.ico`, …) — and
  returns the bytes inline. New shape on `ExtractData`:
  ```ts
  data.logo: ExtractLogo | null
  // {
  //   url: string,
  //   source: "link-icon-svg" | "link-apple-touch-icon"
  //         | "link-icon-large" | "link-icon"
  //         | "link-shortcut-icon" | "well-known-apple-180"
  //         | "well-known-apple" | "well-known-svg"
  //         | "well-known-png-large" | "well-known-ico",
  //   content_type: string,
  //   size_bytes: number,
  //   base64: string,
  // }
  ```
  Build a data URI with
  ``` `data:${logo.content_type};base64,${logo.base64}` ```.
  Favicons are symbol-only by construction (no designer ships a
  wordmark inside a 16-180 px icon), which sidesteps the
  lockup-vs-symbol problem callers used to handle client-side
  with a vision-model round-trip.

- **`sdk.web.fetchAsset({ url })`** — server-side fetch of an
  arbitrary asset (image / font / css / json …) bypassing browser
  CORS. Returns `{ data: { url, final_url, content_type,
  size_bytes, base64 }, creditsCharged, walletBalance, auditId }`.

  Use when `fetch(url, {mode:"cors"})` and `<img crossOrigin>`
  canvas readback both fail because the asset server doesn't
  return Access-Control-Allow-Origin headers. For the common
  "logo of a webpage" case the embed in `extract.data.logo` is
  enough — reach for `fetchAsset` only for secondary assets
  (og:image, hero illustration, document download) or for an
  asset on a page you haven't extracted.

  - Same envelope auth + scope (`web.extract`) as `extract`.
  - Pricing: flat 1 mc per successful fetch, refund on failure.
  - Server caps: 15 s timeout, 10 MB body, http/https only.
  - JSON-RPC errors propagate verbatim — `-32071`
    insufficient_balance, `-32042` scope mismatch, `-32000`
    upstream fetch failure (with `data.reason`).

- New type exports from the package root:
  - `ExtractLogo`, `ExtractIconDeclaration`
  - `FetchAssetArgs`, `FetchAssetResult`

## [0.1.0-alpha.22] - 2026-05-13

### Added

- **`sdk.web` namespace** — new top-level namespace targeting the
  web extractor proxy at `extract.aithos.be`. Same JSON-RPC + signed-
  envelope protocol as `sdk.compute`, distinct audience so a mandate
  can carry one capability without the other.
- **`sdk.web.extract({ url, waitUntil?, timeoutMs?, mandateId?, ... })`**
  — fetch and clean a public webpage on the user's behalf, returning:
  - structured `content` (main HTML, plain text, images, links,
    forms)
  - sanitized `structure` (heading tree, sections, nav links)
  - purged + minified `styles.css`
  - `visual_signature`: deterministic design-system digest computed
    server-side without any LLM (palette, typography, dominant
    radii, spacing, layout mode, component digests for buttons,
    inputs, cards)
- **`WEB_EXTRACT_SCOPE = 'web.extract'`** — opt-in scope a mandate
  must carry to authorise extractions on behalf of an owner. Mint
  through `sdk.mandates.create({ scopes: [WEB_EXTRACT_SCOPE] })`.
- **`AithosSdkEndpoints.web`** + **`webInvokeUrl(endpoints)`** —
  endpoint config (defaults to `https://extract.aithos.be`).
- Full type tree exported from the package root: `ExtractArgs`,
  `ExtractResult`, `ExtractData`, `ExtractMeta`, `ExtractStructure`,
  `ExtractContent`, `ExtractStyles`, `VisualSignature`,
  `PaletteEntry`, `ComponentStyle`, `ExtractHeading`,
  `ExtractSection`, `ExtractLink`, `ExtractImage`, `ExtractForm`,
  `ExtractFormField`.

### Pricing

- A single successful extraction is debited at **1 microcredit**,
  refunded in full on extraction failure. Compare with ~30 mc for a
  comparable LLM-based extraction through `sdk.compute.invokeBedrock`
  — ~30× cheaper for the same downstream task.

### Errors

- `-32071` (insufficient_balance) — propagated as `AithosSDKError`
  with `code = '-32071'` and `data = { required, available }`. Same
  shape as `sdk.compute`, so existing balance-handling code paths
  work without changes.
- `-32042` (scope mismatch) — raised when a delegate mandate does
  not carry `web.extract`.

## [0.1.0-alpha.21] - 2026-05-12

### Added

- **`zone.findSectionsByTitle(title)`** — return every section in the
  zone whose `title` matches `title` exactly (case-sensitive). Always
  returns an array; may be empty, one, or several (the Aithos protocol
  does not require section titles to be unique within a zone).
- **`zone.updateSectionsByTitle(title, patch)`** — stage an update for
  every matching section in one call. Returns the array of staged
  `section.id`s (empty when no match). Silent on no-match — useful
  for upsert flows ("update Voice if it exists, otherwise add it").
- **`zone.deleteSectionsByTitle(title)`** — same semantics for delete.

  These three are SDK-side ergonomic affordances for the LLM-driven
  "act on the section called X" use case. The protocol's only
  normative anchor remains `section.id`. The current implementation
  resolves titles client-side by loading the full zone and filtering;
  when a future revision of `aithos.get_ethos_zone` (or a dedicated
  `aithos.find_sections` primitive) supports server-side title
  filtering, the implementation can swap to a single RPC without
  changing the public signatures here. The contract (async, exact
  case-sensitive match, plural semantics) is intentionally aligned
  with what such an API would return.

## [0.1.0-alpha.20] - 2026-05-12

### Added

- **`compute.invokeUrlFetch()`** — fetch one or more URLs and have
  Claude analyse the content via Anthropic's `web_fetch` server-side
  tool. Routes through `api.anthropic.com` directly (NOT Bedrock)
  because Bedrock does not expose Anthropic's server-side tools. The
  proxy hides this multi-backend detail; the wallet, envelope, and
  mandate-scope contracts are unchanged. Returns a final assistant
  message plus per-URL fetch metadata and citation spans. Configurable
  `maxFetches` (1..10, default 5), `maxContentTokens` (1k..200k,
  default 100k), `citations` (default true), `allowedDomains` /
  `blockedDomains` (mutually exclusive). New mandate scope
  `compute.url_fetch` (distinct from `compute.invoke`).

  Public types: `InvokeUrlFetchArgs`, `InvokeUrlFetchResult`,
  `UrlFetchCitation`, `UrlFetchMetadata`, `UrlFetchModelId`. Tests in
  `test/compute.test.ts` cover the happy path, error wrapping, and
  abort propagation.

### Fixed

- **`parseDelegateBundle` accepts the `issuer` field** as the mandate
  subject DID. `mintDelegateBundle` in protocol-client emits the
  SignedMandate wire shape which carries the subject's DID under
  `issuer`, not `subject_did` — the parser was rejecting every
  freshly-minted bundle with `mandate.subject_did missing or
  malformed`. Reads `issuer` first; falls back to legacy `subject_did`
  / `subjectDid` so older fixtures and externally-minted bundles
  keep working. Regression test in `test/auth-j3.test.ts`.

### Changed

- **`claude-opus-4-7` removed from canonical model list.** The
  `InvokeBedrockArgs.model` docstring and the `UrlFetchModelId` union
  now list `claude-opus-4-6` instead. Opus 4.7 is provisioned on the
  proxy's Bedrock account but commercially gated — `InvokeModel`
  returns `AccessDeniedException` pointing to AWS Sales, every
  cross-region quota for Opus 4.7 is 0 by default, and quota-increase
  requests below 30M tokens/min are refused. `claude-opus-4-6` is the
  strongest model currently invocable; it works immediately, runs at
  $5 / $25 per million input / output tokens. Re-enable 4.7 in the
  proxy `MODEL_ALIASES` once AWS Sales grants access.

### Tests

- New `test/ethos-first-edition.test.ts` covering the alpha.7
  first-edition publish path on a fresh Ethos (zone reads return [],
  `publish()` routes to `publish_ethos_edition` with height=1,
  rejects circle/self mutations on first edition, rejects
  update/delete operations on a fresh Ethos).

### Chore

- Drop the SDK-local `CLAUDE.md` (workflow notes are deduplicated by
  `/Volumes/Math17/aithos/code/CLAUDE.md` at the monorepo root, which
  covers the same FUSE-mount unlink(2) git workaround across all
  repos).

## [0.1.0-alpha.14] - 2026-05-10

### Fixed (CRITICAL — alpha.13 follow-up)

- **`auth.handleCallback()` concurrent calls now share a single
  promise.** alpha.12's StrictMode dedup (cleaning the URL before the
  exchange await) inadvertently broke the legitimate first-time-SSO
  flow: in dev mode React StrictMode invokes the mount effect twice,
  the first call cleaned the URL + started the exchange (returning a
  pending promise), the second call saw the now-clean URL and
  resolved to `null` immediately. The component's own
  `cancelled`-flag pattern then discarded the first call's result
  and navigated home with the second call's null. Net effect: a
  perfectly-completed SSO sign-in landed the user on `/` with a JWT
  but no `phase = "needs-bootstrap"` ever set, hiding the Welcome
  form alpha.13 added.

  alpha.14 caches the in-flight promise per `AithosAuth` instance
  and returns it on every concurrent call until it settles. Both
  StrictMode invocations now resolve to the same value, the second
  call doesn't race the URL clean, the AuthCallback component sees
  a populated session and renders Welcome correctly. Cache cleanup
  uses `then(cleanup, cleanup)` rather than `finally(cleanup)` so a
  rejection on the underlying promise doesn't leak as an
  unhandledrejection.

## [0.1.0-alpha.13] - 2026-05-10

### Added

- **`auth.completeSsoFirstLogin({ handle, displayName? })`** — closes
  the gap left by Google sign-in for a brand-new account. After
  `signInWithGoogle()` + `handleCallback()` for a first-time SSO user,
  the SDK has a session JWT and a server-released `enc_key`, but no
  Aithos identity (no Ed25519 seeds, no `did.json` published, no blob
  in the auth vault). This new method:

  1. Generates a fresh `BrowserIdentity` client-side (4 keypairs,
     derived DID).
  2. Calls `aithos.publish_identity` on `api.aithos.be` so subsequent
     reads/writes via `sdk.ethos` and friends have an ethos to anchor
     to.
  3. AES-GCM-encrypts the seeds with the session's `enc_key`,
     `PUT /auth/blob`. From then on, every Google sign-in for this
     user receives the encrypted blob and hydrates locally without
     another bootstrap.
  4. Hydrates `ownerSigners` + `keyStore` so `canSignAsOwner()` flips
     to true.
  5. Returns a recovery-file Blob — even though the user authenticated
     via Google, the freshly-generated seeds are the only material
     that can sign Aithos artifacts. Without the recovery file,
     losing access to the Google account loses the ethos forever.

  Detect the post-Google bootstrap state by checking
  `getCurrentSession()?.blob_version === 0` after `handleCallback()`
  resolves with a non-null session.

### Fixed

- **Strict `plaintext.identity.did === session.did` check in
  `handleCallback()` is gone.** That check made `signInWithGoogle`
  unusable for any user whose blob was bootstrapped via the new
  first-login flow (the auth backend assigns a placeholder DID at
  user-record creation time, but the blob is built around a
  client-derived DID — they intentionally differ). The blob is now
  the truth source for hydration; `session.did` is auth-side
  bookkeeping only and is no longer used for signing.

  Password-flow users are unaffected — for them the two DIDs match by
  construction (the client passes its derived DID to `/auth/register`
  before the JWT is minted).

## [0.1.0-alpha.12] - 2026-05-10

### Fixed

- **`auth.handleCallback()` no longer fails the second time it's called
  for the same `?aithos_code=` URL.** React StrictMode (dev) and any
  framework that double-invokes a mount effect would trip the
  single-use code check on the second call, surfacing
  `auth_code_consumed: aithos_code expired or already used` to a user
  who actually signed in correctly. The SDK now strips the code from
  the URL synchronously, BEFORE the network round-trip — the second
  invocation sees a clean URL and returns `null` silently. Single
  network call, no server-side change required.

  This fix should have shipped in alpha.11 alongside the
  `signInWithGoogle({ appId, returnTo })` work, but landed slightly
  after the alpha.11 publish window. alpha.12 is alpha.11 + this
  one-line fix.

## [0.1.0-alpha.11] - 2026-05-10

### Added

- **`auth.signInWithGoogle({ appId, returnTo })`** — per-app redirect
  routing. Apps that register with Aithos (entry in
  `aithos-auth-apps`) can now declare a `returnTo` URL on the Google
  sign-in flow, validated server-side against the app's
  `allowed_redirect_uris` allowlist. The post-OAuth redirect lands the
  user on the consumer app's own origin instead of the legacy
  hard-coded `app.aithos.be/auth/callback`.

  In dev: pass `returnTo: "${window.location.origin}/auth/callback"` so
  Google sign-in returns to `http://localhost:<port>/auth/callback`
  rather than dumping the aithos_code on app.aithos.be where the dev
  app can't see it.

  `appId` and `returnTo` must be passed together. Half-presence rejects
  at the SDK level with `auth_sso_app_redirect_pair_required`. Apps that
  pass neither continue to use the legacy redirect path (backward
  compat).

  Requires the Aithos auth backend to ship the matching server-side
  changes (validates the pair against `aithos-auth-apps`, embeds the
  validated URI in the signed state JWT). Older auth backends ignore
  the new query params and behave like alpha.10.

## [0.1.0-alpha.10] - 2026-05-10

### Fixed (CRITICAL — silent data loss)

- **`EthosClient.publish()` no longer wipes private-zone content when a
  delegate cannot decrypt the existing edition.** Before this fix, a
  delegate holding `ethos.write.<zone>` whose key didn't appear in any
  wrap of the existing zone ciphertext could stage an `addSection` and
  publish — the new edition would replace the (still-encrypted-for-others)
  content with **only** the staged section, silently destroying every
  section the owner had in that zone.

  The bug was a missing `zoneDecryptErrors[zone]` check in the delegate
  branch of `publish()`: the read path correctly threw
  `ethos_zone_unreadable` when decryption failed, but the write path
  didn't and proceeded with `base = []` to compute `newSections`. The
  protocol-level `publishPrivateZoneAsDelegate` treats `newSections` as
  the COMPLETE new content (the server can't merge ciphertext it can't
  read), so passing `[]`-derived content was destructive.

  alpha.10 throws `AithosSDKError("ethos_delegate_cannot_overwrite_unreadable")`
  in this case, with the offending zone and the underlying decrypt
  error in `data`. The mandate isn't usable for that zone until the
  owner publishes once (so the delegate's wrap gets bootstrapped) — at
  which point reads start returning content and writes preserve it.

  This bug only ever affected `aithos-sdk` consumers; apps using
  `@aithos/protocol-client` directly (e.g. `app.aithos.be`) gate their
  UI on `snapshot.zoneDecryptErrors` and don't reach the publish path
  in this state.

  No new tests added in this patch (would require a multi-key delegate
  + encryption fixture beyond what the unit suite mocks today); the
  fix is verified end-to-end via the e2e harness against production.

## [0.1.0-alpha.9] - 2026-05-10

### Fixed

- **`sdk.compute.invokeBedrock` now supports the delegate signing path.**
  Previously the method always required an owner (`sdk_no_owner`), so
  any session that only held an imported mandate — exactly the case the
  whole compute-mandate design was meant to enable — couldn't actually
  call compute. Now: if no owner is loaded, the SDK looks up the
  delegate matching `args.mandateId`, signs the envelope with the
  delegate's keypair (multibase `verificationMethod`), and attaches the
  full SignedMandate so the proxy verifies signature + authorisation in
  one pass.

  Owner takes precedence when both are loaded — the mandate is then
  dereferenced server-side from `params.mandate_id`, no envelope-level
  attachment.

  New error code: `sdk_no_delegate_for_mandate` — surfaces when neither
  owner nor a matching delegate is loaded. The previous
  `sdk_no_owner` code is preserved for entry points without a delegate
  fallback.

- **`mandates.create({ ..., notBefore })` exposes the not-before
  override.** Default behaviour now signs `not_before = now - 30s` (via
  `@aithos/protocol-client@>=0.1.0-alpha.13`) so a server whose clock
  runs slightly behind the client doesn't reject the freshly-minted
  mandate as "not yet valid" anymore. Callers can pass an explicit
  `Date` for advanced flows (delayed-activation mandates, deterministic
  tests).

### Changed

- Peer dependency bumped: `@aithos/protocol-client` ≥ `0.1.0-alpha.13`
  (was alpha.12). Required for the `notBefore`-threading fix and the
  `MANDATE_NOTBEFORE_OFFSET_SECONDS_DEFAULT` export.

### Tests

- New `test/compute-delegate-path.test.ts` covering: owner path,
  delegate path with matching mandate, delegate path without matching
  mandate (rejection), and envelope-shape assertions on both paths.
- `sdk.test.ts` updated: the "no owner" rejection test now expects
  `sdk_no_delegate_for_mandate`. 130/130 passing.

## [0.1.0-alpha.8] - 2026-05-10

### Fixed

- **`auth.importMandate({ bundle })` no longer rejects every freshly-minted
  delegate bundle.** The bundle parser was reading `mandate.subject_did`
  to find the subject's DID, but real mandates emitted by
  `mintDelegateBundle` (in protocol-client) carry the subject under
  `mandate.issuer` — there is no `subject_did` field. Result: every user
  who downloaded a bundle from `sdk.mandates.create()` and tried to
  import it back hit `auth_invalid_delegate_bundle: delegate bundle is
  invalid: mandate.subject_did missing or malformed`.

  The bug was masked by the test suite, which fed `parseDelegateBundle`
  a hand-rolled fixture using the (wrong) `subject_did` field name.
  Parser now reads `mandate.issuer` first; `subject_did` /
  `subjectDid` are accepted as legacy fallbacks so older fixtures and
  any externally-minted bundles using the older shape keep working.
  Error message updated to point at `mandate.issuer` if the field is
  truly absent or malformed.

  Regression test added that exercises the real wire shape (with
  `issuer` and the full SignedMandate structure).

## [0.1.0-alpha.7] - 2026-05-10

### Fixed

- **`me.zone(...).sections()` no longer throws on a fresh Ethos.**
  After a successful `auth.signUp()` in alpha.6 (which provisions the
  identity on `api.aithos.be` via `publish_identity`), the user has a
  ready-to-edit Ethos but no editions yet. Reading the editor was
  raising `not found: edition for did:aithos:...` (code -32020) and
  blocking the editor UI from rendering. SDK now treats that specific
  server response as "zero sections in every zone" and returns empty
  arrays. Other -32020 cases (e.g. `not found: manifest @<height>`)
  still propagate, so genuine read failures stay visible.
- **`me.publish()` works on a fresh Ethos.** When no edition exists yet,
  `publish()` now routes to a new internal first-edition path that
  builds a `height=1` manifest from the staged ADD mutations and POSTs
  `aithos.publish_ethos_edition` with a `#public`-signed envelope.
  After this first publish, subsequent publishes take the regular
  `publishZoneEdit` (next-edition) path automatically.

### Added

- **`AithosRpcError` introspection** for downstream consumers — re-export
  so callers can `instanceof`-check a server-side error and inspect its
  JSON-RPC code if they need to.

### Limitations

- The first-edition path supports the **public zone only**. Staging
  circle/self additions before the first publish raises
  `AithosSDKError("ethos_first_edition_public_only", ...)`. Encrypted
  zones can be added in a subsequent edition once the public zone has
  been seeded.
- Stating an `update` or `delete` mutation against a fresh Ethos
  raises `AithosSDKError("ethos_first_edition_invalid_op", ...)` —
  there's no prior edition for those ops to target.

### Bumped

- Peer dependency: `@aithos/protocol-client` is now `>=0.1.0-alpha.12`
  (was `>=0.1.0-alpha.11`). Required for the new
  `buildSignedFirstEditionFromSections` helper and the
  `writeEndpoint()` export the first-edition path uses.

## [0.1.0-alpha.6] - 2026-05-10

### Fixed

- **`AithosAuth.signUp()` now provisions the user's Ethos on
  `api.aithos.be`.** Previously, sign-up only created the auth user via
  `POST /auth/register`. The `ethos-index` row + `did.json` on
  `api.aithos.be` were missing, so the very first call to
  `me.publish()` errored with `-32020 subject identity not published`.
  signUp now signs and POSTs an `aithos.publish_identity` envelope
  (root-signed) to `${apiBaseUrl}/mcp/primitives/write` immediately
  after `/auth/register` succeeds. All crypto stays internal — the
  consumer-facing API of `auth.signUp({ email, password, handle })` is
  unchanged.

  Failure handling: 2 retries with exponential backoff on transient
  errors (5xx, network). On JSON-RPC error or definitive failure,
  throws `AithosSDKError("ethos_bootstrap_failed", …)` and the local
  state is NOT hydrated (no session, no keystore write). Known limit:
  the auth account on `auth.aithos.be` has been created at this point,
  so re-signing-up with the same email will fail with email-taken — a
  self-heal on `signIn` (re-attempt `publish_identity` if the row is
  missing) is planned for the next release.

### Added

- **`apiBaseUrl?: string` on `AithosAuthConfig`.** Override the
  primitives API endpoint (default `https://api.aithos.be`) for
  staging / self-hosted / test deployments. Symmetrical to the
  existing `authBaseUrl`.
- **`DEFAULT_API_BASE_URL` exported from the package root.**
- **Typed `compute` namespace on `mandates.create()` —
  opt-in, capped token-spending capability.** Mirror at the SDK
  surface of the protocol-level `compute.invoke` scope (protocol-core
  v0.4.0). Token spending becomes a separate, named, validated input
  on `CreateMandateInput` — never an implicit side-effect of an ethos
  scope, never something an app can smuggle past a consent UI by
  adding a string to `scopes[]`.

  Shape:

  ```ts
  await sdk.mandates.create({
    granteeId: "urn:agent:bob",
    scopes: ["ethos.read.public"],     // ethos read access (optional)
    ttlSeconds: 86_400,
    compute: {                          // opt-in token spending
      dailyCapMicrocredits: 5_000,
      totalCapMicrocredits: 100_000,
      maxCreditsPerCall: 500,
      allowedModels: ["claude-haiku-4-5"],
    },
  });
  ```

  Invariants enforced at the SDK boundary AND in the protocol layer:
    - Passing `"compute.invoke"` directly inside `scopes[]` is rejected
      (`mandates_invalid_scopes`) — the namespace is the only path,
      so a consent UI reviewing `compute` cannot be bypassed.
    - The `compute` namespace requires at least one of
      `dailyCapMicrocredits` or `totalCapMicrocredits`. An unbounded
      compute mandate is the bearer-token footgun this whole layer
      exists to prevent (`mandates_invalid_compute`).
    - Numeric caps must be positive integers; `allowedModels` entries
      must be non-empty strings.
    - The compute mandate may stand alone (`scopes: []` is permitted
      iff `compute` is present) — supports agents that consume tokens
      without reading any ethos data (creative assistants, etc.).

  New exports: `COMPUTE_INVOKE_SCOPE` constant,
  `CreateMandateComputeInput` type.

  Note: the underlying call to `@aithos/protocol-client@^0.1.0-alpha.11`
  uses an `as unknown as` cast on the `constraints` argument because
  the installed protocol-client does not yet type the `compute` field
  on `MandateConstraints`. The cast goes away when the dependency
  picks up `@aithos/protocol-core@0.4.0`. Wire-format-wise, the
  canonicalizer serializes whatever's in `constraints`, so the
  mandate produced is already 0.4.0-shaped.

## [0.1.0-alpha.5] - 2026-05-08

This release lands the migration-proof internal architecture and
high-level `sdk.ethos` / `sdk.mandates` namespaces decided in the
J1–J6 design discussion. The SDK consumer surface is now stable with
respect to the future Web Crypto / non-extractable key migration —
no public method exposes seed material.

### Added

- **Internal `Signer` abstraction (J1).** Private to the SDK; today
  wraps raw Ed25519 seeds via `@noble/ed25519` (`RawSeedSigner`),
  tomorrow swaps to `crypto.subtle` non-extractable `CryptoKey` with
  no consumer-facing change. `OwnerSigners` bundles the four sphere
  signers; `_unsafeStoredIdentity` is the documented escape hatch for
  protocol-client interop until upstream gains a Signer-shaped API.
- **`AithosKeyStore` — pluggable key persistence (J2).** New
  contract: `loadOwner` / `saveOwner` / `clearOwner` +
  `list/load/save/remove/clearAllDelegates`. Bundled implementations
  `indexedDbKeyStore` (default in browsers, stores hex seeds — same
  posture as `app.aithos.be` does today via extension-kit) and
  `memoryKeyStore` (tests / SSR). Stored shapes (`StoredOwnerKeys`,
  `StoredDelegateKeys`) are versioned (`0.1.0-hex`) and
  opaque-to-consumer.
- **`AithosAuth.signInWithRecovery({ file })` (J3).** Local-only
  sign-in from a recovery JSON file. Hydrates owner signers and
  persists to keyStore; no JWT obtained.
- **`AithosAuth.importMandate({ bundle })` / `removeMandate(id)`
  (J3).** Parse `.aithos-delegate.json`, register the delegate
  actor, persist to keyStore. Works alongside an owner OR
  delegate-only.
- **`AithosAuth.resume()` (J3).** Boot-time hydration from both
  stores. Strict consistency: JWT and owner must agree on DID.
- **`AithosAuth` state accessors (J3).** `getOwnerInfo()`,
  `getDelegates()`, `canSignAsOwner()`, `canSignAsDelegateFor(did)`.
  `getCurrentSession()` continues to surface JWT-backed sessions.
- **`sdk.ethos` namespace — high-level ethos editing (J4).** Replaces
  the protocol-client passthrough with `EthosNamespace` /
  `EthosClient` / `EthosZone`. Lazy + commit pattern: mutations
  stage in memory, `await me.publish()` posts a single new edition.
  `sdk.ethos.me()` for owner mode; `sdk.ethos.of(did)` resolves
  to owner / delegate / anonymous mode based on what auth holds.
  Anonymous mode is read-only on the public zone; delegate mode
  enforces mandate scopes before publish.
- **`sdk.mandates` namespace — owner-side mandate lifecycle (J5).**
  `create({ granteeId, scopes, ttlSeconds })` mints + publishes +
  returns the shareable `.aithos-delegate.json` Blob; `list()`
  pages through `aithos.list_mandates`; `revoke(id)` posts a §4.2
  revocation envelope.
- **`Scope` typed union for mandate scopes** (J5):
  `ethos.{read,write}.{public,circle,self}`.

### Changed (BREAKING)

- **`new AithosSDK({ auth, appDid })` replaces `{ identity, appDid }`
  (J6).** `AithosAuth` is now the single owner of session +
  signing material. The SDK reads the active owner / delegate set
  from auth at call time, so sign-in/out picks up automatically
  across all namespaces. Migration path for apps:

  ```ts
  const auth = new AithosAuth();
  await auth.signIn({ email, password });   // or signUp, Google, recovery
  const sdk = new AithosSDK({ auth, appDid });
  ```

- **`AithosAuth.signIn` now decrypts the vault blob** and hydrates
  the owner signers in memory + persists to keyStore. Previously
  derived `enc_key` and zeroized it without using it.
- **`AithosAuth` constructor accepts `keyStore?`** (defaults to
  `defaultKeyStore()` — IndexedDB in browsers, memory elsewhere).

### Removed (BREAKING)

- The top-level `ethos`, `mandates` namespace re-exports of
  protocol-client are gone from the SDK barrel. Apps that need the
  protocol primitives directly should `import { … } from
  "@aithos/protocol-client"`. The new `sdk.ethos` and `sdk.mandates`
  instance namespaces cover the high-level use cases.

### Notes

- 22 new unit tests across J1–J6 (106 total, all green).
- Bumped peer dep on `@aithos/protocol-client` to `>=0.1.0-alpha.11`
  for `decryptBlob` / `parseBlob` / `argon2id` primitives.
- `revoke()` URL is hardcoded to `https://api.aithos.be` until
  `@aithos/protocol-client` exposes a public configuration API for
  the `api` endpoint. Self-hosters: track the TODO in
  `src/mandates.ts`.
- App migration (`innoesate/aithos/app`) is intentionally NOT
  included in this release — the SDK ships first, the app picks
  up the new API in a follow-up.

## [0.1.0-alpha.4] - 2026-05-07

### Added

- Email + password authentication path on `AithosAuth` — `signIn`,
  `signUp`. Pluggable `AithosSessionStore` (sessionStorage,
  localStorage, noopStore) for JWT persistence.

## [0.1.0-alpha.3] - 2026-05-06

### Added

- **`AithosAuth` — Sign in with Google.** New standalone class (not part
  of `AithosSDK`) that drives the OAuth round-trip with the Aithos auth
  backend (`auth.aithos.be`).
  - `auth.signInWithGoogle({ appState? })` — synchronous redirect to
    Google's consent screen. `appState` (≤ 1024 chars) survives the round-
    trip and comes back as `?app_state=…` on the callback URL.
  - `auth.handleCallback()` — inspects the current URL for `?aithos_code=`,
    exchanges it at the backend, returns an `AithosSession` (JWT, DID,
    handle, encrypted vault blob, vault key, `is_first_login`). Strips
    the callback params via `history.replaceState` so a refresh doesn't
    replay the redeem.
  - `auth.exchange(aithosCode)` — programmatic redemption for routers
    that pull the code from the URL themselves.
  - `auth.signOut()` — stateless no-op; sessions are JWTs.
- New exports: `AithosAuth`, `DEFAULT_AUTH_BASE_URL`, types
  `AithosAuthConfig`, `AithosSession`, `SignInWithGoogleOptions`.
- 11 new unit tests for `AithosAuth` (mock `fetch` + `window`).

### Notes

- Auth lives **outside** `AithosSDK` because the sign-in flow runs
  *before* the user has a `BrowserIdentity` — that's what the flow
  returns (via the encrypted vault blob).
- The `AithosSession` shape uses snake_case fields (e.g. `enc_key_b64`,
  `is_first_login`) wire-compatible with the auth Lambda's
  `SsoExchangeResponse`. No mapping layer; the SDK is transparent if
  you open the Network panel.
- Storage of the session/blob/key is intentionally the app's call. See
  the README "Auth — sessions and storage" section for recommended
  patterns.

## [0.1.0-alpha.2] - 2026-05-06

### Added

- `sdk.wallet.getBalance()` — read-only wallet balance lookup. Returns
  `{ balance, dailySpent, exists }` in microcredits. Routed through the
  compute proxy at `${compute}/v1/invoke` as the JSON-RPC method
  `aithos.wallet_get_balance`, gated by the same signed-envelope
  verification as `compute.invokeBedrock` so only the owner of the user's
  `#public` key can read their balance. Requires the matching backend
  change in `aithos-platform-compute-proxy` (deployed alongside this
  release) — earlier proxy builds will return `method not found`.

### Changed

- `WalletNamespace` now receives the user `identity` and `appDid` (passed
  by the `AithosSDK` constructor automatically). Direct callers that
  instantiated `new WalletNamespace({ userDid, endpoints, fetch })`
  manually must add the two extra fields. The high-level
  `new AithosSDK({ identity, appDid })` flow is unchanged.

## [0.1.0-alpha.1] - 2026-05-05

First public release on npm.

### Added

- Initial scaffold of `@aithos/sdk`.
- `AithosSDK` class with namespaced surface: `compute`, `wallet`, `ethos`,
  `onboarding`, `mandates`.
- `compute.invokeBedrock()` — single-shot Bedrock invocation through the
  Aithos compute proxy with envelope signing and mandate enforcement.
- `wallet.createTopupSession()` — creates a Stripe Checkout session for a
  credit pack and returns the hosted checkout URL.
- Re-exports of `@aithos/protocol-client` primitives (identity, mandates,
  ethos zones, onboarding, DID) under stable namespaces.
- 29 unit tests across SDK construction, compute (mock-fetch + real
  envelope signing), wallet, and endpoint URL composition.

### Peer dependencies

- `@aithos/protocol-client` `>=0.1.0-alpha.7 <0.2.0`
