From 614a61a74d8d077e92cbd0b108c3865baf1ec1c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 05:02:22 +0000 Subject: [PATCH] fix(scripts): ADR-0087 gate reads an any index signature (D8 p4) and a src/index.tsx entry (D9) The gate half of the 2026-10-27 ADR-0087 amendment (#26455). Predicate 4 of type-surface-only gains the index-signature limb: a bare reference to an exported interface, or an object-literal type alias, holds when an index signature typed any at the merge base carries another value type at HEAD and the kind, heritage and every other member are identical. The runtime-export-retired entry mapping reads one source entry, src/index.ts or src/index.tsx; both present is refused as ambiguous. New self-test battery IX (52 cases), roster floor 56 -> 57. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01Am8eYNReiNP66D2JihPS1q --- scripts/check-adr-0087-registration.mjs | 299 +++++++++++++++++++++++- 1 file changed, 288 insertions(+), 11 deletions(-) diff --git a/scripts/check-adr-0087-registration.mjs b/scripts/check-adr-0087-registration.mjs index f9058dc7569..aa514794fe8 100644 --- a/scripts/check-adr-0087-registration.mjs +++ b/scripts/check-adr-0087-registration.mjs @@ -462,6 +462,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ 'REX-U: unit pins on the operand grammar and the FROM-side reader': 24, 'REX-L: the compiler is loaded only when a marker names the category': 6, 'WS (#26220): ui workspace packages, `.tsx` modules, aliases and shims': 11, + 'IX (#26220): predicate 4 reads an `any` index signature; the entry may be `src/index.tsx`': 52, 'G6: a changeset that was ALREADY breaking at base is inherited': 1, 'R15: a changeset RENAMED AND turned breaking in the same commit': 5, 'G10: a PURE rename of an ALREADY-breaking stock changeset': 3, @@ -494,7 +495,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ // DELETING an entry silences that battery's floor exactly as effectively as // zeroing it, so the roster's own size is pinned too. -const SELF_TEST_BATTERY_FLOOR = 56; +const SELF_TEST_BATTERY_FLOOR = 57; // The key an assertion is filed under when no battery is open. It is not a // declared battery, so it reds by the same set difference rather than silently @@ -3661,6 +3662,134 @@ export function resolveMemberPath(text, segments) { return { ok: true, from, to }; } +/** + * The declaration of an exported `interface` -- or a `type` alias whose RHS is ONE + * object type literal -- split into what predicate 4's index-signature limb + * compares: its heritage (type parameters, `extends` clause) and its top-level + * members, each normalised. `null` for any other shape, which the limb refuses. + * + * @returns {{ kind: string, heritage: string, members: string[] }|null} + */ +export function structuralDeclarationParts(text, symbol) { + const masked = maskComments(text); + const exported = exportedTypeDeclaration(masked, symbol); + if (!exported || (exported.kind !== 'interface' && exported.kind !== 'type')) return null; + const structural = maskCommentsAndLiterals(text); + let open; + let heritageEnd; + if (exported.kind === 'interface') { + open = declarationBodyStart(structural, exported.restStart); + heritageEnd = open; + } else { + const eq = aliasEqualsIndex(structural, exported.restStart); + if (eq === -1) return null; + open = eq + 1; + while (open < structural.length && /\s/.test(structural[open])) open++; + if (structural[open] !== '{') return null; + heritageEnd = eq; + // ONE object type literal and nothing else: `{ ... } & X` is not this shape. + const close = matchBracket(structural, open); + if (close === -1 || structural.slice(close + 1, aliasRhsEnd(structural, eq)).trim() !== '') return null; + } + if (open === -1) return null; + const close = matchBracket(structural, open); + if (close === -1) return null; + return { + kind: exported.kind, + heritage: normaliseSurface(masked.slice(exported.restStart, heritageEnd)), + members: splitTopLevelMembers(masked.slice(open + 1, close), structural.slice(open + 1, close)), + }; +} + +/** + * The top-level members of a brace body's INSIDE, normalised, separators dropped. + * Depth is walked on the literal-masked projection, and the `>` of an `=>` is not + * a closer -- counting it would end a nested literal early and hand a nested index + * signature to the caller as a top-level one. + */ +function splitTopLevelMembers(inside, structural) { + const members = []; + let depth = 0; + let from = 0; + for (let i = 0; i < structural.length; i++) { + const c = structural[i]; + if (c === '{' || c === '[' || c === '(' || c === '<') depth++; + else if (c === '}' || c === ']' || c === ')' || (c === '>' && structural[i - 1] !== '=')) { if (depth > 0) depth--; } + else if (depth === 0 && (c === ';' || c === ',')) { + const m = normaliseSurface(inside.slice(from, i)); + if (m !== '') members.push(m); + from = i + 1; + } + } + const last = normaliseSurface(inside.slice(from)); + if (last !== '') members.push(last); + return members; +} + +// `[key: K]: V`, optionally `readonly`: the key part and the value type. A mapped +// type (`[P in K]`) and a computed member name (`[Symbol.iterator]()`) do not match. +const INDEX_SIGNATURE_RE = /^((?:readonly\s+)?\[\s*[A-Za-z_$][\w$]*\s*:\s*[^\]]+\])\s*:\s*([\s\S]+)$/; + +/** A normalised member as an index signature `{ key, value }`, or null. */ +export function readIndexSignature(member) { + const m = INDEX_SIGNATURE_RE.exec(member); + return m ? { key: normaliseSurface(m[1]), value: normaliseSurface(m[2]) } : null; +} + +/** + * Predicate 4's index-signature limb (ADR-0087 D8, amended 2026-10-27, #26220). + * + * For a bare reference to an exported `interface`, or a `type` alias whose RHS is + * one object type literal, the limb holds when ALL of: + * 1. at the merge base at least one index signature's value type is `any`; + * 2. at HEAD at least one of THOSE carries another value type (`unknown` + * counts as narrowed here, and only here); + * 3. everything else is identical: the kind, the heritage, and every member, + * position by position. Only the value types of base-`any` index signatures + * are set aside -- a non-`any` index signature that changes is a re-typed + * member like any other, so this reads no wider than the ADR's text. + * #6048 (a member removed beside a concrete surface) fails 3 and keeps its + * founding-case refusal. ⛔ Never widen a condition here without the ADR first. + * + * @returns {{ ok: true, from: string, to: string } | { ok: false, reason: string }} + */ +export function indexSignatureLimb(baseText, headText, symbol) { + const before = structuralDeclarationParts(baseText, symbol); + const after = structuralDeclarationParts(headText, symbol); + if (!before || !after) { + return { ok: false, reason: 'it reads only an exported `interface`, or a `type` alias whose right-hand side is one object type literal, at both revs' }; + } + if (before.kind !== after.kind || before.heritage !== after.heritage) { + return { ok: false, reason: `the declaration's heritage moved (\`${before.kind}${before.heritage ? ` ${before.heritage}` : ''}\` -> \`${after.kind}${after.heritage ? ` ${after.heritage}` : ''}\`)` }; + } + if (before.members.length !== after.members.length) { + return { ok: false, reason: `the member count moved (${before.members.length} -> ${after.members.length}): a member was added or removed` }; + } + const narrowed = []; + let anyAtBase = 0; + for (let i = 0; i < before.members.length; i++) { + const b = before.members[i]; + const a = after.members[i]; + const bi = readIndexSignature(b); + if (bi && bi.value === 'any') { + anyAtBase++; + const ai = readIndexSignature(a); + if (ai && ai.key === bi.key) { + if (ai.value !== 'any') narrowed.push({ from: b, to: a }); + continue; + } + } + if (a !== b) return { ok: false, reason: `member ${i + 1} moved (\`${b}\` -> \`${a}\`)` }; + } + if (anyAtBase === 0) return { ok: false, reason: 'no index signature is typed `any` at the merge base' }; + if (narrowed.length === 0) return { ok: false, reason: 'every index signature typed `any` at the merge base is still `any` at HEAD' }; + return { + ok: true, + from: narrowed.map((n) => n.from).join('; '), + to: narrowed.map((n) => n.to).join('; '), + }; +} + /** * What predicate 4 can read about ONE parsed reference, or why it is refused. * @@ -4170,6 +4299,13 @@ export function verifyTypeSurfaceOnly(refs, { base, head, cwd, bumps, packages, continue; } if (!before.erased) { + // The 2026-10-27 amendment (#26220): an `any` INDEX SIGNATURE is the one member + // that can be erased inside a concrete declaration. A bare reference only. + const limb = parsedRef.dotted ? { ok: false, reason: 'it does not apply to a dotted member path' } : indexSignatureLimb(baseText, headText, parsedRef.symbol); + if (limb.ok) { + verified.push({ ref, from: limb.from, to: limb.to }); + continue; + } problems.push( `\`type-surface-only ${ref}\` [predicate 4: narrowed-from-erased] is FALSE: at the merge base\n` + ` ${before.shape} was already CONCRETE (${before.type === null @@ -4182,6 +4318,7 @@ export function verifyTypeSurfaceOnly(refs, { base, head, cwd, bumps, packages, ' exempt from it. Predicate 4 is what pays for that exemption: a surface that was already\n' + ' concrete did not move off `any`, and a member removed from it is exactly the class the\n' + ' ledger DOES serve.\n' + + ` The index-signature limb (ADR-0087 D8, amended 2026-10-27) does not hold either: ${limb.reason}.\n` + ' fix: register the migration.', ); continue; @@ -4245,7 +4382,9 @@ export function verifyTypeSurfaceOnly(refs, { base, head, cwd, bumps, packages, // is located through the package's `exports` map -- `resolveTypesEntry`, // imported from the readme-exports gate, never re-implemented -- and tsup's // convention: its declaration entry `dist/index.d.ts` (or the `.d.mts` / -// `.d.cts` twin tsup writes beside it) is generated from `src/index.ts`. A +// `.d.cts` twin tsup writes beside it) is generated from `src/index.ts` -- or, +// since the 2026-10-27 amendment (#26220), from `src/index.tsx`, the entry +// vite-plugin-dts builds the `ui/` packages from; exactly one of the two. A // published package whose entry cannot be mapped that way is REFUSED, loudly // and by name, never guessed. // 2. no-metadata-surface -- at the merge base no operand is declared in a @@ -4528,6 +4667,8 @@ export async function loadExportReader() { /** tsup's declaration entry, and the `.d.mts` / `.d.cts` twins it writes beside it. */ const TSUP_DTS_ENTRY_RE = /^dist\/index\.d\.[cm]?ts$/; +/** The source files that entry may be generated from: exactly one must exist (D9, amended 2026-10-27, #26220). */ +const SOURCE_ENTRY_NAMES = Object.freeze(['src/index.ts', 'src/index.tsx']); /** Where a rev's blobs live inside the compiler's file system. Never a real path. */ const VIRTUAL_ROOT = '/__adr0087__/'; const TS_SOURCE_RE = /\.(?:d\.)?[cm]?tsx?$/; @@ -4536,9 +4677,12 @@ const TS_SOURCE_RE = /\.(?:d\.)?[cm]?tsx?$/; * The repo-relative SOURCE entry of a workspace package at a rev, or a refusal. * * Only the mapping the ruling names: the package's `exports` map, read by - * `resolveTypesEntry`, lands on tsup's `dist/index.d.ts` (or its twin), which tsup - * generates from `src/index.ts`. Anything else -- no types entry, another file - * name, no `src/index.ts` -- is refused by name rather than guessed. + * `resolveTypesEntry`, lands on `dist/index.d.ts` (or its tsup twin), generated + * from the package's ONE source entry, `src/index.ts` or `src/index.tsx` (the + * 2026-10-27 amendment, #26220: vite-plugin-dts builds the `ui/` packages from + * the `.tsx`). Anything else -- no types entry, another file name, neither + * source, BOTH sources -- is refused by name rather than guessed. The build tool + * is never asked: the source file's name is the fact read here. */ function sourceEntryAt(rev, cwd, pkgName, pkgs, kit) { const info = pkgs.get(pkgName); @@ -4553,17 +4697,25 @@ function sourceEntryAt(rev, cwd, pkgName, pkgs, kit) { try { manifest = JSON.parse(showOrNull(rev, info.file, cwd) ?? ''); } catch { return { refusal: `${info.file} does not parse at ${rev.slice(0, 12)}` }; } const declared = kit.resolveTypesEntry(manifest, '.').entry?.replace(/^\.\//, '') ?? null; const dir = info.file.slice(0, -'package.json'.length); - const entry = `${dir}src/index.ts`; - if (declared === null || !TSUP_DTS_ENTRY_RE.test(declared) || showOrNull(rev, entry, cwd) === null) { + const present = SOURCE_ENTRY_NAMES.map((name) => `${dir}${name}`).filter((p) => showOrNull(rev, p, cwd) !== null); + if (declared !== null && TSUP_DTS_ENTRY_RE.test(declared) && present.length > 1) { + return { + refusal: `the published entry of \`${pkgName}\` is AMBIGUOUS at ${rev.slice(0, 12)}: its \`exports\` map names \`${declared}\`,\n` + + ` and the package carries BOTH ${present.join(' and ')}. The mapping reads ONE source entry\n` + + ' (ADR-0087 D9, amended 2026-10-27), and which one the build compiles is the build tool\'s\n' + + ' answer, which this gate never asks. Delete the stale one. Refused, never guessed', + }; + } + if (declared === null || !TSUP_DTS_ENTRY_RE.test(declared) || present.length === 0) { return { refusal: `the published entry of \`${pkgName}\` cannot be mapped to its source at ${rev.slice(0, 12)}: its \`exports\` map\n` + - ` names ${declared === null ? 'no types entry' : `\`${declared}\``}, and the only mapping this gate reads is tsup's\n` + - ` \`dist/index.d.ts\` (or its \`.d.mts\` / \`.d.cts\` twin) -> \`src/index.ts\`` + - (declared !== null && TSUP_DTS_ENTRY_RE.test(declared) ? `, whose source ${entry} does not exist there` : '') + '.\n' + + ` names ${declared === null ? 'no types entry' : `\`${declared}\``}, and the only mapping this gate reads is\n` + + ` \`dist/index.d.ts\` (or its \`.d.mts\` / \`.d.cts\` twin) -> \`src/index.ts\` or \`src/index.tsx\`` + + (declared !== null && TSUP_DTS_ENTRY_RE.test(declared) ? `, and neither ${dir}src/index.ts nor ${dir}src/index.tsx exists there` : '') + '.\n' + ' An entry this gate cannot map is refused, never guessed', }; } - return { entry, dir }; + return { entry: present[0], dir }; } /** @@ -7710,6 +7862,131 @@ function selfTest() { `WS-U5: a glob richer than \`\` / \`/*\` is refused, never matched against nothing (got ${rich?.message ?? 'no throw'})`); } + // ---- IX (#26220): the 2026-10-27 amendments, predicate 4 and the entry ----- + // + // Two limbs the maintainer's decision A put in ADR-0087 before any code here: + // D8's predicate 4 reads an `any` INDEX SIGNATURE inside a concrete declaration + // (#26293's `ComponentRendererProps`), and D9's entry mapping reads ONE source + // entry, `src/index.ts` or `src/index.tsx` (21 of 38 published ui packages build + // from the `.tsx`). Every green here has a red beside it that differs in the one + // thing the limb must not admit. + battery('IX (#26220): predicate 4 reads an `any` index signature; the entry may be `src/index.tsx`'); + { + const IX_PKGS = { + '@objectstack/spec': { dir: 'packages/spec', private: false }, + '@object-ui/types': { dir: 'ui/packages/types', private: false }, + }; + const IX_PATH = 'ui/packages/types/src/base.ts'; + const IX_REF = `${IX_PATH}#ComponentRendererProps`; + const IX_BASE = 'export interface ComponentRendererProps {\n schema: TSchema;\n [key: string]: any;\n}\n'; + const IX_HEAD = IX_BASE.replace('[key: string]: any;', '[key: string]: unknown;'); + const IX_CS = (ref = IX_REF) => CS({ + bumps: [['@object-ui/types', 'minor']], + body: 'refactor(types)!: forwarded renderer props are `unknown`\n\n**BREAKING**: narrow a forwarded key before use.\n\n' + + `\n`, + }); + const ix = (base, head, ref = IX_REF) => run(mk({ + pkgs: IX_PKGS, + baseFiles: { [IX_PATH]: base }, + files: { [IX_PATH]: head, '.changeset/x.md': IX_CS(ref) }, + })); + const P4_FALSE = /predicate 4: narrowed-from-erased\] is FALSE/; + + const g1 = ix(IX_BASE, IX_HEAD); + green('IX-G1 THE #26293 SHAPE: an `any` index signature becomes `unknown`, every other member and the heritage unchanged', g1); + assert(/ComponentRendererProps \(\[key: string\]: any -> \[key: string\]: unknown\)/.test(g1.judged[0]?.detail ?? ''), + `IX-G1b: the verdict names the index signature that moved, both sides. Got: ${g1.judged[0]?.detail}`); + green('IX-G2 an object-literal `type` alias, `readonly` index signature, narrowed to a concrete type', ix( + 'export type ComponentRendererProps = { id: string; readonly [k: string]: any };\n', + 'export type ComponentRendererProps = { id: string; readonly [k: string]: string };\n', + )); + + red('IX-R1 a member REMOVED beside the narrowed index signature (the #6048 class)', ix( + IX_BASE, IX_HEAD.replace(' schema: TSchema;\n', ''), + ), [P4_FALSE, /#6048/, /index-signature limb .* does not hold either: the member count moved \(2 -> 1\)/]); + red('IX-R2 a member RE-TYPED beside it', ix(IX_BASE, IX_HEAD.replace('schema: TSchema;', 'schema: string;')), + [P4_FALSE, /member 1 moved \(`schema: TSchema` -> `schema: string`\)/]); + red('IX-R3 a member made OPTIONAL beside it', ix(IX_BASE, IX_HEAD.replace('schema: TSchema;', 'schema?: TSchema;')), + [P4_FALSE, /member 1 moved/]); + red('IX-R4 the HERITAGE moved: a type-parameter default changed', ix(IX_BASE, IX_HEAD.replace('', '')), + [P4_FALSE, /heritage moved/]); + red('IX-R5 an `extends` clause added', ix(IX_BASE, IX_HEAD.replace(' {', ' extends Base {')), + [P4_FALSE, /heritage moved/]); + red('IX-R6 the base index signature was NOT `any`: a concrete value type re-typed is no narrowing off erased', ix( + IX_BASE.replace('[key: string]: any;', '[key: string]: string;'), IX_HEAD.replace('[key: string]: unknown;', '[key: string]: number;'), + ), [P4_FALSE, /member 2 moved/]); + red('IX-R7 base index signature `unknown` (not `any`) is outside the limb', ix( + IX_BASE.replace('[key: string]: any;', '[key: string]: unknown;'), IX_HEAD.replace('[key: string]: unknown;', '[key: string]: string;'), + ), [P4_FALSE, /member 2 moved/]); + red('IX-R8 still `any` at HEAD: nothing narrowed', ix(IX_BASE, `${IX_BASE}// touched\n`), + [P4_FALSE, /still `any` at HEAD/]); + red('IX-R9 the KEY of the index signature moved with it', ix(IX_BASE, IX_HEAD.replace('[key: string]', '[key: number]')), + [P4_FALSE, /member 2 moved/]); + red('IX-R10 a `type` alias that is an INTERSECTION is not the shape the limb reads', ix( + 'export type ComponentRendererProps = { id: string; [k: string]: any } & Base;\n', + 'export type ComponentRendererProps = { id: string; [k: string]: unknown } & Base;\n', + ), [P4_FALSE, /reads only an exported `interface`, or a `type` alias whose right-hand side is one object type literal/]); + red('IX-R11 a NESTED `any` index signature is a member re-typed, never a top-level one (the `=>` is not a closer)', ix( + 'export interface ComponentRendererProps {\n meta: { cb: () => void; [k: string]: any; };\n}\n', + 'export interface ComponentRendererProps {\n meta: { cb: () => void; [k: string]: unknown; };\n}\n', + ), [P4_FALSE, /member 1 moved/]); + red('IX-R12 a DOTTED member path keeps reading a return annotation alone', ix( + 'export class Registry {\n props(): { [k: string]: any } {\n return {};\n }\n}\n', + 'export class Registry {\n props(): { [k: string]: unknown } {\n return {};\n }\n}\n', + `${IX_PATH}#Registry.props`, + ), [P4_FALSE, /does not apply to a dotted member path/]); + + assert(readIndexSignature('readonly [k: string]: any')?.value === 'any' && readIndexSignature('[key: string]: unknown')?.key === '[key: string]', + 'IX-U1: an index signature reads as its key part and value type, `readonly` kept in the key'); + assert(readIndexSignature('[P in Keys]: string') === null && readIndexSignature('[Symbol.iterator](): Iterator') === null, + 'IX-U2: a mapped type and a computed member name are not index signatures'); + assert(JSON.stringify(structuralDeclarationParts(IX_BASE, 'ComponentRendererProps')) === + JSON.stringify({ kind: 'interface', heritage: '', members: ['schema: TSchema', '[key: string]: any'] }), + `IX-U3: the parts the limb compares (got ${JSON.stringify(structuralDeclarationParts(IX_BASE, 'ComponentRendererProps'))})`); + assert(indexSignatureLimb(ACTOR_BASE, ACTOR_BASE.replace(' roles: string[];\n', ''), 'ActorUser').ok === false, + 'IX-U4: THE #6048 SHAPE fails the limb -- it has no `any` index signature and it lost a member'); + + // D9: the entry is `src/index.tsx`. + const TX_PKG = '@object-ui/plugin-x'; + const txPath = (rel) => `ui/packages/plugin-x/${rel}`; + const TX_INDEX_HEAD = "export { Widget } from './Widget.js';\n"; + const TX_BASE = { + [txPath('package.json')]: JSON.stringify({ + name: TX_PKG, version: '1.0.0', types: 'dist/index.d.ts', + exports: { '.': { types: './dist/index.d.ts', import: './dist/index.mjs' } }, + }), + [txPath('src/index.tsx')]: `${TX_INDEX_HEAD}export { Gone } from './Gone.js';\nexport const Root = () =>
;\n`, + [txPath('src/Widget.tsx')]: 'export const Widget = () => ;\n', + [txPath('src/Gone.tsx')]: 'export function Gone() {\n return

;\n}\n', + 'packages/platform-objects/src/audit/sys-email-template.object.ts': "export const SysEmailTemplate = { name: 'sys_email_template' };\n", + }; + const TX_CS = CS({ + bumps: [[TX_PKG, 'minor']], + body: 'refactor(plugin-x)!: retire `Gone`\n\n**BREAKING**: retired export.\n\n- FROM `Gone` TO nothing: delete the import.\n\n' + + `\n`, + }); + const tx = (baseOver = {}, headOver = {}) => run(mk({ + pkgs: { '@objectstack/spec': { dir: 'packages/spec', private: false } }, + baseFiles: { ...TX_BASE, ...baseOver }, + files: { [txPath('src/index.tsx')]: `${TX_INDEX_HEAD}export const Root = () =>

;\n`, '.changeset/x.md': TX_CS, ...headOver }, + })); + const t1 = tx(); + green('IX-T1 a package whose ONE source entry is `src/index.tsx` (the vite-plugin-dts shape) retires an export', t1); + assert(/Gone \(export, ui\/packages\/plugin-x\/src\/Gone\.tsx\)/.test(t1.judged[0]?.detail ?? ''), + `IX-T1b: the export is read through the \`.tsx\` entry to its declaration. Got: ${t1.judged[0]?.detail}`); + // The `.ts` twin exports the same names, so a reader that silently PICKED one + // would read a clean retirement here: only the ambiguity refusal holds it red. + const TX_TWIN_BASE = `${TX_INDEX_HEAD}export { Gone } from './Gone.js';\n`; + red('IX-T2 BOTH `src/index.ts` and `src/index.tsx`: ambiguous, refused rather than picked', tx( + { [txPath('src/index.ts')]: TX_TWIN_BASE }, + { [txPath('src/index.ts')]: TX_INDEX_HEAD }, + ), [/published entry of `@object-ui\/plugin-x` is AMBIGUOUS/, /BOTH ui\/packages\/plugin-x\/src\/index\.ts and ui\/packages\/plugin-x\/src\/index\.tsx/]); + red('IX-T3 NEITHER source entry: unmappable, by name', tx( + { [txPath('src/main.tsx')]: TX_BASE[txPath('src/index.tsx')] }, + { [txPath('src/index.tsx')]: null, [txPath('src/main.tsx')]: `${TX_INDEX_HEAD}export const Root = () =>
;\n` }, + ), [/cannot be mapped to its source/, /neither ui\/packages\/plugin-x\/src\/index\.ts nor ui\/packages\/plugin-x\/src\/index\.tsx exists there/]); + } + // ---- G6: a changeset that was ALREADY breaking at base is inherited ------- battery('G6: a changeset that was ALREADY breaking at base is inherited'); {