@metaobjectsdev/sdk 0.15.18 → 0.15.19-rc.1

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/LICENSE CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  Apache License
2
3
  Version 2.0, January 2004
3
4
  http://www.apache.org/licenses/
@@ -32,33 +33,36 @@
32
33
  not limited to compiled object code, generated documentation,
33
34
  and conversions to other media types.
34
35
 
35
- "Work" shall mean the work of authorship made available under
36
- the License, as indicated by a copyright notice that is included in
37
- or attached to the work (an example is provided in the Appendix below).
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
38
40
 
39
41
  "Derivative Works" shall mean any work, whether in Source or Object
40
42
  form, that is based on (or derived from) the Work and for which the
41
- editorial revisions, annotations, elaborations, or other transformations
43
+ editorial revisions, annotations, elaborations, or other modifications
42
44
  represent, as a whole, an original work of authorship. For the purposes
43
45
  of this License, Derivative Works shall not include works that remain
44
46
  separable from, or merely link (or bind by name) to the interfaces of,
45
47
  the Work and Derivative Works thereof.
46
48
 
47
- "Contribution" shall mean, as submitted to the Licensor for inclusion
48
- in the Work by the copyright owner or by an individual or Legal Entity
49
- authorized to submit on behalf of the copyright owner. For the purposes
50
- of this definition, "submitted" means any form of electronic, verbal,
51
- or written communication sent to the Licensor or its representatives,
52
- including but not limited to communication on electronic mailing lists,
53
- source code control systems, and issue tracking systems that are managed
54
- by, or on behalf of, the Licensor for the purpose of recording and
55
- discussing modifications to the Work, but excluding communication that
56
- is conspicuously marked or designated in writing by the copyright owner
57
- as "Not a Contribution."
58
-
59
- "Contributor" shall mean Licensor and any Legal Entity on behalf of
60
- whom a Contribution has been received by the Licensor and included
61
- within the Work.
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
62
66
 
63
67
  2. Grant of Copyright License. Subject to the terms and conditions of
64
68
  this License, each Contributor hereby grants to You a perpetual,
@@ -74,22 +78,22 @@
74
78
  use, offer to sell, sell, import, and otherwise transfer the Work,
75
79
  where such license applies only to those patent claims licensable
76
80
  by such Contributor that are necessarily infringed by their
77
- Contribution(s) alone or by the combination of their Contribution(s)
81
+ Contribution(s) alone or by combination of their Contribution(s)
78
82
  with the Work to which such Contribution(s) was submitted. If You
79
- institute patent litigation against any entity (including a cross-claim
80
- or counterclaim in a lawsuit) alleging that the Work or any
81
- Contribution embodied within the Work constitutes direct or contributory
82
- patent infringement, then any patent licenses granted to You under
83
- this License for that Work shall terminate as of the date such
84
- litigation is filed.
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
85
89
 
86
90
  4. Redistribution. You may reproduce and distribute copies of the
87
91
  Work or Derivative Works thereof in any medium, with or without
88
92
  modifications, and in Source or Object form, provided that You
89
93
  meet the following conditions:
90
94
 
91
- (a) You must give any other recipients of the Work or Derivative
92
- Works a copy of this License; and
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
93
97
 
94
98
  (b) You must cause any modified files to carry prominent notices
95
99
  stating that You changed the files; and
@@ -101,24 +105,28 @@
101
105
  the Derivative Works; and
102
106
 
103
107
  (d) If the Work includes a "NOTICE" text file as part of its
104
- distribution, You must include a readable copy of the
105
- attribution notices contained within such NOTICE file, in
106
- at least one of the following places: within a NOTICE text
107
- file distributed as part of the Derivative Works; within
108
- the Source form or documentation, if provided along with the
109
- Derivative Works; or, within a display generated by the
110
- Derivative Works, if and wherever such third-party notices
111
- normally appear. The contents of the NOTICE file are for
112
- informational purposes only and do not modify the License.
113
- You may add Your own attribution notices within Derivative
114
- Works that You distribute, alongside or in addition to the
115
- NOTICE text from the Work, provided that such additional
116
- attribution notices cannot be construed as modifying the License.
117
-
118
- You may add Your own license statement for Your modifications and
119
- may provide additional grant of rights to use, reproduce, modify,
120
- prepare derivative works of, distribute, and sublicense such modifications,
121
- as an additional requirement of this License.
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
122
130
 
123
131
  5. Submission of Contributions. Unless You explicitly state otherwise,
124
132
  any Contribution intentionally submitted for inclusion in the Work
@@ -137,10 +145,10 @@
137
145
  agreed to in writing, Licensor provides the Work (and each
138
146
  Contributor provides its Contributions) on an "AS IS" BASIS,
139
147
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
140
- implied, including, without limitation, any conditions of TITLE,
141
- NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
142
- PURPOSE. You are solely responsible for determining the
143
- appropriateness of using or reproducing the Work and assume any
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
144
152
  risks associated with Your exercise of permissions under this License.
145
153
 
146
154
  8. Limitation of Liability. In no event and under no legal theory,
@@ -148,20 +156,23 @@
148
156
  unless required by applicable law (such as deliberate and grossly
149
157
  negligent acts) or agreed to in writing, shall any Contributor be
150
158
  liable to You for damages, including any direct, indirect, special,
151
- incidental, or exemplary damages of any character arising as a
159
+ incidental, or consequential damages of any character arising as a
152
160
  result of this License or out of the use or inability to use the
153
161
  Work (including but not limited to damages for loss of goodwill,
154
- work stoppage, computer failure or malfunction, or all other
155
- commercial damages or losses), even if such Contributor has been
156
- advised of the possibility of such damages.
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
157
165
 
158
166
  9. Accepting Warranty or Additional Liability. While redistributing
159
167
  the Work or Derivative Works thereof, You may choose to offer,
160
168
  and charge a fee for, acceptance of support, warranty, indemnity,
161
169
  or other liability obligations and/or rights consistent with this
162
- License. However, in accepting such obligations, You may offer
163
- only obligations consistent to this License provided that the
164
- obligations are consistent with this License.
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
165
176
 
166
177
  END OF TERMS AND CONDITIONS
167
178
 
@@ -171,19 +182,21 @@
171
182
  boilerplate notice, with the fields enclosed by brackets "[]"
172
183
  replaced with your own identifying information. (Don't include
173
184
  the brackets!) The text should be enclosed in the appropriate
174
- comment syntax for the file format in question. Also add information
175
- on how to contact you electronically and/or by mail.
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
176
189
 
177
- Copyright 2026 MetaObjects Contributors
190
+ Copyright 2026 MetaObjects Contributors
178
191
 
179
- Licensed under the Apache License, Version 2.0 (the "License");
180
- you may not use this file except in compliance with the License.
181
- You may obtain a copy of the License at
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
182
195
 
183
- http://www.apache.org/licenses/LICENSE-2.0
196
+ http://www.apache.org/licenses/LICENSE-2.0
184
197
 
185
- Unless required by applicable law or agreed to in writing, software
186
- distributed under the License is distributed on an "AS IS" BASIS,
187
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
188
- See the License for the specific language governing permissions and
189
- limitations under the License.
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -189,6 +189,43 @@ emits two distinct finders (named by the FK field) automatically — no annotati
189
189
  reverse finder is a **codegen feature, not an attribute** (there is no reverse-nav `@`-attr
190
190
  to author). Advisory severity — a modernization opportunity, not a failing finding.
191
191
 
192
+ **New-vocabulary OPPORTUNITY (the inverse hunt — advisory).** The checks above audit
193
+ custom vocabulary the adopter *already* registered; this one hunts where the app
194
+ **should** register vocabulary but hand-coded the pattern instead. Smells
195
+ (grep-then-verify; require a *recurring, closed* set — never flag a one-off):
196
+ - **N parallel hand-written integration modules** sharing a payload shape and a config
197
+ pattern (a closed set of channels / providers / export targets, each a near-copy with
198
+ a different transport) → candidate for ONE project-registered subtype whose variants
199
+ sit behind a closed structural-variant discriminator (the `source.rdb` `@kind`
200
+ pattern), plus a small owned generator emitting the per-variant wiring.
201
+ - **An ad-hoc string discriminator steering code switches** — a string column whose
202
+ values select per-variant behavior/config scattered across the codebase. If the values
203
+ are just a closed symbol set, that is `field.enum` (`@values`), NOT new vocabulary. It
204
+ earns a subtype only when the discriminated concept **owns behavior or attributes of
205
+ its own** (ADR-0037 step 2a).
206
+ - **A provider-shaped pattern re-implemented per instance** — repeated registration /
207
+ config blocks restating a shape one declared node per instance could carry, with codegen
208
+ emitting the repetition.
209
+ - **A downstream integration modeled entirely in code** (an outbound webhook / notifier /
210
+ queue publisher / tool wiring beyond `template.toolcall`) where registered vocabulary +
211
+ a small generator would own the payload wiring, the names-only fail-closed config check,
212
+ and drift detection.
213
+
214
+ Run the **ADR-0037 ordered test** before proposing: derivable → derive; differs from an
215
+ existing subtype only by a property → an attribute; a plain validated value → an
216
+ attribute; one-off author-supplied properties → the `attr.properties` bag. Only a concept
217
+ with its own behavior/attributes earns a subtype, and transport/protocol is never the
218
+ subtype axis (keep the node protocol- and address-free; variants behind the discriminator).
219
+ **Converge before inventing** — check shipped vocabulary AND the planned/chartered names
220
+ (see CALIBRATION "planned, not shipped"); never claim a chartered name for a project-local
221
+ type. **Recommendation:** propose a project-registered provider (explicit loader wiring,
222
+ ADR-0023 — never a loosened `strict` free-ride), a `metadata_sketch` of the node, and the
223
+ owned generator that retires the duplication; a second independent consumer needing the
224
+ same concept is a consumer→core promotion candidate (ADR-0011 — file an upstream issue).
225
+ **Verdict: VOCAB CANDIDATE (advisory)** — a modeling opportunity, never a failing finding,
226
+ never a tier gate; bias to under-flagging (the >15% false-positive kill criterion applies
227
+ with full force). Full guidance: `docs/features/downstream-metadata-decisions.md`.
228
+
192
229
  ---
193
230
 
194
231
  ## Classification scheme (every surface; classify on codegen AND runtime)
@@ -200,7 +237,8 @@ to author). Advisory severity — a modernization opportunity, not a failing fin
200
237
  | **CODEGEN CANDIDATE (high)** | Standard CRUD/list/form over a modeled or modelable entity. | Author the view + generate; parity-gate. |
201
238
  | **CODEGEN CANDIDATE (partial)** | Generatable data layer, bespoke presentation. | Generate data layer; keep viz hand-written. |
202
239
  | **DYNAMIC-RUNTIME CANDIDATE** | Behavior that could be metadata-driven at runtime. | Assess runtime-metadata feasibility. |
203
- | **BESPOKE (keep)** | Genuine custom: aggregations, graph, SSE, auth, search, viz. | Leave hand-written — still import generated types. |
240
+ | **BESPOKE (keep)** | Genuine custom: irreducible SQL (recursive CTEs, window functions, set ops — NOT plain count/sum/avg rollups, which are `origin.aggregate` on a projection), graph, SSE, auth, search, viz. | Leave hand-written — still import generated types. |
241
+ | **VOCAB CANDIDATE (advisory)** | A recurring, closed hand-coded pattern (parallel integration modules / an ad-hoc string discriminator) that project-registered vocabulary — a custom subtype/attr via a provider — plus a small owned generator would own. | Propose the provider + `metadata_sketch`; apply the ADR-0037 ordered test; advisory only. |
204
242
 
205
243
  **Gold-standard exception.** A hand-written component that *derives* from generated metadata
206
244
  cannot drift — flag as good. A "bespoke" component hardcoding a shape metadata knows is a
@@ -220,6 +258,10 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
220
258
  5. **Runtime schema patching** (`ALTER TABLE … ADD COLUMN IF NOT EXISTS`, `_ensure_schema()`) — N schema owners.
221
259
  6. **N declarations of one shape** — same entity as Drizzle table + Zod schema + Pydantic model + hand dataclass; target is 1 + N generated.
222
260
  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.
261
+ 8. **Hand-written `CREATE VIEW` / read-only SQL standing in for a projection (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 `object.projection` + origins express this shape? It can when every output column 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`, optionally row-scoped with `@filter`), (c) a child collection → `origin.collection` (`@via`), or (d) a column borrowed via `extends` — and the joins follow declared relationships / `identity.reference` FKs.
262
+ - **Expressible → CODEGEN CANDIDATE (high):** 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. Flag in the finding that **`meta verify --db` cannot catch this**: an unmodeled DB view is *unmanaged*, so this audit is the only gate that sees it. Parity-gate: the generated view returns row-identical results before the hand-written SQL is deleted.
263
+ - **Not expressible → BESPOKE (keep), with a NAMED justification:** record the construct that makes it irreducible (recursive CTE, window function / `OVER`, `UNION` / `INTERSECT` / `EXCEPT`, `DISTINCT ON`, lateral join, non-aggregate expression column). "It's an aggregation" is NOT a justification — plain count/sum/avg/min/max rollups are `origin.aggregate`.
264
+ 9. **A closed variant-set hand-modeled per instance** — N sibling modules / classes / config blocks, one per channel / provider / target, sharing a payload + config shape and diverging only by transport. Grep for sibling-file families and switch-on-a-string dispatch; verify the set is closed and recurring (never a one-off). → axis I "New-vocabulary OPPORTUNITY" (VOCAB CANDIDATE, advisory).
223
265
 
224
266
  ---
225
267
 
@@ -238,6 +280,10 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
238
280
  own + customize / author a template-spec / fix upstream / stopgap.
239
281
  - **Verify the DB artifact, not just the types** — computed view columns may appear in the
240
282
  contract but be dropped from the view DDL; the contract may lie.
283
+ - **A hand-authored DB view is invisible to `meta verify --db`.** An unmodeled view is
284
+ *unmanaged* (informational only — never actionable drift, never auto-dropped), so a
285
+ hand-written view standing in for an expressible `object.projection` can never be
286
+ outsourced to the drift gate; hunt it here (drift signature 8, below).
241
287
  - **Version skew:** check *actually-resolved* package versions, not declared; consuming a fix
242
288
  requires a coordinated lockstep bump, not a source-file copy.
243
289
 
@@ -63,7 +63,13 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
63
63
  - **`source.rdb`** (`@table`, `@schema`) — hunt hard-coded physical table/schema names that
64
64
  diverge from the default naming the source models.
65
65
  - **`@kind` = `view` / `materializedView`** — hunt hand-written SQL views where an authored
66
- projection source (read-only `@kind`) belongs.
66
+ projection source (read-only `@kind`) belongs. Apply the **view-necessity test** (SKILL.md,
67
+ drift signature 8): a hand-written `CREATE VIEW` (or read-only SQL mirroring a read model) is a
68
+ CODEGEN CANDIDATE when its shape is expressible via `origin.passthrough` / `origin.aggregate` /
69
+ `origin.collection` + `extends` — convert it to an `object.projection` so `meta migrate` emits
70
+ the view DDL. It is BESPOKE-keep only when a NAMED irreducible construct (recursive CTE, window
71
+ function, set op) blocks projection authoring. `meta verify --db` can't see an unmodeled view,
72
+ so this is audit-only.
67
73
  - **`@kind` = `storedProc` / `tableFunction`** (`@parameterRef`) — hunt hand-called procs /
68
74
  table functions that a modeled callable source with `@parameterRef` already describes.
69
75
  - **`@role` = `primary`** (multi-source write-through) — hunt manual CQRS / write-through
@@ -157,6 +163,12 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
157
163
  - **`attr.class`** — binding facet (`field.class` binding); hunt hand-wired type-binding
158
164
  facets. (`attr.base`, `attr.string`, `attr.int`, `attr.long`, `attr.double`, `attr.boolean`
159
165
  are the value-type primitives behind typed attrs — not direct audit targets.)
166
+ - **Custom-provider extension point** (`attr.properties` is the one-off escape hatch;
167
+ `template.toolcall` is the historical register→extend precedent) — hunt a *recurring,
168
+ closed* variant set hand-coded as parallel modules (channels / providers / export targets)
169
+ that a project-registered subtype with a closed variant discriminator + a small owned
170
+ generator would own. Apply the ADR-0037 ordered test before proposing; advisory (VOCAB
171
+ CANDIDATE); see SKILL.md axis I "New-vocabulary OPPORTUNITY".
160
172
 
161
173
  ## Common documentation attrs (any node)
162
174
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: metaobjects-authoring
3
- description: Use when authoring or modifying MetaObjects metadata — fields, entities, relationships, sources, enums, abstracts/inheritance — in YAML or canonical JSON.
3
+ description: Use when authoring or modifying MetaObjects metadata — fields, entities, relationships, sources, enums, abstracts/inheritance — in YAML or canonical JSON; includes deciding whether and how to register custom vocabulary (new subtypes or attributes via a provider).
4
4
  ---
5
5
 
6
6
  # Authoring MetaObjects metadata
@@ -355,17 +355,39 @@ metamodel attribute*: you declare the FK once via `identity.reference`, and the
355
355
  finders fall out of codegen. So never hand-roll a `findByParentId` / `WHERE fk = ?` helper —
356
356
  consume the generated finder.
357
357
 
358
- **Extending the metamodel (custom providers):** the same ordered procedure above
359
- governs new vocabulary you register — apply it mechanically before registering
360
- anything. A would-be subtype that differs from an existing one only by a *property*
361
- is an **attribute**, not a subtype (a "short string" isn't a new field subtype —
362
- that is `@maxLength`); a plain string that merely needs validating is a **validation
363
- attribute**, not a subtype (its native type is still `string`, and there's no
364
- behavior to own); a concept with its own native type or behavior is a **subtype**,
365
- and structural variants *within* such a subtype are `@kind`. Every new first-class
366
- element also requires a registered provider + a `registry-conformance` fixture
367
- (ADR-0023 strict provenance), and closed enums (including any `@kind` value-set)
368
- carry `allowedValues` in the gate (ADR-0036). ADR-0037 is the authority.
358
+ ### Extending the metamodel custom providers, and the downstream lifecycle
359
+
360
+ The same ordered procedure above governs new vocabulary you register apply it
361
+ mechanically before registering anything. A would-be subtype that differs from an
362
+ existing one only by a *property* is an **attribute**, not a subtype (a "short
363
+ string" isn't a new field subtype that is `@maxLength`); a plain string that merely
364
+ needs validating is a **validation attribute**, not a subtype (its native type is
365
+ still `string`, and there's no behavior to own); a concept with its own native type
366
+ or behavior is a **subtype**, and structural variants *within* such a subtype are
367
+ `@kind`. Every new first-class element also requires a registered provider + a
368
+ `registry-conformance` fixture (ADR-0023 strict provenance), and closed enums
369
+ (including any `@kind` value-set) carry `allowedValues` in the gate (ADR-0036).
370
+ ADR-0037 is the authority.
371
+
372
+ **Converge before inventing.** Search the shipped vocabulary first (`meta types
373
+ <term>`) and check the roadmap — if core already models (or plans) the concept, model
374
+ yours in a fold-in-friendly shape. The type names `api`, `operation`, `surface`, and
375
+ `binding` are chartered for planned core vocabulary — never claim one for a
376
+ project-local type (a later core release would force a breaking rename).
377
+
378
+ **Design rules for downstream vocabulary that ages well.** Keep the node protocol-
379
+ and address-free — the subtype names the *concept*; transport/protocol is a closed
380
+ `@kind` variant *within* the subtype (as `source.rdb` puts table/view behind
381
+ `@kind`), never the subtype axis, and endpoints/addresses never enter metadata.
382
+ Declare config as the *names* of required keys (values stay in env/config) and
383
+ generate a fail-closed check. Reference typed payloads instead of inlining shapes.
384
+
385
+ **Lifecycle.** Register with explicit provider wiring — never loosen `strict` to
386
+ free-ride ad-hoc attrs. When core later ships the concept your provider shrinks from
387
+ `register` to `extend` (the `template.toolcall` precedent). A second independent
388
+ consumer wanting the same concept is a consumer→core promotion candidate (ADR-0011) —
389
+ open an upstream issue rather than adding core vocabulary yourself. Full guidance:
390
+ `docs/features/downstream-metadata-decisions.md`.
369
391
 
370
392
  ### Currency
371
393
 
@@ -599,7 +621,12 @@ identity passes through via `extends` (`identity.primary: { name: id, extends:
599
621
  the exposure (fail-closed). Give it a read-only `source.rdb` `@kind: view`
600
622
  child (`source.rdb: { kind: view, table: v_author }`) — codegen keys projection
601
623
  detection + view DDL off that read-only source, so without it `meta gen` emits
602
- nothing for the projection.
624
+ nothing for the projection. **The `CREATE VIEW` body is generated from those
625
+ `origin.*` children by the Node `meta migrate` — never hand-author view SQL for a
626
+ shape origins can express** (an unmodeled view is *unmanaged*, so `meta verify --db`
627
+ can't even catch the drift). Hand-write a view only when a construct origins can't
628
+ express (recursive CTE, window function, set operation) blocks projection authoring,
629
+ and keep that DDL in a hand-edited migration file.
603
630
 
604
631
  **A `passthrough` field must match its `@from` source's type.** A passthrough
605
632
  forwards the source value unchanged, so the projection field's `field.<subType>`
@@ -45,8 +45,12 @@ Every emitted file carries a `@generated` header. This is load-bearing:
45
45
 
46
46
  Practical rule: **pattern-derivable-from-metadata = regenerate; business logic =
47
47
  hand-write in a non-generated file.** FK columns, CRUD, validator chains,
48
- type-safe finders, `relations()` blocks — all derived, never hand-coded. Custom
49
- SQL views, regex from outside metadata, and domain logic are what you hand-write.
48
+ type-safe finders, `relations()` blocks — all derived, never hand-coded. What you
49
+ hand-write is what metadata genuinely can't express: regex from outside metadata,
50
+ domain logic, and *irreducible* SQL views (recursive CTEs, window functions, set
51
+ ops). Most views are NOT irreducible — model them as an `object.projection` and the
52
+ view DDL is generated (see the projection bullet below); a hand-written view for a
53
+ shape origins can express is drift the drift gate can't even see.
50
54
 
51
55
  ## Selecting generators by stable name
52
56
 
@@ -86,6 +90,14 @@ the data access too.
86
90
  `meta migrate` its DB view), and you **call that generated query from your
87
91
  route**. Declaring the projection is only half the win — *consuming* its
88
92
  generated query is the other half.
93
+ - **Never hand-author the view SQL for a shape origins can express.** The
94
+ `CREATE VIEW` body is emitted by the Node `meta migrate` from the projection's
95
+ `origin.*` children — hand-writing it is a second source of truth that drifts
96
+ silently, because an unmodeled DB view is *unmanaged*: `meta verify --db` never
97
+ flags it. Hand-written view SQL is legitimate only when a named construct
98
+ origins can't express (recursive CTE, window function, set op) blocks
99
+ projection authoring — then keep that DDL in a hand-edited migration file and
100
+ justify it in review.
89
101
 
90
102
  `meta gen --list` prints every generator by stable name; the `generators` array in
91
103
  `metaobjects.config.ts` is where you opt each one in or out.
@@ -89,6 +89,13 @@ first group together:
89
89
  | `SpringOutputParserGenerator` | the `template.output` parser-on-receipt (see the prompts reference) |
90
90
  | `SpringFilterAllowlistGenerator` | per-entity filter allowlist |
91
91
 
92
+ **Projections (read-only views).** An `object.projection` (read-only `source.rdb`
93
+ `@kind: view` child) is served read-only through OMDB at the ObjectManager layer
94
+ (mutating ops throw); no controller is generated (controllers cover writable entities
95
+ only). Its `CREATE VIEW` DDL is emitted by the Node `meta migrate` from the
96
+ projection's `origin.*` children — never hand-author the view SQL for a shape origins
97
+ can express (an unmodeled view is unmanaged and drifts silently).
98
+
92
99
  Metadata lives under `src/main/metaobjects/` in the same canonical JSON the other
93
100
  ports read — fused-key form, `source.rdb` + `@table`, `@column` for a renamed
94
101
  physical column.
@@ -106,6 +106,13 @@ All live in `metaobjects-codegen-kotlin` under
106
106
  | `KotlinStoredProcGenerator` | stored-procedure call wrappers for `source.rdb` `@kind="storedProc"` |
107
107
  | `KotlinFilterAllowlistGenerator` | per-entity filter allowlist |
108
108
 
109
+ **Projections (read-only views).** For an `object.projection` (read-only `source.rdb`
110
+ `@kind: view` child), `KotlinExposedTableGenerator` emits a read-only Exposed `Table`
111
+ wrapper (same column mapping, no write path). The `CREATE VIEW` DDL is emitted by the
112
+ Node `meta migrate` from the projection's `origin.*` children — never hand-author the
113
+ view SQL for a shape origins can express (an unmodeled view is unmanaged and drifts
114
+ silently).
115
+
109
116
  Metadata lives under `src/main/metaobjects/` in the same canonical JSON the other
110
117
  ports read — fused-key form, `source.rdb` + `@table`, `@column` for a renamed
111
118
  physical column.
@@ -80,6 +80,14 @@ From `@metaobjectsdev/codegen-ts/generators` (server-side, framework-neutral):
80
80
  | `promptRender()` | `render<Name>()` per `template.prompt` |
81
81
  | `outputParser()` | `<Name>.output.ts` (`parse*` / `safeParse*`) per `template.output` |
82
82
 
83
+ **Projections (read-only views).** For an `object.projection` (a read-only `source.rdb`
84
+ `@kind: view` child), `entityFile()` emits a `pgView(...)` + read-only Zod + a read-only
85
+ finder (no create/update/delete). The `CREATE VIEW` DDL is generated by `meta migrate`
86
+ from the projection's `origin.*` children — `origin.passthrough` (a forwarded column),
87
+ `origin.aggregate` (a `count`/`sum`/`avg`/`min`/`max`, optionally row-scoped with
88
+ `@filter`), `origin.collection` (a nested array). **Never hand-write the view SQL** for a
89
+ shape origins can express; an unmodeled view is unmanaged and drifts silently.
90
+
83
91
  ## Discriminator inheritance (TPH)
84
92
 
85
93
  The TS reference implementation fully supports **table-per-hierarchy (TPH)
@@ -17,7 +17,12 @@ Drift is any place where a derived artifact has fallen out of sync with the
17
17
  metadata that should define it. The ones a developer must actively guard:
18
18
 
19
19
  - **DB-vs-metadata** — the live database schema has diverged from the metadata
20
- (a column the metadata no longer declares, a missing index, a type mismatch).
20
+ (a column the metadata no longer declares, a missing index, a type mismatch). A
21
+ **modeled projection's** view body is compared too — a changed `CREATE VIEW` emits
22
+ a `replace-view`. But a **hand-authored, unmodeled view is *unmanaged*** (reported
23
+ as informational, never failed, never dropped), so a hand-written view standing in
24
+ for an expressible `object.projection` is the one drift class `verify --db` can't
25
+ catch — the `metaobjects-audit` skill is the only gate that sees it.
21
26
  - **Generated-vs-metadata (codegen)** — committed generated code no longer matches
22
27
  what the current metadata would emit (someone edited a `@generated` file, or
23
28
  forgot to regenerate after changing metadata).
@@ -127,6 +127,35 @@ A non-unique recency index is `index.lookup`:
127
127
  "@orders": ["asc", "desc"], "@where": "archived_at IS NULL" } }
128
128
  ```
129
129
 
130
+ The `@where` / `@using` / `@expr` / `@orders` attributes are **index** physical
131
+ escapes on `identity.secondary` / `index.lookup` — they are NOT a raw-SQL escape
132
+ hatch for views. There is no attribute that injects hand-written SQL into a
133
+ projection view body (by design — see below).
134
+
135
+ ## Projection views (generated view DDL)
136
+
137
+ A read-only projection (`object.projection` with a `source.rdb` `@kind: view` child)
138
+ does **not** get a hand-written `CREATE VIEW`. `meta migrate` synthesizes the view
139
+ DDL from the projection's `origin.*` children — `passthrough` columns, `aggregate`
140
+ rollups (`count`/`sum`/`avg`/`min`/`max`), and `collection` joins — through the one
141
+ canonical view-SQL emitter shared with drift detection.
142
+
143
+ - Change the projection (add a passthrough, change an aggregate) → `meta migrate`
144
+ emits a `create-view` / `replace-view`; a change to a source-table column the view
145
+ selects auto-recreates the dependent view.
146
+ - `meta verify --db` **body-compares** a modeled view — a live `CREATE VIEW` that no
147
+ longer matches the projection is `replace-view` drift.
148
+ - A **hand-authored, unmodeled** DB view is *unmanaged* — never diffed, never
149
+ dropped, never reported as actionable drift. That is the blind spot: a hand-written
150
+ view that could have been a projection drifts silently, so it is caught by the
151
+ `metaobjects-audit` skill, not here.
152
+
153
+ **Do not hand-author view SQL for a shape origins can express** — model it as a
154
+ projection so the view DDL is generated and drift-checked. The only case for
155
+ hand-written view DDL is a genuinely irreducible view (recursive CTE, window
156
+ function, set operation) that origins can't express; carry that in a hand-edited
157
+ migration file.
158
+
130
159
  ## Adopting an existing database (non-destructive)
131
160
 
132
161
  `meta verify --db` / `meta migrate` can reach **zero drift** against a hand-built
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/sdk",
3
- "version": "0.15.18",
3
+ "version": "0.15.19-rc.1",
4
4
  "description": "Workspace helpers and agent-docs utilities for MetaObjects projects.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "access": "public"
57
57
  },
58
58
  "dependencies": {
59
- "@metaobjectsdev/metadata": "0.15.18",
59
+ "@metaobjectsdev/metadata": "0.15.19-rc.1",
60
60
  "zod": "^3.23.0"
61
61
  },
62
62
  "devDependencies": {