Skip to content

Commit 4735bbd

Browse files
dmealingclaude
andcommitted
docs: polish batch — ADR status, badges, layout tree, dead files (W6)
- ADR-0007 (source-v2 paradigm) and ADR-0018 (per-kind physical-name attrs) were still marked Proposed; both shipped across all five ports months ago. - README: npm / Maven Central / PyPI version badges (all three verified to resolve to the live 0.20.1 / 7.11.3 / 0.19.5) + an Apache-2.0 badge. No CI badge — the heavy gates deliberately do not run on PRs, so a green badge there would overstate what a PR proves. - README repo layout was missing five root directories a reader will hit (examples/, library/, templates/, agent-context/, scripts/) and described the fixtures tree as five corpora. Each now carries a one-line "what is this for". - docs/bugs/2026-06-28-projection-aggregate-view-ddl.md: RESOLVED header naming the fix commit; kept as a record of the contract-vs-DDL divergence class. - @metaobjectsdev/forge had no README and exports nothing — documented as private/incubating (the reserved slot for the MCP tier) so nobody mistakes it for a shipped package or depends on it. - The Angular packages' separate 0.6.x line is now stated where the claim is made (README capability matrix + the client port doc), so the version mismatch reads as deliberate rather than a packaging error. - server/java/Jenkinsfile removed — vestigial, gated on a `master` branch that does not exist here; CI is GitHub Actions plus the self-hosted local-ci. - tools/migrate-source-v2.ts moved to scripts/ with a header saying it is a historical one-off (the source.dbTable vocabulary it migrates from is retired, so it is a no-op on current metadata). tools/ is gone; scripts/ is the one home. NOT done, premise was wrong: the plan called for dropping the JUnit-4 property from server/java/pom.xml on the grounds that the tests use JUnit 5. They do not — 240 Java test files import org.junit.Test/Assert vs 13 on jupiter. Removing it would break the reactor. Left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent c10d1a1 commit 4735bbd

17 files changed

Lines changed: 190 additions & 51 deletions

File tree

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# MetaObjects
22

3+
[![npm](https://img.shields.io/npm/v/%40metaobjectsdev%2Fcli?label=npm%20%40metaobjectsdev%2Fcli)](https://www.npmjs.com/package/@metaobjectsdev/cli)
4+
[![Maven Central](https://img.shields.io/maven-central/v/com.metaobjects/metaobjects-metadata?label=maven%20central)](https://central.sonatype.com/artifact/com.metaobjects/metaobjects-metadata)
5+
[![PyPI](https://img.shields.io/pypi/v/metaobjects?label=pypi)](https://pypi.org/project/metaobjects/)
6+
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
7+
38
A **cross-language metadata standard** for declaring typed entity models that
49
drive code generation, runtime metadata access, drift detection, and prompt
510
construction — across TypeScript, Java, Kotlin, C#, and Python.
@@ -111,7 +116,7 @@ first-week wedge plan — and `meta init` picks up from there.
111116
| Template-drift verify | Yes | Yes (`Verify.check`) | Yes (via Java) | Yes (`dotnet meta verify`) | Yes (`metaobjects.render.verify`) |
112117
| YAML authoring (sigil-free → JSON) | Yes | Yes | Yes (via Java) | Yes | Yes |
113118
| Runtime metadata (ObjectManager-style) | Yes (`runtime-ts`) | Yes (OMDB) | Yes (via Java OMDB + Exposed) | Roadmap | Yes (ObjectManager) |
114-
| React / Angular UI client (browser) | Yes — React (`@metaobjectsdev/react` + `@metaobjectsdev/tanstack`) and Angular 18 (`@metaobjectsdev/angular`, published at `0.6.0`); both codegen + runtime | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST |
119+
| React / Angular UI client (browser) | Yes — React (`@metaobjectsdev/react` + `@metaobjectsdev/tanstack`) and Angular 18 (`@metaobjectsdev/angular`, on its own `0.6.x` line — deliberately versioned separately from the lockstep `@metaobjectsdev/*` packages, since the Angular tier moves on its own cadence); both codegen + runtime | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST | Consumes TS client via REST |
115120
| Cross-port REST routes for the client | Generated (`routesFile()` → Fastify) | Generated (`SpringControllerGenerator` → Spring `@RestController`, incl. filter/sort) | Generated (`KotlinSpringControllerGenerator` → Spring `@RestController`, incl. filter/sort) | Generated (`RoutesGenerator` → ASP.NET Minimal API) | Generated (`router_generator` → FastAPI `APIRouter`, incl. filter/sort) |
116121

117122
A "Yes" means the feature is covered by the shared conformance corpora at
@@ -160,19 +165,33 @@ metaobjects/
160165
├── README.md # you are here
161166
├── CLAUDE.md # project instructions for Claude
162167
├── spec/ # canonical metamodel docs, ADRs, roadmap
163-
├── fixtures/ # cross-language conformance corpora
164-
│ ├── conformance/ # metamodel (loader + serializer + navigation)
168+
├── fixtures/ # 19 cross-language conformance corpora — the oracle
169+
│ ├── conformance/ # metamodel (loader + serializer + navigation), 249 fixtures
165170
│ ├── yaml-conformance/ # YAML authoring desugar
166171
│ ├── render-conformance/ # FR-004 byte-identical render oracle
167172
│ ├── verify-conformance/ # FR-004 template-drift gate
168-
│ └── persistence-conformance/ # on-demand integration tests vs real Postgres
173+
│ ├── extract-conformance/ # FR-010 tolerant output parsing
174+
│ ├── api-contract-conformance/ # the REST wire contract, reference + generated lanes
175+
│ ├── persistence-conformance/ # on-demand integration tests vs real Postgres
176+
│ └── … # registry, validation, codegen, provider-composition, …
177+
│ # full matrix: docs/CONFORMANCE.md
169178
├── docs/
170179
│ ├── README.md # docs index
171180
│ ├── features/ # feature reference (one file per metamodel feature)
172181
│ ├── ports/ # per-port quickstarts
173182
│ ├── recipes/ # deployment recipes (Cloudflare D1, …)
183+
│ ├── CONFORMANCE.md # corpus × port matrix + fixture→feature index
174184
│ ├── superpowers/specs/ # design specs
175185
│ └── RELEASING.md # npm publish procedure
186+
├── examples/
187+
│ └── advanced-modeling/ # a worked, runnable non-toy model
188+
├── library/ # shipped standard-library metadata, opt-in per project
189+
│ └── ai/ # via `libraries: ["ai"]` (e.g. the LLM-call trace base)
190+
├── templates/ # canonical api/docs Mustache templates (the SSOT the
191+
│ # ports embed; byte-gated so copies cannot drift)
192+
├── agent-context/ # the shared source the per-port AI-assistant context
193+
│ # surfaces are generated from (AGENTS.md, skills, llms.txt)
194+
├── scripts/ # CI parity (`ci-local.sh`), codegen of embedded assets, one-offs
176195
177196
├── server/ # runs on a server
178197
│ ├── typescript/ # the reference port

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
138138
`@dbColumn` → use `source.rdb` + `@kind` + `@table` / `@column` + `@role`, ADR-0007/0018);
139139
taxonomy impurity (entity over read-only primary source; read model that should be
140140
`object.projection`; `value` carrying identity/source, ADR-0028); copy-pasted base-field
141-
blocks instead of abstract + `extends`; `@`-prefixed YAML keys (ADR-0006); relative refs
141+
blocks — or the same-named field re-declared across objects (drift signature 10) — instead of
142+
abstract + `extends`; `@`-prefixed YAML keys (ADR-0006); relative refs
142143
in committed canonical JSON (ADR-0032); DB-type-as-logical-subtype (ADR-0013); per-port
143144
migration engine where schema is Node-`meta`-owned (ADR-0015).
144145

@@ -296,6 +297,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
296297
- **Exposure contract, expressible → CODEGEN CANDIDATE (high):** a subset / renamed / versioned / multi-base read model → convert to an `object.projection` with a read-only `source.rdb` `@kind: view` child, let `meta migrate` emit the `CREATE VIEW`, and consume the generated read-only query — the hand-written view is a second source of truth for a derivable shape. Parity-gate: the generated view returns row-identical results before the hand-written SQL is deleted.
297298
- **Not expressible → carry it in `@sql` or `@unmanaged`, never a hand-edited migration (#208, ADR-0043).** When a NAMED irreducible construct blocks origin authoring — recursive CTE, window function / `OVER`, `UNION` / `INTERSECT` / `EXCEPT`, lateral join — the body still belongs in the metadata: carry the hand-written SQL in the `source.rdb` **`@sql`** escape — a read-only-`@kind` body the tool REGISTERS, fingerprints, and drift-checks (adopt a pre-existing view with `meta migrate --allow adopt-view`); `@sql` forbids `origin.*` children (two sources of truth). A DB object whose DDL is owned **entirely elsewhere** (Flyway / a hand-migration) → mark its source **`@unmanaged: true`** (legal on any `@kind` incl. `table`); `meta migrate` never creates/drops/drift-checks it and `verify --db` reports it as external. `@sql` and `@unmanaged` are mutually exclusive. **Only a view left *undeclared*** — neither modeled, nor `@sql`, nor `@unmanaged` — is truly *unmanaged*, invisible to `meta verify --db`, so this audit is the only gate that sees it. "It's an aggregation" is NOT an irreducibility justification (plain count/sum/avg/min/max rollups are `origin.aggregate`); nor is a `DISTINCT ON` pick-one-row (`origin.first`) or a non-aggregate expression column (`origin.computed`).
298299
9. **A closed variant-set hand-modeled per instance** — N sibling modules / classes / config blocks, one per channel / provider / target, sharing a payload + config shape and diverging only by transport. Grep for sibling-file families and switch-on-a-string dispatch; verify the set is closed and recurring (never a one-off). → axis I "New-vocabulary OPPORTUNITY" (VOCAB CANDIDATE, advisory).
300+
10. **N declarations of one FIELD across objects (same-name-field census)** — the field-level sibling of signature 6. Census field names across `object.*` nodes (`grep -rn 'name: <field>'` the metadata dir); a name recurring in ≥2 objects where a canonical owner exists — one whose name the field embeds (`<owner><Field>`: `wizardId``Wizard.id`, `orderTotal``Order.total`) or whose type+constraints it matches — is provenance loss → `extends: Owner.field` (dotted child targets, ADR-0029). **VERIFY by diffing the copies' attrs: a `@maxLength` / `@required` / validator divergence across them is drift already shipping — cite it.** Evidence multiplier: `extends` already used elsewhere in the repo raises confidence. Do NOT flag: generic names on unrelated concepts (`id` / `name` / `status` with no owner-embedding name and no matching constraints); required per-node attrs a loader forces (e.g. `payloadRef` / `format` on sibling `template.prompt` nodes — a product constraint, not a copy).
299301

300302
---
301303

@@ -343,6 +345,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
343345
`voRequest` / `voResponse` jsonb columns must be authored `field.object` — the loader
344346
must not mutate the tree; vendor SDK client + pricing are BYO (ADR-0024).
345347
- No `meta verify --templates` gate; no declared `@maxChars` / `@maxTokens` budget.
348+
- **Template-text duplication.** Hash every external template file (`md5sum` the template dir) and diff within groups sharing a `payloadRef`. A byte-identical cluster, or N≥3 templates sharing an identical skeleton with only slot lines differing, is ONE prompt maintained N times — a wording fix must be applied N times and WILL be missed. Recommend a shared `{{> group/partial }}` include (the render engine inlines partials recursively) or one template + payload-carried variant fields. Do NOT flag: the per-node `payloadRef` / `@format` repetition across `template.prompt` nodes (loader-forced, not a copy); templates meant to diverge independently — require N≥3 and structural identity before flagging.
346349

347350
---
348351

docs/bugs/2026-06-28-projection-aggregate-view-ddl.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ title: "Projection origin.aggregate columns dropped from generated view DDL (inv
55
labels: bug
66
---
77

8+
> **RESOLVED.** Both gaps are fixed and gated. The inverse-FK `@via` join
9+
> resolution and the `origin.aggregate` `@filter` rendering landed in
10+
> `7619002b` ("fix(codegen-ts): render origin.aggregate @filter in projection
11+
> view DDL", #90); `@filter` is now registered in all five ports with an
12+
> `origin-aggregate-filtered` registry-conformance fixture. Kept as a written
13+
> record of the failure mode — the contract-vs-DDL divergence class it describes
14+
> is worth recognising again.
15+
816
**Affected port(s):** TypeScript (codegen-ts view-SQL builder)
917
**Package + version:** `@metaobjectsdev/codegen-ts` 0.12.5 (`projection/extract-view-spec.ts`), surfaced via `@metaobjectsdev/migrate-ts` 0.12.0 + `@metaobjectsdev/cli` 0.12.5, dialect postgres.
1018

docs/ports/typescript-client.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,12 @@ for the rare case where you need a shape the generator doesn't cover).
500500

501501
## Angular 18
502502

503+
> **Versioning.** The two `@metaobjectsdev/angular*` packages are on their own
504+
> `0.6.x` line, deliberately decoupled from the lockstep version the rest of the
505+
> `@metaobjectsdev/*` packages share. The Angular tier moves on its own cadence,
506+
> so a version mismatch against the React/TanStack packages is expected, not a
507+
> packaging error.
508+
503509
Angular 18 ships as a **second universal browser-side client tier** alongside
504510
the React + TanStack pair. Same architecture — one runtime package
505511
(`@metaobjectsdev/angular`) plus one codegen package

fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-audit/SKILL.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
138138
`@dbColumn` → use `source.rdb` + `@kind` + `@table` / `@column` + `@role`, ADR-0007/0018);
139139
taxonomy impurity (entity over read-only primary source; read model that should be
140140
`object.projection`; `value` carrying identity/source, ADR-0028); copy-pasted base-field
141-
blocks instead of abstract + `extends`; `@`-prefixed YAML keys (ADR-0006); relative refs
141+
blocks — or the same-named field re-declared across objects (drift signature 10) — instead of
142+
abstract + `extends`; `@`-prefixed YAML keys (ADR-0006); relative refs
142143
in committed canonical JSON (ADR-0032); DB-type-as-logical-subtype (ADR-0013); per-port
143144
migration engine where schema is Node-`meta`-owned (ADR-0015).
144145

@@ -296,6 +297,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
296297
- **Exposure contract, expressible → CODEGEN CANDIDATE (high):** a subset / renamed / versioned / multi-base read model → convert to an `object.projection` with a read-only `source.rdb` `@kind: view` child, let `meta migrate` emit the `CREATE VIEW`, and consume the generated read-only query — the hand-written view is a second source of truth for a derivable shape. Parity-gate: the generated view returns row-identical results before the hand-written SQL is deleted.
297298
- **Not expressible → carry it in `@sql` or `@unmanaged`, never a hand-edited migration (#208, ADR-0043).** When a NAMED irreducible construct blocks origin authoring — recursive CTE, window function / `OVER`, `UNION` / `INTERSECT` / `EXCEPT`, lateral join — the body still belongs in the metadata: carry the hand-written SQL in the `source.rdb` **`@sql`** escape — a read-only-`@kind` body the tool REGISTERS, fingerprints, and drift-checks (adopt a pre-existing view with `meta migrate --allow adopt-view`); `@sql` forbids `origin.*` children (two sources of truth). A DB object whose DDL is owned **entirely elsewhere** (Flyway / a hand-migration) → mark its source **`@unmanaged: true`** (legal on any `@kind` incl. `table`); `meta migrate` never creates/drops/drift-checks it and `verify --db` reports it as external. `@sql` and `@unmanaged` are mutually exclusive. **Only a view left *undeclared*** — neither modeled, nor `@sql`, nor `@unmanaged` — is truly *unmanaged*, invisible to `meta verify --db`, so this audit is the only gate that sees it. "It's an aggregation" is NOT an irreducibility justification (plain count/sum/avg/min/max rollups are `origin.aggregate`); nor is a `DISTINCT ON` pick-one-row (`origin.first`) or a non-aggregate expression column (`origin.computed`).
298299
9. **A closed variant-set hand-modeled per instance** — N sibling modules / classes / config blocks, one per channel / provider / target, sharing a payload + config shape and diverging only by transport. Grep for sibling-file families and switch-on-a-string dispatch; verify the set is closed and recurring (never a one-off). → axis I "New-vocabulary OPPORTUNITY" (VOCAB CANDIDATE, advisory).
300+
10. **N declarations of one FIELD across objects (same-name-field census)** — the field-level sibling of signature 6. Census field names across `object.*` nodes (`grep -rn 'name: <field>'` the metadata dir); a name recurring in ≥2 objects where a canonical owner exists — one whose name the field embeds (`<owner><Field>`: `wizardId``Wizard.id`, `orderTotal``Order.total`) or whose type+constraints it matches — is provenance loss → `extends: Owner.field` (dotted child targets, ADR-0029). **VERIFY by diffing the copies' attrs: a `@maxLength` / `@required` / validator divergence across them is drift already shipping — cite it.** Evidence multiplier: `extends` already used elsewhere in the repo raises confidence. Do NOT flag: generic names on unrelated concepts (`id` / `name` / `status` with no owner-embedding name and no matching constraints); required per-node attrs a loader forces (e.g. `payloadRef` / `format` on sibling `template.prompt` nodes — a product constraint, not a copy).
299301

300302
---
301303

@@ -343,6 +345,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
343345
`voRequest` / `voResponse` jsonb columns must be authored `field.object` — the loader
344346
must not mutate the tree; vendor SDK client + pricing are BYO (ADR-0024).
345347
- No `meta verify --templates` gate; no declared `@maxChars` / `@maxTokens` budget.
348+
- **Template-text duplication.** Hash every external template file (`md5sum` the template dir) and diff within groups sharing a `payloadRef`. A byte-identical cluster, or N≥3 templates sharing an identical skeleton with only slot lines differing, is ONE prompt maintained N times — a wording fix must be applied N times and WILL be missed. Recommend a shared `{{> group/partial }}` include (the render engine inlines partials recursively) or one template + payload-carried variant fields. Do NOT flag: the per-node `payloadRef` / `@format` repetition across `template.prompt` nodes (loader-forced, not a copy); templates meant to diverge independently — require N≥3 and structural identity before flagging.
346349

347350
---
348351

fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,31 @@ name), `@default`, `@filterable`, `@sortable`. On temporal fields
216216
(`field.date`/`field.time`/`field.timestamp`) `@autoSet` stamps the value
217217
automatically — see the `@autoSet` callout under Timestamps below.
218218

219+
#### What `@required` actually means
220+
221+
`@required: true` is **NOT NULL** (presence). Two consequences that are easy to
222+
get wrong:
223+
224+
1. **On a non-array string it also rejects `""` — but only at the wire tier.**
225+
Generated *input* validation (the create/patch models behind POST/PATCH)
226+
rejects the empty string by default; whitespace-only IS accepted. Generated
227+
in-process read models never enforce this at construction, so reading an
228+
existing row containing `""` does not throw.
229+
2. **To say "must be provided, but may be empty", author an explicit
230+
`validator.length` with `@min: 0`.** An explicitly authored `@min` is always
231+
authoritative over that implicit non-empty floor; the floor applies only when
232+
no `@min` is authored at all. Dropping `@required` is NOT the way to express
233+
this — that makes the field optional and loses presence typing.
234+
235+
```jsonc
236+
// must be provided; empty string allowed
237+
{ "field.string": { "name": "note", "@required": true, "children": [
238+
{ "validator.length": { "name": "noteLen", "@min": 0 } }
239+
]}}
240+
```
241+
242+
Arrays are unaffected: `@required` on an array field is presence only.
243+
219244
### Choosing the right shape — the general decision procedure (ADR-0037)
220245

221246
This procedure decides the shape of **any** concept entering the metamodel — a

0 commit comments

Comments
 (0)