From 72dd24910173d96b27a9c373b7c086f2f61959cc Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sat, 17 Oct 2026 03:17:29 -0400 Subject: [PATCH 1/8] fix: close the rc.4 adopter findings for 1.1.0 Reporting, in every port: - A bare entity name the path already fixes resolves in that entity's package: a dimension's @of after @via resolves in the package of the entity the @via reaches, and a report's @spine owner resolves in @from's package. Gated by reporting-cross-package-short-names. - @agg any / all may omit @of and quantify the measure's @filter or @segment over the rows (bool_or / bool_and on Postgres, MAX / MIN of the condition on SQLite, D1 and MySQL, and in the Cube model). New loader rule M0 refuses an @of-less measure of any other @agg, and an @of-less any / all with no condition, with ERR_INVALID_MEASURE. Tooling: - meta gen no longer tells a docs-only run to wire a prompt generator, and claims emitted value objects only when an entity generator ran. - agentDocsFile() warns when it skips agent/ui.md; includeUiTier is documented. - meta init warns when a pre-release CLI writes generatedBy, and detect-prerelease-pins.sh fails on a pre-release generatedBy. - prerelease-link keeps a pom's final-newline state, reports only real repins, and keeps its git exclude lines in a managed block that unlink removes. --- AGENTS.md | 2 +- CHANGELOG.md | 49 +++++++ README.md | 2 +- .../references/declaring-reports.md | 6 +- .../references/reporting.md | 2 +- .../references/typescript-docs.md | 2 +- docs/CONFORMANCE.md | 6 +- .../migrations/docs-are-generators.md | 9 +- docs/features/prerelease.md | 27 ++-- docs/features/reporting.md | 42 ++++-- examples/showcase/site-payload.json | 2 +- .../references/declaring-reports.md | 6 +- .../references/reporting.md | 2 +- .../references/declaring-reports.md | 6 +- .../references/reporting.md | 2 +- .../references/declaring-reports.md | 6 +- .../references/reporting.md | 2 +- .../references/declaring-reports.md | 6 +- .../references/reporting.md | 2 +- .../references/typescript-docs.md | 2 +- .../references/declaring-reports.md | 6 +- .../references/reporting.md | 2 +- .../references/typescript-docs.md | 2 +- .../expected-errors.json | 15 ++ .../input/meta.ops.json | 78 ++++++++++ .../providers.json | 1 + .../expected-errors.json | 15 ++ .../input/meta.ops.json | 81 +++++++++++ .../providers.json | 1 + .../reporting-any-all-predicate/expected.json | 128 ++++++++++++++++ .../input/meta.ops.json | 37 +++++ .../providers.json | 1 + .../expected.json | 115 +++++++++++++++ .../input/meta.play.json | 21 +++ .../input/meta.reports.json | 19 +++ .../input/meta.world.json | 18 +++ .../providers.json | 1 + .../expected/model/cubes/Call.yml | 3 + .../measure-boolean-mysql/meta.json | 11 ++ .../expected/model/cubes/Call.yml | 3 + fixtures/cube-model/measure-boolean/meta.json | 11 ++ .../metamodel-docs/expected/types/measure.md | 4 +- .../expected-registry.json | 6 +- .../ReportRowCodegenTests.cs | 57 ++++++++ .../ReportShapeTests.cs | 34 +++++ .../MetaObjects/Core/Reporting/ReportShape.cs | 85 +++++++++-- .../Core/Reporting/ReportingSchema.cs | 7 +- .../Loader/ValidationPasses.Reporting.cs | 45 ++++-- .../MetaObjects/SpecMetamodel/reporting.json | 4 +- .../loader/ReportingValidation.java | 44 ++++-- .../reporting/AggregateMeasure.java | 4 +- .../metaobjects/reporting/ReportShape.java | 76 ++++++++-- .../reporting/ReportShapeTest.java | 55 +++++++ server/python/src/metaobjects/core_types.py | 6 +- .../metaobjects/loader/validate_reporting.py | 43 ++++-- .../meta/core/reporting/report_shape.py | 91 ++++++++++-- .../metaobjects/spec_metamodel/reporting.json | 4 +- .../tests/codegen/test_report_router.py | 31 ++++ server/python/tests/test_report_shape.py | 60 ++++++++ .../packages/cli/src/commands/init.ts | 16 +- .../packages/cli/src/lib/version.ts | 11 ++ .../packages/cli/test/unit/version.test.ts | 40 +++++ .../codegen-ts/src/cube/cube-members.ts | 13 +- .../src/generators/agent-docs-file.ts | 17 ++- .../src/projection/report-ddl-emit.ts | 6 + .../src/projection/report-resolve.ts | 16 +- .../codegen-ts/src/prompt-generator-gate.ts | 22 ++- .../codegen-ts/src/reference/agent-docs.ts | 17 ++- .../packages/codegen-ts/src/runner.ts | 7 +- .../test/agent-docs-surface.test.ts | 42 ++++++ .../test/projection/report-ddl-emit.test.ts | 69 +++++++++ .../test/prompt-generator-gate.test.ts | 51 ++++++- .../test/report-views-pg.test.ts | 30 ++++ .../test/report-views-sqlite.test.ts | 34 +++++ .../src/core/reporting/report-describe.ts | 8 +- .../src/core/reporting/report-shape.ts | 88 ++++++++++- .../reporting-definition.embedded.ts | 6 +- .../typescript/packages/metadata/src/index.ts | 1 + .../src/loader/reporting-validation.ts | 44 ++++-- .../metadata/test/report-shape.test.ts | 36 +++++ site-reference/types/measure.html | 6 +- spec/metamodel/reporting.json | 4 +- tools/prerelease/detect-prerelease-pins.sh | 42 +++++- tools/prerelease/prerelease-link.sh | 137 +++++++++++++++--- tools/prerelease/test-prerelease-link.sh | 115 ++++++++++++++- 85 files changed, 2091 insertions(+), 195 deletions(-) create mode 100644 fixtures/conformance/error-measure-any-without-condition/expected-errors.json create mode 100644 fixtures/conformance/error-measure-any-without-condition/input/meta.ops.json create mode 100644 fixtures/conformance/error-measure-any-without-condition/providers.json create mode 100644 fixtures/conformance/error-measure-sum-without-of/expected-errors.json create mode 100644 fixtures/conformance/error-measure-sum-without-of/input/meta.ops.json create mode 100644 fixtures/conformance/error-measure-sum-without-of/providers.json create mode 100644 fixtures/conformance/reporting-any-all-predicate/expected.json create mode 100644 fixtures/conformance/reporting-any-all-predicate/input/meta.ops.json create mode 100644 fixtures/conformance/reporting-any-all-predicate/providers.json create mode 100644 fixtures/conformance/reporting-cross-package-short-names/expected.json create mode 100644 fixtures/conformance/reporting-cross-package-short-names/input/meta.play.json create mode 100644 fixtures/conformance/reporting-cross-package-short-names/input/meta.reports.json create mode 100644 fixtures/conformance/reporting-cross-package-short-names/input/meta.world.json create mode 100644 fixtures/conformance/reporting-cross-package-short-names/providers.json create mode 100644 server/typescript/packages/cli/test/unit/version.test.ts diff --git a/AGENTS.md b/AGENTS.md index 396cd39d7..1e010f5ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,7 +72,7 @@ PyPI has had no product change since `0.25.0` — nothing is broken. - **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. **Cross-port conformance corpora** (every port runs the shared corpus): -- Metamodel: `fixtures/conformance/` (380 fixtures; 29 shared corpora in total — per-corpus counts + the corpus x port matrix live in `docs/CONFORMANCE.md`). TS / C# / Java / Python all green. +- Metamodel: `fixtures/conformance/` (384 fixtures; 29 shared corpora in total — per-corpus counts + the corpus x port matrix live in `docs/CONFORMANCE.md`). TS / C# / Java / Python all green. - Render: `fixtures/render-conformance/`. TS / C# / Java / Kotlin / Python byte-identical. - 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 FR-044 `report-*` scenarios also run on SQLite, MySQL and D1 where a port has the engine, see [docs/CONFORMANCE.md](docs/CONFORMANCE.md)). 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".) - 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. Three sub-corpora run the **generated lane only, on all five ports** — `write-through/`, `projection/` (F22: a view-only `object.projection` serves GET list + GET by id and answers every write verb with `405 {"error": "method_not_allowed"}`) and `report/` (FR-044: a view-backed `object.report` serves GET list, answers `POST` with that same `405`, and mounts no `/{id}` route). That is deliberate, not a gap: what is under test is whether a port's GENERATOR emits those routes, and a hand-rolled reference server would answer every scenario by construction. The `m2m/` sub-corpus also gates **TPH x M:N together** (base-declared, subtype-declared, abstract-mid-declared, a non-subtype source onto a subtype TARGET, and the cross-subtype source id answering `200 []`) — the two corpora were originally built disjoint (`tph/` had no relationships, `m2m/` no discriminators), which is precisely how that defect class survived. diff --git a/CHANGELOG.md b/CHANGELOG.md index a20ccf233..4351919d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,8 +50,57 @@ database does not have. `true` runs such a list when the files are supplied another way. Every Java and Kotlin `requires` closure, and the skills' pom examples, are compiled over the fitness corpus in a test. +- **`@agg: any` / `all` without `@of`, in every port.** A `measure.aggregate` with `@agg: any` + or `all` may omit `@of` and declare a `@filter` or `@segment` instead: it is then true when + some row (`any`) or every row (`all`) in the group passes that condition. `{ "name": + "hasError", "@agg": "any", "@filter": { "success": false } }` states "did any call fail" + (`bool_or(success = FALSE)`) over a `success` column, which a boolean `@of` could not. A row + whose condition is NULL is skipped, as `bool_or` skips one. It lowers + to `bool_or()` / `bool_and()` on Postgres and `MAX` / `MIN` of the + condition on SQLite, D1 and MySQL, and to the same SQL in the Cube model. The registry makes + `measure.aggregate` `@of` optional (`metamodelVersion` stays `1.1`), and a new loader rule M0 + refuses, with `ERR_INVALID_MEASURE`, an `@of`-less measure whose `@agg` is not `any` or `all` + (it was `ERR_MISSING_REQUIRED_ATTR`) and an `@of`-less `any` / `all` with no condition. + Gated by the `reporting-any-all-predicate`, `error-measure-any-without-condition` and + `error-measure-sum-without-of` conformance fixtures. Found by an adopter on 1.1.0-rc.4. ### Fixed +- **A bare entity name the path already fixes resolves in that entity's package, in every port.** + A dimension's `"@of": "Realm.name"` after a `@via` hop onto `world::Realm` was resolved in + the declaring entity's package and refused (`must name a field of 'world::Realm', the entity + @via ... reaches`), so a cross-package `@of` needed the full name. It now resolves in the + package of the entity the `@via` reaches, and the owner of a report's `@spine` resolves in + `@from`'s package rather than the report's. A fully-qualified name is unchanged. The loader and every port's report shape use + the one rule. Gated by the `reporting-cross-package-short-names` conformance fixture. Found + by an adopter on 1.1.0-rc.4. +- **`meta gen` with only documentation generators no longer says to wire a prompt generator.** + A project whose `generators` lists only docs generators was told, for each `template.prompt`, + to add `promptRender()`, and that the payload value objects "were emitted regardless" when the + run had emitted no code. The advisory now fires only when the run lists a generator that + writes code, and it says the value objects were emitted only when `entityFile()` (or another + entity-module generator) ran. Found by an adopter on 1.1.0-rc.4. +- **`agentDocsFile()` says when it skips `agent/ui.md`.** The page is written only when the run + lists a client UI generator or the generator is given `includeUiTier: true`, and before this + a project whose UI is generated by a separate config lost the page with no message. `meta + gen` now warns `agent/ui.md not written: no client UI generator in this run; set + includeUiTier: true if another config generates the UI.`, and the option is documented in + `docs/features/migrations/docs-are-generators.md`. Found by an adopter on 1.1.0-rc.4. +- **A pre-release CLI's version in `.metaobjects/.agent-context.json` is caught.** `meta init` + stamps the running CLI's version into that committed file's `generatedBy`; run with a release + candidate, it recorded `1.1.0-rc.4` and `tools/prerelease/detect-prerelease-pins.sh` passed. + `meta init` now warns when it writes a pre-release `generatedBy`, and the detector fails on + one and names `meta init --refresh-docs --docs-only`, run with the release CLI, as the fix. + Found by an adopter on 1.1.0-rc.4. +- **`prerelease-link` leaves a pom it did not change byte-identical, and reports only real + repins.** A `link` / `unlink` round trip added a final newline to a `pom.xml` that had none, + so `git status` showed it modified, and both commands said "repinned ... in `app/pom.xml`" + for a module pinned only through its parent's `${metaobjects.version}`. The rewrites keep a + file's final-newline state, and a repin is reported only for a file whose text changed. + Found by an adopter on 1.1.0-rc.4. +- **`prerelease-link unlink` removes the git exclude lines `link` added.** `link` appended its + ignore rules as bare lines to the clone's shared `info/exclude`, and `unlink` left them there. + They now sit in one managed block, which `unlink` removes without touching a line the user + wrote, and a repeated `link` does not add them twice. - `verify --codegen` (Python, C#) says when the committed output was generated with another column rule, another table rule, or both, and names the `naming` setting that reproduces it, instead of advising a regeneration that would commit the wrong column or table names. diff --git a/README.md b/README.md index 411ba70bc..b918024df 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ metaobjects/ ├── CLAUDE.md # project instructions for Claude ├── spec/ # canonical metamodel docs, ADRs, roadmap ├── fixtures/ # 25 cross-language conformance corpora — the oracle -│ ├── conformance/ # metamodel (loader + serializer + navigation), 380 fixtures +│ ├── conformance/ # metamodel (loader + serializer + navigation), 384 fixtures │ ├── yaml-conformance/ # YAML authoring desugar │ ├── render-conformance/ # FR-004 byte-identical render oracle │ ├── verify-conformance/ # FR-004 template-drift gate diff --git a/agent-context/skills/metaobjects-authoring/references/declaring-reports.md b/agent-context/skills/metaobjects-authoring/references/declaring-reports.md index 289368316..ba19b0d33 100644 --- a/agent-context/skills/metaobjects-authoring/references/declaring-reports.md +++ b/agent-context/skills/metaobjects-authoring/references/declaring-reports.md @@ -14,7 +14,9 @@ and an `object.report` combines them by name. Four node kinds: - `dimension.attribute` / `dimension.time` — what to group by (`@of: Entity.field`; a time dimension lists the `@grains` it supports: `hour`, `day`, `week`, `month`, `quarter`, `year`); - `measure.aggregate` (`@agg`: `count`, `sum`, `avg`, `min`, `max`, `any`, `all`) and `measure.ratio` - (`@numerator` / `@denominator`, both measures of the entity); + (`@numerator` / `@denominator`, both measures of the entity). `any` / `all` take a + `field.boolean` `@of`, or no `@of` and a `@filter` or `@segment`: `{ "name": "hasError", + "@agg": "any", "@filter": { "success": false } }` is "some row has `success` false"; - `segment.filter` — a named, reusable `@filter` ("active purchase"); - `object.report` — a top-level object: `@from` an entity, `@dimensions` (`name` or `name:grain`), `@measures`. @@ -63,6 +65,8 @@ Three rules an author trips on: a `sum`. The view joins the hop through an `identity.reference` between the two entities (`{ "identity.reference": { "name": "fkProgram", "@fields": ["programId"], "@references": "Program" } }`); a relationship with none behind it loads and then fails `meta migrate`. + After `@via`, a bare entity name in `@of` resolves in the package of the entity the `@via` + reaches (`"@of": "Realm.name"` after a hop onto `world::Realm`). 3. **A report declares no fields.** Its columns are derived: one per dimension, then one per measure (a time dimension at a grain is ``, so `purchasedAt:day` is `purchasedAtDay`). A `field.*` or `identity.*` child on a report is an error. diff --git a/agent-context/skills/metaobjects-authoring/references/reporting.md b/agent-context/skills/metaobjects-authoring/references/reporting.md index 37fa747e2..31489594b 100644 --- a/agent-context/skills/metaobjects-authoring/references/reporting.md +++ b/agent-context/skills/metaobjects-authoring/references/reporting.md @@ -47,7 +47,7 @@ A report declares no fields. Its columns are one per `@dimensions` item in liste | `avg` of `int`, `long`, `currency`, `decimal` | the measure's name | `decimal` | no | | `avg` of `double` / `float` | the measure's name | `double` | no | | `min` / `max` | the measure's name | the `@of` field's type | no | -| `any` / `all` (boolean OR / AND, `bool_or` / `bool_and`; `@of` must be a `field.boolean`) | the measure's name | `boolean` | no | +| `any` / `all` (boolean OR / AND, `bool_or` / `bool_and`; over a `field.boolean` `@of`, or with no `@of` over the measure's `@filter` / `@segment`) | the measure's name | `boolean` | no | | `measure.ratio` | the measure's name | `decimal` | no | | any measure above except `count`, declaring `@default` | the measure's name | as above | yes: it reads the default instead of null | | in a `@spine` report, a dimension over a column of the spine entity | the dimension's name | the `@of` field's type | yes when the field is `@required` or in that entity's `identity.primary`; no for a dimension beyond the spine entity | diff --git a/agent-context/skills/metaobjects-codegen/references/typescript-docs.md b/agent-context/skills/metaobjects-codegen/references/typescript-docs.md index 2159716b4..07e6a361f 100644 --- a/agent-context/skills/metaobjects-codegen/references/typescript-docs.md +++ b/agent-context/skills/metaobjects-codegen/references/typescript-docs.md @@ -21,7 +21,7 @@ generate, docs included). Each factory is an ordinary entry in `generators`, fro | `docsFile()` | `docs` | the neutral model surface — one page per entity and per template (`.md`, `