Skip to content

Commit 7c41344

Browse files
authored
feat(cli): add maven and nuget dependency transports to meta deps (#541)
* feat: maven and nuget dependency transports for meta deps sync/check (FR-023) A shared model published as a jar or nupkg is now a declared dependency: { name, maven: groupId:artifactId, version, dir? } and { name, nuget, version, dir? }. Resolution reads the local Maven repository (honouring MAVEN_REPO_LOCAL, maven.repo.local and settings.xml) or the NuGet global packages folder (NUGET_PACKAGES), never downloads, and names the GAV or package id plus the fetch command when the package is missing. Java, C# and Python config and lock readers accept the new keys. * no-mistakes(review): Harden zip-slip guard, normalize packaged dir, update deps help * no-mistakes(test): Use PACKAGED_MODEL_DIR constant in deps help text * no-mistakes(document): Updated README dependency transport row and roadmap FR-023 line; llms mirrors left for release cut * no-mistakes(document): Fixed stale npm/python-deferred claim in spec/roadmap.md FR-023 line; README, CLI doc, metadata-dependencies and CHANGELOG already match the change.
1 parent 0bfe483 commit 7c41344

32 files changed

Lines changed: 2110 additions & 126 deletions

‎CHANGELOG.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ loading. A port that reports `1.2` does not know that vocabulary; upgrade every
1818

1919
### Added
2020

21+
- **`maven` and `nuget` dependency transports (FR-023).** `meta deps sync` and `meta deps check` can
22+
now read a shared model out of a published package that is already in the local cache, so a JVM or
23+
.NET consumer no longer needs the publisher's directory on disk. `{ "name": "x", "maven":
24+
"com.acme:model", "version": "1.0.0" }` reads the jar in the local Maven repository (honouring
25+
`$MAVEN_REPO_LOCAL`, `-Dmaven.repo.local`, `settings.xml`'s `<localRepository>`, then
26+
`~/.m2/repository`); `{ "name": "x", "nuget": "Acme.Model", "version": "1.0.0" }` reads the package
27+
in the NuGet global packages folder (`$NUGET_PACKAGES`, else `~/.nuget/packages`). Both read the
28+
manifest and artifact from the package's `metaobjects/` directory (`dir` changes it), write the same
29+
snapshot and lock entry a `path` sync writes, and refuse a package that is not cached with the
30+
coordinates and the command that fetches it (`mvn dependency:get`, `dotnet restore`); nothing is
31+
downloaded. The config and lock readers in Java, Kotlin, Python and C# accept the new keys, and
32+
their `check` reports such a dependency `unresolved` and names the Node `meta deps check`.
33+
`dependency-conformance` gains 2 resolution cases and 18 transport cases.
2134
- **Java, Kotlin and C#: publish a shared model (FR-023).** A `shared-model` generator in every one
2235
of the three ports writes the artifact `<name>.metaobjects.json` and the manifest
2336
`metaobjects.pkg.json`, byte-identical to the TypeScript `sharedModelFile()` and gated by

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ first-week wedge plan — and `meta init` picks up from there.
187187
| 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` |
188188
| 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 |
189189
| Libraries (`libraries: [...]`) | Yes | Yes | Yes (via Java) | Yes | Yes |
190-
| 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) |
190+
| Metadata dependencies (`dependencies`) | Yes (`meta deps sync`: `path`, `npm`, `python`, `maven` and `nuget` 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) |
191191
| Runtime metadata (ObjectManager-style) | Yes (`runtime-ts`) | Yes (OMDB) | Yes (via Java OMDB + Exposed) | Roadmap | Yes (ObjectManager) |
192192
| 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 |
193193
| Own a generator (`eject`) | Yes (`meta eject`) | Yes (`mvn metaobjects:eject`) | Yes (`mvn metaobjects:eject`) | Yes (`dotnet meta eject`) | Yes (`metaobjects eject`) |

‎docs/CONFORMANCE.md‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
4949
| [`fixtures/provider-composition-conformance/`](../fixtures/provider-composition-conformance/) | 18 (11 error-shape + 4 compose-load + 3 attr-ownership) | ✓ | ✓ | — (JVM registry via Java) | ✓ | ✓ |
5050
| [`fixtures/source-resolution-conformance/`](../fixtures/source-resolution-conformance/) | 25 cases | ✓ (reference implementation) | ✓ | inherits via Java | ✓ | ✓ |
5151
| [`fixtures/scope-conformance/`](../fixtures/scope-conformance/) | 10 cases | ✓ (reference implementation) | ✓ | inherits via Java | ✓ | ✓ |
52-
| [`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; check arm, all 11, as `verify --deps`) | ✓ (resolution arm, all 26 identically; check arm, all 11, as `deps check` and `verify --deps`; the transport arm is Node-only) |
52+
| [`fixtures/dependency-conformance/`](../fixtures/dependency-conformance/) | 39 cases (28 resolution + 11 check) + 32 transport cases | ✓ (reference implementation; all three arms) | ✓ (resolution arm, all 28; check arm, all 11) | inherits via Java | ✓ (resolution arm, all 28; check arm, all 11, as `verify --deps`) | ✓ (resolution arm, all 28 identically; check arm, all 11, as `deps check` and `verify --deps`; the transport arm is Node-only) |
5353
| [`fixtures/shared-model-conformance/`](../fixtures/shared-model-conformance/) | 11 cases (7 publish + 4 refusal) | ✓ (reference implementation) | ✓ (all 11; `SharedModelConformanceTest`) | inherits via Java (`KotlinSharedModelGeneratorTest` pins the generator) | ✓ (all 11; `SharedModelConformanceTests`) | ✓ (serializer arm: the 7 publish cases' document bytes; no publisher) |
5454
| [`fixtures/agent-context-conformance/`](../fixtures/agent-context-conformance/) | 4 | ✓ (the emitter is TS-owned) | — | — | — | — |
5555
| [`fixtures/metamodel-docs/`](../fixtures/metamodel-docs/) | 1 | ✓ (docs emit is TS-owned) | — | — | — | — |
@@ -463,17 +463,17 @@ also matches before a trailing newline. Kotlin has no runner of its own: it inhe
463463
the Java loader. Each of those two guards is pinned by a test beside the corpus, since
464464
the corpus asserts matches only and carries no invalid-pattern case.
465465

466-
### `fixtures/dependency-conformance/` (37 cases + 14 transport cases)
466+
### `fixtures/dependency-conformance/` (39 cases + 32 transport cases)
467467

468-
All 37 cases → [features/metadata-dependencies.md](features/metadata-dependencies.md)
468+
All 39 cases → [features/metadata-dependencies.md](features/metadata-dependencies.md)
469469
(declaring a dependency, `meta deps sync`, the committed snapshot + lock, default
470470
exclusion of imported metadata, overlay/extends across the boundary, every load-
471471
and resolution-time failure, and the upstream check). File-shaped like `scope-conformance/`
472472
above: one committed `cases.json`, no per-port fixture, no ledger. The corpus has three arms:
473473

474-
- **Resolution (26 cases).** Every case's `expectFiles`/`expectImported`/`expectSelected`/
474+
- **Resolution (28 cases).** Every case's `expectFiles`/`expectImported`/`expectSelected`/
475475
`expectMigrateGoverned`/`expectLoadError` assertions. **TypeScript (the reference
476-
implementation), Python, Java and C# all run all 26, identically**, with no exemption. The two
476+
implementation), Python, Java and C# all run all 28, identically**, with no exemption. The two
477477
overlay cases that need a view child as incidental content use `view.currency` (the one
478478
concrete `view.*` subtype registered cross-port), not `view.text` — a prior revision used
479479
`view.text`, which Python did not register then, and carried a since-discharged allowlist
@@ -486,12 +486,13 @@ above: one committed `cases.json`, no per-port fixture, no ledger. The corpus ha
486486
(`metaobjects deps check` and `verify --deps`), and by **C#**, in `DependencyConformanceTests`
487487
(`dotnet meta verify --deps`). The arm exists so that each port's check is written against the
488488
same edge cases (Phase 2, #482).
489-
- **Transport (14 cases, `transport-cases.json`).** The `npm` and `python` transports `meta deps sync`
490-
uses to find an installed package: npm packages under a temp `node_modules`, Python packages on a
491-
temp import path, the directory each resolves to, the exact lock written, and each refusal. Run by
489+
- **Transport (32 cases, `transport-cases.json`).** The `npm`, `python`, `maven` and `nuget` transports
490+
`meta deps sync` uses to find an installed package: npm packages under a temp `node_modules`, Python
491+
packages on a temp import path, jars in a temp local Maven repository, packages in a temp NuGet cache,
492+
the directory each resolves to, the exact lock written, and each refusal. Run by
492493
Node only, in `cli/test/dependency-transport-conformance.test.ts`, because Node `meta deps sync` is
493-
the only writer of snapshots and the lock. `cases.json` also carries an `npm` and a `python`
494-
dependency loading from their committed snapshot with nothing installed, which both ports run.
494+
the only writer of snapshots and the lock. `cases.json` also carries an `npm`, a `python`, a `maven` and a `nuget`
495+
dependency loading from their committed snapshot with nothing installed, which every port runs.
495496

496497
**C#** (S6 of FR-023 Phase 2) runs the resolution arm on two rungs of the source ladder: the
497498
neutral one (`Resolve(null, dir)`), and the explicit metadata directory a
@@ -640,7 +641,7 @@ JSON and verify payload feeds it unchanged.
640641

641642
The fixtures in the nine corpora mapped above (metamodel 449 + yaml 16 + verify 45
642643
+ render 18 + persistence 59 + api-contract 121 + source-resolution 25 + scope 10 +
643-
dependency 37) each map to a feature doc, and so do the two requirement corpora, `shared-model-conformance`, the
644+
dependency 39) each map to a feature doc, and so do the two requirement corpora, `shared-model-conformance`, the
644645
`cube-model` corpus and `explorer-conformance`, whose case lists live in their own READMEs. None are orphaned today. The remaining
645646
corpora in the totals table gate tooling contracts (registry manifests, provider
646647
composition, agent context, docs emit) rather than user-facing metamodel behaviour,

‎docs/features/cli.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ command surface splits in two:
3434
| **Vocabulary upgrade** (`upgrade`) | **Node `meta`** | `meta upgrade [--to <version>] [--apply]` | **any backend** — rewrites RETIRED metadata vocabulary (`@violation` → `@counterexample`, `@readOnly` → `@mutability`, dropping `@verifiedBy`) and resolves ATTRIBUTE CONTRADICTIONS (`@fields` beside `@expr` on an index key). Node-only because it edits the metadata documents themselves, which every port shares; a non-TS project runs `npx meta upgrade` against its own `metaobjects/`. **Canonical JSON and YAML alike.** Previews by default. Retirements needing a human decision are refused and the run exits non-zero, so CI cannot record a partial migration as finished |
3535
| **Model export** (`export`) | **Node `meta`** | `meta export [--effective] [--out <file>]` | the loaded model, overlays merged, as one canonical JSON document. `--effective` also writes every inherited field, child and attribute onto the node that inherits it, which is the input the [explorer builder](explorer-data.md) reads. The output is for reading: loading an effective document back would declare each inherited member twice |
3636
| **Vocabulary search** (`types`) | **Node `meta`** | `meta types [query]` | **any backend** — apropos/`kubectl explain` over the live metamodel registry (names + descriptions + when-to-use); the vocabulary is cross-port identical (registry-conformance) |
37-
| **Dependency sync** (`deps`) | **Node `meta`** | `meta deps sync \| check \| list` | **any backend** — resolves a declared metadata dependency (`path`, or an installed `npm` / `python` package) into a committed snapshot + lock (FR-023), so `sync`/`check`/`list` themselves stay Node-only regardless of your server language. TypeScript, Python and Java then LOAD that snapshot at every rung of their own source ladder (Kotlin loads through Java's loader and Maven plugin); C# loads the snapshot through `dotnet meta` (Phase 2 S6). On a project whose own metadata Node `meta` cannot read (a Maven `src/main/resources/metadata` directory, a C# metadata directory, or another port's vocabulary), `sync` and `check` prove the locked dependencies load together and say that the port's own `verify` is the full check. See [`metadata-dependencies.md`](metadata-dependencies.md) |
37+
| **Dependency sync** (`deps`) | **Node `meta`** | `meta deps sync \| check \| list` | **any backend** — resolves a declared metadata dependency (`path`, an installed `npm` / `python` package, or a `maven` jar / `nuget` package already in the local Maven repository / NuGet cache; it never downloads) into a committed snapshot + lock (FR-023), so `sync`/`check`/`list` themselves stay Node-only regardless of your server language. TypeScript, Python and Java then LOAD that snapshot at every rung of their own source ladder (Kotlin loads through Java's loader and Maven plugin); C# loads the snapshot through `dotnet meta` (Phase 2 S6). On a project whose own metadata Node `meta` cannot read (a Maven `src/main/resources/metadata` directory, a C# metadata directory, or another port's vocabulary), `sync` and `check` prove the locked dependencies load together and say that the port's own `verify` is the full check. See [`metadata-dependencies.md`](metadata-dependencies.md) |
3838
| **Canonical format** (`fmt`) | every port | `meta fmt` / `dotnet meta fmt` / `mvn metaobjects:fmt` / `metaobjects fmt` | (#304) ships in **every** port — the canonical serializer already exists in all five, so formatting is local, not Node-only. `--check` lists non-canonical files and exits non-zero without changing anything. Formats each file STANDALONE — own-mode, declared-here layer only (ADR-0039); an `extends` onto another file's base is preserved as-is, never resolved or erred on; a file declaring `overlay: true` anywhere — whether or not a same-file base exists — is reported as skipped rather than merged (merging would drop the overlay marker, a structural change fmt never makes). A YAML file is always skipped — no canonical YAML emitter exists (ADR-0006). Before writing, every port reloads the whole project with the candidate swapped in and refuses the write unless the reloaded model's canonical form is byte-identical to the untouched baseline — fmt never changes meaning. See [Fixture corpus](#meta-fmt--canonical-formatting-304) below and `fixtures/fmt-conformance/README.md` |
3939
| 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) |
4040
| 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 |
@@ -56,7 +56,7 @@ vocabulary everywhere, each port implementing the modes it supports.
5656
| `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 |
5757
| `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 |
5858
| `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. | — |
59-
| `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`; Python (`metaobjects verify --deps`, plus `metaobjects deps check`), C# (`dotnet meta verify --deps`), and Java and Kotlin (`-Dmeta.verify.mode=deps`) have the `path`-only counterpart.** See [`metadata-dependencies.md`](metadata-dependencies.md). | no |
59+
| `verify --deps` | **Dependency drift** (FR-023) — re-resolves each declared dependency (its `path`, its installed `npm` / `python` package, or its `maven` / `nuget` package in the local cache) 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`; Python (`metaobjects verify --deps`, plus `metaobjects deps check`), C# (`dotnet meta verify --deps`), and Java and Kotlin (`-Dmeta.verify.mode=deps`) have the `path`-only counterpart.** See [`metadata-dependencies.md`](metadata-dependencies.md). | no |
6060

6161
Rules of the contract:
6262

0 commit comments

Comments
 (0)