Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b917327
feat(metadata): reportShape, the derived fields of an object.report (…
dmealing Oct 4, 2026
e8be681
feat(codegen-ts): time-grain and relative-date SQL per dialect (FR-044)
dmealing Oct 4, 2026
c377bbe
refactor(codegen-ts): extract walkViaPath from buildJoinTree
dmealing Oct 4, 2026
c6ef1de
feat(codegen-ts): extractReportSpec, an object.report as a view spec …
dmealing Oct 4, 2026
6cb2b64
feat(codegen-ts): emitReportViewDdl for Postgres, SQLite and MySQL (F…
dmealing Oct 4, 2026
486da20
feat(codegen-ts): lower view-backed reports through buildProjectionVi…
dmealing Oct 4, 2026
d2345ab
fix(integration-tests): keep the report-shapes artifact outside the c…
dmealing Oct 4, 2026
34cbe14
test(integration): report views converge and return the expected rows…
dmealing Oct 4, 2026
1587af6
feat(codegen-ts): MySQL report view SQL through buildReportViews; rec…
dmealing Oct 4, 2026
9dcf05d
feat(runtime-ts): ObjectManager reads a view-backed report (FR-044)
dmealing Oct 4, 2026
7541041
fix(docs): MySQL report recipe declares a managed view source; run th…
dmealing Oct 4, 2026
0a0485b
fix(runtime-ts): the report read model reads the same source the lowe…
dmealing Oct 4, 2026
e797069
test(persistence-conformance): six report scenarios (FR-044)
dmealing Oct 4, 2026
39dd911
feat(csharp): report shape and generated keyless row for a view-backe…
dmealing Oct 4, 2026
62e70e8
feat(python): report shape and ObjectManager read of a view-backed re…
dmealing Oct 4, 2026
3a6bb26
fix(csharp): refuse a report whose derived field name equals its row …
dmealing Oct 4, 2026
e638979
feat(java): report shape and OMDB read of a view-backed report (FR-044)
dmealing Oct 4, 2026
5dad12b
docs(reporting): teach reports in the authoring skill; document the l…
dmealing Oct 4, 2026
136a378
fix(docs): keep the agent schema page byte-identical for a model with…
dmealing Oct 4, 2026
732f8e3
feat(kotlin): Exposed table for a view-backed report (FR-044)
dmealing Oct 4, 2026
134a033
fix(kotlin): document report table generation and its refusals; reser…
dmealing Oct 4, 2026
6da9fec
fix(reporting): resolve report references as the loader does; refuse …
dmealing Oct 4, 2026
f1cb45d
test(reporting): engine tests read real views and stop touching what …
dmealing Oct 4, 2026
fe95a2d
docs(reporting): a @via hop needs a declared foreign key; state the k…
dmealing Oct 4, 2026
d99a611
fix(java): resolve report references as the loader does; refuse a rep…
dmealing Oct 4, 2026
f0301d7
fix(kotlin): report table uses the shape's own resolution; refuse a r…
dmealing Oct 4, 2026
950ef04
fix(csharp): resolve report references as the loader does; refuse a r…
dmealing Oct 4, 2026
09b2afe
fix(python): resolve report references as the loader does; test the T…
dmealing Oct 4, 2026
1299ebc
docs(changelog): report reference fixes in the four ports, the field.…
dmealing Oct 4, 2026
c7839ec
docs(reporting): state the field.object limit per port (FR-044)
dmealing Oct 4, 2026
46c8d04
fix(codegen-ts): a @via report lowers in a package whose name contain…
dmealing Oct 4, 2026
836c36f
no-mistakes(document): Fix stale FR-044 roadmap status and C# port co…
dmealing Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/rules/cross-language-porting.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Preserve the following contracts exactly across all language ports:

**Metamodel subtype vocabularies (must be identical across languages):** the `registry-conformance` gate (`fixtures/registry-conformance/`) is the structural enforcer of this rule — each port emits its registry as a canonical manifest byte-matched to `expected-registry.json`. **All five ports (TS / C# / Java / Kotlin / Python) are live + green** (SP-G Java/Kotlin reconciliation complete; the JVM runners compose from the defined metamodel provider set so codegen-base/om classpath SPI does not pollute the measured vocabulary). See `fixtures/registry-conformance/README.md`.
- Filter operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `like`, `isNull`
- Object subtypes: `entity` (owns data: own identity, writable sources, lifecycle), `value` (pure shape: NO identity, NO source, ever; constructed — by caller/embedding — never populated; may `extends` entity fields for shape; a value-hosted field may carry `origin.passthrough` but never an assembly origin), `projection` (derived read-only representation: fields `extends`-bound / origin-derived / self-declared-under-external-assembly, all read-only at subtype level; identity optional and MUST extend an entity identity; sources restricted to read-only `@kind`s; the declared field set IS the exposure — inclusive list, fail-closed). A field carrying `origin.*` is derived ⇒ read-only wherever it lives (incl. on entities). An entity's primary source must be a writable `@kind` (read-only kinds only in read role). See [ADR-0028](spec/decisions/ADR-0028-object-taxonomy-projection-value-purity.md). (FR-024 Phase E — `object.projection`/`value` are registered in `expected-registry.json` and the projection/value validation passes [identity pass-through, value-purity, projection-licensing, `@via` inference/cardinality, extends/origin agreement, derived-field providability] are enforced cross-port in all 5 ports. The **B4b** entity-primary-source-readonly cutover [the "writable `@kind`" clause above — `ERR_ENTITY_PRIMARY_SOURCE_READONLY`] + the projection codegen fan-out (read-only DTOs for view-kind projections; FR-015 proc-callables for proc-kind projections in TypeScript, C# and Kotlin ONLY — Java and Python ship no callable generator at all, so the cross-port claim does NOT cover that clause; api-docs label `object.projection` units as `projection` and document their generated `<Name>Dto`) are now shipped cross-port; the remaining FR-024 work is the declared-API surface — tracked in #10.) **`report` (FR-044 Plan 1)** is a root object subtype registered in all five ports, with the `dimension.attribute` / `dimension.time` / `measure.aggregate` / `measure.ratio` / `segment.filter` children on `object.entity` and the relative-date filter value — loader-validated (`ERR_INVALID_DIMENSION` / `ERR_INVALID_MEASURE` / `ERR_INVALID_REPORT` / `ERR_REPORT_FOREIGN_MEASURE`, plus `ERR_BAD_ATTR_FILTER` for relative dates off a reporting host) and **inert in every generator and in `meta migrate`**: an `object.report` emits nothing until its lowering plans land, gated by the 27 `reporting` conformance fixtures and the `codegen-noop` corpus. See [docs/features/reporting.md](docs/features/reporting.md).
- Object subtypes: `entity` (owns data: own identity, writable sources, lifecycle), `value` (pure shape: NO identity, NO source, ever; constructed — by caller/embedding — never populated; may `extends` entity fields for shape; a value-hosted field may carry `origin.passthrough` but never an assembly origin), `projection` (derived read-only representation: fields `extends`-bound / origin-derived / self-declared-under-external-assembly, all read-only at subtype level; identity optional and MUST extend an entity identity; sources restricted to read-only `@kind`s; the declared field set IS the exposure — inclusive list, fail-closed). A field carrying `origin.*` is derived ⇒ read-only wherever it lives (incl. on entities). An entity's primary source must be a writable `@kind` (read-only kinds only in read role). See [ADR-0028](spec/decisions/ADR-0028-object-taxonomy-projection-value-purity.md). (FR-024 Phase E — `object.projection`/`value` are registered in `expected-registry.json` and the projection/value validation passes [identity pass-through, value-purity, projection-licensing, `@via` inference/cardinality, extends/origin agreement, derived-field providability] are enforced cross-port in all 5 ports. The **B4b** entity-primary-source-readonly cutover [the "writable `@kind`" clause above — `ERR_ENTITY_PRIMARY_SOURCE_READONLY`] + the projection codegen fan-out (read-only DTOs for view-kind projections; FR-015 proc-callables for proc-kind projections in TypeScript, C# and Kotlin ONLY — Java and Python ship no callable generator at all, so the cross-port claim does NOT cover that clause; api-docs label `object.projection` units as `projection` and document their generated `<Name>Dto`) are now shipped cross-port; the remaining FR-024 work is the declared-API surface — tracked in #10.) **`report` (FR-044 Plan 1)** is a root object subtype registered in all five ports, with the `dimension.attribute` / `dimension.time` / `measure.aggregate` / `measure.ratio` / `segment.filter` children on `object.entity` and the relative-date filter value — loader-validated (`ERR_INVALID_DIMENSION` / `ERR_INVALID_MEASURE` / `ERR_INVALID_REPORT` / `ERR_REPORT_FOREIGN_MEASURE`, plus `ERR_BAD_ATTR_FILTER` for relative dates off a reporting host) and, since FR-044 Plan 2, **lowered only when it declares a read-only `source.rdb` of `@kind: view`**: `meta migrate` creates that view (TypeScript only, ADR-0015), every port reads it (persistence corpus, `report-shapes.json`), C# generates its typed row and Kotlin its Exposed table object, and everything else (routes, typed clients, filter allowlists, api-docs, and a report with no view source at all) stays inert, gated by the 27 `reporting` conformance fixtures and the `codegen-noop` corpus. See [docs/features/reporting.md](docs/features/reporting.md).
- Source subtypes: `rdb` (paradigm; ADR-0007). The pre-v2 `dbTable`/`dbView` subtypes are RETIRED — `source.rdb` + `@kind: table|view|materializedView|storedProc|tableFunction` is the form, with read-only-ness derived from `@kind`. Multi-source via `@role` (exactly one `primary` per object). Source physical name = `@table` (NOT `@name`); field physical name = `@column` (renamed from `@dbColumn`). Referential actions on relationships: `@onDelete` / `@onUpdate`.
- Origin subtypes: `passthrough`, `aggregate`, `collection`, `computed`, `first` (concrete; `base` is the abstract root). `passthrough` is legal on an `object.value`-hosted field (FR-015 parameter lineage); the four assembly origins (`aggregate`/`computed`/`collection`/`first`) live on `object.projection` only — a value-hosted assembly origin is `ERR_SUBTYPE_RULE_VIOLATION` (#210).
- Relationship subtypes: `association`, `aggregation`, `composition`. Cardinality via `@cardinality: one|many`; target via `@objectRef`. **M:N (FR-018) slim vocabulary:** `@cardinality: "many"` + `@objectRef` (target) + `@through` (the junction/through entity — a third entity that MUST declare two `identity.reference` children, one per FK side). The relationship's FK fields are **derived** from those references (the `identity.reference` SSOT for FK direction), never restated. `@sourceRefField` (optional) disambiguates a *directed* self-join by naming the source-side FK field on the junction (the other reference is the target side); on a `@cardinality: one` relationship it instead names which of several `identity.reference` nodes onto the same target this relationship navigates, short-circuiting the unique-candidate/`@sourceRefField`/name-pairing ladder (#368, [ADR-0029](spec/decisions/ADR-0029-entity-child-extends-and-via-inference.md) Amendment 1) — an unresolvable 1:N reference set is `ERR_INVALID_RELATIONSHIP` at load. `@symmetric` (optional boolean) marks an *undirected* self-join (union-on-read) — valid only when `@objectRef` == the declaring entity, and mutually exclusive with `@sourceRefField`. The pre-FR-018 `@joinEntity`/`@joinFields` attrs are REMOVED. Validation errors: symmetric-on-hetero / symmetric+sourceRefField → `ERR_BAD_ATTR_VALUE`; junction-missing-two-references / sourceRefField-not-matching / M:N-attr-on-1:N / **junction-unpairable** → `ERR_INVALID_RELATIONSHIP`. **Unpairable means the junction declares its two references but neither resolves to the navigating entity, or neither to the `@objectRef` target** — declaring two references is NOT enough, and the loader now checks WHAT they point at (owner ruling 2026-09-20). It does so by running the real FK derivation and converting its failure, never a parallel re-implementation, so the loader and the derivation cannot drift; scope mirrors codegen's own iteration exactly — every CONCRETE, non-projection object crossed with its EFFECTIVE relationships, NOT deduped by declaration, because pairing is a property of the navigating entity and an inherited M:N can pair from one subtype and not another. Before the ruling this loaded clean and then diverged: TS and C# warned and emitted no traversal route (a silent 404), while Java, Kotlin and Python failed the build. Gated by `fixtures/conformance/error-relationship-m2m-junction-unpairable/`.
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ PyPI has had no product change since `0.25.0` — nothing is broken.
- YAML / verify corpora green across the ports that ship those layers.
- **Codegen-compile gate** (all five ports; a GATE, not a corpus — it has no fixtures of its own and no row in the matrix). Every corpus above gates BEHAVIOUR; none asks whether the emitted code BUILDS, which is how four "generated code does not compile" defects shipped in 1.0.4 with the whole matrix green — `gen` exits 0 in all four cases and the adopter's build is the first thing that disagrees. Each port generates from `fixtures/persistence-conformance/canonical/meta.fitness.json` (reused deliberately: a second kitchen sink would drift from the one the other corpora already maintain) and compiles the emitted tree with its real compiler — `ts.createProgram` / Roslyn / `javac` / `KotlinCompilation` / (Python, having no static compiler) importing the generated package plus `ruff` F821. **Every port excludes its framework-bound route tier** (TS `routesFile`, C# `RoutesGenerator`, Java `SpringControllerGenerator`, Kotlin `KotlinSpringControllerGenerator`): those imports are not on an in-memory compile's classpath and stubbing them drowns the signal, so that tier is proven by the api-contract integration lane instead. One cross-port rule, not four local concessions. Found 5 further real defects on first run. Boundary detail: `docs/CONFORMANCE.md` → "Split coverage".

**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 + the nine 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); FR-044 Plan 1 (the reporting vocabulary — `dimension.attribute`/`dimension.time`, `measure.aggregate`/`measure.ratio`, `segment.filter`, `object.report`, plus the relative-date filter value — registered and loader-validated in all five ports behind 27 conformance fixtures + the `codegen-noop` corpus; **reports generate nothing yet**, see [docs/features/reporting.md](docs/features/reporting.md)).
**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 + the nine 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); FR-044 Plan 1 (the reporting vocabulary — `dimension.attribute`/`dimension.time`, `measure.aggregate`/`measure.ratio`, `segment.filter`, `object.report`, plus the relative-date filter value — registered and loader-validated in all five ports behind 27 conformance fixtures + the `codegen-noop` corpus; a report that declares a read-only `source.rdb @kind: view` is lowered to a SQL view by `meta migrate` and read by every port (Plan 2), and no route or typed client is generated for a report yet, see [docs/features/reporting.md](docs/features/reporting.md)).

**Latest release: 1.0.13** (2026-10-03) — npm `1.0.13`, PyPI `1.0.13`, NuGet `1.0.13`, Maven Central `8.0.13`. A PATCH: an already-plural entity name (`Stats`, `Settings`) no longer doubles in API-surface names (REST paths, hooks, finders, DbSets) in any port, while default physical table names stay frozen on the old rule; two entities that would share one API name are now a generation error; two Kotlin controller compile fixes (`field.inet` filter ops, `@dbColumnType: uuid` on a string field). Gated by a private `1.0.13-rc.1` build on the adopter estate (`rc-gate.sh` 7/7) and a full `--strict-toolchains` local CI run. The previous release, 1.0.12 (2026-10-02), added `fmt` in every CLI, a deprecated-reference `verify` advisory, the `onLocate` extract hook, and an Exposed 1.x Kotlin output mode.

Expand Down
48 changes: 44 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,53 @@ it until 1.1 ships._
`measure.aggregate` or an `object.report`. Four new error codes (`ERR_INVALID_DIMENSION`,
`ERR_INVALID_MEASURE`, `ERR_INVALID_REPORT`, `ERR_REPORT_FOREIGN_MEASURE`) and extended
`ERR_BAD_ATTR_FILTER` carry the load-time rules, gated by 27 new shared conformance fixtures.
`measure.derived` is not registered (it waits for FR-037 R5). **No generated output yet:** an
`object.report` emits no view DDL, route, client code or docs page, `meta migrate` proposes
nothing for it, and a model using the new names generates exactly what it did without them.
See [docs/features/reporting.md](docs/features/reporting.md).
`measure.derived` is not registered (it waits for FR-037 R5). A model that does not use the
new names generates exactly what it did without them; the next entry says what a report
becomes. See [docs/features/reporting.md](docs/features/reporting.md).
- **A report with a view source becomes a SQL view, and every port reads it (FR-044).** An
`object.report` that declares a read-only `source.rdb` of `@kind: view` is now lowered by
TypeScript: `meta migrate` creates the view on Postgres, SQLite and D1 (a changed report view
is dropped and re-created; a derived report view whose `@from` entity has no table fails
migrate naming the report and the entity; a report with an `@sql` source skips that check). A
derived report view is also refused, by name, when its `@from` is a TPH subtype (the subtype
shares its base's table, so the view would count every subtype's rows: declare it from the base
with an `@filter` on the discriminator field), when a `@via` hop has no `identity.reference`
behind it, and when a filter's `in` list is empty. A bare `@of` or `@via` on a dimension or
measure inherited from a base in another package resolves in that base's package, as the loader
resolves it. MySQL SQL comes from `buildReportViews(root,
{ dialect: "mysql" })` and the "Reports" section of `docs/recipes/mysql.md`, since `meta
migrate` does not target MySQL. A report with no `source.*` still generates nothing. Time
grains and relative dates are UTC, weeks start on Monday, a `sum` of nothing and a ratio over
zero are null, and a count is zero. The TypeScript `ObjectManager`, Java OMDB and the Python
`ObjectManager` read a view-backed report (list and count, with filter, sort and limit on the
derived fields; by-id and writes are refused); C# generates a keyless EF Core row type and
`DbContext` mapping for it and Kotlin an Exposed table object. `meta docs` lists the view on
the agent schema page. **Still absent:** no route, typed client, filter allowlist or api-docs
entry for a report in any port, no `measure.derived`, and no query-time grouping. Six shared
persistence scenarios (`report-*.yaml`) and `report-shapes.json` hold the ports to the same
columns; the `metaobjects-authoring` skill now teaches reports (`references/reporting.md`).
Anyone who declared a view-sourced report under the unreleased 1.1 vocabulary will now see a
`CREATE VIEW` from `meta migrate`. Kotlin `gen` fails, naming the report and the dimension or
measure, for a view-backed report with a derived field named after a Kotlin hard keyword or
with two derived fields that land on one column property. A view-backed report with a
dimension or measure over a `field.object` is refused by name by Java OMDB (on read), Kotlin
`gen` and C# `gen`; group by a scalar field. A `@measures` item may be written dotted
(`Sale.total`) and reads the same as the bare name in every port. Java OQL
(`executeQuery`) with a report as its result class builds rows from the report's derived
fields.

### Fixed

- **Java OMDB reads a projection whose view is named by `@view`.** The read mapping took the
view name from `@table` only, so a projection declared with the kind-matching `@view` alias
had no read mapping. It now resolves the source's physical name (`@view`, then the legacy
`@table`), the same rule the TypeScript toolchain creates the view under.
- **Kotlin: a field named after an Exposed `Table` property that was not reserved now gets the
`Column` suffix.** An entity or report field named `schemaName` now emits the column property
`schemaNameColumn`; it collided with Exposed's `Table.schemaName` and did not compile before.
With `exposedApi=1` the same now holds for `options` and `storageParameters`, which are
`Table` properties only in Exposed 1.x; `exposedApi=0` output for those two names is unchanged.
The physical column names do not change.
- **Java: a bare string authored for an `isArray` attribute is now ONE item.** The Java parser
used to split it on commas, which left stray quotes in the items; TypeScript and Python
already kept it whole. All three now agree, so a Java model that relied on the split (a
Expand Down
Loading
Loading