# Migrate @suaveplan/api-plugin-contacts to genesis at enterprise quality **Package**: `@suaveplan/api-plugin-contacts` **Status**: PROPOSED **Type**: new-package ## Why `@suaveplan/api-plugin-contacts` is the HTTP face of `@suaveplan/contacts`. The upstream version on `main` shipped to 1.0.0 with ~30 `as any` casts in route handlers, untyped `buildTree`, no shared error hierarchy, no OTel instrumentation, no real-database integration tests, and `maxGroupDepth` declared in config but never enforced. Genesis policy forbids all of those defects, so the package is being rebuilt at 0.1.0 against the genesis `@suaveplan/contacts` store interfaces (`ContactStore` / `GroupStore` / `MembershipStore` with `generateId`) rather than the upstream port. Migrating closes the `contacts` plugin family — the first two siblings (`@suaveplan/contacts` and `@suaveplan/macos-contacts`) are already at gold standard. ## What changes - ADDED: `createContactsPlugin(options?: ContactsPluginOptions): PluginDescriptor` — factory mounting `/contacts` and `/groups` route trees on the host `OpenAPIHono`. - ADDED: `ContactsPluginOptionsSchema`, `ContactsPluginOptions`, `ContactsPluginConfig` — Zod-validated configuration. - ADDED: `SurrealContactRepository`, `SurrealGroupRepository`, `SurrealMembershipRepository` — `@suaveplan/contacts` store-interface implementations over `SurrealAdapter`. - ADDED: `CONTACTS_API_CODES` / `ContactsApiError` (`@suaveplan/error` based) — 12 stable codes covering every failure path. - ADDED: `getContactsApiInstruments` / `resetContactsApiInstruments` — 6 OTel instruments (4 counters + 2 histograms) emitted from production hot paths. - ADDED: Typed Hono `ContextVariableMap` augmentation — `contactRepository`, `groupRepository`, `membershipRepository`, `eventBus` slots (eliminates upstream's `as any` casts). - ADDED: `ServiceRegistry` augmentation — exposes `contactRepository` / `groupRepository` / `membershipRepository` as typed services to sibling plugins. - ADDED: SurrealQL schema files for `contact`, `contact_group`, `group_membership` tables and the `MigrationRunner` integration. - ADDED: Bulk-import endpoint honouring `maxImportBatchSize` with per-row error reporting (Zod-validated). - ADDED: Repository-level `maxGroupDepth` enforcement (`createGroup` and `moveGroup` raise `CONTACTS_API.GROUP_DEPTH_EXCEEDED`). - ADDED: Typed `buildTree(root: Group, descendants: readonly Group[]): GroupTreeNode` helper. - ADDED: Integration tests using `@suaveplan/testing-integration-surrealdb` testcontainers — real Surreal v3, no mocks. ## Out of scope - CSV / VCF import parsing — already lives in `@suaveplan/contacts/import-export/contact-importer`; the bulk-import endpoint accepts pre-parsed JSON rows. - GDPR export / erase, schema-registry versioning, audit-log integration, rate-limiting, dedup, domain normalisation — `enterprise/api-plugin-contacts.md` §3-4 list these as *plugin-composition* improvements. They belong on the host (already mountable via `@suaveplan/api-plugin-audit`, `@suaveplan/api-plugin-rate-limit`, `@suaveplan/api-plugin-export`); duplicating them inside this plugin would re-publish behaviour the host owns. - Sync adapters (Google / iCloud / Outlook) and a saved-segments rules engine — `enterprise/api-plugin-contacts.md` §4 items 8/3, deferred to dedicated plugins. ## Source mapping This change derives from `../suaveplan/enterprise/api-plugin-contacts.md` and the upstream source at `../suaveplan/packages/server/api-plugin-contacts/src/`. | Requirement | Source lines | Notes | |---|---|---| | Re-export `ContactsConfigSchema` + `createContactsPlugin` from root | L57, L98, L113 | verbatim; genesis names them `ContactsPluginConfigSchema` + `createContactsPlugin` | | `maxImportBatchSize` (default 1000) and `maxGroupDepth` (default 10) config | L12, L49 | verbatim | | `/contacts` + `/groups` route trees with `/groups/:id/members` and `/groups/:id/resolve` | L12, L37-L38 | upstream files `routes/contacts/*`, `routes/groups/*` | | `SurrealContactRepository`, `SurrealGroupRepository`, `SurrealMembershipRepository` exported | L21 | verbatim | | Hierarchical group nesting with materialised `path` + `depth` | L12, L49 | upstream `group.repository.ts` L19-L58 | | Enforce `maxGroupDepth` in repository on insert + move | L49, L99 | upstream gap closed; raises `GROUP_DEPTH_EXCEEDED` | | Bulk-import endpoint honouring `maxImportBatchSize` with per-row errors | L50, L99-L100 | upstream `routes/contacts/contacts.routes.ts` L218-L263 baseline; genesis adds Zod boundary + typed errors | | Merge contacts endpoint (channel + tag union) | upstream `contacts.routes.ts` L265-L331 | preserved | | Group subtree (`GET /groups/:id/tree`) + reparent (`POST /groups/:id/move`) | upstream `groups.routes.ts` L206-L270 | preserved; `buildTree` becomes fully typed | | Group membership add / remove / list / resolve | upstream `groups.members.routes.ts` L24-L196 | preserved | | Zod schemas on every HTTP body | L79 | verbatim; `@hono/zod-openapi` adapter | | Real-SurrealDB integration tests via `test:with-db:filter` | L107, L118 | genesis uses `@suaveplan/testing-integration-surrealdb` testcontainers | | `@suaveplan/error` hierarchy with typed code registry | L81, L105 | upstream gap closed (12 codes) | ### Requirements with no source line | Requirement | Justification | |---|---| | OpenTelemetry instruments emitted from production paths | Genesis-wide observability policy (`CLAUDE.md` Code Organization) — every plugin emits counters + histograms; not package-specific in upstream `enterprise/*.md`. | | Typed Hono `ContextVariableMap` augmentation in place of `as any` casts | Closes a defect in the upstream port (~30 `as any` casts in `routes/**`) flagged by genesis CLAUDE.md rule 9 (no static-analysis suppression / cast-only escape hatches). | | 100% Istanbul coverage threshold | Genesis-wide policy. | | `@suaveplan/testing/runner` for all test primitives | CLAUDE.md rule 11. | ## Verification approach - `src/error-codes/error-codes.test.ts` — covers code registry stability; pinned by `tests/dep-graph.test.ts` for SuaveplanError lineage. - `src/otel/instruments.test.ts` — asserts every instrument binds against a real in-memory `MeterProvider` after `resetContactsApiInstruments`. - `src/repositories/contact-repository.test.ts`, `src/repositories/group-repository.test.ts`, `src/repositories/membership-repository.test.ts` — unit-shape tests for query builder + mapping with an injectable adapter contract. - `src/routes/contacts/build-routes.test.ts`, `src/routes/groups/build-routes.test.ts` — route-tree composition and shape assertions. - `src/plugin/plugin.test.ts` — plugin descriptor validation, ServiceRegistry slot registration, middleware injection. - `src/api-plugin-contacts.spec-conformance.test.ts` — pins every public-API claim: exported symbols, error-code registry, OTel instrument names, route paths, ServiceRegistry slots. - `tests/integration/full-boot.test.ts` — boots auth + database + contacts plugins against real SurrealDB testcontainers and walks the full CRUD + membership + audience-resolve roundtrip. - `tests/integration/group-depth.test.ts` — pins the `maxGroupDepth` repository guard against real SurrealDB. - `tests/integration/bulk-import.test.ts` — pins per-row error reporting against real SurrealDB. ## Migration impact None — additive only. No consumer of `@suaveplan/api-plugin-contacts` exists in genesis yet.