Skip to content

Commit a6c3fb8

Browse files
committed
test(tooling): pin the DEPTH axis with both controls and the new failure mode
wip Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
1 parent eea6dcd commit a6c3fb8

1 file changed

Lines changed: 150 additions & 1 deletion

File tree

scripts/check-changeset-no-major.mjs

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1650,11 +1650,12 @@ const SELF_TEST_BATTERIES = Object.freeze({
16501650
'The wiring: these fixtures must actually run on every PR': 22,
16511651
"The LEVEL axis: #16044's two heads, one word apart (#16055)": 56,
16521652
'The GRAIN: a PR-scoped declaration judged at PR scope (#16361)': 25,
1653+
'THE DEPTH: a nested package is a candidate the axis can refuse (#16713)': 21,
16531654
});
16541655

16551656
// DELETING an entry silences that battery's floor exactly as effectively as
16561657
// zeroing it, so the roster's own size is pinned too.
1657-
const SELF_TEST_BATTERY_FLOOR = 15;
1658+
const SELF_TEST_BATTERY_FLOOR = 16;
16581659

16591660
// The key an assertion is filed under when no battery is open. It is not a
16601661
// declared battery, so it reds by the same set difference rather than silently
@@ -2786,6 +2787,154 @@ function selfTest() {
27862787
}
27872788
}
27882789

2790+
// ── THE DEPTH: a nested package is a candidate at all (#16713) ───────────
2791+
//
2792+
// The axis used to read the package segment one path segment wide, so it
2793+
// saw 23 of this workspace's 74 packages and the other 51 — every driver,
2794+
// service, plugin, connector, trigger, adapter and app — could pair a
2795+
// `Clause-②: yes` with a `patch` and stay green. THE PAIR IS THE CONTROL
2796+
// here exactly as it is above: the nested leg going red proves nothing on
2797+
// its own, because "the matcher was widened" and "the gate now refuses
2798+
// everything" produce the same red. So every fixture below is answered by a
2799+
// control that must STAY green, and the flat leg is re-asserted in this
2800+
// same harness so a nested red is readable as a widening rather than as a
2801+
// gate that lost its discrimination.
2802+
battery('THE DEPTH: a nested package is a candidate the axis can refuse (#16713)');
2803+
{
2804+
const owners = (p) => JSON.stringify(publishedSourceOwners(p));
2805+
const declaredYes = { value: 'yes', payload: true, readings: ['carrier: on'] };
2806+
2807+
// The shape reading, at three depths and its controls. Depth-agnostic is
2808+
// the whole point: a repair that merely allowed ONE extra segment passes
2809+
// the first two of these and fails the third.
2810+
assert(owners('packages/cli/src/commands/lint.ts') === '["packages/cli"]', `flat: one segment ⇒ the package dir — got ${owners('packages/cli/src/commands/lint.ts')}`);
2811+
assert(
2812+
owners('packages/drivers/driver-sql/src/sql-driver.ts') === '["packages/drivers/driver-sql"]',
2813+
`nested: the GROUP is not the package, the dir under it is — got ${owners('packages/drivers/driver-sql/src/sql-driver.ts')}`,
2814+
);
2815+
assert(
2816+
owners('packages/a/b/c/src/x.ts') === '["packages/a/b/c"]',
2817+
`three levels deep reads the same way — a fix that hard-codes ONE optional group segment fails HERE, which is why the reading is a walk and not a wider pattern — got ${owners('packages/a/b/c/src/x.ts')}`,
2818+
);
2819+
assert(owners('packages/drivers/driver-sql/README.md') === '[]', 'control: a path with no `src/` segment owns nothing — otherwise the three positives above would hold for every file in the repo');
2820+
assert(
2821+
owners('packages/cli/bin/os.mjs') === '[]',
2822+
'control: `bin/**` is still NOT read — WHICH roots ship is #16692\'s axis and this card must not silently close it; a fix that reddened here would be answering a different card',
2823+
);
2824+
assert(owners('scripts/check-changeset-no-major.mjs') === '[]', 'control: outside `packages/` there is no owner at all');
2825+
assert(owners('packages/src/x.ts') === '[]', 'control: the owner must be at least `packages/<something>` — `packages` itself is not a package');
2826+
assert(owners('packages/cli/src') === '[]', 'control: a path that IS `src` is not a path INSIDE `src/` — the walk stops one short of the end');
2827+
2828+
// The multi-candidate case, which this repo really contains, and the
2829+
// reason the walk resolves SHALLOWEST first. `packages/create-objectstack`
2830+
// ships a scaffold template that carries its own `package.json`
2831+
// (`objectstack-blank`, private, not a workspace member), and the template
2832+
// has a `src/` of its own. Resolving to the NEAREST manifest would name
2833+
// the private template and DROP the real package — a regression against
2834+
// the one-segment reading this replaces.
2835+
const twin = 'packages/create-objectstack/src/templates/blank/src/objects/note.object.ts';
2836+
assert(
2837+
owners(twin) === '["packages/create-objectstack","packages/create-objectstack/src/templates/blank"]',
2838+
`a path can own two candidates and they are ordered SHALLOWEST first — got ${owners(twin)}`,
2839+
);
2840+
2841+
const NESTED = '@objectstack/driver-sql';
2842+
const NESTED_DIR = 'packages/drivers/driver-sql';
2843+
const CS = '.changeset/depth-leg.md';
2844+
const nestedRepo = (bump) =>
2845+
makeRepo(
2846+
{ [`${NESTED_DIR}/package.json`]: JSON.stringify({ name: NESTED, version: '0.0.0' }), [`${NESTED_DIR}/src/sql-driver.ts`]: 'export const before = 1;\n' },
2847+
{ [`${NESTED_DIR}/src/sql-driver.ts`]: 'export const after = 2;\n', [CS]: `---\n"${NESTED}": ${bump}\n---\n\nbody\n` },
2848+
);
2849+
const levelOf = ({ dir, base }) => {
2850+
const scanned = scan({ cwd: dir, base });
2851+
const touched = packagesTouched({ cwd: dir, from: scanned.base, head: 'HEAD' });
2852+
return { touched, result: judgeLevel({ levels: scanned.levels, touched, declaration: declaredYes }) };
2853+
};
2854+
2855+
// THE LEG THIS CARD IS ABOUT. Byte for byte the assertion the flat leg
2856+
// has carried since #16055, with the package one directory deeper.
2857+
const nestedPatch = levelOf(nestedRepo('patch'));
2858+
assert(
2859+
nestedPatch.touched.packages.includes(NESTED),
2860+
`end to end: a nested package's src moved must NAME the package from its own manifest — got ${JSON.stringify(nestedPatch.touched)}`,
2861+
);
2862+
assert(
2863+
nestedPatch.result.verdict === 'enforce',
2864+
`end to end: a real diff that moves ${NESTED_DIR}/src/** and grades it \`patch\` under a \`yes\` declaration is REFUSED — got ${nestedPatch.result.verdict}`,
2865+
);
2866+
assert(renderLevel(nestedPatch.result).exitCode === 1, 'and it EXITS 1 — the exit code is what becomes the check-run conclusion, and a verdict name CI never reads is not a refusal');
2867+
assert(
2868+
renderLevel(nestedPatch.result).stderr.join('\n').includes(NESTED),
2869+
'the refusal must NAME the nested package — an author who cannot see which line is being asked about cannot act on it',
2870+
);
2871+
2872+
// CONTROL 1, the level: the same repository one word along. Without this,
2873+
// the red above is equally consistent with "any nested diff is now
2874+
// refused", which is the shape a tolerance-free fix must not have.
2875+
const nestedMinor = levelOf(nestedRepo('minor'));
2876+
assert(
2877+
nestedMinor.touched.packages.includes(NESTED) && nestedMinor.result.verdict === 'clean',
2878+
`control: the same nested diff graded \`minor\` PASSES while still being SEEN — so the refusal is about the level, not about the depth — got ${JSON.stringify(nestedMinor.touched)} / ${nestedMinor.result.verdict}`,
2879+
);
2880+
assert(renderLevel(nestedMinor.result).exitCode === 0, 'and the two nested legs differ in EXIT CODE, one word apart');
2881+
2882+
// CONTROL 2, the flat leg, re-driven HERE. #16055's assertion lives in
2883+
// its own battery; re-stating it inside this harness is what makes the
2884+
// nested red above readable as a WIDENING rather than as a gate that
2885+
// stopped discriminating.
2886+
const flat = levelOf(
2887+
makeRepo(
2888+
{ 'packages/cli/package.json': JSON.stringify({ name: '@objectstack/cli', version: '0.0.0' }), 'packages/cli/src/commands/lint.ts': 'export const before = 1;\n' },
2889+
{ 'packages/cli/src/commands/lint.ts': 'export const after = 2;\n', [CS]: '---\n"@objectstack/cli": patch\n---\n\nbody\n' },
2890+
),
2891+
);
2892+
assert(
2893+
flat.result.verdict === 'enforce' && renderLevel(flat.result).exitCode === 1,
2894+
`control: the FLAT leg still reds in this same harness — a nested red beside a flat green would mean the reading moved rather than widened — got ${flat.result.verdict}`,
2895+
);
2896+
2897+
// CONTROL 3, the nonsense leg: a diff that publishes nothing must stay
2898+
// green under the very same `yes`. An implementation that simply always
2899+
// enforced would satisfy every positive above and fail only here.
2900+
const nonsense = levelOf(
2901+
makeRepo(
2902+
{ 'packages/cli/package.json': JSON.stringify({ name: '@objectstack/cli', version: '0.0.0' }), 'content/docs/a.mdx': 'a\n' },
2903+
{ 'content/docs/a.mdx': 'b\n', [CS]: '---\n"@objectstack/cli": patch\n---\n\nbody\n' },
2904+
),
2905+
);
2906+
assert(
2907+
nonsense.touched.packages.length === 0 && nonsense.result.verdict === 'clean' && renderLevel(nonsense.result).exitCode === 0,
2908+
`control: a diff that moves no published source is still not this gate's business under a \`yes\` — got ${JSON.stringify(nonsense.touched)} / ${nonsense.result.verdict}`,
2909+
);
2910+
2911+
// ⭐ THE NEW FAILURE MODE. Widening the shape means nested paths now
2912+
// MATCH, so a nested dir whose manifest cannot be read has somewhere to
2913+
// land. Before this change it landed in NEITHER set — the exact shape
2914+
// #4690 forbids, and the one the filing card names: the gate could not
2915+
// report a limb it never grew. `packages/mystery` pins this for a flat
2916+
// dir in the battery above; this pins it at depth.
2917+
const { dir: nmDir, base: nmBase } = makeRepo(
2918+
{ 'packages/newgroup/newpkg/src/a.ts': 'a\n' },
2919+
{ 'packages/newgroup/newpkg/src/a.ts': 'b\n', [CS]: '---\n"@objectstack/cli": patch\n---\n\nbody\n' },
2920+
);
2921+
const nmScanned = scan({ cwd: nmDir, base: nmBase });
2922+
const nmTouched = packagesTouched({ cwd: nmDir, from: nmScanned.base, head: 'HEAD' });
2923+
assert(
2924+
nmTouched.unreadable.includes('packages/newgroup/newpkg'),
2925+
`a NESTED dir whose manifest cannot be read is reported as unreadable, not as absent (#4690) — got ${JSON.stringify(nmTouched)}`,
2926+
);
2927+
assert(nmTouched.packages.length === 0, 'and it is not named as a package either — an unreadable manifest yields no name to report');
2928+
assert(
2929+
nmTouched.unreadable.length + nmTouched.packages.length === 1,
2930+
'the invariant the widening owes: a path that MATCHES the shape lands in exactly one of the two sets, never in neither — landing in neither is the whole finding this battery closes',
2931+
);
2932+
assert(
2933+
renderLevel(judgeLevel({ levels: nmScanned.levels, touched: nmTouched, declaration: declaredYes })).stdout.join('\n').includes('packages/newgroup/newpkg'),
2934+
'and the tick PRINTS it — an offender that could not be seen must be stated beside the green, or the green is the same silent pass this card is about',
2935+
);
2936+
}
2937+
27892938
} finally {
27902939
for (const dir of repos) rmSync(dir, { recursive: true, force: true });
27912940
}

0 commit comments

Comments
 (0)