fix(verify): give the declared-unresolvable control a fixture subject the workspace can never supply - #17979
Conversation
… the workspace can never supply The host-resolution CONTROL builds a temp host app that DECLARES the enterprise multi-org package and deliberately does not install it, then pins the `declared-but-not-installed` remedy wording. Since ADR-0132 made that package a tracked workspace member, pnpm's hoisted store carries it and vitest's own `pnpm exec` bin shim exports a NODE_PATH that reaches the store — so on any tree with a full local build the CJS-shaped host resolve SUCCEEDED, the organization wall came up, and boot was refused several steps later by the membership-policy gate whose message even says "This is NOT ... a missing package". The control never reached the wording it exists to pin: red on a built tree, green on CI, and proving nothing in either state. `bootStack` now takes the subject as an option defaulting to the real package, so the one case whose content is an ABSENCE hands in a `@fixture/*` name the workspace can never contain, and a PREMISE block proves that absence instead of assuming it. The other four cases stay on the real subject and keep the default pinned behaviourally. Same repair #16539 and #16552 landed for packages/qa/dogfood and packages/types. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
`expect(undefined).not.toMatch(...)` is a TypeError, not a pass, so the leg asserting that the REAL subject is reachable from the fixture's anchor went red on exactly the tree it describes — a built one. Each of the two legal shapes now asserts its own discriminating fact: a resolve that succeeded did not come out of the host root, and a resolve that failed did not fail on the bare specifier. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 39daf648ae8cff17c47718967572aee6ad264742 && git checkout 39daf648ae8cff17c47718967572aee6ad264742
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 38472cef9210ba8eebb2e5696310bdcf0fb2ee11 250e8741336b1ef3f5d593350d5809ab242a12bf && git checkout -B drift-repro 38472cef9210ba8eebb2e5696310bdcf0fb2ee11 && git merge --no-ff 250e8741336b1ef3f5d593350d5809ab242a12bf
node scripts/docs-audit/affected-docs.mjs --json 38472cef9210ba8eebb2e5696310bdcf0fb2ee11
|
Verification delta — readings that landed after the body was written
1. The two gates the body listed as unmeasured are now measured. Both had answered exit 3 — 2. The gate account closes at 59/59, not the 52 the body names. Every one of the 59 carries a recorded exit code captured BEFORE any pipe, so the reconciler's zero is derived from those codes rather than asserted by me. 3. The ablation was repeated on the filer's exact condition. The table in the body was measured with only the 34-package closure this card needs. Repeated on the 72/72 whole-workspace build, same commit
Restore proved again: ⇒ Read together with the body: every acceptance item is measured, and nothing in the derived gate set is left NOT MEASURED. Generated by Claude Code |
Contract reviewPR #17979 · card #17911 · head
① ⭐ This is a clause-② review, and the declaration was flipped to reach itThe claim comment declared ⇒ One optional key joins a published interface. A declaration is a judgement; re-declaring is the seat's act, ⛔ not the dev's — and the dev's refusal to self-declare is the behaviour the checker's own text demands. ⛔ Two faults of this seat's, both caught before this review and both recorded rather than quietly fixed:
⭐ The checker's own sentence on that case is worth keeping: “agreement on ABSENCE is its silent case” — comparing two carriers cannot see a gate that was never hung on either. ⇒ The same shape as this round's other lesson (a narrowing by absence is invisible to every instrument that searches for what is present), in a different instrument. ② The shape — the precedent was ported, and the two alternatives are worse for stated reasonsThe dev put the shape question up rather than settling it (A/B/C). A is right, and this seat rules A:
⇒ The card was ruled adjudicable because the fix was precedent, twice (#16539, #16552). A and only A is that precedent. ③ ⭐ The design detail that makes the default safe⇒ Only the one case that asserts “declared but not installed” moves to a name the workspace can never supply; the other four deliberately keep the real package, which is what keeps the default pinned behaviourally rather than leaving it untested. ⛔ A blanket rename would have silently stopped testing the default. And the absence is proved, not assumed: a ④ The ablation — and the leg that makes both-green mean somethingRelayed on the dev's record; ⛔ this seat did not re-run it (it needs a whole-workspace build): ⭐ Both-legs-green cannot distinguish “fixed” from “did not trigger”, and the dev knew it: it replaced the expected tail with a sentinel (mutation proved on disk: anchor 1 → 0, sentinel 1), and exactly one test failed — the CONTROL — with the Received side carrying the real remedy, “It IS declared in …'s package.json, so the declaration is not the problem — repair the install there”, naming the bare specifier. ⇒ The assertion is now reached, which is the defect this card was actually about: a control that cannot reach its assertion proves nothing when green. Reverse control ×2, both RED as required: subject ⑤ ⭐ “Why has CI stayed green” — answered structurally, stronger than the card's suspicionThe card suspected ⇒ ⭐ Not “this shard happened to exclude it” but “no turbo test graph in this workspace can build it at all”, on any of the six shards. A suspicion of sampling was measured into a structural fact. ⑥ Scope, declared rather than buried
Fences hold: 3 files, ⛔ Gates: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0; two first answered exit 3 PREREQUISITE NOT MET and were re-run to a real verdict after the 72/72 build rather than recorded as passes. Verdict and what remainsPASS, and the
Generated by Claude Code |
Fixes #17911
Clause-②: no
The host-resolution
declared-unresolvableCONTROL builds a temp host app thatDECLARES the enterprise multi-org package and deliberately does not install it,
then pins the
declared-but-not-installedremedy wording. Since ADR-0132 /#16215 made that package a tracked workspace member, pnpm's hoisted store
carries it and vitest's own
pnpm execbin shim exports aNODE_PATHthatreaches that store — so on a built tree the CJS-shaped host resolve SUCCEEDS,
the organization wall really comes up, and boot is refused several steps later
by the membership-policy gate.
That is the half that matters: the control never reached the wording it exists
to pin. Red on any tree with a full local build, green on CI, and proving
nothing in either state. This ports the repair #16539 (dogfood) and #16552
(
packages/types/src/node.test.ts) already landed, to its third site.What changed
bootStacktakes the subject as an option,BootOptions.organizationsPackage,defaulting to the real package (
ORGANIZATIONS_PKG). Production callers neverpass it, and the operator-facing sentence still names
@objectstack/organizationsliterally — only the specifier moves.
@fixture/host-organizations, a name this workspace can never contain.PREMISEblock proves that absence instead of assuming it, including the legthat would have caught this card: the fixture subject fails on the BARE
SPECIFIER, which an unbuilt workspace package can never satisfy.
keeps the default pinned behaviourally: each is decided by something the
ambient workspace cannot supply — an app-local
node_modulescopy, which winsover
NODE_PATH, or the UNDECLARED arm, whose fallback is this module's ownESM
import(), and Node's ESM resolver does not consultNODE_PATH.⛔ No manifest gained
@objectstack/organizations(ADR-0132's entitlementboundary;
packages/plugins/organizations/src/no-framework-dependents.pin.test.tsholds it). ⛔
packages/plugins/organizations/**is untouched. ⛔ The ESM/CJSasymmetry is left exactly as measured — it is why this bites the host-resolution
control and not the
plugin-devfail-fast tests.Two-leg ablation, redone here
The card's ablation was the filer's testimony; triage could not re-run it. It was
re-measured on this branch. One thing moves: whether
packages/plugins/organizations/distexists. Same tree, same command(
pnpm --filter @objectstack/verify exec vitest run src/harness.host-resolution.test.ts --maxWorkers=2),exit codes captured before any pipe.
organizations/dist9ccc4179e)250e87413)Tests 1 failed | 6 passed (7)Tests 11 passed (11)Tests 7 passed (7)Tests 11 passed (11)The before/present failure is the card's, verbatim — the assertion at the CONTROL
receives the membership-policy gate instead of the remedy:
Restore discipline, both legs:
distbacked up withcp -aoutside the repo,restored, and the restore PROVED —
files_before=6 files_after=6,index.js=present,git status --porcelain -- packages/plugins/organizationsempty. A trap on EXIT/INT/TERM with absolute paths covered the case where the
container's foreground cap killed the run mid-leg. No moved-aside build directory
was left behind.
The control demonstrably REACHES its wording now
⛔ Both-legs-green alone cannot distinguish "fixed" from "did not trigger", so
the assertion was driven to failure on purpose: the EXPECTED tail was replaced
with a sentinel the real remedy cannot contain, the mutation was proved on disk
(anchor occurrences 1 to 0, sentinel occurrences 1), and the run's Received side
is what the control actually got:
Exactly one test failed under that mutation (the CONTROL), and the importer's own
message names the BARE specifier — the failure shape leg 2 of the PREMISE pins.
Restored from
HEADafterwards; restore proved by blob hash equality plus anempty
git diff HEAD.Reverse control — the CONTROL still controls what it is for
The fixture subject was deliberately pointed at a REAL workspace package name.
Both legs go RED, and their failure modes differ, which is itself the mechanism:
@objectstack/organizations(the historical name)3 failed | 8 passed@objectstack/types(a different real workspace package)3 failed | 8 passedmod.OrganizationsPlugin is not a constructorIn both, the PREMISE leg
is absent from every ambient store the runner exposesalso fires — that is the guard which would have caught this card in the first
place. Each mutation was proved on disk before its run and restored from
HEADafter it, with blob-hash equality and an empty
git diff HEADas the proof.Why CI has stayed green — measured, not suspected
The card flagged this as the thing to measure first and did not assert it. It
suspected the shard's dependency closure. The measurement says something stronger
and simpler.
Turbo's
testtask declaresdependsOn: ["^build"]— a package's DEPENDENCIESare built, never the package itself. Nothing in the workspace may depend on
@objectstack/organizations(ADR-0132, held byno-framework-dependents.pin.test.ts),so it is in no package's
^buildclosure. Measured on this tree:⇒ Not merely "this shard's closure need not include it": no
turbo run testtask graph in this workspace can build it at all, on any of the six Test Core
shards, including the shard that runs the package's own suite. Its
dist/neverexists on a Test Core runner, so the host resolve never reaches the store and the
control has been green for the wrong reason on every CI run.
The contrast confirms it is job isolation rather than luck —
Build Corerunsturbo run build --filter=!@objectstack/docs, whose plan is 79 tasks and DOESinclude
@objectstack/organizations#build— on its own runner, whosenode_modulesno Test Core shard ever sees. The state that reproduces this is adeveloper's full local build, which is exactly the state someone verifying a
change is in.
Same shape #16539 recorded for the dogfood copy.
Verification
pnpm --filter @objectstack/verify test— exit 0,Test Files 14 passed (14)/Tests 107 passed (107)(103 before; the 4 new ones are the PREMISE block).pnpm --filter @objectstack/verify typecheck— exit 0;check:test-typecheckholds at0 file(s) / 0 error(s), so the shrink-only TEST_DEBT ledger did not move.pnpm --filter @objectstack/verify build— exit 0.turbo run build --filter='@objectstack/verify...' --filter='@objectstack/organizations...'— 34/34 successful.scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(not a hand-written list) and run individually with each exit code captured BEFORE any pipe: 52 commands, 50 exit 0.Acceptance notes
PREREQUISITE NOT MET, thecode both scripts use for "nothing was measured", explicitly distinct from a
finding's exit 1:
check:dual-build-cjs-loadsandcheck:type-check-debtbothread built output for the WHOLE workspace. Their readings after a
whole-workspace build are in the verification comment on the card.
face named only the test file and the changeset. The fix needs one more file —
packages/verify/src/harness.ts— because the specifier is hard-coded in theseam under test, so the fixture cannot be renamed without the harness accepting
the name. This is the same shape the precedent had: dogfood: the enterprise-organizations
declared-unresolvableCONTROL flips red once packages/plugins/organizations is BUILT — its premise died when the package moved to open core #16539 changedpackages/qa/dogfood/test/enterprise-organizations.ts(a non-test helper) togive
probeOrganizationsits optionalpkgparameter.PRIVATE package, so its parameter published nothing.
BootOptionsis exportedfrom
@objectstack/verify's entry point, soorganizationsPackagebecomespublished API and the changeset is
minorrather than thepatchatest-only fixture rename would take. It is additive and optional, and this
interface already carries options that exist so a test can isolate one belt
(
analytics,multiTenant: 'posture-only'). The alternative — reading anoption the type never declares — was rejected as a surface that lies. Say the
word and it can be reshaped.
⚠️note about./harnessbeing imported without its.jsextension and the TS7006 thatwould add to this package's frozen TEST_DEBT entry. That remains true and
untouched here; the one-line fix belongs to whoever takes that card.
Generated by Claude Code