Skip to content

Commit b349337

Browse files
authored
feat(metadata): mark reserved vocabulary by implementing port (#555)
* feat(registry): vocabulary only some ports implement is cross-port, marked implementedIn The 13 generic view.* controls (with view.textarea @rows and the view.image attrs) and object.* @object used to be carved out of the registry manifest as presentation-only or native-binding vocabulary. They are now in the shared contract in every port, marked rather than removed: - implementedIn names the ports that act on an entry so far (["typescript"] for the view controls, ["java"] for @object). Every other port registers it, loads it strictly and ignores it; an unregistered attr on it still fails. - ports names the ports a language-specific attr belongs to: @dbSetName ["csharp"] and @objectadapter ["java", "kotlin"]. All five ports register the metaobjects-ui-web provider and @object / @objectadapter, emit implementedIn in their manifests, and byte-match expected-registry.json. Java and Python gain the ui-web provider and the spec-reader support for implementedIn; Java's extends-value mapping accepts a double-valued attr. Three conformance fixtures gate the loading behaviour (view-kind1-controls, object-kind1-jvm-binding-attrs, error-kind1-view-unregistered-attr). meta types marks these entries in its text output and carries implementedIn and ports in --format json. The json tsOnly field is kept, derived from implementedIn, and deprecated; it is removed in 1.4. The metamodel docs, site reference pages and agent-context fixtures are regenerated, and docs/CONFORMANCE.md and the fixture counts are updated. * no-mistakes(review): apply reviewer inline fixes * no-mistakes(review): Restore CI-safe golden path in GeneratorGoldenTests * no-mistakes(document): Docs already updated on branch for implementedIn, tsOnly, counts (454) and carve-out removal. One stale historical sentence in registry README fixed. No unresolved gaps.
1 parent ea57ad0 commit b349337

90 files changed

Lines changed: 2469 additions & 327 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ PyPI has had no product change since `0.25.0` — nothing is broken.
7171
- **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.
7272

7373
**Cross-port conformance corpora** (every port runs the shared corpus):
74-
- 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.
74+
- 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.
7575
- Render: `fixtures/render-conformance/`. TS / C# / Java / Kotlin / Python byte-identical.
7676
- 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".)
7777
- 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.

‎CHANGELOG.md‎

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,32 @@ here.**
1313
**`metamodelVersion` moves to `1.3`.** This release adds vocabulary (`field.duration`,
1414
`validator.exactlyOne`, `@constraintName`, `@condition`, `@maxLength` on `field.enum`,
1515
`@sequenceStart`, `@sequenceName`, `@flattenedColumnPrefix`, the `pascal` column rule, and
16-
`@dbSetName`, a C#-only attr) and nothing that loaded before stops loading. A registry attr entry
17-
gains `ports`, carried into the registry manifest: the ports that act on a language-specific attr
18-
(`@dbSetName` names `["csharp"]`). A port that reports `1.2` does not know that vocabulary; upgrade
19-
every port together.
16+
`@dbSetName`, a C#-only attr) and nothing that loaded before stops loading. The registry gains two
17+
fields on a type or attr entry, carried into the registry manifest: `implementedIn` (cross-port
18+
vocabulary only the named ports implement so far, e.g. the `view.*` controls, `["typescript"]`) and
19+
`ports` (vocabulary only the named ports ever act on, e.g. `@dbSetName`, `["csharp"]`, and
20+
`@objectAdapter`, `["java", "kotlin"]`: the JVM runtime both share acts on it). The manifest's row
21+
set grows by the `implementedIn` rows that used to be carved out. A port that reports `1.2` does not
22+
know that vocabulary; upgrade every port together.
2023

2124
### Added
2225

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

514539
- **C#, upgrade note: the next EF migration scaffolds your indexes and comments.** The generated
515540
context now declares every `@unique`, `identity.secondary` and `index.lookup` as an EF index

‎agent-context/templates/always-on.md.mustache‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ from.
7272
- **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.
7373

7474
## Authoring rules you must not violate
75-
- **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.
75+
- **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.
7676
- Nodes are fused-key maps: `{"<type>.<subType>": { ... }}` (e.g. `{"field.string": {"name": "email"}}`) — never split the type and subtype into separate keys.
7777
- Attribute names are unique within a node; for multi-value use one array attr (`@values: [...]`).
7878
- An inline `@maxLength: 50` equals an `attr` child of the same name — never write both.

‎docs/CONFORMANCE.md‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
2626

2727
| Corpus | Fixtures | TS | Java | Kotlin | C# | Python |
2828
|---|---|---|---|---|---|---|
29-
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 451 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
29+
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 454 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
3030
| [`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 |
3131
| [`fixtures/verify-conformance/`](../fixtures/verify-conformance/) | 45 | ✓ | ✓ | inherits via Java | ✓ | ✓ |
3232
| [`fixtures/verify-strict-conformance/`](../fixtures/verify-strict-conformance/) | 1 | ✓ | — | — | — | ✓ |
@@ -132,6 +132,30 @@ are tracked in [#544](https://github.com/metaobjectsdev/metaobjects/issues/544).
132132
- *TODO, Python:* the vocabulary loads; the runtime has no DDL path. If it gains one, the same
133133
semantic and pin apply.
134134

135+
**Cross-port vocabulary implemented in some ports so far (`implementedIn`).** Each entry
136+
below is in the shared registry and the registry manifest, marked `implementedIn`. A port that
137+
has not implemented it must still register it and load it strictly (and then ignore it); an
138+
unregistered attr on it still fails. Gated by the conformance fixtures `view-kind1-controls`,
139+
`object-kind1-jvm-binding-attrs` and `error-kind1-view-unregistered-attr`, and by the registry
140+
canonical (`fixtures/registry-conformance/expected-registry.json`).
141+
142+
- *Implemented in TypeScript only* (`implementedIn: ["typescript"]`). The TS web client and TS
143+
form codegen act on them; no other port does yet.
144+
- `view.checkbox`, `view.date`, `view.dropdown`, `view.hidden`, `view.hotlink`, `view.month`,
145+
`view.number`, `view.password`, `view.radio`, `view.text`, `view.web` (types, no attrs)
146+
- `view.textarea`, and its `@rows`
147+
- `view.image`, and its `@accept`, `@aspectRatio`, `@maxBytes`, `@maxEdge`, `@store`
148+
- *Implemented in Java only* (`implementedIn: ["java"]`): `object.* @object` (ADR-0001), the
149+
native class an object binds to. Java's runtime acts on it; Java's own behaviour is unchanged.
150+
- *Loads strictly in every port.* Each port registers the `metaobjects-ui-web` provider
151+
(`view.textarea @rows`, the `view.image` attrs) and `@object` / `@objectAdapter` on
152+
`object.*`, loads all three fixtures strictly, and emits `implementedIn` in its manifest, which
153+
byte-matches the canonical in all five ports (`KindOneVocabularyTests` in C#;
154+
`registry-conformance.test.ts` in TypeScript; `RegistryManifestConformanceTest` in Java and
155+
Kotlin; `test_registry_conformance.py` in Python).
156+
- *Port-scoped, for comparison* (`ports`, language-specific by nature, never "pending"):
157+
`@dbSetName` (`["csharp"]`) and `@objectAdapter` (`["java", "kotlin"]`).
158+
135159
**Native index vocabulary and `@description` in the ORM models.** `meta migrate` creates
136160
every index (`identity.secondary`, `index.lookup`, a field's `@unique`, with `@where`, `@using`,
137161
`@orders`, `@expr` and the resolved name) and writes `@description` as `COMMENT ON`. What each
@@ -326,7 +350,7 @@ unit-test runners (`bun test`, `dotnet test`, `pytest`, `mvn test`) pull Docker.
326350

327351
## Fixture-to-doc mapping
328352

329-
### `fixtures/conformance/` — metamodel loader + canonical serializer (451)
353+
### `fixtures/conformance/` — metamodel loader + canonical serializer (454)
330354

331355
| Fixture prefix | Feature doc |
332356
|---|---|
@@ -687,7 +711,7 @@ JSON and verify payload feeds it unchanged.
687711

688712
## Orphaned fixtures (tested but not yet documented)
689713

690-
The fixtures in the nine corpora mapped above (metamodel 451 + yaml 16 + verify 45
714+
The fixtures in the nine corpora mapped above (metamodel 454 + yaml 16 + verify 45
691715
+ render 18 + persistence 62 + api-contract 123 + source-resolution 25 + scope 10 +
692716
dependency 47) each map to a feature doc, and so do the two requirement corpora, `shared-model-conformance`, the
693717
`cube-model` corpus and `explorer-conformance`, whose case lists live in their own READMEs. None are orphaned today. The remaining

‎docs/features/image-upload.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ generated Zod schema, and the REST payload all carry a plain string.
99

1010
This is a **TS-web-only** feature — the upload widget, crop UI and adapter
1111
contract exist only in the TypeScript web client, and no other port ships one.
12-
The `view.image` subtype itself LOADS in every port (all the generic `view.*`
13-
controls register for loading everywhere; they stay manifest-excluded as
14-
PRESENTATION_ONLY — `fixtures/registry-conformance/README.md`), but its five
15-
attrs (`@aspectRatio` … `@maxBytes`) are registered only by the TypeScript
16-
`metaobjects-ui-web` provider, so a `view.image` carrying them is authorable in
17-
TypeScript alone. A backend port reads the field as the plain string it is.
12+
`view.image` and its five attrs (`@aspectRatio` … `@maxBytes`) are cross-port
13+
vocabulary that only TypeScript implements so far
14+
(`implementedIn: ["typescript"]` in the registry manifest —
15+
`fixtures/registry-conformance/README.md`). Every port registers the attrs and
16+
loads them strictly, and none but TypeScript acts on them. A backend port reads
17+
the field as the plain string it is.
1818

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

‎docs/features/migrations/base-subtypes-are-not-authorable.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,8 @@ the owning field's subtype, so a boolean default stays a boolean rather than bei
7171
concrete view subtypes while the non-TS ports registered only `view.base` and `view.currency` — so
7272
a backend port rejected `view.text` with `ERR_UNKNOWN_SUBTYPE`. That asymmetry is since gone: every
7373
port now registers the generic `view.*` controls (`view.text`, `view.textarea`, `view.checkbox`,
74-
`view.radio`, `view.image`, …) for LOADING, still excluded from the cross-port manifest as
75-
PRESENTATION_ONLY (`fixtures/registry-conformance/README.md`), while the attrs ON those controls
76-
remain registered only by the TypeScript UI provider. The removal stands unchanged: `view.base`
74+
`view.radio`, `view.image`, …), which are cross-port vocabulary only TypeScript implements so far
75+
(`fixtures/registry-conformance/README.md`). The removal stands unchanged: `view.base`
7776
itself is refused in every port.
7877

7978
Nothing is lost that carried information — a `view.base` node declared no kind and no attrs, and

‎examples/showcase/site-payload.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"metamodel": "1.3"
88
},
99
"counts": {
10-
"fixtures": 451,
10+
"fixtures": 454,
1111
"corpora": 38,
1212
"baseTypes": 17
1313
},

0 commit comments

Comments
 (0)