# Changelog

All notable changes to `@isnap/sdk` will be documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Pre-1.0 versions may introduce breaking changes between 0.x → 0.(x+1) minor bumps.

## [Unreleased]

### Added
- Initial public surface targeting iSnap API contract `v1.0.0`.
- `ISnapClient` with retries (jittered exponential backoff on 429/5xx), per-attempt timeout, auto-`Idempotency-Key` for mutating verbs, header injection, abortable requests.
- Resource classes: `messages`, `chats`, `lines`, `lookup`, `attachments`, `webhooks`, `byod`, `trial`.
- `attachments.upload()` — transparent 3-step (POST register → presigned R2 PUT → poll until `ready`) with 5 GB cap and four typed failure modes.
- Cursor pagination as `AsyncIterable` with optional `AbortSignal` on `messages.list`, `webhooks.deliveries`, `webhooks.events`.
- Webhook helpers: `verifyWebhook` (HMAC-SHA256 + 5min replay window + multi-secret rotation), `parseWebhookEvent`, prefix-based type guards (`isMessageEvent`, `isReactionEvent`, `isLineEvent`, `isTypingIndicatorEvent`, `isTrialEvent`, `isPreOrderEvent`, `isBindingEvent`, `isAdminEvent`).
- Webhook event-type aliases re-synced with the backend catalog (additive, MINOR): `MessageEventType` gains `message.fallback_triggered` (iMessage→SMS/RCS degradation) and `LineEventType` gains `line.capability_changed`; two new families ship complete — `BindingEventType`/`BindingEvent`/`isBindingEvent` (`binding.released`, shared-plan contact-binding release) and `AdminEventType`/`AdminEvent`/`isAdminEvent` (`webhook.test`, the suspended-state probe). All eight aliases are now locked to the generated `WebhookEventName` enum by a compile-time parity assertion (`src/webhooks/catalog-parity.test.ts`), so a future catalog change — including a brand-new family — fails `bun run typecheck` instead of silently stranding consumers who cast `event.event_type as MessageEventType` for exhaustiveness. Runtime narrowing is unchanged: the guards already prefix-matched every one of these.
- Error hierarchy mirroring contract §6: `APIError` + 13 subclasses, all carrying `status`, `code`, `traceId`, `requestId`. Connection failures surface as `APIConnectionError` with native `Error.cause`.
- Generated TypeScript types (`src/generated/openapi.d.ts`) compiled from the backend's frozen `openapi.json`.
- `maxRetryDelayMs` client option (default `60_000`) — ceiling on how long a single retry may sleep. A 429 whose `retry_after` exceeds it is rethrown as `RateLimitError` (with `retryAfter` intact) instead of slept through, so a day-scale `Retry-After` such as the trial rebind cap can no longer park a call for hours. The exponential-backoff branch is clamped to the same ceiling.
- Wholesale + test-line methods on `client.lines`: `mintShared(body?)` (`POST /v1/lines/shared`) mints a fresh **billable** shared wholesale line — wholesale-partner only in v1, so a direct customer is rejected with `PaymentRequiredError` (402) and a scoped API key with `PermissionDeniedError` (403); `convertToTest(id)` / `convertFromTest(id)` (`POST /v1/lines/{id}/convert-to-test` / `convert-from-test`) flip a line between billable and test. Every `mintShared()` call mints a new line — the auto `Idempotency-Key` only dedupes that one call's internal retries; a caller-level retry must reuse `opts.idempotencyKey`.
- System probes on the client itself: `client.version()` (`GET /v1/version`) and `client.health()` (`GET /v1/health`). `health()` takes the **bare-body** path — `/v1/health` is the one endpoint that returns no `{success,data}` envelope — and resolves a degraded `503` as data instead of throwing, so a status check reads `status`/`checks` without a `try`/`catch`. Named to be unmistakable from the per-line `client.lines.health(id)`: this one is the global backend probe on the client.
- `coverage.test.ts` guard: for every `/v1` operation in the published `openapi.json`, asserts an SDK wrapper exists (or is listed as deliberately un-wrapped, each with a reason). The expected set is derived from the spec, not hand-written, so a newly published endpoint reddens the suite until it is wrapped.

### Changed
- `src/generated/openapi.d.ts` is now generated from the **published** contract. `packages/backend/openapi.json` carries only the partner-facing `/v1/*` surface, so the internal operator routes (`/admin`, `/va`, `/farm-ops`, `/kam`, `/auth`, …) and the bridge-only `/v1/device/*` contract — plus every schema reachable only from them — are no longer emitted into the SDK types. Those routes are the dashboard, the ops back-office and the device bridge; none of them is part of the partner contract this SDK wraps, and no `ISnapClient` method referenced their types, so no documented method changes shape.

### Fixed
- `User-Agent` misreported the SDK version. `SDK_VERSION` was left at `1.0.0` through `package.json`'s bumps to `1.1.0` and `1.2.0`, and it is the sole input to the header, so every request from the published package mis-identified its own version to server, proxy and CDN logs. `SDK_VERSION` now tracks `package.json` (`1.2.0`), and `src/version.test.ts` reads the published version off disk and fails the build the next time they disagree.
- **`ReactionEvent.data` is now the flat contract §4.3.2 payload** (`parent_message_id`, `reaction_id`, `reaction_type`, `reaction_emoji`, `direction`, a direction-appropriate handle, and one of `received_at` / `added_at` / `removed_at`). The previous `{ reaction: Reaction }` nesting never matched any delivery, so the documented happy path (`event.data.reaction.id`) always threw `TypeError` — with TypeScript's blessing. `reaction_type` now also admits `custom` and `reaction_emoji` is nullable, neither of which the borrowed REST `Reaction` schema could express. The new `ReactionEventData` is a union discriminated on `direction`, so reading `from_handle` on an outbound payload (or `actor_handle` on an inbound one) is a compile error — **source-breaking for consumers that read those keys without narrowing**, though no such consumer could have been working at runtime. No wire, schema, or `api_version` change: the producers were already correct.
- `packages/sdk/examples/` is now linted and typechecked. `lint` covers `examples/`, and they are in `tsconfig.json` itself — so the single `tsc --noEmit` pass sees them and, more importantly, so does the editor: a mistake in an example is red as you type rather than minutes later in CI, which is how these defects reached a published tarball in the first place. `tsconfig.build.json` carries its own `include` + `rootDir` so emit stays bounded to `src/` no matter how the editor program grows, and `dist/` is byte-identical to before. That gap had let four defects ship in reference code: `handle-webhook.ts` imported the non-existent `isTypingEvent` (it is `isTypingIndicatorEvent`) so the example died at import, dereferenced `event.data.reaction.id`, and dropped every `pre_order.*` delivery into its unhandled `else` (no `isPreOrderEvent` branch); `upload-attachment.ts` sent `attachment_ids` instead of the contract's `attachments`.
- Regenerated `src/generated/openapi.d.ts` against API `1.3.0`. `lookup.check()` now types the `202` pending trio (`pending`, `probe_id`, `retry_after`), so the re-poll branch is reachable without a cast.
- `verify:generated` script + `src/generated.test.ts` assert the committed types are byte-identical to `openapi-typescript` output, so the published tarball can no longer ship a stale contract.
