This commit is contained in:
@@ -451,15 +451,24 @@ describe('AppModule federation gating', (): void => {
|
||||
);
|
||||
|
||||
it(
|
||||
'attributes an invalid monorepo-root dotenv tier to the default',
|
||||
'rejects an invalid explicit monorepo-root dotenv tier with a typed startup refusal',
|
||||
async (): Promise<void> => {
|
||||
const graph = await loadModuleGraphFromDotenv({
|
||||
const failure = await loadModuleGraphFromDotenv({
|
||||
rootEnvContents: 'MOSAIC_STORAGE_TIER=invalid\n',
|
||||
expectedProcessTier: 'invalid',
|
||||
});
|
||||
}).then(
|
||||
(): undefined => undefined,
|
||||
(error: unknown): unknown => error,
|
||||
);
|
||||
|
||||
expect(graph.imports).not.toContain(graph.federationModule);
|
||||
expectBootLogLine(graph.bootLogLines, 'local', 'default');
|
||||
expect(failure).toBeInstanceOf(Error);
|
||||
expect(failure).toMatchObject({
|
||||
name: 'MosaicConfigEnvironmentError',
|
||||
code: 'invalid_storage_tier',
|
||||
});
|
||||
expect((failure as Error).message).toBe(
|
||||
'Invalid MOSAIC_STORAGE_TIER; expected "local", "standalone", or "federated".',
|
||||
);
|
||||
},
|
||||
MODULE_IMPORT_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveChatRuntimeMode } from './chat-runtime.js';
|
||||
|
||||
interface TypedSelectionFailure {
|
||||
readonly name: string;
|
||||
readonly code: string;
|
||||
}
|
||||
|
||||
describe('#1182 fail closed — a wrong answer must not be read as no answer', () => {
|
||||
it('FL-07 rejects an invalid explicit CHAT_HARNESS_RUNTIME before embedded construction', () => {
|
||||
let embeddedConstructionCount = 0;
|
||||
let failure: unknown;
|
||||
|
||||
try {
|
||||
const mode = resolveChatRuntimeMode({ CHAT_HARNESS_RUNTIME: 'pi-rpc-typo' });
|
||||
if (mode === 'legacy') {
|
||||
embeddedConstructionCount += 1;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
failure = error;
|
||||
}
|
||||
|
||||
expect
|
||||
.soft(failure, 'invalid explicit runtime must produce a typed selection failure')
|
||||
.toMatchObject({
|
||||
name: 'ChatRuntimeConfigurationError',
|
||||
code: 'invalid_chat_harness_runtime',
|
||||
} satisfies TypedSelectionFailure);
|
||||
expect(
|
||||
embeddedConstructionCount,
|
||||
'invalid explicit runtime must fail before the embedded runtime is constructed',
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it.each([{}, { CHAT_HARNESS_RUNTIME: '' }])(
|
||||
'preserves the documented transitional legacy default for true absence: %j',
|
||||
(env) => {
|
||||
expect(resolveChatRuntimeMode(env)).toBe('legacy');
|
||||
},
|
||||
);
|
||||
|
||||
it('anti-drift: runtime selection has no invalid-enum-to-legacy catch-all', () => {
|
||||
const source = readFileSync(new URL('./chat-runtime.ts', import.meta.url), 'utf8');
|
||||
|
||||
expect(
|
||||
source.includes("env['CHAT_HARNESS_RUNTIME'] === 'pi-rpc' ? 'pi-rpc' : 'legacy'"),
|
||||
'closed runtime enums must distinguish invalid explicit input from absence',
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -41,14 +41,28 @@ export class ChatRuntimeUnavailableError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/** Typed startup refusal for an invalid explicit chat-runtime selection. */
|
||||
export class ChatRuntimeConfigurationError extends Error {
|
||||
readonly code = 'invalid_chat_harness_runtime' as const;
|
||||
|
||||
constructor() {
|
||||
super('Invalid CHAT_HARNESS_RUNTIME; expected "legacy" or "pi-rpc".');
|
||||
this.name = 'ChatRuntimeConfigurationError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the process-wide chat runtime mode from the environment. Anything other
|
||||
* than the exact opt-in token `pi-rpc` keeps the legacy embedded runtime.
|
||||
* Resolves the process-wide chat runtime mode from the environment. Only true
|
||||
* absence (unset or empty) retains the documented transitional legacy default;
|
||||
* any other explicit value must be a member of the closed runtime enum.
|
||||
*/
|
||||
export function resolveChatRuntimeMode(
|
||||
env: Record<string, string | undefined> = process.env,
|
||||
): ChatRuntimeMode {
|
||||
return env['CHAT_HARNESS_RUNTIME'] === 'pi-rpc' ? 'pi-rpc' : 'legacy';
|
||||
const runtime = env['CHAT_HARNESS_RUNTIME'];
|
||||
if (runtime === undefined || runtime === '') return 'legacy';
|
||||
if (runtime === 'legacy' || runtime === 'pi-rpc') return runtime;
|
||||
throw new ChatRuntimeConfigurationError();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user