@metaobjectsdev/metadata 0.24.0-rc.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) 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/object/object-definition.embedded.js +2 -2
  20. package/dist/core/object/object-definition.embedded.js.map +1 -1
  21. package/dist/core/requirement/meta-requirement.d.ts +1 -2
  22. package/dist/core/requirement/meta-requirement.d.ts.map +1 -1
  23. package/dist/core/requirement/meta-requirement.js +2 -6
  24. package/dist/core/requirement/meta-requirement.js.map +1 -1
  25. package/dist/core/requirement/requirement-constants.d.ts +3 -8
  26. package/dist/core/requirement/requirement-constants.d.ts.map +1 -1
  27. package/dist/core/requirement/requirement-constants.js +2 -9
  28. package/dist/core/requirement/requirement-constants.js.map +1 -1
  29. package/dist/core/requirement/requirement-definition.embedded.d.ts.map +1 -1
  30. package/dist/core/requirement/requirement-definition.embedded.js +8 -46
  31. package/dist/core/requirement/requirement-definition.embedded.js.map +1 -1
  32. package/dist/core/requirement/resolve-claim.d.ts +37 -0
  33. package/dist/core/requirement/resolve-claim.d.ts.map +1 -0
  34. package/dist/core/requirement/resolve-claim.js +83 -0
  35. package/dist/core/requirement/resolve-claim.js.map +1 -0
  36. package/dist/core-types.d.ts.map +1 -1
  37. package/dist/core-types.js +10 -10
  38. package/dist/core-types.js.map +1 -1
  39. package/dist/errors.d.ts +2 -2
  40. package/dist/errors.d.ts.map +1 -1
  41. package/dist/errors.js +16 -2
  42. package/dist/errors.js.map +1 -1
  43. package/dist/index.d.ts +7 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +13 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/library/library-sources.d.ts +11 -0
  48. package/dist/library/library-sources.d.ts.map +1 -1
  49. package/dist/library/library-sources.js +13 -0
  50. package/dist/library/library-sources.js.map +1 -1
  51. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  52. package/dist/loader/meta-data-loader.js +10 -9
  53. package/dist/loader/meta-data-loader.js.map +1 -1
  54. package/dist/loader/validation-passes.js +2 -2
  55. package/dist/loader/validation-passes.js.map +1 -1
  56. package/dist/naming-refs.d.ts.map +1 -1
  57. package/dist/naming-refs.js +2 -3
  58. package/dist/naming-refs.js.map +1 -1
  59. package/dist/parser-core.d.ts.map +1 -1
  60. package/dist/parser-core.js +22 -5
  61. package/dist/parser-core.js.map +1 -1
  62. package/dist/persistence/origin/meta-origin.d.ts +0 -10
  63. package/dist/persistence/origin/meta-origin.d.ts.map +1 -1
  64. package/dist/persistence/origin/meta-origin.js +1 -15
  65. package/dist/persistence/origin/meta-origin.js.map +1 -1
  66. package/dist/persistence/origin/origin-constants.d.ts +2 -4
  67. package/dist/persistence/origin/origin-constants.d.ts.map +1 -1
  68. package/dist/persistence/origin/origin-constants.js +9 -7
  69. package/dist/persistence/origin/origin-constants.js.map +1 -1
  70. package/dist/persistence/origin/origin-definition.embedded.d.ts.map +1 -1
  71. package/dist/persistence/origin/origin-definition.embedded.js +0 -16
  72. package/dist/persistence/origin/origin-definition.embedded.js.map +1 -1
  73. package/dist/registry-manifest.d.ts +9 -4
  74. package/dist/registry-manifest.d.ts.map +1 -1
  75. package/dist/registry-manifest.js +9 -4
  76. package/dist/registry-manifest.js.map +1 -1
  77. package/dist/retired-vocabulary.d.ts +92 -0
  78. package/dist/retired-vocabulary.d.ts.map +1 -0
  79. package/dist/retired-vocabulary.js +204 -0
  80. package/dist/retired-vocabulary.js.map +1 -0
  81. package/dist/super-resolve.d.ts +33 -0
  82. package/dist/super-resolve.d.ts.map +1 -1
  83. package/dist/super-resolve.js +56 -1
  84. package/dist/super-resolve.js.map +1 -1
  85. package/dist/template/prompt-definition.embedded.js +1 -1
  86. package/dist/template/prompt-definition.embedded.js.map +1 -1
  87. package/dist/template/template-constants.d.ts +6 -0
  88. package/dist/template/template-constants.d.ts.map +1 -1
  89. package/dist/template/template-constants.js +24 -1
  90. package/dist/template/template-constants.js.map +1 -1
  91. package/dist/template/template-definition.embedded.d.ts.map +1 -1
  92. package/dist/template/template-definition.embedded.js +34 -21
  93. package/dist/template/template-definition.embedded.js.map +1 -1
  94. package/dist/vocabulary-rewrite.d.ts +33 -0
  95. package/dist/vocabulary-rewrite.d.ts.map +1 -0
  96. package/dist/vocabulary-rewrite.js +310 -0
  97. package/dist/vocabulary-rewrite.js.map +1 -0
  98. package/package.json +6 -1
  99. package/src/attr-schema-validate.ts +17 -2
  100. package/src/core/field/field-constants.ts +24 -5
  101. package/src/core/field/field-definition.embedded.ts +8 -3
  102. package/src/core/field/validate-field-mutability.ts +259 -0
  103. package/src/core/identity/identity-constants.ts +16 -0
  104. package/src/core/object/object-definition.embedded.ts +2 -2
  105. package/src/core/requirement/meta-requirement.ts +1 -7
  106. package/src/core/requirement/requirement-constants.ts +2 -9
  107. package/src/core/requirement/requirement-definition.embedded.ts +8 -46
  108. package/src/core/requirement/resolve-claim.ts +98 -0
  109. package/src/core-types.ts +9 -10
  110. package/src/errors.ts +16 -2
  111. package/src/index.ts +29 -1
  112. package/src/library/library-sources.ts +14 -0
  113. package/src/loader/meta-data-loader.ts +10 -9
  114. package/src/loader/validation-passes.ts +3 -3
  115. package/src/naming-refs.ts +1 -3
  116. package/src/parser-core.ts +27 -5
  117. package/src/persistence/origin/meta-origin.ts +0 -16
  118. package/src/persistence/origin/origin-constants.ts +9 -8
  119. package/src/persistence/origin/origin-definition.embedded.ts +0 -16
  120. package/src/registry-manifest.ts +9 -4
  121. package/src/retired-vocabulary.ts +280 -0
  122. package/src/super-resolve.ts +59 -1
  123. package/src/template/prompt-definition.embedded.ts +1 -1
  124. package/src/template/template-constants.ts +27 -1
  125. package/src/template/template-definition.embedded.ts +34 -21
  126. package/src/vocabulary-rewrite.ts +346 -0
  127. package/dist/core/field/validate-field-readonly.d.ts +0 -9
  128. package/dist/core/field/validate-field-readonly.d.ts.map +0 -1
  129. package/dist/core/field/validate-field-readonly.js +0 -128
  130. package/dist/core/field/validate-field-readonly.js.map +0 -1
  131. package/src/core/field/validate-field-readonly.ts +0 -154
@@ -0,0 +1,259 @@
1
+ // Validation pass: field-level @mutability cross-attribute rules (FR-037 R1).
2
+ //
3
+ // @mutability is ONE axis — who may write this field, and when — with three
4
+ // mutually exclusive modes, `readWrite` (default) < `writeOnce` < `readOnly`.
5
+ // Modelling it as one enum rather than two booleans is what makes the illegal
6
+ // pair unrepresentable and gives inheritance a total order.
7
+ //
8
+ // Codes:
9
+ // ERR_MUTABILITY_AUTOSET_CONFLICT — @autoSet on a field whose @mutability is
10
+ // `writeOnce` or `readOnly`. @autoSet already says the SERVER supplies the
11
+ // value; that is a different axis from who may write it, and the pair says
12
+ // two contradictory things about the same column. The boolean era left
13
+ // readOnly × @autoSet representable but UNVALIDATED — the enum cut closes
14
+ // both arms with one rule.
15
+ // ERR_MUTABILITY_DOWNGRADE — a subtype LOOSENS an inherited mode. Replaces
16
+ // ERR_READONLY_DOWNGRADE: the rule now spans three modes, so a code named
17
+ // READONLY would misdescribe a `writeOnce` → `readWrite` loosening.
18
+ // ERR_READONLY_ASSIGNED_PRIMARY — KEEPS ITS NAME: the condition is genuinely
19
+ // readOnly-specific. Note the asymmetry that justifies the enum — `writeOnce`
20
+ // on an assigned primary key is LEGAL, and indeed the natural declaration:
21
+ // the caller supplies the key on create and can never change it after.
22
+ // WARN_MUTABILITY_VALUE_OBJECT — a non-default @mutability on a field child of
23
+ // object.value. A value has no persistence semantics, so the contract is
24
+ // advisory (codegen may still use it for record/struct treatment).
25
+ // WARN_MUTABILITY_READONLY_HOST — `writeOnce` on a host nothing writes anyway
26
+ // (a projection, or a read-only source @kind). Benign, not an error: the
27
+ // declaration is merely inert, and a projection legitimately `extends` an
28
+ // entity whose field carries it.
29
+
30
+ import type { MetaData } from "../../shared/meta-data.js";
31
+ import { ParseError } from "../../errors.js";
32
+ import type { LoaderWarning } from "../../source.js";
33
+ import {
34
+ TYPE_OBJECT,
35
+ TYPE_FIELD,
36
+ TYPE_IDENTITY,
37
+ TYPE_SOURCE,
38
+ } from "../../shared/base-types.js";
39
+ import { OBJECT_SUBTYPE_VALUE, OBJECT_SUBTYPE_PROJECTION } from "../object/object-constants.js";
40
+ import {
41
+ IDENTITY_SUBTYPE_PRIMARY,
42
+ IDENTITY_ATTR_GENERATION,
43
+ IDENTITY_ATTR_FIELDS,
44
+ GENERATION_ASSIGNED,
45
+ } from "../identity/identity-constants.js";
46
+ import {
47
+ FIELD_ATTR_MUTABILITY,
48
+ FIELD_ATTR_AUTO_SET,
49
+ MUTABILITY_READ_WRITE,
50
+ MUTABILITY_WRITE_ONCE,
51
+ MUTABILITY_READ_ONLY,
52
+ MUTABILITY_MODES,
53
+ type MutabilityMode,
54
+ } from "./field-constants.js";
55
+ import { isReadOnlySource } from "../../shared/node-guards.js";
56
+
57
+ export interface FieldMutabilityValidationResult {
58
+ errors: ParseError[];
59
+ warnings: LoaderWarning[];
60
+ }
61
+
62
+ /**
63
+ * A field's EFFECTIVE mutability mode — resolving (ADR-0039), so a concrete
64
+ * field inheriting from an abstract parent sees the parent's declaration.
65
+ * Absent ⇒ `readWrite`. THE accessor every consumer should use.
66
+ */
67
+ export function fieldMutability(field: MetaData): MutabilityMode {
68
+ const v = field.attr(FIELD_ATTR_MUTABILITY);
69
+ return isMutabilityMode(v) ? v : MUTABILITY_READ_WRITE;
70
+ }
71
+
72
+ /** True when nothing may write this field — the `@readOnly: true` of the old
73
+ * vocabulary. Does NOT cover derived (origin-bearing) fields; callers that
74
+ * need both compose this with `isDerived()`, exactly as before. */
75
+ export function isReadOnlyMutability(field: MetaData): boolean {
76
+ return fieldMutability(field) === MUTABILITY_READ_ONLY;
77
+ }
78
+
79
+ /** True when the field is settable on create but excluded from the update shape. */
80
+ export function isWriteOnceMutability(field: MetaData): boolean {
81
+ return fieldMutability(field) === MUTABILITY_WRITE_ONCE;
82
+ }
83
+
84
+ function isMutabilityMode(v: unknown): v is MutabilityMode {
85
+ return typeof v === "string" && (MUTABILITY_MODES as readonly string[]).includes(v);
86
+ }
87
+
88
+ /** The mode's rank on the tightening order. Declaration order IS the order, so
89
+ * "may only tighten" is an index comparison, not a lookup table. */
90
+ function rank(mode: MutabilityMode): number {
91
+ return (MUTABILITY_MODES as readonly string[]).indexOf(mode);
92
+ }
93
+
94
+ export function validateFieldMutability(root: MetaData): FieldMutabilityValidationResult {
95
+ const errors: ParseError[] = [];
96
+ const warnings: LoaderWarning[] = [];
97
+
98
+ // ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
99
+ for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
100
+ const isValueObject = obj.subType === OBJECT_SUBTYPE_VALUE;
101
+ const hostNeverWritten = isWriteHostReadOnly(obj);
102
+
103
+ for (const ownField of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
104
+ // ADR-0039: own — these are own-vs-super comparisons by design. `ownMode`
105
+ // must see what THIS node declared: resolving would mask a downgrade, and
106
+ // would also warn on a value that merely inherited the mode.
107
+ const ownDeclared = ownField.ownAttr(FIELD_ATTR_MUTABILITY);
108
+ const ownMode = isMutabilityMode(ownDeclared) ? ownDeclared : undefined;
109
+
110
+ // 1) WARN_MUTABILITY_VALUE_OBJECT — a non-default mode declared on a value's
111
+ // own field. Advisory: a value has no persistence semantics.
112
+ if (isValueObject && ownMode !== undefined && ownMode !== MUTABILITY_READ_WRITE) {
113
+ warnings.push({
114
+ code: "WARN_MUTABILITY_VALUE_OBJECT",
115
+ message:
116
+ `field "${ownField.name}" on object.value "${obj.name}" declares ` +
117
+ `@mutability: "${ownMode}"; value objects have no persistence semantics, so the ` +
118
+ `write contract is advisory (codegen may use it for record/struct treatment).`,
119
+ source: ownField.source,
120
+ });
121
+ }
122
+
123
+ // 2) ERR_MUTABILITY_DOWNGRADE — a subtype may TIGHTEN an inherited mode,
124
+ // never loosen it. Rank comparison over the declaration order.
125
+ if (ownMode !== undefined) {
126
+ const inherited = inheritedField(obj, ownField.name);
127
+ if (inherited !== undefined) {
128
+ const inheritedMode = declaredMode(inherited);
129
+ if (inheritedMode !== undefined && rank(ownMode) < rank(inheritedMode)) {
130
+ errors.push(
131
+ new ParseError(
132
+ `field "${ownField.name}" on "${obj.name}" sets @mutability: "${ownMode}", but ` +
133
+ `its extends-chain parent declares "${inheritedMode}". A subtype may only ` +
134
+ `TIGHTEN an inherited mode (${MUTABILITY_MODES.join(" < ")}), never loosen it ` +
135
+ `(FR-037 R1).`,
136
+ { code: "ERR_MUTABILITY_DOWNGRADE", source: ownField.source },
137
+ ),
138
+ );
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ // The remaining rules read the EFFECTIVE tree — an inherited mode is just as
145
+ // binding as a declared one for "is this combination coherent?".
146
+ for (const field of obj.children().filter((c) => c.type === TYPE_FIELD)) {
147
+ const mode = fieldMutability(field);
148
+
149
+ // 3) ERR_MUTABILITY_AUTOSET_CONFLICT — @autoSet with a non-readWrite mode.
150
+ // Both arms: readOnly (representable-but-unvalidated in the boolean era)
151
+ // and writeOnce (new). @autoSet means the SERVER supplies the value, so
152
+ // constraining who ELSE may write it is contradictory, not additive.
153
+ if (mode !== MUTABILITY_READ_WRITE && field.attr(FIELD_ATTR_AUTO_SET) !== undefined) {
154
+ errors.push(
155
+ new ParseError(
156
+ `field "${field.name}" on "${obj.name}" declares @autoSet together with ` +
157
+ `@mutability: "${mode}". @autoSet already means the SERVER supplies the value; ` +
158
+ `@mutability says who may write it. Drop @mutability (an @autoSet field is ` +
159
+ `already excluded from every input shape) or drop @autoSet (FR-037 R1).`,
160
+ { code: "ERR_MUTABILITY_AUTOSET_CONFLICT", source: field.source },
161
+ ),
162
+ );
163
+ }
164
+
165
+ // 5) WARN_MUTABILITY_READONLY_HOST — writeOnce on a host nothing writes.
166
+ // Benign: the declaration is inert, not wrong, and a projection may
167
+ // legitimately inherit it from the entity it extends.
168
+ if (mode === MUTABILITY_WRITE_ONCE && hostNeverWritten) {
169
+ warnings.push({
170
+ code: "WARN_MUTABILITY_READONLY_HOST",
171
+ message:
172
+ `field "${field.name}" on "${obj.name}" declares @mutability: "writeOnce", but its ` +
173
+ `host is never written (a projection, or a read-only source @kind). The declaration ` +
174
+ `is inert — nothing creates a row here for it to be settable on.`,
175
+ source: field.source,
176
+ });
177
+ }
178
+ }
179
+
180
+ // 4) ERR_READONLY_ASSIGNED_PRIMARY — readOnly on an ASSIGNED primary key.
181
+ // Note what is NOT here: `writeOnce` on an assigned key is legal, and is
182
+ // the natural declaration for one. That asymmetry is why this code keeps
183
+ // its readOnly-specific name.
184
+ if (!isValueObject) {
185
+ const primaryAssignedFields = primaryAssignedFieldNames(obj);
186
+ if (primaryAssignedFields.size > 0) {
187
+ for (const field of obj.children().filter((c) => c.type === TYPE_FIELD)) {
188
+ if (!primaryAssignedFields.has(field.name)) continue;
189
+ if (fieldMutability(field) !== MUTABILITY_READ_ONLY) continue;
190
+ errors.push(
191
+ new ParseError(
192
+ `field "${field.name}" on "${obj.name}" is @mutability: "readOnly" AND the target ` +
193
+ `of identity.primary with @generation: "assigned"; the application has no path ` +
194
+ `to populate the identity value. Use @mutability: "writeOnce" if the intent is ` +
195
+ `"set once on create, never changed" (FR-037 R1).`,
196
+ { code: "ERR_READONLY_ASSIGNED_PRIMARY", source: field.source },
197
+ ),
198
+ );
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ return { errors, warnings };
205
+ }
206
+
207
+ /** The mode a node DECLARED (own), or undefined when it declared none. */
208
+ function declaredMode(field: MetaData): MutabilityMode | undefined {
209
+ // ADR-0039: own — the downgrade rule needs the EXPLICIT mode on the DECLARING
210
+ // node; resolving would report the child's own value back at itself.
211
+ const v = field.ownAttr(FIELD_ATTR_MUTABILITY);
212
+ return isMutabilityMode(v) ? v : undefined;
213
+ }
214
+
215
+ /** True when no write path reaches this object: an object.projection, or an
216
+ * object whose every source is a read-only `@kind`. */
217
+ function isWriteHostReadOnly(obj: MetaData): boolean {
218
+ if (obj.subType === OBJECT_SUBTYPE_PROJECTION) return true;
219
+ // ADR-0039: resolving — a source may be inherited via extends.
220
+ const sources = obj.children().filter((c) => c.type === TYPE_SOURCE);
221
+ if (sources.length === 0) return false;
222
+ return sources.every((s) => isReadOnlySource(s));
223
+ }
224
+
225
+ /** Walk the extends chain looking for a field with the same name; return its
226
+ * declaring node (own attrs preserved) if found. */
227
+ function inheritedField(obj: MetaData, name: string): MetaData | undefined {
228
+ let cursor = obj.superResolved;
229
+ while (cursor !== undefined) {
230
+ // ADR-0039: own — super-chain walk reading each level's OWN fields to find the
231
+ // declaring node (the comparison needs the declaring node's own mode).
232
+ const f = cursor.ownChildren().find((c) => c.type === TYPE_FIELD && c.name === name);
233
+ if (f !== undefined) return f;
234
+ cursor = cursor.superResolved;
235
+ }
236
+ return undefined;
237
+ }
238
+
239
+ /** Names of fields participating in any identity.primary with @generation:
240
+ * "assigned" on `obj` or its extends chain. */
241
+ function primaryAssignedFieldNames(obj: MetaData): Set<string> {
242
+ const out = new Set<string>();
243
+ for (const id of obj.children()) {
244
+ if (id.type !== TYPE_IDENTITY) continue;
245
+ if (id.subType !== IDENTITY_SUBTYPE_PRIMARY) continue;
246
+ // ADR-0039: resolving — an identity may inherit @generation / @fields via extends.
247
+ const gen = id.attr(IDENTITY_ATTR_GENERATION);
248
+ if (gen !== GENERATION_ASSIGNED) continue;
249
+ const fields = id.attr(IDENTITY_ATTR_FIELDS);
250
+ if (Array.isArray(fields)) {
251
+ for (const fName of fields) {
252
+ if (typeof fName === "string") out.add(fName);
253
+ }
254
+ } else if (typeof fields === "string") {
255
+ out.add(fields);
256
+ }
257
+ }
258
+ return out;
259
+ }
@@ -15,6 +15,22 @@ export const IDENTITY_SUBTYPES = [
15
15
  ] as const;
16
16
  export type IdentitySubType = (typeof IDENTITY_SUBTYPES)[number];
17
17
 
18
+ /**
19
+ * The identity subtypes that denote a UNIQUE key.
20
+ *
21
+ * ADR-0040 put uniqueness in the TYPE: `identity.primary` and `identity.secondary` are both
22
+ * unique keys (secondary IS the unique alternate key — `@unique` was removed from it
23
+ * precisely because the subtype already says so), while `identity.reference` is a foreign
24
+ * key and carries no uniqueness at all.
25
+ *
26
+ * Named here because more than one rule needs "is this a candidate key?" and answering it
27
+ * by listing subtypes at each site is how the two drift apart.
28
+ */
29
+ export const IDENTITY_UNIQUE_KEY_SUBTYPES: readonly string[] = [
30
+ IDENTITY_SUBTYPE_PRIMARY,
31
+ IDENTITY_SUBTYPE_SECONDARY,
32
+ ];
33
+
18
34
  // ---------------------------------------------------------------------------
19
35
  // Identity attrs
20
36
  // ---------------------------------------------------------------------------
@@ -104,7 +104,7 @@ export const OBJECT_DEFINITION: ProviderDefinition = {
104
104
  "extendsBase": true,
105
105
  "description": "A value object — pure shape with NO identity and NO source, ever. Constructed (by caller / embedding), never populated from a store. May `extends` an entity's fields to reuse shape. Equality is by content.",
106
106
  "whenToUse": "You need a reusable typed shape with NO identity and NO table — an embedded value, a DTO, a prompt/response payload. Constructed in memory, never persisted on its own.",
107
- "rules": "object.value is pure shape: it NEVER declares an identity and NEVER declares a source, in any role. It is constructed — by a caller or by embedding — and is never populated from a backing store. A value-hosted field may carry origin.passthrough (FR-015 parameter lineage) but NEVER an assembly origin (origin.aggregate / origin.computed / origin.collection / origin.first) — passthrough on a value is lineage; assembly origins live on object.projection (#210). It may `extends` an entity's fields to reuse their shape. @normalize is the object-level default ASCII normalization mode applied to this value's enum fields' tolerant extract (each field may still override per-field). See ADR-0028 (object taxonomy, value purity).",
107
+ "rules": "object.value is pure shape: it NEVER declares an identity and NEVER declares a source, in any role. It is constructed — by a caller or by embedding — and is never populated from a backing store. A value-hosted field may carry origin.passthrough (FR-015 parameter lineage) but NEVER an assembly origin (origin.aggregate / origin.computed / origin.first) — passthrough on a value is lineage; assembly origins live on object.projection (#210). It may `extends` an entity's fields to reuse their shape. @normalize is the object-level default ASCII normalization mode applied to this value's enum fields' tolerant extract (each field may still override per-field). See ADR-0028 (object taxonomy, value purity).",
108
108
  "children": [
109
109
  {
110
110
  "type": "relationship",
@@ -121,7 +121,7 @@ export const OBJECT_DEFINITION: ProviderDefinition = {
121
121
  "extendsBase": true,
122
122
  "description": "A derived read-only representation of entities. Its fields are extends-bound / origin-derived / self-declared-under-external-assembly, all read-only at the subtype level. Identity is optional and MUST extend an entity identity; sources are restricted to read-only @kinds. The declared field set IS the exposure (inclusive, fail-closed).",
123
123
  "whenToUse": "You need a derived, read-only view over entities — a report, summary, or joined/aggregated read model. Declare it (with origin.* children) instead of hand-writing the SELECT/joins; it stays read-only and regenerates.",
124
- "rules": "object.projection is a derived read-only representation: every field is extends-bound, origin-derived, or self-declared-under-external-assembly, and all are read-only at the subtype level. Identity is optional and, when present, MUST extend an entity identity. Sources are restricted to read-only @kinds. The declared field set IS the exposure — an inclusive list, fail-closed. A projection NEVER declares relationships (derivation is expressed via @via, not a relationship child) and NEVER co-locates templates — hence its child set omits both relationship and template. The assembly origins (origin.aggregate / origin.computed / origin.collection / origin.first) live HERE, not on object.value; a SOURCELESS projection is the legal shape for a wire-assembled read model, and a template-level @payloadRef/@responseRef may target one (#210). See ADR-0028 (object taxonomy, projection).",
124
+ "rules": "object.projection is a derived read-only representation: every field is extends-bound, origin-derived, or self-declared-under-external-assembly, and all are read-only at the subtype level. Identity is optional and, when present, MUST extend an entity identity. Sources are restricted to read-only @kinds. The declared field set IS the exposure — an inclusive list, fail-closed. A projection NEVER declares relationships (derivation is expressed via @via, not a relationship child) and NEVER co-locates templates — hence its child set omits both relationship and template. The assembly origins (origin.aggregate / origin.computed / origin.first) live HERE, not on object.value; a SOURCELESS projection is the legal shape for a wire-assembled read model, and a template-level @payloadRef/@responseRef may target one (#210). See ADR-0028 (object taxonomy, projection).",
125
125
  "children": [
126
126
  {
127
127
  "type": "attr",
@@ -13,7 +13,6 @@ import {
13
13
  REQUIREMENT_ATTR_DISPOSITION,
14
14
  REQUIREMENT_ATTR_TRACKED_BY,
15
15
  REQUIREMENT_ATTR_IMPLEMENTED_BY,
16
- REQUIREMENT_ATTR_VERIFIED_BY,
17
16
  REQUIREMENT_LINK_FLOOR_LEVEL,
18
17
  REQUIREMENT_STATUS_PLANNED,
19
18
  REQUIREMENT_STATUSES_REQUIRING_LIVE_NODES,
@@ -78,11 +77,6 @@ export class MetaRequirement extends MetaData {
78
77
  return Array.isArray(v) ? (v as string[]) : [];
79
78
  }
80
79
 
81
- verifiedBy(): string[] {
82
- const v = this.attr(REQUIREMENT_ATTR_VERIFIED_BY);
83
- return Array.isArray(v) ? (v as string[]) : [];
84
- }
85
-
86
80
  /** True when this requirement is permitted to reference the model at all.
87
81
  *
88
82
  * An UNLEVELLED architectural requirement always may — its claim set is the
@@ -98,7 +92,7 @@ export class MetaRequirement extends MetaData {
98
92
  }
99
93
 
100
94
  /** True when a dangling `@implementedBy` is an ERROR rather than expected.
101
- * An abandoned or superseded requirement's nodes are supposed to be gone. */
95
+ * `planned` is the only exemption — there the nodes do not exist YET. */
102
96
  requiresLiveNodes(): boolean {
103
97
  const s = this.status();
104
98
  return s !== undefined && REQUIREMENT_STATUSES_REQUIRING_LIVE_NODES.includes(s);
@@ -34,10 +34,8 @@ export const REQUIREMENT_ATTR_STATUS = "status";
34
34
  export const REQUIREMENT_ATTR_DISPOSITION = "disposition";
35
35
  export const REQUIREMENT_ATTR_TRACKED_BY = "trackedBy";
36
36
  export const REQUIREMENT_ATTR_STATEMENT = "statement";
37
- export const REQUIREMENT_ATTR_VIOLATION = "violation";
37
+ export const REQUIREMENT_ATTR_COUNTEREXAMPLE = "counterexample";
38
38
  export const REQUIREMENT_ATTR_IMPLEMENTED_BY = "implementedBy";
39
- export const REQUIREMENT_ATTR_VERIFIED_BY = "verifiedBy";
40
- export const REQUIREMENT_ATTR_SUPERSEDED_BY = "supersededBy";
41
39
 
42
40
  // ---------------------------------------------------------------------------
43
41
  // Status — a closed enum, enforced by the registry via `allowedValues`. This is
@@ -49,22 +47,17 @@ export const REQUIREMENT_ATTR_SUPERSEDED_BY = "supersededBy";
49
47
  export const REQUIREMENT_STATUS_PLANNED = "planned";
50
48
  export const REQUIREMENT_STATUS_LIVE = "live";
51
49
  export const REQUIREMENT_STATUS_PARTIAL = "partial";
52
- export const REQUIREMENT_STATUS_ABANDONED = "abandoned";
53
- export const REQUIREMENT_STATUS_SUPERSEDED = "superseded";
54
50
 
55
51
  export const REQUIREMENT_STATUSES = [
56
52
  REQUIREMENT_STATUS_PLANNED,
57
53
  REQUIREMENT_STATUS_LIVE,
58
54
  REQUIREMENT_STATUS_PARTIAL,
59
- REQUIREMENT_STATUS_ABANDONED,
60
- REQUIREMENT_STATUS_SUPERSEDED,
61
55
  ] as const;
62
56
  export type RequirementStatus = (typeof REQUIREMENT_STATUSES)[number];
63
57
 
64
58
  /** Statuses whose implementing nodes are supposed to still exist. A dangling
65
59
  * `@implementedBy` on one of these means the model moved and the requirement is
66
- * stale. On `planned` the nodes do not exist YET; on `abandoned`/`superseded`
67
- * they are supposed to be GONE — which is the whole point of those entries. */
60
+ * stale. `planned` is the only exemption — there the nodes do not exist YET. */
68
61
  export const REQUIREMENT_STATUSES_REQUIRING_LIVE_NODES: readonly RequirementStatus[] = [
69
62
  REQUIREMENT_STATUS_LIVE,
70
63
  REQUIREMENT_STATUS_PARTIAL,
@@ -32,11 +32,9 @@ export const REQUIREMENT_DEFINITION: ProviderDefinition = {
32
32
  "allowedValues": [
33
33
  "planned",
34
34
  "live",
35
- "partial",
36
- "abandoned",
37
- "superseded"
35
+ "partial"
38
36
  ],
39
- "description": "planned intended but not built yet; live implemented and in use; partial implemented with known gaps; abandoned built then deliberately retired; superseded replaced by a different mechanism. A dangling @implementedBy is an ERROR on live/partial (the model moved, the requirement is stale) and ALLOWED on planned/abandoned/superseded on planned the nodes do not exist YET, on the other two they are meant to be gone, and that is the entry doing its job. A planned requirement also never contributes to object coverage: planning a capability must not silence the warning that nothing implements it."
37
+ "description": "planned intended but not built yet; live implemented and in use; partial implemented with known gaps. A requirement is PRESCRIPTIVE — it states what SHOULD happen and is never a journal of what happened — so a capability that no longer applies is DELETED, not annotated as retired; the record of it having existed belongs to version control and to notes on the entries that survive. A dangling @implementedBy is an ERROR on live/partial (the model moved, the requirement is stale) and ALLOWED on planned, where the nodes do not exist YET. A planned requirement also never contributes to object coverage: planning a capability must not silence the warning that nothing implements it."
40
38
  },
41
39
  {
42
40
  "type": "attr",
@@ -57,7 +55,7 @@ export const REQUIREMENT_DEFINITION: ProviderDefinition = {
57
55
  "isArray": true,
58
56
  "min": 0,
59
57
  "max": 1,
60
- "description": "Issue or ticket references for outstanding work — a URL, an owner/repo#123 shorthand, or a tracker key. Free-form and NOT resolved by verify, which does not reach the network; unlike @verifiedBy, nothing here is checked to exist. Its job is to stop a deferred gap becoming invisible, so verify warns when a deferred requirement names no ticket. Also the right place to link the ticket that a planned requirement will be built under."
58
+ "description": "Issue or ticket references for outstanding work — a URL, an owner/repo#123 shorthand, or a tracker key. Free-form and NOT resolved by verify, which does not reach the network; nothing here is checked to exist. Its job is to stop a deferred gap becoming invisible, so verify warns when a deferred requirement names no ticket. Also the right place to link the ticket that a planned requirement will be built under."
61
59
  },
62
60
  {
63
61
  "type": "attr",
@@ -70,10 +68,10 @@ export const REQUIREMENT_DEFINITION: ProviderDefinition = {
70
68
  {
71
69
  "type": "attr",
72
70
  "subType": "string",
73
- "name": "violation",
71
+ "name": "counterexample",
74
72
  "min": 1,
75
73
  "max": 1,
76
- "description": "What breaking it looks like, in one sentence. A requirement MUST be violable: 'every entity has a uuid primary key' is (point at one with a composite string key); 'things are persisted' is not, and is a description rather than a requirement."
74
+ "description": "What breaking it looks like, in one sentence — a STATIC falsifiability test, authored once, never a state. A requirement MUST be violable: 'every entity has a uuid primary key' is (point at one with a composite string key); 'things are persisted' is not, and is a description rather than a requirement. Renamed from @violation in 0.24.0, which read as a status."
77
75
  },
78
76
  {
79
77
  "type": "attr",
@@ -84,23 +82,6 @@ export const REQUIREMENT_DEFINITION: ProviderDefinition = {
84
82
  "max": 1,
85
83
  "description": "FQN references to the model nodes realising this requirement. Legal on level 4 (an object) and level 5 (a field, view or identity) only; an organisational level carrying it is ERR_REQUIREMENT_LINK_ABOVE_FLOOR. Many-to-many by construction — several requirements may name the same node."
86
84
  },
87
- {
88
- "type": "attr",
89
- "subType": "string",
90
- "name": "verifiedBy",
91
- "isArray": true,
92
- "min": 0,
93
- "max": 1,
94
- "description": "OPTIONAL — omit unless you have opened the test and read what it asserts. Names of tests that assert the behaviour. verify checks each name EXISTS and is not skipped; it never runs them, and it cannot tell whether the named test verifies this requirement — any occurrence in the test corpus satisfies it."
95
- },
96
- {
97
- "type": "attr",
98
- "subType": "string",
99
- "name": "supersededBy",
100
- "min": 0,
101
- "max": 1,
102
- "description": "The requirement that replaced this one. Expected on status=superseded."
103
- },
104
85
  {
105
86
  "type": "requirement",
106
87
  "subType": "*",
@@ -134,9 +115,7 @@ export const REQUIREMENT_DEFINITION: ProviderDefinition = {
134
115
  "allowedValues": [
135
116
  "planned",
136
117
  "live",
137
- "partial",
138
- "abandoned",
139
- "superseded"
118
+ "partial"
140
119
  ],
141
120
  "description": "As on requirement.functional. A live or partial architectural requirement claimed by NOTHING is an error: a policy declared and applied to nothing. A planned one is exempt from that check — it is not applied yet by definition."
142
121
  },
@@ -172,10 +151,10 @@ export const REQUIREMENT_DEFINITION: ProviderDefinition = {
172
151
  {
173
152
  "type": "attr",
174
153
  "subType": "string",
175
- "name": "violation",
154
+ "name": "counterexample",
176
155
  "min": 1,
177
156
  "max": 1,
178
- "description": "What breaking it looks like — the node that would contradict it. This is what makes universality checkable."
157
+ "description": "What breaking it looks like — the node that would contradict it. A STATIC falsifiability test, not a state. This is what makes universality checkable. Renamed from @violation in 0.24.0, which read as a status."
179
158
  },
180
159
  {
181
160
  "type": "attr",
@@ -186,23 +165,6 @@ export const REQUIREMENT_DEFINITION: ProviderDefinition = {
186
165
  "max": 1,
187
166
  "description": "FQN references to the nodes applying this policy. High fan-out is normal and expected: one uuid-primary-key requirement is claimed by every entity."
188
167
  },
189
- {
190
- "type": "attr",
191
- "subType": "string",
192
- "name": "verifiedBy",
193
- "isArray": true,
194
- "min": 0,
195
- "max": 1,
196
- "description": "OPTIONAL — omit unless you have opened the test and read what it asserts. Names of tests that assert the policy holds. verify checks each name EXISTS and is not skipped; it never runs them, and it cannot tell whether the named test verifies this requirement — any occurrence in the test corpus satisfies it."
197
- },
198
- {
199
- "type": "attr",
200
- "subType": "string",
201
- "name": "supersededBy",
202
- "min": 0,
203
- "max": 1,
204
- "description": "The requirement that replaced this one. Expected on status=superseded."
205
- },
206
168
  {
207
169
  "type": "requirement",
208
170
  "subType": "*",
@@ -0,0 +1,98 @@
1
+ // resolveClaim — resolve an `@implementedBy` reference to the node it names.
2
+ //
3
+ // Moved here from the CLI's requirement-check so `codegen-ts` can share ONE
4
+ // resolver (FR-038): the requirement-test generator has to know each claimed
5
+ // node's TYPE to fan out per concern, and a second implementation would fork the
6
+ // ADR-0042 package-local binding contract the loader already owns.
7
+
8
+ import { TYPE_OBJECT, TYPE_REQUIREMENT } from "../../shared/base-types.js";
9
+ import { PACKAGE_SEPARATOR } from "../../shared/structural.js";
10
+ import { resolveObjectRef } from "../../naming-refs.js";
11
+ import type { MetaData } from "../../shared/meta-data.js";
12
+
13
+ /**
14
+ * Resolve the owner segment of an `@implementedBy` reference to the node it names.
15
+ *
16
+ * OBJECTS FIRST, through the loader's own resolver, so package-local binding stays the
17
+ * ADR-0042 contract and never a parallel name scan (#228).
18
+ *
19
+ * Then ROOT-LEVEL NON-OBJECT nodes — `template.prompt` and its siblings today. The
20
+ * attribute is documented as naming "the model nodes realising this requirement", and a
21
+ * declared prompt is one: it is the durable artifact a capability like "the game master
22
+ * is told what the party can see" actually lives in. Resolving only objects meant the
23
+ * prompt estate — the thing whose retirement is hardest to see in a model, since a
24
+ * removed prompt leaves no table behind — was the one part of a model that could not
25
+ * carry a status. So L4 means "a declared top-level model node", not "an object".
26
+ *
27
+ * Requirements themselves are excluded: hierarchy is nesting, and a requirement claiming
28
+ * a requirement would be a second, contradictory parent mechanism.
29
+ */
30
+ export function resolveClaimTarget(
31
+ root: MetaData,
32
+ owner: string,
33
+ referrerPkg: string,
34
+ ): MetaData | undefined {
35
+ const { node } = resolveObjectRef(root, owner, referrerPkg);
36
+ if (node !== undefined) return node;
37
+
38
+ const candidates = root
39
+ .children()
40
+ .filter((c) => c.type !== TYPE_OBJECT && c.type !== TYPE_REQUIREMENT);
41
+
42
+ // A fully-qualified reference binds exactly, like every other FQN in the model.
43
+ if (owner.includes(PACKAGE_SEPARATOR)) {
44
+ return candidates.find((c) => c.resolutionKey() === owner);
45
+ }
46
+ // A bare reference prefers the referrer's own package, then a root-level node of that
47
+ // bare name. An ambiguous bare name binds NOTHING — same fail-closed rule objects use,
48
+ // because silently picking one of two same-named nodes is how a claim ends up pointing
49
+ // at the wrong thing without anyone noticing.
50
+ const local =
51
+ referrerPkg === ""
52
+ ? []
53
+ : candidates.filter(
54
+ (c) => c.resolutionKey() === `${referrerPkg}${PACKAGE_SEPARATOR}${owner}`,
55
+ );
56
+ if (local.length === 1) return local[0];
57
+ // Root-level (unpackaged) only, matching resolveObjectRef's own bare fallback. A bare
58
+ // ref must not reach into an arbitrary package just because the name is unique there.
59
+ const bare = candidates.filter(
60
+ (c) => c.name === owner && c.resolutionKey() === owner,
61
+ );
62
+ return bare.length === 1 ? bare[0] : undefined;
63
+ }
64
+
65
+ /** Walk dotted member segments by CHILD NAME from an object node.
66
+ *
67
+ * Exported alongside `resolveClaimTarget` because the CLI's coverage pass needs the
68
+ * OWNER node's `resolutionKey()` while only using member resolution as a yes/no
69
+ * validity test. Composing them into `resolveClaim` there would key coverage on the
70
+ * member instead of the object — a silent behaviour change. */
71
+ export function resolveMember(obj: MetaData, path: string[]): MetaData | undefined {
72
+ let cur: MetaData | undefined = obj;
73
+ for (const seg of path) {
74
+ if (cur === undefined) return undefined;
75
+ cur = cur.children().find((c) => c.name === seg);
76
+ }
77
+ return cur;
78
+ }
79
+
80
+ /**
81
+ * Resolve a full `@implementedBy` reference — owner segment plus any dotted member
82
+ * segments — to the node it names, or undefined when it does not resolve.
83
+ *
84
+ * Resolution walks to the FULL depth of the reference, so `Council.slug.display`
85
+ * yields the view node rather than stopping at the field. FR-038's fan-out keys on
86
+ * the resolved node's type, and a resolver that stopped short would silently collapse
87
+ * two concerns into one.
88
+ */
89
+ export function resolveClaim(
90
+ root: MetaData,
91
+ ref: string,
92
+ referrerPkg: string,
93
+ ): MetaData | undefined {
94
+ const segs = ref.split(".");
95
+ const owner = resolveClaimTarget(root, segs[0] ?? "", referrerPkg);
96
+ if (owner === undefined || segs.length === 1) return owner;
97
+ return resolveMember(owner, segs.slice(1));
98
+ }
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