@metaobjectsdev/metadata 0.14.2 → 0.15.0-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.
Files changed (142) hide show
  1. package/dist/attr-schema-validate.js +22 -1
  2. package/dist/attr-schema-validate.js.map +1 -1
  3. package/dist/core/attr/meta-attr.d.ts.map +1 -1
  4. package/dist/core/attr/meta-attr.js +2 -0
  5. package/dist/core/attr/meta-attr.js.map +1 -1
  6. package/dist/core/field/field-constants.d.ts +26 -1
  7. package/dist/core/field/field-constants.d.ts.map +1 -1
  8. package/dist/core/field/field-constants.js +29 -0
  9. package/dist/core/field/field-constants.js.map +1 -1
  10. package/dist/core/field/field-definition.embedded.d.ts.map +1 -1
  11. package/dist/core/field/field-definition.embedded.js +28 -0
  12. package/dist/core/field/field-definition.embedded.js.map +1 -1
  13. package/dist/core/field/meta-field.d.ts +12 -3
  14. package/dist/core/field/meta-field.d.ts.map +1 -1
  15. package/dist/core/field/meta-field.js +33 -15
  16. package/dist/core/field/meta-field.js.map +1 -1
  17. package/dist/core/field/validate-field-readonly.d.ts.map +1 -1
  18. package/dist/core/field/validate-field-readonly.js +15 -3
  19. package/dist/core/field/validate-field-readonly.js.map +1 -1
  20. package/dist/core/identity/meta-identity.d.ts +2 -0
  21. package/dist/core/identity/meta-identity.d.ts.map +1 -1
  22. package/dist/core/identity/meta-identity.js +16 -8
  23. package/dist/core/identity/meta-identity.js.map +1 -1
  24. package/dist/core/identity/validate-identity-passthrough.d.ts.map +1 -1
  25. package/dist/core/identity/validate-identity-passthrough.js +9 -1
  26. package/dist/core/identity/validate-identity-passthrough.js.map +1 -1
  27. package/dist/core/object/meta-object.d.ts.map +1 -1
  28. package/dist/core/object/meta-object.js +15 -5
  29. package/dist/core/object/meta-object.js.map +1 -1
  30. package/dist/core/object/validate-discriminator.d.ts.map +1 -1
  31. package/dist/core/object/validate-discriminator.js +17 -2
  32. package/dist/core/object/validate-discriminator.js.map +1 -1
  33. package/dist/core/query/query-constants.d.ts.map +1 -1
  34. package/dist/core/query/query-constants.js +7 -1
  35. package/dist/core/query/query-constants.js.map +1 -1
  36. package/dist/core/relationship/meta-relationship.d.ts.map +1 -1
  37. package/dist/core/relationship/meta-relationship.js +9 -7
  38. package/dist/core/relationship/meta-relationship.js.map +1 -1
  39. package/dist/core/validator/meta-validator.d.ts.map +1 -1
  40. package/dist/core/validator/meta-validator.js +6 -3
  41. package/dist/core/validator/meta-validator.js.map +1 -1
  42. package/dist/core-types.js +2 -2
  43. package/dist/core-types.js.map +1 -1
  44. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  45. package/dist/loader/meta-data-loader.js +4 -0
  46. package/dist/loader/meta-data-loader.js.map +1 -1
  47. package/dist/loader/validation-passes.d.ts.map +1 -1
  48. package/dist/loader/validation-passes.js +97 -39
  49. package/dist/loader/validation-passes.js.map +1 -1
  50. package/dist/loader/validation-registry.d.ts.map +1 -1
  51. package/dist/loader/validation-registry.js +7 -2
  52. package/dist/loader/validation-registry.js.map +1 -1
  53. package/dist/naming.d.ts.map +1 -1
  54. package/dist/naming.js +6 -3
  55. package/dist/naming.js.map +1 -1
  56. package/dist/object-serializer.d.ts.map +1 -1
  57. package/dist/object-serializer.js +6 -2
  58. package/dist/object-serializer.js.map +1 -1
  59. package/dist/parser-core.js +5 -0
  60. package/dist/parser-core.js.map +1 -1
  61. package/dist/persistence/db/db-constants.d.ts +20 -9
  62. package/dist/persistence/db/db-constants.d.ts.map +1 -1
  63. package/dist/persistence/db/db-constants.js +20 -15
  64. package/dist/persistence/db/db-constants.js.map +1 -1
  65. package/dist/persistence/db/db-definition.embedded.d.ts.map +1 -1
  66. package/dist/persistence/db/db-definition.embedded.js +19 -1
  67. package/dist/persistence/db/db-definition.embedded.js.map +1 -1
  68. package/dist/persistence/origin/meta-origin.d.ts.map +1 -1
  69. package/dist/persistence/origin/meta-origin.js +6 -0
  70. package/dist/persistence/origin/meta-origin.js.map +1 -1
  71. package/dist/persistence/source/meta-source.d.ts.map +1 -1
  72. package/dist/persistence/source/meta-source.js +11 -5
  73. package/dist/persistence/source/meta-source.js.map +1 -1
  74. package/dist/persistence/source/validate-source-parameter-ref.d.ts.map +1 -1
  75. package/dist/persistence/source/validate-source-parameter-ref.js +14 -5
  76. package/dist/persistence/source/validate-source-parameter-ref.js.map +1 -1
  77. package/dist/persistence/source/validate-source-physical-names.d.ts.map +1 -1
  78. package/dist/persistence/source/validate-source-physical-names.js +9 -1
  79. package/dist/persistence/source/validate-source-physical-names.js.map +1 -1
  80. package/dist/persistence/source/validate-source-roles.d.ts.map +1 -1
  81. package/dist/persistence/source/validate-source-roles.js +5 -1
  82. package/dist/persistence/source/validate-source-roles.js.map +1 -1
  83. package/dist/presentation/layout/meta-layout.d.ts.map +1 -1
  84. package/dist/presentation/layout/meta-layout.js +12 -6
  85. package/dist/presentation/layout/meta-layout.js.map +1 -1
  86. package/dist/registry-manifest.d.ts +12 -2
  87. package/dist/registry-manifest.d.ts.map +1 -1
  88. package/dist/registry-manifest.js +20 -8
  89. package/dist/registry-manifest.js.map +1 -1
  90. package/dist/serializer-json.d.ts.map +1 -1
  91. package/dist/serializer-json.js +8 -1
  92. package/dist/serializer-json.js.map +1 -1
  93. package/dist/shared/meta-data.d.ts +14 -0
  94. package/dist/shared/meta-data.d.ts.map +1 -1
  95. package/dist/shared/meta-data.js +30 -0
  96. package/dist/shared/meta-data.js.map +1 -1
  97. package/dist/shared/meta-root.d.ts.map +1 -1
  98. package/dist/shared/meta-root.js +8 -2
  99. package/dist/shared/meta-root.js.map +1 -1
  100. package/dist/subtype-rules.js +4 -3
  101. package/dist/subtype-rules.js.map +1 -1
  102. package/dist/super-resolve.d.ts.map +1 -1
  103. package/dist/super-resolve.js +3 -0
  104. package/dist/super-resolve.js.map +1 -1
  105. package/dist/validate-max-occurs.js +5 -0
  106. package/dist/validate-max-occurs.js.map +1 -1
  107. package/package.json +1 -1
  108. package/src/attr-schema-validate.ts +24 -1
  109. package/src/core/attr/meta-attr.ts +2 -0
  110. package/src/core/field/field-constants.ts +34 -0
  111. package/src/core/field/field-definition.embedded.ts +28 -0
  112. package/src/core/field/meta-field.ts +30 -12
  113. package/src/core/field/validate-field-readonly.ts +15 -3
  114. package/src/core/identity/meta-identity.ts +16 -8
  115. package/src/core/identity/validate-identity-passthrough.ts +9 -1
  116. package/src/core/object/meta-object.ts +5 -0
  117. package/src/core/object/validate-discriminator.ts +17 -2
  118. package/src/core/query/query-constants.ts +7 -1
  119. package/src/core/relationship/meta-relationship.ts +9 -7
  120. package/src/core/validator/meta-validator.ts +6 -3
  121. package/src/core-types.ts +2 -2
  122. package/src/loader/meta-data-loader.ts +4 -0
  123. package/src/loader/validation-passes.ts +97 -39
  124. package/src/loader/validation-registry.ts +7 -2
  125. package/src/naming.ts +6 -3
  126. package/src/object-serializer.ts +6 -2
  127. package/src/parser-core.ts +5 -0
  128. package/src/persistence/db/db-constants.ts +20 -15
  129. package/src/persistence/db/db-definition.embedded.ts +19 -1
  130. package/src/persistence/origin/meta-origin.ts +6 -0
  131. package/src/persistence/source/meta-source.ts +11 -5
  132. package/src/persistence/source/validate-source-parameter-ref.ts +14 -5
  133. package/src/persistence/source/validate-source-physical-names.ts +9 -1
  134. package/src/persistence/source/validate-source-roles.ts +5 -1
  135. package/src/presentation/layout/meta-layout.ts +12 -6
  136. package/src/registry-manifest.ts +28 -8
  137. package/src/serializer-json.ts +8 -1
  138. package/src/shared/meta-data.ts +30 -0
  139. package/src/shared/meta-root.ts +4 -0
  140. package/src/subtype-rules.ts +4 -3
  141. package/src/super-resolve.ts +3 -0
  142. package/src/validate-max-occurs.ts +5 -0
@@ -107,7 +107,8 @@ import {
107
107
 
108
108
  export function validateDataGridSortFields(root: MetaData): ParseError[] {
109
109
  const errors: ParseError[] = [];
110
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
110
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
111
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
111
112
  // Use children() so inherited fields (via extends:/super:) are
112
113
  // visible when validating @defaultSortField references.
113
114
  const effective = obj.children();
@@ -115,7 +116,8 @@ export function validateDataGridSortFields(root: MetaData): ParseError[] {
115
116
  effective.filter((c) => c.type === TYPE_FIELD).map((f) => f.name),
116
117
  );
117
118
  for (const layout of effective.filter((c) => c.type === TYPE_LAYOUT && c.subType === LAYOUT_SUBTYPE_DATA_GRID)) {
118
- const sortField = layout.ownAttr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
119
+ // ADR-0039: resolving — a layout may inherit its grid attrs via extends.
120
+ const sortField = layout.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
119
121
  if (typeof sortField === "string" && !fieldNames.has(sortField)) {
120
122
  errors.push(
121
123
  new ParseError(
@@ -144,6 +146,8 @@ export function validateDataGridSortFields(root: MetaData): ParseError[] {
144
146
  /** Recursively collect all template.* nodes anywhere in the metadata tree. */
145
147
  function allTemplates(node: MetaData): MetaData[] {
146
148
  const out: MetaData[] = [];
149
+ // ADR-0039: own — structural walk over the PHYSICAL declaration tree collecting
150
+ // every declared template.* node (each visited once at its declaration site).
147
151
  for (const c of node.ownChildren()) {
148
152
  if (c.type === TYPE_TEMPLATE) out.push(c);
149
153
  out.push(...allTemplates(c));
@@ -161,9 +165,11 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
161
165
  // body). The closed-enum membership of @kind is handled by validateAttrSchema
162
166
  // (allowedValues) — here we only enforce the conditional ref presence.
163
167
  if (tmpl.subType === TEMPLATE_SUBTYPE_OUTPUT) {
164
- const kind = tmpl.ownAttr(TEMPLATE_ATTR_KIND);
168
+ // ADR-0039: resolving — a template may inherit its refs/attrs via extends.
169
+ const kind = tmpl.attr(TEMPLATE_ATTR_KIND);
165
170
  if (kind === TEMPLATE_KIND_EMAIL) {
166
- if (typeof tmpl.ownAttr(TEMPLATE_ATTR_SUBJECT_REF) !== "string") {
171
+ // ADR-0039: resolving a template may inherit @subjectRef via extends.
172
+ if (typeof tmpl.attr(TEMPLATE_ATTR_SUBJECT_REF) !== "string") {
167
173
  errors.push(
168
174
  new ParseError(
169
175
  `template "${tmpl.name}" @kind "email" requires @subjectRef`,
@@ -171,7 +177,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
171
177
  ),
172
178
  );
173
179
  }
174
- if (typeof tmpl.ownAttr(TEMPLATE_ATTR_HTML_BODY_REF) !== "string") {
180
+ // ADR-0039: resolving a template may inherit @htmlBodyRef via extends.
181
+ if (typeof tmpl.attr(TEMPLATE_ATTR_HTML_BODY_REF) !== "string") {
175
182
  errors.push(
176
183
  new ParseError(
177
184
  `template "${tmpl.name}" @kind "email" requires @htmlBodyRef`,
@@ -183,7 +190,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
183
190
  // @kind absent or "document" → require @textRef. (An out-of-enum @kind is
184
191
  // separately reported by validateAttrSchema; we still require @textRef so a
185
192
  // document is never bodyless.)
186
- if (typeof tmpl.ownAttr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
193
+ // ADR-0039: resolving a template may inherit @textRef via extends.
194
+ if (typeof tmpl.attr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
187
195
  errors.push(
188
196
  new ParseError(
189
197
  `template "${tmpl.name}" @kind "document" requires @textRef`,
@@ -194,7 +202,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
194
202
  }
195
203
  } else if (tmpl.subType === TEMPLATE_SUBTYPE_PROMPT) {
196
204
  // template.prompt always carries a renderable body via @textRef.
197
- if (typeof tmpl.ownAttr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
205
+ // ADR-0039: resolving a template may inherit @textRef via extends.
206
+ if (typeof tmpl.attr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
198
207
  errors.push(
199
208
  new ParseError(
200
209
  `template "${tmpl.name}" requires @textRef`,
@@ -204,9 +213,11 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
204
213
  }
205
214
  }
206
215
 
207
- const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
216
+ // ADR-0039: resolving — a template may inherit @payloadRef via extends.
217
+ const payloadRef = tmpl.attr(TEMPLATE_ATTR_PAYLOAD_REF);
208
218
  if (typeof payloadRef !== "string") continue; // absence handled by the required-attr schema check
209
- const payload = root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
219
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
220
+ const payload = root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
210
221
  if (!payload || payload.subType !== OBJECT_SUBTYPE_VALUE) {
211
222
  // FR5d — @payloadRef is a reference; emit format=resolved with
212
223
  // referrer=template FQN, target=the unresolved payloadRef string.
@@ -221,9 +232,11 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
221
232
  );
222
233
  continue;
223
234
  }
224
- const responseRef = tmpl.ownAttr(TEMPLATE_ATTR_RESPONSE_REF);
235
+ // ADR-0039: resolving — a template may inherit @responseRef via extends.
236
+ const responseRef = tmpl.attr(TEMPLATE_ATTR_RESPONSE_REF);
225
237
  if (typeof responseRef === "string") {
226
- const resVo = root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, responseRef));
238
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
239
+ const resVo = root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, responseRef));
227
240
  if (!resVo || resVo.subType !== OBJECT_SUBTYPE_VALUE) {
228
241
  errors.push(
229
242
  new ParseError(
@@ -236,7 +249,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
236
249
  const fieldNames = new Set(
237
250
  payload.children().filter((c) => c.type === TYPE_FIELD).map((f) => f.name),
238
251
  );
239
- const slots = tmpl.ownAttr(TEMPLATE_ATTR_REQUIRED_SLOTS);
252
+ // ADR-0039: resolving — a template may inherit @requiredSlots via extends.
253
+ const slots = tmpl.attr(TEMPLATE_ATTR_REQUIRED_SLOTS);
240
254
  const slotList = Array.isArray(slots) ? slots : typeof slots === "string" ? [slots] : [];
241
255
  for (const slot of slotList) {
242
256
  if (typeof slot === "string" && !fieldNames.has(slot)) {
@@ -265,14 +279,16 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
265
279
 
266
280
  export function validateFilterableHasIndex(root: MetaData): string[] {
267
281
  const warnings: string[] = [];
268
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
282
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
283
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
269
284
  // Use children() so inherited fields and identities (via extends:/super:)
270
285
  // are included when checking filterable-without-index.
271
286
  const effective = obj.children();
272
287
  // Build the set of field names that are part of any identity on this object.
273
288
  const indexedFieldNames = new Set<string>();
274
289
  for (const identity of effective.filter((c) => c.type === TYPE_IDENTITY)) {
275
- const fields = identity.ownAttr(IDENTITY_ATTR_FIELDS);
290
+ // ADR-0039: resolving — an identity may inherit @fields via extends.
291
+ const fields = identity.attr(IDENTITY_ATTR_FIELDS);
276
292
  if (typeof fields === "string") {
277
293
  for (const name of fields.split(",")) indexedFieldNames.add(name.trim());
278
294
  } else if (Array.isArray(fields)) {
@@ -281,9 +297,11 @@ export function validateFilterableHasIndex(root: MetaData): string[] {
281
297
  }
282
298
 
283
299
  for (const field of effective.filter((c) => c.type === TYPE_FIELD)) {
284
- const filterable = field.ownAttr(FIELD_ATTR_FILTERABLE);
300
+ // ADR-0039: resolving — a concrete field may inherit @filterable via extends.
301
+ const filterable = field.attr(FIELD_ATTR_FILTERABLE);
285
302
  if (filterable !== true) continue;
286
- if (field.ownAttr(FIELD_ATTR_DB_INDEXED) === true) continue;
303
+ // ADR-0039: resolving — a concrete field may inherit @db.indexed via extends.
304
+ if (field.attr(FIELD_ATTR_DB_INDEXED) === true) continue;
287
305
  if (indexedFieldNames.has(field.name)) continue;
288
306
  warnings.push(
289
307
  `[filterable-without-index] field "${obj.name}.${field.name}" has @filterable: true but is not ` +
@@ -306,10 +324,12 @@ export function validateFilterableHasIndex(root: MetaData): string[] {
306
324
 
307
325
  export function validateFilterableHasSupportedOps(root: MetaData): ParseError[] {
308
326
  const errors: ParseError[] = [];
309
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
327
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
328
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
310
329
  // children() — inherited @filterable fields (via extends:/super:) are visible.
311
330
  for (const field of obj.children().filter((c) => c.type === TYPE_FIELD)) {
312
- if (field.ownAttr(FIELD_ATTR_FILTERABLE) !== true) continue;
331
+ // ADR-0039: resolving — a concrete field may inherit @filterable via extends.
332
+ if (field.attr(FIELD_ATTR_FILTERABLE) !== true) continue;
313
333
  if (opsForSubType(field.subType).length > 0) continue;
314
334
  errors.push(
315
335
  new ParseError(
@@ -341,7 +361,8 @@ export function validateFilterableHasSupportedOps(root: MetaData): ParseError[]
341
361
  function _findObject(root: MetaData, name: string): MetaData | undefined {
342
362
  // FR-032 — origin heads are FQN-qualified after the desugar/sweep; match on
343
363
  // the effective FQN resolution key (with bare back-compat).
344
- return root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
364
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
365
+ return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
345
366
  }
346
367
 
347
368
  function _findField(obj: MetaData, name: string): MetaData | undefined {
@@ -497,7 +518,8 @@ function _validateViaPath(
497
518
  );
498
519
  return undefined;
499
520
  }
500
- const refTarget = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
521
+ // ADR-0039: resolving — a relationship may inherit @objectRef via extends.
522
+ const refTarget = rel.attr(RELATIONSHIP_ATTR_OBJECT_REF);
501
523
  if (typeof refTarget !== "string" || refTarget === "") {
502
524
  errors.push(
503
525
  new ParseError(
@@ -587,6 +609,8 @@ function _deriveBaseEntity(
587
609
 
588
610
  // 1) The extended identity anchors the base entity (declared, not inferred).
589
611
  // The anchor is the entity NAMED in the ref's owner part — see _refNamedOwner.
612
+ // ADR-0039: own — inspects THIS projection's OWN declared identities to read
613
+ // their extends refs (an inherited identity carries no local superRef to anchor).
590
614
  for (const identity of obj.ownChildren().filter((c) => c.type === TYPE_IDENTITY)) {
591
615
  const extended = identity.superResolved;
592
616
  if (extended !== undefined && extended.type === TYPE_IDENTITY) {
@@ -600,6 +624,8 @@ function _deriveBaseEntity(
600
624
  // 2) Fallback: the single distinct entity targeted by plain field-extends —
601
625
  // again preferring the ref-named owner over the physical declaring ancestor.
602
626
  const targets = new Set<MetaData>();
627
+ // ADR-0039: own — inspects THIS projection's OWN declared fields to read their
628
+ // extends refs (an inherited field carries no local superRef to anchor).
603
629
  for (const f of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
604
630
  const sup = f.superResolved;
605
631
  if (sup === undefined) continue;
@@ -679,7 +705,8 @@ function _inferViaSingleHop(
679
705
  .children()
680
706
  .filter((c) => c.type === TYPE_RELATIONSHIP)
681
707
  .filter((rel) => {
682
- const ref = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
708
+ // ADR-0039: resolving — a relationship may inherit @objectRef via extends.
709
+ const ref = rel.attr(RELATIONSHIP_ATTR_OBJECT_REF);
683
710
  return typeof ref === "string" && stripPackage(ref) === targetEntity.name;
684
711
  });
685
712
  // FR5d — referrer is `<host-FQN>::<fieldName>`, target is the from/of ref
@@ -815,15 +842,19 @@ function _checkExtendsOriginAgreement(
815
842
 
816
843
  export function validateOriginPaths(root: MetaData): ParseError[] {
817
844
  const errors: ParseError[] = [];
818
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
845
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
846
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
819
847
  // FR-024 B5: object.value hosts are EXEMPT from @via inference and
820
848
  // cardinality checks — a value's origin.passthrough is FR-015 parameter
821
849
  // lineage (values are constructed, never assembled; spec §7), not an
822
850
  // assembly path. Their @from refs are still resolution-validated.
823
851
  const isValueHost = obj.subType === OBJECT_SUBTYPE_VALUE;
852
+ // ADR-0039: own — origin validation operates on the OWN-declared field+origin
853
+ // layer (the established cross-port contract; origin.* never inherits, ADR-0029).
824
854
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
825
855
  for (const origin of field.ownChildren().filter((c) => c.type === TYPE_ORIGIN)) {
826
856
  if (origin.subType === ORIGIN_SUBTYPE_PASSTHROUGH) {
857
+ // ADR-0039: own — origin.* never inherits (ADR-0029).
827
858
  const from = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_FROM);
828
859
  if (typeof from !== "string" || from === "") {
829
860
  // Missing-attr (not a reference resolution failure) — keep the
@@ -842,6 +873,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
842
873
  if (fromTarget !== undefined) {
843
874
  _checkExtendsOriginAgreement(field, fromTarget.field, from, obj, origin.source, errors);
844
875
  }
876
+ // ADR-0039: own — origin.* never inherits (ADR-0029).
845
877
  const via = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_VIA);
846
878
  if (typeof via === "string" && via !== "") {
847
879
  const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
@@ -864,6 +896,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
864
896
  }
865
897
  }
866
898
  } else if (origin.subType === ORIGIN_SUBTYPE_AGGREGATE) {
899
+ // ADR-0039: own — origin.* never inherits (ADR-0029).
867
900
  const of_ = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_OF);
868
901
  if (typeof of_ !== "string" || of_ === "") {
869
902
  errors.push(
@@ -878,6 +911,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
878
911
  // an aggregate computes something new (count/sum/…); spec §4 defines
879
912
  // agreement for passthrough only.
880
913
  const ofTarget = _validateFromPath(of_, root, obj, field.name, origin.source, errors, "origin.aggregate.@of");
914
+ // ADR-0039: own — origin.* never inherits (ADR-0029).
881
915
  const via = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_VIA);
882
916
  if (typeof via === "string" && via !== "") {
883
917
  const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
@@ -953,14 +987,17 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
953
987
 
954
988
  export function validateDerivedFieldProvidability(root: MetaData): ParseError[] {
955
989
  const errors: ParseError[] = [];
990
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
956
991
  for (const obj of root
957
- .ownChildren()
992
+ .children()
958
993
  .filter((c) => c.type === TYPE_OBJECT && c.subType === OBJECT_SUBTYPE_ENTITY)) {
959
994
  const hasReadCapableSource = obj
960
995
  .children()
961
996
  .filter((c) => c.type === TYPE_SOURCE)
962
997
  .some((s) => (s as MetaSource).isReadOnly());
963
998
  if (hasReadCapableSource) continue;
999
+ // ADR-0039: own — origin-bearing fields are validated on the OWN-declared
1000
+ // layer (mirrors validateOriginPaths; origin.* never inherits, ADR-0029).
964
1001
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
965
1002
  if (!field.ownChildren().some((c) => c.type === TYPE_ORIGIN)) continue;
966
1003
  errors.push(
@@ -994,9 +1031,14 @@ export function validateDerivedFieldProvidability(root: MetaData): ParseError[]
994
1031
 
995
1032
  export function validateFieldObjectStorage(root: MetaData): ParseError[] {
996
1033
  const errors: ParseError[] = [];
997
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
1034
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
1035
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
1036
+ // ADR-0039: own — the concrete field is an OWN child of this object; its
1037
+ // inheritable attrs (@objectRef/@storage) are read resolving below.
998
1038
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
999
- const objectRef = field.ownAttr(FIELD_ATTR_OBJECT_REF);
1039
+ // ADR-0039: resolving — a concrete field.object may inherit @objectRef from
1040
+ // an abstract base via extends; reading own-only would wrongly reject it.
1041
+ const objectRef = field.attr(FIELD_ATTR_OBJECT_REF);
1000
1042
  const hasObjectRef = typeof objectRef === "string" && objectRef.length > 0;
1001
1043
 
1002
1044
  if (field.subType === FIELD_SUBTYPE_OBJECT && !hasObjectRef) {
@@ -1011,9 +1053,10 @@ export function validateFieldObjectStorage(root: MetaData): ParseError[] {
1011
1053
  continue;
1012
1054
  }
1013
1055
 
1014
- const storage = field.ownAttr(FIELD_ATTR_STORAGE);
1056
+ // ADR-0039: resolving — @storage and array-ness may be inherited via extends.
1057
+ const storage = field.attr(FIELD_ATTR_STORAGE);
1015
1058
  if (storage === undefined || storage === null) continue;
1016
- if (storage === STORAGE_FLATTENED && field.isArray === true) {
1059
+ if (storage === STORAGE_FLATTENED && field.resolvedIsArray()) {
1017
1060
  errors.push(
1018
1061
  new ParseError(
1019
1062
  `field "${obj.name}.${field.name}" sets @storage "flattened" with isArray=true; flattened storage requires a single nested value`,
@@ -1053,13 +1096,17 @@ const _MAP_SCALAR_VALUE_SUBTYPES = new Set<string>([
1053
1096
 
1054
1097
  export function validateFieldMap(root: MetaData): ParseError[] {
1055
1098
  const errors: ParseError[] = [];
1056
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
1099
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
1100
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
1101
+ // ADR-0039: own — the concrete field is an OWN child; its inheritable attrs
1102
+ // (@valueType/@objectRef) are read resolving below.
1057
1103
  for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
1058
1104
  if (field.subType !== FIELD_SUBTYPE_MAP) continue;
1059
1105
 
1060
- const valueType = field.ownAttr(FIELD_ATTR_VALUE_TYPE);
1106
+ // ADR-0039: resolving — @valueType / @objectRef may be inherited via extends.
1107
+ const valueType = field.attr(FIELD_ATTR_VALUE_TYPE);
1061
1108
  const hasValueType = typeof valueType === "string" && valueType.length > 0;
1062
- const objectRef = field.ownAttr(FIELD_ATTR_OBJECT_REF);
1109
+ const objectRef = field.attr(FIELD_ATTR_OBJECT_REF);
1063
1110
  const hasObjectRef = typeof objectRef === "string" && objectRef.length > 0;
1064
1111
 
1065
1112
  if (hasValueType === hasObjectRef) {
@@ -1160,6 +1207,8 @@ export function validateFieldDefaults(root: MetaData): ParseError[] {
1160
1207
  function _walkFieldDefaults(node: MetaData, errors: ParseError[]): void {
1161
1208
  if (node.type === TYPE_FIELD && node.subType !== FIELD_SUBTYPE_ENUM) {
1162
1209
  // Enum @default membership is validated by attr-schema-validate Check 5.
1210
+ // ADR-0039: own — validates the @default declared on THIS node (matches the
1211
+ // @values / FR-011 own-attr passes; an inherited default was gated on its parent).
1163
1212
  const raw = node.ownAttr(FIELD_ATTR_DEFAULT);
1164
1213
  if (raw !== undefined && raw !== null && !Array.isArray(raw) && typeof raw !== "object") {
1165
1214
  const def = _stringifyDefault(raw as string | number | boolean);
@@ -1179,6 +1228,7 @@ function _walkFieldDefaults(node: MetaData, errors: ParseError[]): void {
1179
1228
  }
1180
1229
  }
1181
1230
  }
1231
+ // ADR-0039: own — structural walk visiting every physical node once at its site.
1182
1232
  for (const child of node.ownChildren()) _walkFieldDefaults(child, errors);
1183
1233
  }
1184
1234
 
@@ -1193,18 +1243,21 @@ function _walkFieldDefaults(node: MetaData, errors: ParseError[]): void {
1193
1243
 
1194
1244
  export function validateDataGridFilterValues(root: MetaData): ParseError[] {
1195
1245
  const errors: ParseError[] = [];
1196
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
1246
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
1247
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
1197
1248
  const effective = obj.children();
1198
1249
  const allow = new Map<string, readonly string[]>();
1199
1250
  for (const f of effective.filter((c) => c.type === TYPE_FIELD)) {
1200
- if (f.ownAttr(FIELD_ATTR_FILTERABLE) === true) {
1251
+ // ADR-0039: resolving a concrete field may inherit @filterable via extends.
1252
+ if (f.attr(FIELD_ATTR_FILTERABLE) === true) {
1201
1253
  allow.set(f.name, opsForSubType(f.subType));
1202
1254
  }
1203
1255
  }
1204
1256
  for (const layout of effective.filter(
1205
1257
  (c) => c.type === TYPE_LAYOUT && c.subType === LAYOUT_SUBTYPE_DATA_GRID,
1206
1258
  )) {
1207
- const filter = layout.ownAttr(LAYOUT_DATA_GRID_ATTR_FILTER);
1259
+ // ADR-0039: resolving — a layout may inherit @filter via extends.
1260
+ const filter = layout.attr(LAYOUT_DATA_GRID_ATTR_FILTER);
1208
1261
  // Type errors (e.g. legacy string form) are reported by validateAttrSchema.
1209
1262
  if (typeof filter !== "object" || filter === null || Array.isArray(filter)) continue;
1210
1263
  checkFilterClauses(filter as Record<string, unknown>, allow, obj.name, layout.name, layout.source, errors);
@@ -1257,13 +1310,18 @@ function _countJunctionReferences(junction: MetaData): number {
1257
1310
 
1258
1311
  export function validateRelationships(root: MetaData): ParseError[] {
1259
1312
  const errors: ParseError[] = [];
1260
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
1313
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
1314
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
1315
+ // ADR-0039: own — a relationship is validated on the entity that DECLARES it
1316
+ // (the M:N slim-vocabulary rules apply to own-declared relationships; its
1317
+ // inheritable attrs are read resolving below).
1261
1318
  for (const rel of obj.ownChildren().filter((c) => c.type === TYPE_RELATIONSHIP)) {
1262
- const through = rel.ownAttr(RELATIONSHIP_ATTR_THROUGH);
1263
- const sourceRefField = rel.ownAttr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD);
1264
- const symmetric = rel.ownAttr(RELATIONSHIP_ATTR_SYMMETRIC) === true;
1265
- const cardinality = rel.ownAttr(RELATIONSHIP_ATTR_CARDINALITY);
1266
- const objectRef = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF);
1319
+ // ADR-0039: resolving — a relationship may inherit its M:N attrs via extends.
1320
+ const through = rel.attr(RELATIONSHIP_ATTR_THROUGH);
1321
+ const sourceRefField = rel.attr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD);
1322
+ const symmetric = rel.attr(RELATIONSHIP_ATTR_SYMMETRIC) === true;
1323
+ const cardinality = rel.attr(RELATIONSHIP_ATTR_CARDINALITY);
1324
+ const objectRef = rel.attr(RELATIONSHIP_ATTR_OBJECT_REF);
1267
1325
 
1268
1326
  const hasThrough = typeof through === "string" && through !== "";
1269
1327
  const hasSourceRefField = typeof sourceRefField === "string" && sourceRefField !== "";
@@ -19,7 +19,8 @@ class SymbolTableImpl implements SymbolTable {
19
19
 
20
20
  static build(root: MetaData): SymbolTableImpl {
21
21
  const t = new SymbolTableImpl();
22
- for (const child of root.ownChildren()) {
22
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
23
+ for (const child of root.children()) {
23
24
  if (child.type !== TYPE_OBJECT) continue;
24
25
  if (child.name) t.byRef.set(child.name, child);
25
26
  t.byRef.set(child.fqn(), child);
@@ -60,7 +61,9 @@ export function runRegisteredValidation(root: MetaData, registry: TypeRegistry):
60
61
  const def = registry.find(node.type, node.subType);
61
62
  if (def) {
62
63
  for (const desc of def.references ?? []) {
63
- const raw = node.ownAttr(desc.attr);
64
+ // ADR-0039: resolving — a reference attr (e.g. @objectRef/@through) may be
65
+ // inherited via extends; read the effective value.
66
+ const raw = node.attr(desc.attr);
64
67
  if (typeof raw !== "string" || raw === "") continue; // absence is the required-attr pass's job
65
68
  const entityRef = desc.dottedFieldPath ? (raw.split(".")[0] ?? raw) : raw;
66
69
  const target = ctx.symbols.resolveObject(entityRef);
@@ -88,6 +91,8 @@ export function runRegisteredValidation(root: MetaData, registry: TypeRegistry):
88
91
  }
89
92
  def.validate?.(node, ctx);
90
93
  }
94
+ // ADR-0039: own — structural walk visiting every physical node once at its
95
+ // declaration site (an inherited child is validated on its declaring parent).
91
96
  for (const child of node.ownChildren()) walk(child);
92
97
  }
93
98
  }
package/src/naming.ts CHANGED
@@ -82,7 +82,8 @@ export function resolveColumnName(
82
82
  field: MetaData,
83
83
  strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
84
84
  ): string {
85
- const col = field.ownAttr(FIELD_ATTR_COLUMN);
85
+ // ADR-0039: resolving — a concrete field may inherit @column via extends.
86
+ const col = field.attr(FIELD_ATTR_COLUMN);
86
87
  if (typeof col === "string" && col) return col;
87
88
  return applyColumnNamingStrategy(field.name, strategy);
88
89
  }
@@ -95,12 +96,14 @@ export function resolveColumnName(
95
96
  * SQLite treats it as the only allowed value (no schema concept).
96
97
  */
97
98
  export function resolveTableSchema(entity: MetaData): string | undefined {
98
- const source = entity.ownChildren().find(
99
+ // ADR-0039: resolving — a concrete entity may inherit its source.rdb via extends.
100
+ const source = entity.children().find(
99
101
  (c): c is MetaSource =>
100
102
  c instanceof MetaSource && c.role === SOURCE_ROLE_PRIMARY,
101
103
  );
102
104
  if (!source) return undefined;
103
- const schema = source.ownAttr(SOURCE_ATTR_SCHEMA);
105
+ // ADR-0039: resolving — an inherited source's @schema lives on the super node.
106
+ const schema = source.attr(SOURCE_ATTR_SCHEMA);
104
107
  if (typeof schema === "string" && schema !== "") return schema;
105
108
  return undefined;
106
109
  }
@@ -41,7 +41,8 @@ export function objectToJson(
41
41
  for (const field of mo.fields()) {
42
42
  const raw = instance[field.name];
43
43
  if (raw === undefined) continue; // absent field → omit
44
- out[field.name] = field.isArray
44
+ // ADR-0039: resolving — array-ness may be inherited via extends.
45
+ out[field.name] = field.resolvedIsArray()
45
46
  ? mapArray(raw, (el) => toJsonValue(field, el, mo))
46
47
  : toJsonValue(field, raw, mo);
47
48
  }
@@ -57,7 +58,8 @@ export function jsonToObject(
57
58
  for (const field of mo.fields()) {
58
59
  const raw = json[field.name];
59
60
  if (raw === undefined) continue;
60
- out[field.name] = field.isArray
61
+ // ADR-0039: resolving — array-ness may be inherited via extends.
62
+ out[field.name] = field.resolvedIsArray()
61
63
  ? mapArray(raw, (el) => fromJsonValue(field, el, mo))
62
64
  : fromJsonValue(field, raw, mo);
63
65
  }
@@ -147,6 +149,8 @@ function coerceNumeric(raw: unknown): unknown {
147
149
  function resolveObjectRef(field: MetaField, mo: MetaObject): MetaObject | undefined {
148
150
  const ref = field.objectRef;
149
151
  if (ref === undefined) return undefined;
152
+ // ADR-0039: own — lookup is on the tree ROOT, which has no super chain
153
+ // (it never `extends`), so its own children ARE its effective children.
150
154
  const found: MetaData | undefined = mo.root().ownChildByTypeAndName(TYPE_OBJECT, ref);
151
155
  if (found === undefined) return undefined;
152
156
  return found as MetaObject;
@@ -672,6 +672,8 @@ function parseNodeInto(
672
672
  // Snapshot of own attrs (name → value) — used to detect ERR_MERGE_CONFLICT
673
673
  // when a new contribution sets an attr the target already declared to a
674
674
  // different non-empty value.
675
+ // ADR-0039: own — overlay/merge machinery, operating on the AUTHORED
676
+ // declaration layer (pre super-resolution).
675
677
  preMergeAttrSnapshot = new Map(target.ownAttrs());
676
678
  }
677
679
 
@@ -897,6 +899,8 @@ function createOrFindMetaData(
897
899
 
898
900
  // Look up an existing child with (type, name). Skip unnamed nodes — they
899
901
  // are always distinct (e.g. inline validators, anonymous attrs).
902
+ // ADR-0039: own — overlay/merge lookup on the AUTHORED declaration layer
903
+ // (an overlay targets a same-file/same-node own child, pre super-resolution).
900
904
  const existing = name !== "" ? parent.ownChildByTypeAndName(type, name) : undefined;
901
905
 
902
906
  if (isOverlayNode) {
@@ -1245,6 +1249,7 @@ function processChildren(
1245
1249
  childNodePath,
1246
1250
  );
1247
1251
 
1252
+ // ADR-0039: own — tree-building dedup on the AUTHORED declaration layer.
1248
1253
  if (childModel !== undefined && !parent.ownChildren().includes(childModel)) {
1249
1254
  parent.addChild(childModel);
1250
1255
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  import {
4
4
  FIELD_SUBTYPE_STRING,
5
- FIELD_SUBTYPE_TIMESTAMP,
6
5
  } from "../../core/field/field-constants.js";
7
6
 
8
7
  /** Column name override on a field (maps to @column in metadata). */
@@ -38,20 +37,29 @@ export const FIELD_ATTR_DB_COLUMN_TYPE = "dbColumnType";
38
37
  export const DB_COLUMN_TYPE_UUID = "uuid";
39
38
  /** `@dbColumnType: jsonb` — genuinely-open `jsonb` column (legal on field.string). */
40
39
  export const DB_COLUMN_TYPE_JSONB = "jsonb";
41
- /** `@dbColumnType: timestamp_with_tz` — `timestamp with time zone` (legal on field.timestamp). */
42
- export const DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ = "timestamp_with_tz";
43
- /** `@dbColumnType: uuid_array` — native Postgres `uuid[]` column (legal on field.string). */
44
- export const DB_COLUMN_TYPE_UUID_ARRAY = "uuid_array";
45
- /** `@dbColumnType: text_array` — native Postgres `text[]` column (legal on field.string). */
46
- export const DB_COLUMN_TYPE_TEXT_ARRAY = "text_array";
47
40
 
48
- /** The closed set of legal `@dbColumnType` values (raw-dialect passthrough deferred). */
41
+ /**
42
+ * ADR-0036 Wave 2: `@localTime` — a boolean flag on `field.timestamp` marking the
43
+ * rare naive / wall-clock case. When true the column is Postgres `timestamp
44
+ * without time zone`; absent/false (the default) is an absolute instant →
45
+ * `timestamptz`. This replaces the retired `@dbColumnType: timestamp_with_tz`
46
+ * escape hatch — timezone-awareness now lives in the logical field type
47
+ * (instant-by-default) + this opt-out, not in a physical column-type string.
48
+ */
49
+ export const FIELD_ATTR_LOCAL_TIME = "localTime";
50
+
51
+ /**
52
+ * The closed set of legal `@dbColumnType` values (raw-dialect passthrough deferred).
53
+ * dbColumnType slim-and-derive (ADR-0036 Wave 1, decision 4): the native-array
54
+ * overrides `uuid_array`/`text_array` are fully removed — native `uuid[]`/`text[]`
55
+ * are DERIVED from a field subtype + `isArray`, never declared via an escape hatch.
56
+ * Wave 2 (decision 1) retires `timestamp_with_tz` entirely — tz-awareness moves to
57
+ * `field.timestamp` (instant by default) + `@localTime` (the naive opt-out). The
58
+ * legal set is now just `{ uuid, jsonb }`, both on field.string.
59
+ */
49
60
  export const DB_COLUMN_TYPE_VALUES = [
50
61
  DB_COLUMN_TYPE_UUID,
51
62
  DB_COLUMN_TYPE_JSONB,
52
- DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ,
53
- DB_COLUMN_TYPE_UUID_ARRAY,
54
- DB_COLUMN_TYPE_TEXT_ARRAY,
55
63
  ] as const;
56
64
  export type DbColumnTypeValue = (typeof DB_COLUMN_TYPE_VALUES)[number];
57
65
 
@@ -59,12 +67,9 @@ export type DbColumnTypeValue = (typeof DB_COLUMN_TYPE_VALUES)[number];
59
67
  * Legal `@dbColumnType` value → the field subtypes it may be applied to (ADR-0013,
60
68
  * R6 Plan 2b). Any other (subtype × value) pairing — or an unrecognized value — is
61
69
  * an ERR_BAD_ATTR_VALUE. Keyed by value; the value-set is enforced by membership in
62
- * this map. Subtype names are the bare FIELD_SUBTYPE_* constants (string / timestamp).
70
+ * this map. Subtype names are the bare FIELD_SUBTYPE_* constants (string).
63
71
  */
64
72
  export const DB_COLUMN_TYPE_LEGAL_SUBTYPES: Readonly<Record<DbColumnTypeValue, readonly string[]>> = {
65
73
  [DB_COLUMN_TYPE_UUID]: [FIELD_SUBTYPE_STRING],
66
74
  [DB_COLUMN_TYPE_JSONB]: [FIELD_SUBTYPE_STRING],
67
- [DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ]: [FIELD_SUBTYPE_TIMESTAMP],
68
- [DB_COLUMN_TYPE_UUID_ARRAY]: [FIELD_SUBTYPE_STRING],
69
- [DB_COLUMN_TYPE_TEXT_ARRAY]: [FIELD_SUBTYPE_STRING],
70
75
  } as const;
@@ -35,7 +35,25 @@ export const DB_DEFINITION: ProviderDefinition = {
35
35
  "name": "dbColumnType",
36
36
  "min": 0,
37
37
  "max": 1,
38
- "description": "Physical DB column-type override (ADR-0013 escape hatch). Legal values are uuid | jsonb | timestamp_with_tz | uuid_array | text_array, each legal only on a specific logical field subtype (uuid/jsonb/uuid_array/text_array on field.string, timestamp_with_tz on field.timestamp). uuid_array/text_array emit native SQL arrays (uuid[]/text[]). The logical field type and its native binding are unchanged."
38
+ "allowedValues": [
39
+ "uuid",
40
+ "jsonb"
41
+ ],
42
+ "description": "Physical DB column-type override (ADR-0013 escape hatch). Legal values are uuid | jsonb, both on field.string (uuid = native Postgres uuid column over a string-typed field; jsonb = genuinely-open JSON column). The logical field type and its native binding are unchanged. Native SQL arrays (uuid[]/text[]) are NOT declared here — they are derived from a field subtype + isArray (ADR-0036 Wave 1). The retired timestamp_with_tz value is gone — timezone-awareness lives in field.timestamp (instant by default) + @localTime (the naive opt-out), per ADR-0036 Wave 2."
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ "type": "field",
48
+ "subType": "timestamp",
49
+ "children": [
50
+ {
51
+ "type": "attr",
52
+ "subType": "boolean",
53
+ "name": "localTime",
54
+ "min": 0,
55
+ "max": 1,
56
+ "description": "When true, the timestamp is a naive wall-clock value with no timezone (Postgres `timestamp without time zone`); absent/false (the default) = an absolute instant (`timestamptz`). ADR-0036 Wave 2 — replaces the retired @dbColumnType: timestamp_with_tz escape hatch."
39
57
  }
40
58
  ]
41
59
  },
@@ -30,12 +30,14 @@ export class MetaOrigin extends MetaData {}
30
30
  export class MetaPassthroughOrigin extends MetaOrigin {
31
31
  /** The dotted-path cross-entity field reference this origin passes through (e.g. "Program.title"). */
32
32
  get from(): string | undefined {
33
+ // ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
33
34
  const v = this.ownAttr(ORIGIN_PASSTHROUGH_ATTR_FROM);
34
35
  return typeof v === "string" ? v : undefined;
35
36
  }
36
37
 
37
38
  /** Optional dotted relationship path used to reach the source entity (e.g. "Program.weeks"). */
38
39
  get via(): string | undefined {
40
+ // ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
39
41
  const v = this.ownAttr(ORIGIN_PASSTHROUGH_ATTR_VIA);
40
42
  return typeof v === "string" ? v : undefined;
41
43
  }
@@ -50,18 +52,21 @@ export class MetaPassthroughOrigin extends MetaOrigin {
50
52
  export class MetaAggregateOrigin extends MetaOrigin {
51
53
  /** The aggregate function (count | sum | avg | min | max). */
52
54
  get agg(): AggregateFunction | undefined {
55
+ // ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
53
56
  const v = this.ownAttr(ORIGIN_AGGREGATE_ATTR_AGG);
54
57
  return typeof v === "string" ? (v as AggregateFunction) : undefined;
55
58
  }
56
59
 
57
60
  /** The dotted-path target of the aggregate (e.g. "Week.id"). */
58
61
  get of(): string | undefined {
62
+ // ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
59
63
  const v = this.ownAttr(ORIGIN_AGGREGATE_ATTR_OF);
60
64
  return typeof v === "string" ? v : undefined;
61
65
  }
62
66
 
63
67
  /** The dotted relationship path the aggregate walks (e.g. "Program.weeks"). */
64
68
  get via(): string | undefined {
69
+ // ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
65
70
  const v = this.ownAttr(ORIGIN_AGGREGATE_ATTR_VIA);
66
71
  return typeof v === "string" ? v : undefined;
67
72
  }
@@ -76,6 +81,7 @@ export class MetaAggregateOrigin extends MetaOrigin {
76
81
  export class MetaCollectionOrigin extends MetaOrigin {
77
82
  /** The dotted relationship path (or wildcard selector) this collection is sourced from. */
78
83
  get via(): string | undefined {
84
+ // ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
79
85
  const v = this.ownAttr(ORIGIN_COLLECTION_ATTR_VIA);
80
86
  return typeof v === "string" ? v : undefined;
81
87
  }