You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 659c7a4
Browse filesBrowse the repository at this point in the historyBrowse files
docs(agent-context): document TPH/discriminator inheritance in all ports + Kotlin snapshot gate
An AI session reading the codegen skill references concluded discriminator-based
(TPH — table-per-hierarchy) inheritance persistence "isn't supported" — because
only references/csharp.md documented it. TPH is fully supported and codegen-
emitted in all 5 ports (single shared table + discriminator + polymorphic
subtype CRUD, conformance-gated), but the Kotlin/Java/Python/TS codegen skill
references had ZERO mentions, so an agent saw no support and wrongly said so.
Root-cause doc fix:
- Add source-grounded TPH coverage to metaobjects-codegen/references/{kotlin,java,
python,typescript}.md (each port's real idiom: Kotlin Exposed single-table +
polymorphic controller, Java Spring-JPA, Python SQLAlchemy-polymorphic + FastAPI,
TS Drizzle single-table + Fastify) — grounded in the actual generators (KotlinTphPlan
/ TphPlan / tph_plan.py / tph-discriminator.ts + runtime-ts/src/tph.ts).
- Add a "Discriminator inheritance (TPH)" note to metaobjects-authoring/SKILL.md.
- Add a "Discriminator-based inheritance (TPH) with persistence" section to
docs/features/abstracts-and-inheritance.md (YAML example + per-port ORM mapping +
runtime subtype contract).
- Regenerate fixtures/agent-context-conformance/ goldens (agent-context-conformance
test green). NOTE: the all-or-nothing regen also corrects PRE-EXISTING stale
verify/AGENTS/CLAUDE goldens (the live-DB-warning content from 49269da was never
regenerated) — unrelated to TPH, swept in because goldens regenerate as a set.
Kotlin snapshot ("finish that work off"): add the entity-with-tph byte-level
snapshot fixture to codegen-kotlin so the generated AuthTable.kt (single table,
discriminator col, nullable subtype cols) + polymorphic AuthController.kt
(discriminator inject-from-URL, subtype-scoped CRUD, cross-subtype 404, immutable
discriminator) are gated at the unit level, not just integration.
KotlinCodegenSnapshotTest 13/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ew1XfYSbEAezxjs9opynAe
Copy file name to clipboardExpand all lines: agent-context/skills/metaobjects-codegen/references/java.md
+19-3Lines changed: 19 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -82,13 +82,29 @@ first group together:
82
82
83
83
| Generator | Output |
84
84
|---|---|
85
-
|`SpringControllerGenerator`|`<Entity>Controller.java` per writable entity (`source.rdb``@kind="table"`) — Spring Web MVC, five CRUD endpoints on the cross-port REST contract (`?sort`, `?limit`/`?offset`, `?withCount=1` envelope, 404/400 envelopes) |
86
-
|`SpringDtoGenerator`|`<Entity>Dto.java` as a Java 21 `record`; wrapped primitives (`Long`/`Integer`/`Boolean`) so missing JSON props deserialise to `null`; currency = `Long` (integer minor units) |
87
-
|`SpringRepositoryGenerator`|`<Entity>Repository.java` — a hand-stubbed `interface` the consumer implements with their persistence layer (Spring Data JPA / jOOQ / JDBC) |
85
+
|`SpringControllerGenerator`|`<Entity>Controller.java` per writable entity (`source.rdb``@kind="table"`) — Spring Web MVC, five CRUD endpoints on the cross-port REST contract (`?sort`, `?limit`/`?offset`, `?withCount=1` envelope, 404/400 envelopes). A TPH `@discriminator` base emits ONE controller: polymorphic `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable.|
86
+
|`SpringDtoGenerator`|`<Entity>Dto.java` as a Java 21 `record`; wrapped primitives (`Long`/`Integer`/`Boolean`) so missing JSON props deserialise to `null`; currency = `Long` (integer minor units). A TPH `@discriminator` base's DTO is the **union** of every subtype's columns (subtype-only fields folded nullable, validation dropped), so one wire shape backs the polymorphic + per-subtype endpoints.|
87
+
|`SpringRepositoryGenerator`|`<Entity>Repository.java` — a hand-stubbed `interface` the consumer implements with their persistence layer (Spring Data JPA / jOOQ / JDBC). For a TPH base the interface is polymorphic + per-subtype-scoped (`listByType`/`findByIdAndType`/`createWithType`/`updateByIdAndType`/`deleteByIdAndType`) over the single table; subtype entities emit no own controller/DTO/repository — they fold into the base.|
88
88
|`SpringPayloadGenerator`| a Java 21 `record` per template payload VO |
89
89
|`SpringOutputParserGenerator`| the `template.output` parser-on-receipt (see the prompts reference) |
Copy file name to clipboardExpand all lines: agent-context/skills/metaobjects-codegen/references/kotlin.md
+19-3Lines changed: 19 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -94,10 +94,10 @@ All live in `metaobjects-codegen-kotlin` under
94
94
95
95
| Generator | Output |
96
96
|---|---|
97
-
|`KotlinEntityGenerator`|`<Entity>.kt` — `@Serializable data class` per `object.entity` / `object.value`|
98
-
|`KotlinExposedTableGenerator`|`<Entity>Table.kt` — Exposed `Table` object (PK + FK + `@storage` columns) for entities with `source.rdb`|
97
+
|`KotlinEntityGenerator`|`<Entity>.kt` — `@Serializable data class` per `object.entity` / `object.value`. A TPH `@discriminator` base's data class is the **union** of every subtype's columns (each folded nullable, validation dropped) so one wire shape backs the polymorphic + per-subtype endpoints.|
98
+
|`KotlinExposedTableGenerator`|`<Entity>Table.kt` — Exposed `Table` object (PK + FK + `@storage` columns) for entities with `source.rdb`. A TPH `@discriminator` base emits ONE `Table` for the whole hierarchy — every subtype-only column folded in `.nullable()` (a row of another subtype stores null there) — single-table inheritance; subtype entities emit no table of their own.|
99
99
|`KotlinRelationsGenerator`|`<Entity>Relations.kt` — extension fns for `@cardinality="many"` query helpers |
100
-
|`KotlinSpringControllerGenerator`|`<Entity>Controller.kt` — Spring `@RestController`, five CRUD endpoints on the cross-port REST contract, for writable entities (`source.rdb``@kind="table"`) |
100
+
|`KotlinSpringControllerGenerator`|`<Entity>Controller.kt` — Spring `@RestController`, five CRUD endpoints on the cross-port REST contract, for writable entities (`source.rdb``@kind="table"`). A TPH `@discriminator` base emits ONE controller: polymorphic `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete are scoped to the subtype (cross-subtype → 404); the discriminator is immutable.|
101
101
|`KotlinPayloadGenerator`|`<Template>Payload.kt` — `@Serializable` payload data class from a template's `@payloadRef`|
102
102
|`KotlinOutputParserGenerator`| the `template.output` parser-on-receipt (see the prompts reference) |
103
103
|`KotlinValidatorGenerator`|`MetadataStartupValidator.kt` + `ExposedTableValidator.kt` (once per project) |
@@ -108,3 +108,19 @@ All live in `metaobjects-codegen-kotlin` under
108
108
Metadata lives under `src/main/metaobjects/` in the same canonical JSON the other
109
109
ports read — fused-key form, `source.rdb` + `@table`, `@column` for a renamed
Copy file name to clipboardExpand all lines: agent-context/skills/metaobjects-codegen/references/python.md
+18-2Lines changed: 18 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -37,12 +37,28 @@ a renamed physical column).
37
37
38
38
| Stable name | Output |
39
39
|---|---|
40
-
|`entity`| one **Pydantic model** per `object.entity` / projection (the `entity-model` generator): typed fields from the metadata, nullability from `@required`, `@maxLength`/validators, enum fields → a Python `Enum`. This is the typed data model. |
41
-
|`routes`| a **FastAPI `APIRouter`** per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). The router declares a repository **`Protocol`** you implement and inject. |
40
+
|`entity`| one **Pydantic model** per `object.entity` / projection (the `entity-model` generator): typed fields from the metadata, nullability from `@required`, `@maxLength`/validators, enum fields → a Python `Enum`. This is the typed data model. A TPH concrete subtype (`@discriminatorValue`) pins the inherited `@discriminator` field to a `Literal[...]` so the model rejects a foreign-subtype tag. |
41
+
|`routes`| a **FastAPI `APIRouter`** per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). The router declares a repository **`Protocol`** you implement and inject. A TPH `@discriminator` base emits ONE polymorphic router: `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable. Its repository `Protocol` is subtype-keyed (`subtype=None` for the polymorphic base) so your implementation applies the single-table discriminator scope. |
42
42
|`filter-allowlist`| per-entity filter allowlist (FR-009 — the server-side field+operator allowlist the routes validate against). |
43
43
|`payload` / `output-parser` / `output-prompt` / `extractor` / `render-helper` / `trace-helper`| the `template.output` prompt-pillar artifacts — see the **prompts** reference. |
44
44
|`template`| the generic Mustache `template` primitive. |
|`entityFile()`|`<Entity>.ts` — Drizzle table + FK `.references()` + `relations()` + inferred types + Zod insert/update schemas + `<Entity>FilterAllowlist` / `<Entity>SortAllowlist`. A TPH `@discriminator` base folds every subtype's columns into ONE Drizzle table (subtype-only columns nullable, no default — single-table inheritance) and emits a discriminated-union type + per-subtype Zod schemas + a `parse<Base>` dispatcher; subtype entities emit no table of their own.|
|`routesFile()`|`<Entity>.routes.ts` — Fastify CRUD routes on the cross-port REST contract. `routesFileHono()` is the Hono/Workers variant |
74
+
|`routesFile()`|`<Entity>.routes.ts` — Fastify CRUD routes on the cross-port REST contract. `routesFileHono()` is the Hono/Workers variant. A TPH `@discriminator` base mounts polymorphic `GET /<base>(+/:id)` plus a per-subtype CRUD set at `<basePath>/<discriminatorValue lowercased>` — create omits the discriminator (the URL names the subtype; the runtime injects it); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable via the runtime `discriminator` option.|
75
75
|`barrel()`|`index.ts` re-exporting each `<Entity>.ts` (one-shot, not per-entity) |
76
76
|`promptRender()`|`render<Name>()` per `template.prompt`|
77
77
|`outputParser()`|`<Name>.output.ts` (`parse*` / `safeParse*`) per `template.output`|
78
78
79
+
## Discriminator inheritance (TPH)
80
+
81
+
The TS reference implementation fully supports **table-per-hierarchy (TPH)
82
+
inheritance** (`tph-discriminator.ts` is the shared descriptor): an `object.entity`
83
+
carrying `@discriminator` (naming a `field.enum`) is the base; concrete entities
84
+
that `extends` it and declare `@discriminatorValue` are its subtypes, all persisted
85
+
to the base's **single** Drizzle table (single-table inheritance). `entityFile()`
86
+
folds each subtype's columns into that table nullable and emits the
87
+
discriminated-union type + per-subtype Zod schemas + a `parse<Base>` dispatcher;
88
+
`routesFile()` mounts polymorphic reads + per-subtype CRUD scoped by the
89
+
discriminator. At runtime, `@metaobjectsdev/runtime-ts`'s ObjectManager enforces the
90
+
subtype contract: it injects the discriminator on create, scopes every
91
+
read/update/delete to the subtype (a foreign-subtype row is invisible), and treats
92
+
the discriminator as immutable — mirroring the generated per-subtype route's
93
+
cross-subtype 404. Conformance-gated by `fixtures/api-contract-conformance/tph`
94
+
(HTTP wire shape) and `fixtures/persistence-conformance/tph-*` (single-table
95
+
runtime semantics).
96
+
79
97
## Docs — `meta docs` (one door, two surfaces)
80
98
81
99
Documentation is NOT a `meta gen` generator. The single door is the `meta docs`
0 commit comments