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/` (451 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/` (454 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
33 changes: 29 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,32 @@ here.**
**`metamodelVersion` moves to `1.3`.** This release adds vocabulary (`field.duration`,
`validator.exactlyOne`, `@constraintName`, `@condition`, `@maxLength` on `field.enum`,
`@sequenceStart`, `@sequenceName`, `@flattenedColumnPrefix`, the `pascal` column rule, and
`@dbSetName`, a C#-only attr) and nothing that loaded before stops loading. A registry attr entry
gains `ports`, carried into the registry manifest: the ports that act on a language-specific attr
(`@dbSetName` names `["csharp"]`). A port that reports `1.2` does not know that vocabulary; upgrade
every port together.
`@dbSetName`, a C#-only attr) and nothing that loaded before stops loading. The registry gains two
fields on a type or attr entry, carried into the registry manifest: `implementedIn` (cross-port
vocabulary only the named ports implement so far, e.g. the `view.*` controls, `["typescript"]`) and
`ports` (vocabulary only the named ports ever act on, e.g. `@dbSetName`, `["csharp"]`, and
`@objectAdapter`, `["java", "kotlin"]`: the JVM runtime both share acts on it). The manifest's row
set grows by the `implementedIn` rows that used to be carved out. A port that reports `1.2` does not
know that vocabulary; upgrade every port together.

### Added

- **Cross-port vocabulary that only some ports implement so far (`implementedIn`).** A registry
type or attr may name the ports that implement it so far (`"implementedIn": ["typescript"]` on
its `spec/metamodel` entry, carried into the registry manifest). It is still cross-port
vocabulary: every port registers it and loads it strictly, and a port that has not implemented
it ignores it. The 13 generic `view.*` controls, `view.textarea @rows` and the five `view.image`
attrs are `["typescript"]`; `object.* @object` is `["java"]`. They used to be carved out of the
manifest (`PRESENTATION_ONLY`, `NATIVE_BINDING`); both carve-outs are gone, and the registry
canonical grows by those rows. `meta types` marks them `[ts; pending elsewhere]` / `[java;
pending elsewhere]` (it used to say `[ts-only]`, "the cross-port contract does not carry it"; its
JSON change is under Changed), and the metamodel docs now cover the view controls.
`@objectAdapter` joins `@dbSetName` as port-scoped (`"ports": ["java", "kotlin"]`). C#, Java,
Kotlin and Python now register the `metaobjects-ui-web` attrs (`view.textarea @rows` and the
`view.image` attrs), and C# and Python register `@object` / `@objectAdapter`, so a model that
uses any of them loads strictly in every port. Gated by the conformance fixtures
`view-kind1-controls`, `object-kind1-jvm-binding-attrs` and
`error-kind1-view-unregistered-attr`, and by the registry canonical.
- **Port-scoped vocabulary, and `@dbSetName`.** A registry attr may name the ports
that act on it (`"ports": ["csharp"]` on its `spec/metamodel` entry, carried into the registry
manifest). Every port registers and type-checks it, so a model that carries it loads under every
Expand Down Expand Up @@ -510,6 +529,12 @@ every port together.
Python, `Verify` in C#, the `verify` field of `RenderRequest` in Java and Kotlin). If a template
already named a missing variable, regenerate and fix the name; the generated helper text changes
(TypeScript `prompts.ts` and Python helpers gain a `verify` argument).
- **Deprecated, CLI: `tsOnly` in `meta types --format json`; read `implementedIn`.** Each row
and attr now names the ports that implement it so far (`"implementedIn": ["typescript"]`), and a
port-scoped attr carries `ports`. `tsOnly` is still emitted through 1.3.x, derived from
`implementedIn` (`true` exactly when it is `["typescript"]`), and is removed in 1.4. The text
output's `[ts-only]` marker is now `[ts; pending elsewhere]`, and a port-scoped attr shows
`[<port>-only]` (`[java+kotlin]` for `@objectAdapter`, which names two).

- **C#, upgrade note: the next EF migration scaffolds your indexes and comments.** The generated
context now declares every `@unique`, `identity.secondary` and `index.lookup` as an EF index
Expand Down
2 changes: 1 addition & 1 deletion agent-context/templates/always-on.md.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ from.
- **JVM:** serialize a MetaObject-backed instance (a `pojoAware`-flavor generated class, a runtime `ValueObject`, or any `MetaObjectAware` type) through the MetaObjects JSON layer — never hand-configure a Jackson/Gson mapper around the framework fields to make a default mapper cope.

## Authoring rules you must not violate
- **Search the vocabulary before concluding it cannot be expressed — `meta types <term>`.** An unregistered TYPE or SUBTYPE fails the load in every command. An undeclared `@attr` is different: it fails `meta verify`, which loads strict, and NOT `meta gen`, which does not — so an invented attribute passes codegen silently and is caught by the drift gate rather than by the build. Either way inventing vocabulary is a failure you will hit, not a shortcut. Search names with `meta types <term>`, names and descriptions with `--all` ("find it by what it does"), and one construct's valid `@attrs` with `meta types <type>.<subType> --detail`; `--format json` returns the same answer as one machine-readable document, each attr with its `allowedValues`. Rows marked `[ts-only]` are TypeScript-only vocabulary the cross-port contract does not carry. `[<port>-only]` (such as `[csharp-only]`) marks vocabulary only that port ever acts on. It is the Node `meta` CLI and, like `meta migrate`, works whatever your backend — it reads the registry, so it needs no project. Ask for NEW vocabulary only once that search comes up empty.
- **Search the vocabulary before concluding it cannot be expressed — `meta types <term>`.** An unregistered TYPE or SUBTYPE fails the load in every command. An undeclared `@attr` is different: it fails `meta verify`, which loads strict, and NOT `meta gen`, which does not — so an invented attribute passes codegen silently and is caught by the drift gate rather than by the build. Either way inventing vocabulary is a failure you will hit, not a shortcut. Search names with `meta types <term>`, names and descriptions with `--all` ("find it by what it does"), and one construct's valid `@attrs` with `meta types <type>.<subType> --detail`; `--format json` returns the same answer as one machine-readable document, each attr with its `allowedValues`. Rows marked `[ts; pending elsewhere]` (or `[java; …]`) are cross-port vocabulary only that port implements so far: every port loads it, the others ignore it. `[<port>-only]` (`[csharp-only]`; several ports: `[java+kotlin]`) marks vocabulary only those ports ever act on. It is the Node `meta` CLI and, like `meta migrate`, works whatever your backend — it reads the registry, so it needs no project. Ask for NEW vocabulary only once that search comes up empty.
- Nodes are fused-key maps: `{"<type>.<subType>": { ... }}` (e.g. `{"field.string": {"name": "email"}}`) — never split the type and subtype into separate keys.
- Attribute names are unique within a node; for multi-value use one array attr (`@values: [...]`).
- An inline `@maxLength: 50` equals an `attr` child of the same name — never write both.
Expand Down
30 changes: 27 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) | 451 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 454 | ✓ | ✓ | 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 @@ -132,6 +132,30 @@ are tracked in [#544](https://github.com/metaobjectsdev/metaobjects/issues/544).
- *TODO, Python:* the vocabulary loads; the runtime has no DDL path. If it gains one, the same
semantic and pin apply.

**Cross-port vocabulary implemented in some ports so far (`implementedIn`).** Each entry
below is in the shared registry and the registry manifest, marked `implementedIn`. A port that
has not implemented it must still register it and load it strictly (and then ignore it); an
unregistered attr on it still fails. Gated by the conformance fixtures `view-kind1-controls`,
`object-kind1-jvm-binding-attrs` and `error-kind1-view-unregistered-attr`, and by the registry
canonical (`fixtures/registry-conformance/expected-registry.json`).

- *Implemented in TypeScript only* (`implementedIn: ["typescript"]`). The TS web client and TS
form codegen act on them; no other port does yet.
- `view.checkbox`, `view.date`, `view.dropdown`, `view.hidden`, `view.hotlink`, `view.month`,
`view.number`, `view.password`, `view.radio`, `view.text`, `view.web` (types, no attrs)
- `view.textarea`, and its `@rows`
- `view.image`, and its `@accept`, `@aspectRatio`, `@maxBytes`, `@maxEdge`, `@store`
- *Implemented in Java only* (`implementedIn: ["java"]`): `object.* @object` (ADR-0001), the
native class an object binds to. Java's runtime acts on it; Java's own behaviour is unchanged.
- *Loads strictly in every port.* Each port registers the `metaobjects-ui-web` provider
(`view.textarea @rows`, the `view.image` attrs) and `@object` / `@objectAdapter` on
`object.*`, loads all three fixtures strictly, and emits `implementedIn` in its manifest, which
byte-matches the canonical in all five ports (`KindOneVocabularyTests` in C#;
`registry-conformance.test.ts` in TypeScript; `RegistryManifestConformanceTest` in Java and
Kotlin; `test_registry_conformance.py` in Python).
- *Port-scoped, for comparison* (`ports`, language-specific by nature, never "pending"):
`@dbSetName` (`["csharp"]`) and `@objectAdapter` (`["java", "kotlin"]`).

**Native index vocabulary and `@description` in the ORM models.** `meta migrate` creates
every index (`identity.secondary`, `index.lookup`, a field's `@unique`, with `@where`, `@using`,
`@orders`, `@expr` and the resolved name) and writes `@description` as `COMMENT ON`. What each
Expand Down Expand Up @@ -326,7 +350,7 @@ unit-test runners (`bun test`, `dotnet test`, `pytest`, `mvn test`) pull Docker.

## Fixture-to-doc mapping

### `fixtures/conformance/` — metamodel loader + canonical serializer (451)
### `fixtures/conformance/` — metamodel loader + canonical serializer (454)

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

## Orphaned fixtures (tested but not yet documented)

The fixtures in the nine corpora mapped above (metamodel 451 + yaml 16 + verify 45
The fixtures in the nine corpora mapped above (metamodel 454 + 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
12 changes: 6 additions & 6 deletions docs/features/image-upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ generated Zod schema, and the REST payload all carry a plain string.

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.
`view.image` and its five attrs (`@aspectRatio` … `@maxBytes`) are cross-port
vocabulary that only TypeScript implements so far
(`implementedIn: ["typescript"]` in the registry manifest —
`fixtures/registry-conformance/README.md`). Every port registers the attrs and
loads them strictly, and none but TypeScript acts on them. A backend port reads
the field as the plain string it is.

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

Expand Down
5 changes: 2 additions & 3 deletions docs/features/migrations/base-subtypes-are-not-authorable.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,8 @@ the owning field's subtype, so a boolean default stays a boolean rather than bei
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`
`view.radio`, `view.image`, …), which are cross-port vocabulary only TypeScript implements so far
(`fixtures/registry-conformance/README.md`). 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
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": 451,
"fixtures": 454,
"corpora": 38,
"baseTypes": 17
},
Expand Down
Loading
Loading