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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The metamodel is the **durable spine**; generated code is the **disposable artif

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, its `verify` checks and a `requirement-tests` generator in every port, and its authoring lint in TypeScript only. The sixth is content the other five compose, shipped as named opt-in artifacts:

1. **Codegen** *(reference helpers, not core — see "Core vs helpers" above; ejectable in every port)* — emit 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.
1. **Codegen** *(reference helpers, not core — see "Core vs helpers" above; ejectable in every port)* — emit 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 (TypeScript only).
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). **C# runtime metadata is on the roadmap** — its EF Core output is generated code, not a metadata-driven runtime; the runtime `MetaObjects` package depends only on YamlDotNet and ships no data access.
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.
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`.
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ here.**

## [Unreleased]

### Docs

- **Port docs and `llms-full.txt` now say what a fresh reader got wrong.** Each port page opens with its keep-your-ORM path (TypeScript `runtime: false`, Java and Kotlin records and data classes, sourceless C# POCOs as DTOs, Pydantic in Python); three-way merge is scoped to TypeScript everywhere it was claimed unscoped, with what the other ports do instead (committed hash manifest, refusal by name, eject); framework baselines are stated (Java 21 rather than the contradictory 17, Spring Boot 3.x with Boot 2 unsupported, .NET 8, Python 3.11, Node first and Bun second); and each port page carries its known gaps with absolute links.

### Added

- **Report export: `GET /<segment>?format=csv|json|xml` on a served report, in all five ports.**
Expand Down
2 changes: 1 addition & 1 deletion docs/features/codegen-data-shapes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ MetaObjects **major** release; deprecations are announced before removal.
|---|---|---|
| **Data extraction** | per-port walker (TS / C# / Python / Java / Kotlin) | typed data dict |
| **Template rendering** | shared Mustache via `@metaobjectsdev/render` | Mustache spec |
| **File emission** | the runner + overwrite policy | three-way merge |
| **File emission** | the runner + overwrite policy | hash manifest (refuse an edited file); three-way merge in TypeScript only |

The middle layer is **language-agnostic**: the same Mustache template that
renders `<Entity>.md` from TypeScript walks identically from C# or Python.
Expand Down
2 changes: 1 addition & 1 deletion docs/features/migrations-and-drift.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ There are **7 drift sources**, and the toolchain has a guard for each.
| **Code-vs-API-doc** | Cross-port codegen from the same metadata. | Build time |
| **DB-vs-metadata** | `meta verify --db` (TS CLI) — introspects the live DB and fails if it has drifted from metadata. Includes modeled projection **view bodies** (a changed `CREATE VIEW` is drift, reported with what differs: the first differing excerpt of the text on SQLite/D1, a fingerprint mismatch on Postgres). A view `meta migrate` drops and recreates only because the migration alters a table it reads, with its definition unchanged, is not drift and is not reported; a hand-authored *unmodeled* view is unmanaged and never flagged. A schema concern owned by the Node toolchain regardless of server language; on the JVM ports the runtime auto-create/validator path was removed (ADR-0015) and the `metaobjects:verify` Maven goal is not available. Cloudflare D1 has no client wire protocol, so it can't go through `--db`'s Kysely-driver introspection — use `meta verify --dialect d1 [--d1 <binding>] [--remote]` instead (the same wrangler-shelled-out path `meta migrate --dialect d1` uses); `--remote` is required to check the *deployed* database, not the local `wrangler dev` shadow copy. Pointing `--db file:` at wrangler's local D1 state directory (`.wrangler/state/**/d1/**`) still runs, but only verifies that local copy — `verify` warns when it detects this. | CI on every PR |
| **Migration-vs-metadata** | The Node `meta migrate` emits migrations FROM metadata diffs — they cannot drift from metadata by construction. Schema migrations for **every** port are owned by this Node toolchain (`@metaobjectsdev/cli migrate`, ADR-0015); the C# and Python migrate surfaces were removed. | Build time |
| **Generated-edited** | `@generated` headers in emitted code + three-way merge that preserves hand-edits inside non-generated regions. | Code review |
| **Generated-edited** | `@generated` headers in emitted code + a committed hash manifest that refuses an edited file by name; in TypeScript only, a three-way merge also preserves hand-edits on the machine that generated the file. | Code review |
| **Prompt-vs-payload** | FR-004 `Renderer.verify` parses `{{...}}` references in templates and checks each one exists on the payload VO. | Build time + runtime |
| **Generated-vs-runtime** | Kotlin / Java `MetadataStartupValidator` (from Spring `ApplicationReadyEvent`) re-loads metadata at startup and asserts the generated table objects match. | App startup |

Expand Down
2 changes: 1 addition & 1 deletion docs/features/own-your-codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ survive depends on what the toolchain can see, and it is worth knowing which cas
are in:

- **On the machine that generated the file**, `.metaobjects/.gen-state/` holds a full
snapshot of what was last written, and `meta gen` does a real three-way merge: your
snapshot of what was last written, and the TypeScript `meta gen` does a real three-way merge (C#, Python, Java and Kotlin do not merge; see below): your
edit and the new generated content are combined, and you only see a conflict when
they touch the same lines.
- **Anywhere else** — a fresh clone, a teammate's checkout, CI — the snapshot bodies are
Expand Down
12 changes: 6 additions & 6 deletions docs/llms/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ Reference generators you own, ejectable in every port. They emit per-language co

**What is guaranteed, and what is yours** (ADR-0034 Amendment 3). The core is the promise: the metamodel, loader, canonical format and registry; runtime metadata access (the `ObjectManager`, not the HTTP adapters that mount it); schema migrations (`meta migrate`); the drift gates (`meta verify`); prompt render and the reply parser. Every generator that writes application code into the repo is a reference helper — a starting point that compiles and passes its reference fixtures, which the adopter copies with `meta eject` and owns. A defect in the core is a MetaObjects bug; a defect in a generator is fixed in the reference, and the adopter's copy is theirs. The test is mechanical: what the tool guarantees is core; what it writes into your repo is a helper.

- TypeScript: Drizzle + Zod + Fastify integration (and React / TanStack on the client side)
- Java: Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) via `codegen-spring`
- Kotlin: KotlinPoet + Exposed + Spring via `codegen-kotlin`
- C#: EF Core + ASP.NET
- Python: Pydantic + FastAPI
- TypeScript: Drizzle + Zod + Fastify integration (and React / TanStack on the client side). **Keep your ORM:** a target with `runtime: false` makes the `entity` generator write Zod schemas and inferred types only, with no Drizzle and no `runtime-ts` import
- Java: Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) via `codegen-spring`. **Keep your ORM:** `SpringDtoGenerator` and `SpringValueObjectGenerator` write records (jakarta validation) and your JPA entity stays hand-written. Baseline Java 21, Spring Boot 3.x (Boot 2 is not supported)
- Kotlin: KotlinPoet + Exposed + Spring via `codegen-kotlin`. **Keep your ORM:** `KotlinEntityGenerator` alone writes plain data classes; JVM 21, Spring Boot 3.x
- C#: EF Core + ASP.NET (.NET 8). **Keep your EF model:** an object declared with no `source.rdb` generates a POCO with DataAnnotations and no EF mapping, a contract separate from your entities
- Python: Pydantic + FastAPI (Python 3.11+). **Keep your ORM:** the `entity` generator writes Pydantic models with no ORM of any kind

Hand-edit-preserving regeneration uses a three-way merge so engineers can extend generated entities without losing their work when metadata changes. M:N relationship codegen (FR-018) ships in all five ports: entity navigation, idiomatic ORM wiring (Drizzle m2m / EF Core `UsingEntity` / Spring repo interface / Exposed / Pydantic), and REST traversal (`GET /<source-plural>/{id}/<relation>`).
Hand-edit-preserving regeneration is **TypeScript-only**: `meta gen` three-way-merges your edits into regenerated output on the machine that generated the file. C#, Python, Java and Kotlin do not merge. Each decides from a committed hash manifest (`.metaobjects/.gen-state/.hashes.json`): a file that still matches what the generator wrote is overwritten, an edited one is refused by name (Java and Kotlin can be told to replace it with `handEdits=overwrite`), and `eject` hands you the generator to own instead. M:N relationship codegen (FR-018) ships in all five ports: entity navigation, idiomatic ORM wiring (Drizzle m2m / EF Core `UsingEntity` / Spring repo interface / Exposed / Pydantic), and REST traversal (`GET /<source-plural>/{id}/<relation>`).

### Runtime metadata

Expand Down
2 changes: 1 addition & 1 deletion docs/llms/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ MetaObjects pillars are capabilities of the same metadata spine, not separate pr
- **Requirements and testing** — vocabulary, the `verify` checks and a `requirement-tests` generator in all five ports; the authoring lint is TypeScript-only; dogfooded on maintainer-owned projects, no outside adopter yet.
- **Libraries** — since 1.0.4, in all five ports: `ai` is `stable`, `iam` is `preview` (the same labels `meta gen --list` prints).

- **Codegen** — reference generators you own (ejectable in every port) that emit per-language code from a single metadata model. Drizzle + Zod + Fastify (TypeScript), Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) (Java via `codegen-spring`), KotlinPoet + Exposed + Spring (Kotlin via `codegen-kotlin`), EF Core + ASP.NET (C#), Pydantic + FastAPI (Python). Hand-edit-preserving regeneration via three-way merge. Includes M:N relationship codegen (FR-018) in all five ports — entity navigation, idiomatic ORM wiring, and REST traversal (`GET /<source-plural>/{id}/<relation>`).
- **Codegen** — reference generators you own (ejectable in every port) that emit per-language code from a single metadata model. Drizzle + Zod + Fastify (TypeScript), Spring REST + DTO + repository interfaces (the persistence impl is consumer-supplied) (Java via `codegen-spring`), KotlinPoet + Exposed + Spring (Kotlin via `codegen-kotlin`), EF Core + ASP.NET (C#), Pydantic + FastAPI (Python). Hand-edit-preserving regeneration via three-way merge is TypeScript-only; the other ports refuse an edited generated file by name and let you eject the generator to own it. Keep your ORM: contract-only output (TypeScript `runtime: false`: Zod and types; Java and Kotlin: records and data classes; C#: sourceless POCOs; Python: Pydantic) leaves your Prisma, JPA or EF Core models untouched. Includes M:N relationship codegen (FR-018) in all five ports — entity navigation, idiomatic ORM wiring, and REST traversal (`GET /<source-plural>/{id}/<relation>`).
- **Runtime metadata** — load metadata at runtime and drive behavior dynamically: CRUD, validation, relationships, dynamic admin UIs. (Typed tool payloads are declared today; MCP exposure of tools is on the roadmap.) Kysely (TS), a DB-API 2 driver (pg8000 / psycopg) via ObjectManager (Python), modernized JDBC + Spring-tx via OMDB (Java), Exposed (Kotlin); C# runtime metadata is on the roadmap (its EF Core output is generated code, not a metadata-driven runtime). Runtime queries return native in-process types (ADR-0019); wire canonicalization happens only at the serialization boundary.
- **Drift detection** — catch divergence between generated code and metadata before it ships. `verify` is one verb with explicit subverbs (ADR-0021): `verify --codegen` (regen-and-diff against committed output), `verify --templates` (prompt `{{field}}` ↔ payload-VO drift), and `verify --db` (live-DB schema drift, Node `meta` only). Surfaces drift as build-time breakage rather than a production incident.
- **Prompt construction** — treat LLM prompts as governed metadata instead of strings scattered across services. A typed payload declared as a projection (so payload bloat and token cost are a diff, not a mystery), external provider-resolved prompt text, and a logic-less Mustache engine that renders deterministically: snapshot-testable in CI, byte-stable so an exact-prefix prompt-cache hit doesn't break on a stray whitespace, and drift-checked at build time so a renamed field can't silently degrade a prompt. Conformance-gated, so the guarantee holds in every language port. Render + payload-VO codegen + `verify` + `template.output` parser-on-receipt (FR-006) + the output-format prompt fragment & tolerant `extract` parser (FR-010/FR-011) ship in all five ports today.
Expand Down
51 changes: 48 additions & 3 deletions docs/ports/csharp.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,43 @@

A first-class full-stack target on .NET 8. Loader + conformance + EF Core codegen
+ render engine + the `meta` CLI all ship. Targets EF Core + ASP.NET Core +
Postgres + Npgsql.
Postgres + Npgsql. The baseline is **.NET 8** (`net8.0`); the tool and the packages are not
built for earlier runtimes.

## Regeneration and hand edits

`dotnet meta gen` has no three-way merge (that is TypeScript-only). It decides per file from a
committed hash manifest, `.metaobjects/.gen-state/.hashes.json`: a file that still matches what
the generator wrote is overwritten, and an edited one is refused by name. To change what a
generator emits, `dotnet meta eject <name>` copies it into `codegen/generators/` for you to own.
Commit the manifest.

## Keep your EF Core model: contracts as plain DTOs

Contracts and entities are not forced into one type. Declare an object with **no
`source.rdb`** (a *sourceless* object) and `EntityGenerator` writes a POCO with DataAnnotations
and **no EF mapping**: no `[Table]`, no `DbSet<>`, no `AppDbContext` entry, no CRUD routes, no
filter allowlist. A sourced entity gets `[Table]`/`[Key]`/`[Column]` and the full EF Core
surface. So the same model can hold sourceless request/response contracts that you hand to your
own controllers and sourced entities where you want MetaObjects to own the mapping; your
existing hand-written EF entities are not touched.

A declaration with no `source.rdb` (this one is a plain DTO POCO):

```json
{ "object.entity": {
"name": "AuthorSummary",
"children": [
{ "field.long": { "name": "id" } },
{ "field.string": { "name": "name", "@required": true, "@maxLength": 200 } },
{ "identity.primary": { "@fields": "id" } }
]
}}
```

Wire only the generators you want in `codegen/Generators.cs`; leave `db-context` out if you keep your own `DbContext`, since the generated `AppDbContext` emits Postgres column types (`uuid`, `jsonb`, `timestamptz`) today. Note that the generated CRUD
routes use the *entity* POCO, as described under [Use](#use); the DTO shape above is for
objects you keep sourceless.

## Install

Expand Down Expand Up @@ -720,8 +756,17 @@ ship too — the generated `<Entity>FilterAllowlist` (`FilterAllowlistGenerator`
feeds `FilterParser.Parse` + `EfCoreFilterDispatch.ApplyFilter`, both wired
directly into the generated list handler. A read-only projection
(`source.rdb @kind: view`) and a view-backed report get the same filter and sort
on their list route, against an allowlist of their own fields. Remaining gaps are in
[`server/csharp/MetaObjects.Codegen/Generators/KNOWN_GAPS.md`](../../server/csharp/MetaObjects.Codegen/Generators/KNOWN_GAPS.md).
on their list route, against an allowlist of their own fields.

**Known gaps** (full list with workarounds in
[KNOWN_GAPS.md](https://github.com/metaobjectsdev/metaobjects/blob/main/server/csharp/MetaObjects.Codegen/Generators/KNOWN_GAPS.md)):
no `filter[or]` / `filter[and]` boolean grouping and a single sort key only (both outside the
cross-port contract today); the `<Entity>View` / `<Entity>Views` names are reserved for a
write-through entity's generated read model; an `EmitMappedClass` override does not reach that
read model; M:N traversal on a write-through entity is not routed through the replica view; the
ordered-comparison filter fallback in `EfCoreFilterDispatch` needs a hand-written typed
dispatcher for some column types; there is no metadata-driven runtime tier (EF Core output is
generated code), and no `migrate` surface (schema is TypeScript-owned).

## Capability snapshot

Expand Down
Loading
Loading