Files
2026-05-27 23:29:53 +01:00

13 KiB

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.