Skip to content

Commit e2456aa

Browse files
authored
feat(reporting): lower view-backed object.report to SQL views and read it in every port (FR-044 Plan 2) (#399)
* feat(metadata): reportShape, the derived fields of an object.report (FR-044) * feat(codegen-ts): time-grain and relative-date SQL per dialect (FR-044) * refactor(codegen-ts): extract walkViaPath from buildJoinTree * feat(codegen-ts): extractReportSpec, an object.report as a view spec (FR-044) * feat(codegen-ts): emitReportViewDdl for Postgres, SQLite and MySQL (FR-044) * feat(codegen-ts): lower view-backed reports through buildProjectionViews; canonical reports and shape artifact (FR-044) * fix(integration-tests): keep the report-shapes artifact outside the canonical metadata directory (FR-044) * test(integration): report views converge and return the expected rows on Postgres and SQLite (FR-044) Postgres: convergence x3, the Task 10 values read straight off the six canonical views, UTC buckets under a New York session zone, empty-group row, INNER vs LEFT OUTER join, relative window, and a changed report taking the drop-and-create path. SQLite: convergence (verbatim text), the same values, week boundary, quarter and year grains, relative window, tuple distinct count via json_array, and the hour bucket's literal pinned to the .000Z spelling the TS adapters store. No emitter change. * feat(codegen-ts): MySQL report view SQL through buildReportViews; recipe (FR-044) A MySQL 8.4 value test creates the six canonical report views under the default sql_mode (ONLY_FULL_GROUP_BY asserted, not assumed) and pins the Task 10 rows, the 2/3 ratio at 0.6667 (Review Focus 5), the bigint type of a SIGNED-cast sum, and the Table D grain and Table E relative-date values. No emitter change was needed. docs/recipes/mysql.md gains a Reports section (meta migrate still does not own a MySQL schema); the skill reference and the regenerated agent-context goldens follow. * feat(runtime-ts): ObjectManager reads a view-backed report (FR-044) * fix(docs): MySQL report recipe declares a managed view source; run the recipe's declaration in the test (FR-044) buildReportViews skips a report whose source.rdb is @Unmanaged, so the recipe's @Unmanaged: true declaration returned no SQL. The recipe now declares @kind: view only (meta migrate never targets MySQL, so nothing manages the view either way) and says that an unmanaged source is skipped. The MySQL test now reads the recipe's own fenced declaration, runs the recipe's loadDirectory + buildReportViews shape, and asserts one view, and none once @Unmanaged is added. Tables and views are created in beforeAll after dropping stale ones, so tests run alone (-t) and rerun against a persistent server. * fix(runtime-ts): the report read model reads the same source the lowering names (FR-044) * test(persistence-conformance): six report scenarios (FR-044) Six shared read scenarios over the view-backed reports in the canonical model: grouped measures, totals, totals over an empty table, time grains, hour and week buckets, and a relative-date filter. List and count only; no get, no write. The TypeScript runner discovers them from queries/ with no list to update and passes all 33 query scenarios. Persistence corpus count 33 -> 39 (27 -> 33 query) in docs/CONFORMANCE.md; README gains a Report scenarios subsection. Other ports' persistence lanes are red on these until their own tasks. * feat(csharp): report shape and generated keyless row for a view-backed report (FR-044) * feat(python): report shape and ObjectManager read of a view-backed report (FR-044) * fix(csharp): refuse a report whose derived field name equals its row class name (FR-044) * feat(java): report shape and OMDB read of a view-backed report (FR-044) * docs(reporting): teach reports in the authoring skill; document the lowering (FR-044) * fix(docs): keep the agent schema page byte-identical for a model with no report view; correct reporting doc details (FR-044) * feat(kotlin): Exposed table for a view-backed report (FR-044) KotlinExposedTableGenerator emits the read-only Exposed table of an object.report that declares a source.rdb @kind: view, with one column per derived field (contract Table B) taken from the JVM ReportShape. A report with no view, or over a kind the lowering skips, still generates nothing, and every other Kotlin generator still skips reports. A report table binds by literal (no names artifact is emitted for a report), types an enum column by the enum of the entity the dimension reads, and reads a derived decimal with no declared precision at 38,18 so Exposed does not round a ratio to four places. A derived field named after a Kotlin keyword, or two that land on one column property, is a generation error naming the report and the item. Also reserves schemaName as an Exposed Table member in safeColumnProperty: a column property of that name did not compile. Six hand-written reference tables put the Kotlin persistence lane on the six shared report scenarios. * fix(kotlin): document report table generation and its refusals; reserve Exposed 1.x table members (FR-044) * fix(reporting): resolve report references as the loader does; refuse what cannot be lowered (FR-044) The lowering and the read shape disagreed with validateReporting about what a loadable model means. Each case loaded clean and then failed, or was silently wrong, at migrate or at read. - @Of and @via resolve in the package of the entity that DECLARES the dimension or measure, not the @from entity's. A member inherited from a base in another package now resolves, and a same-named entity in the report's package can no longer capture the reference and mistype the column. - Without @via the field is read from @from, and a @via walk starts at @from, as the loader's does. - A dotted @measures item (Sale.total, loader rule R3) names the measure by its last segment. One canonical report now uses the dotted form; schema.postgres.sql and report-shapes.json are byte-identical. - A report is classified (skip / @SQL / derive) by the same source its view is named by and the runtime reads: primary, else first. A replica declared first no longer decides it. The report-shapes generator uses the same selector. - A derived report @from a TPH subtype is refused: the subtype shares its base's table, so the view aggregated every subtype's rows. An @SQL or @Unmanaged report is the author's body and is not refused. - A @via hop with no identity.reference behind it says which hop and what it needs. An empty in list, a time dimension without a grain and a grain outside the closed set are refused by name instead of reaching the DDL. * test(reporting): engine tests read real views and stop touching what they do not own (FR-044) - MySQL: drop only the views and tables this file creates, by name, instead of every view in the database (a shared test database lost unrelated views). - Postgres: changing a report runs a plain migrate with no dropView allowance, so a broken drop-and-create pairing would fail the test. - The NULL-component tuple count is read through a lowered view over nullable columns on Postgres and SQLite, not through hand-written SQL. - A decimal sum and a double / float sum are created and read on Postgres, with the view's column types asserted. - Every inline model asserts the loader returned no errors. - The SQLite inet residue is narrowed to the two inet columns. * docs(reporting): a @via hop needs a declared foreign key; state the known limits (FR-044) - The documented @via example could not be lowered: its relationship had no identity.reference behind it. The example now declares one, and the feature doc, authoring skill rule 2 and the skill reference say a hop needs it. - Known limits: a derived report from a TPH subtype is refused (declare it from the base with a filter on the discriminator); an abstract view-backed report and non-view source kinds get no C# row or Kotlin table; a dimension over a field.object is not supported across ports. - Which source decides when a report declares several (primary, else first), the dotted @measures form, and that count counts non-null @Of. - CHANGELOG [Unreleased] and the agent-context goldens follow. * fix(java): resolve report references as the loader does; refuse a report over a field.object (FR-044) The report shape resolved a bare @Of in the @from entity's package. The loader resolves it in the package of the entity that DECLARES the dimension or measure, so a member inherited from a base in another package either failed to resolve or was typed from a same-named decoy. The shape now follows the loader: declaring package, the named entity must be @from or an ancestor, and without @via the field is read from @from. A dotted @measures item (Sale.total) names the measure by its last segment. A time dimension item with no grain, or one outside the closed set, does not resolve. OMDB: a derived field over a field.object is refused by name when the read model is built (it was a NullPointerException on read). OQL with a report result class builds rows from the read model. getObjectRef leaves an object with no metadata to the base method. A projection whose view is named by @view now has a read mapping: the view name is the source's physical name, one rule for projections and reports. * fix(kotlin): report table uses the shape's own resolution; refuse a report over a field.object (FR-044) The enum class of a report column is taken from ReportShape.ofEntity, so the generator restates nothing about packages or @via. gen fails, naming the report and the dimension or measure, when a derived field reads a field.object. Tests cover a member inherited across packages (with and without a same-named decoy), a dotted @measures item, and an abstract view-backed report (generates nothing). * fix(csharp): resolve report references as the loader does; refuse a report over a field.object (FR-044) ReportShapes resolves @Of in the declaring entity's package, checks the named entity is @from or an ancestor, and reads the field from @from when there is no @via. A dotted @measures item names the measure by its last segment. A report row with a dimension over a field.object silently lost that property; gen now refuses it by name. An abstract view-backed report generates nothing (tested). * fix(python): resolve report references as the loader does; test the Table B rows and carry rules (FR-044) report_shape resolves @Of in the declaring entity's package, checks the named entity is @from or an ancestor, and reads the field from @from when there is no @via. A dotted @measures item names the measure by its last segment. The shapes test helper names the view by the read model's rule (primary, else first). New direct tests for sum/avg/min/max by @Of subtype and for what a derived field carries from its type source. ADR-0039 comments on the own-only reads. * docs(changelog): report reference fixes in the four ports, the field.object refusal, OMDB @view projections (FR-044) * docs(reporting): state the field.object limit per port (FR-044) Java OMDB (on read), Kotlin gen and C# gen now refuse a report whose column is typed by a field.object, naming the report and the item; the TypeScript and Python runtimes read it as parsed JSON. The Known limits entry and the skill reference said the other ports were not gated for it. * fix(codegen-ts): a @via report lowers in a package whose name contains a dot; document the redeclared-field limit (FR-044) The @via walk was handed the @from entity's resolution key as its head, and the walk splits on every dot, so a report in a package such as com.acme resolved no hop and was refused with a false 'no foreign key' message. The head is now the entity's short name, resolved in its own package, which is still that entity when another package has one of the same name. Known limits gains the quiet form of the @via rule: when the reached subtype redeclares the field and @Of names the base, the view reads the base's column. * no-mistakes(document): Fix stale FR-044 roadmap status and C# port codegen inventory for report lowering
1 parent c8640ce commit e2456aa

132 files changed

Lines changed: 13892 additions & 344 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/rules/cross-language-porting.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Preserve the following contracts exactly across all language ports:
1717

1818
**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`.
1919
- Filter operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `like`, `isNull`
20-
- 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).
20+
- 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).
2121
- 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`.
2222
- 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).
2323
- 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/`.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ PyPI has had no product change since `0.25.0` — nothing is broken.
8282
- YAML / verify corpora green across the ports that ship those layers.
8383
- **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".
8484

85-
**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)).
85+
**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)).
8686

8787
**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.
8888

‎CHANGELOG.md‎

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

3565
### Fixed
3666

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

0 commit comments

Comments
 (0)