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
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/` (428 fixtures; 35 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/` (428 fixtures; 36 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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ here.**
and the `wire` setting, and a project on any port runs them through the Node `meta` CLI. They are
reference helpers: opt-in, listed by `meta gen --list`, ejectable and gated by `verify --codegen`
(ADR-0034 Amendment 10). Protobuf is not built. See `docs/features/contract-emitters.md`.
- **The data spine for a model explorer.** These changes are data only. No screen reads the new
data yet.
- `meta export --effective` writes the effective canonical JSON. Every field, child and attribute
a node inherits through `extends` is written onto that node.
- `meta verify --format json` adds `requirementLedger`, one row per requirement. A row carries its
digest, each `@implementedBy` link with the node the gate resolved it to, the findings against
it, and a `clean`, `warn` or `error` verdict. Text output is unchanged.
- `meta gen` records, in `.hashes.json`, the node each per-entity file was rendered from:
`{ "hash", "generatedBy", "sourceNode" }`. `EmittedFile.sourceNode` lets an owned generator set
it, and `perEntity` sets it automatically. A file rendered from the whole model keeps the plain
hash entry. The field is not called `generator`, because on the JVM that word claims ownership.
- `@metaobjectsdev/docs-site` exports `buildExplorer` and `serializeExplorer`. Together they build a
deterministic `explorer.json` from those three inputs. The file holds nodes, typed edges, the
requirement ledger, the generated files for each node, and derived slices for each package,
requirement subtree and prompt.

Gated by the new `fixtures/explorer-conformance/` corpus. See `docs/features/explorer-data.md`.
No metamodel vocabulary changes, and `metamodelVersion` does not move.
- **A recipe for checking an API change against the last release.** Emit the document at the last tag
and at `HEAD`, then run oasdiff. See `docs/recipes/check-api-against-last-release.md`.
- **A `pydantic` extra on the Python package.** `pip install "metaobjects[pydantic]"` installs
Expand Down
23 changes: 20 additions & 3 deletions docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Conformance coverage

The MetaObjects standard ships **35 shared conformance corpora** under
The MetaObjects standard ships **36 shared conformance corpora** under
[`fixtures/`](../fixtures/). Every port runs every corpus that is *applicable to
it* and asserts the same expected behaviour against the same fixtures. **This page
is the inverse index**: fixture → feature doc + per-port pass status, and it is the
Expand Down Expand Up @@ -61,6 +61,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
| [`fixtures/codegen-noop/`](../fixtures/codegen-noop/) (FR-044 — reporting vocabulary: what is lowered, what stays inert) | 1 model pair (`reporting/with` vs `reporting/without`) | ✓ (codegen + migrate) | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/cube-model/`](../fixtures/cube-model/) (FR-044 Plan 4 — the `cube-model` generator's golden Cube files, plus the canonical model's golden) | 46 cases (36 trees + 10 errors) + 1 canonical golden | ✓ (the exporter is TypeScript-only, spec R6; its `cube` lane also loads the files into a real Cube) | — | — | — | — |
| [`fixtures/contract-conformance/`](../fixtures/contract-conformance/) (FR-022 — the `contract-schema` and `openapi` generators' golden trees and refusals) | 19 cases (14 trees + 5 errors) | ✓ (one neutral engine, ADR-0034 Amendment 10; the generated API lanes also hold real traffic to the documents) | — | — | — | — |
| [`fixtures/explorer-conformance/`](../fixtures/explorer-conformance/) (the `explorer.json` data spine: `meta export --effective` + `meta verify --format json` + the generated-file manifest in, one explorer document out) | 1 case (`shop`) | ✓ (the builder is TypeScript, and reads only port-neutral inputs) | — | — | — | — |

A ✓ means the port runs the corpus green; an explicit `n / m` is used where a port
carries a ledgered divergence. The two ledgered YAML fixtures are documented
Expand Down Expand Up @@ -610,12 +611,28 @@ message. The case list, with the rule each pins, is the
(`server/typescript/packages/integration-tests/cube-live/cube-model.live.ts`) loads the same
files into a real Cube.

### `fixtures/explorer-conformance/` (1 case)

The `shop` case → [features/explorer-data.md](features/explorer-data.md). Its `project/` is a
small model with inheritance, a value object, a reference, a prompt with a payload and a reply,
and a five-level requirement tree that has a resolved member claim, a dangling planned claim and a
live requirement nothing implements. `effective.json` and `verify.json` are what
`meta export --effective` and `meta verify --format json` write for it, `hashes.json` is a
hand-written manifest with node provenance, and `expected.json` is the explorer document built
from the three, compared byte for byte.

**One port runs it:** TypeScript, in
`server/typescript/packages/docs-site/test/explorer-build.test.ts` (the builder) and
`server/typescript/packages/cli/test/explorer-fixture.test.ts` (the two committed inputs are
what the CLI writes today). The builder reads no loader, so a port that writes the same effective
JSON and verify payload feeds it unchanged.

## Orphaned fixtures (tested but not yet documented)

The fixtures in the nine corpora mapped above (metamodel 428 + yaml 16 + verify 45
+ render 18 + persistence 59 + api-contract 119 + source-resolution 25 + scope 10 +
dependency 37) each map to a feature doc, and so do the two requirement corpora, `shared-model-conformance` and the
`cube-model` corpus, whose case lists live in their own READMEs. None are orphaned today. The remaining
dependency 37) 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
corpora in the totals table gate tooling contracts (registry manifests, provider
composition, agent context, docs emit) rather than user-facing metamodel behaviour,
so they have no feature-doc row.
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ docs/
│ ├── reporting.md # dimensions, measures, segments, reports: vocabulary and load rules (FR-044)
│ ├── contract-emitters.md # the contract-schema and openapi generators: JSON Schema and OpenAPI for the REST contract (FR-022)
│ ├── cube-export.md # the cube-model generator: reporting vocabulary as Cube data-model files (FR-044)
│ ├── explorer-data.md # explorer.json and its inputs: export --effective, the verify ledger rows, manifest provenance
│ └── own-your-codegen.md # scaffold-and-own generator ownership (ADR-0034)
└── ports/ # one file per language/framework port
├── typescript.md
Expand Down Expand Up @@ -78,6 +79,7 @@ this tree is documentation, not the source of truth.
| Keep Prisma as the ORM, with `schema.prisma` generated from the model and gated by `verify` (TypeScript) | [`recipes/prisma.md`](recipes/prisma.md) |
| Publish the API as JSON Schema or OpenAPI, and check a change against the last release | [`features/contract-emitters.md`](features/contract-emitters.md), [`recipes/check-api-against-last-release.md`](recipes/check-api-against-last-release.md) |
| Export the reporting vocabulary to Cube (`cube-model`: cubes, joins, rollups; the `cube` lane that checks it against a real Cube) | [`features/cube-export.md`](features/cube-export.md) |
| Build the data a model explorer reads (`explorer.json` from `meta export --effective`, `meta verify --format json` and the generated-file manifest) | [`features/explorer-data.md`](features/explorer-data.md) |
| Wire prompt construction (FR-004) | [`features/templates-and-payloads.md`](features/templates-and-payloads.md) |
| Share a metadata shape across multiple instances (abstracts, `extends:`) | [`features/abstracts-and-inheritance.md`](features/abstracts-and-inheritance.md) |
| Add a custom metamodel subtype or attribute to a downstream project | [`features/extending-with-providers.md`](features/extending-with-providers.md) + [`recipes/extending-metaobjects-with-providers.md`](recipes/extending-metaobjects-with-providers.md) |
Expand Down
1 change: 1 addition & 0 deletions docs/features/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ command surface splits in two:
| **Codegen drift** (`verify --codegen`) | **Node `meta`** | `meta verify --codegen` | TS reference (ADR-0021 D2) — regen-to-temp + diff committed output |
| **Template/prompt drift** (`verify --templates`) | **Node `meta`** | `meta verify --templates` | TS reference (ADR-0021 D2) — `{{field}}`↔payload; part of the bare-`verify` default |
| **Vocabulary upgrade** (`upgrade`) | **Node `meta`** | `meta upgrade [--to <version>] [--apply]` | **any backend** — rewrites RETIRED metadata vocabulary (`@violation` → `@counterexample`, `@readOnly` → `@mutability`, dropping `@verifiedBy`) and resolves ATTRIBUTE CONTRADICTIONS (`@fields` beside `@expr` on an index key). Node-only because it edits the metadata documents themselves, which every port shares; a non-TS project runs `npx meta upgrade` against its own `metaobjects/`. **Canonical JSON and YAML alike.** Previews by default. Retirements needing a human decision are refused and the run exits non-zero, so CI cannot record a partial migration as finished |
| **Model export** (`export`) | **Node `meta`** | `meta export [--effective] [--out <file>]` | the loaded model, overlays merged, as one canonical JSON document. `--effective` also writes every inherited field, child and attribute onto the node that inherits it, which is the input the [explorer builder](explorer-data.md) reads. The output is for reading: loading an effective document back would declare each inherited member twice |
| **Vocabulary search** (`types`) | **Node `meta`** | `meta types [query]` | **any backend** — apropos/`kubectl explain` over the live metamodel registry (names + descriptions + when-to-use); the vocabulary is cross-port identical (registry-conformance) |
| **Dependency sync** (`deps`) | **Node `meta`** | `meta deps sync \| check \| list` | **any backend** — resolves a declared metadata dependency (`path`, or an installed `npm` / `python` package) into a committed snapshot + lock (FR-023), so `sync`/`check`/`list` themselves stay Node-only regardless of your server language. TypeScript, Python and Java then LOAD that snapshot at every rung of their own source ladder (Kotlin loads through Java's loader and Maven plugin); C# loads the snapshot through `dotnet meta` (Phase 2 S6). On a project whose own metadata Node `meta` cannot read (a Maven `src/main/resources/metadata` directory, a C# metadata directory, or another port's vocabulary), `sync` and `check` prove the locked dependencies load together and say that the port's own `verify` is the full check. See [`metadata-dependencies.md`](metadata-dependencies.md) |
| **Canonical format** (`fmt`) | every port | `meta fmt` / `dotnet meta fmt` / `mvn metaobjects:fmt` / `metaobjects fmt` | (#304) ships in **every** port — the canonical serializer already exists in all five, so formatting is local, not Node-only. `--check` lists non-canonical files and exits non-zero without changing anything. Formats each file STANDALONE — own-mode, declared-here layer only (ADR-0039); an `extends` onto another file's base is preserved as-is, never resolved or erred on; a file declaring `overlay: true` anywhere — whether or not a same-file base exists — is reported as skipped rather than merged (merging would drop the overlay marker, a structural change fmt never makes). A YAML file is always skipped — no canonical YAML emitter exists (ADR-0006). Before writing, every port reloads the whole project with the candidate swapped in and refuses the write unless the reloaded model's canonical form is byte-identical to the untouched baseline — fmt never changes meaning. See [Fixture corpus](#meta-fmt--canonical-formatting-304) below and `fixtures/fmt-conformance/README.md` |
Expand Down
Loading
Loading