@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
@@ -36,7 +36,9 @@ export class MetaSource extends MetaData {
36
36
  * back-compat accessor that reads ONLY the @table slot — callers should
37
37
  * use physicalName for the FR-016 four-step rule. */
38
38
  get tableName(): string | undefined {
39
- const v = this.ownAttr(SOURCE_ATTR_TABLE);
39
+ // ADR-0039: resolving — a source may be inherited via extends (an entity
40
+ // extending BaseEntity inherits its source.rdb); @table can live on the super.
41
+ const v = this.attr(SOURCE_ATTR_TABLE);
40
42
  return typeof v === "string" && v !== "" ? v : undefined;
41
43
  }
42
44
 
@@ -45,13 +47,15 @@ export class MetaSource extends MetaData {
45
47
  * `"table"` when omitted (ADR-0007 Rule 3 — per-paradigm default).
46
48
  */
47
49
  get effectiveKind(): string {
48
- const v = this.ownAttr(SOURCE_ATTR_KIND);
50
+ // ADR-0039: resolving — an inherited source's @kind lives on the super node.
51
+ const v = this.attr(SOURCE_ATTR_KIND);
49
52
  return typeof v === "string" && v !== "" ? v : DEFAULT_SOURCE_KIND;
50
53
  }
51
54
 
52
55
  /** The multi-source role for this source (defaults to "primary" when omitted). */
53
56
  get role(): string {
54
- const v = this.ownAttr(SOURCE_ATTR_ROLE);
57
+ // ADR-0039: resolving — an inherited source's @role lives on the super node.
58
+ const v = this.attr(SOURCE_ATTR_ROLE);
55
59
  return typeof v === "string" && v !== "" ? v : DEFAULT_SOURCE_ROLE;
56
60
  }
57
61
 
@@ -85,13 +89,15 @@ export class MetaSource extends MetaData {
85
89
  // Step 1: kind-matching alias.
86
90
  const canonicalAttr = PHYSICAL_NAME_ATTR_BY_KIND.get(kind);
87
91
  if (canonicalAttr !== undefined) {
88
- const v = this.ownAttr(canonicalAttr);
92
+ // ADR-0039: resolving — an inherited source's physical-name alias lives on the super.
93
+ const v = this.attr(canonicalAttr);
89
94
  if (typeof v === "string" && v !== "") return v;
90
95
  }
91
96
 
92
97
  // Step 2: legacy @table for non-table kind.
93
98
  if (canonicalAttr !== SOURCE_ATTR_TABLE) {
94
- const legacy = this.ownAttr(SOURCE_ATTR_TABLE);
99
+ // ADR-0039: resolving — inherited @table lives on the super node.
100
+ const legacy = this.attr(SOURCE_ATTR_TABLE);
95
101
  if (typeof legacy === "string" && legacy !== "") return legacy;
96
102
  }
97
103
 
@@ -55,7 +55,8 @@ export function validateSourceParameterRef(root: MetaData): ParseError[] {
55
55
  // matches via resolutionKey() (these fixtures declare package at metadata.root
56
56
  // only, so obj.package is undefined and the file-default must be folded in).
57
57
  const objectIndex = new Map<string, MetaData>();
58
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
58
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
59
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
59
60
  objectIndex.set(obj.name, obj);
60
61
  const fqn = obj.package !== undefined && obj.package !== ""
61
62
  ? `${obj.package}${PACKAGE_SEPARATOR}${obj.name}`
@@ -64,12 +65,15 @@ export function validateSourceParameterRef(root: MetaData): ParseError[] {
64
65
  objectIndex.set(obj.resolutionKey(), obj);
65
66
  }
66
67
 
67
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
68
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
69
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
70
+ // ADR-0039: own — declaration-layer source iteration (mirrors validateSourceRoles).
68
71
  for (const source of obj.ownChildren().filter(
69
72
  (c): c is MetaSource =>
70
73
  c.type === TYPE_SOURCE && c.subType === SOURCE_SUBTYPE_RDB && c instanceof MetaSource,
71
74
  )) {
72
- const ref = source.ownAttr(SOURCE_ATTR_PARAMETER_REF);
75
+ // ADR-0039: resolving — a source may inherit @parameterRef via extends.
76
+ const ref = source.attr(SOURCE_ATTR_PARAMETER_REF);
73
77
  if (typeof ref !== "string" || ref === "") continue;
74
78
 
75
79
  // ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND — checked even before resolution
@@ -114,11 +118,15 @@ export function validateSourceParameterRef(root: MetaData): ParseError[] {
114
118
  // with origin.passthrough must have a subtype matching the referenced
115
119
  // field. The origin path validation pass checks the from-path resolves;
116
120
  // here we just check the subtype alignment.
117
- for (const paramField of target.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
121
+ // ADR-0039: resolving the parameter value-object's fields may be inherited.
122
+ for (const paramField of target.children().filter((c) => c.type === TYPE_FIELD)) {
123
+ // ADR-0039: own — origin.* never inherits (ADR-0029); the origin child and
124
+ // its @from are read own.
118
125
  const passthrough = paramField.ownChildren().find(
119
126
  (c) => c.type === TYPE_ORIGIN && c.subType === ORIGIN_SUBTYPE_PASSTHROUGH,
120
127
  );
121
128
  if (passthrough === undefined) continue;
129
+ // ADR-0039: own — origin.* never inherits (ADR-0029).
122
130
  const from = passthrough.ownAttr(ORIGIN_PASSTHROUGH_ATTR_FROM);
123
131
  if (typeof from !== "string" || from === "") continue;
124
132
  const dot = from.indexOf(".");
@@ -127,7 +135,8 @@ export function validateSourceParameterRef(root: MetaData): ParseError[] {
127
135
  const targetFieldName = from.slice(dot + 1);
128
136
  const targetEntity = objectIndex.get(targetEntityName);
129
137
  if (targetEntity === undefined) continue; // origin-paths pass surfaces this
130
- const targetField = targetEntity.ownChildren().find(
138
+ // ADR-0039: resolving — the referenced entity's field may be inherited via extends.
139
+ const targetField = targetEntity.children().find(
131
140
  (c) => c.type === TYPE_FIELD && c.name === targetFieldName,
132
141
  );
133
142
  if (targetField === undefined) continue;
@@ -42,7 +42,11 @@ export function validateSourcePhysicalNames(root: MetaData): PhysicalNameValidat
42
42
  const errors: ParseError[] = [];
43
43
  const warnings: LoaderWarning[] = [];
44
44
 
45
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
45
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
46
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
47
+ // ADR-0039: own — validates the physical-name aliases DECLARED on this object's
48
+ // own sources (declaration-layer, mirrors validateSourceRoles); an inherited
49
+ // source's aliases were validated on the object that declares it.
46
50
  const sources = obj
47
51
  .ownChildren()
48
52
  .filter(
@@ -54,6 +58,9 @@ export function validateSourcePhysicalNames(root: MetaData): PhysicalNameValidat
54
58
  // Empty-string check first — explicit "" is meaningless and an
55
59
  // authoring error regardless of which alias was used.
56
60
  for (const attr of ALL_PHYSICAL_NAME_ALIASES) {
61
+ // ADR-0039: own — the "exactly one alias, non-empty" rule is a per-source
62
+ // OWN-declaration constraint (resolving would conflate an inherited alias
63
+ // with an own one and falsely trip ERR_PHYSICAL_NAME_MULTIPLE).
57
64
  const v = source.ownAttr(attr);
58
65
  if (typeof v === "string" && v === "") {
59
66
  errors.push(
@@ -66,6 +73,7 @@ export function validateSourcePhysicalNames(root: MetaData): PhysicalNameValidat
66
73
  }
67
74
 
68
75
  const setAliases = ALL_PHYSICAL_NAME_ALIASES.filter((attr) => {
76
+ // ADR-0039: own — per-source own-declaration constraint (see above).
69
77
  const v = source.ownAttr(attr);
70
78
  return typeof v === "string" && v !== "";
71
79
  });
@@ -23,7 +23,11 @@ import { OBJECT_SUBTYPE_ENTITY } from "../../core/object/object-constants.js";
23
23
  export function validateSourceRoles(root: MetaData): ParseError[] {
24
24
  const errors: ParseError[] = [];
25
25
 
26
- for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
26
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
27
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
28
+ // ADR-0039: own — the one-primary rule is a DECLARATION-layer check (cross-port
29
+ // parity: Java uses getSources(false)); it counts sources declared on THIS object,
30
+ // not inherited ones. The role/kind reads below resolve via MetaSource getters.
27
31
  const sources = obj
28
32
  .ownChildren()
29
33
  .filter((c) => c.type === TYPE_SOURCE)
@@ -18,13 +18,15 @@ import type { SortOrderValue } from "../../core/query/query-constants.js";
18
18
  export class MetaLayout extends MetaData {
19
19
  /** The number of rows per page for the dataGrid layout. */
20
20
  get pageSize(): number | undefined {
21
- const v = this.ownAttr(LAYOUT_DATA_GRID_ATTR_PAGE_SIZE);
21
+ // ADR-0039: resolving — a layout may inherit its grid attrs via extends.
22
+ const v = this.attr(LAYOUT_DATA_GRID_ATTR_PAGE_SIZE);
22
23
  return typeof v === "number" ? v : undefined;
23
24
  }
24
25
 
25
26
  /** The field name to sort by default in the dataGrid layout. */
26
27
  get defaultSortField(): string | undefined {
27
- const v = this.ownAttr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
28
+ // ADR-0039: resolving — a layout may inherit its grid attrs via extends.
29
+ const v = this.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
28
30
  return typeof v === "string" ? v : undefined;
29
31
  }
30
32
 
@@ -33,7 +35,8 @@ export class MetaLayout extends MetaData {
33
35
  * Returns `undefined` when the attr is absent.
34
36
  */
35
37
  get defaultSortOrder(): SortOrderValue | undefined {
36
- const v = this.ownAttr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER);
38
+ // ADR-0039: resolving — a layout may inherit its grid attrs via extends.
39
+ const v = this.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER);
37
40
  return typeof v === "string" ? (v as SortOrderValue) : undefined;
38
41
  }
39
42
 
@@ -42,12 +45,14 @@ export class MetaLayout extends MetaData {
42
45
  * Defaults to `false` when the attr is absent.
43
46
  */
44
47
  get filterable(): boolean {
45
- return this.ownAttr(LAYOUT_DATA_GRID_ATTR_FILTERABLE) === true;
48
+ // ADR-0039: resolving — a layout may inherit @filterable via extends.
49
+ return this.attr(LAYOUT_DATA_GRID_ATTR_FILTERABLE) === true;
46
50
  }
47
51
 
48
52
  /** The desugared preset filter object for the dataGrid layout, or undefined. */
49
53
  get filter(): Record<string, unknown> | undefined {
50
- const v = this.ownAttr(LAYOUT_DATA_GRID_ATTR_FILTER);
54
+ // ADR-0039: resolving — a layout may inherit its grid attrs via extends.
55
+ const v = this.attr(LAYOUT_DATA_GRID_ATTR_FILTER);
51
56
  return typeof v === "object" && v !== null && !Array.isArray(v)
52
57
  ? (v as Record<string, unknown>)
53
58
  : undefined;
@@ -55,7 +60,8 @@ export class MetaLayout extends MetaData {
55
60
 
56
61
  /** The ordered list of field names to display as columns in the dataGrid layout. */
57
62
  get columns(): string[] {
58
- const c = this.ownAttr(LAYOUT_DATA_GRID_ATTR_COLUMNS);
63
+ // ADR-0039: resolving — a layout may inherit its grid attrs via extends.
64
+ const c = this.attr(LAYOUT_DATA_GRID_ATTR_COLUMNS);
59
65
  return Array.isArray(c) ? (c as string[]) : [];
60
66
  }
61
67
  }
@@ -9,12 +9,15 @@
9
9
  //
10
10
  // The IN/OUT boundary is documented in fixtures/registry-conformance/README.md.
11
11
  // In short, the manifest emits: type.subType + per-type/per-attr `description`
12
- // (FR-033) + attrs[{name, valueType, isArray, required, description, rules?,
13
- // example?, whenToUse?}] + the structural constraint graph (children / parents /
14
- // cardinality, FR-033) + commonAttrs + defaultSubTypes. EXCLUDED (per-port-
15
- // physical or not-universally-tracked-on-the-registry): factories/native
16
- // bindings; AttrSchema.default and allowedValues (Java's attr model
17
- // ChildRequirement carries neither).
12
+ // (FR-033) + attrs[{name, valueType, isArray, required, allowedValues?,
13
+ // description, rules?, example?, whenToUse?}] + the structural constraint graph
14
+ // (children / parents / cardinality, FR-033) + commonAttrs + defaultSubTypes.
15
+ // `allowedValues` (ADR-0036 Wave 1, decision 5) is the closed value-set of a
16
+ // closed-enum attr emitted ONLY when the attr declares one, OMITTED for open /
17
+ // format-validated attrs (@currency, @locale). It byte-gates closed vocabularies
18
+ // cross-port (the gate that catches a value-set silently drifting between ports).
19
+ // EXCLUDED (per-port-physical or not-universally-tracked-on-the-registry):
20
+ // factories/native bindings; AttrSchema.default.
18
21
  //
19
22
  // FR-033 Task 5 GREW the manifest: it now also emits the documentation surface
20
23
  // (every type/attr carries a required, non-empty `description`; optional `rules`/
@@ -41,6 +44,14 @@ interface ManifestAttr {
41
44
  /** True for an array-valued attr (a list of the scalar `valueType`); the orthogonal array axis. */
42
45
  isArray: boolean;
43
46
  required: boolean;
47
+ /**
48
+ * The closed value-set of a closed-enum attr (ADR-0036 Wave 1, decision 5).
49
+ * Emitted ONLY when the attr declares a non-empty `allowedValues` set; OMITTED
50
+ * for open / format-validated attrs (e.g. @currency ISO-4217, @locale BCP-47).
51
+ * This byte-gates closed-enum vocabularies cross-port — the gate that catches a
52
+ * value-set drifting between ports (the failure mode the dbColumnType slim hit).
53
+ */
54
+ allowedValues?: readonly string[];
44
55
  /** FR-033 — human/AI-facing description of the attribute (required, non-empty). */
45
56
  description: string;
46
57
  /** FR-033 — prose documenting the complex rules enforced in code. Emitted only when present. */
@@ -119,11 +130,18 @@ function toManifestAttr(attr: AttrSchema): ManifestAttr {
119
130
  // FR-033: the documentation surface (`description` required + non-empty;
120
131
  // `rules`/`example`/`whenToUse` emitted ONLY when present) follows the
121
132
  // existing facets, preserving key order for byte-stability.
133
+ // The closed value-set, emitted only when present + non-empty (decision 5).
134
+ // AttrValue is string | number | boolean | array; a closed enum is always a set
135
+ // of scalar string members, so stringify each for a stable cross-port surface.
136
+ // Spread BEFORE `description` so the JSON key order is name → valueType →
137
+ // isArray → required → (allowedValues?) → description → (rules?/example?/whenToUse?).
138
+ const hasAllowed = attr.allowedValues !== undefined && attr.allowedValues.length > 0;
122
139
  const out: ManifestAttr = {
123
140
  name: attr.name,
124
141
  valueType,
125
142
  isArray,
126
143
  required: attr.required,
144
+ ...(hasAllowed ? { allowedValues: attr.allowedValues!.map((v) => String(v)) } : {}),
127
145
  description: attr.description,
128
146
  };
129
147
  if (attr.rules !== undefined) out.rules = attr.rules;
@@ -309,8 +327,10 @@ export function buildRegistryManifest(registry: TypeRegistry): RegistryManifest
309
327
  * - 2-space indentation (JSON.stringify(_, _, 2)).
310
328
  * - Object keys in a fixed order (JSON.stringify preserves insertion order):
311
329
  * the manifest is built with `types`, `commonAttrs`, `defaultSubTypes`.
312
- * - Each attr: `name`, `valueType`, `isArray`, `required`, `description`,
313
- * then optional `rules`, `example`, `whenToUse` (each omitted when absent).
330
+ * - Each attr: `name`, `valueType`, `isArray`, `required`, then optional
331
+ * `allowedValues` (omitted unless the attr declares a non-empty closed set —
332
+ * decision 5), then `description`, then optional `rules`, `example`,
333
+ * `whenToUse` (each omitted when absent).
314
334
  * - Each type: `type`, `subType`, `description`, then optional `rules`,
315
335
  * `example`, `whenToUse` (omitted when absent), then `attrs`, `children`,
316
336
  * then optional `parents` (omitted when absent/empty).
@@ -122,12 +122,19 @@ function serializeNodeInner(
122
122
  // (re-parsing would seek a non-existent node to merge into). So it is
123
123
  // deliberately NOT serialized.
124
124
 
125
- if (model.isArray === true) {
125
+ // ADR-0039: effective mode resolves array-ness through the super chain (a
126
+ // concrete field that extends an abstract array field is itself an array);
127
+ // own mode (canonicalSerialize) emits only the authored `isArray` so the
128
+ // round-trip reconstructs the same extends tree.
129
+ const isArrayForMode = effective ? model.resolvedIsArray() : model.isArray === true;
130
+ if (isArrayForMode) {
126
131
  obj[RESERVED_KEY_IS_ARRAY] = true;
127
132
  }
128
133
 
129
134
  // In effective mode use children()/attrs() (own + inherited via super chain);
130
135
  // in own mode use ownChildren()/ownAttrs() (declared on this node only).
136
+ // ADR-0039: own-mode canonical serializer — round-trips the AUTHORED form so
137
+ // re-loading reconstructs the same extends tree (the effective branch resolves).
131
138
  const childList = effective ? model.children() : model.ownChildren();
132
139
  const attrMap = effective ? model.attrs() : model.ownAttrs();
133
140
 
@@ -204,6 +204,32 @@ export abstract class MetaData {
204
204
  this.isArray = val;
205
205
  }
206
206
 
207
+ /**
208
+ * Effective array-ness — the RESOLVING accessor for the native `isArray` flag
209
+ * (ADR-0039). `isArray` is a native boolean property, NOT an attr, so it has no
210
+ * resolving path through attrs()/attr(); a concrete node that `extends` an
211
+ * abstract array node inherits its array-ness only through the super chain.
212
+ * Reading the bare `isArray` field is OWN-ONLY and silently drops inherited
213
+ * array-ness — every codegen/runtime/effective-serializer read of a node's
214
+ * effective array-ness MUST route through this method instead.
215
+ *
216
+ * Returns the node's own `isArray` if set true, else walks the super chain.
217
+ * (Own `true` always wins; a node can only widen to array, never narrow — an
218
+ * abstract array base extended by a concrete node stays an array.)
219
+ */
220
+ resolvedIsArray(): boolean {
221
+ return this.cached("resolvedIsArray", () => {
222
+ let node: MetaData | undefined = this;
223
+ const visited = new Set<MetaData>();
224
+ while (node !== undefined && !visited.has(node)) {
225
+ if (node.isArray === true) return true;
226
+ visited.add(node);
227
+ node = node._superData;
228
+ }
229
+ return false;
230
+ });
231
+ }
232
+
207
233
  // ---------------------------------------------------------------------------
208
234
  // dataType
209
235
  // ---------------------------------------------------------------------------
@@ -383,21 +409,25 @@ export abstract class MetaData {
383
409
 
384
410
  /** Own children whose type matches — excludes inherited. */
385
411
  ownChildrenOfType(type: string): MetaData[] {
412
+ // ADR-0039: own-accessor definition — delegates to ownChildren() by contract.
386
413
  return this.ownChildren().filter((c) => c.type === type);
387
414
  }
388
415
 
389
416
  /** Own children matching both type and subType — excludes inherited. */
390
417
  ownChildrenOfSubType(type: string, subType: string): MetaData[] {
418
+ // ADR-0039: own-accessor definition — delegates to ownChildren() by contract.
391
419
  return this.ownChildren().filter((c) => c.type === type && c.subType === subType);
392
420
  }
393
421
 
394
422
  /** First own child with matching name, or undefined — excludes inherited. */
395
423
  ownChildByName(name: string): MetaData | undefined {
424
+ // ADR-0039: own-accessor definition — delegates to ownChildren() by contract.
396
425
  return this.ownChildren().find((c) => c.name === name);
397
426
  }
398
427
 
399
428
  /** First own child matching both type and name, or undefined — excludes inherited. */
400
429
  ownChildByTypeAndName(type: string, name: string): MetaData | undefined {
430
+ // ADR-0039: own-accessor definition — delegates to ownChildren() by contract.
401
431
  return this.ownChildren().find((c) => c.type === type && c.name === name);
402
432
  }
403
433
 
@@ -12,6 +12,8 @@ export class MetaRoot extends MetaData {
12
12
  /** Object entities defined at this root level. */
13
13
  objects(): MetaObject[] {
14
14
  return this.cached("objects", () =>
15
+ // ADR-0039: own — the metadata root has no super chain (it never `extends`),
16
+ // so own children ARE its effective children.
15
17
  this.ownChildren().filter((c): c is MetaObject => c.type === TYPE_OBJECT),
16
18
  );
17
19
  }
@@ -19,6 +21,7 @@ export class MetaRoot extends MetaData {
19
21
  /** Abstract / package-level fields defined at root (rare; e.g. shared id fields). */
20
22
  fields(): MetaField[] {
21
23
  return this.cached("fields", () =>
24
+ // ADR-0039: own — the metadata root has no super chain (it never `extends`).
22
25
  this.ownChildren().filter((c): c is MetaField => c.type === TYPE_FIELD),
23
26
  );
24
27
  }
@@ -26,6 +29,7 @@ export class MetaRoot extends MetaData {
26
29
  /** Find an object by name. */
27
30
  findObject(name: string): MetaObject | undefined {
28
31
  return this.cached(`findObject:${name}`, () => {
32
+ // ADR-0039: own — the metadata root has no super chain (it never `extends`).
29
33
  const child = this.ownChildByTypeAndName(TYPE_OBJECT, name);
30
34
  return child !== undefined ? (child as MetaObject) : undefined;
31
35
  });
@@ -78,6 +78,7 @@ function walk(model: MetaData, errors: ParseError[], warnings: string[]): void {
78
78
  }
79
79
  }
80
80
 
81
+ // ADR-0039: own — structural walk visiting every physical node once at its site.
81
82
  for (const child of model.ownChildren()) {
82
83
  walk(child, errors, warnings);
83
84
  }
@@ -151,9 +152,9 @@ function validateProjectionLicensing(
151
152
  );
152
153
  }
153
154
 
154
- // OWN sources only: an inherited source is validated on the (projection)
155
- // object that declares it; an inherited source from a non-projection super
156
- // is unreachable without first tripping the extends rule above.
155
+ // ADR-0039: own — OWN sources only: an inherited source is validated on the
156
+ // (projection) object that declares it; an inherited source from a non-projection
157
+ // super is unreachable without first tripping the extends rule above.
157
158
  for (const child of model.ownChildren()) {
158
159
  if (child.type !== TYPE_SOURCE) continue;
159
160
  const kind =
@@ -49,6 +49,8 @@ function findInTree(root: MetaData, fqn: string): MetaData | undefined {
49
49
  if (root.package === undefined && root.name !== "" && root.resolutionKey() === fqn) {
50
50
  return root;
51
51
  }
52
+ // ADR-0039: own — super-resolution walk over the PHYSICAL declaration tree;
53
+ // resolving accessors would be circular while the super chain is being wired.
52
54
  for (const child of root.ownChildren()) {
53
55
  const found = findInTree(child, fqn);
54
56
  if (found !== undefined) return found;
@@ -266,6 +268,7 @@ export function resolveDeferredSupers(root: MetaData): DeferredSuperFailure[] {
266
268
 
267
269
  function walk(node: MetaData, visit: (n: MetaData) => void): void {
268
270
  visit(node);
271
+ // ADR-0039: own — super-resolution walk over the PHYSICAL declaration tree.
269
272
  for (const child of node.ownChildren()) {
270
273
  walk(child, visit);
271
274
  }
@@ -16,6 +16,9 @@ export function validateMaxOccurs(root: MetaData, registry: TypeRegistry): Parse
16
16
 
17
17
  function walk(node: MetaData, registry: TypeRegistry, errors: ParseError[]): void {
18
18
  const counts = new Map<string, MetaData[]>();
19
+ // ADR-0039: own — maxOccurs constrains the AUTHORED declaration layer (how many
20
+ // children a node may DECLARE); inherited children were counted at their
21
+ // declaring parent, so only own children count against this node's limit.
19
22
  for (const child of node.ownChildren()) {
20
23
  const key = `${child.type}.${child.subType}`;
21
24
  const list = counts.get(key);
@@ -35,5 +38,7 @@ function walk(node: MetaData, registry: TypeRegistry, errors: ParseError[]): voi
35
38
  );
36
39
  }
37
40
  }
41
+ // ADR-0039: own — structural walk over the physical declaration tree (each
42
+ // declared node is visited once at its declaration site).
38
43
  for (const child of node.ownChildren()) walk(child, registry, errors);
39
44
  }