Skip to content

Commit 669e659

Browse files
dmealingclaude
andcommitted
docs: SP-H Phase 4 — per-subtype matrix confirmed + write-round-trip gate documented (CLAUDE.md/roadmap/spec)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 93657c5 commit 669e659

4 files changed

Lines changed: 50 additions & 3 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ _Last refreshed 2026-05-30._
3232
**Cross-port conformance corpora** (every port runs the shared corpus):
3333
- Metamodel: `fixtures/conformance/` (~90 fixtures). TS / C# / Java / Python all green.
3434
- Render: `fixtures/render-conformance/`. TS / C# / Java / Kotlin / Python byte-identical.
35-
- Persistence: `fixtures/persistence-conformance/`. **Query** scenarios run on every port (TS / C# / Java / Kotlin / Python), each provisioning its test DB by executing the committed, TS-produced `canonical/schema.postgres.sql` (Postgres only — Derby dropped for the cross-port query corpus, ADR-0015). The **migration** scenarios are exercised by **TS only** (TS owns schema migrations).
35+
- Persistence: `fixtures/persistence-conformance/`. **Query** scenarios run on every port (TS / C# / Java / Kotlin / Python), each provisioning its test DB by executing the committed, TS-produced `canonical/schema.postgres.sql` (Postgres only — Derby dropped for the cross-port query corpus, ADR-0015). The **migration** scenarios are exercised by **TS only** (TS owns schema migrations). **The corpus now gates WRITES, not just reads (SP-H):** an `op: roundtrip` scenario type INSERTs through each port's runtime/ORM write codec (NOT raw SQL), reads the row back, and asserts the wire-normalized value. The `AllTypes` entity (`roundtrip-all-types.yaml`) carries one field of **every** persistable `field.*` subtype — string/int/long/double/float/decimal/boolean/date/time/timestamp(+tz)/currency/enum/uuid/object — so every subtype write+read round-trips through every port against Testcontainers PG. (`field.byte`/`field.short`/`field.class` were cut as non-functional registration-only stubs — the matrix tracks only genuinely-supported subtypes; see `fixtures/registry-conformance/README.md` → "Per-subtype write-round-trip matrix".)
3636
- API-contract: `fixtures/api-contract-conformance/`. TS / C# / Java / Kotlin / Python all 20/20 — each port runs **two lanes**: a hand-rolled reference server AND its **generated** API artifact booted over HTTP (the deployed controller/routes; TS+C# full-stack vs Testcontainers PG, Java/Kotlin/Python generated controller + in-memory repo behind the consumer seam). The generated fan-out found 10 real deployment bugs golden snapshots missed.
3737
- YAML / verify corpora green across the ports that ship those layers.
3838

39-
**Key cross-language features shipped:** FR5 family (a/b/c/d/e + WARN envelope-shape — actionable loader errors per ADR-0009); FR-003 (Java RDB runtime persistence + projections; schema migrations are TS-only — the Java migration engine was removed); FR-006 (template.output parser-on-receipt codegen per ADR-0010 in all 5 ports); FR-008 + FR-009 (cross-port REST API contract + 10 filter operators); FR-018 (M:N relationship codegen in all 5 ports — entity navigation + idiomatic ORM wiring [Drizzle m2m / EF Core `UsingEntity` / Spring repo+JPA / Exposed / Pydantic+route as the SQLAlchemy-secondary equivalent] + REST traversal `GET /<source-plural>/{id}/<relation>` + Tier-2 docs, gated by the shared api-contract m2m corpus in both lanes + persistence-conformance; the TanStack M:N client hook is a deferred client-ergonomics follow-up); source v2 paradigm (ADR-0007); metadata-ktx Kotlin facade; per-target output directories (TS codegen).
39+
**Key cross-language features shipped:** FR5 family (a/b/c/d/e + WARN envelope-shape — actionable loader errors per ADR-0009); FR-003 (Java RDB runtime persistence + projections; schema migrations are TS-only — the Java migration engine was removed); FR-006 (template.output parser-on-receipt codegen per ADR-0010 in all 5 ports); FR-008 + FR-009 (cross-port REST API contract + 10 filter operators); FR-018 (M:N relationship codegen in all 5 ports — entity navigation + idiomatic ORM wiring [Drizzle m2m / EF Core `UsingEntity` / Spring repo+JPA / Exposed / Pydantic+route as the SQLAlchemy-secondary equivalent] + REST traversal `GET /<source-plural>/{id}/<relation>` + Tier-2 docs, gated by the shared api-contract m2m corpus in both lanes + persistence-conformance; the TanStack M:N client hook is a deferred client-ergonomics follow-up); SP-H (field-subtype end-to-end hardening: every concrete `field.*` subtype write+read round-trips cross-port via the persistence `op: roundtrip` gate; cut `field.byte`/`field.short`/`field.class` non-functional stubs; cross-port filter-op reconciliation for uuid/currency); source v2 paradigm (ADR-0007); metadata-ktx Kotlin facade; per-target output directories (TS codegen).
4040

4141
**Current release line.** `0.7.0-rc.2` accumulates the 0.6.x → 0.7.0 consumer-friction batch (stock `promptRender()` generator, `db`-parameter generated repo helpers per ADR-0008, Cloudflare Workers deploy recipe, CHANGELOG backfill + naming-convention docs) plus the FR5 + FR-003 Plan 4 + FR-006 Java work. GA promotion is the next release move.
4242

docs/superpowers/specs/2026-06-03-sp-h-field-subtype-hardening-design.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# SP-H — Field-Subtype End-to-End Hardening + Write-Path Conformance
22

33
**Date:** 2026-06-03
4-
**Status:** Designed (from the cross-port field-subtype audit; awaiting spec review before planning/execution)
4+
**Status:** **Phases 1-3 COMPLETE on branch `sp-g-registry-conformance` (Phase 4 finishing).** Cut `field.class` + `field.byte`/`field.short` (all ports + canonical); the persistence corpus now WRITES (`op: roundtrip`, `AllTypes` covers every persistable subtype) and is green on all 5 ports against Testcontainers PG; filter-op bands reconciled cross-port (uuid/currency) + a loader guard for `@filterable`-without-op-band + a filter-allowlist fixture; Java write-codecs (timestamp/currency/enum/native-uuid/time) + Spring `field.time` arm; TS decimal string-exact dataType; Kotlin runs the shared metamodel corpus + a mapper-`else` negative test; bare `field.object` rejected at load. Phase 4 (this unit): per-subtype matrix confirmed (every concrete `field.*` × metamodel / persistence-write-round-trip / codegen, all 5 ports — no residual hole) + docs.
5+
6+
**Validation — the write-gate (Theme 2) was the right call.** Making the corpus WRITE (not just read) is what surfaced the latent breakage "all corpora green" had masked, exactly as the spec predicted. The keystone (`op: roundtrip`) caught, per port: a **TS** enum-CHECK write defect; **Java** timestamp-write (generic `ObjectCodec` rejected `java.util.Date`), native-`uuid`-write, `field.time`-write, and jsonb-write hazards; a **Kotlin** `LocalTime`-write gap; a **C#** read-back-caching defect; and the **uuid/currency filter-op divergences** (filterable in TS, silently dropped elsewhere). None of these had a failing test before the write-gate existed — the structural complement to SP-G (which gated vocabulary) was the missing axis.
57
**Relates to:** the `field.byte`/`field.short` finding (a registration-only stub that worked nowhere — Kotlin codegen threw, Python mis-typed to `str`, TS emitted a `text` DB column, Java had no JDBC codec, zero functional tests; cut in `29057ad5`). The audit that followed proved byte/short was the canary, not the exception.
68

79
## Problem

fixtures/registry-conformance/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,46 @@ MO_UPDATE_COVERAGE_SNAPSHOT=1 bun test packages/metadata/test/registry-coverage.
347347
**Ratchet later.** Once the backlog is burned down (the untested-subtype set
348348
empties), this can be tightened to hard-fail on any untested subtype — turning
349349
the report into a gate that forces a fixture for every new vocabulary member.
350+
351+
## Per-subtype write-round-trip matrix (SP-H)
352+
353+
The registry-conformance gate (above) proves the field-subtype vocabulary is
354+
**identical across ports**, and the metamodel `fixtures/conformance/` corpus
355+
proves each subtype **loads**. SP-H closed the remaining axis: every concrete
356+
`field.*` subtype must also **write+read round-trip through each port's
357+
runtime/ORM** — not just be seeded by raw SQL and read back. That gate lives in
358+
the persistence corpus, not here, but it is recorded here so a future subtype
359+
added without a write-round-trip is visible:
360+
361+
| `field.*` subtype | metamodel corpus | persistence write-round-trip | codegen |
362+
|---|:--:|:--:|:--:|
363+
| string ||||
364+
| int ||||
365+
| long ||||
366+
| double ||||
367+
| float ||||
368+
| decimal ||||
369+
| boolean ||||
370+
| date ||||
371+
| time ||||
372+
| timestamp ||||
373+
| currency ||||
374+
| enum ||||
375+
| uuid ||||
376+
| object ||||
377+
378+
- **persistence write-round-trip** = the `op: roundtrip` scenario type in
379+
`fixtures/persistence-conformance/queries/roundtrip-all-types.yaml`: its
380+
`AllTypes` entity carries one field of every persistable subtype, INSERTed
381+
through each port's runtime/ORM write codec (NOT raw SQL) then read back and
382+
asserted against the wire-normalized `expect`. Run on all five ports
383+
(TS / C# / Java / Kotlin / Python) against Testcontainers Postgres.
384+
- **codegen** = each port emits the `AllTypes` entity + its `all_types` table
385+
(the committed TS-produced `canonical/schema.postgres.sql`), and the
386+
api-contract corpus boots each port's generated artifact.
387+
388+
`field.byte` / `field.short` / `field.class` are **absent by design** — they
389+
were cut as non-functional registration-only stubs (byte/short in `29057ad5`,
390+
class in SP-H), so the matrix tracks only genuinely-supported subtypes. Adding a
391+
new persistable `field.*` subtype means adding a column to `AllTypes` (and a row
392+
here) so the write gate covers it from day one.

spec/roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ _Last refreshed 2026-05-30._
6060
- **OMDB Spring Boot 3 starter** (shipped 2026-05-30). Autoconfiguration wires a `DataSource``ObjectManagerDB` with Spring-tx; closes the OMDB-modernization open question (jOOQ migration ruled out as a non-goal).
6161
- **FR-008/FR-009 — Cross-port REST API contract + 10 filter operators.** Shipped in all 5 ports.
6262
- **FR-018 — Many-to-many relationships.** Shipped in all 5 ports. Slim vocabulary (`@through`/`@sourceRefField`/`@symmetric`, with `@joinEntity`/`@joinFields` removed; FK fields derived from the junction's `identity.reference` SSOT) + a generic runtime M:N resolver (TS / Java / Kotlin / Python / C#) + codegen (entity navigation + idiomatic ORM wiring [Drizzle m2m / EF Core `UsingEntity` / Spring repo+JPA / Exposed / Pydantic+route as the SQLAlchemy-secondary equivalent]) + REST traversal (`GET /<source-plural>/{id}/<relation>`) + Tier-2 docs. Gated by the shared api-contract m2m corpus in both lanes (reference-server + generated-artifact-over-HTTP) + persistence-conformance, across hetero / directed-self-join / symmetric modes. Remaining: a typed TanStack M:N client hook (client-ergonomics follow-up). Designed in `docs/superpowers/specs/2026-06-02-fr-018-many-to-many-relationship-design.md`.
63+
- **SP-G — registry-conformance gate.** Shipped in all 5 ports (TS / C# / Java / Kotlin / Python, live + green). A structural enforcer of the "metamodel subtype vocabularies must be identical across languages" contract: each port emits its registry as a canonical, fully-sorted, byte-stable JSON manifest matched to the single committed `fixtures/registry-conformance/expected-registry.json`. Reconciled the Java/Kotlin registries at source (the JVM runners compose from the defined metamodel provider set so codegen/om SPI doesn't pollute the measured vocabulary). Plus an untested-vocabulary coverage report (`coverage-report.json`) surfacing every registered `(type, subType)`/attr no fixture exercises. See `fixtures/registry-conformance/README.md`.
64+
- **SP-H — field-subtype end-to-end hardening + write-path conformance.** Shipped in all 5 ports. Every concrete `field.*` subtype now write+read round-trips through each port's runtime/ORM, gated cross-port: the persistence corpus gained an `op: roundtrip` scenario type (INSERT via the port's write codec, NOT raw SQL → read-back → assert wire-normalized value) whose `AllTypes` entity carries one field of every persistable subtype against Testcontainers PG — the structural complement to SP-G (which gated vocabulary). Cut `field.byte`/`field.short`/`field.class` as non-functional registration-only stubs; reconciled uuid/currency filter-op bands cross-port + a loader guard for `@filterable`-without-op-band; Java write-codecs (timestamp/currency/enum/native-uuid/time) + Spring `field.time` arm; TS decimal string-exact dataType; Kotlin runs the shared metamodel corpus + a mapper-`else` negative test; bare `field.object` rejected at load (ADR-0013). The write-gate surfaced + drove latent breakage "all corpora green" had masked (TS enum-CHECK, Java timestamp/native-uuid/time/jsonb writes, Kotlin LocalTime-write, C# read-back-caching, the filter-op divergences). Designed in `docs/superpowers/specs/2026-06-03-sp-h-field-subtype-hardening-design.md`.
6365
- **Per-target output directories (TS codegen).** Each generator routes to a named output target (`{ outDir, importBase?, outputLayout?, dbImport? }`).
6466
- **0.6.x → 0.7.0 consumer-friction batch.** Stock `promptRender()` generator; `db`-parameter generated repo helpers (ADR-0008); Cloudflare Workers deploy recipe; CHANGELOG.md backfill + camelCase ↔ snake_case docs. (Currently published as `0.7.0-rc.2`; GA promotion is the next release move.)
6567

0 commit comments

Comments
 (0)