Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .nm-triage-nonprod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Paths that no-mistakes triage counts as non-production (one glob per line).
# Conformance corpora and other test fixtures: data, gated by every port's runner.
fixtures/**
# The metamodel spec and the copies generated from it into each port.
spec/metamodel/*.json
server/csharp/MetaObjects/SpecMetamodel/*.json
server/python/src/metaobjects/spec_metamodel/*.json
server/typescript/packages/metadata/src/**/*.embedded.ts
# The generated reference site and showcase payload.
site-reference/**
examples/showcase/site-payload.json
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,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/` (458 fixtures; 38 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/` (459 fixtures; 38 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.
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ here.**

## [Unreleased]

### Fixed

- **A projection view keeps the columns a reverse `@via` hop feeds.** An origin `@via` that walks
an `identity.reference` backwards (`Project.timeEntries`, #560) loaded, but `meta migrate` left
its `origin.aggregate` / `origin.first` columns out of `CREATE VIEW` without a word. The view
emitter now resolves the hop the way the loader does and joins from the holder of the
reference. Gated by a Postgres test that creates the view and reads it.
- **`origin.aggregate` `sum`/`avg`/`min`/`max` over a value-object field is refused at load.**
`@of: Invoice.total` on a flattened Money field loaded and emitted `SUM(i.total)`, a column that
does not exist. Every port now refuses it with `ERR_INVALID_ORIGIN` and says to aggregate a
scalar field; gated by the `error-origin-aggregate-value-object` conformance fixture. Refusing,
rather than summing the flattened amount column, keeps a sum from adding amounts in different
currencies. `count` is unaffected.

### Changed

- **Agent context routes report work to the reporting vocabulary.** The `meta init` agent guide
and the `metaobjects-authoring` skill now send grouped totals, dashboard numbers and read-only
report views to `object.report` (`references/declaring-reports.md`) before the `@sql` escape,
which they describe as the last resort for bodies the vocabulary cannot express.

## [1.3.0] — 2026-11-11

_npm `1.3.0` (full lockstep across all 14 `@metaobjectsdev/*` publish candidates)._
Expand Down
4 changes: 2 additions & 2 deletions agent-context/skills/metaobjects-authoring/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ This file covers what almost every model needs. The topics below live in
| `references/identities-and-indexes.md` | declaring a primary, secondary or reference identity, an FK, or a non-unique `index.lookup` |
| `references/sources.md` | the `source.rdb` shape and `@kind`; a store MetaObjects does not manage; a derived or computed column (reach for an entity read-view first) |
| `references/abstracts-and-overlay.md` | a shared `abstract` base, `extends`, or a layered `overlay` file |
| `references/declaring-reports.md` | declaring `dimension.*`, `measure.*`, `segment.filter` or an `object.report` |
| `references/declaring-reports.md` | **open first** for a report, dashboard, totals, `GROUP BY`, "per client / per month" or a read-only summary view: declaring `dimension.*`, `measure.*`, `segment.filter` or an `object.report` |
| `references/model-first-rationale.md` | someone argues that hand-writing a layer is cheaper this once |
| `references/relationships.md` | a many-to-many link or self-join, or a "rows that reference this one" query |
| `references/read-views-and-projections.md` | an `object.projection`, `origin.*` vocabulary, `@filter` / `@expr`, or an `@sql` / `@unmanaged` view |
| `references/read-views-and-projections.md` | an `object.projection`, `origin.*` vocabulary, `@filter` / `@expr`, or an `@sql` / `@unmanaged` view (a grouped or aggregate view is `declaring-reports.md` first) |
| `references/inheritance-tph.md` | several entities are variants of one thing sharing a single table (`@discriminator`) |
| `references/metadata-dependencies.md` | the project builds on another package's metadata (`dependencies`, cross-package `overlay`) |
| `references/reporting.md` | what a declared report gives you: what columns a report gets, time grains, null rules, rows with no facts (`@spine`), defaults (`@default`), engine differences |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Projections, `origin.*`, and managed views

> Part of the `metaobjects-authoring` skill. The skill covers the entity read-view, which is the right answer for most grids. Read this when you need an independent exposure (`object.projection`), the `origin.*` vocabulary, `@filter` / `@expr` shapes, or an `@sql` / `@unmanaged` view.
> Part of the `metaobjects-authoring` skill. The skill covers the entity read-view, which is the right answer for most grids. Read this when you need an independent exposure (`object.projection`), the `origin.*` vocabulary, `@filter` / `@expr` shapes, or an `@sql` / `@unmanaged` view. A grouped total or report (per client, per month) is an `object.report`: `declaring-reports.md`.

## Contents
- Read-view or projection?
Expand Down Expand Up @@ -125,8 +125,9 @@ non-aggregate-derived** projection fields: naming an undeclared field fails the

**The `CREATE VIEW` body is generated from those `origin.*` children by the Node
`meta migrate` — never hand-author view SQL for a shape origins can express** (an unmodeled
view is *unmanaged*, so `meta verify --db` can't even catch the drift). For a genuinely
irreducible body (recursive CTE, window function, set operation) that origins can't express,
view is *unmanaged*, so `meta verify --db` can't even catch the drift). **An `@sql` body that
`GROUP BY`s: check `declaring-reports.md` first** (`object.report`, `@spine`, `@default`).
For a genuinely irreducible body (recursive CTE, window function, set operation) that origins can't express,
carry it in the `source.rdb` **`@sql`** escape (#208, ADR-0043) — a hand-written body the
tool registers, fingerprints, and drift-checks (adopt a pre-existing view with
`meta migrate --allow adopt-view`) — rather than a hand-edited migration file where it goes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ column because the filter tier runs against the view.
**When you need an independent exposure** — a subset, renamed columns, a row-filtered
view, a versioned or external shape — that is an `object.projection`. It, the `origin.*`
vocabulary, the structured `@filter` / `@expr` shapes and the `@sql` / `@unmanaged` escapes
are in `references/read-views-and-projections.md`.
are in `references/read-views-and-projections.md`. A grouped or aggregate view (totals per client
or per month) is an `object.report` (`references/declaring-reports.md`); `@sql` is for what neither
can express (a recursive CTE, a window function, a set operation).
3 changes: 3 additions & 0 deletions agent-context/templates/always-on.md.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ Commit the metadata, the generated code, `.metaobjects/migrations/` and
- Once the project declares a `template.prompt`, every LLM prompt is one: the text in a
template, the data in a payload, a structured reply read through its generated extractor
(`metaobjects-prompts`).
- A grouped total, dashboard number or read-only report view (per client, per month) is an
`object.report` over `dimension.*` / `measure.*` (`metaobjects-authoring`,
`references/declaring-reports.md`). A hand-written `@sql` view body is the last resort.
- Never invent metadata vocabulary. Search it: `npx meta types <term>`, and
`npx meta types <type>.<subType> --detail` for one construct's valid attributes.
- Metadata nodes are fused-key maps, `{"field.string": {"name": "email"}}`. Attribute names are
Expand Down
6 changes: 3 additions & 3 deletions docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor

| Corpus | Fixtures | TS | Java | Kotlin | C# | Python |
|---|---|---|---|---|---|---|
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 458 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 459 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
| [`fixtures/yaml-conformance/`](../fixtures/yaml-conformance/) | 16 | 16 / 16 | 15 / 16 (1 ledgered: `yaml-quoted-leading-zero` — Java pipeline strips quotes off `"007"`) | inherits via Java | 15 / 16 (1 ledgered: `error-yaml-coerced-hex-in-string` — YamlDotNet doesn't coerce `0xFF`) | 16 / 16 |
| [`fixtures/verify-conformance/`](../fixtures/verify-conformance/) | 45 | ✓ | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/verify-strict-conformance/`](../fixtures/verify-strict-conformance/) | 1 | ✓ | — | — | — | ✓ |
Expand Down Expand Up @@ -354,7 +354,7 @@ unit-test runners (`bun test`, `dotnet test`, `pytest`, `mvn test`) pull Docker.

## Fixture-to-doc mapping

### `fixtures/conformance/` — metamodel loader + canonical serializer (458)
### `fixtures/conformance/` — metamodel loader + canonical serializer (459)

| Fixture prefix | Feature doc |
|---|---|
Expand Down Expand Up @@ -716,7 +716,7 @@ JSON and verify payload feeds it unchanged.

## Orphaned fixtures (tested but not yet documented)

The fixtures in the nine corpora mapped above (metamodel 458 + yaml 16 + verify 45
The fixtures in the nine corpora mapped above (metamodel 459 + yaml 16 + verify 45
+ render 18 + persistence 62 + api-contract 123 + source-resolution 25 + scope 10 +
dependency 47) each map to a feature doc, and so do the two requirement corpora, `shared-model-conformance`, the
`cube-model` corpus and `explorer-conformance`, whose case lists live in their own READMEs. None are orphaned today. The remaining
Expand Down
6 changes: 6 additions & 0 deletions docs/features/source-kinds.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ max over only the active rows — the filtered-scalar shape a plain aggregate ca
]}}
```

**`sum` / `avg` / `min` / `max` need a scalar `@of`.** A value-object field (`field.object`,
flattened or jsonb) is several columns or a document, so there is no one column to reduce, and a
Money-shaped sum would add amounts in different currencies. The loader refuses it in every port
(`ERR_INVALID_ORIGIN`); model the amount as its own scalar field and group by its currency.
`count` still counts rows.

**More `origin.aggregate` reductions (#195).** Beyond the scalar reduces, `@agg` also takes
`any` / `all` (predicate quantifiers over a `@filter` — `@of` forbidden; empty set →
`any=false`, `all=true`) and `collect` (an array rollup of `@of` into an `isArray` field,
Expand Down
2 changes: 1 addition & 1 deletion examples/showcase/site-payload.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"metamodel": "1.3"
},
"counts": {
"fixtures": 458,
"fixtures": 459,
"corpora": 38,
"baseTypes": 17
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ This file covers what almost every model needs. The topics below live in
| `references/identities-and-indexes.md` | declaring a primary, secondary or reference identity, an FK, or a non-unique `index.lookup` |
| `references/sources.md` | the `source.rdb` shape and `@kind`; a store MetaObjects does not manage; a derived or computed column (reach for an entity read-view first) |
| `references/abstracts-and-overlay.md` | a shared `abstract` base, `extends`, or a layered `overlay` file |
| `references/declaring-reports.md` | declaring `dimension.*`, `measure.*`, `segment.filter` or an `object.report` |
| `references/declaring-reports.md` | **open first** for a report, dashboard, totals, `GROUP BY`, "per client / per month" or a read-only summary view: declaring `dimension.*`, `measure.*`, `segment.filter` or an `object.report` |
| `references/model-first-rationale.md` | someone argues that hand-writing a layer is cheaper this once |
| `references/relationships.md` | a many-to-many link or self-join, or a "rows that reference this one" query |
| `references/read-views-and-projections.md` | an `object.projection`, `origin.*` vocabulary, `@filter` / `@expr`, or an `@sql` / `@unmanaged` view |
| `references/read-views-and-projections.md` | an `object.projection`, `origin.*` vocabulary, `@filter` / `@expr`, or an `@sql` / `@unmanaged` view (a grouped or aggregate view is `declaring-reports.md` first) |
| `references/inheritance-tph.md` | several entities are variants of one thing sharing a single table (`@discriminator`) |
| `references/metadata-dependencies.md` | the project builds on another package's metadata (`dependencies`, cross-package `overlay`) |
| `references/reporting.md` | what a declared report gives you: what columns a report gets, time grains, null rules, rows with no facts (`@spine`), defaults (`@default`), engine differences |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Projections, `origin.*`, and managed views

> Part of the `metaobjects-authoring` skill. The skill covers the entity read-view, which is the right answer for most grids. Read this when you need an independent exposure (`object.projection`), the `origin.*` vocabulary, `@filter` / `@expr` shapes, or an `@sql` / `@unmanaged` view.
> Part of the `metaobjects-authoring` skill. The skill covers the entity read-view, which is the right answer for most grids. Read this when you need an independent exposure (`object.projection`), the `origin.*` vocabulary, `@filter` / `@expr` shapes, or an `@sql` / `@unmanaged` view. A grouped total or report (per client, per month) is an `object.report`: `declaring-reports.md`.

## Contents
- Read-view or projection?
Expand Down Expand Up @@ -125,8 +125,9 @@ non-aggregate-derived** projection fields: naming an undeclared field fails the

**The `CREATE VIEW` body is generated from those `origin.*` children by the Node
`meta migrate` — never hand-author view SQL for a shape origins can express** (an unmodeled
view is *unmanaged*, so `meta verify --db` can't even catch the drift). For a genuinely
irreducible body (recursive CTE, window function, set operation) that origins can't express,
view is *unmanaged*, so `meta verify --db` can't even catch the drift). **An `@sql` body that
`GROUP BY`s: check `declaring-reports.md` first** (`object.report`, `@spine`, `@default`).
For a genuinely irreducible body (recursive CTE, window function, set operation) that origins can't express,
carry it in the `source.rdb` **`@sql`** escape (#208, ADR-0043) — a hand-written body the
tool registers, fingerprints, and drift-checks (adopt a pre-existing view with
`meta migrate --allow adopt-view`) — rather than a hand-edited migration file where it goes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ column because the filter tier runs against the view.
**When you need an independent exposure** — a subset, renamed columns, a row-filtered
view, a versioned or external shape — that is an `object.projection`. It, the `origin.*`
vocabulary, the structured `@filter` / `@expr` shapes and the `@sql` / `@unmanaged` escapes
are in `references/read-views-and-projections.md`.
are in `references/read-views-and-projections.md`. A grouped or aggregate view (totals per client
or per month) is an `object.report` (`references/declaring-reports.md`); `@sql` is for what neither
can express (a recursive CTE, a window function, a set operation).
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ Commit the metadata, the generated code, `.metaobjects/migrations/` and
- Once the project declares a `template.prompt`, every LLM prompt is one: the text in a
template, the data in a payload, a structured reply read through its generated extractor
(`metaobjects-prompts`).
- A grouped total, dashboard number or read-only report view (per client, per month) is an
`object.report` over `dimension.*` / `measure.*` (`metaobjects-authoring`,
`references/declaring-reports.md`). A hand-written `@sql` view body is the last resort.
- Never invent metadata vocabulary. Search it: `npx meta types <term>`, and
`npx meta types <type>.<subType> --detail` for one construct's valid attributes.
- Metadata nodes are fused-key maps, `{"field.string": {"name": "email"}}`. Attribute names are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ Commit the metadata, the generated code, `.metaobjects/migrations/` and
- Once the project declares a `template.prompt`, every LLM prompt is one: the text in a
template, the data in a payload, a structured reply read through its generated extractor
(`metaobjects-prompts`).
- A grouped total, dashboard number or read-only report view (per client, per month) is an
`object.report` over `dimension.*` / `measure.*` (`metaobjects-authoring`,
`references/declaring-reports.md`). A hand-written `@sql` view body is the last resort.
- Never invent metadata vocabulary. Search it: `npx meta types <term>`, and
`npx meta types <type>.<subType> --detail` for one construct's valid attributes.
- Metadata nodes are fused-key maps, `{"field.string": {"name": "email"}}`. Attribute names are
Expand Down
Loading
Loading