Skip to content

Commit eaff41d

Browse files
committed
Merge remote-tracking branch 'origin/main' into followup/m2m-selfjoin-declaring-entity
# Conflicts: # CHANGELOG.md # server/python/src/metaobjects/meta/core/relationship/derive_m2m_fields.py
2 parents 06daf4f + e613da2 commit eaff41d

133 files changed

Lines changed: 5677 additions & 561 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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ MetaObjects is a **cross-language metadata standard** for declaring typed entity
66

77
The metamodel is the **durable spine**; generated code is the **disposable artifact**. Substrate is local-first: typed metadata lives in your repo, generated code is idiomatic per-language output that runs without any MetaObjects dependency at runtime. If `@metaobjectsdev/*` disappears tomorrow, you keep working code.
88

9-
## Five pillars
9+
## Six pillars
1010

11-
The first four ship per-language today across the five ports (TS / C# / Java / Python / Kotlin), with cross-port conformance corpora verifying byte-identical behavior. The fifth ships its vocabulary and its `verify` checks in every port, and its test scaffolding in TypeScript only:
11+
The first four ship per-language today across the five ports (TS / C# / Java / Python / Kotlin), with cross-port conformance corpora verifying byte-identical behavior. The fifth ships its vocabulary and its `verify` checks in every port, and its test scaffolding in TypeScript only. The sixth is content the other five compose, shipped as named opt-in artifacts:
1212

1313
1. **Codegen** — emit idiomatic per-language code (Drizzle/Zod + Fastify for TS, EF Core + ASP.NET for C#, Spring REST + DTO + Repository for Java via `codegen-spring`, Pydantic + FastAPI for Python, KotlinPoet + Exposed + Spring for Kotlin via `codegen-kotlin`). Hand-edit-preserving regen via three-way merge.
1414
2. **Runtime metadata** — load metadata at runtime, drive behavior dynamically (CRUD, validation, relationships, dynamic admin UIs, LLM tool registration). On Kysely (TS), a DB-API 2 driver via ObjectManager (Python), modernized JDBC + Spring-tx via OMDB (Java), Exposed (Kotlin), EF Core (C#).
1515
3. **Drift detection** — `meta verify` catches divergence between code and metadata (covers entity codegen, prompt templates, output parsers, schema). Quality-of-life on top of codegen + runtime.
1616
4. **Prompt construction** — a prompt is code, not a string scattered across services. Declare a prompt's payload as a typed projection (payload bloat becomes a diff), keep its text external and provider-resolved, and render it deterministically: snapshot-testable, cache-stable (no whitespace change silently breaking exact-prefix prompt-cache hits), and drift-checked at build time so a renamed field can't degrade a prompt. Conformance-gated, so the guarantee holds in every language port. **Render + payload-VO codegen + `verify` + parser-on-receipt for a *responding* `template.prompt` — one carrying `@responseRef` (FR-006) — + the output-format prompt fragment & tolerant `extract` parser (FR-010) ship in all five ports today** (since 0.24.0 the whole inbound tier keys off `@responseRef`; a `template.output` is outbound-only and emits no parser — ADR-0052) — the library-side building blocks of the pillar are complete. The one remaining library-side piece is MCP exposure of declared prompts/tools (see `spec/roadmap.md`); the application-level consolidation (eval harness, end-to-end declared-prompt orchestration) and consumer adoption are exercised in adopter projects, not in this library repo. Designed in `docs/superpowers/specs/2026-05-22-fr-004-cross-language-prompt-construction-design.md`.
1717
5. **Requirements and testing** — declare what the software is supposed to *do* in the same model as the entities, so a capability claim is checkable instead of prose. The other four pillars keep the code honest about the *model*; this one asks whether the thing you said the software does is actually built — an absence no test can fail on, because a test exercises code that exists. `requirement.functional` (fails when *nothing* implements it) and `requirement.architectural` (fails when something *violates* it) are registered vocabulary in all five ports, with the loader enforcing the closed `@status` enum. `@implementedBy` is **resolved, not trusted** — it names a real member of the real model, so a claim whose implementation was renamed or deleted fails the build instead of going quietly stale. `meta verify` reports the ledger on every run (unresolved links, entities no claim covers, gaps recorded versus gaps nobody has ruled on) plus an authoring lint whose findings can never fail a build; `meta docs` renders it for humans and for agents. **The port split, stated exactly: the vocabulary and the `verify` checks are cross-port; `requirementTests()` — which scaffolds a test stub per claim — is TypeScript-only.** A project that declares no `requirement.*` nodes sees no change at all. Note the standing carve-out: `agent-context/skills/metaobjects-fit-assessment/SKILL.md` deliberately does NOT treat `requirement.*` as an assessment axis — see the ruling in that file before "finishing the job" there.
1818

19+
6. **Libraries** — reusable declared design, shipped as metadata and opted into by name (`"libraries": ["iam"]` in `.metaobjects/config.json`). Not a sixth verb: a library is the **reuse unit that composes the other five** — entities, requirements, the generators its design implies, its runtime packages — as one named, opt-in, drift-gated artifact. What makes it a pillar rather than a folder of YAML is the fifth: without requirements a library is a schema snippet; with them it is design an adopter's build is held to, which is the same test the requirements pillar passes (*does it change what an agent can be checked against?*). **A library is LAYERED and its core layer is INERT** — the core declares no `source.rdb`, and a sourceless object generates nothing and migrates to nothing (#248), so `["iam"]` adds zero tables and zero generated code while making the design present and resolvable; `["iam", "iam/db"]` is the separate opt-in that proposes the schema. **Copy is the expected mode** (`meta eject <library>` — ADR-0034's ruling applied to metadata), and an ejected copy still named in `libraries` is refused at load (`ERR_LIBRARY_PACKAGE_COLLISION`) rather than merging asymmetrically. Object coverage activates on ADOPTER-authored requirements only, so a library cannot volunteer a project for a gate it did not ask for. `iam` (preview) and `ai` (stable) ship today; rows appear in `meta gen --list` beside the generators. See [docs/features/libraries.md](docs/features/libraries.md) and `docs/superpowers/specs/2026-09-13-fr-043-feature-and-nfr-packages-design.md`.
20+
1921
## Status
2022

21-
_Last refreshed 2026-09-12._
23+
_Last refreshed 2026-09-13._
2224

2325
**1.0 gating — the quiet period is RETIRED (2026-09-06).** `docs/1.0-readiness.md` §G3 no
2426
longer asks for "one coordinated release with no metamodel-breaking change." It measured a

‎CHANGELOG.md‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,82 @@ here.**
1010

1111
## [Unreleased]
1212

13+
### Added
14+
15+
- **Libraries: reusable declared design you opt into** (FR-043, the sixth pillar).
16+
`"libraries": ["iam"]` in `.metaobjects/config.json` brings a shipped, requirement-backed
17+
model into your project. Two ship: **`iam`** (`preview`) — users, nestable typed groups,
18+
roles as permission bundles, grants global or scoped to a group, nine entities and eleven
19+
requirements — and **`ai`** (`stable`), the LLM-call trace envelope that already existed.
20+
21+
**A library is LAYERED, and the core layer is INERT.** The core declares no `source.rdb`,
22+
and a sourceless object generates nothing and migrates to nothing (#248), so
23+
`["iam"]` adds **zero tables and zero generated code** — the design is present and
24+
resolvable, and nothing else happens until you add `["iam", "iam/db"]`. A layer token
25+
implies its core; a token whose layer is unknown is dropped whole rather than reduced to
26+
it, because answering a mistyped `iam/database` with an inert core and no tables is the
27+
worst of the available outcomes.
28+
29+
**Copy is the expected mode.** `meta eject <library>` copies every layer into your first
30+
DECLARED source root with a provenance header, and `meta eject --list` reports how far
31+
your copy has drifted from the shipped tree — nodes changed, only-upstream, only-yours —
32+
matched by name with the package neutralized, because renaming the package is something
33+
you are invited to do. Ejecting and leaving the library in `libraries` is refused at load
34+
(`ERR_LIBRARY_PACKAGE_COLLISION`): both trees merge, and the merge is asymmetric —
35+
additions take effect, deletions do not. Its mirror `ERR_LIBRARY_PACKAGE_NOT_OWNED`
36+
refuses a NEW node declared into a library's package; an `overlay: true` amendment stays
37+
open. See [libraries.md](docs/features/libraries.md).
38+
39+
`meta gen --list` carries `kind: "library"` rows beside the generators — one door, one
40+
namespace — with `useWhen`, `layers`, `provides`, and under `--probe` what your selection
41+
actually added here.
42+
43+
- **`overlay: true` licenses an attribute override.** `ERR_MERGE_CONFLICT` now fires only
44+
on an UNMARKED conflicting redeclaration. The flag is the author saying "I know about the
45+
other declaration and I mean to change it", and without this an adopter could not disagree
46+
with a library's shipped requirement without ejecting the whole ledger. All four loaders
47+
(Kotlin inherits the JVM's); one new conformance fixture takes over the unmarked-conflict
48+
error branch, so the coverage moved rather than being deleted.
49+
50+
### Changed
51+
52+
- **`libraries` moved to `.metaobjects/config.json`**, out of `metaobjects.config.ts`,
53+
outright and with no dual-read — a sweep of the estate found zero uses of the key. Which
54+
designs a project adopts is a fact about the PROJECT, not about how one port generates
55+
code from it. A config still carrying the old key gets a pointed error rather than
56+
silence.
57+
58+
- **`library/ai` is SPLIT into `model` + `db` layers, and its requirements are new.**
59+
Opting into `"ai"` alone no longer proposes `CREATE TABLE llm_call` — that moved to
60+
`"ai/db"`. Breaking-ish for an `ai` adopter tracking the library: add `"ai/db"` to keep
61+
the table. The concrete-`LlmCall` wart was previously carried as accepted on the grounds
62+
that splitting would change what existing adopters get; the estate sweep found there are
63+
none, so it was closed rather than documented.
64+
65+
- **`trace-helper` keys on a declared ANCHOR, not a hard-coded entity name** — and the
66+
name it hard-coded was never actually matching the shipped base. It compared
67+
`"LlmCallBase"` against the SHORT name, so any adopter entity of that name in any package
68+
emitted a helper writing columns that entity does not declare. It now resolves the anchor
69+
its library's manifest declares and compares by node identity, in all three ports that
70+
ship it. Two self-extinguishing warnings cover the halves of the choice: a library opted
71+
into whose implied generator is not wired, and a generator wired whose library is not.
72+
73+
- **Object coverage activates on ADOPTER-authored requirements only.** A library shipping
74+
its own ledger would otherwise switch the unclaimed-entity gate on across a project that
75+
has never written a requirement. Library entries are still counted and still checked;
76+
they simply cannot volunteer you. `meta verify` prints `coverage: not measured (no
77+
project-authored requirements)` rather than a ratio, and the JSON omits the pair rather
78+
than zeroing it — `0/0 claimed` and "not measured" mean opposite things.
79+
80+
**A project that already has a ledger sees its coverage denominator grow** to include an
81+
opted-in library's entities. They are all claimed by the library's own ledger, so no new
82+
warnings appear, but the printed numbers move.
83+
84+
- **A shipped library's files carry a stable `library:<ref>.yaml` source id in every
85+
build.** It was the file's basename in a checkout and `library:…` when embedded, so one
86+
node's error envelope read differently depending on how the library was resolved — and
87+
collided with an adopter file of that name.
88+
1389
### Fixed
1490

1591
- **An M:N relationship inherited through `extends` derived its junction FK columns
@@ -96,6 +172,19 @@ here.**
96172
No vocabulary change: `metamodelVersion` stays `1.0` and the registry manifest is
97173
untouched.
98174

175+
||||||| 7dafb055e
176+
177+
- **Both shipped libraries failed `meta verify`'s requirement gate**, in metadata an
178+
adopter cannot fix: every L4 in `ai` claimed FIELDS (`ERR_REQUIREMENT_L4_NOT_OBJECT`),
179+
and both libraries wrote their concerns as SIBLINGS of the L2 segment their own comments
180+
said they were children of, leaving that L2 claiming nothing in its whole subtree. The
181+
load test proved they LOAD clean, which is a different claim, and nothing checked the
182+
other one. Both ledgers are fixed as the model intends — concerns nested under their L2,
183+
each L4 naming the OBJECT with its fields in an L5 child — and a new standalone gate
184+
holds every shipped library, and every future one, to zero loader errors, zero loader
185+
warnings, zero gate findings, zero lint findings, no unruled gaps, and every entity
186+
claimed by its own ledger.
187+
99188
### Changed
100189

101190
- **Codegen is OPT-IN: no port ships a default generator suite** (ADR-0034 Amendment 2).

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ metaobjects/
201201
├── CLAUDE.md # project instructions for Claude
202202
├── spec/ # canonical metamodel docs, ADRs, roadmap
203203
├── fixtures/ # 22 cross-language conformance corpora — the oracle
204-
│ ├── conformance/ # metamodel (loader + serializer + navigation), 314 fixtures
204+
│ ├── conformance/ # metamodel (loader + serializer + navigation), 329 fixtures
205205
│ ├── yaml-conformance/ # YAML authoring desugar
206206
│ ├── render-conformance/ # FR-004 byte-identical render oracle
207207
│ ├── verify-conformance/ # FR-004 template-drift gate

‎agent-context/skills/metaobjects-audit/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
351351

352352
**Score only where a constant actually exists.** A fact declared in the project's CODEGEN CONFIG rather than in metadata — a bespoke route mount path, a hand-chosen JSON envelope key — is genuinely spelled twice when a client re-types it, but no generated constant holds it. That is a GENERATOR GAP, reported as one, never a literal-site finding: never invent a constant that the emitters do not emit.
353353

354-
**No verify subverb sees this** — `--codegen` diffs generated files, `--db` compares schema to metadata — so this audit is the only gate. Remedy: reference the constant — but **check the artifact is emitted at all before scoring the literals, because on THREE of five ports an existing project emits none**. C# and Python have a real default suite and get it by upgrading; TypeScript's `generators: [...]` and the JVM's `<generators>` are each the COMPLETE list, so `meta init` scaffolding `namesFile()` covers only a project initialized at 1.0 and upgrading the package never edits a config written earlier. Where no artifact exists, the un-wired generator is the FIRST finding and the first remedy (`namesFile()` on TS after `meta eject names`; `SpringNamesGenerator` / `KotlinNamesGenerator` in the pom) — score the literals under it rather than as N independent findings, since one config line fixes the cause and generated code stops embedding the names too.
354+
**No verify subverb sees this** — `--codegen` diffs generated files, `--db` compares schema to metadata — so this audit is the only gate. Remedy: reference the constant — but **check the artifact is emitted at all before scoring the literals, because on ALL FIVE ports an existing project emits none until it asks**. ADR-0034 Amendment 2 made codegen opt-in everywhere: C# and Python require `--generators`, TypeScript's `generators: [...]` and the JVM's `<generators>` are each the COMPLETE list, and `meta init` scaffolds `generators: []` — so no port begins emitting this artifact merely because the package was upgraded. Where no artifact exists, the un-wired generator is the FIRST finding and the first remedy (`namesFile()` on TS after `meta eject names`; `SpringNamesGenerator` / `KotlinNamesGenerator` in the pom) — score the literals under it rather than as N independent findings, since one config line fixes the cause and generated code stops embedding the names too.
355355

356356
**Report TWO numbers for this signature, always, and never just the first: (a) is the artifact EMITTED, and (b) how many of the literal sites actually IMPORT it — the adoption ratio, as `<adopted>/<total> sites`.** Emission is the cheap half and it is the half that gets done: measured on an adopter whose audit proved the artifact emitted correctly, ran the generator to show it, and then adopted it at **0 of 53 sites** (37 table + 16 column). Wiring the generator changes the scorecard; it changes nothing a reader of the code experiences, because the second spelling is still the one every query uses. An audit that reports only (a) makes the estate look upgraded while every literal it found is still there — so a project that emits the artifact and imports it nowhere scores WORSE than one that has not started, not better, because it now carries a third spelling that claims to be the source of truth.
357357

‎agent-context/skills/metaobjects-audit/references/capability-checklist.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
7171
migration script, a body-to-column map (drift signature 11). Every port emits a per-object
7272
names artifact from the declaration, so a literal is a second source of truth even when it
7373
agrees with the naming strategy today. A typed ORM handle in its place is correct. **Check
74-
the artifact is emitted at all before scoring the literals: on TypeScript and the JVM the
75-
generator list in the config IS the complete list, so an existing project emits none and the
76-
un-wired generator is the finding FIRST** (C# and Python have a real default suite and get it
77-
by upgrading). **This entry is the physical-name INSTANCE of signature 11.** The rule that
74+
the artifact is emitted at all before scoring the literals: on every port the selection IS
75+
the complete list — the config's on TypeScript and the JVM, `--generators` on C# and Python —
76+
so an existing project emits none and the un-wired generator is the finding FIRST** (no port
77+
ships a default suite; ADR-0034 Amendment 2). **This entry is the physical-name INSTANCE of signature 11.** The rule that
7878
generates the rest — and the reason an enum member compared as a bare string is NOT one — is
7979
the Cross-cutting entry below.
8080
- **`@kind` = `view` / `materializedView`** — hunt hand-written SQL views where an authored

‎agent-context/skills/metaobjects-audit/references/csharp.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ rejected (exit 2).
5151
| `FromSqlInterpolated(` outside `.g.cs` | stored-proc call — candidate for the `callable` generator |
5252
| `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
5353
| `HasPrecision(` hand-coded | `field.decimal` with `@precision`/`@scale` drives this from the `entity` generator |
54-
| a table/column string in raw-SQL EF calls, or `nameof(Entity.Prop)` standing in for a column | second spelling of a declared physical name — reference `<Entity>Names.g.cs` (`AuthorNames.SourcePrimaryTable` / `<Field>Column`, default suite — `Names.Name` is the OBJECT's name, not the table); an EF property inside LINQ is the typed handle — correct |
54+
| a table/column string in raw-SQL EF calls, or `nameof(Entity.Prop)` standing in for a column | second spelling of a declared physical name — reference `<Entity>Names.g.cs` (`AuthorNames.SourcePrimaryTable` / `<Field>Column`, emitted when `names` is named in `--generators` — `Names.Name` is the OBJECT's name, not the table); an EF property inside LINQ is the typed handle — correct |
5555

5656
---
5757

@@ -66,7 +66,7 @@ selection uses stable names via `dotnet meta gen --generators <names>`, over a
6666
**So do not score a C# project down for "not owning its generators", and do not
6767
recommend writing one.** The customization path here is the **declarative template**:
6868
`dotnet meta gen --template-spec <json> --template-root <dir>`, whose entries append to
69-
the default suite. A finding of the form "the built-ins do not emit the shape this
69+
your `--generators` selection. A finding of the form "the built-ins do not emit the shape this
7070
project needs" resolves to a template-spec, not to generator code. Worked example with
7171
the full JSON: `docs/ports/csharp.md`.
7272

0 commit comments

Comments
 (0)