Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .tmp-drive/corpus/.metaobjects/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"schema_version": 1,
"sources": [],
"extract": {}
}
5 changes: 5 additions & 0 deletions .tmp-drive/corpus/err.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
meta: meta verify — running --templates (default). Explicit subverbs: --templates (prompt drift), --db/--dialect d1 (schema drift), --codegen (codegen drift), --docs (docs drift), --deps (dependency drift), --replay/--replay-snapshot (the committed migration chain replays from empty).
meta: meta verify — no template.* nodes found; the template gate had nothing to check.
meta: meta verify — fields: 1 authoring warning(s) (advisory — does not fail the build):
meta: WARN_REFERENCE_FIELD_NOT_FOUND [acme::app::stock::Item.owner_fk]: identity.reference "owner_fk" lists "ownerIdd" in @fields, but acme::app::stock::Item has no field of that name, inherited and overlaid fields included. Nothing checks this at load, so the reference is built on a field that does not exist. Rename the entry to an existing field, or declare the field.
meta: meta verify — not run: schema (--db <url>), codegen (--codegen), docs (--docs), deps (--deps), replay (--replay) — a bare 'meta verify' runs only the template gate (plus the requirement ledger); name each gate to run it
63 changes: 63 additions & 0 deletions .tmp-drive/corpus/metaobjects/meta.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"metadata.root": {
"package": "acme::app",
"children": [
{
"object.entity": {
"name": "Owner",
"package": "::stock",
"children": [
{
"field.long": {
"name": "id"
}
},
{
"identity.primary": {
"name": "pk",
"@fields": [
"id"
]
}
}
]
}
},
{
"object.entity": {
"name": "Item",
"package": "::stock",
"children": [
{
"field.long": {
"name": "id"
}
},
{
"field.long": {
"name": "ownerId"
}
},
{
"identity.primary": {
"name": "pk",
"@fields": [
"id"
]
}
},
{
"identity.reference": {
"name": "owner_fk",
"@fields": [
"ownerIdd"
],
"@references": "Owner"
}
}
]
}
}
]
}
}
86 changes: 86 additions & 0 deletions .tmp-drive/corpus/out.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"verify": [
{
"gate": "templates",
"ran": true,
"ok": true
},
{
"gate": "schema",
"ran": false,
"ok": true
},
{
"gate": "codegen",
"ran": false,
"ok": true
},
{
"gate": "docs",
"ran": false,
"ok": true
},
{
"gate": "deps",
"ran": false,
"ok": true
},
{
"gate": "requirements",
"ran": true,
"ok": true
},
{
"gate": "replay",
"ran": false,
"ok": true
}
],
"exitCode": 0,
"summary": "2 gate(s) ran, all clean; 1 field authoring finding(s)",
"help": [
"1 field authoring finding(s) — see fields.rows[]; each names a reference identity listing a field its object lacks, or a field name declared twice in one children list"
],
"antiPatterns": {
"status": "ran",
"total": 0,
"rows": []
},
"requirements": {
"status": "ran",
"total": 0,
"rows": []
},
"overlays": {
"status": "ran",
"total": 0,
"rows": []
},
"names": {
"status": "ran",
"total": 0,
"rows": []
},
"deprecations": {
"status": "ran",
"total": 0,
"rows": []
},
"fields": {
"status": "ran",
"total": 1,
"rows": [
{
"code": "WARN_REFERENCE_FIELD_NOT_FOUND",
"path": "acme::app::stock::Item.owner_fk",
"severity": "warn",
"source": "lint",
"message": "identity.reference \"owner_fk\" lists \"ownerIdd\" in @fields, but acme::app::stock::Item has no field of that name, inherited and overlaid fields included. Nothing checks this at load, so the reference is built on a field that does not exist. Rename the entry to an existing field, or declare the field."
}
]
},
"notRepresented": [
"per-gate drift detail (which template variable drifted, which schema change, which generated file differs, which migration failed to replay) — printed as text on stderr; this payload carries each gate's pass/fail verdict",
"the loader's own warnings and the agent-context/manifest advisories — printed as text on stderr"
]
}
63 changes: 63 additions & 0 deletions .tmp-java-drive/metaobjects/meta.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"metadata.root": {
"package": "acme::app",
"children": [
{
"object.entity": {
"name": "Owner",
"package": "::stock",
"children": [
{
"field.long": {
"name": "id"
}
},
{
"identity.primary": {
"name": "pk",
"@fields": [
"id"
]
}
}
]
}
},
{
"object.entity": {
"name": "Item",
"package": "::stock",
"children": [
{
"field.long": {
"name": "id"
}
},
{
"field.long": {
"name": "ownerId"
}
},
{
"identity.primary": {
"name": "pk",
"@fields": [
"id"
]
}
},
{
"identity.reference": {
"name": "owner_fk",
"@fields": [
"ownerIdd"
],
"@references": "Owner"
}
}
]
}
}
]
}
}
20 changes: 20 additions & 0 deletions .tmp-java-drive/mvn-out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< drive:field-lint-drive >-----------------------
[INFO] Building field-lint-drive 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- metaobjects:8.0.13:verify (default-cli) @ field-lint-drive ---
[INFO] OM managed types registered via provider
[INFO] MetaData Mojo > LifeCycle Phase: null
[INFO] MetaData Mojo > Create Loader: MetaDataLoader[manual:null]
[WARNING] metaobjects:verify — fields: 1 authoring warning(s) (advisory — does not fail the build):
[WARNING] WARN_REFERENCE_FIELD_NOT_FOUND [acme::app::stock::Item.owner_fk]: identity.reference "owner_fk" lists "ownerIdd" in @fields, but acme::app::stock::Item has no field of that name, inherited and overlaid fields included. Nothing checks this at load, so the reference is built on a field that does not exist. Rename the entry to an existing field, or declare the field.
[INFO] MetaData Verify Mojo > No codegen drift detected across 0 generator(s) in 0 output director(y/ies).
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.436 s
[INFO] Finished at: 2026-10-04T18:15:29-04:00
[INFO] ------------------------------------------------------------------------
24 changes: 24 additions & 0 deletions .tmp-java-drive/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>drive</groupId>
<artifactId>field-lint-drive</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>com.metaobjects</groupId>
<artifactId>metaobjects-maven-plugin</artifactId>
<version>8.0.13</version>
<configuration>
<loader>
<sourceDir>${project.basedir}/metaobjects</sourceDir>
</loader>
</configuration>
</plugin>
</plugins>
</build>
</project>
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/` (361 fixtures; 26 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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ it until 1.1 ships._

### Added

- **`verify` warns about two field mistakes the loader accepts, in every port.** An
`identity.reference` whose `@fields` names a field the object does not have gets
`WARN_REFERENCE_FIELD_NOT_FOUND`. A field name declared more than once in one object's
`children` list gets `WARN_DUPLICATE_FIELD_NAME`. Both load with no error today and still
do: these are advisory warnings, they never change the exit code, and nothing that loaded
before stops loading. A field inherited through `extends` or added by an overlay file
counts as present, and neither a subtype overriding an inherited field nor an overlay
redeclaring one is a duplicate. The lint runs on every `meta verify`, `dotnet meta verify`,
`mvn metaobjects:verify` and `metaobjects verify`, with the same codes and message text,
gated by the new `fixtures/field-lint-conformance/` corpus. Mute it with `--no-field-lint`
(`-Dmeta.verify.noFieldLint=true` in Maven) or `META_NO_FIELD_LINT=1`. In the Node `meta`
it is the `fields` section of `--format json|toon`.

- **Metamodel 1.1: the reporting vocabulary (FR-044), loader-validated in all five ports.**
Registered: `dimension.attribute`, `dimension.time` (`@grains`: `hour, day, week, month,
quarter, year`, weeks start Monday), `measure.aggregate` (`@agg`: `count, sum, avg, min, max`),
Expand Down
8 changes: 5 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 **25 shared conformance corpora** under
The MetaObjects standard ships **26 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 @@ -49,6 +49,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
| [`fixtures/agent-context-conformance/`](../fixtures/agent-context-conformance/) | 4 | ✓ (the emitter is TS-owned) | — | — | — | — |
| [`fixtures/metamodel-docs/`](../fixtures/metamodel-docs/) | 1 | ✓ (docs emit is TS-owned) | — | — | — | — |
| [`fixtures/fmt-conformance/`](../fixtures/fmt-conformance/) (#304 — `meta fmt`) | 12 | ✓ (reference) | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/field-lint-conformance/`](../fixtures/field-lint-conformance/) (the `verify` field authoring lint) | 14 | ✓ (reference) | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/naming-conformance/`](../fixtures/naming-conformance/) | 8 cases | ✓ | ✓ | inherits via Java (`RouteNaming.pluralize`) | ✓ | ✓ |
| [`fixtures/codegen-noop/`](../fixtures/codegen-noop/) (FR-044 — reporting vocabulary is inert) | 1 model pair (`reporting/with` vs `reporting/without`) | ✓ (codegen + migrate) | ✓ | ✓ | ✓ | ✓ |

Expand All @@ -71,8 +72,9 @@ the corpora above do two different jobs. Only the first is a promise to adopters
Mustache engine), `template-output-render-conformance/`, `output-prompt-conformance/`,
`extract-conformance/`, `verify-conformance/`, `verify-strict-conformance/`,
`persistence-conformance/` (runtime reads and writes, and the TS-owned migration
scenarios), `agent-context-conformance/`, `metamodel-docs/` and `fmt-conformance/`
(the canonical serializer, surfaced per-file — ADR-0034's "canonical format" is core).
scenarios), `agent-context-conformance/`, `metamodel-docs/`, `fmt-conformance/`
(the canonical serializer, surfaced per-file — ADR-0034's "canonical format" is core)
and `field-lint-conformance/` (the advisory field lint every port's `verify` prints).
A red cell here is a MetaObjects bug.
- **Template quality checks — not a promise.** The generated lane of
`api-contract-conformance/`, `generator-registry-conformance/` (stable generator names),
Expand Down
22 changes: 22 additions & 0 deletions docs/features/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,28 @@ Rules of the contract:
`deprecated` inherited through the TARGET's own `extends` chain still counts. Advisory
only, appears as `deprecations` in `--format json|toon`, and is muted with
`--no-deprecation-lint` or `META_NO_DEPRECATION_LINT=1`.
- **The field authoring lint runs on every `verify`, in every port.** It reports two
mistakes that load with no error. `WARN_REFERENCE_FIELD_NOT_FOUND` flags an
`identity.reference` whose `@fields` names a field the object does not have; a field
inherited through `extends` or added by an overlay file counts as present.
`WARN_DUPLICATE_FIELD_NAME` flags a field name declared more than once in one object's
`children` list; a subtype overriding an inherited field and an overlay redeclaring a
field are not findings. This lint is advisory only and never changes the exit code. In
the Node `meta` it appears as `fields` in `--format json|toon`. The other ports print it
as text on stderr (Maven logs it as warnings). Mute it per port:

| CLI | Flag | Environment |
|---|---|---|
| Node `meta verify` | `--no-field-lint` | `META_NO_FIELD_LINT=1` |
| `dotnet meta verify` | `--no-field-lint` | `META_NO_FIELD_LINT=1` |
| `mvn metaobjects:verify` | `-Dmeta.verify.noFieldLint=true` | `META_NO_FIELD_LINT=1` |
| `metaobjects verify` | `--no-field-lint` | `META_NO_FIELD_LINT=1` |

The codes and message text are identical in every port, gated by
[`fixtures/field-lint-conformance/`](../../fixtures/field-lint-conformance/README.md). In
Python, for a multi-file collection whose roots declare different packages, the address
printed for a `::`-relative package is expanded against the merged root's package and can
differ from the other ports.

### The prompt directory: `--prompts` everywhere (F101)

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 @@ -8,7 +8,7 @@
},
"counts": {
"fixtures": 361,
"corpora": 25,
"corpora": 26,
"baseTypes": 17
},
"snippets": {
Expand Down
Loading