@metaobjectsdev/sdk 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-context/skills/metaobjects-audit/SKILL.md +25 -12
- package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +54 -20
- package/agent-context/skills/metaobjects-audit/references/csharp.md +7 -4
- package/agent-context/skills/metaobjects-audit/references/java.md +12 -10
- package/agent-context/skills/metaobjects-audit/references/kotlin.md +11 -7
- package/agent-context/skills/metaobjects-audit/references/python.md +7 -4
- package/agent-context/skills/metaobjects-audit/references/typescript.md +1 -1
- package/agent-context/skills/metaobjects-authoring/SKILL.md +171 -39
- package/agent-context/skills/metaobjects-codegen/SKILL.md +45 -16
- package/agent-context/skills/metaobjects-codegen/references/csharp.md +21 -1
- package/agent-context/skills/metaobjects-codegen/references/java.md +43 -5
- package/agent-context/skills/metaobjects-codegen/references/kotlin.md +34 -4
- package/agent-context/skills/metaobjects-codegen/references/python.md +32 -8
- package/agent-context/skills/metaobjects-codegen/references/typescript.md +19 -3
- package/agent-context/skills/metaobjects-fit-assessment/SKILL.md +55 -18
- package/agent-context/skills/metaobjects-prompts/SKILL.md +65 -4
- package/agent-context/skills/metaobjects-prompts/references/csharp.md +21 -0
- package/agent-context/skills/metaobjects-prompts/references/java.md +32 -3
- package/agent-context/skills/metaobjects-prompts/references/kotlin.md +33 -3
- package/agent-context/skills/metaobjects-prompts/references/python.md +29 -3
- package/agent-context/skills/metaobjects-prompts/references/typescript.md +34 -1
- package/agent-context/skills/metaobjects-runtime-ui/SKILL.md +1 -1
- package/agent-context/skills/metaobjects-runtime-ui/references/csharp.md +90 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/python.md +94 -0
- package/agent-context/skills/metaobjects-verify/SKILL.md +50 -15
- package/agent-context/skills/metaobjects-verify/references/migration.md +67 -14
- package/dist/agent-docs/body.d.ts +1 -1
- package/dist/agent-docs/body.d.ts.map +1 -1
- package/dist/agent-docs/body.js +3 -1
- package/dist/agent-docs/body.js.map +1 -1
- package/package.json +2 -2
- package/src/agent-docs/body.ts +3 -1
|
@@ -46,11 +46,13 @@ Every emitted file carries a `@generated` header. This is load-bearing:
|
|
|
46
46
|
Practical rule: **pattern-derivable-from-metadata = regenerate; business logic =
|
|
47
47
|
hand-write in a non-generated file.** FK columns, CRUD, validator chains,
|
|
48
48
|
type-safe finders, `relations()` blocks — all derived, never hand-coded. What you
|
|
49
|
-
hand-write is what metadata genuinely can't express: regex from outside metadata
|
|
50
|
-
domain logic
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
hand-write is what metadata genuinely can't express: regex from outside metadata and
|
|
50
|
+
domain logic. Most views are NOT irreducible — model them as an `object.projection`
|
|
51
|
+
and the view DDL is generated (see the projection bullet below); a hand-written view
|
|
52
|
+
for a shape origins can express is drift the drift gate can't even see. A genuinely
|
|
53
|
+
*irreducible* view body (recursive CTE, window function, set op) isn't hand-written
|
|
54
|
+
loose either — it goes in the `source.rdb` **`@sql`** escape (#208, ADR-0043) so the
|
|
55
|
+
tool still registers, fingerprints, and drift-checks it (see the projection bullet).
|
|
54
56
|
|
|
55
57
|
## Selecting generators by stable name
|
|
56
58
|
|
|
@@ -81,23 +83,50 @@ the data access too.
|
|
|
81
83
|
standard verbs with the runtime helpers and hand-write only the custom ones (see
|
|
82
84
|
the runtime skill's `mountCrudRoutes` / `mount<Verb>Route` / `expose`). You are
|
|
83
85
|
never forced into all-generated or all-hand-written.
|
|
86
|
+
- **Entity's OWN columns + a joined extra → an entity read-view, NOT a projection.**
|
|
87
|
+
The most common legacy view is `SELECT o.*, c.name AS customer_name FROM orders o
|
|
88
|
+
JOIN customers c …` — the entity *with a read route*, not an independent exposure.
|
|
89
|
+
Reach for an **entity read-view** first: keep the entity's writable `table` source
|
|
90
|
+
and add a **non-primary** read-only source (`source.rdb` `@role: replica`
|
|
91
|
+
`@kind: view`), declaring only the *extra* as a derived (`origin.*`) field — the
|
|
92
|
+
entity's own field set already covers `o.*`, so you re-state nothing but the extra.
|
|
93
|
+
Codegen then routes **reads** to the view and **writes** to the table (derived
|
|
94
|
+
fields don't exist there and are excluded from the write codecs); a create/update
|
|
95
|
+
re-reads the row through the view by primary key, so the returned value carries the
|
|
96
|
+
derived columns (read-your-writes). Shipped all five ports (#213 write half + #214
|
|
97
|
+
read half). Reach for a **projection** (below) instead only when it is an
|
|
98
|
+
independent exposure contract — a subset, renamed base columns, a versioned/external
|
|
99
|
+
shape, or a row-filtered view. See `docs/features/source-kinds.md`.
|
|
84
100
|
- **Derived/aggregate data → declare a projection, then USE its generated query.**
|
|
85
101
|
Don't hand-write a join or an `AVG()`/`COUNT()`. Declare an `object.projection`
|
|
86
|
-
with `origin
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
`
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
with `origin.*` children — `origin.passthrough` (a forwarded column),
|
|
103
|
+
`origin.aggregate` (`@agg` `count`/`sum`/`avg`/`min`/`max`, plus the #195
|
|
104
|
+
`any`/`all` predicate quantifiers over a `@filter` and `collect` array-rollup with
|
|
105
|
+
optional `@distinct`/`@orderBy`; any aggregate may be row-scoped with `@filter`),
|
|
106
|
+
`origin.collection` (a nested array), `origin.computed` (a row-level `@expr`), and
|
|
107
|
+
`origin.first` (one related row's column along `@via`/`@of`/`@orderBy`) — **and a
|
|
108
|
+
read-only `source.rdb` `@kind: view` child** (codegen detects a projection by that
|
|
109
|
+
read-only source, not by the subtype alone — omit it and nothing is generated).
|
|
110
|
+
`meta gen` emits a read-only query for it (and `meta migrate` its DB view), and you
|
|
111
|
+
**call that generated query from your route**. Declaring the projection is only half
|
|
112
|
+
the win — *consuming* its generated query is the other half.
|
|
113
|
+
- **Row-filtered views are a projection `@filter`, not hand-written SQL.** An
|
|
114
|
+
object-level `@filter` on `object.projection` (the same `attr.filter` shape as a
|
|
115
|
+
preset filter) scopes the whole view's rows — it lowers to the view's outer
|
|
116
|
+
`WHERE` (#207). This is the metadata-managed way to author a soft-delete / status
|
|
117
|
+
/ type view without hand-writing SQL.
|
|
93
118
|
- **Never hand-author the view SQL for a shape origins can express.** The
|
|
94
119
|
`CREATE VIEW` body is emitted by the Node `meta migrate` from the projection's
|
|
95
120
|
`origin.*` children — hand-writing it is a second source of truth that drifts
|
|
96
121
|
silently, because an unmodeled DB view is *unmanaged*: `meta verify --db` never
|
|
97
|
-
flags it.
|
|
98
|
-
origins can't express
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
flags it. For a genuinely irreducible body (recursive CTE, window function, set
|
|
123
|
+
op) that origins can't express, carry it in the `source.rdb` **`@sql`** escape
|
|
124
|
+
(#208, ADR-0043) — a hand-written body the tool registers, fingerprints, and
|
|
125
|
+
drift-checks (adopt a pre-existing view with `meta migrate --allow adopt-view`) —
|
|
126
|
+
rather than a hand-edited migration file where it goes accidentally unmanaged.
|
|
127
|
+
For a DB object owned entirely elsewhere (Flyway), mark its source
|
|
128
|
+
**`@unmanaged: true`** (view or table); migrate/verify then never touch it.
|
|
129
|
+
`@sql` and `@unmanaged` are mutually exclusive.
|
|
101
130
|
|
|
102
131
|
`meta gen --list` prints every generator by stable name; the `generators` array in
|
|
103
132
|
`metaobjects.config.ts` is where you opt each one in or out.
|
|
@@ -45,13 +45,33 @@ or run the default set. Output lands under `--namespace` in `--output-dir`.
|
|
|
45
45
|
| `routes` | `<Entity>Routes.cs` — ASP.NET **Minimal API** CRUD per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). A TPH base emits polymorphic `GET /<base>(+/{id})` + a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` (create injects the discriminator, cross-subtype get/update/delete → 404). |
|
|
46
46
|
| `filter-allowlist` | per-entity `<Entity>FilterAllowlist` (FR-009 — the server-side field+operator allowlist the routes validate against). |
|
|
47
47
|
| `callable` | `<Entity>.callable.g.cs` — an FR-015 calling method for a `source.rdb @kind="storedProc"|"tableFunction"`, via EF `FromSqlInterpolated` (args from the `@parameterRef` value object in declaration order). |
|
|
48
|
-
| `
|
|
48
|
+
| `payload` | `<Payload>.payload.cs` — the strict typed payload `record` (+ any nested element records) per `template.output` `@payloadRef` (an `object.value`) that the parser/extractor bind to. |
|
|
49
|
+
| `output-parser` / `extractor` / `output-prompt` / `render-helper` | the `template.output` prompt-pillar artifacts — the strict parser, the tolerant `extract`, the **output-format prompt fragment** (`output-prompt`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), and the typed render helper. See the **prompts** reference. |
|
|
49
50
|
| `template` | the generic Mustache `templateGenerator()` primitive. |
|
|
50
51
|
|
|
51
52
|
Metadata lives under `metaobjects/` (or wherever you point `--metadata-dir`) in the
|
|
52
53
|
same canonical JSON every port reads — fused-key form, `source.rdb` + `@table`,
|
|
53
54
|
`@column` for a renamed physical column.
|
|
54
55
|
|
|
56
|
+
**Entity read-view (write-through).** An `object.entity` that keeps its writable `table`
|
|
57
|
+
primary source and adds a `@role: replica` `@kind: view` source is a write-through
|
|
58
|
+
read-view (#214): the generated EF entity carries the derived `origin.*` fields read-only
|
|
59
|
+
and `db-context` registers that read model against the replica view (`.ToView(...)`);
|
|
60
|
+
reads route to the view, writes to the table (derived fields excluded), and a
|
|
61
|
+
create/update re-reads the row via the view by primary key (read-your-writes). The replica
|
|
62
|
+
view's DDL is emitted by the Node `meta migrate` from the same origin assembly as a
|
|
63
|
+
projection view.
|
|
64
|
+
|
|
65
|
+
## Docs — `dotnet meta docs`
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
dotnet meta docs metaobjects --out Docs # → Docs/api/csharp (AGENT-API.md + per-entity pages)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`dotnet meta docs` emits this project's C# SDK api surface (`api/csharp`), including
|
|
72
|
+
`AGENT-API.md` — the exact imports, signatures, and payload field shapes for the
|
|
73
|
+
generated code. **Before calling any generated code, read `api/csharp/AGENT-API.md`.**
|
|
74
|
+
|
|
55
75
|
## Persistence + routes are the deployed artifact
|
|
56
76
|
|
|
57
77
|
C# generates a *complete* server stack: the entity classes + `AppDbContext` ARE the
|
|
@@ -74,6 +74,19 @@ A `metaobjects:verify` Maven goal exists for **codegen-drift** (re-generate and
|
|
|
74
74
|
committed output). Schema migration and live-DB drift are NOT Java goals — they run
|
|
75
75
|
through the Node `meta` tool (see the migration reference).
|
|
76
76
|
|
|
77
|
+
## Docs — `mvn metaobjects:docs`
|
|
78
|
+
|
|
79
|
+
A separate `docs` goal (`DocsMojo` — NOT a `<generator>`) emits this project's SDK api
|
|
80
|
+
surface (default `target/docs/api/java`), including `AGENT-API.md` — the exact imports,
|
|
81
|
+
signatures, and payload field shapes for the generated code.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
mvn metaobjects:docs # → target/docs/api/java (AGENT-API.md + per-entity pages)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Before calling any generated code, read `api/java/AGENT-API.md`** — it carries the
|
|
88
|
+
concrete imports and signatures so you don't have to guess them.
|
|
89
|
+
|
|
77
90
|
## `codegen-spring` generators
|
|
78
91
|
|
|
79
92
|
All live in `metaobjects-codegen-spring` under
|
|
@@ -82,19 +95,44 @@ first group together:
|
|
|
82
95
|
|
|
83
96
|
| Generator | Output |
|
|
84
97
|
|---|---|
|
|
85
|
-
| `SpringControllerGenerator` | `<Entity>Controller.java` per writable entity (`source.rdb` `@kind="table"`) — Spring Web MVC, five CRUD endpoints on the cross-port REST contract (`?sort`, `?limit`/`?offset`, `?withCount=1` envelope, 404/400 envelopes). A TPH `@discriminator` base emits ONE controller: polymorphic `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable. |
|
|
98
|
+
| `SpringControllerGenerator` | `<Entity>Controller.java` per writable entity (`source.rdb` `@kind="table"`) — Spring Web MVC, five CRUD endpoints on the cross-port REST contract (`?filter[field][op]=`, `?sort`, `?limit`/`?offset`, `?withCount=1` envelope, 404/400 envelopes). A TPH `@discriminator` base emits ONE controller: polymorphic `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable. |
|
|
86
99
|
| `SpringDtoGenerator` | `<Entity>Dto.java` as a Java 21 `record`; wrapped primitives (`Long`/`Integer`/`Boolean`) so missing JSON props deserialise to `null`; currency = `Long` (integer minor units). A TPH `@discriminator` base's DTO is the **union** of every subtype's columns (subtype-only fields folded nullable, validation dropped), so one wire shape backs the polymorphic + per-subtype endpoints. |
|
|
87
100
|
| `SpringRepositoryGenerator` | `<Entity>Repository.java` — a hand-stubbed `interface` the consumer implements with their persistence layer (Spring Data JPA / jOOQ / JDBC). For a TPH base the interface is polymorphic + per-subtype-scoped (`listByType`/`findByIdAndType`/`createWithType`/`updateByIdAndType`/`deleteByIdAndType`) over the single table; subtype entities emit no own controller/DTO/repository — they fold into the base. |
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
101
|
+
| `SpringValueObjectGenerator` | a Java 21 `record` per `object.value` reached through a `field.object @storage: jsonb` column (single or `@isArray`, transitively through nested VOs) — the typed component the Jackson jsonb codec serializes to/from (carries jakarta validation, unlike a plain payload record). Program D typed-jsonb VOs. |
|
|
102
|
+
| `SpringPayloadGenerator` | a Java 21 `record` per `template` payload VO |
|
|
103
|
+
| `SpringOutputParserGenerator` | the `template.output` strict parser-on-receipt (see the prompts reference) |
|
|
104
|
+
| `SpringOutputPromptGenerator` | the FR-010 output-format prompt fragment for a `template.output` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
|
|
105
|
+
| `SpringRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
|
|
106
|
+
| `LlmTraceHelperGenerator` | `<Entity>TraceHelper.java` per concrete entity — the LLM-trace helper |
|
|
90
107
|
| `SpringFilterAllowlistGenerator` | per-entity filter allowlist |
|
|
91
108
|
|
|
92
109
|
**Projections (read-only views).** An `object.projection` (read-only `source.rdb`
|
|
93
110
|
`@kind: view` child) is served read-only through OMDB at the ObjectManager layer
|
|
94
111
|
(mutating ops throw); no controller is generated (controllers cover writable entities
|
|
95
112
|
only). Its `CREATE VIEW` DDL is emitted by the Node `meta migrate` from the
|
|
96
|
-
projection's `origin.*` children —
|
|
97
|
-
|
|
113
|
+
projection's `origin.*` children — `origin.passthrough`, `origin.aggregate` (`@agg`
|
|
114
|
+
`count`/`sum`/`avg`/`min`/`max`, plus the #195 `any`/`all` quantifiers over a `@filter`
|
|
115
|
+
and `collect` array-rollup with `@distinct`/`@orderBy`), `origin.collection`,
|
|
116
|
+
`origin.computed` (`@expr`), `origin.first`; an object-level `@filter` scopes the whole
|
|
117
|
+
view's rows (#207 — lowers to the outer `WHERE`). Never hand-author the view SQL for a
|
|
118
|
+
shape origins can express (an unmodeled view is unmanaged and drifts silently); carry a
|
|
119
|
+
genuinely irreducible body (recursive CTE, window function, set op) in the `source.rdb`
|
|
120
|
+
**`@sql`** escape (#208) so the tool still owns it, or mark a Flyway-owned object
|
|
121
|
+
`@unmanaged: true`.
|
|
122
|
+
|
|
123
|
+
**Entity read-view (write-through).** An `object.entity` that keeps its writable `table`
|
|
124
|
+
primary source and adds a `@role: replica` `@kind: view` source is a write-through
|
|
125
|
+
read-view (#214): OMDB routes reads through the replica view and writes to the table
|
|
126
|
+
(derived `origin.*` fields excluded from the write path); a create/update re-reads the
|
|
127
|
+
row via the view by primary key (read-your-writes). The replica view's DDL is emitted by
|
|
128
|
+
`meta migrate` from the same origin assembly as a projection view.
|
|
129
|
+
|
|
130
|
+
### Value-object jsonb columns
|
|
131
|
+
|
|
132
|
+
A `field.object` with `@storage: jsonb` (single or `@isArray`) is a typed jsonb column
|
|
133
|
+
backed by an `object.value`: `SpringValueObjectGenerator` emits that VO as a Java record
|
|
134
|
+
and OMDB's Jackson codec serializes it to/from the column (a single VO, or `List<VO>` for
|
|
135
|
+
an array) — the same typed-jsonb round-trip the other ports ship.
|
|
98
136
|
|
|
99
137
|
Metadata lives under `src/main/metaobjects/` in the same canonical JSON the other
|
|
100
138
|
ports read — fused-key form, `source.rdb` + `@table`, `@column` for a renamed
|
|
@@ -88,6 +88,19 @@ A `metaobjects:verify` Maven goal exists for **codegen-drift** (re-generate and
|
|
|
88
88
|
vs committed output). Schema migration and live-DB drift are NOT JVM goals — they run
|
|
89
89
|
through the Node `meta` tool (see the migration reference).
|
|
90
90
|
|
|
91
|
+
## Docs — `mvn metaobjects:docs -Dmetaobjects.docs.language=kotlin`
|
|
92
|
+
|
|
93
|
+
The shared `docs` goal (`DocsMojo`) renders the Kotlin SDK api surface via
|
|
94
|
+
`KotlinApiDocsRenderer` when pointed at the Kotlin language (default `target/docs/api/kotlin`),
|
|
95
|
+
including `AGENT-API.md` — the exact imports, signatures, and payload field shapes for the
|
|
96
|
+
generated code.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
mvn metaobjects:docs -Dmetaobjects.docs.language=kotlin # → target/docs/api/kotlin (AGENT-API.md + per-entity pages)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Before calling any generated code, read `api/kotlin/AGENT-API.md`.**
|
|
103
|
+
|
|
91
104
|
## `codegen-kotlin` generators
|
|
92
105
|
|
|
93
106
|
All live in `metaobjects-codegen-kotlin` under
|
|
@@ -99,8 +112,12 @@ All live in `metaobjects-codegen-kotlin` under
|
|
|
99
112
|
| `KotlinExposedTableGenerator` | `<Entity>Table.kt` — Exposed `Table` object (PK + FK + `@storage` columns) for entities with `source.rdb`. A TPH `@discriminator` base emits ONE `Table` for the whole hierarchy — every subtype-only column folded in `.nullable()` (a row of another subtype stores null there) — single-table inheritance; subtype entities emit no table of their own. |
|
|
100
113
|
| `KotlinRelationsGenerator` | `<Entity>Relations.kt` — extension fns for `@cardinality="many"` query helpers |
|
|
101
114
|
| `KotlinSpringControllerGenerator` | `<Entity>Controller.kt` — Spring `@RestController`, five CRUD endpoints on the cross-port REST contract, for writable entities (`source.rdb` `@kind="table"`). A TPH `@discriminator` base emits ONE controller: polymorphic `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete are scoped to the subtype (cross-subtype → 404); the discriminator is immutable. |
|
|
115
|
+
| `KotlinRepositoryGenerator` | `<Entity>RepositoryBase.kt` — an `open class` consumer persistence seam per writable entity (the Kotlin peer of Java's `SpringRepositoryGenerator`, but with method bodies). Carries the #203 `@autoSet` CRUD stamping: `onCreate` columns stamped once at insert, `onUpdate` columns re-stamped on every write, both excluded from the caller-supplied set. |
|
|
102
116
|
| `KotlinPayloadGenerator` | `<Template>Payload.kt` — `@Serializable` payload data class from a template's `@payloadRef` |
|
|
103
|
-
| `KotlinOutputParserGenerator` | the `template.output` parser-on-receipt (see the prompts reference) |
|
|
117
|
+
| `KotlinOutputParserGenerator` | the `template.output` strict parser-on-receipt (see the prompts reference) |
|
|
118
|
+
| `KotlinExtractorGenerator` | the FR-010 tolerant `extract` mapper for a `template.output` (all-nullable mirror → strict payload) |
|
|
119
|
+
| `KotlinOutputPromptGenerator` | the FR-010 output-format prompt fragment for a `template.output` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
|
|
120
|
+
| `KotlinRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
|
|
104
121
|
| `KotlinValidatorGenerator` | `MetadataStartupValidator.kt` + `ExposedTableValidator.kt` (once per project) |
|
|
105
122
|
| `KotlinSpringConfigGenerator` | `MetadataExposedConfig.kt` — `@Configuration` wiring `Database.connect()` + the startup validator (once per project) |
|
|
106
123
|
| `KotlinStoredProcGenerator` | stored-procedure call wrappers for `source.rdb` `@kind="storedProc"` |
|
|
@@ -109,9 +126,22 @@ All live in `metaobjects-codegen-kotlin` under
|
|
|
109
126
|
**Projections (read-only views).** For an `object.projection` (read-only `source.rdb`
|
|
110
127
|
`@kind: view` child), `KotlinExposedTableGenerator` emits a read-only Exposed `Table`
|
|
111
128
|
wrapper (same column mapping, no write path). The `CREATE VIEW` DDL is emitted by the
|
|
112
|
-
Node `meta migrate` from the projection's `origin.*` children —
|
|
113
|
-
|
|
114
|
-
|
|
129
|
+
Node `meta migrate` from the projection's `origin.*` children — `origin.passthrough`,
|
|
130
|
+
`origin.aggregate` (`@agg` `count`/`sum`/`avg`/`min`/`max`, plus the #195 `any`/`all`
|
|
131
|
+
quantifiers over a `@filter` and `collect` array-rollup with `@distinct`/`@orderBy`),
|
|
132
|
+
`origin.collection`, `origin.computed` (`@expr`), `origin.first`; an object-level
|
|
133
|
+
`@filter` scopes the whole view's rows (#207 — lowers to the outer `WHERE`). Never
|
|
134
|
+
hand-author the view SQL for a shape origins can express (an unmodeled view is unmanaged
|
|
135
|
+
and drifts silently); carry a genuinely irreducible body (recursive CTE, window function,
|
|
136
|
+
set op) in the `source.rdb` **`@sql`** escape (#208) so the tool still owns it, or mark a
|
|
137
|
+
Flyway-owned object `@unmanaged: true`.
|
|
138
|
+
|
|
139
|
+
**Entity read-view (write-through).** An `object.entity` that keeps its writable `table`
|
|
140
|
+
primary source and adds a `@role: replica` `@kind: view` source is a write-through
|
|
141
|
+
read-view (#214): reads route through the replica view and writes to the table (derived
|
|
142
|
+
`origin.*` fields excluded); a create/update re-reads the row via the view by primary key
|
|
143
|
+
(read-your-writes). The replica view's DDL is emitted by `meta migrate` from the same
|
|
144
|
+
origin assembly as a projection view.
|
|
115
145
|
|
|
116
146
|
Metadata lives under `src/main/metaobjects/` in the same canonical JSON the other
|
|
117
147
|
ports read — fused-key form, `source.rdb` + `@table`, `@column` for a renamed
|
|
@@ -28,6 +28,16 @@ the exact `gen` code path so the two can't diverge). `--templates` is the prompt
|
|
|
28
28
|
drift gate (see the prompts reference). Schema migration + live-DB drift are **not**
|
|
29
29
|
`metaobjects` — they run through the Node `meta` tool (see the migration reference).
|
|
30
30
|
|
|
31
|
+
## Docs — `metaobjects docs`
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
metaobjects docs ./metadata --out ./docs # → ./docs/api/python (AGENT-API.md + per-entity pages)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`metaobjects docs` emits this project's Python SDK api surface (`api/python`), including
|
|
38
|
+
`AGENT-API.md` — the exact imports, signatures, and payload field shapes for the
|
|
39
|
+
generated code. **Before calling any generated code, read `api/python/AGENT-API.md`.**
|
|
40
|
+
|
|
31
41
|
## Generators
|
|
32
42
|
|
|
33
43
|
Wire generators by their stable name (`--generators <names>`), or run the default set.
|
|
@@ -40,9 +50,19 @@ a renamed physical column).
|
|
|
40
50
|
| `entity` | one **Pydantic model** per `object.entity` / projection (the `entity-model` generator): typed fields from the metadata, nullability from `@required`, `@maxLength`/validators, enum fields → a Python `Enum`. This is the typed data model. A TPH concrete subtype (`@discriminatorValue`) pins the inherited `@discriminator` field to a `Literal[...]` so the model rejects a foreign-subtype tag. |
|
|
41
51
|
| `routes` | a **FastAPI `APIRouter`** per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). The router declares a repository **`Protocol`** you implement and inject. A TPH `@discriminator` base emits ONE polymorphic router: `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable. Its repository `Protocol` is subtype-keyed (`subtype=None` for the polymorphic base) so your implementation applies the single-table discriminator scope. |
|
|
42
52
|
| `filter-allowlist` | per-entity filter allowlist (FR-009 — the server-side field+operator allowlist the routes validate against). |
|
|
43
|
-
| `payload` / `output-parser` / `output-prompt` / `extractor` / `render-helper` / `trace-helper` | the `template.output` prompt-pillar artifacts —
|
|
53
|
+
| `payload` / `output-parser` / `output-prompt` / `extractor` / `render-helper` / `trace-helper` | the `template.output` prompt-pillar artifacts — the payload VO, the strict parser, the **output-format prompt fragment** (`output-prompt`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), the tolerant `extract`, the typed render helper, and the LLM-trace helper. See the **prompts** reference. |
|
|
44
54
|
| `template` | the generic Mustache `template` primitive. |
|
|
45
55
|
|
|
56
|
+
**Projections + entity read-views.** An `object.projection` (read-only `source.rdb`
|
|
57
|
+
`@kind: view` child) gets a read-only Pydantic model from the `entity` generator; its
|
|
58
|
+
`CREATE VIEW` DDL is emitted by the Node `meta migrate` from the projection's `origin.*`
|
|
59
|
+
children (`passthrough` / `aggregate` / `collection` / `computed` / `first`) — never
|
|
60
|
+
hand-write the view SQL for a shape origins can express. An `object.entity` that adds a
|
|
61
|
+
`@role: replica` `@kind: view` source alongside its writable `table` is a write-through
|
|
62
|
+
**entity read-view** (#214): the generated read model carries the derived `origin.*`
|
|
63
|
+
fields and writes exclude them — reads route to the view, writes to the table (your
|
|
64
|
+
repository implements the split).
|
|
65
|
+
|
|
46
66
|
## Discriminator inheritance (TPH)
|
|
47
67
|
|
|
48
68
|
Python codegen fully supports **table-per-hierarchy (TPH) inheritance**
|
|
@@ -77,13 +97,17 @@ persistence layer and no runnable server**. Two things you hand-write:
|
|
|
77
97
|
|
|
78
98
|
## Known gaps (current — may require a hand-edit)
|
|
79
99
|
|
|
80
|
-
- **
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
- **Composite PKs need a hand-edit.** The generated router/repository key on a single
|
|
101
|
+
primary key whose Python type is **derived from the PK field's subtype** (`field.uuid`
|
|
102
|
+
PK → `uuid.UUID`, `field.long` → `int`, `field.string` → `str`) via the same mapper the
|
|
103
|
+
Pydantic model uses — so a `field.uuid` PK is `uuid.UUID`, not `int`. A **composite** PK
|
|
104
|
+
falls back to `@fields[0]` and needs a hand-edit until specified.
|
|
105
|
+
- **DTO param is `dict[str, Any]`.** The `POST`/`PATCH`/`PUT` body param is typed
|
|
106
|
+
`dto: dict[str, Any]` and responses return `Any`; the repository `Protocol` uses `Any`
|
|
107
|
+
for the row type. This does **not** mean constraints are unenforced — the router
|
|
108
|
+
validates the body against the generated `<Entity>Create` / `<Entity>Patch` Pydantic
|
|
109
|
+
models before the repository call (FR-036: field constraints run on POST/PATCH over
|
|
110
|
+
HTTP). You can further tighten the router signatures to the typed model by hand.
|
|
87
111
|
|
|
88
112
|
## Re-scaffold this context
|
|
89
113
|
|
|
@@ -79,14 +79,30 @@ From `@metaobjectsdev/codegen-ts/generators` (server-side, framework-neutral):
|
|
|
79
79
|
| `barrel()` | `index.ts` re-exporting each `<Entity>.ts` (one-shot, not per-entity) |
|
|
80
80
|
| `promptRender()` | `render<Name>()` per `template.prompt` |
|
|
81
81
|
| `outputParser()` | `<Name>.output.ts` (`parse*` / `safeParse*`) per `template.output` |
|
|
82
|
+
| `callableFile()` | `<Entity>.callable.ts` — an FR-015 `call<Entity>` wrapper for a `source.rdb` `@kind: storedProc`/`tableFunction` (args from the `@parameterRef` value object, in declaration order) |
|
|
82
83
|
|
|
83
84
|
**Projections (read-only views).** For an `object.projection` (a read-only `source.rdb`
|
|
84
85
|
`@kind: view` child), `entityFile()` emits a `pgView(...)` + read-only Zod + a read-only
|
|
85
86
|
finder (no create/update/delete). The `CREATE VIEW` DDL is generated by `meta migrate`
|
|
86
87
|
from the projection's `origin.*` children — `origin.passthrough` (a forwarded column),
|
|
87
|
-
`origin.aggregate` (
|
|
88
|
-
`@filter`
|
|
89
|
-
|
|
88
|
+
`origin.aggregate` (`@agg` `count`/`sum`/`avg`/`min`/`max`, plus the #195 `any`/`all`
|
|
89
|
+
predicate quantifiers over a `@filter` and `collect` array-rollup with optional
|
|
90
|
+
`@distinct`/`@orderBy`; any aggregate row-scoped with `@filter`), `origin.collection` (a
|
|
91
|
+
nested array), `origin.computed` (a row-level `@expr`), `origin.first` (one related row's
|
|
92
|
+
column along `@via`/`@of`/`@orderBy`). An object-level `@filter` on the projection scopes
|
|
93
|
+
the whole view's rows (#207 — lowers to the outer `WHERE`, the metadata-managed
|
|
94
|
+
soft-delete/status view). **Never hand-write the view SQL** for a shape origins can
|
|
95
|
+
express (an unmodeled view is unmanaged and drifts silently); for a genuinely irreducible
|
|
96
|
+
body (recursive CTE, window function, set op), carry it in the `source.rdb` **`@sql`**
|
|
97
|
+
escape (#208) so the tool still owns it, or mark a Flyway-owned object `@unmanaged: true`.
|
|
98
|
+
|
|
99
|
+
**Entity read-view (write-through).** An `object.entity` that keeps its writable `table`
|
|
100
|
+
primary source and adds a `@role: replica` `@kind: view` source is a write-through
|
|
101
|
+
read-view (#214): `entityFile()` routes generated **reads** through the replica `pgView`
|
|
102
|
+
(the read Zod carries the derived `origin.*` fields via `z.infer`), while `queriesFile()`
|
|
103
|
+
writes target the table with derived fields excluded from the insert/update codecs; a
|
|
104
|
+
create/update re-reads the row via the view by primary key (read-your-writes). The replica
|
|
105
|
+
view's DDL is emitted by `meta migrate` from the same origin assembly as a projection view.
|
|
90
106
|
|
|
91
107
|
## Discriminator inheritance (TPH)
|
|
92
108
|
|
|
@@ -17,8 +17,8 @@ fetch it from GitHub / metaobjects.dev) and let it produce the report below.
|
|
|
17
17
|
|
|
18
18
|
# MetaObjects Fit & Migration Assessment
|
|
19
19
|
|
|
20
|
-
_Assessment prompt v1 (post-Phase-0 refinement). Grounded against MetaObjects npm `0.
|
|
21
|
-
Maven `7.
|
|
20
|
+
_Assessment prompt v1 (post-Phase-0 refinement). Grounded against MetaObjects npm `0.17.x` /
|
|
21
|
+
Maven `7.9.x` — verify every capability claim against the current release before asserting it._
|
|
22
22
|
|
|
23
23
|
You are an AI assistant running a **pre-adoption fit assessment** for MetaObjects
|
|
24
24
|
(https://github.com/metaobjectsdev/metaobjects — the cross-language metadata standard:
|
|
@@ -235,12 +235,27 @@ signature class. Hunt all ten classes:
|
|
|
235
235
|
8. Hand-written `CREATE VIEW` / read-only SQL mirroring a read model. Run the
|
|
236
236
|
**necessity test**: expressible when every output column is a passthrough
|
|
237
237
|
(`origin.passthrough @from/@via`), a count/sum/avg/min/max (`origin.aggregate
|
|
238
|
-
@agg/@of/@via`, row-scoped with `@filter`), a
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
`
|
|
238
|
+
@agg/@of/@via`, row-scoped with `@filter`), a predicate quantifier (`origin.aggregate
|
|
239
|
+
@agg: any|all`), an array rollup (`origin.aggregate @agg: collect`), a non-aggregate
|
|
240
|
+
derived scalar (`origin.computed @expr`), an argmax-style "one related row's column"
|
|
241
|
+
pick (`origin.first @via` — covers the common `DISTINCT ON` / lateral-join shape), a
|
|
242
|
+
child collection (`origin.collection`), a soft-delete/status/type row-scope (an
|
|
243
|
+
object-level `@filter` on `object.projection`), or `extends`-borrowed — and joins
|
|
244
|
+
follow declared relationships/`identity.reference` FKs. Expressible → projection
|
|
245
|
+
candidate (note: an unmodeled hand view is *unmanaged* — invisible to `verify --db`;
|
|
246
|
+
modeling it is what makes it gateable). `DISTINCT ON` and lateral join are **not**
|
|
247
|
+
automatic BESPOKE justifications — check `origin.first` first; they earn BESPOKE only
|
|
248
|
+
when the pick can't collapse to one argmax over one `@via` path (a multi-column
|
|
249
|
+
tiebreak, or a lateral doing more than pick-one-row). Not expressible → BESPOKE with a
|
|
250
|
+
NAMED construct (recursive CTE, window fn, set op, a `DISTINCT ON`/lateral join
|
|
251
|
+
`origin.first` genuinely can't express). "It's an aggregation" is not a justification.
|
|
252
|
+
BESPOKE has a better ending than "hand-write it outside the tool": if the body is
|
|
253
|
+
irreducible but still **yours** to own, carry it in the `source.rdb` **`@sql`** escape
|
|
254
|
+
(#208, ADR-0043) — a hand-written view/proc body the tool registers, fingerprints, and
|
|
255
|
+
drift-checks (adopt a pre-existing view with `meta migrate --allow adopt-view`, no
|
|
256
|
+
rewrite needed); if the object is owned **elsewhere** (Flyway, another team's
|
|
257
|
+
migration), mark its source `@unmanaged: true` so `verify --db` reports it as an
|
|
258
|
+
external, declared object instead of silently missing it.
|
|
244
259
|
9. A closed variant-set hand-modeled per instance (N sibling modules on one payload
|
|
245
260
|
shape) → VOCAB CANDIDATE (advisory only; ADR-0037 ordered test).
|
|
246
261
|
10. One prompt's text/payload/parse scattered across services — a renamed field silently
|
|
@@ -273,8 +288,14 @@ concrete — lead with it.
|
|
|
273
288
|
| the metadata itself | strict provenance (ADR-0023): unknown attrs fail load |
|
|
274
289
|
|
|
275
290
|
State the honest limits in the same section: `verify` cannot catch semantic mismodeling
|
|
276
|
-
(a uuid modeled as string passes `--db`), cannot see unmodeled DB
|
|
277
|
-
`--templates` coverage depends on CLI version.
|
|
291
|
+
(a uuid modeled as string passes `--db`), cannot see a genuinely unmodeled DB object
|
|
292
|
+
(nothing ever declared it), and `--templates` coverage depends on CLI version. Name the
|
|
293
|
+
two declared middle states before writing something off as unmodeled: an
|
|
294
|
+
irreducible-but-owned view/proc registered via `source.rdb @sql` (#208) IS fingerprinted
|
|
295
|
+
and drift-checked, and an object owned by another team's tooling can be declared
|
|
296
|
+
`@unmanaged: true` so `verify --db` reports it as external rather than missing it
|
|
297
|
+
silently — "unmodeled" applies only to what was never declared at all. A gate, not a
|
|
298
|
+
proof system.
|
|
278
299
|
|
|
279
300
|
### P4 — Fit rubric (worked, not vibes)
|
|
280
301
|
|
|
@@ -293,7 +314,7 @@ this table worked row-by-row is invalid output):
|
|
|
293
314
|
| DB not Postgres/SQLite/D1 | schema pillar (`migrate`, `verify --db`) OUT — say so plainly; data-access unaffected |
|
|
294
315
|
| Non-entity-shaped domain (no persistent typed records to speak of) | NOT A FIT — structural, and visible in the code |
|
|
295
316
|
| Few entities today (< ~5) | **NOT a flat verdict — this is the M8 trap.** Small-today ≠ small-forever, and git cannot tell you which. Use the trajectory answer (Input 3): *done at this size* → MARGINAL/NOT A FIT (say so plainly — the leverage won't repay the tooling); *expected to grow / add a language / add an LLM surface* → FIT, and note adopting later costs more (you'd retrofit a spine onto more drift). **Unanswered → emit both branches, never guess.** |
|
|
296
|
-
| Schema owned by another team (DBA-gated) | migrate pillar restricted; model read-only ("metadata follows the schema"); flag the org constraint |
|
|
317
|
+
| Schema owned by another team (DBA-gated) | migrate pillar restricted; model read-only ("metadata follows the schema"); objects that stay owned by that team's own tooling can be declared `source.rdb @unmanaged: true` instead of silently excluded; flag the org constraint |
|
|
297
318
|
| Deep hand-tuned ORM investment | churn warning, not a disqualifier: the plan must reproduce those mappings (`@column`/`@table`/`@dbColumnType`) and price it |
|
|
298
319
|
| Team rejects generated code in the repo | flag; regen-every-build works but `verify --codegen` semantics differ — call the tradeoff |
|
|
299
320
|
|
|
@@ -355,9 +376,21 @@ promising it or counting it in benefits:
|
|
|
355
376
|
from behavior).
|
|
356
377
|
- UUIDs as bare strings → `field.uuid` (never `field.string` + `@dbColumnType: uuid`).
|
|
357
378
|
- hand `COUNT/SUM` subqueries, read-model SQL → `object.projection` + `origin.passthrough`
|
|
358
|
-
/ `origin.aggregate` (+`@filter` for scoped
|
|
379
|
+
/ `origin.aggregate` (`count`/`sum`/`avg`/`min`/`max`, +`@filter` for scoped
|
|
380
|
+
aggregates, +`any`/`all` predicate quantifiers, +`collect` array rollups) /
|
|
381
|
+
`origin.collection` / `origin.computed` (a non-aggregate derived scalar) /
|
|
382
|
+
`origin.first` (an argmax-style "one related row's column" pick — the usual
|
|
383
|
+
`DISTINCT ON`/lateral-join case) — plus a soft-delete/status/type view via the
|
|
384
|
+
object-level `@filter` on `object.projection` instead of a hand `WHERE`.
|
|
359
385
|
**Calibration: most projection fields in real spines are passthrough/`extends`
|
|
360
386
|
re-exposures; aggregates are the minority — lead with passthrough.**
|
|
387
|
+
- a hand-maintained denormalized/computed column kept in sync by app code (an "update
|
|
388
|
+
the other row on write" helper mirroring a joined value onto the entity's own row) →
|
|
389
|
+
an **entity read-view** on the same `object.entity` before reaching for a projection:
|
|
390
|
+
keep the writable `table` source, add a non-primary read-only `view` source, and
|
|
391
|
+
declare the extra as a derived `origin.*` field — writes still target the table,
|
|
392
|
+
reads route through the view and re-read by PK (#214; see `docs/features/source-kinds.md`
|
|
393
|
+
"entity read-view vs projection" for when to use which).
|
|
361
394
|
- hand junction joins → `relationship @cardinality: many @through` (junction declares
|
|
362
395
|
two `identity.reference` children; FR-018).
|
|
363
396
|
- copy-pasted base-field blocks → abstract base + `extends`.
|
|
@@ -491,14 +524,18 @@ Every prose prediction gets a claim. Ceiling statements (P5-b) MUST carry
|
|
|
491
524
|
access still works; say it plainly.
|
|
492
525
|
- **TS**: full stack — Drizzle/Zod/Fastify codegen, filter-operator routes, TanStack/React
|
|
493
526
|
UI runtime, migrations. The only port with UI codegen + runtime.
|
|
494
|
-
- **Java/Spring**: generated DTO records, controllers
|
|
495
|
-
filter
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
527
|
+
- **Java/Spring**: generated DTO records, controllers (parse + validate the full FR-009
|
|
528
|
+
filter-op grammar via the generated `FilterAllowlist` + the `FilterParser` runtime
|
|
529
|
+
helper — per-field op allowlist, `in`-list cap, `eq` sugar; api-contract is 20/20 both
|
|
530
|
+
lanes), filter + sort allowlists (pagination/sort), repository *interfaces* (consumer
|
|
531
|
+
implements — existing ORM sits behind unchanged; the consumer's repository impl
|
|
532
|
+
translates the returned predicates to its persistence DSL), payload records, output
|
|
533
|
+
parsers hand-write the Jackson one-liner; entities stay hand-written on this lane;
|
|
534
|
+
Maven `mvn metaobjects:generate` / `mvn metaobjects:verify` (`codegen`/`templates`
|
|
535
|
+
modes — no live-DB mode).
|
|
499
536
|
- **Kotlin/JVM**: `codegen-kotlin` generates entity + Exposed table + Spring controller +
|
|
500
537
|
payload + relations + filter allowlist + validators + output parsers (runs via Maven
|
|
501
|
-
`
|
|
538
|
+
`mvn metaobjects:generate`).
|
|
502
539
|
- **C#**: generated EF Core entities + `AppDbContext` + CRUD minimal-API routes +
|
|
503
540
|
render/payload/verify via `dotnet meta gen`/`verify`; no ObjectManager runtime tier;
|
|
504
541
|
no migrate surface (TS-owned).
|
|
@@ -21,18 +21,35 @@ value-object declaring exactly what data the text expects.
|
|
|
21
21
|
|
|
22
22
|
| Subtype | Use | Extra attrs |
|
|
23
23
|
|---|---|---|
|
|
24
|
-
| `template.prompt` | LLM-targeted | `@maxTokens`, `@requiredSlots`, `@model` |
|
|
25
|
-
| `template.output` | email / docs / config / export | (
|
|
24
|
+
| `template.prompt` | LLM-targeted | `@maxTokens`, `@requiredSlots`, `@requiredTags`, `@model`, `@responseRef` |
|
|
25
|
+
| `template.output` | email / docs / config / export | `@kind: document \| email` (default `document`), `@promptStyle`, `@requiredTags`; `@kind: email` adds `@subjectRef` / `@htmlBodyRef` / `@textBodyRef` |
|
|
26
26
|
|
|
27
27
|
Both carry the generic attrs:
|
|
28
28
|
|
|
29
29
|
| Attr | Required | Purpose |
|
|
30
30
|
|---|---|---|
|
|
31
31
|
| `@payloadRef` | yes | the `object.value` declaring the payload shape |
|
|
32
|
-
| `@textRef` | yes | the 2-layer logical text reference `group/source`, resolved by a provider |
|
|
32
|
+
| `@textRef` | yes for `template.prompt` and a `template.output @kind: document` (the default) — a `template.output @kind: email` carries **no** `@textRef`; it uses `@subjectRef` + `@htmlBodyRef` (+ optional `@textBodyRef`) instead | the 2-layer logical text reference `group/source`, resolved by a provider |
|
|
33
33
|
| `@format` | no | `text` (default) / `html` / `xml` / `csv` / `json` / `markdown` / `spreadsheet` — drives the escaper |
|
|
34
34
|
| `@maxChars` | no | build-time size budget |
|
|
35
35
|
|
|
36
|
+
`template.output @kind: email` renders a structured `EmailDocument` (subject + HTML
|
|
37
|
+
body + optional plain-text body) instead of one string — the TS render helper emits
|
|
38
|
+
an `EmailDocument`-returning function for it (see the `render-example-email`
|
|
39
|
+
conformance fixture). `@promptStyle` (`guide` / `inline` / `exampleOnly`, FR-010)
|
|
40
|
+
selects how the output-format prompt fragment presents the payload shape to an LLM
|
|
41
|
+
(see "the output-format prompt fragment" below); `@requiredTags` names output tags
|
|
42
|
+
the rendered text must contain (`verify` checks it) on both subtypes.
|
|
43
|
+
`template.prompt` additionally carries `@responseRef` — naming the response
|
|
44
|
+
value-object the prompt expects, for typed LLM-call trace derivation.
|
|
45
|
+
|
|
46
|
+
A third, structurally different subtype is also registered core vocabulary:
|
|
47
|
+
**`template.toolcall`** (`@toolName` + `@payloadRef`, ADR-0011) — a vendor-agnostic
|
|
48
|
+
LLM tool-call envelope with no renderable text body (the body IS the
|
|
49
|
+
`@payloadRef`-typed output schema, so it does not carry the generic `@textRef`/
|
|
50
|
+
`@format` attrs above). The vocabulary exists today; MCP exposure of declared
|
|
51
|
+
prompts/tools is roadmap, not shipped — don't promise it.
|
|
52
|
+
|
|
36
53
|
## The payload is an `object.value` projection
|
|
37
54
|
|
|
38
55
|
The payload is **not** an entity — it's an `object.value` whose every field carries
|
|
@@ -44,6 +61,13 @@ an `origin.*` child saying where its value comes from. Three origin subtypes:
|
|
|
44
61
|
| `origin.aggregate @agg <count\|sum\|avg\|min\|max>` | `count`→long, `avg`→double, others match source |
|
|
45
62
|
| `origin.collection @via "Parent.rel"` | a list of a nested payload, assembled from a relationship |
|
|
46
63
|
|
|
64
|
+
These are the **payload-assembly** origins — the vocabulary this skill covers.
|
|
65
|
+
**Projection** read models (`object.projection` over an entity) carry a fuller origin
|
|
66
|
+
vocabulary — the `@agg` predicate quantifiers `any`/`all`, the `collect` array rollup,
|
|
67
|
+
plus `origin.computed` (a closed `@expr` grammar) and `origin.first` (an argmax-style
|
|
68
|
+
pick) — those live in the `metaobjects-authoring` skill and
|
|
69
|
+
`docs/features/source-kinds.md`, not here: don't reach for them on a payload VO.
|
|
70
|
+
|
|
47
71
|
Declaring the payload as a projection is what makes payload bloat visible: adding a
|
|
48
72
|
field to the prompt is a diff on the `object.value`, and a renamed source field
|
|
49
73
|
breaks the build instead of silently degrading the prompt.
|
|
@@ -184,6 +208,43 @@ file; `verify` catches payload-VO ↔ parser drift at build time too.
|
|
|
184
208
|
The three-step consumer pattern is identical everywhere: render the prompt → call
|
|
185
209
|
your LLM client → parse the response with the generated parser.
|
|
186
210
|
|
|
211
|
+
## `template.output` also generates the output-format prompt fragment (FR-010)
|
|
212
|
+
|
|
213
|
+
For every **json/xml-format** `template.output` whose `@payloadRef` resolves to a
|
|
214
|
+
value-object, codegen additionally emits an `output-prompt` artifact: a
|
|
215
|
+
`render<Name>Format(...)`-shaped function backed by the render engine's
|
|
216
|
+
output-format renderer — the "produce your answer like this" instruction fragment
|
|
217
|
+
you splice into the prompt text so the model returns exactly the shape the parser
|
|
218
|
+
above expects. It's generated only for `json`/`xml` outputs (`text`/`html`/`csv`/
|
|
219
|
+
`markdown`/`spreadsheet` don't get a fragment) and skipped under the same
|
|
220
|
+
unresolved-`@payloadRef` rule as the parser; the fragment and the parser's
|
|
221
|
+
`extract()` codegen agree on the same root name.
|
|
222
|
+
|
|
223
|
+
`@promptStyle` on the `template.output` controls the fragment's presentation
|
|
224
|
+
(default `guide`):
|
|
225
|
+
|
|
226
|
+
| `@promptStyle` | Presentation |
|
|
227
|
+
|---|---|
|
|
228
|
+
| `guide` (default) | a prose field list ("Fill in each field…") followed by an example skeleton |
|
|
229
|
+
| `inline` | a single skeleton whose field values are inline placeholders / enum choices |
|
|
230
|
+
| `exampleOnly` | just a filled example skeleton, nothing else |
|
|
231
|
+
|
|
232
|
+
Guidance is **never** emitted as code comments — models routinely ignore comments,
|
|
233
|
+
so the instruction has to live in the rendered text itself.
|
|
234
|
+
|
|
235
|
+
The fragment is **baked directly from the payload's field tree at codegen time**
|
|
236
|
+
(not hand-authored Mustache text), so it cannot itself drift out of sync with the
|
|
237
|
+
payload the way a hand-written `@textRef` can — regenerating it after a payload
|
|
238
|
+
change is the gate. The JVM render module (Java, shared by Kotlin) additionally
|
|
239
|
+
exposes a field-presence check, `Verify.checkOutputPrompt(fragment,
|
|
240
|
+
requiredFieldNames)`, for asserting a *rendered instance* of the fragment actually
|
|
241
|
+
names every required field — useful in a test of the renderer output itself,
|
|
242
|
+
distinct from the `{{field}}`-vs-payload drift check `verify` runs on hand-authored
|
|
243
|
+
template text. Check the language reference for whether this project's port ships
|
|
244
|
+
the equivalent.
|
|
245
|
+
|
|
187
246
|
---
|
|
188
247
|
|
|
189
|
-
For this project's server-language parser specifics, read
|
|
248
|
+
For this project's server-language parser + output-format-fragment specifics, read
|
|
249
|
+
every `references/*.md` file in this skill's directory (one per server language in
|
|
250
|
+
this project's stack).
|
|
@@ -9,6 +9,7 @@ the payload generator, so the parser and the payload VO can't silently drift.
|
|
|
9
9
|
## Contents
|
|
10
10
|
- Wire the generator
|
|
11
11
|
- What it emits
|
|
12
|
+
- The output-format prompt fragment (FR-010)
|
|
12
13
|
- The three-step consumer pattern
|
|
13
14
|
- Recommended LLM caller (bring-your-own)
|
|
14
15
|
- Consumer dependency
|
|
@@ -52,6 +53,26 @@ generator also emits a tolerant `Extract(string[, ExtractOptions])` (self-contai
|
|
|
52
53
|
components) returning an `ExtractionResult` with a nullable `<Payload>Extracted` mirror
|
|
53
54
|
— a classified per-field report rather than a throw.
|
|
54
55
|
|
|
56
|
+
## The output-format prompt fragment (FR-010)
|
|
57
|
+
|
|
58
|
+
For every json/xml-format `template.output`, `MetaObjects.Codegen`'s
|
|
59
|
+
`OutputPromptGenerator` (stable name `output-prompt-generator`) emits a
|
|
60
|
+
`<TemplateName>.prompt.cs` declaring a static `<TemplateName>Prompt` class with a
|
|
61
|
+
`RenderFormat()` / `RenderFormat(PromptOverrides)` pair, backed by the render
|
|
62
|
+
engine's `OutputFormatRenderer` — the "produce your answer like this" fragment for
|
|
63
|
+
the model. It runs as part of the same `dotnet meta gen` invocation as the payload
|
|
64
|
+
and parser generators:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
dotnet meta gen ./metadata --out ./Generated --namespace Acme.Blog
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`@promptStyle` on the `template.output` (`guide` default / `inline` / `exampleOnly`)
|
|
71
|
+
controls the fragment's presentation; guidance is never emitted as comments. Skipped
|
|
72
|
+
for `template.prompt` nodes, non-json/xml `@format`, and an unresolved
|
|
73
|
+
`@payloadRef` — the same skip contract as the parser generator. The baked spec's
|
|
74
|
+
root name is the payload class name, agreeing with the parser's root.
|
|
75
|
+
|
|
55
76
|
## The three-step consumer pattern
|
|
56
77
|
|
|
57
78
|
Render the prompt → call your LLM client (provider-agnostic; nothing is generated
|