@metaobjectsdev/metadata 1.0.0-rc.4 → 1.0.0-rc.6

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.
@@ -79,6 +79,44 @@ export interface RewriteRefusal extends RetirementNote {
79
79
  readonly line: number;
80
80
  }
81
81
 
82
+ /**
83
+ * The refusal for an authored `<type>.base`, or `undefined` when `typeKey` is not one.
84
+ *
85
+ * Shared by BOTH rewriters. Their detection legitimately differs — one walks JSON key
86
+ * ranges, the other YAML node spans — but the verdict does not, and the `why` plus the
87
+ * migration path were spelled out twice, byte-identically, in two files. A message an
88
+ * adopter reads is a contract: two copies are two places for the next wording or the next
89
+ * guide path to reach one and not the other, and the reader has no way to tell which arm
90
+ * answered them.
91
+ *
92
+ * `line` is deliberately NOT returned. It is the one part that genuinely differs between
93
+ * the arms (a JSON key index vs a YAML span start), so each caller supplies its own.
94
+ *
95
+ * An authored `base` is a LOAD ERROR, not a retirement — the anchor was never authorable,
96
+ * and three of five ports accepted it anyway until 1.0. It is reported here for the same
97
+ * reason retired subtypes are: `meta upgrade` is the command an adopter runs to ask "what
98
+ * does the new version need me to change?", and answering "nothing" about the change the
99
+ * migration guide LEADS WITH is worse than not being asked. It is a REFUSAL rather than a
100
+ * rewrite because choosing the concrete subtype is a decision about what the node IS,
101
+ * which no rewriter can make.
102
+ */
103
+ export function authoredBaseRefusal(
104
+ typeKey: string,
105
+ abstractAnchorTypes: readonly string[] | undefined,
106
+ ): Omit<RewriteRefusal, "line"> | undefined {
107
+ const dot = typeKey.lastIndexOf(".");
108
+ if (dot < 0 || typeKey.slice(dot + 1) !== "base") return undefined;
109
+ if (!(abstractAnchorTypes ?? []).includes(typeKey.slice(0, dot))) return undefined;
110
+ return {
111
+ since: "1.0.0",
112
+ why:
113
+ `"${typeKey}" may not be authored — every "base" subtype is an abstract registry ` +
114
+ "anchor that concrete subtypes inherit from, with no runtime semantics of its own.",
115
+ migration: "docs/features/migrations/base-subtypes-are-not-authorable.md",
116
+ subject: typeKey,
117
+ };
118
+ }
119
+
82
120
  export interface RewriteResult {
83
121
  readonly text: string;
84
122
  readonly changes: readonly RewriteChange[];
@@ -88,6 +126,17 @@ export interface RewriteResult {
88
126
  export interface RewriteOpts {
89
127
  /** Only apply retirements at or before this version. */
90
128
  readonly maxVersion?: string;
129
+ /**
130
+ * Types whose `<type>.base` is an ABSTRACT ANCHOR, so authoring one is a load error
131
+ * (`ERR_ABSTRACT_SUBTYPE_AUTHORED`). Supplied by the CALLER rather than derived here,
132
+ * because this module is deliberately registry-free — and the fact is a registry
133
+ * question: `base` is an anchor exactly when the type registers some OTHER subtype for
134
+ * it to anchor, which a third-party provider's base-only type does not.
135
+ *
136
+ * Omitted ⇒ no anchor refusals, which is the honest default for a caller that cannot
137
+ * say. `meta upgrade` supplies the core set.
138
+ */
139
+ readonly abstractAnchorTypes?: readonly string[];
91
140
  }
92
141
 
93
142
  /** `0.24.0` → `[0,24,0]`, for an ordered comparison rather than a string one. */
@@ -346,6 +395,15 @@ export function rewriteDocument(source: string, opts: RewriteOpts = {}): Rewrite
346
395
  }
347
396
  }
348
397
 
398
+ // An authored `<type>.base` — see authoredBaseRefusal for why this is reported at all.
399
+ // Only the LINE is this arm's own; the verdict is shared with the YAML rewriter.
400
+ for (const r of ranges) {
401
+ const refusal = authoredBaseRefusal(r.typeKey, opts.abstractAnchorTypes);
402
+ if (refusal !== undefined) {
403
+ refusals.push({ ...refusal, line: lineAt(source, r.keyIndex) });
404
+ }
405
+ }
406
+
349
407
  // ── Attribute contradictions: two LIVE attrs that may not sit on one node ──
350
408
  //
351
409
  // Matched per NODE, not per occurrence, because the illegal thing is the pair. `ownKeys`