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 @@ -75,7 +75,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/` (361 fixtures; 25 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/` (363 fixtures; 25 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 **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. Two sub-corpora run the **generated lane only, on all five ports** — `write-through/` and `projection/` (F22: a view-only `object.projection` serves GET list + GET by id and answers every write verb with `405 {"error": "method_not_allowed"}`). 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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@ it until 1.1 ships._

### Fixed

- **Python, C#, Java: the generic `view.*` controls now load.** A document carrying
`view.text`, `view.dropdown` or any of the other web-presentation controls (`textarea`, `date`,
`month`, `hotlink`, `radio`, `checkbox`, `number`, `password`, `hidden`, `web`, `image`)
loaded in TypeScript and failed with `ERR_UNKNOWN_SUBTYPE` in every other port, so metadata
shared between a TypeScript web client and a Python, C#, Java or Kotlin backend could not be
loaded by the backend. Those ports had left the controls unregistered on purpose, as
vocabulary with no backend consumer. They are now registered for LOADING in every port and
remain presentation-only: no backend generator reads them, and they stay out of
`expected-registry.json`, so `metamodelVersion` does not move for this. Gated by the
`view-text-basic` conformance fixture. Attributes on these controls are still registered only
by the TypeScript `ui-web` provider.
- **Python, C#, Java: an inline object-valued attribute now loads as an `attr.properties` bag.**
`"@store": { "collection": "orders" }` on a node that declares no `@store` is the registered
property bag, the same thing the explicit `{ "attr.properties": { "name": ..., "value": {...} } }`
child produces. TypeScript always loaded it that way. Python loaded it as `attr.base` and
failed a strict load with `ERR_UNKNOWN_ATTR`; C# rejected the value with `ERR_BAD_ATTR_VALUE`;
Java (and so Kotlin) loaded it but stored the bag as a JSON-text string. All four loaders now
agree, gated by the `attr-properties-inline` conformance fixture. Nothing that loaded before
stops loading.
- **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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,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), 361 fixtures
│ ├── conformance/ # metamodel (loader + serializer + navigation), 363 fixtures
│ ├── yaml-conformance/ # YAML authoring desugar
│ ├── render-conformance/ # FR-004 byte-identical render oracle
│ ├── verify-conformance/ # FR-004 template-drift gate
Expand Down
2 changes: 1 addition & 1 deletion agent-context/skills/metaobjects-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ The audit never edits code. Pattern: **dry-run → review the diff → apply**.
`APIRouter`; relationship / non-`table` source-kind / `field.object flattened` codegen is partial.
- **C#** has no ObjectManager runtime tier (EF Core is the runtime) — hand services over the generated `DbContext` are expected.
- **Cut subtypes** — `field.byte` / `field.short` / `field.class` are removed; never recommend them.
- **TS/web-only** — `view.*` widget subtypes exist only for TS/web consumers; only `view.base` / `view.currency` are cross-port-gated.
- **TS/web-only** — `view.*` widget subtypes load in every port but are consumed only by TS/web; only `view.base` / `view.currency` are cross-port-gated.
- **Planned, not shipped** — `api.*` / `operation.*` / `binding.*` (FR-024) and MCP exposure of declared prompts/tools are not yet in the registry; their absence is not an adopter defect.
- **Cross-port version-NUMBER skew is by design** — TS/C#/Python `0.x` vs Java/Kotlin `7.x` Maven is correct; never flag the *number lines* differing. But that is exactly why you can't eyeball cross-language drift: compare **`metamodelVersion`** (Phase 0 cross-language consistency item), not the package numbers. A `metamodelVersion` MISMATCH across ports *is* a finding; so is a port lagging its ecosystem's latest release. Also flag *intra-port* drift (mixed versions within one port, or a runtime package in `devDependencies`).
- **Stale upstream prose** — "hand-write the Spring controller" (Java/Kotlin) is out of date; trust `meta gen --list`, not stale prose.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,12 @@ soft-delete / status / type view it models.
options the currency view models. **Cross-port-gated** (with `view.base`).
- **`layout.dataGrid`** (`@columns`, `@defaultSortField`, `@defaultSortOrder`, `@pageSize`) —
hunt hand-written grid column definitions + data hooks a data-grid layout generates.
- **CALIBRATION — TS/web-only:** the `view.*` widget subtypes exist only for TS/web consumers
- **CALIBRATION — TS/web-only:** the `view.*` widget subtypes are consumed only by TS/web
and are NOT in the cross-port registry — `view.text`, `view.textarea`, `view.date`,
`view.month`, `view.hotlink`, `view.dropdown`, `view.radio`, `view.checkbox`, `view.number`,
`view.password`, `view.hidden`, `view.web`. **Audit these only for TS adopters.** Only
`view.month`, `view.hotlink`, `view.image`, `view.dropdown`, `view.radio`, `view.checkbox`,
`view.number`, `view.password`, `view.hidden`, `view.web`. Every port LOADS them, so shared metadata that
carries them is valid in a backend port; nothing outside TS/web reads them. **Audit these
only for TS adopters.** Only
`view.base` / `view.currency` are cross-port-gated.

## Template — `template.*` (prompt pillar)
Expand Down
8 changes: 4 additions & 4 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) | 361 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 363 | ✓ | ✓ | 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/) | 31 | ✓ | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/verify-strict-conformance/`](../fixtures/verify-strict-conformance/) | 1 | ✓ | — | — | — | ✓ |
Expand Down Expand Up @@ -202,7 +202,7 @@ unit-test runners (`bun test`, `dotnet test`, `pytest`, `mvn test`) pull Docker.

## Fixture-to-doc mapping

### `fixtures/conformance/` — metamodel loader + canonical serializer (361)
### `fixtures/conformance/` — metamodel loader + canonical serializer (363)

| Fixture prefix | Feature doc |
|---|---|
Expand Down Expand Up @@ -340,7 +340,7 @@ identically** — every case's `expectFiles`/`expectImported`/`expectSelected`/
`expectMigrateGoverned`/`expectLoadError` assertions apply to both runners with no
exemption. The two overlay cases that need a view child as incidental content use
`view.currency` (the one concrete `view.*` subtype registered cross-port), not
`view.text` — a prior revision used `view.text`, which Python doesn't register, and
`view.text` — a prior revision used `view.text`, which Python did not register then, and
carried a since-discharged allowlist for it. Java, Kotlin and C# have no runner —
Phase 1a is TypeScript + Python only; those three ports arrive in Phase 2.

Expand Down Expand Up @@ -397,7 +397,7 @@ own those two functions), and

## Orphaned fixtures (tested but not yet documented)

The fixtures in the nine corpora mapped above (metamodel 361 + yaml 16 + verify 31
The fixtures in the nine corpora mapped above (metamodel 363 + yaml 16 + verify 31
+ render 15 + persistence 39 + api-contract 61 + source-resolution 25 + scope 10 +
dependency 23) each map to a feature doc. None are orphaned today. The remaining
corpora in the totals table gate tooling contracts (registry manifests, provider
Expand Down
14 changes: 8 additions & 6 deletions docs/features/image-upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ image to a consumer-supplied adapter and stores only the key the adapter
returns. No image bytes ever cross the MetaObjects wire — the field, the
generated Zod schema, and the REST payload all carry a plain string.

This is a **TS-web-only** feature. The metamodel vocabulary (`view.image` and
its five attrs) is registered by the `metaobjects-ui-web` provider and applied
only in TypeScript; the non-TS ports carry a byte-identical mirror of the spec
file for drift parity but never apply the provider, and none of them ship an
upload widget. A `field.string` authored with a `view.image` child is, to a
Java/Kotlin/C#/Python port, just a plain string field.
This is a **TS-web-only** feature — the upload widget, crop UI and adapter
contract exist only in the TypeScript web client, and no other port ships one.
The `view.image` subtype itself LOADS in every port (all the generic `view.*`
controls register for loading everywhere; they stay manifest-excluded as
PRESENTATION_ONLY — `fixtures/registry-conformance/README.md`), but its five
attrs (`@aspectRatio` … `@maxBytes`) are registered only by the TypeScript
`metaobjects-ui-web` provider, so a `view.image` carrying them is authorable in
TypeScript alone. A backend port reads the field as the plain string it is.

## Authoring: `field.string` + `view.image`

Expand Down
12 changes: 8 additions & 4 deletions docs/features/migrations/base-subtypes-are-not-authorable.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,14 @@ the owning field's subtype, so a boolean default stays a boolean rather than bei

### `view.base` is the one genuine removal

`view.base` was a view carrying no kind. Core registers only `view.base` and `view.currency`; the
other view subtypes (`view.text`, `view.textarea`, `view.checkbox`, `view.radio`, `view.image`, …)
come from the TypeScript-side UI provider. A project on a port that does not apply that provider
therefore has `view.currency` and nothing else until it does.
`view.base` was a view carrying no kind. At this migration's cut, TypeScript registered all the
concrete view subtypes while the non-TS ports registered only `view.base` and `view.currency` — so
a backend port rejected `view.text` with `ERR_UNKNOWN_SUBTYPE`. That asymmetry is since gone: every
port now registers the generic `view.*` controls (`view.text`, `view.textarea`, `view.checkbox`,
`view.radio`, `view.image`, …) for LOADING, still excluded from the cross-port manifest as
PRESENTATION_ONLY (`fixtures/registry-conformance/README.md`), while the attrs ON those controls
remain registered only by the TypeScript UI provider. The removal stands unchanged: `view.base`
itself is refused in every port.

Nothing is lost that carried information — a `view.base` node declared no kind and no attrs, and
the JVM never accepted one — but if you were using it as a placeholder, delete it rather than
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.1"
},
"counts": {
"fixtures": 361,
"fixtures": 363,
"corpora": 25,
"baseTypes": 17
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ The audit never edits code. Pattern: **dry-run → review the diff → apply**.
`APIRouter`; relationship / non-`table` source-kind / `field.object flattened` codegen is partial.
- **C#** has no ObjectManager runtime tier (EF Core is the runtime) — hand services over the generated `DbContext` are expected.
- **Cut subtypes** — `field.byte` / `field.short` / `field.class` are removed; never recommend them.
- **TS/web-only** — `view.*` widget subtypes exist only for TS/web consumers; only `view.base` / `view.currency` are cross-port-gated.
- **TS/web-only** — `view.*` widget subtypes load in every port but are consumed only by TS/web; only `view.base` / `view.currency` are cross-port-gated.
- **Planned, not shipped** — `api.*` / `operation.*` / `binding.*` (FR-024) and MCP exposure of declared prompts/tools are not yet in the registry; their absence is not an adopter defect.
- **Cross-port version-NUMBER skew is by design** — TS/C#/Python `0.x` vs Java/Kotlin `7.x` Maven is correct; never flag the *number lines* differing. But that is exactly why you can't eyeball cross-language drift: compare **`metamodelVersion`** (Phase 0 cross-language consistency item), not the package numbers. A `metamodelVersion` MISMATCH across ports *is* a finding; so is a port lagging its ecosystem's latest release. Also flag *intra-port* drift (mixed versions within one port, or a runtime package in `devDependencies`).
- **Stale upstream prose** — "hand-write the Spring controller" (Java/Kotlin) is out of date; trust `meta gen --list`, not stale prose.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,12 @@ soft-delete / status / type view it models.
options the currency view models. **Cross-port-gated** (with `view.base`).
- **`layout.dataGrid`** (`@columns`, `@defaultSortField`, `@defaultSortOrder`, `@pageSize`) —
hunt hand-written grid column definitions + data hooks a data-grid layout generates.
- **CALIBRATION — TS/web-only:** the `view.*` widget subtypes exist only for TS/web consumers
- **CALIBRATION — TS/web-only:** the `view.*` widget subtypes are consumed only by TS/web
and are NOT in the cross-port registry — `view.text`, `view.textarea`, `view.date`,
`view.month`, `view.hotlink`, `view.dropdown`, `view.radio`, `view.checkbox`, `view.number`,
`view.password`, `view.hidden`, `view.web`. **Audit these only for TS adopters.** Only
`view.month`, `view.hotlink`, `view.image`, `view.dropdown`, `view.radio`, `view.checkbox`,
`view.number`, `view.password`, `view.hidden`, `view.web`. Every port LOADS them, so shared metadata that
carries them is valid in a backend port; nothing outside TS/web reads them. **Audit these
only for TS adopters.** Only
`view.base` / `view.currency` are cross-port-gated.

## Template — `template.*` (prompt pillar)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ The audit never edits code. Pattern: **dry-run → review the diff → apply**.
`APIRouter`; relationship / non-`table` source-kind / `field.object flattened` codegen is partial.
- **C#** has no ObjectManager runtime tier (EF Core is the runtime) — hand services over the generated `DbContext` are expected.
- **Cut subtypes** — `field.byte` / `field.short` / `field.class` are removed; never recommend them.
- **TS/web-only** — `view.*` widget subtypes exist only for TS/web consumers; only `view.base` / `view.currency` are cross-port-gated.
- **TS/web-only** — `view.*` widget subtypes load in every port but are consumed only by TS/web; only `view.base` / `view.currency` are cross-port-gated.
- **Planned, not shipped** — `api.*` / `operation.*` / `binding.*` (FR-024) and MCP exposure of declared prompts/tools are not yet in the registry; their absence is not an adopter defect.
- **Cross-port version-NUMBER skew is by design** — TS/C#/Python `0.x` vs Java/Kotlin `7.x` Maven is correct; never flag the *number lines* differing. But that is exactly why you can't eyeball cross-language drift: compare **`metamodelVersion`** (Phase 0 cross-language consistency item), not the package numbers. A `metamodelVersion` MISMATCH across ports *is* a finding; so is a port lagging its ecosystem's latest release. Also flag *intra-port* drift (mixed versions within one port, or a runtime package in `devDependencies`).
- **Stale upstream prose** — "hand-write the Spring controller" (Java/Kotlin) is out of date; trust `meta gen --list`, not stale prose.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,12 @@ soft-delete / status / type view it models.
options the currency view models. **Cross-port-gated** (with `view.base`).
- **`layout.dataGrid`** (`@columns`, `@defaultSortField`, `@defaultSortOrder`, `@pageSize`) —
hunt hand-written grid column definitions + data hooks a data-grid layout generates.
- **CALIBRATION — TS/web-only:** the `view.*` widget subtypes exist only for TS/web consumers
- **CALIBRATION — TS/web-only:** the `view.*` widget subtypes are consumed only by TS/web
and are NOT in the cross-port registry — `view.text`, `view.textarea`, `view.date`,
`view.month`, `view.hotlink`, `view.dropdown`, `view.radio`, `view.checkbox`, `view.number`,
`view.password`, `view.hidden`, `view.web`. **Audit these only for TS adopters.** Only
`view.month`, `view.hotlink`, `view.image`, `view.dropdown`, `view.radio`, `view.checkbox`,
`view.number`, `view.password`, `view.hidden`, `view.web`. Every port LOADS them, so shared metadata that
carries them is valid in a backend port; nothing outside TS/web reads them. **Audit these
only for TS adopters.** Only
`view.base` / `view.currency` are cross-port-gated.

## Template — `template.*` (prompt pillar)
Expand Down
Loading
Loading