@metaobjectsdev/sdk 0.23.2 → 0.24.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.
Files changed (75) hide show
  1. package/agent-context/skills/metaobjects-audit/SKILL.md +20 -7
  2. package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +64 -18
  3. package/agent-context/skills/metaobjects-audit/references/csharp.md +2 -1
  4. package/agent-context/skills/metaobjects-audit/references/java.md +3 -2
  5. package/agent-context/skills/metaobjects-audit/references/kotlin.md +2 -1
  6. package/agent-context/skills/metaobjects-audit/references/requirements.md +3 -4
  7. package/agent-context/skills/metaobjects-audit/references/typescript.md +1 -1
  8. package/agent-context/skills/metaobjects-authoring/SKILL.md +28 -3
  9. package/agent-context/skills/metaobjects-authoring/references/requirements.md +49 -23
  10. package/agent-context/skills/metaobjects-codegen/SKILL.md +4 -3
  11. package/agent-context/skills/metaobjects-codegen/references/csharp.md +2 -2
  12. package/agent-context/skills/metaobjects-codegen/references/java.md +3 -3
  13. package/agent-context/skills/metaobjects-codegen/references/kotlin.md +4 -4
  14. package/agent-context/skills/metaobjects-codegen/references/python.md +2 -2
  15. package/agent-context/skills/metaobjects-codegen/references/typescript.md +3 -3
  16. package/agent-context/skills/metaobjects-fit-assessment/SKILL.md +4 -4
  17. package/agent-context/skills/metaobjects-prompts/SKILL.md +53 -32
  18. package/agent-context/skills/metaobjects-prompts/references/csharp.md +26 -21
  19. package/agent-context/skills/metaobjects-prompts/references/java.md +24 -18
  20. package/agent-context/skills/metaobjects-prompts/references/kotlin.md +25 -20
  21. package/agent-context/skills/metaobjects-prompts/references/python.md +68 -26
  22. package/agent-context/skills/metaobjects-prompts/references/typescript.md +29 -23
  23. package/agent-context/skills/metaobjects-verify/references/migration.md +1 -1
  24. package/agent-context/skills/metaobjects-verify/references/requirements.md +36 -22
  25. package/agent-context/templates/always-on.md.mustache +3 -1
  26. package/dist/agent-docs/body.d.ts +1 -1
  27. package/dist/agent-docs/body.d.ts.map +1 -1
  28. package/dist/agent-docs/body.js +24 -2
  29. package/dist/agent-docs/body.js.map +1 -1
  30. package/dist/collection.d.ts +71 -0
  31. package/dist/collection.d.ts.map +1 -0
  32. package/dist/collection.js +121 -0
  33. package/dist/collection.js.map +1 -0
  34. package/dist/config.d.ts +51 -21
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +76 -10
  37. package/dist/config.js.map +1 -1
  38. package/dist/discovery.d.ts +43 -0
  39. package/dist/discovery.d.ts.map +1 -0
  40. package/dist/discovery.js +100 -0
  41. package/dist/discovery.js.map +1 -0
  42. package/dist/index.d.ts +10 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +17 -4
  45. package/dist/index.js.map +1 -1
  46. package/dist/memory.d.ts +42 -19
  47. package/dist/memory.d.ts.map +1 -1
  48. package/dist/memory.js +38 -95
  49. package/dist/memory.js.map +1 -1
  50. package/dist/metadata-files.d.ts +67 -0
  51. package/dist/metadata-files.d.ts.map +1 -0
  52. package/dist/metadata-files.js +155 -0
  53. package/dist/metadata-files.js.map +1 -0
  54. package/dist/records/any.d.ts +8 -8
  55. package/dist/records/decision.d.ts +2 -2
  56. package/dist/records/glossary.d.ts +4 -4
  57. package/dist/records/principle.d.ts +2 -2
  58. package/dist/scope.d.ts +16 -0
  59. package/dist/scope.d.ts.map +1 -0
  60. package/dist/scope.js +75 -0
  61. package/dist/scope.js.map +1 -0
  62. package/dist/sources.d.ts +86 -0
  63. package/dist/sources.d.ts.map +1 -0
  64. package/dist/sources.js +130 -0
  65. package/dist/sources.js.map +1 -0
  66. package/package.json +2 -2
  67. package/src/agent-docs/body.ts +24 -2
  68. package/src/collection.ts +183 -0
  69. package/src/config.ts +80 -12
  70. package/src/discovery.ts +110 -0
  71. package/src/index.ts +26 -9
  72. package/src/memory.ts +65 -99
  73. package/src/metadata-files.ts +158 -0
  74. package/src/scope.ts +95 -0
  75. package/src/sources.ts +160 -0
@@ -61,6 +61,16 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
61
61
  dataclasses shadowing a generated shape. Diff field-by-field; the divergence is the bug.
62
62
  - [ ] **D. Prompt pillar.** Every LLM prompt-construction site (see § Prompt anti-patterns).
63
63
  - [ ] **E. Owned generators & scaffold-and-own** (see § Owned-codegen assessment).
64
+ - [ ] **E2. Capability ledger — `requirement.*`** (see `references/requirements.md` and the
65
+ Requirement axis of the capability checklist). The one axis whose hunt is hand-written
66
+ PROSE rather than hand-written code: a `CAPABILITIES.md`, a README features table, a
67
+ conventions doc, a known-and-tolerated gap recorded as a TODO, or a rule that exists only
68
+ as a comment above the field it governs. Prose goes stale in silence; `@implementedBy` is
69
+ a reference the loader resolves, so a rename that invalidates the claim is caught.
70
+ **Entirely opt-in — an absent ledger is NOT a defect** (object coverage is a warning by
71
+ design, and a real estate reports most of itself unclaimed on day one). Score the prose
72
+ it would replace, and prefer ONE concrete claim an author can check over a
73
+ whole-estate migration.
64
74
  - [ ] **F. Drift-gate adoption.** Is `meta verify` wired into CI / pre-commit? Which
65
75
  subverbs (`--codegen` / `--templates` / `--db`)? Committed-codegen freshness gate?
66
76
  Advisories heeded? Routine `--no-verify` bypass? Loader `ERR_*` / warnings addressed?
@@ -292,7 +302,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
292
302
  5. **Runtime schema patching** (`ALTER TABLE … ADD COLUMN IF NOT EXISTS`, `_ensure_schema()`) — N schema owners.
293
303
  6. **N declarations of one shape** — same entity as Drizzle table + Zod schema + Pydantic model + hand dataclass; target is 1 + N generated.
294
304
  7. **`own*()` accessor read of an effective property** (ADR-0039) — `ownAttr` / `ownFields` / `own_children` / bare Python `attr(` / `getMetaAttr(name, false)` / native `IsArray` used to read a value or iterate members outside the sanctioned subclass-emit / own-serializer / `@dbColumnType` cases → silently drops `extends`-inherited values. A **correctness defect** (axis G2), not advisory.
295
- 8. **Hand-written `CREATE VIEW` / read-only SQL standing in for a projection or entity read-view (view-necessity test).** Grep migrations, checked-in `.sql`, and repository/query code for `CREATE [OR REPLACE] [MATERIALIZED] VIEW`, and for hand-rolled read-only queries that mirror a read model — a pure-`SELECT` repository/service method with joins or `GROUP BY` feeding a DTO, or a raw-SQL escape (`db.execute(sql…)`, `FromSqlRaw`, a JPA @Query with hand-written SQL). For each, run the **necessity test** — can origins express this shape? A column is derivable when it is (a) a base-entity or relationship-joined column → `origin.passthrough` (`@from` / `@via`), (b) a count/sum/avg/min/max over related rows → `origin.aggregate` (`@agg` / `@of` / `@via`), or an `EXISTS` / `array_agg` → `origin.aggregate` `any` / `all` / `collect` — any of them optionally row-scoped with `@filter`, (c) a child collection → `origin.collection` (`@via`), (d) a computed scalar / **non-aggregate expression column** → `origin.computed` (`@expr`, #195), (e) one related row's column picked by an ordering — an **argmax / `DISTINCT ON … ORDER BY` / correlated `ORDER BY … LIMIT 1`** → `origin.first` (`@of` / `@via` / `@orderBy`, #195), or (f) a column borrowed via `extends` — and the joins follow declared relationships / `identity.reference` FKs.
305
+ 8. **Hand-written `CREATE VIEW` / read-only SQL standing in for a projection or entity read-view (view-necessity test).** Grep migrations, checked-in `.sql`, and repository/query code for `CREATE [OR REPLACE] [MATERIALIZED] VIEW`, and for hand-rolled read-only queries that mirror a read model — a pure-`SELECT` repository/service method with joins or `GROUP BY` feeding a DTO, or a raw-SQL escape (`db.execute(sql…)`, `FromSqlRaw`, a JPA @Query with hand-written SQL). For each, run the **necessity test** — can origins express this shape? A column is derivable when it is (a) a base-entity or relationship-joined column → `origin.passthrough` (`@from` / `@via`), (b) a count/sum/avg/min/max over related rows → `origin.aggregate` (`@agg` / `@of` / `@via`), or an `EXISTS` / `array_agg` → `origin.aggregate` `any` / `all` / `collect` — any of them optionally row-scoped with `@filter`, (d) a computed scalar / **non-aggregate expression column** → `origin.computed` (`@expr`, #195), (e) one related row's column picked by an ordering — an **argmax / `DISTINCT ON … ORDER BY` / correlated `ORDER BY … LIMIT 1`** → `origin.first` (`@of` / `@via` / `@orderBy`, #195), or (f) a column borrowed via `extends` — and the joins follow declared relationships / `identity.reference` FKs.
296
306
  - **Entity-shaped (`SELECT own.* + derived`) → an entity read-view, NOT a projection (#214).** The single most common legacy view is an entity's OWN columns plus a joined/derived extra (`SELECT o.*, c.name AS customer_name`). This is the `Order` **entity with a read route**, not an exposure contract — route it to an **entity read-view**: keep the writable `@role: primary` `@kind: table` source and add a **non-primary `@role: replica` `@kind: view`** source, declaring only the *extra* as a derived `origin.*` field on the entity (the own field set already covers `o.*`). Reads route to the replica view, writes to the table. Reach for a **projection** instead only when the view renames base columns or **row-filters** (`WHERE status='active'`, soft-delete) — the latter is a projection with an object-level `@filter` (#207) that lowers to the outer `WHERE`.
297
307
  - **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.
298
308
  - **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`).
@@ -332,15 +342,17 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
332
342
  - Inline prompt strings (triple-quoted / template-literal constants in service code).
333
343
  - Untyped payloads (`str.format(**dict)` / f-strings / ad-hoc dicts) — payload should be a declared
334
344
  shape: an `object.value` (caller-supplied fields; `origin.passthrough` only — FR-015 parameter
335
- lineage) or, when fields derive by `aggregate` / `collection` / `computed` / `first`, a
345
+ lineage) or, when fields derive by `aggregate` / `computed` / `first`, a
336
346
  **sourceless `object.projection`** carrying those origins (#210 — assembly origins on an
337
347
  `object.value` fail load with `ERR_SUBTYPE_RULE_VIOLATION`; `@payloadRef` accepts the sourceless
338
348
  projection).
339
349
  - Silent-degradation hack (`try/except KeyError` or `?? ''` around formatting) — flag every instance.
340
- - Hand-rolled output parsing (regex / XML / ad-hoc JSON) vs declared `template.output` +
341
- generated `parse*` / `safeParse*` / `extract*` parser — **generated in all five ports**
342
- (Java's generated `<Name>Parser` owns the Jackson `readValue`); flag a hand-rolled parser
343
- in a **non-generated** file where a `template.output` node exists.
350
+ - Hand-rolled output parsing (regex / XML / ad-hoc JSON) vs a declared **responding
351
+ `template.prompt`** (one carrying `@responseRef`) + generated `parse*` / `safeParse*` /
352
+ `extract*` parser — **generated in all five ports** (Java's generated `<Name>Parser` owns
353
+ the Jackson `readValue`); flag a hand-rolled parser in a **non-generated** file where a
354
+ responding `template.prompt` exists. ADR-0052: a `template.output` is outbound only and
355
+ generates no parser, so it is not the node to look for here.
344
356
  - Engine-side formatting breaking byte-identical render (prompt-cache exact-prefix hits
345
357
  depend on byte-stability).
346
358
  - `template.toolcall` candidates: LLM tool schemas hand-defined per call vs modeled
@@ -467,7 +479,8 @@ The audit never edits code. Pattern: **dry-run → review the diff → apply**.
467
479
  leading-wildcard gating — do NOT flag the absence of those in a non-TS port.
468
480
  - **Output-parser codegen** ships in **all five ports** — Java's `SpringOutputParserGenerator`
469
481
  *generates* the `<Name>Parser` (the Jackson `readValue` lives inside that generated file). A
470
- hand-rolled parser in a **non-generated** file where a `template.output` node exists IS a finding.
482
+ hand-rolled parser in a **non-generated** file where a **responding `template.prompt`**
483
+ (`@responseRef`) exists IS a finding. Per ADR-0052 a `template.output` emits no parser.
471
484
  - **Python** still hand-wires the FastAPI router + repository impl around a generated
472
485
  `APIRouter`; relationship / non-`table` source-kind / `field.object flattened` codegen is partial.
473
486
  - **C#** has no ObjectManager runtime tier (EF Core is the runtime) — hand services over the generated `DbContext` are expected.
@@ -50,8 +50,11 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
50
50
  - **`field.map`** (`@valueType`) — hunt ad-hoc open-keyed key/value bags stuffed into a jsonb
51
51
  column by hand.
52
52
  - **Common field attrs** — `@column` (hand column-name mapping), `@default` (hand default
53
- assignment), `@required` (hand presence checks), `@unique` (hand uniqueness), `@readOnly`
54
- (hand write-guards), `@filterable` / `@sortable` (hand filter/sort allowlists),
53
+ assignment), `@required` (hand presence checks), `@unique` (hand uniqueness),
54
+ `@mutability` (hand write-guards: `readOnly` for a column the DB/replication owns,
55
+ `writeOnce` for one set at create and never changed — hunt hand-written
56
+ "cannot be modified after creation" checks), `@filterable` / `@sortable`
57
+ (hand filter/sort allowlists),
55
58
  `@dbColumnType` (hand native-type override), `@example` / `@instruction` (hand prompt
56
59
  hints), `@xmlText` (hand XML-text mapping). The `@db.indexed` attr suppresses the
57
60
  *`@filterable`-without-index* Loader warning (you assert the column is indexed by other
@@ -67,7 +70,7 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
67
70
  - **`@kind` = `view` / `materializedView`** — hunt hand-written SQL views where an authored
68
71
  read-only source belongs. Apply the **view-necessity test** (SKILL.md, drift signature 8): a
69
72
  hand-written `CREATE VIEW` (or read-only SQL mirroring a read model) is a CODEGEN CANDIDATE when
70
- its shape is expressible via `origin.passthrough` / `origin.aggregate` / `origin.collection` /
73
+ its shape is expressible via `origin.passthrough` / `origin.aggregate` /
71
74
  `origin.computed` / `origin.first` + `extends`. Route by shape: an entity's OWN columns plus an
72
75
  extra (`SELECT o.*, …`) → an **entity read-view** (#214: a `@role: replica` view beside the
73
76
  writable `table`); a subset / renamed / row-filtered exposure → an `object.projection` (row-scope
@@ -132,8 +135,6 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
132
135
  subqueries or in-app rollups a derived aggregate field models.
133
136
  - **`origin.passthrough`** (`@from`, `@via`, `@convert`) — hunt denormalized-by-hand copied fields
134
137
  that a passthrough origin pulls across a relationship.
135
- - **`origin.collection`** (`@via`) — hunt hand-assembled child-collection loading a collection
136
- origin derives.
137
138
  - **`origin.computed`** (`@expr` — a closed `attr.expression` grammar) — hunt a hand-computed derived
138
139
  scalar (a formula over other fields) a computed origin models.
139
140
  - **`origin.first`** (`@of`, `@via`, `@orderBy`, `@filter`; `@orderBy` REQUIRED) — hunt a hand
@@ -171,17 +172,26 @@ soft-delete / status / type view it models.
171
172
 
172
173
  ## Template — `template.*` (prompt pillar)
173
174
 
174
- - **`template.prompt`** (`@payloadRef`, `@textRef`, `@responseRef`, `@requiredSlots`,
175
- `@requiredTags`, `@maxTokens`, `@maxChars`, `@format`, `@model`)hunt prompt strings
176
- assembled inline in services, payloads built ad-hoc, output parsing without a typed
177
- `@responseRef`, or token/char budgets enforced by hand.
178
- - **`template.output`** (`@kind` = `document` | `email`; `@subjectRef`, `@htmlBodyRef`,
179
- `@textBodyRef`, `@promptStyle`, `@requiredTags`) hunt hand-built document/email rendering +
180
- hand-written parse-on-receipt the output template + generated render helper/parser cover.
181
- (`@promptStyle` the FR-010 output-format presentation is on `template.output` ONLY;
182
- authoring it on `template.prompt` fails load with `ERR_UNKNOWN_ATTR`.)
183
- - **`template.toolcall`** (`@toolName`, `@payloadRef`) hunt hand-declared LLM tool schemas
184
- a modeled tool call describes.
175
+ **A template subtype's axis is DIRECTION** (ADR-0052): `template.prompt` owns everything about
176
+ talking to a model — both the request and the reply — while `template.output` renders an
177
+ artifact for a person or a file and generates **no parser**.
178
+
179
+ - **`template.prompt`** (`@payloadRef`, `@textRef`, `@responseRef`, `@responseFormat`,
180
+ `@promptStyle`, `@requiredSlots`, `@requiredTags`, `@maxTokens`, `@maxChars`, `@format`,
181
+ `@model`) hunt prompt strings assembled inline in services, payloads built ad-hoc,
182
+ token/char budgets enforced by hand, and **hand-written parse-on-receipt**: a prompt
183
+ declaring `@responseRef` owns the inbound half, so the strict parser, the tolerant
184
+ `extract` mapper and the FR-010 output-format fragment are all generated from it.
185
+ (`@promptStyle` the FR-010 output-format presentation, `guide` / `inline` /
186
+ `exampleOnly` — is on `template.prompt` ONLY; authoring it on `template.output` fails load
187
+ with `ERR_UNKNOWN_ATTR`.)
188
+ - **`template.output`** (`@kind` = `document` | `email`; `@textRef`, `@subjectRef`,
189
+ `@htmlBodyRef`, `@textBodyRef`, `@payloadRef`, `@format`, `@maxChars`, `@requiredTags`) —
190
+ **outbound only.** Hunt hand-built document/email rendering the output template + generated
191
+ render helper cover. It parses nothing: a parser here would be reading back text the system
192
+ just rendered and sent.
193
+ - **`template.toolcall`** (`@toolName`, `@payloadRef`, `@maxTokens`) — hunt hand-declared LLM
194
+ tool schemas a modeled tool call describes.
185
195
  - **`template.base`** — abstract base.
186
196
 
187
197
  ## Attr — `attr.*`
@@ -200,6 +210,40 @@ soft-delete / status / type view it models.
200
210
  generator would own. Apply the ADR-0037 ordered test before proposing; advisory (VOCAB
201
211
  CANDIDATE); see SKILL.md axis I "New-vocabulary OPPORTUNITY".
202
212
 
213
+ ## Requirement — `requirement.*` (capability ledger)
214
+
215
+ The only axis here whose hunt is not "hand-written code the metadata describes" but
216
+ **hand-written PROSE that claims something about the code and nothing checks.** Two
217
+ subtypes with opposite polarity: `requirement.functional` fails when NOTHING implements it
218
+ (existence); `requirement.architectural` fails when something VIOLATES it (universality).
219
+
220
+ - `requirement.functional` — `@statement`, `@status`, `@level`, `@counterexample` — hunt a
221
+ `CAPABILITIES.md`, a features table in a README, a `docs/status/` tree, or a spreadsheet
222
+ that lists what the system does and how done each item is. Prose goes stale silently; a
223
+ declared requirement is a node the loader resolves and `meta verify` reports on.
224
+ - `requirement.architectural` — `@statement`, `@status`, `@counterexample`, optional `@level` —
225
+ hunt a conventions doc, a lint rule with no enforcement, or an ADR whose ruling is
226
+ restated in review comments ("we always do X"). `@level` is optional here on purpose:
227
+ absent keeps the flat object-independent form, present opts into a taxonomy.
228
+ - `@implementedBy` — hunt the mapping from "capability" to "the code that provides it"
229
+ living only in someone's head, a wiki table, or a comment. This is the load-bearing
230
+ attribute: it is a REFERENCE the loader resolves, so a rename or deletion that
231
+ invalidates the claim is caught, where prose would not be.
232
+ - **L4 binds an object; L5 binds one MEMBER of it, as a dotted `pkg::Owner.member` path.**
233
+ Mismatches are errors at both levels (`ERR_REQUIREMENT_L5_NOT_MEMBER` /
234
+ `ERR_REQUIREMENT_L4_NOT_OBJECT`) and the fix is to move the entry to the other level,
235
+ not to change the ref. Reach for L5 for a rule about ONE column — the kind currently
236
+ recorded only as a comment above a field.
237
+ - `@disposition` (`accepted` / `deferred`) + `@trackedBy` — hunt a known-and-tolerated gap
238
+ recorded as a TODO, or a ticket number in a comment. **ABSENT disposition means UNDECIDED**,
239
+ which is the point: a gap nobody has ruled on reads differently from one deliberately
240
+ accepted.
241
+ - **CALIBRATION — entirely opt-in and warning-only where it counts.** A project declaring no
242
+ `requirement.*` nodes sees no diagnostics at all, and object coverage ("entities claimed
243
+ by nothing") ships as a WARNING deliberately — a real estate carrying one requirement will
244
+ report most of itself unclaimed on day one. Do NOT score an absent ledger as a defect;
245
+ score the hand-written prose it would replace.
246
+
203
247
  ## Common documentation attrs (any node)
204
248
 
205
249
  - `@description`, `@title`, `@summary`, `@notes`, `@deprecated`, `@replacedBy`, `@seeAlso`,
@@ -222,8 +266,10 @@ soft-delete / status / type view it models.
222
266
  is **generated in all five ports** (api-contract corpus, both lanes) — **flag hand-rolled
223
267
  filter parsing anywhere.** Only the richer surface (`?search=`, explicit
224
268
  `filter[or][N]` / `filter[and][N]` combinators, leading-wildcard gating) is TS-only.
225
- Output-parser codegen also ships in **all five ports** Java's `SpringOutputParserGenerator`
226
- *generates* the parser (the Jackson `readValue` lives inside that generated file). **Python**
269
+ Output-parser codegen also ships in **all five ports**, keyed on a **responding
270
+ `template.prompt`** (`@responseRef`), never on a `template.output` (ADR-0052) Java's
271
+ `SpringOutputParserGenerator` *generates* the parser (the Jackson `readValue` lives inside
272
+ that generated file). **Python**
227
273
  still hand-wires the FastAPI router around a generated `APIRouter` (relationship /
228
274
  non-`table` source-kind / flattened-object codegen is partial). **C#** has no
229
275
  ObjectManager runtime tier (EF Core *is* the runtime) — hand services over the generated
@@ -104,4 +104,5 @@ version across these packages is an intra-port skew finding.
104
104
  TS-only — do NOT flag its absence in C#.
105
105
  - **Output-parser codegen ships in C#.** `output-parser` / `extractor` / `render-helper`
106
106
  generators are available (`dotnet meta gen --generators output-parser`). Absence of
107
- wired output parsers where `template.output` nodes exist IS a finding.
107
+ wired output parsers where a **responding `template.prompt`** (`@responseRef`) exists IS a
108
+ finding. ADR-0052: a `template.output` is outbound only and generates no parser.
@@ -49,7 +49,7 @@ plugin `<configuration>` — that is the source of truth for which generators ru
49
49
  | Hand-written `@RestController` on a CRUD entity | `SpringControllerGenerator` should own this; trust the `pom.xml` `<generators>` configuration, not stale docs |
50
50
  | `interface <Entity>Repository` with no `@generated` comment | `SpringRepositoryGenerator` emits the stub; compare field by field |
51
51
  | `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
52
- | `ObjectMapper.readValue(` outside a `*Parser.java` file | check if a `template.output` node exists — output-parser codegen ships in Java |
52
+ | `ObjectMapper.readValue(` outside a `*Parser.java` file | check if a responding `template.prompt` (`@responseRef`) exists — output-parser codegen ships in Java |
53
53
  | `LIMIT ?` / `OFFSET ?` assembled by hand | generated CRUD routes handle pagination; OMDB `getObjects` accepts `QueryOptions` |
54
54
 
55
55
  ---
@@ -92,7 +92,8 @@ versioning — **do not flag it** (only intra-port skew matters).
92
92
  *generates* the typed `<Name>Parser` class; the Jackson `readValue` call lives inside that
93
93
  generated file. It is NOT a defect to see Jackson deserialization in a generated `*Parser.java`
94
94
  file. **Do not flag Jackson `readValue` calls in generated `*Parser.java` files.** DO flag a
95
- hand-rolled parser in a *non*-generated file where a `template.output` node exists.
95
+ hand-rolled parser in a *non*-generated file where a responding `template.prompt`
96
+ (`@responseRef`) exists — per ADR-0052 a `template.output` emits no parser.
96
97
  - **Core filter-operator codegen ships in Java — do NOT treat it as deferred.**
97
98
  `SpringControllerGenerator` generates the `?filter[field][op]=value` grammar (all 9 operators
98
99
  `eq/ne/gt/gte/lt/lte/in/like/isNull`): it parses via the runtime `FilterParser`, validates
@@ -101,7 +101,8 @@ cross-port versioning — **do not flag it** (only intra-port skew matters).
101
101
  `KotlinOutputParserGenerator` generates the typed parser class AND its deserialization body
102
102
  (a kotlinx `Json.decodeFromString` call inside the generated file — kotlinx, not Jackson: the
103
103
  #187 Jackson move was jsonb-codec-only, prompt payloads/parsers stay on kotlinx). Only flag
104
- a hand-rolled parser in a NON-generated file where a `template.output` node exists.
104
+ a hand-rolled parser in a NON-generated file where a responding `template.prompt`
105
+ (`@responseRef`) exists — per ADR-0052 a `template.output` emits no parser.
105
106
  - **Core filter-operator codegen ships in Kotlin — do NOT treat it as deferred.**
106
107
  `KotlinSpringControllerGenerator` generates the `?filter[field][op]=value` grammar (all 9
107
108
  operators `eq/ne/gt/gte/lt/lte/in/like/isNull`) validated against the generated filter allowlist
@@ -10,14 +10,13 @@ effort re-deriving what a green run already proves.
10
10
  ## What verify has already proven (do not re-check by hand)
11
11
 
12
12
  Links sit at or below the L4 floor, nesting agrees with levels, `@status` values are legal,
13
- references resolve (with dangling allowed on `abandoned`/`superseded`), and named
14
- `@verifiedBy` tests exist and are not skipped.
13
+ and references resolve (with dangling allowed on `planned`, whose nodes do not exist yet).
15
14
 
16
15
  ## What only a human or an agent reading the code can catch
17
16
 
18
17
  **1. Statuses that are false.** The highest-value finding in the whole dimension. A
19
18
  requirement marked `live` whose implementation was gutted; one marked `partial` that is now
20
- complete; one marked `abandoned` whose code is demonstrably still running. Sample the claims
19
+ complete; one marked `planned` that was quietly built months ago. Sample the claims
21
20
  and read the nodes. `status` is the only payload with controlled evidence behind it — the
22
21
  resurrection protection rides entirely on it being true.
23
22
 
@@ -49,7 +48,7 @@ should be simplified rather than defended.
49
48
  coverage is explicitly rejected — plumbing members are covered by architectural
50
49
  requirements with high fan-out. L5 exists so a member claim *can* be made where it means
51
50
  something, never so every member must carry one.
52
- - **Dangling links on `abandoned` / `superseded`.** Correct, and load-bearing.
51
+ - **Dangling links on `planned`.** Correct the plan precedes the nodes.
53
52
  - **A project with no requirements at all.** The feature is opt-in by declaration; absence is
54
53
  not a finding.
55
54
 
@@ -107,7 +107,7 @@ via `metamodelVersion` (see the Phase 0 cross-language consistency item).
107
107
  TypeScript is the reference implementation — it ships the full feature set:
108
108
 
109
109
  - Filter-operator codegen: **complete** — the core `?filter[field][op]` grammar (all 9 operators `eq/ne/gt/gte/lt/lte/in/like/isNull`, `<Entity>FilterAllowlist`) generates in every port; TS additionally ships the richer surface (`?search=`, `filter[or][N]` / `filter[and][N]` combinators, leading-wildcard gating).
110
- - Output-parser codegen: **complete** (`outputParser()` generator; `parse*`/`safeParse*`/`extract*` per `template.output`).
110
+ - Output-parser codegen: **complete** (`outputParser()` generator; `parse*`/`safeParse*`/`extract*` emitted as `<Name>.response.ts` per **responding `template.prompt`** — one carrying `@responseRef`; ADR-0052).
111
111
  - ObjectManager: **complete** (`@metaobjectsdev/runtime-ts`; `kyselyDriver` / `inMemoryDriver`).
112
112
  - Schema migrate / `meta verify --db`: **complete** (TS owns the shared migrate engine — ADR-0015).
113
113
  - D1 dialect: **TS-only by design** — `dialect: "d1"` in `metaobjects.config.ts` targets Cloudflare D1 via the Wrangler CLI layout. Do NOT recommend porting D1 support to other languages.
@@ -781,7 +781,7 @@ second identity declaration, and re-stating every passthrough column — for no
781
781
  A derived read model that IS an independent exposure is an **`object.projection`**
782
782
  (FR-024): its fields `extends` entity fields (`extends: "Author.id"` — dotted
783
783
  child traversal, package only on the root segment) and/or carry `origin.*`
784
- children (`passthrough` / `aggregate` / `collection` / `computed` / `first`)
784
+ children (`passthrough` / `aggregate` / `computed` / `first`)
785
785
  declaring assembly; its identity passes through via `extends` (`identity.primary:
786
786
  { name: id, extends: "Author.id" }`); it is read-only by construction and the
787
787
  declared field set IS the exposure (fail-closed). Give it a read-only `source.rdb`
@@ -789,6 +789,31 @@ declared field set IS the exposure (fail-closed). Give it a read-only `source.rd
789
789
  projection detection + view DDL off that read-only source, so without it `meta gen`
790
790
  emits nothing for the projection.
791
791
 
792
+ **The borrowed key may be ANY unique key — including a composite, and including the
793
+ entity's `identity.secondary`.** The single-field `extends: "Author.id"` above is the
794
+ common case, not the limit. Both of these are legal:
795
+
796
+ ```yaml
797
+ # Composite primary → composite primary. @fields is COMPUTED from the local
798
+ # pass-through fields, so it is optional here (declare it and it must agree).
799
+ - identity.primary: { name: pk, extends: "Order.pk" } # Order.pk = [tenant, ref]
800
+
801
+ # A read model keyed on the entity's BUSINESS key, never surfacing its surrogate id.
802
+ # Account has both: identity.primary pk (auto-increment id) AND identity.secondary
803
+ # byCode (tenant + code). The view exposes tenant + code and borrows byCode.
804
+ - identity.primary: { name: pk, extends: "Account.byCode" }
805
+ ```
806
+
807
+ The rule is **uniqueness, not nomination**: ADR-0040 put uniqueness in the type, so
808
+ `identity.primary` and `identity.secondary` are both unique keys and either can back a
809
+ projection's key. `identity.reference` cannot — a foreign key is not unique, so
810
+ `identity.primary extends: "Account.ownerRef"` is `ERR_EXTENDS_TARGET_MISMATCH`.
811
+
812
+ Key correspondence still holds in every case: every field named by the borrowed identity's
813
+ `@fields` needs a local field `extends`-ing that entity field, or the load fails with
814
+ `ERR_IDENTITY_KEY_MISMATCH` — the identity cannot claim a pass-through the fields do not
815
+ make.
816
+
792
817
  **A CONCRETE projection declares its OWN source — never inherits one.** A
793
818
  projection may `extends` another projection to reuse shape, but the child must
794
819
  declare its own `source.rdb`; inheriting the parent's is
@@ -982,7 +1007,7 @@ Reach for it when the project needs to answer any of:
982
1007
 
983
1008
  - **"Why is this field here?"** — an L5 requirement binds a claim to a specific member. Authoring these exhaustively is what surfaces columns nothing reads and vocabularies nobody documented.
984
1009
  - **"What is broken but known?"** — `@status: partial` plus `@disposition: accepted | deferred`. Absent disposition means *undecided*, and `meta verify` counts those: the gaps nobody has ruled on.
985
- - **"What did we build and then retire?"** — `@status: abandoned` is the one status where a dangling `@implementedBy` is *correct*, so the record survives the deletion.
1010
+ - **"What did we say we would build and have not?"** — `@status: planned` is the one status where a dangling `@implementedBy` is *correct*, because the entry precedes the nodes.
986
1011
  - **"What have we committed to build?"** — `@status: planned`. Its references may dangle, and it never counts toward object coverage.
987
1012
  - **"Which ticket covers this?"** — `@trackedBy`.
988
1013
 
@@ -995,7 +1020,7 @@ If the project declares any `requirement.*` node, `references/requirements.md` i
995
1020
  name: everyStoredRowIsAddressable
996
1021
  status: live
997
1022
  statement: Every persisted row declares the identity by which it is addressed.
998
- violation: A row that can be inserted but never pointed at.
1023
+ counterexample: A row that can be inserted but never pointed at.
999
1024
  implementedBy: [acme::shop::Order]
1000
1025
  ```
1001
1026
 
@@ -4,24 +4,23 @@ Capabilities are **metadata**, declared in `metaobjects/` beside the entities th
4
4
  describe. Read the existing requirement nodes before designing anything. Two rules matter
5
5
  more than the rest.
6
6
 
7
- **1. When you retire something, record it at that moment.** Set the requirement's
8
- `status` to `abandoned` (built, then deliberately dropped) or `superseded` (something else
9
- does it now name it in `supersededBy`), in the same change that removes the code.
7
+ **1. A requirement is PRESCRIPTIVE it states what should be true, never what happened.**
8
+ So when a capability is retired, **delete its requirement** in the same change that removes
9
+ the code. `status` is `planned | live | partial`; there is no member meaning "we used to do
10
+ this", because every one of those three describes something meant to be true now or soon.
10
11
 
11
- This is the one thing a requirement does that the rest of the model cannot. Given a brief
12
- matching a retired feature, agents reading only the model proposed **reviving** it 24 times
13
- out of 24, each believing it was reusing. A retired feature is *more* attractive than a
14
- live one: purpose-built for exactly the request, never complicated by production.
12
+ What a deleted entry leaves behind is a diff, which is the right home for it. If something
13
+ about the retirement is worth carrying forward why it went, what replaced it put that in
14
+ `notes` on the entry that survives, where a reader looking at today's model will find it.
15
15
 
16
- Leaving a dangling `implementedBy` on an `abandoned` or `superseded` requirement is
17
- **correct** those nodes are supposed to be gone, and `verify` allows it deliberately. On
18
- `live` or `partial` the same dangling reference is an error: the model moved and the
19
- requirement went stale.
16
+ Leaving a dangling `implementedBy` is **correct only on `planned`**: the entry precedes the
17
+ nodes. On `live` or `partial` the same dangling reference is an error the model moved and
18
+ the requirement went stale.
20
19
 
21
20
  **2. When you add an entity, claim it.** Every `object.entity` should appear in some
22
21
  requirement's `implementedBy`, or `verify` says so.
23
22
 
24
- **Every requirement states its violation.** One sentence: what breaking it looks like.
23
+ **Every requirement states its counterexample.** One sentence: what breaking it looks like.
25
24
  *"Every entity has a uuid primary key"* is violable — point at one with a composite string
26
25
  key. *"Things are persisted"* is not, and is a description rather than a requirement. Same
27
26
  rule kills *"the system is reliable"*. If you cannot say what breaking it looks like,
@@ -33,7 +32,7 @@ decide the split before writing any of them:
33
32
 
34
33
  - `title` — a short **noun-phrase** label (`name` is an identifier; this is what an index shows)
35
34
  - `statement` — **the claim**. This IS the description of what the requirement is
36
- - `violation` — **the counterexample** that makes the claim checkable
35
+ - `counterexample` — **what would falsify the claim**, which is what makes it checkable
37
36
  - `description` — **the scope**: what the claim covers, what it deliberately does not, and
38
37
  which sibling entry owns the rest
39
38
  - `notes` — **the evidence**: how you know the `status` is true — citations, vocabularies, the
@@ -52,6 +51,32 @@ service — these never reference the model. **L4** binds a declared top-level n
52
51
  validator, identity, or a template's child. `implementedBy` above L4 is an error.
53
52
  Regrouping *moves* a node; it does not edit a parent string.
54
53
 
54
+ **Write an L5 ref as a dotted `pkg::Owner.member` path** — the level and the shape of the
55
+ ref must agree, and this is the single easiest thing to get wrong:
56
+
57
+ ```yaml
58
+ # L4 binds the OBJECT
59
+ level: 4
60
+ implementedBy: ["acme::orders::Order"]
61
+
62
+ # L5 binds ONE MEMBER of it — dotted owner.member, not the object
63
+ level: 5
64
+ implementedBy: ["acme::orders::Order.placedAt"]
65
+ ```
66
+
67
+ Both mismatches are caught and they are symmetric: an L5 whose ref names an object is
68
+ `ERR_REQUIREMENT_L5_NOT_MEMBER`, an L4 whose ref names a member is
69
+ `ERR_REQUIREMENT_L4_NOT_OBJECT`. **In both cases the fix is to move the entry to the other
70
+ level, not to rewrite the ref** — the ref is usually right and the level is usually the
71
+ mistake. The instinct that produces the error is reaching for the object a rule is *about*;
72
+ what L5 wants is the member the rule is *carried by*. Copying an L4 block and changing only
73
+ `level: 5` produces exactly this failure.
74
+
75
+ Reach for L5 when the statement is about ONE column and its entity's own requirement could
76
+ not express it — "this lifecycle is `resolvedAt IS NULL` and never `status`", "this id is
77
+ nullable, which is why the parent is `partial`". Do NOT use L5 for a blanket rule that
78
+ happens to touch many fields; that is an architectural requirement.
79
+
55
80
  Claim your prompts. A `template.prompt` is a model node realising a capability exactly as
56
81
  an entity is, and it is the node whose retirement is hardest to see later — a removed
57
82
  prompt leaves no table behind. A prompt estate with no requirement entries is the same
@@ -82,8 +107,9 @@ usually one nobody read carefully. Then say what was DECIDED, which is a separat
82
107
  - **absent** — undecided, and that is a real state. `verify` counts these, because
83
108
  *"which gaps has nobody ruled on?"* is the question a review exists to answer.
84
109
 
85
- A `partial` nobody intends to finish is usually `abandoned` — built then deliberately
86
- retired, the one status where a dangling reference is correct.
110
+ A `partial` nobody intends to finish is `partial` + `@disposition: accepted` the gap is
111
+ understood and deliberately not being closed. If the capability itself is gone, delete the
112
+ requirement instead.
87
113
 
88
114
  **`status: planned` locks in work you have not started.** Its references may dangle (write
89
115
  the requirement before the entity), and it never counts toward object coverage — otherwise
@@ -96,22 +122,22 @@ ambition rather than work.
96
122
  level: 3
97
123
  status: live
98
124
  statement: "Scene pacing follows story beats"
99
- violation: "A scene that advances on a clock rather than on the story"
125
+ counterexample: "A scene that advances on a clock rather than on the story"
100
126
  children:
101
127
  - requirement.functional:
102
- name: TurnTimer
128
+ name: BeatProgression
103
129
  level: 4
104
- status: abandoned # retired deliberately -- do NOT revive
105
- statement: "Pacing was driven by a per-turn wall-clock timer"
106
- violation: "Pacing driven by elapsed time instead of beat completion"
107
- supersededBy: BeatProgression
108
- implementedBy: ["game::turn::TurnTimer"] # gone, and that is the point
130
+ status: planned # not built yet -- refs may dangle
131
+ statement: "A scene advances when its beat completes"
132
+ counterexample: "A scene that advances with its beat unresolved"
133
+ trackedBy: ["acme/game#412"]
134
+ implementedBy: ["game::turn::BeatProgression"] # does not exist YET
109
135
 
110
136
  - requirement.architectural:
111
137
  name: UuidPrimaryKeys
112
138
  status: live
113
139
  statement: "Every entity has a uuid primary key"
114
- violation: "An entity keyed by a composite string"
140
+ counterexample: "An entity keyed by a composite string"
115
141
  implementedBy: ["game::turn::Turn", "game::world::Location"]
116
142
  ```
117
143
 
@@ -61,8 +61,9 @@ name** (kebab-case) that surfaces in diagnostics — reference generators by tha
61
61
  name, never by inlining what they emit. Typical generators cover: the entity
62
62
  type/model, the DB table/schema, query/finder helpers, REST routes, client
63
63
  form/grid/hook artifacts, filter + sort allowlists, payload value-objects, and
64
- `template.output` parsers. You enable the subset your project needs; an abstract
65
- entity never emits instance/write artifacts regardless.
64
+ parsers for a responding `template.prompt` (one carrying `@responseRef`). You
65
+ enable the subset your project needs; an abstract entity never emits
66
+ instance/write artifacts regardless.
66
67
 
67
68
  Per-entity opt-outs exist (e.g. skipping client-side artifacts for a given
68
69
  entity) and are set as attributes on the entity in metadata, not in code.
@@ -103,7 +104,7 @@ the data access too.
103
104
  `origin.aggregate` (`@agg` `count`/`sum`/`avg`/`min`/`max`, plus the #195
104
105
  `any`/`all` predicate quantifiers over a `@filter` and `collect` array-rollup with
105
106
  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.computed` (a row-level `@expr`), and
107
108
  `origin.first` (one related row's column along `@via`/`@of`/`@orderBy`) — **and a
108
109
  read-only `source.rdb` `@kind: view` child** (codegen detects a projection by that
109
110
  read-only source, not by the subtype alone — omit it and nothing is generated).
@@ -45,8 +45,8 @@ 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
- | `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. |
48
+ | `payload` | `<Payload>.payload.cs` — the strict typed payload `record` (+ any nested element records) per `template.prompt` `@payloadRef` / `@responseRef` (each an `object.value`): `@payloadRef` types the REQUEST, `@responseRef` the REPLY the parser/extractor bind to. |
49
+ | `output-parser` / `extractor` / `output-prompt` / `render-helper` | the prompt-pillar artifacts for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: these tiers are INBOUND; `template.output` is outbound only and emits no parser). 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. |
50
50
  | `template` | the generic Mustache `templateGenerator()` primitive. |
51
51
 
52
52
  Metadata lives under `metaobjects/` (or wherever you point `--metadata-dir`) in the
@@ -101,8 +101,8 @@ separate `metaobjects-codegen-base` module instead.)
101
101
  | `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. |
102
102
  | `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. |
103
103
  | `SpringPayloadGenerator` | a Java 21 `record` per `template` payload VO |
104
- | `SpringOutputParserGenerator` | the `template.output` strict parser-on-receipt (see the prompts reference) |
105
- | `SpringOutputPromptGenerator` | the FR-010 output-format prompt fragment for a `template.output` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
104
+ | `SpringOutputParserGenerator` | the strict parser-on-receipt for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: INBOUND; a `template.output` emits no parser). See the prompts reference. |
105
+ | `SpringOutputPromptGenerator` | the FR-010 output-format prompt fragment for a responding `template.prompt` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
106
106
  | `SpringRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
107
107
  | `LlmTraceHelperGenerator` | `<Entity>TraceHelper.java` per concrete entity — the LLM-trace helper |
108
108
  | `SpringFilterAllowlistGenerator` | per-entity filter allowlist |
@@ -114,7 +114,7 @@ separate `metaobjects-codegen-base` module instead.)
114
114
  only). Its `CREATE VIEW` DDL is emitted by the Node `meta migrate` from the
115
115
  projection's `origin.*` children — `origin.passthrough`, `origin.aggregate` (`@agg`
116
116
  `count`/`sum`/`avg`/`min`/`max`, plus the #195 `any`/`all` quantifiers over a `@filter`
117
- and `collect` array-rollup with `@distinct`/`@orderBy`), `origin.collection`,
117
+ and `collect` array-rollup with `@distinct`/`@orderBy`),
118
118
  `origin.computed` (`@expr`), `origin.first`; an object-level `@filter` scopes the whole
119
119
  view's rows (#207 — lowers to the outer `WHERE`). Never hand-author the view SQL for a
120
120
  shape origins can express (an unmodeled view is unmanaged and drifts silently); carry a
@@ -114,9 +114,9 @@ All live in `metaobjects-codegen-kotlin` under
114
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
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. |
116
116
  | `KotlinPayloadGenerator` | `<Template>Payload.kt` — `@Serializable` payload data class from a template's `@payloadRef` |
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`) |
117
+ | `KotlinOutputParserGenerator` | the strict parser-on-receipt for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: INBOUND; a `template.output` emits no parser). See the prompts reference. |
118
+ | `KotlinExtractorGenerator` | the FR-010 tolerant `extract` mapper for a responding `template.prompt` (all-nullable mirror → strict payload) |
119
+ | `KotlinOutputPromptGenerator` | the FR-010 output-format prompt fragment for a responding `template.prompt` (presentation via `@promptStyle: guide`/`inline`/`exampleOnly`) |
120
120
  | `KotlinRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
121
121
  | `KotlinValidatorGenerator` | `MetadataStartupValidator.kt` + `ExposedTableValidator.kt` (once per project) |
122
122
  | `KotlinSpringConfigGenerator` | `MetadataExposedConfig.kt` — `@Configuration` wiring `Database.connect()` + the startup validator (once per project) |
@@ -129,7 +129,7 @@ wrapper (same column mapping, no write path). The `CREATE VIEW` DDL is emitted b
129
129
  Node `meta migrate` from the projection's `origin.*` children — `origin.passthrough`,
130
130
  `origin.aggregate` (`@agg` `count`/`sum`/`avg`/`min`/`max`, plus the #195 `any`/`all`
131
131
  quantifiers over a `@filter` and `collect` array-rollup with `@distinct`/`@orderBy`),
132
- `origin.collection`, `origin.computed` (`@expr`), `origin.first`; an object-level
132
+ `origin.computed` (`@expr`), `origin.first`; an object-level
133
133
  `@filter` scopes the whole view's rows (#207 — lowers to the outer `WHERE`). Never
134
134
  hand-author the view SQL for a shape origins can express (an unmodeled view is unmanaged
135
135
  and drifts silently); carry a genuinely irreducible body (recursive CTE, window function,
@@ -50,13 +50,13 @@ a renamed physical column).
50
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. |
51
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. |
52
52
  | `filter-allowlist` | per-entity filter allowlist (FR-009 — the server-side field+operator allowlist the routes validate against). |
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. |
53
+ | `payload` / `output-parser` / `output-prompt` / `extractor` / `render-helper` / `trace-helper` | the prompt-pillar artifacts for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: these tiers are INBOUND; `template.output` is outbound only and emits no parser). The payload VO, the strict parser (`<template>_response_parser`), the **output-format prompt fragment** (`<template>_response_format`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), the tolerant `extract`, the typed render helper, and the LLM-trace helper. See the **prompts** reference. |
54
54
  | `template` | the generic Mustache `template` primitive. |
55
55
 
56
56
  **Projections + entity read-views.** An `object.projection` (read-only `source.rdb`
57
57
  `@kind: view` child) gets a read-only Pydantic model from the `entity` generator; its
58
58
  `CREATE VIEW` DDL is emitted by the Node `meta migrate` from the projection's `origin.*`
59
- children (`passthrough` / `aggregate` / `collection` / `computed` / `first`) — never
59
+ children (`passthrough` / `aggregate` / `computed` / `first`) — never
60
60
  hand-write the view SQL for a shape origins can express. An `object.entity` that adds a
61
61
  `@role: replica` `@kind: view` source alongside its writable `table` is a write-through
62
62
  **entity read-view** (#214): the generated read model carries the derived `origin.*`
@@ -78,7 +78,7 @@ From `@metaobjectsdev/codegen-ts/generators` (server-side, framework-neutral):
78
78
  | `routesFile()` | `<Entity>.routes.ts` — Fastify CRUD routes on the cross-port REST contract. `routesFileHono()` is the Hono/Workers variant. A TPH `@discriminator` base mounts polymorphic `GET /<base>(+/:id)` plus a per-subtype CRUD set at `<basePath>/<discriminatorValue lowercased>` — create omits the discriminator (the URL names the subtype; the runtime injects it); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable via the runtime `discriminator` option. |
79
79
  | `barrel()` | `index.ts` re-exporting each `<Entity>.ts` (one-shot, not per-entity) |
80
80
  | `promptRender()` | `render<Name>()` per `template.prompt` |
81
- | `outputParser()` | `<Name>.output.ts` (`parse*` / `safeParse*`) per `template.output` |
81
+ | `outputParser()` | `<Name>.response.ts` (`parse*` / `safeParse*`) per **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: this tier is INBOUND; `template.output` is outbound only and emits nothing here). Siblings: `outputPrompt()` → `<Name>.responseFormat.ts` (the FR-010 output-format fragment, presentation via `@promptStyle`), `extractor()` → `<Name>.extractor.ts` (the tolerant `extract` mapper). |
82
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) |
83
83
 
84
84
  **Projections (read-only views).** For an `object.projection` (a read-only `source.rdb`
@@ -87,8 +87,8 @@ finder (no create/update/delete). The `CREATE VIEW` DDL is generated by `meta mi
87
87
  from the projection's `origin.*` children — `origin.passthrough` (a forwarded column),
88
88
  `origin.aggregate` (`@agg` `count`/`sum`/`avg`/`min`/`max`, plus the #195 `any`/`all`
89
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
90
+ `@distinct`/`@orderBy`; any aggregate row-scoped with `@filter`),
91
+ `origin.computed` (a row-level `@expr`), `origin.first` (one related row's
92
92
  column along `@via`/`@of`/`@orderBy`). An object-level `@filter` on the projection scopes
93
93
  the whole view's rows (#207 — lowers to the outer `WHERE`, the metadata-managed
94
94
  soft-delete/status view). **Never hand-write the view SQL** for a shape origins can