Files
suaveplan/openspec/changes/schema-registry-completion/tasks.md
2026-05-27 23:29:53 +01:00

151 lines
13 KiB
Markdown

# 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<n>]` 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<string, SchemaDefinition[]>` for versions and `Map<string, SubjectConfig>` for configs
- [ ] 6.2 Create `src/store/database/database.ts` exporting `DatabaseSchemaStore<TAdapter extends DatabaseAdapter>` (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/<yyyy-mm-dd>-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<TAdapter>` (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.