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
fix(loader): reject a projection that inherits its source through extends
All four loaders now emit ERR_PROJECTION_INHERITED_SOURCE for a CONCRETE
object.projection that inherits a source.* via extends instead of declaring its
own. No new vocabulary, no codegen change, no registry change.
The shape produced a broken artifact, differently in every port, because two
source predicates disagree by design: "which source am I bound to" resolves
through the super chain (an entity legitimately inherits its table — TPH /
BaseEntity, and making that resolving fixed a real JVM bug), while "what KIND of
source am I" is own-only (projection-ness is a property of the declaring object).
In TypeScript hasAnyRdbSource selected the object for route generation while
isProjection returned false, so routes-file fell through to the writable branch
and mounted full mountCrudRoutes POST/PATCH/DELETE over a read-only view —
against a Drizzle binding entity-file never emitted. Java and Kotlin skipped on
their subtype gate, Python on the resolved source's kind, C# emitted nothing.
Five ports, four behaviors, no working output anywhere.
The fix guards the shape rather than flipping either predicate. Both readings are
right for what they were designed for; only their intersection is incoherent, and
it is incoherent by construction — extends only ADDS members, so a child
projection's extra fields have no provider in the parent's view, and both objects
would claim one physical view while declaring different exposures (the declared
field set IS the exposure, fail-closed).
Prior art splits the same way. Shared-storage inheritance inherits binding AND
writability together (Hibernate @immutable is inherited by entity subclasses; EF
Core keyless ToView types; SQLAlchemy single-table). Shape-reuse inheritance does
not inherit the binding at all — JPA @MappedSuperclass has no table of its own,
and Django documents inheriting db_table from an abstract base as a trap for
exactly this reason. A projection is the second kind. Systems that expose views
also derive writability structurally per object rather than splitting it from the
binding (jOOQ TableRecord vs UpdatableRecord; Prisma disables mutations on views).
Enforced at the CONCRETE level (mirrors #236): an abstract projection base may
carry shared shape, and a source on one is inert until a concrete child extends
it. Skipped when the super is not a legal projection, so a projection extending an
entity reports one error at its root cause instead of two.
Gated by a shared error-projection-inherited-source fixture run by all five ports
(TS 516, Java 516, Python 495, C# 842 conformance; full suites green). Nothing in
the repo relies on the shape — a corpus scan found it only inside an existing
error fixture — and no port generated working output for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,7 +32,7 @@ _Last refreshed 2026-08-05._
32
32
-**Kotlin** — `codegen-kotlin` (KotlinPoet on JVM): entity + Exposed table + Spring controller + payload + relations + filter allowlist + validator + stored-proc + output-parser generators. `integration-tests-kotlin` runs the persistence-conformance corpus through Exposed against Testcontainers Postgres.
33
33
34
34
**Cross-port conformance corpora** (every port runs the shared corpus):
35
-
- Metamodel: `fixtures/conformance/` (261 fixtures; 19 shared corpora in total — per-corpus counts + the corpus x port matrix live in `docs/CONFORMANCE.md`). TS / C# / Java / Python all green.
35
+
- Metamodel: `fixtures/conformance/` (262 fixtures; 19 shared corpora in total — per-corpus counts + the corpus x port matrix live in `docs/CONFORMANCE.md`). TS / C# / Java / Python all green.
- 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 — plus an **array-of-VO** `field.object @isArray @storage:jsonb` column (`labels`, written as 2-element / empty-`[]` / single-element arrays across the three rows) — so every subtype write+read (incl. the array-of-value-object jsonb codec) 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".)
38
38
- API-contract: `fixtures/api-contract-conformance/`. TS / C# / Java / Kotlin / Python all green — 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.
| Python | PyPI `0.20.14` | Loader + serializer + render + verify + codegen + `ObjectManager` runtime. Fully green across all corpora. |
101
101
102
-
Conformance fixtures live at [`fixtures/`](https://github.com/metaobjectsdev/metaobjects/tree/main/fixtures). Every port runs the shared corpus byte-identically: metamodel (`conformance/`, 261 fixtures), render, persistence (Testcontainers Postgres, with an `op: roundtrip` gate so every `field.*` subtype write+read round-trips on every port), api-contract (41 scenarios — 26 core plus TPH / M:N / jsonb / write-through — two lanes: a reference server AND each port's generated API booted over HTTP), registry (byte-matched metamodel-vocabulary manifest, live + green in all five ports), and yaml/verify.
102
+
Conformance fixtures live at [`fixtures/`](https://github.com/metaobjectsdev/metaobjects/tree/main/fixtures). Every port runs the shared corpus byte-identically: metamodel (`conformance/`, 262 fixtures), render, persistence (Testcontainers Postgres, with an `op: roundtrip` gate so every `field.*` subtype write+read round-trips on every port), api-contract (41 scenarios — 26 core plus TPH / M:N / jsonb / write-through — two lanes: a reference server AND each port's generated API booted over HTTP), registry (byte-matched metamodel-vocabulary manifest, live + green in all five ports), and yaml/verify.
Copy file name to clipboardExpand all lines: docs/llms/llms.txt
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -31,7 +31,7 @@ This `llms.txt` is the short index; the deep, version-matched how-to is the scaf
31
31
## Spec and standard
32
32
33
33
- [Specification (canonical, target-agnostic)](https://github.com/metaobjectsdev/metaobjects/tree/main/spec): the normative metadata schema and semantics that every implementation must conform to.
34
-
- [Conformance fixtures](https://github.com/metaobjectsdev/metaobjects/tree/main/fixtures): cross-port corpora — `conformance/` (metamodel, 261 fixtures), `render-conformance/`, `persistence-conformance/` (against Testcontainers Postgres, including an `op: roundtrip` gate so every `field.*` subtype write+read round-trips on every port), `api-contract-conformance/` (41 scenarios — 26 core REST contract + filter operators, plus TPH / M:N / jsonb / write-through — run in two lanes: a reference server AND each port's generated API booted over HTTP), `registry-conformance/` (byte-matched metamodel-vocabulary manifest, live + green in all five ports), and `yaml-conformance/` / `verify-conformance/`. Every port runs the shared corpus byte-identically.
34
+
- [Conformance fixtures](https://github.com/metaobjectsdev/metaobjects/tree/main/fixtures): cross-port corpora — `conformance/` (metamodel, 262 fixtures), `render-conformance/`, `persistence-conformance/` (against Testcontainers Postgres, including an `op: roundtrip` gate so every `field.*` subtype write+read round-trips on every port), `api-contract-conformance/` (41 scenarios — 26 core REST contract + filter operators, plus TPH / M:N / jsonb / write-through — run in two lanes: a reference server AND each port's generated API booted over HTTP), `registry-conformance/` (byte-matched metamodel-vocabulary manifest, live + green in all five ports), and `yaml-conformance/` / `verify-conformance/`. Every port runs the shared corpus byte-identically.
35
35
- [Roadmap](https://github.com/metaobjectsdev/metaobjects/blob/main/spec/roadmap.md): current + planned work across all implementations.
Copy file name to clipboardExpand all lines: fixtures/conformance/ERROR-CODES.json
+1Lines changed: 1 addition & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -14,6 +14,7 @@
14
14
"ERR_PROJECTION_IDENTITY_NOT_EXTENDED": "FR-024: an identity.* on an object.projection lacks extends — a projection identity is a pass-through of an entity identity.",
15
15
"ERR_IDENTITY_KEY_MISMATCH": "FR-024: identity key correspondence broken — an extended-identity field has no local pass-through field extending it, or an explicit @fields disagrees with the computed pass-through key.",
16
16
"ERR_PROJECTION_SOURCE_WRITABLE": "FR-024 (ADR-0028): a source.* on an object.projection has a writable @kind (table, or @kind omitted which defaults to table) — a projection is a derived read-only representation; its sources must be read-only kinds (view, materializedView, storedProc, tableFunction).",
17
+
"ERR_PROJECTION_INHERITED_SOURCE": "FR-024 (ADR-0028): a concrete object.projection inherits a source.* through extends instead of declaring its own. A projection's extends is shape lineage, not a shared-storage hierarchy: extends only ADDS members, so the child's extra fields have no provider in the parent's view, and two objects would claim one physical view with different declared exposures. Declare the source on the concrete projection; an abstract projection base carries shape only.",
17
18
"ERR_INVALID_SUBTYPE_CHILD": "A child node type/subType is not permitted under its parent.",
18
19
"ERR_CHILD_NOT_ALLOWED": "FR-033: a structural child (field/identity/source/validator/… — not an attr) is placed under a parent whose registered childRules do not admit it (the structural analogue of ERR_UNKNOWN_ATTR). Strict-load only; a no-op under wildcard childRules. The detail names the parent, the child (type.subType 'name'), and which placement was rejected.",
19
20
"ERR_UNKNOWN_ATTR": "An attribute name is not declared on the node's type.",
0 commit comments