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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ loading. A port that reports `1.2` does not know that vocabulary; upgrade every

### Added

- **Java and Kotlin: a Spring app loads a shared model's committed snapshot at runtime, and
`metaobjects:verify` checks dependency drift.** `metaobjects:generate` now ships
`.metaobjects/deps.lock.json` and `.metaobjects/deps/**` as resources (target path `.metaobjects`)
when the project has a lock. When `classpath:.metaobjects/deps.lock.json` exists, the Spring
application loader loads each snapshot before `metaobjects.metadata-sources`, verified as the build
does (`ERR_DEPENDENCY_SNAPSHOT_STALE`, `ERR_DEPENDENCY_PACKAGE_NOT_OWNED`), so an entity that
`extends` an imported node boots; an imported node stays load-only. With no lock and nothing
declared, nothing changes. `mvn metaobjects:verify -Dmeta.verify.mode=deps` re-resolves each
declared `path` dependency, hashes its artifact against the lock and fails with
`ERR_DEPENDENCY_UPSTREAM_DRIFT` when it is `drifted` or `unresolved`, passing the shared
`expectCheck` corpus arm; it is opt-in and not part of the default gate set. New public API:
`Dependencies.check`, `Dependencies.SnapshotReader`, `DependencySet.ofSnapshot` and a
`MetaDataLoader.fromUris` overload that takes a `DependencySet`. See `docs/features/metadata-dependencies.md`.

- **A model explorer site, built by `docsSiteFile()`.** The docs site now shows what the
software does and how the model is designed: a main page with count tiles and the feature and
quality cards with their roll-ups, a model map with slices (package, requirement subtree,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ first-week wedge plan — and `meta init` picks up from there.
| Capability requirements (`requirement.*`) | `meta verify` gate + authoring lint; `requirementTests()` stubs | `mvn metaobjects:verify` gate; JUnit Jupiter `requirement-tests` | Gate via the Java Maven goal; JUnit Jupiter `requirement-tests` | `dotnet meta verify` gate; xUnit `requirement-tests` | `metaobjects verify` gate; pytest `requirement-tests` |
| Reporting vocabulary (`dimension` / `measure` / `segment` / `object.report`, FR-044) | Registered; a view-backed report becomes a SQL view in `meta migrate`, is read by `ObjectManager`, and gets generated read-only list routes and a TanStack list hook (no grid or form); the `cube-model` generator writes the vocabulary as Cube data-model files | Registered; OMDB reads a view-backed report; generates a read-only Spring controller, DTO, repository and filter allowlist | Registered (via Java); generates an Exposed table object, a row data class, a filter allowlist and a read-only Spring controller per view-backed report | Registered; generates a keyless EF Core row type, read-only routes and a filter allowlist per view-backed report | Registered; `ObjectManager` reads a view-backed report; generates a Pydantic row model, a read-only FastAPI router and a filter allowlist |
| Libraries (`libraries: [...]`) | Yes | Yes | Yes (via Java) | Yes | Yes |
| Metadata dependencies (`dependencies`) | Yes (`meta deps sync`: `path`, `npm` and `python` transports) | Yes (loads the synced snapshot; `meta deps sync` is Node) | Yes (via Java) | Yes (loads the synced snapshot) | Yes (loads the synced snapshot) |
| Metadata dependencies (`dependencies`) | Yes (`meta deps sync`: `path`, `npm` and `python` transports) | Yes (loads the synced snapshot; `-Dmeta.verify.mode=deps` checks drift; `meta deps sync` is Node) | Yes (via Java) | Yes (loads the synced snapshot) | Yes (loads the synced snapshot) |
| Runtime metadata (ObjectManager-style) | Yes (`runtime-ts`) | Yes (OMDB) | Yes (via Java OMDB + Exposed) | Roadmap | Yes (ObjectManager) |
| React / Angular UI client (browser) | React: **published** (`@metaobjectsdev/react` + `@metaobjectsdev/tanstack`), codegen + runtime. Angular 18: **source-only by decision** ([ADR-0048](spec/decisions/ADR-0048-angular-tier-source-only.md)) — `@metaobjectsdev/angular` + `@metaobjectsdev/codegen-ts-angular` build in-repo on their own `0.6.x` line but are deliberately not on npm (`npm i @metaobjectsdev/angular` will 404) until they meet the ADR's promotion bar. Consume them from source. | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST |
| Own a generator (`eject`) | Yes (`meta eject`) | Yes (`mvn metaobjects:eject`) | Yes (`mvn metaobjects:eject`) | Yes (`dotnet meta eject`) | Yes (`metaobjects eject`) |
Expand Down
9 changes: 5 additions & 4 deletions docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
| [`fixtures/provider-composition-conformance/`](../fixtures/provider-composition-conformance/) | 18 (11 error-shape + 4 compose-load + 3 attr-ownership) | ✓ | ✓ | — (JVM registry via Java) | ✓ | ✓ |
| [`fixtures/source-resolution-conformance/`](../fixtures/source-resolution-conformance/) | 25 cases | ✓ (reference implementation) | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/scope-conformance/`](../fixtures/scope-conformance/) | 10 cases | ✓ (reference implementation) | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/dependency-conformance/`](../fixtures/dependency-conformance/) | 37 cases (26 resolution + 11 check) + 14 transport cases | ✓ (reference implementation; all three arms) | ✓ (resolution arm, all 26; no check, so the 11 check cases are not run) | inherits via Java | ✓ (resolution arm, all 26; no check, so the 11 check cases are not run) | ✓ (resolution arm, all 26 identically; no check yet, so the 11 check cases are not run; the transport arm is Node-only) |
| [`fixtures/dependency-conformance/`](../fixtures/dependency-conformance/) | 37 cases (26 resolution + 11 check) + 14 transport cases | ✓ (reference implementation; all three arms) | ✓ (resolution arm, all 26; check arm, all 11) | inherits via Java | ✓ (resolution arm, all 26; no check, so the 11 check cases are not run) | ✓ (resolution arm, all 26 identically; no check yet, so the 11 check cases are not run; the transport arm is Node-only) |
| [`fixtures/shared-model-conformance/`](../fixtures/shared-model-conformance/) | 11 cases (7 publish + 4 refusal) | ✓ (reference implementation) | — (Phase 2) | — (Phase 2) | — (Phase 2) | ✓ (serializer arm: the 7 publish cases' document bytes; no publisher) |
| [`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) | — | — | — | — |
Expand Down Expand Up @@ -480,9 +480,10 @@ above: one committed `cases.json`, no per-port fixture, no ledger. The corpus ha
for it. Kotlin inherits the Java run through the same loader and Maven plugin.
- **Check (11 cases, `expectCheck`).** For each declared dependency, `current`, `drifted` or
`unresolved` (a missing path, a bad manifest, a hash mismatch, or not yet in the lock). Run
by TypeScript only, in `cli/test/dependency-check-conformance.test.ts`; Python and Java have no
`deps check` yet and filter these cases out. The arm exists so that each port's check is
written against the same edge cases (Phase 2, #482).
by TypeScript, in `cli/test/dependency-check-conformance.test.ts`, and by Java in
`DependencyCheckConformanceTest` (the engine behind `metaobjects:verify -Dmeta.verify.mode=deps`;
Kotlin runs the same goal). Python has no `deps check` yet and filters these cases out. The arm
exists so that each port's check is written against the same edge cases (Phase 2, #482).
- **Transport (14 cases, `transport-cases.json`).** The `npm` and `python` transports `meta deps sync`
uses to find an installed package: npm packages under a temp `node_modules`, Python packages on a
temp import path, the directory each resolves to, the exact lock written, and each refusal. Run by
Expand Down
6 changes: 3 additions & 3 deletions docs/features/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ command surface splits in two:
| TS codegen | Node `meta` | `meta gen` | TS projects. **No `--template-spec` flag, deliberately** — `metaobjects.config.ts` already takes generator VALUES, so a declarative template generator is declared there (`templateGenerator()`, or `templateSpecToGenerators(parseTemplateSpec(spec))` to reuse a C#/Python spec file). Keeping it in the config is what lets `meta verify --codegen` regenerate WITH it, since that gate re-runs the config's generator list; see [declarative template scopes](codegen-concepts.md#declarative-template-scopes) |
| C# codegen | `dotnet meta` | `dotnet meta init` / `gen` / `verify --templates` / `verify --codegen` | a .NET tool (`ToolCommandName=dotnet-meta`); invoked `dotnet meta` so it never shadows the Node `meta`; ships the ADR-0021 D2 subverbs (`--db` rejected, exit 2; bare `verify` = `--templates` plus `--codegen` when an output directory (`csharp.outDir` in `.metaobjects/config.json`, or `--out`) and `codegen/Generators.cs` are present). **The generator list is `codegen/Generators.cs`** (ADR-0034 Amendment 9): `dotnet meta init` writes it with `codegen/Program.cs` (two lines that run it) and `codegen/Codegen.csproj`, and `gen` / `verify --codegen` always run it through `dotnet run --project codegen`. With no `codegen/Generators.cs` they exit 2 and write nothing; `--generators` only narrows the list to declared entries. Every entry may set its own output directory (`ApiDocsGenerator { OutDir = ... }`), and `verify --codegen` compares each one. A declarative Mustache template-spec (the cross-port JSON contract shared with Python) is one more entry, `TemplateSpecGenerator { Spec = "<json>", TemplateRoot = "<dir>" }`; `gen` no longer reads `--template-spec` or a project-root `template-spec.json`, and a run that finds either with no such entry fails before writing anything; see [declarative template scopes](codegen-concepts.md#declarative-template-scopes). `dotnet meta docs` was removed in 1.1.0: it prints the line that points at [the migration section](migrations/docs-are-generators.md#c) and exits 2 |
| Java/Kotlin codegen | Maven plugin | `mvn metaobjects:generate` (`metaobjects:generate`) | Kotlin generators run through the same goal — see below. **No `--template-spec` flag, deliberately** — `<generator>` already loads a consumer class from the project classpath, so the declarative surface is `com.metaobjects.generator.template.TemplateScopeGenerator` wired as an ordinary `<generator>` with `<template>` / `<scope>` / `<outputPattern>` / `<templatesDir>` / `<format>` args (covers BOTH Java and Kotlin); see [declarative template scopes](codegen-concepts.md#declarative-template-scopes). The `generate` and `verify` goals are declared `threadSafe` and support parallel multi-module reactor builds (`mvn -T`) (#233) |
| Java/Kotlin verify | Maven plugin | `mvn metaobjects:verify -Dmeta.verify.mode=codegen\|templates` (`metaobjects:verify`) | parameter-driven ADR-0021 D2 modes (one goal covers BOTH Java + Kotlin): unset (default — `codegen` and `templates`; the template gate is skipped with a note when `templateRoot` is unset) / `codegen` (regen + fail on drift vs committed output, generator-neutral) / `templates` (`{{field}}`↔payload drift via the render `Verify` engine). `db` rejected ("schema verify is the migrate engine, ADR-0015") |
| Java/Kotlin verify | Maven plugin | `mvn metaobjects:verify -Dmeta.verify.mode=codegen\|templates\|deps` (`metaobjects:verify`) | parameter-driven ADR-0021 D2 modes (one goal covers BOTH Java + Kotlin): unset (default — `codegen` and `templates`; the template gate is skipped with a note when `templateRoot` is unset) / `codegen` (regen + fail on drift vs committed output, generator-neutral) / `templates` (`{{field}}`↔payload drift via the render `Verify` engine) / `deps` (opt-in dependency drift, the `path` transport only). `db` rejected ("schema verify is the migrate engine, ADR-0015") |
| Python codegen | console-script | `metaobjects gen` / `verify --codegen` / `verify --templates` | `[project.scripts] metaobjects` — **not** `meta` (that's the Node schema CLI); ships the ADR-0021 D2 subverbs (`--db` rejected, exit 2). **The generator list is `metaobjects.config.yaml`** (ADR-0034 Amendment 9, [below](#declarative-config-metaobjectsconfigyaml--python-codegen)): `gen` and `verify --codegen` run each target's `generators` (and its `templateSpec`, the declarative Mustache template-codegen surface shared with C#) into its `outDir`, and with no config they exit 2 and write nothing. `--generators` only narrows the list to declared entries. `metaobjects docs` was removed in 1.1.0: it prints the line that points at [the migration section](migrations/docs-are-generators.md#python) and exits 2 |

## `verify` is one verb with explicit subverbs (ADR-0021 D2)
Expand All @@ -56,7 +56,7 @@ vocabulary everywhere, each port implementing the modes it supports.
| `verify --codegen` | **Codegen drift** — regenerate from metadata into a temp dir and fail if it differs from the committed generated output. Catches "metadata changed but `meta gen` wasn't re-run". A hand-edited generated file is NOT drift — `meta gen` three-way-merges hand edits by design, so the gate compares the *generated contribution* against the committed `.gen-state/.hashes.json` rather than the file byte-for-byte. It still LISTS every generated file carrying a hand edit (count + paths, and how to see the edit) as a notice that does not change the exit code; `--forbid-hand-edits` makes any hand edit fail the build, for teams that want generated code untouchable (Node `meta` only). | no |
| `verify --templates` | **Template/prompt drift** — `Renderer.verify` checks each `template.*` node's `{{field}}` references against its payload VO (FR-004). A Jinja body (Python and TypeScript, ADR-0061) is checked the same way. | no |
| `verify --docs` | **Removed in 1.1.0** (ADR-0034 Amendment 9). It prints where the check went and exits 2: documentation is written by entries in `generators`, so `verify --codegen` checks it with the rest of the declared output. | — |
| `verify --deps` | **Dependency drift** (FR-023) — re-resolves each declared dependency (its `path`, or its installed `npm` / `python` package) and compares its installed artifact's hash against `.metaobjects/deps.lock.json`, same comparison as `meta deps check`. Needs the publisher reachable, so it is never part of the bare-`verify` default. A `drifted` or `unresolved` dependency fails with `ERR_DEPENDENCY_UPSTREAM_DRIFT`. **Node `meta` only.** See [`metadata-dependencies.md`](metadata-dependencies.md). | no |
| `verify --deps` | **Dependency drift** (FR-023) — re-resolves each declared dependency (its `path`, or its installed `npm` / `python` package) and compares its installed artifact's hash against `.metaobjects/deps.lock.json`, same comparison as `meta deps check`. Needs the publisher reachable, so it is never part of the bare-`verify` default. A `drifted` or `unresolved` dependency fails with `ERR_DEPENDENCY_UPSTREAM_DRIFT`. **Node `meta`; Java and Kotlin have the `path`-only counterpart `-Dmeta.verify.mode=deps`.** See [`metadata-dependencies.md`](metadata-dependencies.md). | no |

Rules of the contract:

Expand Down Expand Up @@ -229,7 +229,7 @@ output; drift is computed per **unique `outputDir`** over the whole `<generators
selection, so several generators may share one `outputDir` without cross-flagging each
other's committed files as stale) and `-Dmeta.verify.mode=templates` (each `template.*` node's `{{field}}`↔payload-VO
field tree via the render `Verify` engine, resolving refs through a filesystem provider rooted
at `-Dmeta.verify.templateRoot`). The one goal covers BOTH Java (`codegen-spring`) and Kotlin
at `-Dmeta.verify.templateRoot`), and `-Dmeta.verify.mode=deps` (opt-in, never in the unset default set: the dependency drift check of `verify --deps`, resolving `path` dependencies only; see the Java port page). The one goal covers BOTH Java (`codegen-spring`) and Kotlin
(`codegen-kotlin`) since they share it. `mode=db` is **cleanly rejected** ("schema verify is the
migrate engine, ADR-0015"); an unknown mode fails listing the valid ones. (Schema `--db` remains
Node-only by the ADR-0015 design — see below.)
Expand Down
19 changes: 15 additions & 4 deletions docs/features/metadata-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ owned packages — with no resolution at all; it just reads
## Every dependency-aware port loads the committed snapshot, never the live path

Once synced, TypeScript's, Python's and Java's collection resolvers (Kotlin loads through
Java's) load dependency artifacts from the committed
Java's; a Spring app's runtime loader reads the same snapshot off the classpath) load dependency artifacts from the committed
snapshot **first**, then your own project files, offline, with no transport
involved. At load time it checks the snapshot against the lock and refuses
(`ERR_DEPENDENCY_SNAPSHOT_STALE`) when they disagree — a missing lock for a declared
Expand Down Expand Up @@ -378,7 +378,8 @@ classifies those as breaking automatically — see [Deferred](#deferred).
`verify --deps` runs `meta deps check`'s comparison as one of `verify`'s gated
subverbs (never part of the bare default — it needs the publisher reachable, its
`path` or its installed package, which CI checking out only your own repo may not
have). Each declared
have). Java and Kotlin run the `path` case of the same comparison as
`mvn metaobjects:verify -Dmeta.verify.mode=deps` ([Java port page](../ports/java.md#consuming-a-shared-model-dependencies)). Each declared
dependency reports one of:

- **`current`** — the installed artifact's hash matches the lock.
Expand Down Expand Up @@ -644,6 +645,15 @@ Two rulings shape it:
`.metaobjects/deps.lock.json` is on the classpath, so a Spring app whose entity `extends` a
dependency's node does not fail at boot for want of one line of configuration.

**Java and Kotlin: runtime load and `verify` (shipped, S4 and S5).** A Spring app loads the committed
snapshot at runtime: `metaobjects:generate` ships `.metaobjects/deps.lock.json` and
`.metaobjects/deps/**` on the classpath, and the application loader reads them when the lock is
there, with the build-time scoping and error codes. `mvn metaobjects:verify -Dmeta.verify.mode=deps`
is the Java and Kotlin `meta deps check`: it resolves a `path` dependency, hashes its artifact against
the lock and fails with `ERR_DEPENDENCY_UPSTREAM_DRIFT` on `drifted` or `unresolved`. It is opt-in, and
an `npm` or `python` dependency is `unresolved` there (run the Node `meta deps check` for those). See
[the Java port page](../ports/java.md#consuming-a-shared-model-dependencies).

**`meta deps sync` and `check` on a Maven- or C#-shaped project (shipped).** Both commands read
only `.metaobjects/config.json`, and finish with a proof that the dependency set loads. A project
whose own metadata is in a build-tool directory (`src/main/resources/metadata`, a C# project's
Expand Down Expand Up @@ -691,7 +701,7 @@ but **not** built yet — do not assume any of the following exists:
an imported entity — "what's imported" is a load-time/codegen concept today, not a
runtime one.
- **Java, Kotlin, Python and C# as publishers** — Java, Kotlin and C# consume (S3 and S6);
publishing is TypeScript only. Spring apps do not yet auto-load the snapshot at runtime (S4).
publishing is TypeScript only.

## Verified by

Expand All @@ -702,7 +712,8 @@ but **not** built yet — do not assume any of the following exists:
resolution-time error above, run by TypeScript (the reference implementation),
Python and Java (Kotlin loads through the Java loader and Maven plugin, so it inherits the
corpus run and adds a codegen test that an imported entity emits nothing). 11 pin the upstream
check (`current`, `drifted`, `unresolved`), run by TypeScript; Python and Java have no check yet.
check (`current`, `drifted`, `unresolved`), run by TypeScript and Java (Kotlin through the same
`metaobjects:verify` goal); Python has no check yet.
Three artifacts under `fixtures/dependency-conformance/artifacts/` are
pinned by sha256 in the README so the corpus itself can never silently drift.
Python's runner asserts all 26 resolution cases identically to TypeScript's, with no
Expand Down
Loading
Loading