diff --git a/src/config/io.observe-recovery.ts b/src/config/io.observe-recovery.ts index a8521095005..040673e8948 100644 --- a/src/config/io.observe-recovery.ts +++ b/src/config/io.observe-recovery.ts @@ -12,6 +12,7 @@ import { persistBoundedClobberedConfigSnapshot, persistBoundedClobberedConfigSnapshotSync, } from "./io.clobber-snapshot.js"; +import { resolveConfigObserveSuspiciousReasons } from "./io.observe-suspicious.js"; import { formatConfigIssueSummary } from "./issue-format.js"; import { resolveStateDir } from "./paths.js"; import { @@ -510,49 +511,6 @@ function createBackupRestoreAuditAppendParams(params: { }); } -function isUpdateChannelOnlyRoot(value: unknown): boolean { - if (!isRecord(value)) { - return false; - } - const keys = Object.keys(value); - if (keys.length !== 1 || keys[0] !== "update") { - return false; - } - const update = value.update; - if (!isRecord(update)) { - return false; - } - const updateKeys = Object.keys(update); - return updateKeys.length === 1 && typeof update.channel === "string"; -} - -function resolveConfigObserveSuspiciousReasons(params: { - bytes: number; - hasMeta: boolean; - gatewayMode: string | null; - parsed: unknown; - lastKnownGood?: ConfigHealthFingerprint; -}): string[] { - const reasons: string[] = []; - const baseline = params.lastKnownGood; - if (!baseline) { - return reasons; - } - if (baseline.bytes >= 512 && params.bytes < Math.floor(baseline.bytes * 0.5)) { - reasons.push(`size-drop-vs-last-good:${baseline.bytes}->${params.bytes}`); - } - if (baseline.hasMeta && !params.hasMeta) { - reasons.push("missing-meta-vs-last-good"); - } - if (baseline.gatewayMode && !params.gatewayMode) { - reasons.push("gateway-mode-missing-vs-last-good"); - } - if (baseline.gatewayMode && isUpdateChannelOnlyRoot(params.parsed)) { - reasons.push("update-channel-only-root"); - } - return reasons; -} - function resolveSuspiciousSignature( current: ConfigHealthFingerprint, suspicious: string[], diff --git a/src/config/io.observe-suspicious.ts b/src/config/io.observe-suspicious.ts new file mode 100644 index 00000000000..d8f2edd11c6 --- /dev/null +++ b/src/config/io.observe-suspicious.ts @@ -0,0 +1,50 @@ +import { isRecord } from "../utils.js"; + +export type ConfigObserveSuspiciousBaseline = { + bytes: number; + hasMeta: boolean; + gatewayMode: string | null; +}; + +function isUpdateChannelOnlyRoot(value: unknown): boolean { + if (!isRecord(value)) { + return false; + } + const keys = Object.keys(value); + if (keys.length !== 1 || keys[0] !== "update") { + return false; + } + const update = value.update; + if (!isRecord(update)) { + return false; + } + const updateKeys = Object.keys(update); + return updateKeys.length === 1 && typeof update.channel === "string"; +} + +export function resolveConfigObserveSuspiciousReasons(params: { + bytes: number; + hasMeta: boolean; + gatewayMode: string | null; + parsed: unknown; + lastKnownGood?: ConfigObserveSuspiciousBaseline; +}): string[] { + const reasons: string[] = []; + const baseline = params.lastKnownGood; + if (!baseline) { + return reasons; + } + if (baseline.bytes >= 512 && params.bytes < Math.floor(baseline.bytes * 0.5)) { + reasons.push(`size-drop-vs-last-good:${baseline.bytes}->${params.bytes}`); + } + if (baseline.hasMeta && !params.hasMeta) { + reasons.push("missing-meta-vs-last-good"); + } + if (baseline.gatewayMode && !params.gatewayMode) { + reasons.push("gateway-mode-missing-vs-last-good"); + } + if (baseline.gatewayMode && isUpdateChannelOnlyRoot(params.parsed)) { + reasons.push("update-channel-only-root"); + } + return reasons; +} diff --git a/src/config/io.ts b/src/config/io.ts index 2a516018856..7c22500e954 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -70,6 +70,7 @@ import { promoteConfigSnapshotToLastKnownGood as promoteConfigSnapshotToLastKnownGoodWithDeps, recoverConfigFromLastKnownGood as recoverConfigFromLastKnownGoodWithDeps, } from "./io.observe-recovery.js"; +import { resolveConfigObserveSuspiciousReasons } from "./io.observe-suspicious.js"; import { retainGeneratedOwnerDisplaySecret } from "./io.owner-display-secret.js"; import { collectChangedPaths, @@ -638,49 +639,6 @@ function setConfigHealthEntry( }; } -function isUpdateChannelOnlyRoot(value: unknown): boolean { - if (!isRecord(value)) { - return false; - } - const keys = Object.keys(value); - if (keys.length !== 1 || keys[0] !== "update") { - return false; - } - const update = value.update; - if (!isRecord(update)) { - return false; - } - const updateKeys = Object.keys(update); - return updateKeys.length === 1 && typeof update.channel === "string"; -} - -function resolveConfigObserveSuspiciousReasons(params: { - bytes: number; - hasMeta: boolean; - gatewayMode: string | null; - parsed: unknown; - lastKnownGood?: ConfigHealthFingerprint; -}): string[] { - const reasons: string[] = []; - const baseline = params.lastKnownGood; - if (!baseline) { - return reasons; - } - if (baseline.bytes >= 512 && params.bytes < Math.floor(baseline.bytes * 0.5)) { - reasons.push(`size-drop-vs-last-good:${baseline.bytes}->${params.bytes}`); - } - if (baseline.hasMeta && !params.hasMeta) { - reasons.push("missing-meta-vs-last-good"); - } - if (baseline.gatewayMode && !params.gatewayMode) { - reasons.push("gateway-mode-missing-vs-last-good"); - } - if (baseline.gatewayMode && isUpdateChannelOnlyRoot(params.parsed)) { - reasons.push("update-channel-only-root"); - } - return reasons; -} - async function readConfigFingerprintForPath( deps: Required, targetPath: string,