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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ here.**

### Added

- **Java and Kotlin consume a shared model (FR-023 Phase 2).** A project that declares
`dependencies` in `.metaobjects/config.json` loads its committed `.metaobjects/deps/` snapshot
ahead of its own files, in the Maven plugin (every rung of the source ladder) and the Java
loader. The lock is checked as in TypeScript and Python (`ERR_DEPENDENCY_SNAPSHOT_STALE`,
`_METAMODEL_INCOMPATIBLE`, `_NODE_COLLISION`), a local node declared into a dependency's package
fails with `ERR_DEPENDENCY_PACKAGE_NOT_OWNED`, and the Java and Kotlin generators emit nothing for
an imported object unless `scope.include` names its package. Runs all 24 resolution cases of
`fixtures/dependency-conformance/` (the 11 check cases pin the Node `meta deps check`); Kotlin loads through the same loader and adds a codegen test.
Spring runtime auto-load and `verify` drift for dependencies are later slices.
- **Java, Kotlin and C# can compile and match `scope` patterns.** `com.metaobjects.config.Scope`
(Java; Kotlin inherits it) and `MetaObjects.Config.Scope` (C#) port the scope-pattern grammar from
TypeScript (`compilePattern` / `compileScope` / `matchesScope`), and both run the shared
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ sixth ships two libraries at their own stability labels:
`meta eject <library>`. See [`docs/features/libraries.md`](docs/features/libraries.md).

**Sharing a model across your own projects** *(the TypeScript toolchain publishes;
TypeScript and Python projects consume, over a `path` dependency such as a sibling
checkout or submodule; Java, Kotlin and C# arrive in Phase 2)* — one project publishes
TypeScript, Python, Java and Kotlin projects consume, over a `path` dependency such as a sibling
checkout or submodule; C# arrives in Phase 2)* — one project publishes
part of its model (a common `Customer`, an audited base, a set of enums) with
`sharedModelFile()`, and another declares it in `dependencies`, syncs it into a
committed, hash-locked snapshot and builds on it with `extends` and references. When the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ artifact is a contract whose bytes a cross-port corpus pins and whose hash
consumers verify, so everything a consumer checks stays in the package, in the two
functions the copy calls (`buildSharedModel`, `renderSharedModelFiles`). Do not
replace those calls: an unmodified copy publishes the same bytes as the packaged
generator. Only TypeScript can run it in Phase 1a. **Phase 1a is TypeScript and Python
only, full stop** — TypeScript and Python are the only two ports that can
*consume* a dependency at all today, and only the TypeScript toolchain can
*publish* one; Java, Kotlin, and C# do not read `dependencies` yet (Phase 2).
generator. Only TypeScript can run it in Phase 1a. TypeScript, Python, Java and
Kotlin (through Java's loader and Maven plugin) can *consume* a dependency, and
only the TypeScript toolchain can *publish* one; C# does not read
`dependencies` yet (Phase 2).
Full detail: `docs/features/metadata-dependencies.md`.
13 changes: 6 additions & 7 deletions docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
| [`fixtures/provider-composition-conformance/`](../fixtures/provider-composition-conformance/) | 9 (5 error-shape + 4 compose-load) | ✓ | ✓ | — (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/) | 35 cases (24 resolution + 11 check) | ✓ (reference implementation; both arms) | — (Phase 2) | — (Phase 2) | — (Phase 2) | ✓ (resolution arm, all 24 identically; no check yet, so the 11 check cases are not run) |
| [`fixtures/dependency-conformance/`](../fixtures/dependency-conformance/) | 35 cases (24 resolution + 11 check) | ✓ (reference implementation; both arms) | ✓ (resolution arm, all 24; no check, so the 11 check cases are not run) | inherits via Java | — (Phase 2) | ✓ (resolution arm, all 24 identically; no check yet, so the 11 check cases are not run) |
| [`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 @@ -450,19 +450,18 @@ above: one committed `cases.json`, no per-port fixture, no ledger. The corpus ha

- **Resolution (24 cases).** Every case's `expectFiles`/`expectImported`/`expectSelected`/
`expectMigrateGoverned`/`expectLoadError` assertions. **TypeScript (the reference
implementation) and Python both run all 24, identically**, with no exemption. The two
implementation), Python and Java all run all 24, identically**, with no exemption. The two
overlay cases that need a view child as incidental content use `view.currency` (the one
concrete `view.*` subtype registered cross-port), not `view.text` — a prior revision used
`view.text`, which Python did not register then, and carried a since-discharged allowlist
for it.
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 has no
`deps check` yet and filters these cases out. The arm exists so that each port's check is
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).

Java, Kotlin and C# have no runner —
Phase 1a is TypeScript + Python only; those three ports arrive in Phase 2.
C# has no runner yet; it arrives in a later Phase 2 slice.

### `fixtures/shared-model-conformance/` (11 cases)

Expand Down
2 changes: 1 addition & 1 deletion docs/features/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ command surface splits in two:
| **Template/prompt drift** (`verify --templates`) | **Node `meta`** | `meta verify --templates` | TS reference (ADR-0021 D2) — `{{field}}`↔payload; part of the bare-`verify` default |
| **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 |
| **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) |
| **Dependency sync** (`deps`) | **Node `meta`** | `meta deps sync \| check \| list` | **any backend** — resolves a declared metadata dependency's `path` transport into a committed snapshot + lock (FR-023), so `sync`/`check`/`list` themselves stay Node-only regardless of your server language. TypeScript and Python then LOAD that snapshot at every rung of their own source ladder — the two ports that implement dependency resolution in Phase 1a; Java, Kotlin, and C# don't read `dependencies` yet (Phase 2). 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) |
| **Dependency sync** (`deps`) | **Node `meta`** | `meta deps sync \| check \| list` | **any backend** — resolves a declared metadata dependency's `path` transport 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# doesn't read `dependencies` yet (Phase 2). 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) |
| **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` |
| 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 |
Expand Down
50 changes: 28 additions & 22 deletions docs/features/metadata-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ 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 and Python's collection resolvers — the two ports that
implement dependencies in Phase 1a — load dependency artifacts from the committed
Once synced, TypeScript's, Python's and Java's collection resolvers (Kotlin loads through
Java's) 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 @@ -242,8 +242,10 @@ inScope(fqn) = !imported(fqn) || explicitlyIncluded(packageOf(fqn), scope.includ
```

So `scope.include` narrows a Python project's *own* codegen output not at all — it
only ever widens which imported package is let back in. Do not read the TypeScript
formula above as describing both ports.
only ever widens which imported package is let back in. **Java and Kotlin follow Python**
(`DependencySet.inScope`, read by `GeneratorUtil`'s selection points and `RestSurfaceGate`):
the import-exclusion conjunct only, because those generators have never scoped the project's
own objects either. Do not read the TypeScript formula above as describing every port.

`migrate`, `verify --db`, and offline generate compose the schema-side twin, which
also removes an excluded import from the *expected* schema before drift is computed
Expand Down Expand Up @@ -390,15 +392,16 @@ publishes the same bytes as the packaged generator, and a test has a consumer sy
verify what an ejected copy published. See
[own-your-codegen](own-your-codegen.md#shared-model-you-own-the-inputs-the-package-owns-the-artifact).

**Only TypeScript publishes in Phase 1a — and only TypeScript and Python consume.**
TypeScript and Python are the only two ports that can *consume* a dependency at all
today (load its snapshot, extend it, overlay it) — Java, Kotlin, and C# don't read
`dependencies` yet (Phase 2). Of those two, only the TypeScript toolchain can
*publish*: it alone can generate the artifact a publisher ships. A Python project
that wants to publish a shared model runs a TypeScript `sharedModelFile()`
generator against its own metadata the same way any TypeScript consumer would; a
Java, C#, or Kotlin project cannot yet participate as either a publisher or a
consumer.
**Only TypeScript publishes in Phase 1a — TypeScript, Python, Java and Kotlin consume.**
TypeScript, Python, Java and Kotlin can *consume* a dependency (load its snapshot, extend
it, overlay it); C# doesn't read `dependencies` yet (Phase 2). Java reads it at every rung
of the Maven plugin's source ladder (`<sourceDir>`/`<sources>`, the neutral config, the default
directory), and Kotlin inherits that through the same plugin and loader, so a Spring
or Kotlin build consumes a shared model with no extra configuration. Only the TypeScript
toolchain can *publish*: it alone can generate the artifact a publisher ships. A Python,
Java or Kotlin project that wants to publish a shared model runs a TypeScript
`sharedModelFile()` generator against its own metadata the same way any TypeScript consumer
would; a C# project cannot yet participate as either a publisher or a consumer.

## Vendoring vs. depending

Expand Down Expand Up @@ -432,10 +435,10 @@ cross-port contract every consumer gets today.

## Phase 2: Java, Kotlin and C#

Tracked in [#482](https://github.com/metaobjectsdev/metaobjects/issues/482). Today only
TypeScript and Python consume a dependency; Java, Kotlin and C# ignore the `dependencies` key,
and a model there that references a node a dependency would supply fails loudly
(`ERR_UNRESOLVED_OBJECT_REF`), never silently. Phase 2 adds no new contract. The manifest, the
Tracked in [#482](https://github.com/metaobjectsdev/metaobjects/issues/482). Today TypeScript,
Python, Java and Kotlin consume a dependency (Java and Kotlin shipped in slice S3); C# ignores the
`dependencies` key, and a model there that references a node a dependency would supply fails
loudly (`ERR_UNRESOLVED_OBJECT_REF`), never silently. Phase 2 adds no new contract. The manifest, the
lock, the committed snapshot and the 24-case resolution corpus are plain files that every port
already has the pieces to read; what is missing is each port *loading* the snapshot.

Expand Down Expand Up @@ -495,17 +498,20 @@ but **not** built in Phase 1a — do not assume any of the following exists:
- **A runtime `ObjectManager` scope predicate.** Nothing at runtime refuses to serve
an imported entity — "what's imported" is a load-time/codegen concept today, not a
runtime one.
- **Java, Kotlin, and C# as dependency consumers or publishers** — Phase 1a is
TypeScript + Python only; the other three ports arrive in Phase 2.
- **C# as a dependency consumer, and Java, Kotlin and C# as publishers** — Java and
Kotlin consume from Phase 2 slice S3; C# arrives in a later slice. Spring apps do not yet
auto-load the snapshot at runtime (S4).

## Verified by

- [`fixtures/dependency-conformance/`](../../fixtures/dependency-conformance/) — 35
cases. 24 pin resolution, the exhaustive `imported`/`inScope`/`inMigrateScope` sets,
overlay/extends/reference behavior against a synced snapshot, and every load- and
resolution-time error above, run by TypeScript (the reference implementation) and
Python. 11 pin the upstream check (`current`, `drifted`, `unresolved`), run by TypeScript;
Python has no check yet. Three artifacts under `fixtures/dependency-conformance/artifacts/` are
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.
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 24 resolution cases identically to TypeScript's, with no
exemptions — the two overlay cases that need a view child as incidental content
Expand Down
5 changes: 3 additions & 2 deletions docs/features/metadata-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ sources.** It answers a different question than the ladder above — not "where
my metadata live" but "what does my metadata build on that lives elsewhere" — so a
TypeScript project using a declared `sources` list and a Python project using its
own `metadata:` key both get their declared dependencies resolved the same way, on
top of whichever rung supplied their own files. **TypeScript and Python only, in
Phase 1a** — Java, Kotlin, and C# don't read `dependencies` yet (Phase 2). See
top of whichever rung supplied their own files. **TypeScript, Python, Java and Kotlin**
(Kotlin through Java's loader and Maven plugin) — C# doesn't read `dependencies` yet
(Phase 2). See
[`metadata-dependencies.md`](metadata-dependencies.md).

A config file that exists but is malformed is an error at its own rung; it never
Expand Down
Loading
Loading