Skip to content

Commit 611795e

Browse files
os-salesclaude
andauthored
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 `⚠️` note about `./harness` being imported without its `.js` extension and the TS7006 that would 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](https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ca9d9d3 commit 611795e

3 files changed

Lines changed: 226 additions & 7 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/verify': minor
3+
---
4+
5+
verify: let `bootStack` be told which package `multiTenant: true` resolves, so the
6+
`declared-unresolvable` control can name a subject the workspace can never supply
7+
8+
`BootOptions` gains an optional `organizationsPackage`. It defaults to
9+
`@objectstack/organizations` and production callers never pass it — the
10+
operator-facing error still names that package literally, because in every
11+
production boot it is the subject. Only the specifier moves.
12+
13+
Why it exists: a fixture whose whole content is "this host root DECLARED the
14+
package and does not have it" cannot state the second half with a name the
15+
workspace owns. Since ADR-0132 the multi-org runtime is a tracked workspace
16+
package, pnpm's hoisted store carries it, and a `pnpm exec`-launched runner
17+
exports a `NODE_PATH` that reaches that store — so such a fixture resolved the
18+
package out of the ambient workspace the moment it had been built, and its
19+
verdict became a function of an unrelated package's build state rather than of
20+
its own directory. The harness's own host-resolution control now hands in a
21+
`@fixture/*` name and proves the absence instead of assuming it, the repair
22+
already landed for `packages/qa/dogfood` and `packages/types`.

‎packages/verify/src/harness.host-resolution.test.ts‎

Lines changed: 157 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,53 @@
2727
* that is the whole point), registering the same `org-scoping` service and
2828
* posture entitlement the real one does. What is under test here is
2929
* RESOLUTION, not the enterprise semantics.
30+
*
31+
* ── #17911 — why ONE case below drives a `@fixture/*` subject ────────────────
32+
*
33+
* The CONTROL's whole content is "this host root DECLARED it and does not have
34+
* it". Until ADR-0132 / #16215 that came free: `@objectstack/organizations` was
35+
* cloud-private, so a temp host that declared it and did not install it was
36+
* unresolvable by construction. It is a tracked workspace package now, pnpm's
37+
* hoisted store carries it, and vitest's own `pnpm exec` bin shim exports a
38+
* `NODE_PATH` that reaches that store — so the package RESOLVED, the wall came
39+
* up, and boot was refused several steps later by the membership-policy gate
40+
* whose message even says "This is NOT … a missing package". The CONTROL never
41+
* reached the wording it exists to pin: red on any tree with a full local
42+
* build, green on CI, and proving nothing in either state.
43+
*
44+
* So that one case hands in a name this workspace can never contain and the
45+
* PREMISE block below PROVES the absence instead of assuming it — the repair
46+
* #16539 (dogfood) and #16552 (`packages/types/src/node.test.ts`) landed, for
47+
* the reason those cards state verbatim: no workspace name is safe from
48+
* becoming one.
49+
*
50+
* ⛔ The other four cases deliberately stay on the REAL subject, and that is
51+
* what keeps `ORGANIZATIONS_PKG` pinned as `bootStack`'s default: each of them
52+
* is decided by something the ambient workspace cannot supply — an app-local
53+
* `node_modules` copy, which wins over `NODE_PATH`, or the UNDECLARED arm,
54+
* whose fallback is this module's own ESM `import()` and Node's ESM resolver
55+
* does not consult `NODE_PATH` at all. Measured in both build states (#17911).
3056
*/
3157

3258
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
3359
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3460
import { tmpdir } from 'node:os';
3561
import { join } from 'node:path';
36-
import { bootStack } from './harness.js';
62+
import { createHostRequire } from '@objectstack/types/node';
63+
import { bootStack, ORGANIZATIONS_PKG } from './harness.js';
64+
65+
/**
66+
* The subject the `declared-unresolvable` CONTROL is built around: modelled on
67+
* the real enterprise package, fixture-only in NAME.
68+
*
69+
* ⚠️ #17911 — it must stay a `@fixture/*` name. A name this workspace owns
70+
* cannot state "this host root does not have it", because the hoisted store and
71+
* the launcher's `NODE_PATH` answer that question instead of the fixture; and no
72+
* workspace name is safe from becoming one (`@objectstack/organizations` was
73+
* cloud-private when this case was written). Only a name the workspace can never
74+
* contain is, and the PREMISE cases at the bottom prove this one still is not.
75+
*/
76+
const FIXTURE_ORGANIZATIONS = '@fixture/host-organizations';
3777

3878
/**
3979
* Stand-in for `@objectstack/organizations`. Mirrors the real plugin's
@@ -92,14 +132,30 @@ let appDeclaredNoLoadableEntry: string;
92132
* untouched by #14270, so this case must render byte-identically before and
93133
* after — that is what makes the third arm's flip a measurement rather than a
94134
* rewrite that moved everything.
135+
*
136+
* ⚠️ #17911 — "NOT installed" is a property of THIS directory, and only a
137+
* subject the workspace can never supply keeps it one. See
138+
* {@link FIXTURE_ORGANIZATIONS}.
95139
*/
96140
let appDeclaredNotInstalled: string;
141+
/**
142+
* #17911 PREMISE leg 1 only — a host that really does install the fixture
143+
* subject. Without it, a subject that exists NOWHERE (a typo, a deleted scope)
144+
* would satisfy the absence leg and the CONTROL would be vacuous.
145+
*/
146+
let appFixtureInstalled: string;
97147

98148
function writeApp(
99149
prefix: string,
100-
opts: { withOrganizations: boolean; declare?: boolean; typesOnly?: boolean },
150+
opts: { withOrganizations: boolean; declare?: boolean; typesOnly?: boolean; pkg?: string },
101151
): string {
102152
const declare = opts.declare ?? opts.withOrganizations;
153+
// #17911 — the subject this host declares and/or installs. Defaults to the
154+
// real package, which is what four of the five cases need; the CONTROL hands
155+
// in `FIXTURE_ORGANIZATIONS` because its verdict is an ABSENCE and only a name
156+
// the workspace can never supply keeps that absence a property of this
157+
// directory.
158+
const pkg = opts.pkg ?? ORGANIZATIONS_PKG;
103159
const dir = mkdtempSync(join(tmpdir(), prefix));
104160
writeFileSync(
105161
join(dir, 'package.json'),
@@ -108,15 +164,16 @@ function writeApp(
108164
name: 'hostres-fixture',
109165
private: true,
110166
type: 'module',
111-
...(declare ? { dependencies: { '@objectstack/organizations': '*' } } : {}),
167+
...(declare ? { dependencies: { [pkg]: '*' } } : {}),
112168
},
113169
null,
114170
2,
115171
),
116172
'utf8',
117173
);
118174
if (opts.withOrganizations) {
119-
const pkgDir = join(dir, 'node_modules', '@objectstack', 'organizations');
175+
// The specifier is a scoped name, so it lands on disk as `<scope>/<name>`.
176+
const pkgDir = join(dir, 'node_modules', ...pkg.split('/'));
120177
mkdirSync(pkgDir, { recursive: true });
121178
if (opts.typesOnly) {
122179
// A publish whose `exports` names a `types` target and nothing else: no
@@ -126,7 +183,7 @@ function writeApp(
126183
writeFileSync(
127184
join(pkgDir, 'package.json'),
128185
JSON.stringify({
129-
name: '@objectstack/organizations',
186+
name: pkg,
130187
version: '0.0.0-fixture',
131188
type: 'module',
132189
exports: { '.': { types: './index.d.ts' } },
@@ -138,7 +195,7 @@ function writeApp(
138195
writeFileSync(
139196
join(pkgDir, 'package.json'),
140197
JSON.stringify({
141-
name: '@objectstack/organizations',
198+
name: pkg,
142199
version: '0.0.0-fixture',
143200
type: 'module',
144201
main: 'index.js',
@@ -165,6 +222,11 @@ beforeAll(() => {
165222
appDeclaredNotInstalled = writeApp('os-verify-org-host-not-installed-', {
166223
withOrganizations: false,
167224
declare: true,
225+
pkg: FIXTURE_ORGANIZATIONS,
226+
});
227+
appFixtureInstalled = writeApp('os-verify-org-host-fixture-ok-', {
228+
withOrganizations: true,
229+
pkg: FIXTURE_ORGANIZATIONS,
168230
});
169231
});
170232

@@ -175,6 +237,7 @@ afterAll(() => {
175237
appInstalledButUndeclared,
176238
appDeclaredNoLoadableEntry,
177239
appDeclaredNotInstalled,
240+
appFixtureInstalled,
178241
]) {
179242
if (dir) rmSync(dir, { recursive: true, force: true });
180243
}
@@ -278,6 +341,14 @@ describe('bootStack multiTenant — host-app package resolution (#4700)', () =>
278341
// Not a new behaviour, a CONTROL. #14270 rewrote this branch's SHAPE
279342
// (two-way → three-way); this arm's text has to come out byte-identical,
280343
// or the third kind's fix moved something that was already right.
344+
// ⚠️ #17911 — `organizationsPackage` is why this case can still SAY
345+
// "not installed". On the real subject the host's declaration was
346+
// honoured, the hoisted store answered the resolve, the wall came up and
347+
// boot died several steps later at the membership-policy gate — the
348+
// assertion below was never reached, in either direction. The subject is
349+
// a `@fixture/*` name the workspace can never contain; the PREMISE block
350+
// proves it is absent, and `bootStack`'s DEFAULT subject stays pinned by
351+
// the four cases above, which pass no option at all.
281352
// ⚠️ try/catch rather than `.then(onFulfilled, onRejected)`: `./harness`
282353
// is imported without its `.js` extension, so under NodeNext the
283354
// specifier does not resolve and every symbol it names is `any` — which
@@ -290,6 +361,7 @@ describe('bootStack multiTenant — host-app package resolution (#4700)', () =>
290361
const stack = await bootStack(app as never, {
291362
multiTenant: true,
292363
hostRoot: appDeclaredNotInstalled,
364+
organizationsPackage: FIXTURE_ORGANIZATIONS,
293365
});
294366
await stack.stop();
295367
} catch (e) {
@@ -342,3 +414,82 @@ describe('bootStack multiTenant — host-app package resolution (#4700)', () =>
342414
BOOT_TIMEOUT,
343415
);
344416
});
417+
418+
/**
419+
* #17911 — the premises the CONTROL rests on, asserted instead of assumed.
420+
*
421+
* Both resolution legs are load-bearing and they fail in opposite directions.
422+
* Without leg 1 a subject that exists nowhere at all satisfies leg 2 and the
423+
* CONTROL is vacuous; without leg 2 the fixture stops deciding what the host
424+
* root has, and the CONTROL silently starts measuring the ambient workspace —
425+
* which is exactly how this case broke.
426+
*/
427+
describe('PREMISE — the CONTROL subject is host-only (#17911)', () => {
428+
it('resolves from a host app that installs it', () => {
429+
// Leg 1. A host that really installs the fixture can see it, so
430+
// "unresolvable" elsewhere is a statement about the resolver's anchor and
431+
// not about a typo or a scope nobody publishes.
432+
const fromHost = createHostRequire(appFixtureInstalled).resolve(FIXTURE_ORGANIZATIONS);
433+
expect(fromHost).toContain(appFixtureInstalled);
434+
});
435+
436+
it('is absent from every ambient store the runner exposes', () => {
437+
// Leg 2, and the guard that would have caught this card. Asserted on the
438+
// BARE SPECIFIER, ⛔ never on `/Cannot find module/` alone: a package the
439+
// runner CAN see whose entry file merely is not on disk throws
440+
// MODULE_NOT_FOUND too, naming `<store>/<pkg>/dist/index.js` instead of the
441+
// specifier. That second throw is precisely what kept this CONTROL green
442+
// while the property went unguarded — `@objectstack/organizations` was
443+
// reachable through the pnpm bin shim's NODE_PATH and simply unbuilt on
444+
// CI's task graph. Read as a bare-specifier failure, the premise can no
445+
// longer be satisfied by an unbuilt workspace package, in either build
446+
// state.
447+
expect(() => createHostRequire(appDeclaredNotInstalled).resolve(FIXTURE_ORGANIZATIONS)).toThrow(
448+
new RegExp(`Cannot find module '${FIXTURE_ORGANIZATIONS}'`),
449+
);
450+
});
451+
452+
it('and the REAL package is reachable from that same anchor — the asymmetry is the point', () => {
453+
// The other half of leg 2, and the one that makes this file's history
454+
// legible: from the very same host root, the real subject DOES resolve,
455+
// through the hoisted store the launcher's NODE_PATH exposes. That is not a
456+
// defect to fix here (ADR-0132 put the package in this workspace on
457+
// purpose); it is the reason the CONTROL may not be built on that name.
458+
//
459+
// ⚠️ Build-state dependent BY CONSTRUCTION, so it asserts reachability of
460+
// the package DIRECTORY rather than a loadable entry: `require.resolve`
461+
// throws MODULE_NOT_FOUND for an unbuilt package too, and pinning "it
462+
// resolves" would make this case itself a verdict about whether a sibling
463+
// package had been built — the defect it documents.
464+
let resolved: string | undefined;
465+
let resolveError: string | undefined;
466+
try {
467+
resolved = createHostRequire(appDeclaredNotInstalled).resolve(ORGANIZATIONS_PKG);
468+
} catch (e) {
469+
resolveError = (e as Error).message;
470+
}
471+
// Two legal shapes, and each asserts its own discriminating fact — ⛔ not
472+
// one assertion that a `undefined` would satisfy vacuously.
473+
if (resolved !== undefined) {
474+
// Built tree: it resolved, and NOT out of this host root — which is the
475+
// whole mechanism, stated as an assertion.
476+
expect(resolved).not.toContain(appDeclaredNotInstalled);
477+
} else {
478+
// Unbuilt tree: the package DIRECTORY is still reachable, so the throw
479+
// names a path inside the store. What must never happen is the
480+
// bare-specifier failure leg 2 asserts for the fixture — that is the one
481+
// reading that would make the two subjects interchangeable.
482+
expect(resolveError).not.toMatch(new RegExp(`Cannot find module '${ORGANIZATIONS_PKG}'`));
483+
}
484+
});
485+
486+
it('and bootStack still binds the ENTERPRISE package as its default subject', () => {
487+
// What the fixture subject must NOT quietly become: the default. Production
488+
// callers pass no `organizationsPackage`, and the one they get is the real
489+
// package — behaviourally pinned by the four cases above, which pass no
490+
// option and are decided by the real name.
491+
expect(ORGANIZATIONS_PKG).toBe('@objectstack/organizations');
492+
expect(FIXTURE_ORGANIZATIONS).not.toBe(ORGANIZATIONS_PKG);
493+
expect(FIXTURE_ORGANIZATIONS.startsWith('@fixture/')).toBe(true);
494+
});
495+
});

‎packages/verify/src/harness.ts‎

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ const DEFAULT_ADMIN_EMAIL = 'admin@objectos.ai';
8585
const DEFAULT_ADMIN_PASSWORD = 'admin123';
8686
const DEFAULT_AUTH_SECRET = 'objectstack-verify-secret';
8787

88+
/**
89+
* The enterprise multi-org runtime this harness mounts under `multiTenant: true`
90+
* — the one and only subject `bootStack` resolves from the host app, and the
91+
* default of {@link BootOptions.organizationsPackage}.
92+
*
93+
* Exported for the host-resolution suite's premise case, which pins this value
94+
* so a test seam can never quietly become the production subject. ⛔ Deliberately
95+
* NOT re-exported from `./index.ts`: it is not part of this package's published
96+
* API.
97+
*/
98+
export const ORGANIZATIONS_PKG = '@objectstack/organizations';
99+
88100
/**
89101
* A booted stack: the HTTP surface (`api` / `raw` / `signIn` / `signUp` /
90102
* `apiAs`) plus the in-process handle (`hooks` / `validate` / `flows` /
@@ -347,6 +359,36 @@ export interface BootOptions {
347359
* array order. Default `[]`.
348360
*/
349361
extraPlugins?: unknown[];
362+
/**
363+
* The specifier `multiTenant: true` resolves from the host app. Defaults to
364+
* the real subject, {@link ORGANIZATIONS_PKG}; ⛔ production callers never
365+
* pass it.
366+
*
367+
* ## Why it exists (#17911, the same repair #16539 / #16552 landed)
368+
*
369+
* Every verdict this boot path reaches is a statement about what a host root
370+
* HAS and, just as load-bearing, what it has NOT got. Until ADR-0132 / #16215
371+
* the second half came free: `@objectstack/organizations` was cloud-private,
372+
* so a temp host that declared it and did not install it was unresolvable by
373+
* construction. It is a tracked workspace package now; pnpm's hoisted store
374+
* carries it and every `pnpm exec`-launched runner (vitest's bin shim
375+
* included) exports a `NODE_PATH` that reaches that store. From then on a
376+
* "declared, not installed" fixture's verdict was a function of whether an
377+
* unrelated package had been BUILT — green on CI, whose test graph never
378+
* builds it, red on any tree that had run a full local build.
379+
*
380+
* The visible half of that is a false red. The half that matters is the quiet
381+
* one: a fixture whose subject is reachable is no longer deciding what the
382+
* host root has, and nothing says so. So a case that needs the absence to be
383+
* a property of ITS OWN directory hands in a name this workspace can never
384+
* contain (`@fixture/*`) and proves the absence rather than assuming it — and
385+
* no workspace name is safe from becoming one.
386+
*
387+
* ⛔ It does NOT rename the package in the operator-facing sentence: the error
388+
* this boot throws names {@link ORGANIZATIONS_PKG} literally, because in every
389+
* production boot that is the subject. Only the specifier moves.
390+
*/
391+
organizationsPackage?: string;
350392
}
351393

352394
/**
@@ -548,7 +590,11 @@ export async function bootStack(
548590
// package declares it — and it is what stops the next app-supplied
549591
// package added to this path from silently missing `packages/verify`'s own
550592
// dependencies.
551-
const organizationsPkg = '@objectstack/organizations';
593+
// #17911: the subject is a parameter with the real package as its default,
594+
// so a fixture case whose whole content is "this host root does NOT have
595+
// it" can hand in a name the workspace can never supply. Production callers
596+
// pass nothing and get `ORGANIZATIONS_PKG` — see BootOptions.organizationsPackage.
597+
const organizationsPkg = opts.organizationsPackage ?? ORGANIZATIONS_PKG;
552598
const hostRoot = opts.hostRoot ?? process.cwd();
553599
let mod: any;
554600
try {

0 commit comments

Comments
 (0)