@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.
- package/agent-context/skills/metaobjects-audit/SKILL.md +20 -7
- package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +64 -18
- package/agent-context/skills/metaobjects-audit/references/csharp.md +2 -1
- package/agent-context/skills/metaobjects-audit/references/java.md +3 -2
- package/agent-context/skills/metaobjects-audit/references/kotlin.md +2 -1
- package/agent-context/skills/metaobjects-audit/references/requirements.md +3 -4
- package/agent-context/skills/metaobjects-audit/references/typescript.md +1 -1
- package/agent-context/skills/metaobjects-authoring/SKILL.md +28 -3
- package/agent-context/skills/metaobjects-authoring/references/requirements.md +49 -23
- package/agent-context/skills/metaobjects-codegen/SKILL.md +4 -3
- package/agent-context/skills/metaobjects-codegen/references/csharp.md +2 -2
- package/agent-context/skills/metaobjects-codegen/references/java.md +3 -3
- package/agent-context/skills/metaobjects-codegen/references/kotlin.md +4 -4
- package/agent-context/skills/metaobjects-codegen/references/python.md +2 -2
- package/agent-context/skills/metaobjects-codegen/references/typescript.md +3 -3
- package/agent-context/skills/metaobjects-fit-assessment/SKILL.md +4 -4
- package/agent-context/skills/metaobjects-prompts/SKILL.md +53 -32
- package/agent-context/skills/metaobjects-prompts/references/csharp.md +26 -21
- package/agent-context/skills/metaobjects-prompts/references/java.md +24 -18
- package/agent-context/skills/metaobjects-prompts/references/kotlin.md +25 -20
- package/agent-context/skills/metaobjects-prompts/references/python.md +68 -26
- package/agent-context/skills/metaobjects-prompts/references/typescript.md +29 -23
- package/agent-context/skills/metaobjects-verify/references/migration.md +1 -1
- package/agent-context/skills/metaobjects-verify/references/requirements.md +36 -22
- package/agent-context/templates/always-on.md.mustache +3 -1
- 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 +24 -2
- package/dist/agent-docs/body.js.map +1 -1
- package/dist/collection.d.ts +71 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +121 -0
- package/dist/collection.js.map +1 -0
- package/dist/config.d.ts +51 -21
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +76 -10
- package/dist/config.js.map +1 -1
- package/dist/discovery.d.ts +43 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +100 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -4
- package/dist/index.js.map +1 -1
- package/dist/memory.d.ts +42 -19
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +38 -95
- package/dist/memory.js.map +1 -1
- package/dist/metadata-files.d.ts +67 -0
- package/dist/metadata-files.d.ts.map +1 -0
- package/dist/metadata-files.js +155 -0
- package/dist/metadata-files.js.map +1 -0
- package/dist/records/any.d.ts +8 -8
- package/dist/records/decision.d.ts +2 -2
- package/dist/records/glossary.d.ts +4 -4
- package/dist/records/principle.d.ts +2 -2
- package/dist/scope.d.ts +16 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +75 -0
- package/dist/scope.js.map +1 -0
- package/dist/sources.d.ts +86 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +130 -0
- package/dist/sources.js.map +1 -0
- package/package.json +2 -2
- package/src/agent-docs/body.ts +24 -2
- package/src/collection.ts +183 -0
- package/src/config.ts +80 -12
- package/src/discovery.ts +110 -0
- package/src/index.ts +26 -9
- package/src/memory.ts +65 -99
- package/src/metadata-files.ts +158 -0
- package/src/scope.ts +95 -0
- 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`, (
|
|
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` / `
|
|
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
|
|
341
|
-
generated `parse*` / `safeParse*` /
|
|
342
|
-
(Java's generated `<Name>Parser` owns
|
|
343
|
-
in a **non-generated** file where a
|
|
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.
|
|
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),
|
|
54
|
-
(hand write-guards
|
|
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` /
|
|
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
|
-
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- **`template.
|
|
179
|
-
`@
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
226
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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.
|
|
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` / `
|
|
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
|
|
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
|
-
|
|
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.
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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`
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
- `
|
|
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
|
|
86
|
-
|
|
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
|
-
|
|
125
|
+
counterexample: "A scene that advances on a clock rather than on the story"
|
|
100
126
|
children:
|
|
101
127
|
- requirement.functional:
|
|
102
|
-
name:
|
|
128
|
+
name: BeatProgression
|
|
103
129
|
level: 4
|
|
104
|
-
status:
|
|
105
|
-
statement: "
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
implementedBy: ["game::turn::
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
entity never emits
|
|
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.
|
|
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.
|
|
49
|
-
| `output-parser` / `extractor` / `output-prompt` / `render-helper` | the `template.
|
|
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
|
|
105
|
-
| `SpringOutputPromptGenerator` | the FR-010 output-format prompt fragment for a `template.
|
|
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`),
|
|
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
|
|
118
|
-
| `KotlinExtractorGenerator` | the FR-010 tolerant `extract` mapper for a `template.
|
|
119
|
-
| `KotlinOutputPromptGenerator` | the FR-010 output-format prompt fragment for a `template.
|
|
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.
|
|
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.
|
|
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` / `
|
|
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>.
|
|
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`),
|
|
91
|
-
|
|
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
|