Skip to content

Commit 4512f9e

Browse files
committed
fix: matrix baseline override keeps resolved global on invalid merge (#2179)
Codex round-2 finding: resolveVoiceDriftConfig merged the RAW global config with the series override and let resolveCheckConfig collapse the whole blob to bare schema defaults if the merge was invalid — while the finding-emitting applySeriesCheckConfig keeps the resolved global on an invalid override. A bad per-series value (e.g. sigmaThreshold beyond the schema max) would make the matrix use the default 1.5 while findings used the global threshold, re-opening the matrix/finding disagreement. Now resolves the global first, then overlays the override on it, keeping the resolved global on parse failure — byte-for-byte the applySeriesCheckConfig contract. Refs #2179
1 parent d86c3cf commit 4512f9e

2 files changed

Lines changed: 25 additions & 7 deletions

File tree

server/services/pipeline/voiceFingerprint.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,19 @@ export async function resolveVoiceDriftConfig(settings, seriesOverrides) {
5858
const s = settings || await getSettings();
5959
const check = getCheckById(s, VOICE_DRIFT_CHECK_ID);
6060
const stored = readChecksSlice(s)[VOICE_DRIFT_CHECK_ID]?.config;
61+
// Resolve the GLOBAL config first (schema-validated, defaults filled).
62+
const global = resolveCheckConfig(check, stored);
6163
const override = seriesOverrides && typeof seriesOverrides === 'object' && !Array.isArray(seriesOverrides)
6264
? seriesOverrides[VOICE_DRIFT_CHECK_ID]
6365
: null;
64-
// Overlay the per-series override on the global stored config before validating,
65-
// mirroring applySeriesCheckConfig's `{ ...config, ...override }` merge. A
66-
// non-object override is ignored (the spread of a non-object is a no-op).
67-
const merged = (override && typeof override === 'object' && !Array.isArray(override))
68-
? { ...(stored || {}), ...override }
69-
: stored;
70-
return resolveCheckConfig(check, merged);
66+
if (!override || typeof override !== 'object' || Array.isArray(override)) return global;
67+
// Overlay the per-series override on the RESOLVED global, EXACTLY as
68+
// applySeriesCheckConfig does — including its failure mode: if the merged blob
69+
// is invalid (e.g. a hand-edited out-of-range value), keep the resolved global
70+
// rather than collapsing to bare schema defaults, so the matrix view and the
71+
// finding-emitting run stay in lockstep even on a bad override.
72+
const parsed = check.configSchema.safeParse({ ...global, ...override });
73+
return parsed.success ? parsed.data : global;
7174
}
7275

7376
/**

server/services/pipeline/voiceFingerprint.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ describe('resolveVoiceDriftConfig', () => {
8888
const cfg = await resolveVoiceDriftConfig(undefined, { [VOICE_DRIFT_CHECK_ID]: 'nope' });
8989
expect(cfg.baselineMode).toBe('drafted');
9090
});
91+
92+
it('keeps the resolved global config when a per-series override is invalid (#1591 parity)', async () => {
93+
// Mirror applySeriesCheckConfig: an out-of-range override must NOT collapse the
94+
// matrix to bare schema defaults — it keeps the resolved global, so the matrix
95+
// view and the finding-emitting run agree even on a bad override.
96+
settingsStore = {
97+
pipelineEditorialChecks: {
98+
checks: { [VOICE_DRIFT_CHECK_ID]: { config: { sigmaThreshold: 2.5 } } },
99+
},
100+
};
101+
const cfg = await resolveVoiceDriftConfig(undefined, {
102+
[VOICE_DRIFT_CHECK_ID]: { sigmaThreshold: 999999 }, // > schema max(4) → invalid merge
103+
});
104+
expect(cfg.sigmaThreshold).toBe(2.5); // resolved global, NOT the default 1.5
105+
});
91106
});
92107

93108
describe('getVoiceFingerprint', () => {

0 commit comments

Comments
 (0)