mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
fix(cli): atomic config writes, gated trial warning, and write-failure signal
Five findings from a fifth (final scoped) max-effort review of the previous commit, all local: 1. writeConfig now writes atomically (pid-suffixed temp file + renameSync — rename within one directory is atomic on POSIX). This closes the real hazard behind the review's torn-read finding: readConfig's corrupted-file catch RESETS the config to defaults (telemetry re-enabled, anonymousId rotated, trial fields wiped), so a concurrent reader catching a non-atomic write mid-flight would silently destroy the user's config — and the previous commit's per-render readConfigFresh() at the arm site multiplied exposure to exactly that window. Verified against a real filesystem, not just the mocked unit tests. 2. writeConfig now returns whether the write landed (errors still swallowed — telemetry must never break the CLI). persistDeParallelRouterTrialFired uses it to stop immediately on a genuine fs failure (retrying an unwritable file is pointless) and reserve its retries for actual concurrent clobbers, instead of 3 blind write attempts + 4 disk reads. 3. The persistence-failure console.warn is now !quiet-gated like every other trial message — a quiet/batch-json render on an unwritable ~/.hyperframes no longer emits unexpected stderr that CI wrappers asserting empty stderr would misread as a render failure. The in-process latch already guarantees the safety behavior whether or not the warning prints. 4. The arm site short-circuits on the in-process fired latch BEFORE the fresh config read — post-fired batch rows no longer pay a per-row config read + parse + shared-cache invalidation for an answer module state already knows. 5. Replaced the new `as T` assertions in render.test.ts's config-state factory with an explicitly typed vi.hoisted return (repo TypeScript convention: no `as T`). config.test.ts: node:fs mock gains renameSync (faithful to the new atomic write); new test covers the success/failure return and asserts no temp file survives a write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2542e94277
commit
6172d79dc2
@@ -10,26 +10,30 @@ const producerState = vi.hoisted(() => ({
|
||||
executeImpl: async (_job: Record<string, unknown>): Promise<void> => undefined,
|
||||
}));
|
||||
|
||||
const configState = vi.hoisted(() => ({
|
||||
// Defaults to "trial already fired" so the pre-existing renderLocal tests
|
||||
// below (which predate the DE-parallel-router trial and don't expect
|
||||
// HF_DE_PARALLEL_ROUTER to be touched) keep their exact prior behavior.
|
||||
//
|
||||
// `disk` is the authoritative "file"; `cache` models config.ts's real
|
||||
// process-lifetime cachedConfig. Modeling them SEPARATELY matters: a mock
|
||||
// where readConfig/readConfigFresh both read one live object hides
|
||||
// exactly the class of bug where production code reads the stale cache
|
||||
// when it needed a fresh disk read (review finding).
|
||||
disk: {
|
||||
telemetryEnabled: true,
|
||||
deParallelRouterTrialFired: true,
|
||||
} as Record<string, unknown>,
|
||||
cache: null as Record<string, unknown> | null,
|
||||
writeConfigCalls: [] as Array<Record<string, unknown>>,
|
||||
// Simulate the real writeConfig's silent fs-error swallowing (unwritable
|
||||
// ~/.hyperframes): the next N writes are recorded but never reach `disk`.
|
||||
failWrites: 0,
|
||||
}));
|
||||
// Defaults to "trial already fired" so the pre-existing renderLocal tests
|
||||
// below (which predate the DE-parallel-router trial and don't expect
|
||||
// HF_DE_PARALLEL_ROUTER to be touched) keep their exact prior behavior.
|
||||
//
|
||||
// `disk` is the authoritative "file"; `cache` models config.ts's real
|
||||
// process-lifetime cachedConfig. Modeling them SEPARATELY matters: a mock
|
||||
// where readConfig/readConfigFresh both read one live object hides exactly
|
||||
// the class of bug where production code reads the stale cache when it
|
||||
// needed a fresh disk read (review finding). `failWrites` simulates the
|
||||
// real writeConfig's silent fs-error swallowing (unwritable ~/.hyperframes):
|
||||
// the next N writes are recorded but never reach `disk`.
|
||||
const configState = vi.hoisted(
|
||||
(): {
|
||||
disk: Record<string, unknown>;
|
||||
cache: Record<string, unknown> | null;
|
||||
writeConfigCalls: Array<Record<string, unknown>>;
|
||||
failWrites: number;
|
||||
} => ({
|
||||
disk: { telemetryEnabled: true, deParallelRouterTrialFired: true },
|
||||
cache: null,
|
||||
writeConfigCalls: [],
|
||||
failWrites: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
const trackingState = vi.hoisted(() => ({
|
||||
// maybeEnableDeParallelRouterTrial gates on the real shouldTrack(), which
|
||||
@@ -91,10 +95,11 @@ vi.mock("../telemetry/config.js", () => ({
|
||||
configState.writeConfigCalls.push({ ...config });
|
||||
if (configState.failWrites > 0) {
|
||||
configState.failWrites--;
|
||||
return; // swallowed silently, like the real writeConfig's catch {}
|
||||
return false; // swallowed silently, like the real writeConfig's catch {}
|
||||
}
|
||||
configState.disk = { ...config };
|
||||
configState.cache = { ...config };
|
||||
return true;
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user