Repository navigation
Commit 611795e
fix(verify): give the declared-unresolvable control a fixture subject the workspace can never supply (#17979)
Fixes #17911
Clause-②: no
The host-resolution `declared-unresolvable` 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 /
#16215 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 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
- `bootStack` takes the subject as an option,
`BootOptions.organizationsPackage`,
defaulting to the real package (`ORGANIZATIONS_PKG`). Production callers
never
pass it, and the operator-facing sentence still names
`@objectstack/organizations`
literally — only the specifier moves.
- The CONTROL — the one case whose entire content is an ABSENCE — hands
in
`@fixture/host-organizations`, a name this workspace can never contain.
- A `PREMISE` block proves that absence instead of assuming it,
including the leg
that would have caught this card: the fixture subject fails on the BARE
SPECIFIER, which an unbuilt workspace package can never satisfy.
- ⛔ The other four cases deliberately stay on the REAL subject. That is
what
keeps the default pinned behaviourally: each is decided by something the
ambient workspace cannot supply — an app-local `node_modules` copy,
which wins
over `NODE_PATH`, or the UNDECLARED arm, whose fallback is this module's
own
ESM `import()`, and Node's ESM resolver does not consult `NODE_PATH`.
⛔ No manifest gained `@objectstack/organizations` (ADR-0132's
entitlement
boundary;
`packages/plugins/organizations/src/no-framework-dependents.pin.test.ts`
holds it). ⛔ `packages/plugins/organizations/**` is untouched. ⛔ The
ESM/CJS
asymmetry is left exactly as measured — it is why this bites the
host-resolution
control and not the `plugin-dev` fail-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/dist` exists. 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/dist` | before the fix (`9ccc4179e`) | after the fix
(`250e87413`) |
|:--|:--|:--|
| **present** | exit **1** — `Tests 1 failed \| 6 passed (7)` | exit
**0** — `Tests 11 passed (11)` |
| **absent** | exit **0** — `Tests 7 passed (7)` | exit **0** — `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:
```
AssertionError: expected '✖ FATAL: tenancy posture \'isolated\'…' to contain 'It IS declared in /tmp/os-verify-org-…'
+ ✖ FATAL: tenancy posture 'isolated' is walled, but this deployment never declared a membership policy.
…
+ This is NOT a licensing failure and NOT a missing package: the multi-org runtime is
+ present and about to enforce the wall. Do not chase either.
```
Restore discipline, both legs: `dist` backed up with `cp -a` outside the
repo,
restored, and the restore PROVED — `files_before=6 files_after=6`,
`index.js=present`, `git status --porcelain --
packages/plugins/organizations`
empty. 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:
```
- It IS declared in /tmp/os-verify-org-host-not-installed-bLyCCs's package.json, so the declaration is not the problem — OS17911-SENTINEL-THE-CONTROL-REACHED-ITS-ASSERTION.
+ verify: multiTenant=true requires the enterprise @objectstack/organizations package (migrated from plugin-org-scoping, ADR-0105 D12). It IS declared in /tmp/os-verify-org-host-not-installed-bLyCCs's package.json, so the declaration is not the problem — repair the install there (`pnpm install`, un-prune, rebuild its dist). (Cannot find module '@fixture/host-organizations': the host app DECLARES it (dependencies: "*") but it could not be resolved.
```
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 `HEAD` afterwards; restore proved by blob hash equality
plus an
empty `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:
| subject | control verdict | what it received |
|:--|:--|:--|
| `@objectstack/organizations` (the historical name) | **RED**, exit 1 —
`3 failed \| 8 passed` | the membership-policy gate — the original
defect, reproduced |
| `@objectstack/types` (a different real workspace package) | **RED**,
exit 1 — `3 failed \| 8 passed` | `mod.OrganizationsPlugin is not a
constructor` |
In both, the PREMISE leg `is absent from every ambient store the runner
exposes`
also 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
`HEAD`
after it, with blob-hash equality and an empty `git diff HEAD` as 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 `test` task declares `dependsOn: ["^build"]` — a package's
DEPENDENCIES
are built, never the package itself. Nothing in the workspace may depend
on
`@objectstack/organizations` (ADR-0132, held by
`no-framework-dependents.pin.test.ts`),
so it is in no package's `^build` closure. Measured on this tree:
```
turbo run test --filter=@objectstack/verify --dry=json
total tasks: 34 · @objectstack/organizations tasks: 0
CONTROL — @objectstack/spec#build present: true
turbo run test test:repo --dry=json # the ENTIRE workspace
total tasks: 228 · build tasks scheduled: 68
CONTROL — is @objectstack/organizations#build anywhere: false
CONTROL — is @objectstack/organizations#test anywhere: true
```
⇒ Not merely "this shard's closure need not include it": **no `turbo run
test`
task 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/` never
exists 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
Core` runs
`turbo run build --filter=!@objectstack/docs`, whose plan is **79 tasks
and DOES
include `@objectstack/organizations#build`** — on its own runner, whose
`node_modules` no Test Core shard ever sees. The state that reproduces
this is a
developer'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-typecheck` holds at `0 file(s) / 0 error(s)`, so the
shrink-only TEST_DEBT ledger did not move.
- `pnpm --filter @objectstack/verify build` — exit **0**.
- Dependency closure built with `turbo run build
--filter='@objectstack/verify...'
--filter='@objectstack/organizations...'` — 34/34 successful.
- Gate families derived from the real change set with
`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
- The two remaining gate commands answered **exit 3 — `PREREQUISITE NOT
MET`**, the
code both scripts use for "nothing was measured", explicitly distinct
from a
finding's exit 1: `check:dual-build-cjs-loads` and
`check:type-check-debt` both
read built output for the WHOLE workspace. Their readings after a
whole-workspace build are in the verification comment on the card.
- **Scope note, declared rather than buried:** the claim comment's
declared file
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 the
seam under test, so the fixture cannot be renamed without the harness
accepting
the name. This is the same shape the precedent had: #16539 changed
`packages/qa/dogfood/test/enterprise-organizations.ts` (a non-test
helper) to
give `probeOrganizations` its optional `pkg` parameter.
- **One judgement worth a reviewer's eye:** that precedent's seam lives
in a
PRIVATE package, so its parameter published nothing. `BootOptions` is
exported
from `@objectstack/verify`'s entry point, so `organizationsPackage`
becomes
published API and the changeset is `minor` rather than the `patch` a
test-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
an
option the type never declares — was rejected as a surface that lies.
Say the
word and it can be reshaped.
- Noted, not filed: the CONTROL's docblock still carries a `1 parent ca9d9d3 commit 611795e
3 files changed
Lines changed: 226 additions & 7 deletions
File tree
- .changeset
- packages/verify/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
30 | 56 | | |
31 | 57 | | |
32 | 58 | | |
33 | 59 | | |
34 | 60 | | |
35 | 61 | | |
36 | | - | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
37 | 77 | | |
38 | 78 | | |
39 | 79 | | |
| |||
92 | 132 | | |
93 | 133 | | |
94 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
95 | 139 | | |
96 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
97 | 147 | | |
98 | 148 | | |
99 | 149 | | |
100 | | - | |
| 150 | + | |
101 | 151 | | |
102 | 152 | | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
103 | 159 | | |
104 | 160 | | |
105 | 161 | | |
| |||
108 | 164 | | |
109 | 165 | | |
110 | 166 | | |
111 | | - | |
| 167 | + | |
112 | 168 | | |
113 | 169 | | |
114 | 170 | | |
115 | 171 | | |
116 | 172 | | |
117 | 173 | | |
118 | 174 | | |
119 | | - | |
| 175 | + | |
| 176 | + | |
120 | 177 | | |
121 | 178 | | |
122 | 179 | | |
| |||
126 | 183 | | |
127 | 184 | | |
128 | 185 | | |
129 | | - | |
| 186 | + | |
130 | 187 | | |
131 | 188 | | |
132 | 189 | | |
| |||
138 | 195 | | |
139 | 196 | | |
140 | 197 | | |
141 | | - | |
| 198 | + | |
142 | 199 | | |
143 | 200 | | |
144 | 201 | | |
| |||
165 | 222 | | |
166 | 223 | | |
167 | 224 | | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
168 | 230 | | |
169 | 231 | | |
170 | 232 | | |
| |||
175 | 237 | | |
176 | 238 | | |
177 | 239 | | |
| 240 | + | |
178 | 241 | | |
179 | 242 | | |
180 | 243 | | |
| |||
278 | 341 | | |
279 | 342 | | |
280 | 343 | | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
281 | 352 | | |
282 | 353 | | |
283 | 354 | | |
| |||
290 | 361 | | |
291 | 362 | | |
292 | 363 | | |
| 364 | + | |
293 | 365 | | |
294 | 366 | | |
295 | 367 | | |
| |||
342 | 414 | | |
343 | 415 | | |
344 | 416 | | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
88 | 100 | | |
89 | 101 | | |
90 | 102 | | |
| |||
347 | 359 | | |
348 | 360 | | |
349 | 361 | | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
350 | 392 | | |
351 | 393 | | |
352 | 394 | | |
| |||
548 | 590 | | |
549 | 591 | | |
550 | 592 | | |
551 | | - | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
552 | 598 | | |
553 | 599 | | |
554 | 600 | | |
| |||
0 commit comments