Skip to content

Commit 83801c4

Browse files
committed
refactor: share config observe recovery helpers
1 parent 812f96c commit 83801c4

1 file changed

Lines changed: 109 additions & 70 deletions

File tree

src/config/io.observe-recovery.ts

Lines changed: 109 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,17 @@ function createConfigObserveAuditAppendParams(
196196
};
197197
}
198198

199+
function createConfigObserveAnomalyAuditAppendParams(
200+
deps: ObserveRecoveryDeps,
201+
params: Omit<ConfigObserveAuditRecordParams, "restoredFromBackup" | "restoredBackupPath">,
202+
) {
203+
return createConfigObserveAuditAppendParams(deps, {
204+
...params,
205+
restoredFromBackup: false,
206+
restoredBackupPath: null,
207+
});
208+
}
209+
199210
function hashConfigRaw(raw: string | null): string {
200211
return crypto
201212
.createHash("sha256")
@@ -283,6 +294,14 @@ function createConfigHealthFingerprint(params: {
283294
};
284295
}
285296

297+
function parseConfigRawOrEmpty(deps: ObserveRecoveryDeps, raw: string): unknown {
298+
try {
299+
return deps.json5.parse(raw);
300+
} catch {
301+
return {};
302+
}
303+
}
304+
286305
function resolveConfigHealthStatePath(env: NodeJS.ProcessEnv, homedir: () => string): string {
287306
return path.join(resolveStateDir(env, homedir), "logs", "config-health.json");
288307
}
@@ -358,6 +377,16 @@ function setConfigHealthEntry(
358377
};
359378
}
360379

380+
function createLastObservedSuspiciousEntry(
381+
entry: ConfigHealthEntry,
382+
suspiciousSignature: string,
383+
): ConfigHealthEntry {
384+
return {
385+
...entry,
386+
lastObservedSuspiciousSignature: suspiciousSignature,
387+
};
388+
}
389+
361390
function isUpdateChannelOnlyRoot(value: unknown): boolean {
362391
if (!isRecord(value)) {
363392
return false;
@@ -401,27 +430,52 @@ function resolveConfigObserveSuspiciousReasons(params: {
401430
return reasons;
402431
}
403432

433+
function resolveSuspiciousSignature(
434+
current: ConfigHealthFingerprint,
435+
suspicious: string[],
436+
): string {
437+
return `${current.hash}:${suspicious.join(",")}`;
438+
}
439+
440+
function resolveConfigReadRecoveryContext(params: {
441+
current: ConfigHealthFingerprint;
442+
parsed: unknown;
443+
entry: ConfigHealthEntry;
444+
backupBaseline?: ConfigHealthFingerprint;
445+
}): { suspicious: string[]; suspiciousSignature: string } | null {
446+
const suspicious = resolveConfigObserveSuspiciousReasons({
447+
bytes: params.current.bytes,
448+
hasMeta: params.current.hasMeta,
449+
gatewayMode: params.current.gatewayMode,
450+
parsed: params.parsed,
451+
lastKnownGood: params.backupBaseline,
452+
});
453+
if (!suspicious.includes("update-channel-only-root")) {
454+
return null;
455+
}
456+
const suspiciousSignature = resolveSuspiciousSignature(params.current, suspicious);
457+
if (params.entry.lastObservedSuspiciousSignature === suspiciousSignature) {
458+
return null;
459+
}
460+
return { suspicious, suspiciousSignature };
461+
}
462+
404463
async function readConfigFingerprintForPath(
405464
deps: ObserveRecoveryDeps,
406465
targetPath: string,
407466
): Promise<ConfigHealthFingerprint | null> {
408467
try {
409468
const raw = await deps.fs.promises.readFile(targetPath, "utf-8");
410469
const stat = await deps.fs.promises.stat(targetPath).catch(() => null);
411-
let parsed: unknown = {};
412-
try {
413-
parsed = deps.json5.parse(raw);
414-
} catch {}
415-
return {
470+
const parsed = parseConfigRawOrEmpty(deps, raw);
471+
return createConfigHealthFingerprint({
416472
hash: hashConfigRaw(raw),
417-
bytes: Buffer.byteLength(raw, "utf-8"),
418-
mtimeMs: stat?.mtimeMs ?? null,
419-
ctimeMs: stat?.ctimeMs ?? null,
420-
...resolveConfigStatMetadata(stat as Record<string, unknown> | null),
421-
hasMeta: hasConfigMeta(parsed),
422-
gatewayMode: resolveGatewayMode(parsed),
473+
raw,
474+
parsed,
475+
gatewaySource: parsed,
476+
stat: stat as ConfigStatMetadataSource,
423477
observedAt: new Date().toISOString(),
424-
};
478+
});
425479
} catch {
426480
return null;
427481
}
@@ -434,20 +488,15 @@ function readConfigFingerprintForPathSync(
434488
try {
435489
const raw = deps.fs.readFileSync(targetPath, "utf-8");
436490
const stat = deps.fs.statSync(targetPath, { throwIfNoEntry: false }) ?? null;
437-
let parsed: unknown = {};
438-
try {
439-
parsed = deps.json5.parse(raw);
440-
} catch {}
441-
return {
491+
const parsed = parseConfigRawOrEmpty(deps, raw);
492+
return createConfigHealthFingerprint({
442493
hash: hashConfigRaw(raw),
443-
bytes: Buffer.byteLength(raw, "utf-8"),
444-
mtimeMs: stat?.mtimeMs ?? null,
445-
ctimeMs: stat?.ctimeMs ?? null,
446-
...resolveConfigStatMetadata(stat),
447-
hasMeta: hasConfigMeta(parsed),
448-
gatewayMode: resolveGatewayMode(parsed),
494+
raw,
495+
parsed,
496+
gatewaySource: parsed,
497+
stat,
449498
observedAt: new Date().toISOString(),
450-
};
499+
});
451500
} catch {
452501
return null;
453502
}
@@ -519,21 +568,16 @@ export async function maybeRecoverSuspiciousConfigRead(params: {
519568
entry.lastKnownGood ??
520569
(await readConfigFingerprintForPath(params.deps, backupPath)) ??
521570
undefined;
522-
const suspicious = resolveConfigObserveSuspiciousReasons({
523-
bytes: current.bytes,
524-
hasMeta: current.hasMeta,
525-
gatewayMode: current.gatewayMode,
571+
const recoveryContext = resolveConfigReadRecoveryContext({
572+
current,
526573
parsed: params.parsed,
527-
lastKnownGood: backupBaseline,
574+
entry,
575+
backupBaseline,
528576
});
529-
if (!suspicious.includes("update-channel-only-root")) {
530-
return { raw: params.raw, parsed: params.parsed };
531-
}
532-
533-
const suspiciousSignature = `${current.hash}:${suspicious.join(",")}`;
534-
if (entry.lastObservedSuspiciousSignature === suspiciousSignature) {
577+
if (!recoveryContext) {
535578
return { raw: params.raw, parsed: params.parsed };
536579
}
580+
const { suspicious, suspiciousSignature } = recoveryContext;
537581

538582
const backupRaw = await params.deps.fs.promises.readFile(backupPath, "utf-8").catch(() => null);
539583
if (!backupRaw) {
@@ -581,10 +625,11 @@ export async function maybeRecoverSuspiciousConfigRead(params: {
581625
}),
582626
);
583627

584-
healthState = setConfigHealthEntry(healthState, params.configPath, {
585-
...entry,
586-
lastObservedSuspiciousSignature: suspiciousSignature,
587-
});
628+
healthState = setConfigHealthEntry(
629+
healthState,
630+
params.configPath,
631+
createLastObservedSuspiciousEntry(entry, suspiciousSignature),
632+
);
588633
await writeConfigHealthState(params.deps, healthState);
589634
return { raw: backupRaw, parsed: backupParsed };
590635
}
@@ -611,21 +656,16 @@ export function maybeRecoverSuspiciousConfigReadSync(params: {
611656
const backupPath = `${params.configPath}.bak`;
612657
const backupBaseline =
613658
entry.lastKnownGood ?? readConfigFingerprintForPathSync(params.deps, backupPath) ?? undefined;
614-
const suspicious = resolveConfigObserveSuspiciousReasons({
615-
bytes: current.bytes,
616-
hasMeta: current.hasMeta,
617-
gatewayMode: current.gatewayMode,
659+
const recoveryContext = resolveConfigReadRecoveryContext({
660+
current,
618661
parsed: params.parsed,
619-
lastKnownGood: backupBaseline,
662+
entry,
663+
backupBaseline,
620664
});
621-
if (!suspicious.includes("update-channel-only-root")) {
622-
return { raw: params.raw, parsed: params.parsed };
623-
}
624-
625-
const suspiciousSignature = `${current.hash}:${suspicious.join(",")}`;
626-
if (entry.lastObservedSuspiciousSignature === suspiciousSignature) {
665+
if (!recoveryContext) {
627666
return { raw: params.raw, parsed: params.parsed };
628667
}
668+
const { suspicious, suspiciousSignature } = recoveryContext;
629669

630670
let backupRaw: string;
631671
try {
@@ -675,10 +715,11 @@ export function maybeRecoverSuspiciousConfigReadSync(params: {
675715
}),
676716
);
677717

678-
healthState = setConfigHealthEntry(healthState, params.configPath, {
679-
...entry,
680-
lastObservedSuspiciousSignature: suspiciousSignature,
681-
});
718+
healthState = setConfigHealthEntry(
719+
healthState,
720+
params.configPath,
721+
createLastObservedSuspiciousEntry(entry, suspiciousSignature),
722+
);
682723
writeConfigHealthStateSync(params.deps, healthState);
683724
return { raw: backupRaw, parsed: backupParsed };
684725
}
@@ -744,7 +785,7 @@ export async function observeConfigSnapshot(
744785
return;
745786
}
746787

747-
const suspiciousSignature = `${current.hash}:${suspicious.join(",")}`;
788+
const suspiciousSignature = resolveSuspiciousSignature(current, suspicious);
748789
if (entry.lastObservedSuspiciousSignature === suspiciousSignature) {
749790
return;
750791
}
@@ -761,7 +802,7 @@ export async function observeConfigSnapshot(
761802

762803
deps.logger.warn(`Config observe anomaly: ${snapshot.path} (${suspicious.join(", ")})`);
763804
await appendConfigAuditRecord(
764-
createConfigObserveAuditAppendParams(deps, {
805+
createConfigObserveAnomalyAuditAppendParams(deps, {
765806
ts: now,
766807
configPath: snapshot.path,
767808
valid: snapshot.valid,
@@ -770,15 +811,14 @@ export async function observeConfigSnapshot(
770811
lastKnownGood: entry.lastKnownGood,
771812
backup,
772813
clobberedPath,
773-
restoredFromBackup: false,
774-
restoredBackupPath: null,
775814
}),
776815
);
777816

778-
healthState = setConfigHealthEntry(healthState, snapshot.path, {
779-
...entry,
780-
lastObservedSuspiciousSignature: suspiciousSignature,
781-
});
817+
healthState = setConfigHealthEntry(
818+
healthState,
819+
snapshot.path,
820+
createLastObservedSuspiciousEntry(entry, suspiciousSignature),
821+
);
782822
await writeConfigHealthState(deps, healthState);
783823
}
784824

@@ -826,7 +866,7 @@ export function observeConfigSnapshotSync(
826866
return;
827867
}
828868

829-
const suspiciousSignature = `${current.hash}:${suspicious.join(",")}`;
869+
const suspiciousSignature = resolveSuspiciousSignature(current, suspicious);
830870
if (entry.lastObservedSuspiciousSignature === suspiciousSignature) {
831871
return;
832872
}
@@ -843,7 +883,7 @@ export function observeConfigSnapshotSync(
843883

844884
deps.logger.warn(`Config observe anomaly: ${snapshot.path} (${suspicious.join(", ")})`);
845885
appendConfigAuditRecordSync(
846-
createConfigObserveAuditAppendParams(deps, {
886+
createConfigObserveAnomalyAuditAppendParams(deps, {
847887
ts: now,
848888
configPath: snapshot.path,
849889
valid: snapshot.valid,
@@ -852,14 +892,13 @@ export function observeConfigSnapshotSync(
852892
lastKnownGood: entry.lastKnownGood,
853893
backup,
854894
clobberedPath,
855-
restoredFromBackup: false,
856-
restoredBackupPath: null,
857895
}),
858896
);
859897

860-
healthState = setConfigHealthEntry(healthState, snapshot.path, {
861-
...entry,
862-
lastObservedSuspiciousSignature: suspiciousSignature,
863-
});
898+
healthState = setConfigHealthEntry(
899+
healthState,
900+
snapshot.path,
901+
createLastObservedSuspiciousEntry(entry, suspiciousSignature),
902+
);
864903
writeConfigHealthStateSync(deps, healthState);
865904
}

0 commit comments

Comments
 (0)