test(gateway): KBN-101-02a RED supplement — close the verifier's twelve findings

Author phase only; still no implementation. Kept as a distinct fifth commit so
the RED baseline, the contract restoration, the span audit and this supplement
remain separately reviewable.

The finding that mattered: every frozen-policy case mocked ./main.js wholesale,
so nothing executed main.ts, AppModule or the ConfigModule provider, and the
three independent reads at main.ts:28, app.module.ts:39 and config.module.ts:12
could all have survived a fully green matrix. Fence revision 15 -> 16 (granted
solely for apps/gateway/src/main.spec.ts) is used to import the real modules
with no ./main.js mock and assert no independent loadConfig, no tier probe and
no NestFactory.create. The existing anchoring case is left red-in-place rather
than deleted: its import-time-exit premise is the opposite of the new contract,
so it must be migrated by whoever implements GREEN.

Also closes: the executed dev:local child-process route, which requires a
LOCAL_START_* code AND the absence of the BETTER_AUTH_SECRET throw, since a
nonzero exit alone is satisfied by a crash; the real database onModuleInit
lifecycle, asserting migrations never run; the full disposable state-root
contract (absent, not-a-directory, symlink, non-private, foreign-owner), with
ownership substituted at lstat because a filesystem-built case passes trivially
wherever CI runs as root; unreadable and malformed sources, which refuse rather
than being skipped, built as EISDIR so they cannot be vacuous under any uid;
per-source non-local tier detection for all seven labels; canary retention and
console cases; post-freeze source-mutation cases; and a three-form static-import
guard.

Two defects of my own are fixed rather than papered over. The non-canonical
state-root case normalized to the valid path before the policy saw it, so it
demanded a refusal on input identical to the accepted one — a red that could
never go green for its stated reason. And the first substitution of @nestjs/core
was wholesale, which dropped APP_GUARD and killed app.module.ts at import, so
two cases failed for a reason they did not state.

Classification types are unions, not strings: a declaredTier?: string is
value-free only by convention, and a two-member union cannot carry a canary.

77 failing / 15 passing (92 cases, 6 files). Typecheck, lint and Prettier clean.
Zero non-assertion failures. Seven of the fifteen greens are vacuous against the
stub, up from three; that rise is recorded in the document so it cannot later be
read as coverage. The first RED report's implication that the GREEN targets were
covered behaviorally is corrected there too.

Refs: KBN-101-02a (Stack #1178)
This commit is contained in:
shaggy committed 2026-08-12 21:05:24 -05:00
1 parent 09c75f3b10
commit 1a77739410
8 files changed
+986 -35

No files matched your search

@@ -196,3 +196,76 @@ describe('KBN-101-02a DatabaseModule — state lives under the declared root', (
expect(dataDir.startsWith(`${stateRoot}/`)).toBe(true);
});
});
/**
* The lifecycle, not just the factories.
*
* Provider factories construct handles; `onModuleInit()` is what actually runs
* DDL — `runPgliteMigrations(handle)` and `storageAdapter.migrate()`. A guard
* placed only in the factories leaves that path reachable whenever a handle is
* obtained by any other route, and every case above would stay green. These
* cases drive the lifecycle hook directly.
*/
describe('KBN-101-02a DatabaseModule — migrations are a lifecycle effect, not a factory effect', () => {
interface Handle {
db: unknown;
close: () => void;
}
async function instantiate(config: unknown): Promise<{
instance: { onModuleInit: () => Promise<void> };
adapterMigrate: ReturnType<typeof vi.fn>;
}> {
const mod = await import('./database.module.js');
const adapterMigrate = vi.fn(async () => {});
const handle: Handle = { db: {}, close: vi.fn() };
const adapter = { name: 'mock', migrate: adapterMigrate, close: vi.fn(async () => {}) };
const instance = new mod.DatabaseModule(
handle as never,
adapter as never,
config as never,
) as unknown as { onModuleInit: () => Promise<void> };
return { instance, adapterMigrate };
}
it('refuses to migrate a postgres storage shape while the local policy is established', async () => {
vi.resetModules();
await establishLocalChannel();
const { instance, adapterMigrate } = await instantiate({
tier: 'local',
storage: { type: 'postgres', url: 'postgresql://mosaic:mosaic@localhost:5433/mosaic' },
});
await expect(instance.onModuleInit()).rejects.toThrow(/LOCAL_START_/);
expect(adapterMigrate, 'storage migrate() ran on a refusing start').not.toHaveBeenCalled();
expect(runPgliteMigrationsMock).not.toHaveBeenCalled();
});
it('refuses to migrate a non-local tier while the local policy is established', async () => {
vi.resetModules();
await establishLocalChannel();
const { instance, adapterMigrate } = await instantiate({
tier: 'standalone',
storage: { type: 'pglite' },
});
await expect(instance.onModuleInit()).rejects.toThrow(/LOCAL_START_/);
expect(adapterMigrate).not.toHaveBeenCalled();
expect(runPgliteMigrationsMock).not.toHaveBeenCalled();
});
it('migrates against the frozen policy rather than the injected config', async () => {
vi.resetModules();
await establishLocalChannel();
// The injected config claims a tier the policy does not allow. A lifecycle
// hook that branches on `this.config.tier` follows the injected value; one
// that reads the frozen policy does not.
const { instance, adapterMigrate } = await instantiate({
tier: 'standalone',
storage: { type: 'pglite' },
});
await instance.onModuleInit().catch(() => undefined);
expect(adapterMigrate).not.toHaveBeenCalled();
});
});
+119 -2
View File
@@ -10,6 +10,8 @@
* zero calls to each.
*/
import { spawnSync } from 'node:child_process';
import { mkdtempSync, readFileSync } from 'node:fs';
import { readFile, mkdtemp, realpath } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
@@ -100,12 +102,36 @@ async function declareAdmissibleLocalStart(mod: LocalDevModule): Promise<string>
describe('KBN-101-02a local-dev — row 9, import order is structural', () => {
it('does not statically import main.js: the module able to start must not load first', async () => {
const source = await readFile(join(HERE, 'local-dev.ts'), 'utf8');
const staticMainImport = /^\s*import\s[^\n]*from\s+['"]\.\/main\.js['"]/m;
// Both static forms, because they have identical effect and only one of them
// has a `from` clause. A guard written against `import … from './main.js'`
// alone is satisfied by the bare side-effect form `import './main.js'`,
// which loads the module graph just as eagerly — the guard would be green
// while the property it names is false.
const staticBindingImport = /^\s*import\s[^\n]*from\s+['"]\.\/main\.js['"]/m;
const staticSideEffectImport = /^\s*import\s+['"]\.\/main\.js['"]/m;
const staticRequire = /require\(\s*['"]\.\/main\.js['"]\s*\)/;
// A static import would load main.js — and everything it pulls in, up to and
// including the DB client — before the policy exists. "Refused before
// connection" then becomes an assertion rather than a structural property.
expect(staticMainImport.test(source)).toBe(false);
expect(staticBindingImport.test(source), 'static binding import of main.js').toBe(false);
expect(staticSideEffectImport.test(source), 'static side-effect import of main.js').toBe(false);
expect(staticRequire.test(source), 'static require of main.js').toBe(false);
});
// The negative guards above are satisfied by a module that never references
// main.js at all. This is the positive half: the dynamic import must exist and
// must sit after the establish call, so removing the establish line cannot
// leave the file passing.
it('places the dynamic import of main.js after the establish call, in source order', async () => {
const source = await readFile(join(HERE, 'local-dev.ts'), 'utf8');
const dynamicImport = source.search(/await\s+import\(\s*['"]\.\/main\.js['"]\s*\)/);
const establish = source.search(/establishFrozenLocalPolicy\s*\(/);
expect(dynamicImport, 'no dynamic import of main.js').toBeGreaterThan(-1);
expect(establish, 'no establishFrozenLocalPolicy call').toBeGreaterThan(-1);
expect(establish).toBeLessThan(dynamicImport);
});
it('dynamically imports main.js', async () => {
@@ -226,3 +252,94 @@ describe('KBN-101-02a local-dev — the channel has its own command', () => {
expect(manifest.scripts?.['dev']).toBe('tsx watch src/main.ts');
});
});
/**
* The package script, executed rather than read.
*
* The string assertions above are satisfied by `"dev:local": "tsx src/local-dev.ts"`
* pointed at a module that exports `runLocalDev()` and never calls it — which is
* exactly what this file is today: running that command right now exits 0 having
* started nothing and refused nothing. A route that silently succeeds is worse
* than one that is missing, because the operator's shell says it worked.
*
* Safety of running the real route in-band: the child is given no
* BETTER_AUTH_SECRET and no DATABASE_URL. If a future GREEN wrongly reached
* `main.ts`, `bootstrap()` throws on the missing secret at its first statement,
* before `loadConfig()` and before `detectAndAssertTier()` — so no configuration
* is read, no connection is attempted, and nothing listens. The assertions below
* treat that path as a FAILURE rather than a pass, so it cannot be mistaken for
* a refusal.
*/
describe('KBN-101-02a local-dev — the package route refuses, not merely the module', () => {
const GATEWAY_DIR = join(HERE, '..');
const CHILD_TIMEOUT_MS = 120_000;
function runDevLocal(extraEnv: Record<string, string> = {}): {
status: number | null;
signal: NodeJS.Signals | null;
output: string;
} {
const manifest = JSON.parse(readFileSync(join(GATEWAY_DIR, 'package.json'), 'utf8')) as {
scripts?: Record<string, string>;
};
const script = manifest.scripts?.['dev:local'];
expect(script, 'no dev:local script to execute').toBeDefined();
const env: Record<string, string> = {};
for (const [key, value] of Object.entries(process.env)) {
if (value === undefined) {
continue;
}
// Ambient declarations would defeat the point: this case is about what the
// route does when nothing has been declared.
if (key.startsWith('MOSAIC_LOCAL_DEV_') || key === 'DATABASE_URL') {
continue;
}
env[key] = value;
}
delete env['BETTER_AUTH_SECRET'];
const result = spawnSync('sh', ['-c', script as string], {
cwd: GATEWAY_DIR,
env: { ...env, ...extraEnv },
encoding: 'utf8',
timeout: CHILD_TIMEOUT_MS,
killSignal: 'SIGKILL',
});
return {
status: result.status,
signal: result.signal,
output: `${result.stdout ?? ''}${result.stderr ?? ''}`,
};
}
it('exits nonzero with a refusal code when nothing is declared', () => {
const { status, signal, output } = runDevLocal();
expect(signal, 'the route hung and had to be killed').toBeNull();
expect(status, 'the route exited 0 with nothing declared').not.toBe(0);
// Nonzero alone would be satisfied by a crash, a missing dependency, or the
// BETTER_AUTH_SECRET throw inside bootstrap — all of which mean the refusal
// did NOT happen and the start was merely broken.
expect(output).toMatch(/LOCAL_START_[A-Z_]+/);
expect(output, 'exited via bootstrap rather than refusing before it').not.toContain(
'BETTER_AUTH_SECRET',
);
});
it('exits nonzero with a DSN refusal, and echoes no DSN, when one is inherited', () => {
const { status, signal, output } = runDevLocal({
DATABASE_URL: 'postgresql://mosaic:mosaic@localhost:5433/mosaic',
MOSAIC_LOCAL_DEV_TIER: 'local',
MOSAIC_LOCAL_DEV_STORAGE: 'pglite',
MOSAIC_LOCAL_DEV_QUEUE: 'local',
MOSAIC_LOCAL_DEV_STATE_ROOT: mkdtempSync(join(tmpdir(), 'kbn-02a-route-')),
});
expect(signal, 'the route hung and had to be killed').toBeNull();
expect(status).not.toBe(0);
expect(output).toContain('LOCAL_START_POSTGRES_DSN_PRESENT');
expect(output, 'the refusal echoed the DSN it refused').not.toContain('mosaic:mosaic');
});
});
@@ -10,10 +10,11 @@
* Nothing here connects to anything. DSN strings are fixture text.
*/
import { mkdir, mkdtemp, symlink, writeFile } from 'node:fs/promises';
import type * as NodeFs from 'node:fs';
import { chmod, mkdir, mkdtemp, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, expect, it, vi } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type * as PolicyNamespace from './local-start-policy.js';
import type {
@@ -21,6 +22,7 @@ import type {
LocalStartDeclaration,
LocalStartRefusalCode,
} from './local-start-policy.js';
import type { LocalStartSourceLabel } from './source-inventory.js';
const FORBIDDEN_DSN = 'postgresql://mosaic:mosaic@localhost:5433/mosaic';
@@ -32,6 +34,16 @@ async function freshPolicy(): Promise<PolicyModule> {
return import('./local-start-policy.js');
}
/** Undo module substitutions so one case's mock cannot leak into the next. */
const mockedFsCases: (() => void)[] = [];
afterEach(() => {
while (mockedFsCases.length > 0) {
mockedFsCases.pop()?.();
}
vi.resetModules();
});
interface Bed {
tempRoot: string;
anchor: string;
@@ -275,21 +287,125 @@ describe('KBN-101-02a establishFrozenLocalPolicy — row 8, PGlite and disposabl
);
});
// The traversal segment is concatenated, NOT built with join(). `join()`
// normalizes `a/../a` to `a` before the policy is ever called, so a case built
// that way asserts a refusal on a string identical to the valid one — it can
// only be made green by also refusing the valid declaration. A red that cannot
// go green for its stated reason is the same defect class as a green that
// cannot go red.
it('refuses a state root that is not its own canonical form', async () => {
const bed = await makeBed();
const policy = await freshPolicy();
const noncanonical = `${bed.tempRoot}/session-state/../session-state`;
expect(noncanonical).not.toBe(bed.stateRoot);
expect(noncanonical).toContain('/../');
await expectRefusal(
policy,
establishInput(bed, {
declaration: { ...validDeclaration(bed.stateRoot), stateRoot: noncanonical },
}),
'LOCAL_START_STATE_ROOT_ESCAPES',
);
});
it('refuses a state root that does not exist rather than creating it', async () => {
const bed = await makeBed();
const policy = await freshPolicy();
const absent = join(bed.tempRoot, 'never-created');
await expectRefusal(
policy,
establishInput(bed, {
declaration: { ...validDeclaration(bed.stateRoot), stateRoot: absent },
}),
'LOCAL_START_STATE_ROOT_ABSENT',
);
});
it('refuses a state root that exists but is a file', async () => {
const bed = await makeBed();
const notADir = join(bed.tempRoot, 'a-file');
await writeFile(notADir, 'not a directory\n', 'utf8');
const policy = await freshPolicy();
await expectRefusal(
policy,
establishInput(bed, {
declaration: {
...validDeclaration(bed.stateRoot),
stateRoot: join(bed.tempRoot, 'session-state', '..', 'session-state'),
},
declaration: { ...validDeclaration(bed.stateRoot), stateRoot: notADir },
}),
'LOCAL_START_STATE_ROOT_ESCAPES',
'LOCAL_START_STATE_ROOT_NOT_DIRECTORY',
);
});
// lstat, not stat: a stat on a symlinked root reports the TARGET, so every
// later bar would describe a directory the operator never named.
it('refuses a state root that is itself a symlink, even to a valid directory', async () => {
const bed = await makeBed();
const realRoot = join(bed.tempRoot, 'real-root');
await mkdir(realRoot, { recursive: true, mode: 0o700 });
const linkedRoot = join(bed.tempRoot, 'linked-root');
await symlink(realRoot, linkedRoot, 'dir');
const policy = await freshPolicy();
await expectRefusal(
policy,
establishInput(bed, {
declaration: { ...validDeclaration(bed.stateRoot), stateRoot: linkedRoot },
}),
'LOCAL_START_STATE_ROOT_IS_SYMLINK',
);
});
it('refuses a state root readable by group or other', async () => {
const bed = await makeBed();
const shared = join(bed.tempRoot, 'shared-root');
await mkdir(shared, { recursive: true });
await chmod(shared, 0o755);
const policy = await freshPolicy();
await expectRefusal(
policy,
establishInput(bed, {
declaration: { ...validDeclaration(bed.stateRoot), stateRoot: shared },
}),
'LOCAL_START_STATE_ROOT_NOT_PRIVATE',
);
});
// Ownership is the one bar that cannot be built on the filesystem without
// privileges: an unprivileged process cannot chown, and a privileged one owns
// everything it creates, so a filesystem-built case would pass trivially
// wherever CI runs as root. That is a vacuous case, not a cheap one. The stat
// result is therefore substituted directly. This couples the case to the
// mechanism (the implementation must learn ownership from an lstat), which is
// inherent to the bar rather than an accident of this test.
it('refuses a state root owned by another user', async () => {
const bed = await makeBed();
const foreignUid = (process.getuid?.() ?? 0) + 1;
vi.resetModules();
vi.doMock('node:fs', async () => {
const actual = await vi.importActual<typeof NodeFs>('node:fs');
return {
...actual,
lstatSync: (target: string, ...rest: unknown[]) => {
const stats = (
actual.lstatSync as unknown as (...args: unknown[]) => Record<string, unknown>
)(target, ...rest);
if (target === bed.stateRoot) {
return {
...stats,
uid: foreignUid,
isDirectory: () => true,
isSymbolicLink: () => false,
};
}
return stats;
},
};
});
mockedFsCases.push(() => {
vi.doUnmock('node:fs');
});
const policy = await import('./local-start-policy.js');
await expectRefusal(policy, establishInput(bed), 'LOCAL_START_STATE_ROOT_NOT_OWNED');
});
it('refuses when a derived child escapes the state root through a symlink', async () => {
const bed = await makeBed();
const escapeTarget = join(bed.tempRoot, 'outside');
@@ -384,3 +500,137 @@ describe('KBN-101-02a establishFrozenLocalPolicy — row 9, one-shot freeze', ()
expect((thrown as { code?: string }).code).toBe('LOCAL_START_POLICY_NOT_ESTABLISHED');
});
});
/** Where each file source lives, relative to the bed's anchor and home base. */
function sourcePath(
bed: Bed,
label: Exclude<LocalStartSourceLabel, 'process-environment'>,
): string {
const repo = join(bed.anchor, '..', '..', '..');
const daemonDir = join(bed.homeBase, '.config', 'mosaic', 'gateway');
const paths: Record<Exclude<LocalStartSourceLabel, 'process-environment'>, string> = {
'daemon-dotenv': join(daemonDir, '.env'),
'monorepo-root-dotenv': join(repo, '.env'),
'gateway-local-dotenv': join(repo, 'apps', 'gateway', '.env'),
'daemon-json': join(daemonDir, 'mosaic.config.json'),
'app-json': join(repo, 'apps', 'gateway', 'mosaic.config.json'),
'monorepo-root-json': join(repo, 'mosaic.config.json'),
};
return paths[label];
}
const CLEAN_LOCAL_JSON = JSON.stringify({
tier: 'local',
storage: { type: 'pglite' },
queue: { type: 'local' },
memory: { type: 'none' },
});
async function writeSource(bed: Bed, label: Parameters<typeof sourcePath>[1], body: string) {
const path = sourcePath(bed, label);
await mkdir(join(path, '..'), { recursive: true });
await writeFile(path, body, 'utf8');
return path;
}
/**
* A source that cannot be read or parsed has UNKNOWN contents, and an unknown
* source has not been shown to be free of a forbidden value. Skipping it — or
* letting a clean higher-precedence source stand in for it — is precisely the
* "a forbidden value refuses even when shadowed" rule failing in its other
* direction: here the forbidden value is not absent, it is unreadable.
*/
describe('KBN-101-02a establishFrozenLocalPolicy — unreadable and malformed sources', () => {
it('refuses malformed JSON in a LOSING source even when the winning source is clean', async () => {
const bed = await makeBed();
await writeSource(bed, 'monorepo-root-json', CLEAN_LOCAL_JSON);
await writeSource(bed, 'app-json', '{ "tier": "local", ');
const policy = await freshPolicy();
await expectRefusal(policy, establishInput(bed), 'LOCAL_START_SOURCE_MALFORMED');
});
it('refuses malformed JSON in the daemon config specifically', async () => {
const bed = await makeBed();
await writeSource(bed, 'monorepo-root-json', CLEAN_LOCAL_JSON);
await writeSource(bed, 'daemon-json', 'not json at all');
const policy = await freshPolicy();
await expectRefusal(policy, establishInput(bed), 'LOCAL_START_SOURCE_MALFORMED');
});
// A directory where a file is expected fails the read with EISDIR for every
// uid. Mode 0o000 would not: a process running as root reads it anyway, so
// that case would pass trivially in exactly the environments CI uses.
it('refuses a source that cannot be read at all', async () => {
const bed = await makeBed();
await writeSource(bed, 'monorepo-root-json', CLEAN_LOCAL_JSON);
await mkdir(sourcePath(bed, 'gateway-local-dotenv'), { recursive: true });
const policy = await freshPolicy();
await expectRefusal(policy, establishInput(bed), 'LOCAL_START_SOURCE_UNREADABLE');
});
it('names the offending source label without quoting its contents', async () => {
const bed = await makeBed();
await writeSource(bed, 'app-json', `{ "tier": "local", "note": "${FORBIDDEN_DSN}"`);
const policy = await freshPolicy();
let thrown: unknown;
try {
policy.establishFrozenLocalPolicy(establishInput(bed));
} catch (error) {
thrown = error;
}
const refusal = thrown as InstanceType<PolicyModule['LocalStartRefusal']>;
expect(refusal?.code).toBe('LOCAL_START_SOURCE_MALFORMED');
expect(refusal.sourceLabels).toContain('app-json');
expect(`${refusal.message} ${JSON.stringify(refusal.sourceLabels)}`).not.toContain(
FORBIDDEN_DSN,
);
});
});
/**
* The freeze is a promise about the FUTURE, and a snapshot taken at establish
* time proves nothing about it. These cases mutate the underlying sources after
* establish and then read the policy back: a consumer that re-reads a file on
* demand, or holds a live reference into one, fails here and passes every case
* that only inspects the object returned at establish time.
*/
describe('KBN-101-02a establishFrozenLocalPolicy — the freeze survives source mutation', () => {
it('retains the frozen policy after a source file is rewritten to carry a DSN', async () => {
const bed = await makeBed();
await writeSource(bed, 'monorepo-root-json', CLEAN_LOCAL_JSON);
const policy = await freshPolicy();
const established = policy.establishFrozenLocalPolicy(establishInput(bed));
await writeSource(
bed,
'monorepo-root-json',
JSON.stringify({
tier: 'standalone',
storage: { type: 'postgres', url: FORBIDDEN_DSN },
queue: { type: 'bullmq' },
memory: { type: 'none' },
}),
);
const after = policy.requireFrozenLocalPolicy();
expect(after).toBe(established);
expect(after.tier).toBe('local');
expect(after.storage.type).toBe('pglite');
expect(JSON.stringify(after)).not.toContain(FORBIDDEN_DSN);
});
it('retains the frozen policy after the ambient environment gains a DSN', async () => {
const bed = await makeBed();
await writeSource(bed, 'monorepo-root-json', CLEAN_LOCAL_JSON);
const policy = await freshPolicy();
const env: Record<string, string | undefined> = {};
const established = policy.establishFrozenLocalPolicy(establishInput(bed, { env }));
env['DATABASE_URL'] = FORBIDDEN_DSN;
const after = policy.requireFrozenLocalPolicy();
expect(after).toBe(established);
expect(after.storage.type).toBe('pglite');
expect(JSON.stringify(after)).not.toContain(FORBIDDEN_DSN);
});
});
@@ -25,6 +25,13 @@ export type LocalStartRefusalCode =
| 'LOCAL_START_STATE_ROOT_MISSING'
| 'LOCAL_START_STATE_ROOT_NOT_ABSOLUTE'
| 'LOCAL_START_STATE_ROOT_ESCAPES'
| 'LOCAL_START_STATE_ROOT_ABSENT'
| 'LOCAL_START_STATE_ROOT_NOT_DIRECTORY'
| 'LOCAL_START_STATE_ROOT_IS_SYMLINK'
| 'LOCAL_START_STATE_ROOT_NOT_PRIVATE'
| 'LOCAL_START_STATE_ROOT_NOT_OWNED'
| 'LOCAL_START_SOURCE_UNREADABLE'
| 'LOCAL_START_SOURCE_MALFORMED'
| 'LOCAL_START_CROSS_FIELD_MISMATCH'
| 'LOCAL_START_ALREADY_ESTABLISHED'
| 'LOCAL_START_POLICY_NOT_ESTABLISHED';
@@ -48,6 +55,34 @@ export class LocalStartRefusal extends Error {
}
}
/**
* The disposable state-root contract, stated so it is testable.
*
* "Disposable" is not a property of the path string; it is a property of the
* directory the string names. A declared root is admitted only when ALL of the
* following hold, each with its own refusal code so a removed guard turns
* exactly one case red:
*
* 1. absolute (`LOCAL_START_STATE_ROOT_NOT_ABSOLUTE`);
* 2. already its own canonical form, lexically (`LOCAL_START_STATE_ROOT_ESCAPES`);
* 3. it exists (`LOCAL_START_STATE_ROOT_ABSENT`) — the local channel does not
* create the root, because a channel that creates its own root cannot
* distinguish "session-scoped" from "silently reused";
* 4. it is a directory (`LOCAL_START_STATE_ROOT_NOT_DIRECTORY`);
* 5. the root itself is not a symlink (`LOCAL_START_STATE_ROOT_IS_SYMLINK`) —
* checked with lstat, since a stat on a symlinked root reports the target and
* every subsequent check would describe a directory the operator did not name;
* 6. it is owned by the current user (`LOCAL_START_STATE_ROOT_NOT_OWNED`);
* 7. it is private — no group or other permission bits
* (`LOCAL_START_STATE_ROOT_NOT_PRIVATE`);
* 8. no derived child escapes it through a symlink (`LOCAL_START_STATE_ROOT_ESCAPES`).
*
* Only 1, 2 and 8 were covered by the first RED pass. A derived-child symlink
* check alone admits a root that is missing, is a file, is a symlink into
* someone else's directory, or is world-writable — none of which is disposable
* in any sense that protects the operator's data.
*/
/** Explicit declarations supplied for THIS invocation. Nothing is defaulted. */
export interface LocalStartDeclaration {
readonly tier?: string;
@@ -81,8 +116,10 @@ let established: FrozenLocalPolicy | undefined;
* - refuses any PostgreSQL DSN or non-local tier found in ANY source, shadowed
* or not;
* - refuses cross-field mismatches (local+postgres, local+files, non-local+pglite);
* - canonicalizes DB/storage/queue children under the state root and refuses
* traversal or symlink escape; never uses HOME/CWD defaults;
* - refuses a source it cannot read or parse, EVEN when a clean source would win
* precedence — an unknown source has not been shown to be safe;
* - enforces the full disposable state-root contract above; never uses HOME/CWD
* defaults;
* - deep-clones and deep-freezes an allowed-only object;
* - a second call refuses with LOCAL_START_ALREADY_ESTABLISHED; there is no reset.
*/
@@ -14,7 +14,7 @@
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
LOCAL_START_SOURCE_LABELS,
@@ -291,3 +291,151 @@ describe('KBN-101-02a inventoryLocalStartSources', () => {
expect(JSON.stringify(sources)).not.toContain('mosaic:mosaic');
});
});
/**
* Tier and DSN are separate forbidden classes and must be separately detected.
*
* The DSN cases above would all still pass if the tier check were deleted
* outright, because every DSN fixture also declares a non-local tier. These
* cases plant a non-local tier and NO DSN, in exactly one source at a time, so
* deleting the tier guard for one source turns exactly one case red and the DSN
* cases stay green.
*/
const NON_LOCAL_TIER_JSON = JSON.stringify({
tier: 'standalone',
storage: { type: 'pglite' },
queue: { type: 'local' },
memory: { type: 'none' },
});
async function plantNonLocalTierIn(
layout: Layout,
target: Exclude<LocalStartSourceLabel, 'process-environment'>,
): Promise<void> {
for (const [label, path] of Object.entries(layout.paths)) {
const isTarget = label === target;
if (label.endsWith('json')) {
await writeFixture(path, isTarget ? NON_LOCAL_TIER_JSON : CLEAN_LOCAL_JSON);
continue;
}
await writeFixture(
path,
isTarget ? 'MOSAIC_STORAGE_TIER=standalone\n' : 'MOSAIC_LOG_LEVEL=info\n',
);
}
}
describe('KBN-101-02a inventoryLocalStartSources — non-local tier, per source, without any DSN', () => {
const fileLabels: Exclude<LocalStartSourceLabel, 'process-environment'>[] = [
'daemon-dotenv',
'monorepo-root-dotenv',
'gateway-local-dotenv',
'daemon-json',
'app-json',
'monorepo-root-json',
];
for (const label of fileLabels) {
it(`classifies a non-local tier declared only in ${label}`, async () => {
const layout = await makeLayout();
await plantNonLocalTierIn(layout, label);
const sources = inventoryLocalStartSources({
anchor: layout.anchor,
homeBase: layout.homeBase,
env: {},
});
expect(find(sources, label).declaredTier).toBe('non-local');
expect(find(sources, label).carriesPostgresDsn).toBe(false);
for (const other of fileLabels.filter((candidate) => candidate !== label)) {
expect(find(sources, other).declaredTier).not.toBe('non-local');
}
});
}
it('classifies a non-local tier inherited from the ambient process environment', async () => {
const layout = await makeLayout();
for (const [label, path] of Object.entries(layout.paths)) {
await writeFixture(
path,
label.endsWith('json') ? CLEAN_LOCAL_JSON : 'MOSAIC_LOG_LEVEL=info\n',
);
}
const sources = inventoryLocalStartSources({
anchor: layout.anchor,
homeBase: layout.homeBase,
env: { MOSAIC_STORAGE_TIER: 'federated' },
});
expect(find(sources, 'process-environment').declaredTier).toBe('non-local');
expect(find(sources, 'process-environment').carriesPostgresDsn).toBe(false);
});
});
/**
* Classification, not quotation.
*
* `declaredTier: string` is value-free only by convention — it holds whatever
* the file said. These cases plant a canary in the value position of every
* source and assert it appears nowhere: not in the result, and not in anything
* written to the console on the way there. The result is serialized whole rather
* than field-by-field, so a field added later is covered without anyone
* remembering to extend this case.
*/
describe('KBN-101-02a inventoryLocalStartSources — no source value is retained or logged', () => {
const CANARY = 'kbn-101-02a-canary-2f7d1c';
it('retains no planted canary anywhere in the result', async () => {
const layout = await makeLayout();
for (const [label, path] of Object.entries(layout.paths)) {
await writeFixture(
path,
label.endsWith('json')
? JSON.stringify({
tier: CANARY,
storage: { type: CANARY, url: `postgresql://${CANARY}@localhost:5432/db` },
queue: { type: CANARY },
})
: `MOSAIC_STORAGE_TIER=${CANARY}\nDATABASE_URL=postgresql://${CANARY}@localhost:5432/db\n`,
);
}
const sources = inventoryLocalStartSources({
anchor: layout.anchor,
homeBase: layout.homeBase,
env: { MOSAIC_STORAGE_TIER: CANARY },
});
expect(JSON.stringify(sources)).not.toContain(CANARY);
});
it('writes no planted canary to the console while inspecting sources', async () => {
const layout = await makeLayout();
await writeFixture(
layout.paths['monorepo-root-json'],
JSON.stringify({ tier: CANARY, storage: { type: CANARY }, queue: { type: CANARY } }),
);
const written: string[] = [];
const methods = ['log', 'info', 'warn', 'error', 'debug', 'trace'] as const;
const spies = methods.map((method) =>
vi.spyOn(console, method).mockImplementation((...args: unknown[]): void => {
written.push(args.map((arg) => String(arg)).join(' '));
}),
);
try {
inventoryLocalStartSources({
anchor: layout.anchor,
homeBase: layout.homeBase,
env: { DATABASE_URL: `postgresql://${CANARY}@localhost:5432/db` },
});
} finally {
for (const spy of spies) {
spy.mockRestore();
}
}
expect(written.join('\n')).not.toContain(CANARY);
});
});
@@ -31,6 +31,28 @@ export const LOCAL_START_SOURCE_LABELS: readonly LocalStartSourceLabel[] = [
'monorepo-root-json',
];
/**
* Finite classifications, not values.
*
* These are unions rather than `string` on purpose. A `declaredTier?: string`
* field is value-free only by convention: it will hold whatever the source said,
* so a canary planted in a config file survives into the inventory and from
* there into anything that logs it. A union of two members cannot carry a
* canary — the type makes retention unrepresentable rather than discouraged.
*/
export type DeclaredTierClass = 'local' | 'non-local';
export type DeclaredStorageClass = 'pglite' | 'non-pglite';
/**
* Whether the source could be read and parsed at all.
*
* A source that cannot be read is not the same as a source that is absent. An
* unreadable or malformed source is a source whose contents are UNKNOWN, and an
* unknown source cannot be shown to be free of a forbidden value — so it refuses
* rather than being skipped.
*/
export type SourceReadability = 'readable' | 'unreadable' | 'malformed';
/**
* What one source was observed to carry.
*
@@ -45,10 +67,12 @@ export interface InventoriedSource {
readonly present: boolean;
/** True when this source carries a PostgreSQL DSN in any recognized key. */
readonly carriesPostgresDsn: boolean;
/** Tier value declared by this source, if it declares one. */
readonly declaredTier?: string;
/** Storage type declared by this source, if it declares one. */
readonly declaredStorageType?: string;
/** Whether this source was readable and parseable. */
readonly readability: SourceReadability;
/** Classification of the tier this source declares, if it declares one. */
readonly declaredTier?: DeclaredTierClass;
/** Classification of the storage type this source declares, if it declares one. */
readonly declaredStorageType?: DeclaredStorageClass;
}
export interface InventoryInput {
@@ -67,6 +91,8 @@ export interface InventoryInput {
* - every label in LOCAL_START_SOURCE_LABELS appears in the result, present or not;
* - MOSAIC_GATEWAY_HOME redirects `daemon-json` ONLY — `daemon-dotenv` stays at
* `$HOME/.config/mosaic/gateway/.env` regardless of the redirect;
* - a source that cannot be read or parsed is reported as such, never skipped
* and never treated as clean;
* - no precedence is applied and no value is retained.
*/
export function inventoryLocalStartSources(_input: InventoryInput): InventoriedSource[] {
+264
View File
@@ -3,6 +3,8 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import * as nodeOs from 'node:os';
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
import * as nodeUrl from 'node:url';
import type * as NestCore from '@nestjs/core';
import type * as MosaicConfigModule from '@mosaicstack/config';
import type { MosaicConfig } from '@mosaicstack/config';
import type * as MosaicStorage from '@mosaicstack/storage';
import { describe, expect, it, vi, type MockInstance } from 'vitest';
@@ -162,3 +164,265 @@ describe('main bootstrap preflight config anchoring', (): void => {
MODULE_IMPORT_TIMEOUT_MS,
);
});
/**
* KBN-101-02a — the real consumers, executed.
*
* The local-dev REDs mock `./main.js` wholesale, which proves the entrypoint
* refuses before it calls bootstrap and proves nothing at all about what
* bootstrap, AppModule and ConfigModule do when they DO run. Those three each
* perform their own independent `loadConfig(resolveGatewayConfigPath())` —
* `main.ts:28`, `app.module.ts:39`, `config.module.ts:12` — and the frozen
* policy is worth nothing if the modules that actually start the process ignore
* it. Every case below drives the real module.
*
* `@nestjs/core` is substituted so a case that wrongly proceeds cannot listen,
* and `@mosaicstack/storage` so no tier probe can reach a real service. Those
* substitutions are also the instrument: the assertions are that they were never
* called.
*
* While red, this block also emits unhandled rejections ('process.exit
* unexpectedly called with "1"'). They are not flake and must not be silenced:
* they are the self-invocation at `main.ts:71` failing asynchronously after the
* case that imported the module has already returned. An entrypoint that starts
* nothing on import cannot produce them, so they disappear when the defect is
* fixed. If they survive GREEN, the entrypoint is still self-invoking.
*
* NOTE — the case above this block ('main bootstrap preflight config anchoring')
* asserts the CURRENT contract, in which importing `main.ts` self-invokes
* `bootstrap()` and exits the process on failure:
*
* await import('./main.js');
* await vi.waitFor(() => { expect(exitSpy).toHaveBeenCalled(); });
*
* That expectation is the direct opposite of the contract this item introduces,
* so it cannot survive GREEN unchanged. It is deliberately left intact here: it
* is the current behavior's regression guard, it must keep passing until the
* behavior actually changes, and the GREEN author has to migrate it to drive the
* exported `bootstrap()` explicitly rather than delete it. A red left where the
* change has to happen is cheaper than a note in a document.
*/
describe('KBN-101-02a main.ts — importing the entrypoint starts nothing', () => {
// The application double has to be complete enough for bootstrap to run all
// the way through, not merely far enough to observe the config read. A thinner
// double fails inside itself (`app.getHttpAdapter is not a function`), which
// would leave these cases unable to go green for the reason they state even
// after the defect is fixed. The HTTP instance is a permissive proxy so the
// double does not have to track every route the controllers mount.
const nestCreateMock = vi.fn(async () => ({
enableCors: vi.fn(),
register: vi.fn(async () => {}),
useGlobalPipes: vi.fn(),
get: vi.fn(() => ({})),
getHttpAdapter: vi.fn(() => ({
getInstance: () => new Proxy({}, { get: () => vi.fn() }),
})),
listen: vi.fn(async () => {}),
}));
const detectAndAssertTierMock = vi.fn(async () => {});
const loadConfigMock = vi.fn(() => ({
tier: 'local' as const,
storage: { type: 'pglite' as const, dataDir: '.mosaic/storage-pglite' },
queue: { type: 'local' as const, dataDir: '.mosaic/queue' },
memory: { type: 'keyword' as const },
}));
async function establishLocalChannel(): Promise<string> {
const tempRoot = await mkdtemp(join(nodeOs.tmpdir(), 'kbn-02a-main-'));
const anchor = join(tempRoot, 'repo', 'apps', 'gateway', 'src');
const homeBase = join(tempRoot, 'home');
const stateRoot = join(tempRoot, 'session-state');
await mkdir(anchor, { recursive: true });
await mkdir(homeBase, { recursive: true });
await mkdir(stateRoot, { recursive: true });
const { establishFrozenLocalPolicy } = await import('./local-start/local-start-policy.js');
establishFrozenLocalPolicy({
declaration: { tier: 'local', storageType: 'pglite', queueType: 'local', stateRoot },
anchor,
homeBase,
env: {},
});
return stateRoot;
}
function substituteStartupSurface(): void {
vi.doMock('./tracing.js', () => ({}));
// Partial, never wholesale. A wholesale substitution drops APP_GUARD and
// every other export app.module.ts evaluates at import time, so the module
// throws before reaching the behavior under test — the case would then be
// red for a reason it does not state, and could never go green by fixing
// the defect it names.
vi.doMock('@nestjs/core', async () => {
const actual = await vi.importActual<typeof NestCore>('@nestjs/core');
return { ...actual, NestFactory: { ...actual.NestFactory, create: nestCreateMock } };
});
vi.doMock('@mosaicstack/storage', async () => {
const actual = await vi.importActual<typeof MosaicStorage>('@mosaicstack/storage');
return { ...actual, detectAndAssertTier: detectAndAssertTierMock };
});
}
function undoSubstitutions(): void {
vi.doUnmock('@mosaicstack/storage');
vi.doUnmock('@nestjs/core');
vi.doUnmock('./tracing.js');
vi.doUnmock('@mosaicstack/config');
}
it(
'does not bootstrap, probe, or exit merely by being imported',
async (): Promise<void> => {
const originalEnv = snapshotProcessEnv();
const exitMock = vi.fn<typeof process.exit>();
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(exitMock);
try {
delete process.env['BETTER_AUTH_SECRET'];
vi.resetModules();
vi.clearAllMocks();
substituteStartupSurface();
await import('./main.js');
// A self-invoking entrypoint fails asynchronously, so give the rejection
// a turn to land before concluding that nothing happened.
await new Promise((resolve) => setImmediate(resolve));
expect(exitSpy, 'importing main.ts exited the process').not.toHaveBeenCalled();
expect(detectAndAssertTierMock, 'importing main.ts probed the tier').not.toHaveBeenCalled();
expect(nestCreateMock, 'importing main.ts created the Nest app').not.toHaveBeenCalled();
} finally {
exitSpy.mockRestore();
undoSubstitutions();
vi.resetModules();
restoreProcessEnv(originalEnv);
}
},
MODULE_IMPORT_TIMEOUT_MS,
);
it(
'exports bootstrap so the local entrypoint can call it after establishing the policy',
async (): Promise<void> => {
const originalEnv = snapshotProcessEnv();
try {
vi.resetModules();
substituteStartupSurface();
const mod = (await import('./main.js')) as { bootstrap?: unknown };
expect(typeof mod.bootstrap, 'main.ts does not export bootstrap').toBe('function');
} finally {
undoSubstitutions();
vi.resetModules();
restoreProcessEnv(originalEnv);
}
},
MODULE_IMPORT_TIMEOUT_MS,
);
it(
'performs no independent config read or tier probe when the frozen policy is established',
async (): Promise<void> => {
const originalEnv = snapshotProcessEnv();
try {
process.env['BETTER_AUTH_SECRET'] = 'fixture-secret';
vi.resetModules();
vi.clearAllMocks();
substituteStartupSurface();
vi.doMock('@mosaicstack/config', async () => {
const actual = await vi.importActual<typeof MosaicConfigModule>('@mosaicstack/config');
return { ...actual, loadConfig: loadConfigMock };
});
await establishLocalChannel();
const mod = (await import('./main.js')) as { bootstrap?: () => Promise<void> };
await mod.bootstrap?.();
expect(
loadConfigMock,
'bootstrap re-read configuration independently',
).not.toHaveBeenCalled();
expect(detectAndAssertTierMock, 'bootstrap probed the tier').not.toHaveBeenCalled();
} finally {
undoSubstitutions();
vi.resetModules();
restoreProcessEnv(originalEnv);
}
},
MODULE_IMPORT_TIMEOUT_MS,
);
it(
'evaluates AppModule without an independent config read under the frozen policy',
async (): Promise<void> => {
const originalEnv = snapshotProcessEnv();
try {
vi.resetModules();
vi.clearAllMocks();
substituteStartupSurface();
vi.doMock('@mosaicstack/config', async () => {
const actual = await vi.importActual<typeof MosaicConfigModule>('@mosaicstack/config');
return { ...actual, loadConfig: loadConfigMock };
});
await establishLocalChannel();
// app.module.ts decides federation at module-evaluation time, so the read
// happens on import — before any provider is constructed.
await import('./app.module.js');
expect(
loadConfigMock,
'AppModule re-read configuration independently',
).not.toHaveBeenCalled();
} finally {
undoSubstitutions();
vi.resetModules();
restoreProcessEnv(originalEnv);
}
},
MODULE_IMPORT_TIMEOUT_MS,
);
it(
'serves MOSAIC_CONFIG from the frozen policy rather than a fresh load',
async (): Promise<void> => {
const originalEnv = snapshotProcessEnv();
try {
vi.resetModules();
vi.clearAllMocks();
substituteStartupSurface();
vi.doMock('@mosaicstack/config', async () => {
const actual = await vi.importActual<typeof MosaicConfigModule>('@mosaicstack/config');
return { ...actual, loadConfig: loadConfigMock };
});
const stateRoot = await establishLocalChannel();
const configModule = await import('./config/config.module.js');
const providers = (Reflect.getMetadata('providers', configModule.ConfigModule) ?? []) as {
provide?: unknown;
useFactory?: () => unknown;
}[];
const provider = providers.find(
(candidate) => candidate.provide === configModule.MOSAIC_CONFIG,
);
expect(provider?.useFactory, 'MOSAIC_CONFIG provider factory not found').toBeDefined();
const served = provider!.useFactory!() as {
tier?: string;
storage?: { type?: string; dataDir?: string };
};
expect(
loadConfigMock,
'the MOSAIC_CONFIG factory called loadConfig',
).not.toHaveBeenCalled();
expect(served.tier).toBe('local');
expect(served.storage?.type).toBe('pglite');
expect(served.storage?.dataDir).toContain(stateRoot);
} finally {
undoSubstitutions();
vi.resetModules();
restoreProcessEnv(originalEnv);
}
},
MODULE_IMPORT_TIMEOUT_MS,
);
});
+55 -19
View File
@@ -379,9 +379,14 @@ performs no dotenv load and no re-read.
This ordering is what makes "refused before connection" true rather than asserted: the modules that
can connect are not even loaded until the policy has been established.
### Path ownership — the 15-path fence for `-02a`
### Path ownership — the 16-path fence for `-02a`
These paths are assigned to `-02a`. A 16th path is a STOP.
These paths are assigned to `-02a`. A 17th path is a STOP.
Revision 15 → 16 was granted by the verifier on the AMEND verdict, solely for
`apps/gateway/src/main.spec.ts`, so the frozen policy can be proven against the actual entrypoint
and module graph rather than against a wholesale mock of them. No other path was added, and
`app.module.spec.ts` was explicitly not authorized.
| # | Path |
| --- | --------------------------------------------------------- |
@@ -400,6 +405,7 @@ These paths are assigned to `-02a`. A 16th path is a STOP.
| 13 | `apps/gateway/src/local-start/local-start-policy.spec.ts` |
| 14 | `apps/gateway/src/local-dev.ts` |
| 15 | `apps/gateway/src/local-dev.spec.ts` |
| 16 | `apps/gateway/src/main.spec.ts` |
**Not owned by `-02a`, and not touched by it:** `packages/db/src/client.ts` (`-01`),
`packages/db/src/migrate.ts` (`-03`), `packages/db/src/credential-handoff/**` (`-10`),
@@ -436,32 +442,62 @@ its stated reason is the same defect class as the span gap that created this ite
### RED phase result
Focused matrix, gateway package: **50 failing / 10 passing (60 cases, 5 files)**. Typecheck and
Focused matrix, gateway package: **77 failing / 15 passing (92 cases, 6 files)**. Typecheck and
lint clean. Zero non-assertion failures — no module-resolution, mock-setup or masking errors, so
every red is a behavioral assertion that names its own guard.
Nine of the fifteen fenced paths are touched in RED. The six GREEN targets (`package.json`,
Ten of the sixteen fenced paths are touched in RED. The six GREEN targets (`package.json`,
`env.ts`, `app.module.ts`, `config/config.module.ts`, `database/database.module.ts`, `main.ts`)
are deliberately unmodified, so their specs are red against the production files as they stand.
The ten passing cases are itemized here rather than counted as coverage, because a green that
**Correction to the first RED report.** That report implied the fenced REDs covered the GREEN
targets behaviorally. They did not. Every frozen-policy case mocked `./main.js` wholesale, so
nothing executed `main.ts`, `AppModule`, or the `ConfigModule` provider, and the three independent
reads at `main.ts:28`, `app.module.ts:39` and `config.module.ts:12` could all have survived a
fully green matrix. The report also claimed the zero-side-effect bars as proven; they were proven
only against scaffolding. This pass closes the first gap and narrows the second:
- `main.ts`, `app.module.ts` and `config/config.module.ts` now have behavior-first REDs that
import the real modules with no `./main.js` mock, and assert no independent `loadConfig`, no
`detectAndAssertTier`, and no `NestFactory.create` (bars Z1 and Z5 through the actual path).
- `database.module.ts` now has REDs that drive the real `onModuleInit()`, asserting that migration
and adapter-migrate never run (bars Z3 and Z4) — still through constructor substitution, not
through a full application boot.
- `package.json` now has a RED that executes the packaged `dev:local` route as a child process,
requiring a `LOCAL_START_*` code in its output and the absence of the `BETTER_AUTH_SECRET`
throw, so a crash or an unrelated failure cannot satisfy it.
- `env.ts` remains covered only by legacy-continuity regression guards, by design.
The bars are therefore proven through the actual entrypoint for the probe and application-create
side effects, and through the real database lifecycle for the migration side effects. They are not
yet proven through a full boot, and this document does not claim they are.
The fifteen passing cases are itemized rather than counted as coverage, because a green that
cannot fail for its stated reason is the same defect class as the span gap that created this item:
| Passing case | Why it is green |
| --------------------------------------------- | -------------------------------------------------------------------- |
| env: config read on import, no policy | legacy regression guard — expected green |
| env: planted dotenv loads, no policy | legacy regression guard — expected green |
| local-dev: production `dev` script untouched | regression guard — expected green |
| local-dev: no static import of `main.js` | structural; the scaffolding already complies, and it must stay green |
| local-dev: importing starts nothing | structural; same |
| policy: refuses to serve when unestablished | scaffolding implements this correctly already |
| policy: no fallback read when unestablished | scaffolding implements this correctly already |
| inventory: retains no raw DSN text | **vacuous today** — the stub returns an empty inventory |
| policy: children beneath the state root | **vacuous today** — the stub concatenates the declared root |
| policy: unaffected by post-establish mutation | **vacuous today** — the stub captured strings, it did not clone |
| Passing case | Why it is green |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| env: config read on import, no policy | legacy regression guard — expected green |
| env: planted dotenv loads, no policy | legacy regression guard — expected green |
| main: anchored config reaches `detectAndAssertTier` | current-contract regression guard; its import-time-exit premise is superseded and must migrate at GREEN |
| local-dev: production `dev` script untouched | regression guard — expected green |
| local-dev: no static import of `main.js` | structural, three forms (binding, side-effect, `require`); must stay green |
| local-dev: importing starts nothing | structural; green today only because nothing invokes `runLocalDev` |
| policy: refuses to serve when unestablished | scaffolding implements this correctly already |
| policy: no fallback read when unestablished | scaffolding implements this correctly already |
| inventory: retains no raw DSN text | **vacuous today** — the stub returns an empty inventory |
| inventory: retains no planted canary | **vacuous today** — same empty inventory |
| inventory: writes no planted canary to the console | **vacuous today** — the stub reads no source, so it can log none |
| policy: children beneath the state root | **vacuous today** — the stub concatenates the declared root |
| policy: unaffected by post-establish input mutation | **vacuous today** — the stub captured strings, it did not clone |
| policy: frozen policy survives a source file gaining a DSN | **vacuous today** — the stub re-serves the same object it built |
| policy: frozen policy survives the environment gaining a DSN | **vacuous today** — same |
The three vacuous greens are meaningful only once the implementation exists. They are re-checked
at GREEN, not carried forward as evidence.
Seven greens are vacuous until the implementation exists, up from three, because this pass added
retention and post-freeze-mutation cases that a stub satisfies trivially. They are evidence of
nothing today and are re-checked at GREEN. The count rising is the honest outcome of asserting
more about a module that does not exist yet — it is recorded here so it cannot later be read as
coverage.
### Scope discipline