@metaobjectsdev/metadata 0.24.0-rc.1 → 0.24.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 (144) hide show
  1. package/dist/attr-schema-validate.d.ts.map +1 -1
  2. package/dist/attr-schema-validate.js +17 -2
  3. package/dist/attr-schema-validate.js.map +1 -1
  4. package/dist/core/field/field-constants.d.ts +18 -5
  5. package/dist/core/field/field-constants.d.ts.map +1 -1
  6. package/dist/core/field/field-constants.js +21 -5
  7. package/dist/core/field/field-constants.js.map +1 -1
  8. package/dist/core/field/field-definition.embedded.d.ts.map +1 -1
  9. package/dist/core/field/field-definition.embedded.js +8 -3
  10. package/dist/core/field/field-definition.embedded.js.map +1 -1
  11. package/dist/core/field/validate-field-mutability.d.ts +22 -0
  12. package/dist/core/field/validate-field-mutability.d.ts.map +1 -0
  13. package/dist/core/field/validate-field-mutability.js +208 -0
  14. package/dist/core/field/validate-field-mutability.js.map +1 -0
  15. package/dist/core/identity/identity-constants.d.ts +12 -0
  16. package/dist/core/identity/identity-constants.d.ts.map +1 -1
  17. package/dist/core/identity/identity-constants.js +15 -0
  18. package/dist/core/identity/identity-constants.js.map +1 -1
  19. package/dist/core/identity/identity-definition.embedded.js +2 -2
  20. package/dist/core/identity/identity-definition.embedded.js.map +1 -1
  21. package/dist/core/index/index-definition.embedded.js +2 -2
  22. package/dist/core/index/index-definition.embedded.js.map +1 -1
  23. package/dist/core/object/object-definition.embedded.js +2 -2
  24. package/dist/core/object/object-definition.embedded.js.map +1 -1
  25. package/dist/core/requirement/meta-requirement.d.ts +1 -2
  26. package/dist/core/requirement/meta-requirement.d.ts.map +1 -1
  27. package/dist/core/requirement/meta-requirement.js +2 -6
  28. package/dist/core/requirement/meta-requirement.js.map +1 -1
  29. package/dist/core/requirement/requirement-constants.d.ts +3 -8
  30. package/dist/core/requirement/requirement-constants.d.ts.map +1 -1
  31. package/dist/core/requirement/requirement-constants.js +2 -9
  32. package/dist/core/requirement/requirement-constants.js.map +1 -1
  33. package/dist/core/requirement/requirement-definition.embedded.d.ts.map +1 -1
  34. package/dist/core/requirement/requirement-definition.embedded.js +8 -46
  35. package/dist/core/requirement/requirement-definition.embedded.js.map +1 -1
  36. package/dist/core/requirement/resolve-claim.d.ts +37 -0
  37. package/dist/core/requirement/resolve-claim.d.ts.map +1 -0
  38. package/dist/core/requirement/resolve-claim.js +83 -0
  39. package/dist/core/requirement/resolve-claim.js.map +1 -0
  40. package/dist/core/vocabulary-rewrite-yaml.d.ts +21 -0
  41. package/dist/core/vocabulary-rewrite-yaml.d.ts.map +1 -0
  42. package/dist/core/vocabulary-rewrite-yaml.js +245 -0
  43. package/dist/core/vocabulary-rewrite-yaml.js.map +1 -0
  44. package/dist/core-types.d.ts.map +1 -1
  45. package/dist/core-types.js +10 -10
  46. package/dist/core-types.js.map +1 -1
  47. package/dist/errors.d.ts +2 -2
  48. package/dist/errors.d.ts.map +1 -1
  49. package/dist/errors.js +37 -4
  50. package/dist/errors.js.map +1 -1
  51. package/dist/index.d.ts +7 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +13 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/library/library-sources.d.ts +11 -0
  56. package/dist/library/library-sources.d.ts.map +1 -1
  57. package/dist/library/library-sources.js +13 -0
  58. package/dist/library/library-sources.js.map +1 -1
  59. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  60. package/dist/loader/meta-data-loader.js +14 -10
  61. package/dist/loader/meta-data-loader.js.map +1 -1
  62. package/dist/loader/validation-passes.d.ts +1 -0
  63. package/dist/loader/validation-passes.d.ts.map +1 -1
  64. package/dist/loader/validation-passes.js +263 -45
  65. package/dist/loader/validation-passes.js.map +1 -1
  66. package/dist/naming-refs.d.ts.map +1 -1
  67. package/dist/naming-refs.js +2 -3
  68. package/dist/naming-refs.js.map +1 -1
  69. package/dist/parser-core.d.ts.map +1 -1
  70. package/dist/parser-core.js +22 -5
  71. package/dist/parser-core.js.map +1 -1
  72. package/dist/persistence/origin/meta-origin.d.ts +0 -10
  73. package/dist/persistence/origin/meta-origin.d.ts.map +1 -1
  74. package/dist/persistence/origin/meta-origin.js +1 -15
  75. package/dist/persistence/origin/meta-origin.js.map +1 -1
  76. package/dist/persistence/origin/origin-constants.d.ts +2 -4
  77. package/dist/persistence/origin/origin-constants.d.ts.map +1 -1
  78. package/dist/persistence/origin/origin-constants.js +9 -7
  79. package/dist/persistence/origin/origin-constants.js.map +1 -1
  80. package/dist/persistence/origin/origin-definition.embedded.d.ts.map +1 -1
  81. package/dist/persistence/origin/origin-definition.embedded.js +5 -21
  82. package/dist/persistence/origin/origin-definition.embedded.js.map +1 -1
  83. package/dist/registry-manifest.d.ts +9 -4
  84. package/dist/registry-manifest.d.ts.map +1 -1
  85. package/dist/registry-manifest.js +9 -4
  86. package/dist/registry-manifest.js.map +1 -1
  87. package/dist/retired-vocabulary.d.ts +92 -0
  88. package/dist/retired-vocabulary.d.ts.map +1 -0
  89. package/dist/retired-vocabulary.js +204 -0
  90. package/dist/retired-vocabulary.js.map +1 -0
  91. package/dist/super-resolve.d.ts +33 -0
  92. package/dist/super-resolve.d.ts.map +1 -1
  93. package/dist/super-resolve.js +56 -1
  94. package/dist/super-resolve.js.map +1 -1
  95. package/dist/template/prompt-definition.embedded.js +1 -1
  96. package/dist/template/prompt-definition.embedded.js.map +1 -1
  97. package/dist/template/template-constants.d.ts +6 -0
  98. package/dist/template/template-constants.d.ts.map +1 -1
  99. package/dist/template/template-constants.js +24 -1
  100. package/dist/template/template-constants.js.map +1 -1
  101. package/dist/template/template-definition.embedded.d.ts.map +1 -1
  102. package/dist/template/template-definition.embedded.js +34 -21
  103. package/dist/template/template-definition.embedded.js.map +1 -1
  104. package/dist/vocabulary-rewrite.d.ts +33 -0
  105. package/dist/vocabulary-rewrite.d.ts.map +1 -0
  106. package/dist/vocabulary-rewrite.js +314 -0
  107. package/dist/vocabulary-rewrite.js.map +1 -0
  108. package/package.json +11 -1
  109. package/src/attr-schema-validate.ts +17 -2
  110. package/src/core/field/field-constants.ts +24 -5
  111. package/src/core/field/field-definition.embedded.ts +8 -3
  112. package/src/core/field/validate-field-mutability.ts +259 -0
  113. package/src/core/identity/identity-constants.ts +16 -0
  114. package/src/core/identity/identity-definition.embedded.ts +2 -2
  115. package/src/core/index/index-definition.embedded.ts +2 -2
  116. package/src/core/object/object-definition.embedded.ts +2 -2
  117. package/src/core/requirement/meta-requirement.ts +1 -7
  118. package/src/core/requirement/requirement-constants.ts +2 -9
  119. package/src/core/requirement/requirement-definition.embedded.ts +8 -46
  120. package/src/core/requirement/resolve-claim.ts +98 -0
  121. package/src/core/vocabulary-rewrite-yaml.ts +267 -0
  122. package/src/core-types.ts +9 -10
  123. package/src/errors.ts +37 -4
  124. package/src/index.ts +29 -1
  125. package/src/library/library-sources.ts +14 -0
  126. package/src/loader/meta-data-loader.ts +15 -10
  127. package/src/loader/validation-passes.ts +323 -50
  128. package/src/naming-refs.ts +1 -3
  129. package/src/parser-core.ts +27 -5
  130. package/src/persistence/origin/meta-origin.ts +0 -16
  131. package/src/persistence/origin/origin-constants.ts +9 -8
  132. package/src/persistence/origin/origin-definition.embedded.ts +5 -21
  133. package/src/registry-manifest.ts +9 -4
  134. package/src/retired-vocabulary.ts +280 -0
  135. package/src/super-resolve.ts +59 -1
  136. package/src/template/prompt-definition.embedded.ts +1 -1
  137. package/src/template/template-constants.ts +27 -1
  138. package/src/template/template-definition.embedded.ts +34 -21
  139. package/src/vocabulary-rewrite.ts +350 -0
  140. package/dist/core/field/validate-field-readonly.d.ts +0 -9
  141. package/dist/core/field/validate-field-readonly.d.ts.map +0 -1
  142. package/dist/core/field/validate-field-readonly.js +0 -128
  143. package/dist/core/field/validate-field-readonly.js.map +0 -1
  144. package/src/core/field/validate-field-readonly.ts +0 -154
@@ -0,0 +1,267 @@
1
+ // server/typescript/packages/metadata/src/core/vocabulary-rewrite-yaml.ts
2
+ //
3
+ // The YAML arm of the raw-document rewriter behind `meta upgrade`.
4
+ //
5
+ // WHY IT IS A SEPARATE MODULE. `vocabulary-rewrite.ts` is reachable from `src/index.ts`, so
6
+ // it may not import `yaml` — that package is Node-only and would land in the browser bundle.
7
+ // This file carries the `yaml` dependency and is reachable ONLY through its own package
8
+ // subpath, which `meta upgrade` dynamic-imports. Same split, and same reason, as
9
+ // `yaml-positions.ts` / `yaml-positions-walker.ts` (see that file's header).
10
+ //
11
+ // WHY IT IS PARSER-DRIVEN WHERE THE JSON ARM IS REGEX-DRIVEN. A hand-rolled YAML mode was
12
+ // tried once and shipped a file-corrupting bug: a multi-item block sequence lost every item
13
+ // but the first, because a hand-written value scanner stops at a newline, and the dominant
14
+ // in-repo flow style (`{ name: x, readOnly: true }`) was not matched at all — so the rename
15
+ // silently did nothing. Both failures are the same failure: YAML's value extent is not
16
+ // derivable by scanning. Here the PARSER reports it. `pair.value.range` covers a four-line
17
+ // block sequence and a one-line flow mapping alike, so neither case is a special case.
18
+ //
19
+ // STILL SURGICAL, NOT PARSE-AND-REPRINT. `doc.toString()` would reflow an adopter's file —
20
+ // line width, quote style, indentation of flow collections — and hand them a diff whose real
21
+ // changes are invisible inside it. So the parse is used only to LOCATE spans; every edit is a
22
+ // span replacement on the original text, and any region not deliberately changed comes back
23
+ // byte-identical. That is the same guarantee the JSON arm makes, by the same means.
24
+ //
25
+ // SIGIL-FREE, PER ADR-0006. YAML authoring writes bare attribute keys (`violation:`) and the
26
+ // desugar re-adds the `@` when lowering to canonical JSON. So a rename emits a BARE key here
27
+ // where the JSON arm emits `"@name"`. A leading `@` is still matched on input — an author who
28
+ // wrote one gets it fixed rather than skipped — but is never introduced.
29
+
30
+ import { LineCounter, isMap, isSeq, parseDocument, type Node, type Pair } from "yaml";
31
+ import {
32
+ RETIRED_VOCABULARY,
33
+ note,
34
+ scopeMatches,
35
+ type RetiredEntry,
36
+ } from "../retired-vocabulary.js";
37
+ import type { RewriteChange, RewriteRefusal, RewriteOpts, RewriteResult } from "../vocabulary-rewrite.js";
38
+
39
+ /**
40
+ * A rewrite result that can also report "I could not read this file".
41
+ *
42
+ * A YAML document that does not parse yields no changes and no refusals, which is
43
+ * indistinguishable from a clean one — and a fixer that reports a file it could not open as
44
+ * clean is the exact defect this arm was written to remove (#339). The flag makes the caller
45
+ * say so out loud.
46
+ */
47
+ export interface YamlRewriteResult extends RewriteResult {
48
+ readonly unparseable: boolean;
49
+ }
50
+
51
+ /** A canonical node key: `<type>.<subType>`. Identical to the JSON arm's scope shape. */
52
+ const TYPE_KEY = /^[a-z][A-Za-z0-9]*\.[A-Za-z0-9_*]+$/;
53
+
54
+ /** `0.24.0` → `[0,24,0]`, for an ordered comparison rather than a string one. */
55
+ function parts(v: string): number[] {
56
+ return v.split(".").map((n) => Number.parseInt(n, 10) || 0);
57
+ }
58
+
59
+ function atOrBefore(a: string, b: string): boolean {
60
+ const [x, y] = [parts(a), parts(b)];
61
+ for (let i = 0; i < Math.max(x.length, y.length); i++) {
62
+ const d = (x[i] ?? 0) - (y[i] ?? 0);
63
+ if (d !== 0) return d < 0;
64
+ }
65
+ return true;
66
+ }
67
+
68
+ /** The plain string a mapping key carries, or undefined when it is not a plain scalar. */
69
+ function keyText(key: unknown): string | undefined {
70
+ const v = (key as { value?: unknown } | null)?.value;
71
+ return typeof v === "string" ? v : undefined;
72
+ }
73
+
74
+ /** Source offsets of a pair's key and of the end of its value. */
75
+ function pairSpan(pair: Pair): { keyStart: number; keyEnd: number; valueEnd: number } | undefined {
76
+ const k = pair.key as { range?: [number, number, number] } | null;
77
+ const v = pair.value as { range?: [number, number, number] } | null;
78
+ if (k?.range === undefined) return undefined;
79
+ return {
80
+ keyStart: k.range[0],
81
+ keyEnd: k.range[1],
82
+ // A valueless key (`verifiedBy:` with nothing after it) still has to be removable.
83
+ valueEnd: v?.range?.[1] ?? k.range[1],
84
+ };
85
+ }
86
+
87
+ /**
88
+ * The span to delete so that removing a pair leaves loadable YAML.
89
+ *
90
+ * Two shapes, and they need opposite treatment — which is precisely what the previous
91
+ * hand-rolled attempt got wrong by handling only one.
92
+ */
93
+ function dropSpan(
94
+ source: string,
95
+ span: { keyStart: number; valueEnd: number },
96
+ flow: boolean,
97
+ ): { start: number; end: number } | undefined {
98
+ let { keyStart: start } = span;
99
+ let end = span.valueEnd;
100
+
101
+ if (flow) {
102
+ // `{ a: 1, readOnly: true }` — take a trailing comma if there is one, else a preceding
103
+ // one, so the mapping ends up with neither a dangling nor a doubled separator.
104
+ //
105
+ // The probe must not commit: scanning forward over the spaces and THEN finding `}`
106
+ // rather than `,` would leave `end` past the space that separates the survivor from the
107
+ // brace, silently reformatting `{ name: x }` into `{ name: x}`.
108
+ let probe = end;
109
+ while (probe < source.length && /[ \t]/.test(source[probe] ?? "")) probe++;
110
+ if (source[probe] === ",") {
111
+ end = probe + 1;
112
+ while (end < source.length && /[ \t]/.test(source[end] ?? "")) end++;
113
+ } else {
114
+ let back = start;
115
+ while (back > 0 && /\s/.test(source[back - 1] ?? "")) back--;
116
+ if (source[back - 1] === ",") start = back - 1;
117
+ }
118
+ return { start, end };
119
+ }
120
+
121
+ // Block mapping — the pair owns whole lines. Absorb its indentation and its line
122
+ // terminator, so removal leaves neither a ragged line nor a blank one.
123
+ while (start > 0 && /[ \t]/.test(source[start - 1] ?? "")) start--;
124
+
125
+ // A pair that is the first key of a block SEQUENCE item (`- verifiedBy: x`) shares its
126
+ // line with the `-`. Deleting it would strand the dash and silently change the sequence's
127
+ // shape, so this refuses rather than guesses — the caller reports it as needing a hand.
128
+ if (source[start - 1] === "-") return undefined;
129
+
130
+ // A multi-line value (a block sequence) already ends ON the newline that closes its last
131
+ // item, so the terminator is spent. Consuming another one here would delete the FOLLOWING
132
+ // key — which is the multi-item-sequence corruption this arm exists to avoid, arriving by
133
+ // a different route.
134
+ if (end === 0 || source[end - 1] !== "\n") {
135
+ while (end < source.length && /[ \t]/.test(source[end] ?? "")) end++;
136
+ // A trailing comment on the key's own line goes with the key it annotates.
137
+ if (source[end] === "#") while (end < source.length && source[end] !== "\n") end++;
138
+ if (source[end] === "\n") end++;
139
+ }
140
+ return { start, end };
141
+ }
142
+
143
+ /** Visit every mapping pair with the `<type>.<subType>` scope governing it. */
144
+ function eachPair(
145
+ node: unknown,
146
+ scope: string | undefined,
147
+ visit: (pair: Pair, scope: string | undefined, flow: boolean) => void,
148
+ ): void {
149
+ if (isMap(node)) {
150
+ const flow = node.flow === true;
151
+ for (const pair of node.items) {
152
+ const k = keyText(pair.key);
153
+ visit(pair, scope, flow);
154
+ // A type key scopes its own BODY, not itself — so the pair above is reported under the
155
+ // enclosing scope while its value descends under this one.
156
+ const inner = k !== undefined && TYPE_KEY.test(k) ? k : scope;
157
+ if (pair.value != null) eachPair(pair.value, inner, visit);
158
+ }
159
+ return;
160
+ }
161
+ if (isSeq(node)) {
162
+ for (const item of node.items) eachPair(item as Node, scope, visit);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Rewrite retired vocabulary in one raw YAML metadata document.
168
+ *
169
+ * Pure: no filesystem, no loader, no registry. Mirrors `rewriteDocument`'s contract exactly —
170
+ * same result shape, same scoping rule, same refusal policy — so `meta upgrade` reports a
171
+ * YAML estate and a JSON estate identically.
172
+ */
173
+ export function rewriteYamlDocument(source: string, opts: RewriteOpts = {}): YamlRewriteResult {
174
+ const changes: RewriteChange[] = [];
175
+ const refusals: RewriteRefusal[] = [];
176
+ const edits: { start: number; end: number; text: string }[] = [];
177
+
178
+ const lineCounter = new LineCounter();
179
+ const doc = parseDocument(source, { lineCounter, keepSourceTokens: true });
180
+ // A document we cannot parse is a document we must not edit. Reporting nothing here is
181
+ // correct: `meta verify` owns malformed YAML, and guessing at spans in a broken file is
182
+ // how a fixer corrupts one.
183
+ if (doc.errors.length > 0 || doc.contents == null) {
184
+ return { text: source, changes, refusals, unparseable: true };
185
+ }
186
+
187
+ const lineOf = (offset: number): number => lineCounter.linePos(offset).line;
188
+ const inWindow = (e: RetiredEntry): boolean =>
189
+ opts.maxVersion === undefined || atOrBefore(e.since, opts.maxVersion);
190
+
191
+ eachPair(doc.contents, undefined, (pair, scope, flow) => {
192
+ const key = keyText(pair.key);
193
+ if (key === undefined) return;
194
+ const span = pairSpan(pair);
195
+ if (span === undefined) return;
196
+
197
+ // A retired SUBTYPE has no attribute to rewrite — the node itself has to be re-modelled,
198
+ // which is the adopter's judgment. Reporting it is what keeps `meta upgrade` from exiting
199
+ // 0 on a document that still will not load.
200
+ if (TYPE_KEY.test(key)) {
201
+ for (const entry of RETIRED_VOCABULARY) {
202
+ if (entry.isSubTypeRetirement !== true || !inWindow(entry)) continue;
203
+ if (`${entry.type}.${entry.subType}` !== key) continue;
204
+ refusals.push({ ...note(entry), subject: key, line: lineOf(span.keyStart) });
205
+ }
206
+ return;
207
+ }
208
+
209
+ // Sigil-free authoring is the norm, but an authored `@` must not make a retirement
210
+ // invisible.
211
+ const bare = key.startsWith("@") ? key.slice(1) : key;
212
+ const raw = source.slice(span.keyEnd, span.valueEnd).replace(/^\s*:\s*/, "").trim();
213
+ const line = lineOf(span.keyStart);
214
+
215
+ for (const entry of RETIRED_VOCABULARY) {
216
+ if (entry.attr !== bare || !inWindow(entry)) continue;
217
+ if (scope === undefined || !scopeMatches(entry, scope)) continue;
218
+
219
+ // A VALUE-scoped retirement only fires on the retired values — the same attribute with
220
+ // a live value must come through untouched.
221
+ if (entry.attrValues !== undefined && !entry.attrValues.some((v) => raw === v || raw === `"${v}"` || raw === `'${v}'`)) {
222
+ continue;
223
+ }
224
+
225
+ const refuse = (): void => {
226
+ refusals.push({ ...note(entry), subject: `@${bare}`, ...(raw !== "" ? { value: raw } : {}), line });
227
+ };
228
+ const drop = (): void => {
229
+ const d = dropSpan(source, span, flow);
230
+ // Undeletable in place (a sequence item's leading key) — report it instead of
231
+ // producing YAML that parses as something else.
232
+ if (d === undefined) {
233
+ refuse();
234
+ return;
235
+ }
236
+ edits.push({ ...d, text: "" });
237
+ changes.push({ attr: bare, from: bare, to: "(removed)", line });
238
+ };
239
+
240
+ const rw = entry.rewrite;
241
+ if (rw === undefined) refuse();
242
+ else if (rw.kind === "renameAttr") {
243
+ // Preserve the author's quoting style; YAML keys are usually bare, but a quoted key
244
+ // must stay quoted or the surrounding style stops being self-consistent.
245
+ const rawKey = source.slice(span.keyStart, span.keyEnd);
246
+ const q = rawKey[0] === '"' || rawKey[0] === "'" ? rawKey[0] : "";
247
+ edits.push({ start: span.keyStart, end: span.keyEnd, text: `${q}${rw.to}${q}` });
248
+ changes.push({ attr: bare, from: bare, to: rw.to, line });
249
+ } else if (rw.kind === "dropAttr") drop();
250
+ else if (raw === String(rw.fromValue) || raw === `"${rw.fromValue}"` || raw === `'${rw.fromValue}'`) {
251
+ const valText = typeof rw.toValue === "string" ? String(rw.toValue) : JSON.stringify(rw.toValue);
252
+ edits.push({ start: span.keyStart, end: span.keyEnd, text: rw.toAttr });
253
+ edits.push({ start: span.keyEnd, end: span.valueEnd, text: `: ${valText}` });
254
+ changes.push({ attr: bare, from: `${bare}: ${raw}`, to: `${rw.toAttr}: ${valText}`, line });
255
+ } else if (rw.otherwise === "drop") drop();
256
+ else refuse();
257
+ }
258
+ });
259
+
260
+ // Applied right-to-left against the ORIGINAL text: rewriting incrementally would invalidate
261
+ // every later offset.
262
+ edits.sort((a, b) => b.start - a.start);
263
+ let text = source;
264
+ for (const e of edits) text = text.slice(0, e.start) + e.text + text.slice(e.end);
265
+
266
+ return { text, changes, refusals, unparseable: false };
267
+ }
package/src/core-types.ts CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  import { MetaRelationship } from "./core/relationship/meta-relationship.js";
40
40
  import { MetaLayout } from "./presentation/layout/meta-layout.js";
41
41
  import { MetaSource } from "./persistence/source/meta-source.js";
42
- import { MetaOrigin, MetaPassthroughOrigin, MetaAggregateOrigin, MetaCollectionOrigin, MetaComputedOrigin, MetaFirstOrigin } from "./persistence/origin/meta-origin.js";
42
+ import { MetaOrigin, MetaPassthroughOrigin, MetaAggregateOrigin, MetaComputedOrigin, MetaFirstOrigin } from "./persistence/origin/meta-origin.js";
43
43
  import { defineProviderFromData, type FactoryMap } from "./provider-data.js";
44
44
  import { FIELD_DEFINITION } from "./core/field/field-definition.embedded.js";
45
45
  import { OBJECT_DEFINITION } from "./core/object/object-definition.embedded.js";
@@ -104,7 +104,6 @@ import {
104
104
  ORIGIN_SUBTYPES,
105
105
  ORIGIN_SUBTYPE_PASSTHROUGH,
106
106
  ORIGIN_SUBTYPE_AGGREGATE,
107
- ORIGIN_SUBTYPE_COLLECTION,
108
107
  ORIGIN_SUBTYPE_COMPUTED,
109
108
  ORIGIN_SUBTYPE_FIRST,
110
109
  } from "./persistence/origin/origin-constants.js";
@@ -169,7 +168,6 @@ const IDENTITY_CLASS_MAP = new Map<string, NodeConstructor>([
169
168
  const ORIGIN_CLASS_MAP = new Map<string, NodeConstructor>([
170
169
  [ORIGIN_SUBTYPE_PASSTHROUGH, MetaPassthroughOrigin],
171
170
  [ORIGIN_SUBTYPE_AGGREGATE, MetaAggregateOrigin],
172
- [ORIGIN_SUBTYPE_COLLECTION, MetaCollectionOrigin],
173
171
  [ORIGIN_SUBTYPE_COMPUTED, MetaComputedOrigin],
174
172
  [ORIGIN_SUBTYPE_FIRST, MetaFirstOrigin],
175
173
  ]);
@@ -377,8 +375,7 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
377
375
  // defineProviderFromData lowers it to TypeDefinitions; the factory (behavior)
378
376
  // stays code via ORIGIN_FACTORIES, dispatching subType→class:
379
377
  // passthrough → MetaPassthroughOrigin, aggregate → MetaAggregateOrigin,
380
- // collection → MetaCollectionOrigin, base (and any unmapped subtype) →
381
- // MetaOrigin (fallback).
378
+ // base (and any unmapped subtype) → MetaOrigin (fallback).
382
379
  // FR-033 S1-simple: origin is an ATTR-ONLY type — the "any attr" wildcard child
383
380
  // rule is DROPPED (strict/fail-closed) and childRules are left EMPTY; the named
384
381
  // attrs still enforce strictly, a misplaced STRUCTURAL child is now
@@ -400,13 +397,15 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
400
397
  // (ADR-0011). Four subtypes: base + prompt + output + toolcall; attr-only
401
398
  // children. A single MetaTemplate class backs every subtype (mirrors source);
402
399
  // per-subtype attr schemas drive validation (prompt + output require
403
- // @payloadRef + @textRef + @format closed enum; prompt adds the LLM overlay;
404
- // output adds @promptStyle + @kind/email part-refs; toolcall has its own set —
405
- // @toolName + @payloadRef, no @textRef requirement since toolcalls have no
400
+ // @payloadRef + @textRef + @format closed enum; prompt adds the LLM overlay
401
+ // plus the ADR-0052 inbound half (@responseRef/@promptStyle/@responseFormat);
402
+ // output adds @kind/email part-refs and is OUTBOUND ONLY; toolcall has its own
403
+ // set — @toolName + @payloadRef, no @textRef requirement since toolcalls have no
406
404
  // renderable body).
407
405
  // FR-033: the template provider's declarative definition (the 4-subtype
408
406
  // vocabulary + the full per-subtype attr constraints — incl. @format/@promptStyle/
409
- // @kind closed-enum allowedValues + defaults + required @payloadRef/@toolName —
407
+ // @responseFormat/@kind closed-enum allowedValues + defaults + required
408
+ // @payloadRef/@toolName —
410
409
  // + real descriptions + the FR-004/ADR-0011 rules prose) is externalized to
411
410
  // spec/metamodel/template.json, embedded at build into TEMPLATE_DEFINITION.
412
411
  // defineProviderFromData lowers it to TypeDefinitions; the factory (behavior)
@@ -525,7 +524,7 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
525
524
  }
526
525
  // NOTE: @implementedBy is deliberately NOT declared as a loader `references`
527
526
  // descriptor. That pass always ERRORS on an unresolved target, and a
528
- // requirement with status `abandoned`/`superseded` exists precisely to name
527
+ // requirement with status `planned` names nodes that do not exist YET, so declaring
529
528
  // nodes that are GONE — declaring it here would make the entries that carry
530
529
  // the mechanism's only controlled evidence fail to load. The loader owns what
531
530
  // is unconditional (the status enum, shape, levels); `meta verify` owns the
package/src/errors.ts CHANGED
@@ -138,8 +138,9 @@ export const ERROR_CODES = [
138
138
  "ERR_PHYSICAL_NAME_KIND_MISMATCH",
139
139
  "ERR_PHYSICAL_NAME_MULTIPLE",
140
140
  // FR-013 — field-level @readOnly cross-attribute validation.
141
+ "ERR_MUTABILITY_AUTOSET_CONFLICT",
142
+ "ERR_MUTABILITY_DOWNGRADE",
141
143
  "ERR_READONLY_ASSIGNED_PRIMARY",
142
- "ERR_READONLY_DOWNGRADE",
143
144
  // FR-015 — source.rdb @parameterRef typed-input validation.
144
145
  "ERR_PARAMETER_REF_UNRESOLVED",
145
146
  "ERR_PARAMETER_REF_NOT_VALUE_OBJECT",
@@ -158,6 +159,23 @@ export const ERROR_CODES = [
158
159
  // SP-H Unit9 — @filterable: true on a field subtype with no filter-operator
159
160
  // band (e.g. field.object). Would silently generate an empty-ops filter.
160
161
  "ERR_FILTERABLE_UNSUPPORTED_SUBTYPE",
162
+ // #335 Half B — @sortable: true on an array field or a subtype with no
163
+ // filter-operator band (e.g. field.object). Would silently emit a sort
164
+ // entry over a column no dialect can ORDER BY.
165
+ "ERR_SORTABLE_UNSUPPORTED_SUBTYPE",
166
+ // #335 Half A — a whole-object @agg:collect (no @of; the carrying field.object
167
+ // rolls related rows up as its declared @objectRef value object) is malformed:
168
+ // carrier is not a field.object with @objectRef, @via absent, @distinct declared
169
+ // (refused — a no-op whenever the value object carries the primary key), an
170
+ // @orderBy key not on the @via TERMINAL entity, or a member's declared type
171
+ // disagreeing with the matched terminal field's. Distinct from ERR_INVALID_ORIGIN
172
+ // so a fixture can tell this arm from a loader that still requires @of.
173
+ "ERR_COLLECT_WHOLE_OBJECT",
174
+ // #335 Half A — a whole-object @agg:collect's value-object member has no
175
+ // matching field (by name) on the @via terminal entity. The lowering
176
+ // projects exactly the declared members; failing open here is how #270
177
+ // turned a curated value object into the full entity.
178
+ "ERR_COLLECT_MEMBER_UNRESOLVED",
161
179
  // ADR-0023 — a registration was attempted against a registry sealed after its
162
180
  // agreed metamodel-provider bootstrap. Codegen cannot invent metamodel attrs.
163
181
  "ERR_REGISTRY_SEALED",
@@ -173,8 +191,10 @@ export const ERROR_CODES = [
173
191
  // clash / required-child cycle / conflicting attr redefinition. The detail names
174
192
  // which of the six checks fired and the offending type(s).
175
193
  "ERR_INVALID_METAMODEL_CONSTRAINT",
176
- // index.lookup field-resolution: @fields is empty or names a field that does
177
- // not exist on the owning entity's effective (resolved via extends) field set.
194
+ // Index-key resolution for index.lookup AND identity.secondary (#342) the key is
195
+ // @fields XOR @expr: neither declared, BOTH declared (@expr is used INSTEAD of
196
+ // @fields), whichever is declared supplies no key, or a named field does not exist
197
+ // on the owning entity's effective (resolved via extends) field set.
178
198
  "ERR_INVALID_INDEX",
179
199
  // #195 — origin.computed @expr: the expression tree's inferred root type does
180
200
  // not equal the carrying field's declared field.<subType>. A computed column's
@@ -208,6 +228,18 @@ export const ERROR_CODES = [
208
228
  // an integer array. An array-of-enum stays string-backed: drop @intValueMap,
209
229
  // or make the field scalar.
210
230
  "ERR_ENUM_INT_VALUE_MAP_ARRAY",
231
+ // Phase-1 metadata-source-resolution — a path source declared in
232
+ // .metaobjects/config.json does not exist on disk.
233
+ "ERR_SOURCE_UNRESOLVED",
234
+ // Phase-1 metadata-source-resolution — a declared source kind (resource or
235
+ // package) is not supported by this toolchain.
236
+ "ERR_SOURCE_KIND_UNSUPPORTED",
237
+ // Phase-1 metadata-source-resolution — a scope include/exclude package
238
+ // pattern is malformed (empty pattern or empty :: segment).
239
+ "ERR_SCOPE_PATTERN_INVALID",
240
+ // Phase-1 metadata-source-resolution — no metadata collection was discovered:
241
+ // no config declaring sources, and no default metaobjects/ directory.
242
+ "ERR_COLLECTION_NOT_FOUND",
211
243
  "ERR_UNKNOWN",
212
244
  ] as const;
213
245
 
@@ -226,7 +258,8 @@ export const WARNING_CODES = [
226
258
  // FR-013 — @readOnly on a field child of object.value. The persistence
227
259
  // implication does not apply to value-objects; the attr is retained for
228
260
  // language-specific record/struct treatment (e.g. Kotlin `val` vs `var`).
229
- "WARN_READONLY_VALUE_OBJECT",
261
+ "WARN_MUTABILITY_VALUE_OBJECT",
262
+ "WARN_MUTABILITY_READONLY_HOST",
230
263
  // #208 — a host object whose source carries @unmanaged also declares an
231
264
  // origin.*-bearing (derived) field. Deliberate asymmetry with
232
265
  // ERR_ORIGIN_UNDER_SQL_BODY: @unmanaged acts on nothing (the tool never
package/src/index.ts CHANGED
@@ -27,6 +27,14 @@ export * from "./shared/base-types.js";
27
27
  export * from "./shared/structural.js";
28
28
  export * from "./core/object/object-constants.js";
29
29
  export * from "./core/field/field-constants.js";
30
+ // FR-037 R1 — THE mutability accessors. Every consumer deciding "may this be
31
+ // written, and when?" must go through these rather than reading the attr, so the
32
+ // absent-means-readWrite default lives in exactly one place per port.
33
+ export {
34
+ fieldMutability,
35
+ isReadOnlyMutability,
36
+ isWriteOnceMutability,
37
+ } from "./core/field/validate-field-mutability.js";
30
38
  export * from "./core/attr/attr-constants.js";
31
39
  export * from "./core/documentation/doc-constants.js";
32
40
  export * from "./core/validator/validator-constants.js";
@@ -34,6 +42,13 @@ export * from "./core/identity/identity-constants.js";
34
42
  export * from "./core/index/index-constants.js";
35
43
  export * from "./core/requirement/requirement-constants.js";
36
44
  export { MetaRequirement } from "./core/requirement/meta-requirement.js";
45
+ // Shared `@implementedBy` resolution — one resolver for the CLI's requirement
46
+ // checks and codegen's requirement-test fan-out (FR-038).
47
+ export {
48
+ resolveClaim,
49
+ resolveClaimTarget,
50
+ resolveMember,
51
+ } from "./core/requirement/resolve-claim.js";
37
52
  export * from "./core/relationship/relationship-constants.js";
38
53
  export * from "./core/query/query-constants.js";
39
54
  export * from "./persistence/source/source-constants.js";
@@ -121,7 +136,6 @@ export {
121
136
  MetaOrigin,
122
137
  MetaPassthroughOrigin,
123
138
  MetaAggregateOrigin,
124
- MetaCollectionOrigin,
125
139
  MetaComputedOrigin,
126
140
  MetaFirstOrigin,
127
141
  } from "./persistence/origin/meta-origin.js";
@@ -273,3 +287,17 @@ export {
273
287
  stripPackage,
274
288
  } from "./naming.js";
275
289
  export type { EntityNameMap, ColumnNamingStrategy } from "./naming.js";
290
+
291
+ // Retired vocabulary: the map the loader reads to explain a retirement, and the raw-document
292
+ // rewriter `meta upgrade` drives from the SAME entries — so the error message and the fix
293
+ // cannot drift apart.
294
+ export {
295
+ RETIRED_VOCABULARY,
296
+ retiredAttr,
297
+ retiredAttrValue,
298
+ retiredSubType,
299
+ retirementHint,
300
+ } from "./retired-vocabulary.js";
301
+ export type { RetirementNote, RetiredEntry, VocabularyRewrite } from "./retired-vocabulary.js";
302
+ export { rewriteDocument } from "./vocabulary-rewrite.js";
303
+ export type { RewriteResult, RewriteChange, RewriteRefusal, RewriteOpts } from "./vocabulary-rewrite.js";
@@ -51,6 +51,20 @@ function getLibraryDir(): string | undefined {
51
51
  return (_cache ??= { dir: libraryDirOnDisk() }).dir;
52
52
  }
53
53
 
54
+ /**
55
+ * The library package names this build ships, sorted.
56
+ *
57
+ * `librarySources` skips an unrecognised package silently — the right behaviour for a
58
+ * programmatic caller asking for something a given version may not ship. A name a human
59
+ * typed into a config file is a different case: skipping it silently resurfaces later as
60
+ * `ERR_UNRESOLVED_SUPER` pointing at the adopter's own metadata, which is the wrong place
61
+ * to go looking. Config readers use this to refuse an unknown name and say what IS
62
+ * available (Python's `project_config` draws the same line, in the same place).
63
+ */
64
+ export function knownLibraryPackages(): string[] {
65
+ return Object.keys(REFS_BY_PACKAGE).sort();
66
+ }
67
+
54
68
  /**
55
69
  * Returns a list of `MetaDataSource` instances for the requested library packages.
56
70
  *
@@ -18,16 +18,16 @@ import { ParseError } from "../errors.js";
18
18
  import type { LoaderWarning } from "../source.js";
19
19
  import { codeSource, resolvedSource } from "../source.js";
20
20
  import { parseJson } from "../parser-json.js";
21
- import { validateDataGridSortFields, validateFilterableHasIndex, validateFilterableHasSupportedOps, validateOriginPaths, validateDerivedFieldProvidability, validateDataGridFilterValues, validateFieldObjectStorage, validateFieldMap, validateTemplatePayloadRefs, validateFieldDefaults, validateRelationships, validateIndexLookupFields, validateProjectionFilter } from "./validation-passes.js";
21
+ import { validateDataGridSortFields, validateFilterableHasIndex, validateFilterableHasSupportedOps, validateSortableHasSupportedSubtype, validateOriginPaths, validateDerivedFieldProvidability, validateDataGridFilterValues, validateFieldObjectStorage, validateFieldMap, validateTemplatePayloadRefs, validateFieldDefaults, validateRelationships, validateIndexLookupFields, validateProjectionFilter } from "./validation-passes.js";
22
22
  import { runRegisteredValidation } from "./validation-registry.js";
23
23
  import { validateSourceRoles } from "../persistence/source/validate-source-roles.js";
24
24
  import { validateSourceEscapes } from "../persistence/source/validate-source-escapes.js";
25
25
  import { validateSourcePhysicalNames } from "../persistence/source/validate-source-physical-names.js";
26
26
  import { validateSourceParameterRef } from "../persistence/source/validate-source-parameter-ref.js";
27
- import { validateFieldReadOnly } from "../core/field/validate-field-readonly.js";
27
+ import { validateFieldMutability } from "../core/field/validate-field-mutability.js";
28
28
  import { validateEnumNormalizeAmbiguity } from "../core/field/validate-enum-normalize-ambiguity.js";
29
29
  import { validateDiscriminator } from "../core/object/validate-discriminator.js";
30
- import { resolveDeferredSupers } from "../super-resolve.js";
30
+ import { resolveDeferredSupers, EXTENDS_TARGET_MISMATCH_RULE } from "../super-resolve.js";
31
31
  import { validateSubtypeRules } from "../subtype-rules.js";
32
32
  import { validateMaxOccurs } from "../validate-max-occurs.js";
33
33
  import { validateIdentityPassthrough } from "../core/identity/validate-identity-passthrough.js";
@@ -534,7 +534,7 @@ export class MetaDataLoader {
534
534
  const t = failure.target;
535
535
  errors.push(
536
536
  new ParseError(
537
- `the extends target '${failure.ref}' is ${t?.type}.${t?.subType} but the extending node '${failure.nodeFqn}' is ${r?.type}.${r?.subType} — a dotted extends must target a node of the same type and subtype`,
537
+ `the extends target '${failure.ref}' is ${t?.type}.${t?.subType} but the extending node '${failure.nodeFqn}' is ${r?.type}.${r?.subType} — ${EXTENDS_TARGET_MISMATCH_RULE}`,
538
538
  {
539
539
  code: "ERR_EXTENDS_TARGET_MISMATCH",
540
540
  source: resolvedSource(failure.source, failure.nodeFqn, failure.ref),
@@ -578,6 +578,10 @@ export class MetaDataLoader {
578
578
  // (would silently generate a filter that rejects every request).
579
579
  errors.push(...validateFilterableHasSupportedOps(root));
580
580
 
581
+ // #335 Half B — @sortable on an array field or a subtype with no operator
582
+ // band → error (would silently emit a sort entry no dialect can execute).
583
+ errors.push(...validateSortableHasSupportedSubtype(root));
584
+
581
585
  // Sixth pass: origin path validation — validates passthrough.@from,
582
586
  // aggregate.@of, and .@via relationship chains.
583
587
  errors.push(...validateOriginPaths(root));
@@ -650,12 +654,13 @@ export class MetaDataLoader {
650
654
  errors.push(...physicalNameResult.errors);
651
655
  envelopeWarnings.push(...physicalNameResult.warnings);
652
656
 
653
- // FR-013 — field-level @readOnly cross-attribute rules
654
- // (ERR_READONLY_DOWNGRADE / ERR_READONLY_ASSIGNED_PRIMARY /
655
- // WARN_READONLY_VALUE_OBJECT).
656
- const readOnlyResult = validateFieldReadOnly(root);
657
- errors.push(...readOnlyResult.errors);
658
- envelopeWarnings.push(...readOnlyResult.warnings);
657
+ // FR-037 R1 — field-level @mutability cross-attribute rules
658
+ // (ERR_MUTABILITY_AUTOSET_CONFLICT / ERR_MUTABILITY_DOWNGRADE /
659
+ // ERR_READONLY_ASSIGNED_PRIMARY / WARN_MUTABILITY_VALUE_OBJECT /
660
+ // WARN_MUTABILITY_READONLY_HOST).
661
+ const mutabilityResult = validateFieldMutability(root);
662
+ errors.push(...mutabilityResult.errors);
663
+ envelopeWarnings.push(...mutabilityResult.warnings);
659
664
 
660
665
  // Authoring guard — a field.enum vocabulary that is ambiguous under the
661
666
  // default `@normalize: strip` (a member equal to the concatenation of