|
4 | 4 |
|
5 | 5 | Accepted (2026-06-12). Defined by FR-024 |
6 | 6 | (`docs/superpowers/specs/2026-06-12-fr-024-entity-surfaces-projections-design.md`). |
| 7 | +**Amended 2026-08-06** — see *Amendment: a concrete projection owns its source* |
| 8 | +below (`ERR_PROJECTION_INHERITED_SOURCE`). |
7 | 9 |
|
8 | 10 | ## Context |
9 | 11 |
|
@@ -49,6 +51,56 @@ semantic lie. |
49 | 51 | Lineage, exposure, read-only-ness, and keys all remain computed; only the |
50 | 52 | licensing is declared. |
51 | 53 |
|
| 54 | +## Amendment (2026-08-06) — a concrete projection owns its source |
| 55 | + |
| 56 | +A **concrete** `object.projection` must declare its own `source.*`; inheriting one |
| 57 | +through `extends` is `ERR_PROJECTION_INHERITED_SOURCE`. An **abstract** projection |
| 58 | +base carries shape only — a source on one is inert until a concrete child extends |
| 59 | +it, at which point the error fires on the child. |
| 60 | + |
| 61 | +**Why.** A projection's `extends` is *shape lineage*, not a shared-storage |
| 62 | +hierarchy. `extends` only ADDS members, so a child inheriting the parent's view |
| 63 | +gains fields that view cannot provide, and both objects then claim one physical |
| 64 | +view while declaring different exposures — which contradicts decision 6's |
| 65 | +fail-closed rule that the declared field set IS the exposure, and gives one view |
| 66 | +two DDL owners. |
| 67 | + |
| 68 | +The shape was also the one place two source predicates disagreed. "Which source am |
| 69 | +I bound to" resolves through the super chain — an entity legitimately inherits its |
| 70 | +table (TPH / a `BaseEntity`), and making that lookup resolving fixed a real |
| 71 | +"inherited source emitted nothing" bug. "What KIND of source am I" is own-only, |
| 72 | +because projection-ness is a property of the declaring object (ADR-0039 sanctioned |
| 73 | +own; see `codegen-ts` `projection-detector.ts`). Both readings are correct for what |
| 74 | +they were designed for; only their intersection was incoherent, and it produced no |
| 75 | +working artifact in any port — TypeScript mounted writable CRUD over a read-only |
| 76 | +view, Java and Kotlin skipped on their subtype gate, Python on the resolved kind, |
| 77 | +and C# emitted nothing. Guarding the shape makes the predicates agree without |
| 78 | +flipping either. |
| 79 | + |
| 80 | +**Prior art.** The split matches how mature ORMs divide the two inheritance |
| 81 | +regimes. Shared-storage inheritance inherits binding AND writability together — |
| 82 | +Hibernate's `@Immutable` "may be applied only to the root entity, and is inherited |
| 83 | +by entity subclasses"; EF Core keyless `ToView` types; SQLAlchemy single-table. |
| 84 | +Shape-reuse inheritance does not inherit the binding at all — JPA |
| 85 | +`@MappedSuperclass` "has no separate table defined for it", and Django documents |
| 86 | +inheriting `db_table` from an abstract base as a trap: "all the child classes … |
| 87 | +would use the same database table, which is almost certainly not what you want". |
| 88 | +A projection is the second kind. Systems that expose views also derive writability |
| 89 | +structurally per object rather than splitting it from the binding (jOOQ's |
| 90 | +`TableRecord` vs `UpdatableRecord`; Prisma disables mutations on views outright). |
| 91 | + |
| 92 | +**The sanctioned pattern** (already in the corpus as |
| 93 | +`fixtures/conformance/projection-extends-projection`): an abstract, sourceless |
| 94 | +projection base carries the shared field shape; each concrete projection declares |
| 95 | +its own read-only source. A versioned successor therefore declares its own view |
| 96 | +rather than silently sharing its predecessor's. |
| 97 | + |
| 98 | +Enforced at the concrete level, mirroring #236's abstract-exemption precedent. The |
| 99 | +check is skipped when the super is not a legal projection, so a projection |
| 100 | +extending an entity still reports one error at its root cause rather than two. |
| 101 | +Gated by `fixtures/conformance/error-projection-inherited-source` across all five |
| 102 | +ports. |
| 103 | + |
52 | 104 | ## Consequences |
53 | 105 |
|
54 | 106 | - The two legacy spellings are REMOVED outright — hard cutover, no deprecation |
|
0 commit comments