Skip to content

Commit d0c45f1

Browse files
committed
test(cli): pin the union fold by the finding's PEDIGREE, not by its exit code
`union-fold-command-parity.test.ts` claimed to pin `authoringRuleUnionStack`, the fold that makes the author-time rule table's INPUT non-empty on an option-B (`packages[]`-only) stack. It could not: since #18677 and #18778 all three doors also run `runPerPackageAuthoringRules`, which judges each package body as its own stack and raises the SAME rule at the SAME path on this fixture. Measured by ablation at 13d5294 — the fold deleted: 6/6 green; the per-package pass deleted: 6/6 green; BOTH deleted: 3 red. The two mechanisms are told apart by the finding's pedigree: the per-package pass prefixes `where` with `package '<id>' — ` and the union run renders it bare. Each refusal case now asserts that prefix is ABSENT, and a new positive control on a per-package-only fixture asserts the same suite can make it PRESENT, so the absence is a measurement rather than a regex that never matches. A structural falsifier is not available for this rule class and the header says so with the reason: the per-package pass is a superset of the union run for reference rules, so a dangling name dangles in both views. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QCdUBjM47SxioST9z5Zwdf
1 parent 13d5294 commit d0c45f1

1 file changed

Lines changed: 172 additions & 0 deletions

File tree

‎packages/cli/test/union-fold-command-parity.test.ts‎

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,55 @@
4949
* that fires on any `packages[]` at all — would satisfy the failing case alone.
5050
* The pair asserts what the card actually claims: that the option-B stack is
5151
* READ, not that it is rejected.
52+
*
53+
* ## ⚠️ [#18897] The refusal cases were satisfied by an IMPOSTOR — and the
54+
* ## impostor is a SIBLING PASS that arrived after this file was written
55+
*
56+
* These three refusal cases claimed to pin `authoringRuleUnionStack` — the fold
57+
* that makes the rule table's INPUT non-empty on an option-B stack. They could
58+
* not. Since #18677 (`os validate`) and #18778 (`os lint`) all three doors ALSO
59+
* run `runPerPackageAuthoringRules`, which judges each `packages[]` entry as its
60+
* own stack. That pass sees this fixture's one package body, raises the SAME
61+
* rule at the SAME path, and refuses with the same exit code. ⇒ `exit 1` +
62+
* `contains(RULE)` + `contains(RULE_PATH)` is satisfied by EITHER mechanism, so
63+
* the control could not tell the fold from its sibling.
64+
*
65+
* ⛔ Not reasoned about — measured by ablation, at `origin/main` 13d52947d8:
66+
*
67+
* ablation this file
68+
* `authoringRuleUnionStack` never folds 6/6 GREEN
69+
* `runPerPackageAuthoringRules` yields no findings 6/6 GREEN
70+
* BOTH of the above 3 RED
71+
*
72+
* ⭐ Either mechanism alone kept every case green; only deleting both turned the
73+
* three refusal cases red. That is what a control satisfied by something other
74+
* than the mechanism it names looks like from the inside — green, and green for
75+
* the wrong reason.
76+
*
77+
* ## What discriminates them: a PEDIGREE, ⛔ not a count and ⛔ not an exit code
78+
*
79+
* `runPerPackageAuthoringRules` prefixes the `where` of every finding IT raises
80+
* with `package '<id>' — `; it owns that prefix, and three sibling files pin the
81+
* same regex. The union run renders `where` bare. Measured on THIS fixture, same
82+
* command, with only the fold's presence moving:
83+
*
84+
* fold intact • object "ob_order" · field "ghost": …
85+
* fold ablated • package 'com.example.ob' — object "ob_order" · field "ghost": …
86+
*
87+
* So each refusal case now asserts the refusal carries NO per-package prefix —
88+
* the finding is the UNION run's, which is the mechanism this file is about.
89+
*
90+
* ⚠️ A real falsifier — a fixture in which the impostor structurally CANNOT
91+
* raise this finding — does not exist for this rule class, and that was measured
92+
* rather than assumed: the per-package pass is a SUPERSET of the union run for
93+
* reference rules (`utils/artifact-packages.ts`: "the per-package run is
94+
* STRICTER"), because each body is judged with the artifact's own `packages[]`
95+
* handed in as resolution context. A name no package provides therefore dangles
96+
* in BOTH views, under every arrangement of packages. ⇒ here the pedigree is the
97+
* whole discriminant, and the negative assertion is made load-bearing by the
98+
* POSITIVE control at the foot of this file — a fixture whose finding really is
99+
* per-package-only. Without that pair, "the prefix is absent" would be satisfied
100+
* by a prefix this suite never produces at all.
52101
*/
53102

54103
import { describe, expect, it } from 'vitest';
@@ -69,6 +118,15 @@ const AUTHORING_COMMANDS = ['validate', 'lint', 'build'] as const;
69118
const RULE = 'object-reference-unknown';
70119
const RULE_PATH = 'objects[0].fields.ghost.reference';
71120

121+
/**
122+
* [#18897] The `where` prefix `runPerPackageAuthoringRules` puts on every
123+
* finding IT raises — the one thing that tells the union run's finding apart
124+
* from the per-package pass's copy of it. ⛔ Not this file's guess at a format:
125+
* the pass owns the prefix, and `validate-/lint-per-package-authoring-parity`
126+
* and `build-text-face-advisory-count` pin the identical regex.
127+
*/
128+
const PER_PACKAGE_WHERE = /package '[^']+' — /;
129+
72130
/**
73131
* The card's repro verbatim: no top-level `objects`, one `packages[]` entry
74132
* carrying an object whose `ghost` lookup points at an object that does not
@@ -100,6 +158,73 @@ const optionBStack = (reference: string): Record<string, unknown> => ({
100158
],
101159
});
102160

161+
/**
162+
* [#18897] The POSITIVE half of the pedigree pair: a project whose ONLY
163+
* author-time finding is one the union run genuinely cannot see.
164+
*
165+
* `core` owns `ob_account`; `orders` owns the view that displays
166+
* `ob_account.industry`. Folded into one union the field HAS a consumer and
167+
* nothing is raised; judged per package, `core` declares a field nothing in
168+
* `core` reads — so the survivor is the per-package pass's alone and carries its
169+
* `where` prefix. It is the falsifier shape PR #18878 landed in
170+
* `lint-per-package-authoring-parity.test.ts`, and it is here for one job: to
171+
* prove that {@link PER_PACKAGE_WHERE} is a prefix this suite CAN observe, so
172+
* the refusal cases' `toBe(false)` is a measurement and not the silence of a
173+
* regex that never matches anything. ⛔ Do not remove the view to "simplify" it.
174+
*/
175+
const perPackageOnlyStack = (): Record<string, unknown> => {
176+
const coreManifest = {
177+
id: 'com.example.obflip.core', name: 'obflip core', namespace: 'ob',
178+
version: '1.0.0', type: 'app', engines: { protocol: '^17' },
179+
};
180+
const coreObjects = [{
181+
name: 'ob_account', label: 'Account', pluralLabel: 'Accounts', sharingModel: 'private',
182+
fields: {
183+
name: { name: 'name', type: 'text', label: 'Account Name', required: true },
184+
industry: { name: 'industry', type: 'text', label: 'Industry' },
185+
},
186+
}];
187+
const coreApps = [{
188+
name: 'ob_crm', label: 'OB CRM',
189+
navigation: [{
190+
id: 'sales_group', type: 'group', label: 'Sales',
191+
children: [{ id: 'nav_accounts', type: 'object', objectName: 'ob_account', label: 'Accounts' }],
192+
}],
193+
}];
194+
const ordersManifest = {
195+
id: 'com.example.obflip.orders', name: 'obflip orders', namespace: 'ob',
196+
version: '1.0.0', type: 'module', engines: { protocol: '^17' },
197+
dependencies: { 'com.example.obflip.core': '^1.0.0' },
198+
};
199+
const ordersObjects = [{
200+
name: 'ob_order', label: 'Order', pluralLabel: 'Orders', sharingModel: 'private',
201+
fields: {
202+
name: { name: 'name', type: 'text', label: 'Order Number', required: true },
203+
account: { name: 'account', type: 'lookup', label: 'Account', reference: 'ob_account' },
204+
},
205+
}];
206+
const ordersViews = [
207+
{
208+
name: 'ob_account_list', label: 'Account List', object: 'ob_account',
209+
list: { label: 'Account List', columns: ['name', 'industry'] },
210+
},
211+
{
212+
name: 'ob_order_list', label: 'Order List', object: 'ob_order',
213+
list: { label: 'Order List', columns: ['name', 'account'] },
214+
},
215+
];
216+
return {
217+
manifest: coreManifest,
218+
objects: [...ordersObjects, ...coreObjects],
219+
apps: [...coreApps],
220+
views: [...ordersViews],
221+
packages: [
222+
{ manifest: { ...ordersManifest, objects: ordersObjects, views: ordersViews } },
223+
{ manifest: { ...coreManifest, objects: coreObjects, apps: coreApps } },
224+
],
225+
};
226+
};
227+
103228
interface Run {
104229
code: number;
105230
output: string;
@@ -152,6 +277,23 @@ describe('#17069 — os validate and os lint judge the option-B stack, not an em
152277
`os ${command} named ${RULE} at a different path than the other doors — the three commands ` +
153278
`must report one finding one way`,
154279
).toContain(RULE_PATH);
280+
// ⭐ [#18897] PEDIGREE. Everything above this line is satisfied by the
281+
// per-package authoring pass, which raises the same rule at the same path
282+
// on this one-package body and refuses with the same code — measured: with
283+
// `authoringRuleUnionStack` ablated all six cases here stayed GREEN, and
284+
// only ablating the per-package pass TOO turned these three red. The union
285+
// run renders `where` bare; the per-package pass prefixes it. So the
286+
// absence of that prefix is what makes this case a reading of the FOLD.
287+
expect(
288+
PER_PACKAGE_WHERE.test(run.output),
289+
`os ${command} refused this stack through the PER-PACKAGE authoring pass, not through the ` +
290+
`union fold this file pins: the finding carries ${String(PER_PACKAGE_WHERE)}, the prefix ` +
291+
`runPerPackageAuthoringRules puts on its own findings. The fold is what makes the rule ` +
292+
`table's INPUT non-empty on an option-B stack, and with it gone this command is refusing ` +
293+
`for a reason #17069 did not buy. ⛔ Do not answer this by deleting the assertion — the ` +
294+
`pedigree is the only thing here that can tell the two mechanisms apart.` +
295+
`\n--- output ---\n${run.output}`,
296+
).toBe(false);
155297
},
156298
180_000,
157299
);
@@ -171,4 +313,34 @@ describe('#17069 — os validate and os lint judge the option-B stack, not an em
171313
},
172314
180_000,
173315
);
316+
317+
it.each(AUTHORING_COMMANDS)(
318+
'⭐ PEDIGREE CONTROL — os %s DOES render the per-package prefix when the finding is the pass\'s own',
319+
(command) => {
320+
// The other half of the pair above. A `toBe(false)` on a regex is only a
321+
// measurement if the same suite can make it true; without this case an
322+
// absent prefix and a prefix nothing ever emits read identically — which
323+
// is the whole class of defect #18897 is about, one level up.
324+
const run = runCommand(command, perPackageOnlyStack());
325+
expect(
326+
run.code,
327+
`os ${command} exited ${run.code} on the per-package-only fixture. Its single finding is an ` +
328+
`ADVISORY (field-no-consumers), so every door reports it and none of them fails on it.` +
329+
`\n--- output ---\n${run.output}`,
330+
).toBe(0);
331+
expect(
332+
PER_PACKAGE_WHERE.test(run.output),
333+
`os ${command} raised no per-package-prefixed finding on a fixture whose only finding the ` +
334+
`union run cannot see (core declares ob_account.industry; orders owns the view that ` +
335+
`displays it). Either the pass stopped running on this door, or the prefix was re-spelled ` +
336+
`— and in both cases the refusal cases above are asserting the absence of something this ` +
337+
`suite no longer produces.\n--- output ---\n${run.output}`,
338+
).toBe(true);
339+
// …and it is that field, not some other advisory that happens to be
340+
// prefixed: the pedigree names the member, never just the shape.
341+
expect(run.output, `os ${command} prefixed a finding, but not the one this fixture is built on`)
342+
.toContain('industry');
343+
},
344+
180_000,
345+
);
174346
});

0 commit comments

Comments
 (0)