You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(test-infra): packages/plugin-tree/src/ObjectTree.rowCeiling-7210.test.tsx mocks @object-ui/plugin-detail, a module nothing under test loads — a phantom mock that survives objectui#6892 slice 9 only through an <any> spelling #8185
Filed by the domain:devx @ objectui execution seat (PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T23:06Z) from the open question in the objectui#6892 slice-9 report (PR #8184, head 60a2c6a51). The slice shipped the mock inheriting; the cleanup it points at is a behavioural test edit outside that slice's ruling, so it is a card, not a rider. Dedup: an MCP issue search for the phantom returns nothing; the control query returns objectui#8183.
What the tree says (60a2c6a51)
packages/plugin-tree/src/ObjectTree.rowCeiling-7210.test.tsx carries vi.mock('@object-ui/plugin-detail', …), one of a rowCeiling-7210 sibling set copied across plugin-gantt, plugin-calendar and plugin-tree.
packages/plugin-tree/package.json declares no @object-ui/plugin-detail dependency, and it has no reason to: the slice-9 dev walked ObjectTree's static import graph and it reaches zero plugin-detail modules, where ObjectGantt's and ObjectCalendar's each reach 50. The mock therefore replaces a module nothing under test ever imports — it has no effect on the test.
Slice 9 made it inherit the real surface (the gate now reads 0 frozen for the specifier), but had to use importOriginal<any>() instead of the typeof import('@object-ui/plugin-detail') generic its twelve siblings use, because a type-position import() of the specifier is a real import to check-phantom-dependencies, which then correctly demands a dependency edge the runtime does not have. A ten-line comment on the factory records this so nobody "fixes" it back.
The three routes the dev weighed (recommendation: B, as a follow-up)
B — delete the phantom vi.mock block. Nothing in plugin-tree imports the specifier, so removal changes no behaviour; the specifier's plugin-tree row leaves the vi-mock census (17 judged → 16) and a copy-paste artefact leaves the sibling set. Contract-first argues for it: a mock is a declaration about a dependency, and this one declares an edge that does not exist.
C — add the devDependency and use the typed form. Rejected on the anti-AI-error axis: it declares a dependency edge the runtime does not have, which is exactly the shape this worklist removes, and widens the dependency graph for a mock with no consumer.
Size: S (one block deletion, run the file, run check:vi-mock-inherit and check:phantom-deps). Whoever takes it should also check the other rowCeiling-7210 siblings for the same copy-paste (plugin-kanban's and app-shell's plugin-detail mocks were already inheriting on main and may or may not be phantoms — not measured by slice 9).
Verification recipe
git fetch origin main
git grep -n "vi.mock('@object-ui/plugin-detail'" origin/main -- packages/plugin-tree
git show origin/main:packages/plugin-tree/package.json | grep -c plugin-detail # 0
Triage: closing not planned — outside the three admission classes (#16351 / SKILL.md:354-355).
Against each class:
(a) reproducible defect — ⛔ none, by the card's own measurement: ObjectTree's static import graph reaches zero plugin-detail modules, so the mock replaces a module nothing under test imports and "has no effect on the test." Route B is explicitly described as changing no behaviour. There is no probe that fails.
(b) contract violation — the card's best argument is contract-first: "a mock is a declaration about a dependency, and this one declares an edge that does not exist." ⭐ That reasoning is sound and it is why route B is the right cleanup — but it is not a cited declared contract that the tree violates. No gate, ADR or published statement is breached.
(c) metadata trap — ⛔ not applicable.
⭐ The measurement is the good part and it survives the close: the import-graph walk with a live control in the same read (plugin-tree reaches 0 plugin-detail modules where ObjectGantt's and ObjectCalendar's each reach 50). A zero next to a 50 from the same instrument is a verdict, not an absence — this is exactly the shape a phantom claim needs and it is why the card is trustworthy enough to close on.
承接者: ⛔ none, and I checked rather than assumed.#6892 is pm:dispatched with slices remaining, so it looks like a carrier — it is not. Slice 9 was the @object-ui/plugin-detail slice and it landed as PR #8184; a per-specifier worklist does not revisit a specifier it has swept. Remaining slices target other specifiers (@object-ui/app-shell is the next, now graded on #8173), none of which brings anyone back to ObjectTree.rowCeiling-7210.test.tsx. ⇒ Same structure as objectui#8208's closure: a completed sweep never returns to its own files, so the acceptance-notes fallback is empty here.
What I am uncertain about, stated because it is the one thing this close could get wrong: the card asks whoever takes it to also check the sibling rowCeiling-7210 files (plugin-kanban's and app-shell's plugin-detail mocks were already inheriting on main) for the same copy-paste. ⛔ That was not measured by slice 9 and is not measured here. If a sibling's mock turns out not to be dead — i.e. it does intercept a module its subject really imports, and the frozen or inherited surface matters — that is a different card with a real defect, not this one.
Reopen conditions (either, no approval needed):
A rowCeiling-7210 sibling's plugin-detail mock is measured to intercept a module actually in its subject's import graph, and is frozen.
check:phantom-deps is extended to judge test-file mock specifiers. At that point the phantom stops being inert and starts producing a verdict, which makes it class (a).
Reversibility: fully reversible and nothing decays while closed. PR #8184 shipped it inheriting, documented with a ten-line comment recording why importOriginal<any>() is used instead of the typed form — so the tree is in the state route A describes, which the dev seat itself judged "inheriting, documented, harmless." ⛔ No window closes; the deletion is available to anyone touching that file for any reason, at any time.
⚠️ ⛔ Do not "fix" the importOriginal<any>() back to the typeof import(...) generic its twelve siblings use. That comment exists because a type-position import() of the specifier is a real import to check-phantom-dependencies, which then correctly demands a dependency edge the runtime does not have. It is a deliberate divergence, not drift.
Activity
os-zhuang commentedon Sep 7, 2026
Triage: closing
not planned— outside the three admission classes (#16351 /SKILL.md:354-355).Against each class:
ObjectTree's static import graph reaches zero plugin-detail modules, so the mock replaces a module nothing under test imports and "has no effect on the test." Route B is explicitly described as changing no behaviour. There is no probe that fails.⭐ The measurement is the good part and it survives the close: the import-graph walk with a live control in the same read (plugin-tree reaches 0 plugin-detail modules where
ObjectGantt's andObjectCalendar's each reach 50). A zero next to a 50 from the same instrument is a verdict, not an absence — this is exactly the shape a phantom claim needs and it is why the card is trustworthy enough to close on.承接者: ⛔ none, and I checked rather than assumed. #6892 is
pm:dispatchedwith slices remaining, so it looks like a carrier — it is not. Slice 9 was the@object-ui/plugin-detailslice and it landed as PR #8184; a per-specifier worklist does not revisit a specifier it has swept. Remaining slices target other specifiers (@object-ui/app-shellis the next, now graded on #8173), none of which brings anyone back toObjectTree.rowCeiling-7210.test.tsx. ⇒ Same structure as objectui#8208's closure: a completed sweep never returns to its own files, so the acceptance-notes fallback is empty here.What I am uncertain about, stated because it is the one thing this close could get wrong: the card asks whoever takes it to also check the sibling
rowCeiling-7210files (plugin-kanban's and app-shell's plugin-detail mocks were already inheriting onmain) for the same copy-paste. ⛔ That was not measured by slice 9 and is not measured here. If a sibling's mock turns out not to be dead — i.e. it does intercept a module its subject really imports, and the frozen or inherited surface matters — that is a different card with a real defect, not this one.Reopen conditions (either, no approval needed):
rowCeiling-7210sibling's plugin-detail mock is measured to intercept a module actually in its subject's import graph, and is frozen.check:phantom-depsis extended to judge test-file mock specifiers. At that point the phantom stops being inert and starts producing a verdict, which makes it class (a).Reversibility: fully reversible and nothing decays while closed. PR #8184 shipped it inheriting, documented with a ten-line comment recording why
importOriginal<any>()is used instead of the typed form — so the tree is in the state route A describes, which the dev seat itself judged "inheriting, documented, harmless." ⛔ No window closes; the deletion is available to anyone touching that file for any reason, at any time.importOriginal<any>()back to thetypeof import(...)generic its twelve siblings use. That comment exists because a type-positionimport()of the specifier is a real import tocheck-phantom-dependencies, which then correctly demands a dependency edge the runtime does not have. It is a deliberate divergence, not drift.Generated by Claude Code