# Tasks — Schema-registry completion > Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. ## 0. Pre-flight (non-negotiable) - [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each) - [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest. ## 1. Spec authoring - [ ] 1.1 Read legacy `packages/universal/schema-registry/src` end-to-end at `../suaveplan/` - [ ] 1.2 Read genesis `packages/core/schema-registry/src` end-to-end and `openspec/specs/` (no canonical schema-registry spec exists yet — this proposal seeds it) - [ ] 1.3 Read `openspec/AGENTS.md` Sections 13 (spec-conformance audit), 14 (source mapping for migrations); §14a (proposal must list affected dependents); §0a (parity checklist) - [ ] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md` - [ ] 1.5 Fill `## Source mapping` in `proposal.md` — every requirement cites a line range OR appears in the "no source line" table with justification - [ ] 1.6 Draft `specs/schema-registry/spec.md` from `openspec/templates/spec-delta.md` — seven ADDED requirements (transitive, diff, store interface, memory store, database store, canonical utilities, subject config) - [ ] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles - [ ] 1.8 Draft `## Migration` section listing the `CompatibilityLevel` union widening and the new async constructor option - [ ] 1.9 `bunx openspec change validate schema-registry-completion` exits with no errors ## 2. Implementation — types - [ ] 2.1 Extend `CompatibilityLevel` in `src/types/schema-registry.types.ts` to include `BACKWARD_TRANSITIVE`, `FORWARD_TRANSITIVE`, `FULL_TRANSITIVE` - [ ] 2.2 Add `SchemaField` interface in `src/types/schema-registry.types.ts` (legacy L15-L26); used by diff and canonical utilities - [ ] 2.3 Add `DiffChangeType` union (`"added" | "removed" | "modified"`) - [ ] 2.4 Add `SchemaDiffChange` and `SchemaDiffResult` interfaces - [ ] 2.5 Add `TransitiveReport` and `TransitiveVersionResult` interfaces - [ ] 2.6 Add `SubjectConfig` interface (`compatibility`, `maxVersions?`, `metadata?`) - [ ] 2.7 Add `SchemaStoreAdapter` interface (8 async methods per legacy L82-L105) - [ ] 2.8 Add optional `fingerprint?: string` to `SchemaDefinition` ## 3. Implementation — utilities - [ ] 3.1 Create `src/utils/canonical/canonical.ts` exporting `canonicalizeSchema`, `computeFingerprint`, `djb2Hash`, `flattenFields`, `isOptionalOrHasDefault` (legacy `src/utils/utils.ts` L1-L70 verbatim) - [ ] 3.2 Sibling `canonical.md` ≥200 words with basic + advanced examples ## 4. Implementation — transitive compatibility - [ ] 4.1 Create `src/core/transitive/transitive.ts` exporting `checkTransitiveCompatibility(history, candidate, mode)`, `buildTransitiveReport(history, candidate, mode)`, `isTransitiveMode(mode)`, `stripTransitive(mode)` - [ ] 4.2 Use the genesis `checkCompatibility` directly (do not duplicate the rules); transitive entry points iterate history and aggregate errors with `[v]` prefixes (legacy convention, L31) - [ ] 4.3 Sibling `transitive.md` ≥200 words ## 5. Implementation — diff - [ ] 5.1 Create `src/core/diff/diff.ts` exporting `diffSchemas(oldSchema, newSchema)` and `formatDiffReport(result)` (legacy `src/diff/schema-differ.ts` L1-L175 verbatim) - [ ] 5.2 Use `flattenFields` from canonical utilities; never duplicate the field-walking logic - [ ] 5.3 Sibling `diff.md` ≥200 words ## 6. Implementation — stores - [ ] 6.1 Create `src/store/memory/memory.ts` exporting `MemorySchemaStore` implementing `SchemaStoreAdapter` against an in-memory `Map` for versions and `Map` for configs - [ ] 6.2 Create `src/store/database/database.ts` exporting `DatabaseSchemaStore` (legacy `src/store/database-store.ts` L1-L147) - [ ] 6.3 Add `@suaveplan/db` to `packages/core/schema-registry/package.json` `dependencies` (`workspace:^`); verify `STATUS.md` lists `@suaveplan/db` as completed (it is — `db-sqlite` is shipped) - [ ] 6.4 Re-target the `DatabaseAdapter` import in the database store to `@suaveplan/db` (legacy used an inline interface) - [ ] 6.5 Sibling `memory.md` and `database.md` ≥200 words each ## 7. Implementation — registry async surface - [ ] 7.1 Extend `SchemaRegistry` constructor to accept `store?: SchemaStoreAdapter` in `RegistryOptions` - [ ] 7.2 When `store` is supplied, route persistence through the store; gate sync method calls with `SchemaRegistryError(SCHEMA_REGISTRY.STORE_FAILURE, ..., { context: { reason: "sync_call_on_async_store" } })` - [ ] 7.3 Add async siblings: `registerAsync`, `getLatestAsync`, `getVersionAsync`, `getAllAsync`, `listSubjectsAsync`, `deleteSubjectAsync`, `checkCompatibilityAsync` - [ ] 7.4 In `register` / `registerAsync`, compute `fingerprint = computeFingerprint(schema as SchemaDefinition)` when `format === "json-schema"` and the schema matches `SchemaDefinition` shape; stamp it onto the resulting `SchemaDefinition` - [ ] 7.5 De-dup: if the latest version's fingerprint equals the candidate's fingerprint, return the existing version (no new version assigned) - [ ] 7.6 Wire the new `*_TRANSITIVE` modes into the existing `register` flow: when `state.compatibility` ends with `_TRANSITIVE`, call `checkTransitiveCompatibility(state.versions, schema, state.compatibility)` instead of `checkCompatibility(schema, latest, ...)` - [ ] 7.7 Validate `setCompatibility(subject, level)` against the seven-mode union; throw `SchemaRegistryError(SCHEMA_REGISTRY.UNKNOWN_COMPATIBILITY_MODE, ...)` on invalid input ## 8. Errors - [ ] 8.1 Add `STORE_FAILURE` and `UNKNOWN_COMPATIBILITY_MODE` to `SCHEMA_REGISTRY_CODES` in `src/errors/schema-registry-error.ts` - [ ] 8.2 Update sibling `schema-registry-error.md` to document the two new codes ## 9. Wiring - [ ] 9.1 Update `packages/core/schema-registry/src/index.ts` to re-export every new symbol (transitive helpers, diff, stores, canonical utilities, types) - [ ] 9.2 Update `packages/core/schema-registry/package.json` `exports` map: add `./transitive`, `./diff`, `./store/memory`, `./store/database`, `./utils/canonical` subpath entries - [ ] 9.3 Update root `README.md` `## Modules` section to link the new co-located `.md` files - [ ] 9.4 Update the package description in `package.json` to mention transitive compatibility, diff, and pluggable stores ## 10. Tests - [ ] 10.1 `src/core/transitive/transitive.test.ts` — multi-version history; `BACKWARD_TRANSITIVE` rejects when ANY old version disagrees; `FORWARD_TRANSITIVE` and `FULL_TRANSITIVE` analogous; per-version error prefixes `[v1]`, `[v2]`, ... - [ ] 10.2 `src/core/transitive/transitive.spec-conformance.test.ts` — exhaustive `isTransitiveMode` / `stripTransitive` over all seven `CompatibilityLevel` values - [ ] 10.3 `src/core/diff/diff.test.ts` — added / removed / modified / type-changed; nested-field diff; `formatDiffReport` rendering with `[BREAKING]` annotation - [ ] 10.4 `src/core/diff/diff.spec-conformance.test.ts` — `fast-check` property `diffSchemas(a, a).changes === []`; `formatDiffReport` empty result text - [ ] 10.5 `src/store/memory/memory.test.ts` — round-trip every method of `SchemaStoreAdapter`; `deleteSubject` returns `false` when subject absent - [ ] 10.6 `src/store/memory/memory.spec-conformance.test.ts` — pins the contract (every method present, every method async) - [ ] 10.7 `src/store/database/database.test.ts` — against a fake `DatabaseAdapter` that records SQL; verifies `INSERT` / `SELECT` / `DELETE` issued correctly with positional params - [ ] 10.8 `src/store/database/database.spec-conformance.test.ts` — error path: `db.query` rejects → `STORE_FAILURE` raised with cause-chain preserving the original error - [ ] 10.9 `src/utils/canonical/canonical.test.ts` — `canonicalizeSchema` sorts at every level; `computeFingerprint` deterministic; `djb2Hash` matches known vectors; `flattenFields` produces dot-paths; `isOptionalOrHasDefault` truthy/falsy - [ ] 10.10 `src/utils/canonical/canonical.spec-conformance.test.ts` — `fast-check` property `canonicalizeSchema(a) === canonicalizeSchema(shuffleFields(a))` (canonicalisation is field-order independent) - [ ] 10.11 Extend `src/core/registry/schema-registry.test.ts` — async surface end-to-end with `MemorySchemaStore`; verify `register` de-dup based on fingerprint; verify transitive mode triggers transitive checker - [ ] 10.12 Extend `src/core/registry/schema-registry.spec-conformance.test.ts` — `UNKNOWN_COMPATIBILITY_MODE` thrown for invalid `setCompatibility`; `STORE_FAILURE` thrown when sync method called on store-backed registry - [ ] 10.13 Every `it(...)` in 10.1-10.12 carries `{ timeout: 5000 }` ## 11. Documentation - [ ] 11.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference) - [ ] 11.2 README links every co-located `.md` under `## Modules` - [ ] 11.3 README `## Features` enumerates the seven new capabilities (transitive, diff, store interface, memory store, database store, canonical utilities, subject config) - [ ] 11.4 Update `CLAUDE.md` package listing for `@suaveplan/schema-registry` to include the new exports ## 12. Pre-archive verification (the 5-gate triple) - [ ] 12.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo - [ ] 12.2 `bunx turbo lint typecheck test --filter=@suaveplan/schema-registry` — green - [ ] 12.3 `bunx turbo test:coverage --filter=@suaveplan/schema-registry` — 100/100/100/100 - [ ] 12.4 `bun scripts/openspec-conformance.ts @suaveplan/schema-registry` — exit 0, no `error` findings - [ ] 12.5 `bunx turbo build --filter=@suaveplan/schema-registry` — green ## 13. Pre-archive 7-row checklist (AGENTS.md Section 13) - [ ] 13.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim (`CompatibilityLevel` widened, `SCHEMA_REGISTRY_CODES` keys, `DiffChangeType` literals) - [ ] 13.2 Zod schemas wired at every declared boundary (`SchemaDefinition`, `SubjectConfig`, `SchemaField`, `RegistryOptions` extended) - [ ] 13.3 Every error code in `SCHEMA_REGISTRY_CODES` thrown by at least one production path (`STORE_FAILURE` and `UNKNOWN_COMPATIBILITY_MODE` covered above; existing four codes remain in place) - [ ] 13.4 Strict-mode behaviors tested on and off (transitive vs. non-transitive, store-backed vs. in-memory) - [ ] 13.5 OTel instruments emitted from production paths and asserted with `InMemorySpanExporter` from `@suaveplan/opentelemetry` (the `register.total` counter and `check.duration_ms` histogram are already in place; new transitive/store paths should record to the same instruments) - [ ] 13.6 Lifecycle dedup tested where claimed (re-register identical schema → fingerprint match → same `SchemaDefinition` returned, no version increment) - [ ] 13.7 No retroactive tick — every box above reflects work done in this change ## 14. Dependent sweep - [ ] 14.1 `bunx turbo test --filter=...@suaveplan/schema-registry` — green; `@suaveplan/events-schema` continues to pass - [ ] 14.2 Search dependents for exhaustive `switch` over `CompatibilityLevel`: `grep -rln "CompatibilityLevel" packages/ src/`. For each hit, verify the switch handles the three new transitive arms (or falls through to the corresponding non-transitive arm) - [ ] 14.3 Search dependents for `SchemaRegistry` constructor: `grep -rln "new SchemaRegistry\|createRegistry" packages/ src/`. None should require code changes — the new option is additive ## 15. Archive - [ ] 15.1 Fold `specs/schema-registry/spec.md` into `openspec/specs/schema-registry/spec.md` (this seeds the canonical capability spec, since one does not yet exist) - [ ] 15.2 Move `openspec/changes/schema-registry-completion/` → `openspec/archive/-schema-registry-completion/` - [ ] 15.3 Refresh `MANIFEST.md` (this change adds `@suaveplan/db` as a dep) ## Audit Note (2026-05-02) Audit verdict: **DEFERRED — 0/78 tasks evidenced in code**. Evidence reviewed: - `packages/core/schema-registry/src/` has only `core/{registry,compatibility}`, `errors/`, `types/`, `constants/`. No `core/transitive/`, no `core/diff/`, no `store/`, no `utils/canonical/`. - `types/schema-registry.types.ts` `CompatibilityLevel` does not include `*_TRANSITIVE` arms (verified by absence of any matching directory or test). - No `@suaveplan/db` dependency in `package.json` (DatabaseSchemaStore depends on it). Missing pieces: 1. Transitive compatibility (`core/transitive/`) and the seven-mode `CompatibilityLevel` widening. 2. Schema diff engine (`core/diff/`) + `formatDiffReport`. 3. Canonical schema utilities (`utils/canonical/`), `SchemaStoreAdapter` interface, `MemorySchemaStore`, `DatabaseSchemaStore` (requires adding `@suaveplan/db` dep), the async surface on `SchemaRegistry`, and the two new error codes. Decision: leave change open. New canonical spec file + dep graph change + async API surface require focused implementation work.