fix(#1392 followup): review-285 N1+N2 — env has no config authority in schema-check; verification throws are fatal at install
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
N1: resolveSchemaCheckConfigPath drops the MOSAIC_CONFIG env candidate entirely (mirrors apps/gateway/src/env.ts's deliberate env-has-no-config-authority stance; a stale env var could verify a database the daemon never reads). verify.ts now passes undefined rather than the env var. New spec: a decoy MOSAIC_CONFIG must never win over the daemon-written config. N2: install treats a THROWN post-install verification as fatal (exit 1 with remediation pointer) in addition to the existing result-object hard-fail — an install must never report success over an unverified database, whichever way the check failed.
This commit is contained in:
@@ -95,11 +95,17 @@ export async function runInstall(opts: InstallOpts): Promise<void> {
|
|||||||
// fatal (#1392): an install that reports success over an empty/partial
|
// fatal (#1392): an install that reports success over an empty/partial
|
||||||
// database is the exact T63 failure this command must never reproduce.
|
// database is the exact T63 failure this command must never reproduce.
|
||||||
let verifyResult: VerifyResult | undefined;
|
let verifyResult: VerifyResult | undefined;
|
||||||
|
let verificationThrew = false;
|
||||||
try {
|
try {
|
||||||
const { runPostInstallVerification } = await import('./verify.js');
|
const { runPostInstallVerification } = await import('./verify.js');
|
||||||
verifyResult = await runPostInstallVerification(configResult.host, configResult.port);
|
verifyResult = await runPostInstallVerification(configResult.host, configResult.port);
|
||||||
} catch {
|
} catch (err) {
|
||||||
// Non-fatal — verification is a courtesy
|
// Health/token/bootstrap courtesy failures are non-fatal, but a THROWN
|
||||||
|
// schema verification must not let install report success either (N2,
|
||||||
|
// rev-code-02 review 285): mark it and treat as fatal below.
|
||||||
|
verificationThrew = true;
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
prompter.warn(`Post-install verification errored: ${msg}`);
|
||||||
}
|
}
|
||||||
if (verifyResult && verifyResult.schemaMigrated === false) {
|
if (verifyResult && verifyResult.schemaMigrated === false) {
|
||||||
prompter.warn(
|
prompter.warn(
|
||||||
@@ -107,6 +113,12 @@ export async function runInstall(opts: InstallOpts): Promise<void> {
|
|||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
if (verificationThrew) {
|
||||||
|
prompter.warn(
|
||||||
|
'Gateway install ABORTED: post-install verification errored (see above); refusing to report success on an unverified database.',
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Stages normally return structured results for expected failures.
|
// Stages normally return structured results for expected failures.
|
||||||
// Anything that reaches here is an unexpected runtime error — render a
|
// Anything that reaches here is an unexpected runtime error — render a
|
||||||
|
|||||||
@@ -153,4 +153,29 @@ describe('resolveSchemaCheckConfigPath', () => {
|
|||||||
if (prevHome !== undefined) vi.stubEnv('HOME', prevHome);
|
if (prevHome !== undefined) vi.stubEnv('HOME', prevHome);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('gives MOSAIC_CONFIG NO authority (N1, review 285): env never overrides file resolution', async () => {
|
||||||
|
const { resolveSchemaCheckConfigPath } = await import('./schema-check.js');
|
||||||
|
const daemonDir = mkdtempSync(join(tmpdir(), 'schema-check-env-'));
|
||||||
|
tmpDirs.push(daemonDir);
|
||||||
|
const daemonHome = join(daemonDir, '.config', 'mosaic', 'gateway');
|
||||||
|
mkdirSync(daemonHome, { recursive: true });
|
||||||
|
writeFileSync(join(daemonHome, 'mosaic.config.json'), JSON.stringify(LOCAL_CFG));
|
||||||
|
// A stale env var pointing at a DIFFERENT file must be ignored entirely:
|
||||||
|
const decoyDir = mkdtempSync(join(tmpdir(), 'schema-check-decoy-'));
|
||||||
|
tmpDirs.push(decoyDir);
|
||||||
|
const decoyPath = join(decoyDir, 'mosaic.config.json');
|
||||||
|
writeFileSync(decoyPath, JSON.stringify(STANDALONE_CFG));
|
||||||
|
|
||||||
|
const prevHome = process.env['HOME'];
|
||||||
|
vi.stubEnv('HOME', daemonDir);
|
||||||
|
vi.stubEnv('MOSAIC_CONFIG', decoyPath);
|
||||||
|
try {
|
||||||
|
const resolved = resolveSchemaCheckConfigPath();
|
||||||
|
expect(resolved).toBe(join(daemonHome, 'mosaic.config.json'));
|
||||||
|
expect(resolved).not.toBe(decoyPath);
|
||||||
|
} finally {
|
||||||
|
if (prevHome !== undefined) vi.stubEnv('HOME', prevHome);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -53,8 +53,11 @@ export const SCHEMA_FAIL_REMEDIATION = [
|
|||||||
*/
|
*/
|
||||||
export function resolveSchemaCheckConfigPath(explicit?: string): string | undefined {
|
export function resolveSchemaCheckConfigPath(explicit?: string): string | undefined {
|
||||||
if (explicit) return resolve(explicit);
|
if (explicit) return resolve(explicit);
|
||||||
|
// NOTE: no env-var candidate, deliberately. apps/gateway/src/env.ts gives env
|
||||||
|
// NO config authority (a stale MOSAIC_CONFIG could verify a database the
|
||||||
|
// daemon never reads — rev-code-02 review 285, note N1). Resolution order
|
||||||
|
// mirrors the daemon's file priorities only.
|
||||||
const candidates = [
|
const candidates = [
|
||||||
process.env['MOSAIC_CONFIG'],
|
|
||||||
join(homedir(), '.config', 'mosaic', 'gateway', 'mosaic.config.json'), // daemon-written
|
join(homedir(), '.config', 'mosaic', 'gateway', 'mosaic.config.json'), // daemon-written
|
||||||
resolve(process.cwd(), 'mosaic.config.json'),
|
resolve(process.cwd(), 'mosaic.config.json'),
|
||||||
join(homedir(), '.mosaic', 'mosaic.config.json'),
|
join(homedir(), '.mosaic', 'mosaic.config.json'),
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export async function runPostInstallVerification(
|
|||||||
const { runMigrations, getMigrationStatus } = await import('@mosaicstack/db');
|
const { runMigrations, getMigrationStatus } = await import('@mosaicstack/db');
|
||||||
const result = await checkDatabaseSchema(
|
const result = await checkDatabaseSchema(
|
||||||
{ runMigrations, getMigrationStatus },
|
{ runMigrations, getMigrationStatus },
|
||||||
process.env['MOSAIC_CONFIG'],
|
undefined, // resolver mirrors daemon file priorities; env has no config authority (N1)
|
||||||
);
|
);
|
||||||
if (result.status === 'ok') {
|
if (result.status === 'ok') {
|
||||||
ok(result.detail);
|
ok(result.detail);
|
||||||
|
|||||||
Reference in New Issue
Block a user