From 9b72d1f652f18935b0443c4a1c460ef49f60f0ad Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 14:50:33 +0000 Subject: [PATCH 1/2] test(dogfood): make the enterprise-organizations control hermetic (#16539) WIP: fixture-only subject + premise legs. Verification pending. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8 --- .../test/enterprise-organizations.test.ts | 127 +++++++++++++++--- .../dogfood/test/enterprise-organizations.ts | 81 ++++++++--- 2 files changed, 171 insertions(+), 37 deletions(-) diff --git a/packages/qa/dogfood/test/enterprise-organizations.test.ts b/packages/qa/dogfood/test/enterprise-organizations.test.ts index 5fcdf4d6a9..3d4c1ef953 100644 --- a/packages/qa/dogfood/test/enterprise-organizations.test.ts +++ b/packages/qa/dogfood/test/enterprise-organizations.test.ts @@ -12,14 +12,49 @@ * So these cases build real host roots on disk (real `node_modules`, a real * stand-in package, nothing mocked) and pin all three verdicts: available, * unavailable, and declared-but-missing. + * + * ── #16539 — why the subject below is a `@fixture/*` name ──────────────────── + * + * Every verdict here is a statement about what a host root HAS and, just as + * load-bearing, what it has NOT got. Until #16215 the second half came free: + * `@objectstack/organizations` was cloud-private, so a temp host that declared + * it and did not install it was unresolvable by construction. #16215 brought + * the package into this workspace; pnpm's hoisted store carries it, and every + * `pnpm exec`-launched runner (vitest's bin shim included) exports a `NODE_PATH` + * that reaches that store. From then on the CONTROL's verdict was a function of + * whether an unrelated package had been BUILT: green on CI, whose task graph + * never builds it, red on any tree that had run a full `pnpm build`. + * + * The visible half of that is a false red. The half that matters is the quiet + * one — a control whose subject is reachable is no longer controlling the thing + * its name claims, and nothing says so. So the cases below drive a name this + * workspace can never contain, and PROVE its absence instead of assuming it; + * `ORGANIZATIONS_PKG` stays pinned as the probe's default subject by its own + * case. #16723 made exactly this repair to `packages/types/src/node.test.ts` + * for the same landing, and this file reuses its `@fixture/*` scope. */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { createHostRequire } from '@objectstack/types/node'; import { probeOrganizations, MULTI_ORG_ENV, ORGANIZATIONS_PKG } from './enterprise-organizations.js'; +/** + * The package name every fixture host below is built around: modelled on the + * real enterprise plugin (its export is the class `serve` / `bootStack` + * construct), fixture-only in NAME. + * + * ⚠️ #16539 — it must stay a `@fixture/*` name. A name this workspace owns + * cannot state "this host root does not have it", because the hoisted store and + * the launcher's `NODE_PATH` answer that question instead of the fixture; and no + * workspace name is safe from becoming one (`@objectstack/organizations` was + * cloud-private when these cases were written). Only a name the workspace can + * never contain is, and the PREMISE cases below prove this one still is not. + */ +const HOST_ONLY = '@fixture/enterprise-organizations'; + let hostWithPkg: string; let hostWithoutPkg: string; /** @@ -39,6 +74,9 @@ let hostDeclaredNoLoadableEntry: string; /** * The `declared-unresolvable` CONTROL: declared and NOT installed. #14270 left * this arm alone, so its wording must come out byte-identical. + * + * ⚠️ #16539 — "NOT installed" is a property of THIS directory, and only a + * subject the workspace can never supply keeps it one. See {@link HOST_ONLY}. */ let hostDeclaredNotInstalled: string; @@ -55,12 +93,12 @@ function writeHost( name: 'dogfood-host-fixture', private: true, type: 'module', - ...(declare ? { dependencies: { [ORGANIZATIONS_PKG]: '*' } } : {}), + ...(declare ? { dependencies: { [HOST_ONLY]: '*' } } : {}), }), 'utf8', ); if (withPkg) { - const pkgDir = join(dir, 'node_modules', ...ORGANIZATIONS_PKG.split('/')); + const pkgDir = join(dir, 'node_modules', ...HOST_ONLY.split('/')); mkdirSync(pkgDir, { recursive: true }); if (opts.typesOnly) { // No `require` condition (the CJS resolver throws) and no `import` @@ -69,7 +107,7 @@ function writeHost( writeFileSync( join(pkgDir, 'package.json'), JSON.stringify({ - name: ORGANIZATIONS_PKG, + name: HOST_ONLY, version: '0.0.0-fixture', type: 'module', exports: { '.': { types: './index.d.ts' } }, @@ -86,7 +124,7 @@ function writeHost( writeFileSync( join(pkgDir, 'package.json'), JSON.stringify({ - name: ORGANIZATIONS_PKG, + name: HOST_ONLY, version: '0.0.0-fixture', type: 'module', main: 'index.js', @@ -127,13 +165,13 @@ describe('enterprise multi-org probe (#4700)', () => { // The verdict the old probe could never reach, no matter what any app or CI // had installed. This is what makes `describe.skipIf(!organizationsAvailable)` // a real gate rather than an unconditional skip. - const probe = await probeOrganizations(hostWithPkg, false); + const probe = await probeOrganizations(hostWithPkg, false, HOST_ONLY); expect(probe.available).toBe(true); expect(probe.reason).toBeUndefined(); }); it('reports UNAVAILABLE, with an actionable reason, when the app lacks it', async () => { - const probe = await probeOrganizations(hostWithoutPkg, false); + const probe = await probeOrganizations(hostWithoutPkg, false, HOST_ONLY); expect(probe.available).toBe(false); // The reason has to name the switch, or the skip stays folklore. expect(probe.reason).toContain(MULTI_ORG_ENV); @@ -145,13 +183,13 @@ describe('enterprise multi-org probe (#4700)', () => { // failure a CI operator cannot miss (Prime Directive #10 / "absence must be // loud"). Without this, a cloud run that lost the package would look exactly // like a cloud run that has it. - await expect(probeOrganizations(hostWithoutPkg, true)).rejects.toThrow( + await expect(probeOrganizations(hostWithoutPkg, true, HOST_ONLY)).rejects.toThrow( new RegExp(`${MULTI_ORG_ENV}=1 declares`), ); }); it('does not throw when the run declares the package AND it is there', async () => { - await expect(probeOrganizations(hostWithPkg, true)).resolves.toEqual({ available: true }); + await expect(probeOrganizations(hostWithPkg, true, HOST_ONLY)).resolves.toEqual({ available: true }); }); it('reports UNAVAILABLE when the package is merely PRESENT but not declared (#4719)', async () => { @@ -161,7 +199,7 @@ describe('enterprise multi-org probe (#4700)', () => { // asked for the enterprise runtime. Worse, in a real pnpm workspace the // "present" half arrives via the bin shim's NODE_PATH, so the verdict moved // with the launcher. - const probe = await probeOrganizations(hostInstalledButUndeclared, false); + const probe = await probeOrganizations(hostInstalledButUndeclared, false, HOST_ONLY); expect(probe.available).toBe(false); expect(probe.reason).toContain("package.json"); }); @@ -169,18 +207,18 @@ describe('enterprise multi-org probe (#4700)', () => { it('THROWS with a DECLARE-it remedy when the run declares it but the app does not (#4719)', async () => { // The remedy has to be the one that works. "Install it" is unfollowable // advice here — it is already installed; the missing act is declaring it. - await expect(probeOrganizations(hostInstalledButUndeclared, true)).rejects.toThrow( - new RegExp(`declare ${ORGANIZATIONS_PKG.replace('/', '\\/')} in .* package\\.json`), + await expect(probeOrganizations(hostInstalledButUndeclared, true, HOST_ONLY)).rejects.toThrow( + new RegExp(`declare ${HOST_ONLY.replace('/', '\\/')} in .* package\\.json`), ); }); it('CONTROL — the `declared-unresolvable` remedy is unchanged: declared, not installed', async () => { // The arm #14270 did NOT touch. Pinned here so the three-way rewrite is a // measurement: this text has to be byte-identical either side of it. - const probe = await probeOrganizations(hostDeclaredNotInstalled, false); + const probe = await probeOrganizations(hostDeclaredNotInstalled, false, HOST_ONLY); expect(probe.available).toBe(false); expect(probe.reason).toContain( - `${hostDeclaredNotInstalled} DECLARES ${ORGANIZATIONS_PKG}, so repair its INSTALL there ` + `${hostDeclaredNotInstalled} DECLARES ${HOST_ONLY}, so repair its INSTALL there ` + '(`pnpm install`, un-prune, rebuild its dist)', ); }); @@ -190,7 +228,7 @@ describe('enterprise multi-org probe (#4700)', () => { // two, so `declared-no-loadable-entry` fell into the else leg and told an // operator whose app DECLARES the package and HAS it installed to declare // it and install it. No install action can change what a package publishes. - const probe = await probeOrganizations(hostDeclaredNoLoadableEntry, false); + const probe = await probeOrganizations(hostDeclaredNoLoadableEntry, false, HOST_ONLY); expect(probe.available).toBe(false); // Which arm fired: the deferral names the two things that are NOT the // problem and hands the remedy to the importer's message, which this @@ -198,7 +236,7 @@ describe('enterprise multi-org probe (#4700)', () => { expect(probe.reason).toContain('and it IS installed, so neither is the problem'); expect(probe.reason).toContain('publishes no entry Node can load'); // ⛔ Neither of the other two arms — both are unfollowable for this kind. - expect(probe.reason).not.toContain(`declare ${ORGANIZATIONS_PKG} in`); + expect(probe.reason).not.toContain(`declare ${HOST_ONLY} in`); expect(probe.reason).not.toContain('repair its INSTALL'); // The message deferred TO has to actually arrive. expect(probe.reason).toContain('publishes no entry that Node can load'); @@ -207,12 +245,67 @@ describe('enterprise multi-org probe (#4700)', () => { it('THROWS with that same deferral when the run declares the package (#14270)', async () => { // The loud half: MULTI_ORG=1 says the package is there, and it IS — it just // cannot be loaded. The refusal must still name the right remedy. - const err = await probeOrganizations(hostDeclaredNoLoadableEntry, true).then( + const err = await probeOrganizations(hostDeclaredNoLoadableEntry, true, HOST_ONLY).then( () => new Error('probeOrganizations resolved; MULTI_ORG=1 must make this a failure'), (e: unknown) => e as Error, ); expect(err.message).toContain(MULTI_ORG_ENV); expect(err.message).toContain('and it IS installed, so neither is the problem'); - expect(err.message).not.toContain(`declare ${ORGANIZATIONS_PKG} in`); + expect(err.message).not.toContain(`declare ${HOST_ONLY} in`); + }); +}); + +/** + * #16539 — the premises the cases above rest on, asserted instead of assumed. + * + * Both legs are load-bearing and they fail in opposite directions. Without leg 1 + * a name that exists nowhere at all satisfies leg 2 and every verdict above is + * vacuous; without leg 2 the fixture stops deciding what the host root has, and + * the CONTROL silently starts measuring the ambient workspace — which is exactly + * how this file broke. + */ +describe('PREMISE — the fixture subject is host-only (#16539)', () => { + it('resolves from a host app that installs it', () => { + // Leg 1. The fixture host really can see it, so "unresolvable" elsewhere is + // a statement about the resolver's anchor and not about a typo. + const fromHost = createHostRequire(hostWithPkg).resolve(HOST_ONLY); + expect(fromHost).toContain(hostWithPkg); + }); + + it('is absent from every ambient store the runner exposes', () => { + // Leg 2, and the guard that would have caught this card. Asserted on the + // BARE SPECIFIER, ⛔ never on `/Cannot find module/` alone: a package the + // runner CAN see whose entry file merely is not on disk throws + // MODULE_NOT_FOUND too, naming `//dist/index.js` instead of the + // specifier. That second throw is what kept the CONTROL above green while + // the property went unguarded — `@objectstack/organizations` was reachable + // through the pnpm bin shim's NODE_PATH and simply unbuilt on CI's graph. + // Read as a bare-specifier failure, the premise can no longer be satisfied + // by an unbuilt workspace package, in either build state. + expect(() => createHostRequire(hostDeclaredNotInstalled).resolve(HOST_ONLY)).toThrow( + new RegExp(`Cannot find module '${HOST_ONLY}'`), + ); + }); + + it('and the probe still binds the ENTERPRISE package as its default subject', () => { + // What the fixture name must NOT quietly become: the subject. Production + // callers pass no specifier, and the one they get is the real package. + expect(ORGANIZATIONS_PKG).toBe('@objectstack/organizations'); + expect(HOST_ONLY).not.toBe(ORGANIZATIONS_PKG); + }); + + it("and this package's own resolution still cannot see it", async () => { + // The sentence that used to sit in `enterprise-organizations.ts` as prose — + // "resolvable from nowhere in the framework workspace" — died with #16215 + // and nothing noticed. It is an assertion now, on the default subject, over + // the arm that actually uses this module's ESM base. + // + // ⚠️ A red here is NOT a fixture problem: it means `@objectstack/dogfood` + // can now resolve the enterprise package, so `organizationsAvailable` is + // true in the framework repo and the multi-org gates have started running + // here. Read the verdict, then decide — do not re-point this case. + const probe = await probeOrganizations(hostWithoutPkg, false); + expect(probe.available).toBe(false); + expect(probe.reason).toContain(`declare ${ORGANIZATIONS_PKG} in`); }); }); diff --git a/packages/qa/dogfood/test/enterprise-organizations.ts b/packages/qa/dogfood/test/enterprise-organizations.ts index 4ff1214e01..15733f3742 100644 --- a/packages/qa/dogfood/test/enterprise-organizations.ts +++ b/packages/qa/dogfood/test/enterprise-organizations.ts @@ -13,8 +13,8 @@ * describe.skipIf(!organizationsAvailable)(...) * * Node ESM resolves a bare specifier against the IMPORTER's own realpath — - * `packages/qa/dogfood`, inside the framework workspace — while the package is - * cloud-private and lives in the host app's `node_modules`. The probe was + * `packages/qa/dogfood`, inside the framework workspace — while the package was + * cloud-private and lived in the host app's `node_modules`. The probe was * therefore **constant-false**: not "false because the package is absent", but * false by construction, in every environment, including the enterprise/cloud CI * whose comment claimed it "runs this". Two whole multi-org gates — the #1994 @@ -38,13 +38,30 @@ * ("Absence must be loud"). Undeclared-and-missing still skips, but the * warning names the switch, so the skip is discoverable rather than folklore. * - * The honest state after this change, stated plainly: in the FRAMEWORK repo the - * package genuinely is not installed, so these gates still skip here — that is - * correct and unavoidable for a cloud-private package. What changed is that the - * skip is now a fact about the environment rather than an artefact of the - * resolver, and a cloud/enterprise run that ships the package will actually - * execute the blocks (and will fail loudly if it thinks it ships the package but - * does not). + * The honest state after this change, stated plainly: these gates still skip in + * the FRAMEWORK repo. What changed is that the skip is now a fact about the + * environment rather than an artefact of the resolver, and a cloud/enterprise run + * that ships the package will actually execute the blocks (and will fail loudly + * if it thinks it ships the package but does not). + * + * ── #16539 — the reason for that skip changed, and prose was the wrong place ── + * + * The paragraph above used to end "…that is correct and unavoidable for a + * cloud-private package", and the probe below used to say the enterprise package + * was "resolvable from nowhere in the framework workspace". #16215 brought the + * multi-organization runtime back to open core: `packages/plugins/organizations` + * IS a member of this workspace now, pnpm's hoisted store carries it, and every + * `pnpm exec`-launched runner exports a `NODE_PATH` that reaches it. Neither + * sentence survived that landing, and nothing said so — which is the whole + * finding, because a nearby CONTROL had quietly stopped controlling the thing + * its name claims. + * + * The skip does survive, for a DIFFERENT reason: `@objectstack/dogfood` does not + * DECLARE the package, and the undeclared arm resolves through this module's own + * ESM base, which never consults `NODE_PATH`. ⛔ That reason is not written down + * here as a second unpinned sentence — it is asserted, as a PREMISE case in + * `enterprise-organizations.test.ts`, so the day it stops being true a test says + * so instead of a comment lying quietly. * * ── #4719 ──────────────────────────────────────────────────────────────────── * @@ -68,7 +85,14 @@ import { createHostImporter, hostImportFailureKind } from '@objectstack/types/node'; -/** The cloud-private enterprise package (ADR-0105 D12). */ +/** + * The enterprise multi-org package (ADR-0105 D12) these gates need. + * + * ⚠️ #16539: no longer cloud-private — #16215 brought it back to open core as + * `packages/plugins/organizations`. It stays the SUBJECT of this probe (the app + * under test is what has to declare and install it), but it is no longer a name + * this repo can use as an example of something a host root does not have. + */ export const ORGANIZATIONS_PKG = '@objectstack/organizations'; /** @@ -91,24 +115,41 @@ export interface OrganizationsProbe { * dogfood suite runs and where a linked enterprise package would be declared). * @param declared Whether the run asserts the package is present; defaults to * reading {@link MULTI_ORG_ENV}. When true, absence THROWS instead of skipping. + * @param pkg The specifier to probe. Defaults to the real subject, + * {@link ORGANIZATIONS_PKG}; ⛔ production callers never pass it. #16539: every + * verdict this function reaches is a statement about what a host root has AND + * has not got, and a name this workspace owns can no longer make the second + * half of that statement — `@objectstack/organizations` became a workspace + * member in #16215, so a fixture host that declares it and does not install it + * is resolved out of the hoisted store instead, whenever that package happens to + * have been built. This file's own tests therefore drive a `@fixture/*` name and + * prove its absence rather than assuming it, the same repair #16723 made to + * `packages/types/src/node.test.ts` for the same landing. */ export async function probeOrganizations( hostRoot?: string, declared: boolean = process.env[MULTI_ORG_ENV] === '1', + pkg: string = ORGANIZATIONS_PKG, ): Promise { const root = hostRoot ?? process.cwd(); // #10943: hand the helper THIS module's resolver. Its undeclared fallback is // documented as "the importing package's own resolution", and a bare // `import()` written inside `@objectstack/types` is that package's - // resolution, not this one's — it can see only `@objectstack/spec`. Measured - // to change nothing for `@objectstack/organizations` itself (cloud-private, - // resolvable from nowhere in the framework workspace); it makes the - // documented sentence true for this probe. + // resolution, not this one's — it can see only `@objectstack/spec`. It makes + // the documented sentence true for this probe. + // + // #16539: the clause that used to close this comment — "measured to change + // nothing for `@objectstack/organizations` itself (cloud-private, resolvable + // from nowhere in the framework workspace)" — died with #16215. It is now the + // test's PREMISE case rather than a comment. Note it was never the mechanism + // behind the CONTROL flip either: a host that DECLARES the package never + // reaches this fallback at all, and the escape route was `createHostRequire`'s + // CJS lookup finding the workspace copy through the pnpm bin shim's NODE_PATH. const importFromHost = createHostImporter(root, { fallbackImport: (specifier) => import(/* webpackIgnore: true */ specifier), }); try { - await importFromHost(ORGANIZATIONS_PKG); + await importFromHost(pkg); return { available: true }; } catch (e) { const detail = (e as Error).message; @@ -123,17 +164,17 @@ export async function probeOrganizations( const kind = hostImportFailureKind(e); const remedy = kind === 'declared-unresolvable' - ? `${root} DECLARES ${ORGANIZATIONS_PKG}, so repair its INSTALL there (\`pnpm install\`, ` + + ? `${root} DECLARES ${pkg}, so repair its INSTALL there (\`pnpm install\`, ` + 'un-prune, rebuild its dist)' : kind === 'declared-no-loadable-entry' - ? `${root} DECLARES ${ORGANIZATIONS_PKG} and it IS installed, so neither is the ` + + ? `${root} DECLARES ${pkg} and it IS installed, so neither is the ` + 'problem — the package publishes no entry Node can load, and the importer\'s ' + 'message below is the authority on what it has to publish' - : `declare ${ORGANIZATIONS_PKG} in ${root}'s own package.json and install it — being ` + + : `declare ${pkg} in ${root}'s own package.json and install it — being ` + 'reachable as somebody else\'s transitive dependency is not enough (#4719)'; if (declared) { throw new Error( - `${MULTI_ORG_ENV}=1 declares that ${ORGANIZATIONS_PKG} (enterprise, ADR-0105 D12) is ` + + `${MULTI_ORG_ENV}=1 declares that ${pkg} (enterprise, ADR-0105 D12) is ` + `installed for this run, but it could not be resolved from ${root}. ` + 'Refusing to skip the multi-org dogfood gates silently: a run that believes it is ' + 'exercising cross-tenant isolation and is not would report green over gates that ' + @@ -144,7 +185,7 @@ export async function probeOrganizations( return { available: false, reason: - `${ORGANIZATIONS_PKG} (enterprise) is not resolvable from ${root} — ` + + `${pkg} (enterprise) is not resolvable from ${root} — ` + `skipping the multi-org gate. To enable it, ${remedy}. Set ${MULTI_ORG_ENV}=1 in a run ` + 'that ships the package to turn this skip into a failure. ' + `(${detail})`, From a988f564423ee37a9a65a3a01bf9c4c7339be035 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 15:42:40 +0000 Subject: [PATCH 2/2] test(dogfood): cite ADR-0132 beside #16215 in the rewritten premises The landing that killed the old premise has a decision record; name it where the premise is restated, per Prime Directive #13. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8 --- packages/qa/dogfood/test/enterprise-organizations.test.ts | 4 ++-- packages/qa/dogfood/test/enterprise-organizations.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/qa/dogfood/test/enterprise-organizations.test.ts b/packages/qa/dogfood/test/enterprise-organizations.test.ts index 3d4c1ef953..8b19788fdb 100644 --- a/packages/qa/dogfood/test/enterprise-organizations.test.ts +++ b/packages/qa/dogfood/test/enterprise-organizations.test.ts @@ -18,8 +18,8 @@ * Every verdict here is a statement about what a host root HAS and, just as * load-bearing, what it has NOT got. Until #16215 the second half came free: * `@objectstack/organizations` was cloud-private, so a temp host that declared - * it and did not install it was unresolvable by construction. #16215 brought - * the package into this workspace; pnpm's hoisted store carries it, and every + * it and did not install it was unresolvable by construction. ADR-0132 (#16215) + * brought the package into this workspace; pnpm's hoisted store carries it, and every * `pnpm exec`-launched runner (vitest's bin shim included) exports a `NODE_PATH` * that reaches that store. From then on the CONTROL's verdict was a function of * whether an unrelated package had been BUILT: green on CI, whose task graph diff --git a/packages/qa/dogfood/test/enterprise-organizations.ts b/packages/qa/dogfood/test/enterprise-organizations.ts index 15733f3742..7d7a7a5d0b 100644 --- a/packages/qa/dogfood/test/enterprise-organizations.ts +++ b/packages/qa/dogfood/test/enterprise-organizations.ts @@ -49,7 +49,8 @@ * The paragraph above used to end "…that is correct and unavoidable for a * cloud-private package", and the probe below used to say the enterprise package * was "resolvable from nowhere in the framework workspace". #16215 brought the - * multi-organization runtime back to open core: `packages/plugins/organizations` + * multi-organization runtime back to open core (ADR-0132): + * `packages/plugins/organizations` * IS a member of this workspace now, pnpm's hoisted store carries it, and every * `pnpm exec`-launched runner exports a `NODE_PATH` that reaches it. Neither * sentence survived that landing, and nothing said so — which is the whole @@ -88,7 +89,7 @@ import { createHostImporter, hostImportFailureKind } from '@objectstack/types/no /** * The enterprise multi-org package (ADR-0105 D12) these gates need. * - * ⚠️ #16539: no longer cloud-private — #16215 brought it back to open core as + * ⚠️ #16539: no longer cloud-private — ADR-0132 / #16215 brought it back to open core as * `packages/plugins/organizations`. It stays the SUBJECT of this probe (the app * under test is what has to declare and install it), but it is no longer a name * this repo can use as an example of something a host root does not have.