Files
OmniRoute/tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts
Diego Rodrigues de Sa e Souza 5ab1e9fe5c feat(video): redact transcript text from logs and durable memory (#12150 P1) (#12427)
P1 of #12150: transcript text no longer reaches call logs or durable memory in the clear.

Reconciled on merge — the two persisted-requestBody assertions were failing, and the failure signature was misleading enough to be worth recording. They reported "expected: true, actual: false", which reads like the redaction not applying. It was not: pollForCallLog waited at most 120 tries x 20ms = 2.4s for the asynchronous SQLite write, then returned null, so assert.ok(row) failed before any redaction assertion ran. The observed durations were 5253ms and 4467ms against that 2.4s ceiling — a starved runner, not a leak. The control test failing alongside the positive one was the tell: a real redaction defect would break one direction, not both.

Replaced the fixed try count with a 30s wall-clock deadline: far past any healthy write, still bounded, and a fast machine still returns on the first pass. A privacy test should not depend on how busy the box is.

Verified: 4/4 three consecutive times under synthetic load, and 3/3 unloaded beforehand. Note the synthetic load reached ~7, below the ~38 where the original failure appeared, but the budget is now 12.5x larger and deadline-based rather than count-based.
2026-09-02 10:19:56 -03:00

150 lines
5.1 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { VideoBridgeGuardrail } from "../../../src/lib/guardrails/videoBridge.ts";
import type { BridgeCacheEntry } from "../../../src/lib/guardrails/modalityBridge/bridgeCache.ts";
// FU-05 (#11652): "the normalized transcript contract must be part of cache
// identity ... cache hits cannot cross transcript identity or provenance
// changes." These tests exercise the third named seam alongside
// normalizeVideoTranscript/describeVideoPart: the guardrail's result-cache
// key, which already folds in a fingerprint of the raw transcript/
// audioTranscript payload (videoBridge.ts::buildVideoResultCacheKey).
function payloadWithTranscript(transcript: unknown): Record<string, unknown> {
return {
model: "example/text-only",
messages: [
{
role: "user",
content: [
{
type: "input_video",
video_url: "data:video/mp4;base64,VFJBTlNDUklQVC1DQUNIRQ==",
transcript,
},
],
},
],
};
}
function makeBridge(onDescribe: () => Promise<{ description: string; durationSeconds: number; framesRequested: number; framesUsed: number }>) {
return new VideoBridgeGuardrail({
deps: {
getSettings: async () => ({
modalityBridgeCacheEnabled: true,
modalityBridgeCacheMaxEntries: 11,
modalityBridgeCacheTtlMinutes: 5,
modalityBridgeVideoEnabled: true,
modalityBridgeVideoModel: "openai/gpt-4o-mini",
modalityBridgeVisionPrompt: "FU-05 transcript cache identity",
}),
getCapabilities: () => ({ supportsVideo: false }),
selectVisionModel: async () => "openai/gpt-4o-mini",
describePart: onDescribe,
},
});
}
test("a cache hit does not cross a transcript provenance change on an otherwise identical video", async () => {
let describeCalls = 0;
const bridge = makeBridge(async () => {
describeCalls += 1;
return {
description: "[Video description: cache identity probe]",
durationSeconds: 5,
framesRequested: 1,
framesUsed: 1,
};
});
const clientDeclared = {
cues: [{ text: "same words", start: 1, end: 2, source: "client" }],
};
const audioBridgeDeclared = {
cues: [{ text: "same words", start: 1, end: 2, source: "audio-bridge" }],
};
await bridge.preCall(payloadWithTranscript(clientDeclared), {});
await bridge.preCall(payloadWithTranscript(clientDeclared), {});
await bridge.preCall(payloadWithTranscript(audioBridgeDeclared), {});
assert.equal(
describeCalls,
2,
"the repeated identical (video, transcript) pair must reuse the cached result, " +
"but the raw provenance change must force a miss even though both sources are " +
"reclassified to client after normalization"
);
});
test("a cache hit does not cross a transcript identity change (different cues, same video)", async () => {
let describeCalls = 0;
const bridge = makeBridge(async () => {
describeCalls += 1;
return {
description: "[Video description: cache identity probe]",
durationSeconds: 5,
framesRequested: 1,
framesUsed: 1,
};
});
await bridge.preCall(
payloadWithTranscript({ cues: [{ text: "first cue", start: 1, end: 2, source: "client" }] }),
{}
);
await bridge.preCall(
payloadWithTranscript({ cues: [{ text: "second cue", start: 1, end: 2, source: "client" }] }),
{}
);
assert.equal(describeCalls, 2, "different transcript content must never share a cache entry");
});
test("the result-cache contract version is bumped to v6 for the descriptionRedacted cache-metadata addition (#12150)", async () => {
let storedMetadata: Record<string, unknown> | undefined;
const bridge = new VideoBridgeGuardrail({
deps: {
getSettings: async () => ({
modalityBridgeCacheEnabled: true,
modalityBridgeVideoEnabled: true,
modalityBridgeVideoModel: "openai/gpt-4o-mini",
modalityBridgeVisionPrompt: "FU-05 cache version",
}),
getCapabilities: () => ({ supportsVideo: false }),
selectVisionModel: async () => "openai/gpt-4o-mini",
resultCache: {
delete: () => undefined,
getEntry: () => undefined,
setEntry: (_key: string, entry: BridgeCacheEntry) => {
storedMetadata = entry.metadata as Record<string, unknown>;
},
},
describePart: async () => ({
description: "[Video description: version probe]",
durationSeconds: 1,
framesRequested: 1,
framesUsed: 1,
}),
},
});
await bridge.preCall(
payloadWithTranscript({ cues: [{ text: "cue", start: 0, end: 1, source: "client" }] }),
{}
);
assert.ok(storedMetadata);
// #12150 P1a: VideoResultCacheMetadata gained `descriptionRedacted`, so the
// contract version must be exactly "v6" — not merely "not the pre-FU-05
// v4" — or a cache entry written before that field existed (v5 or older)
// could be served post-diff with `descriptionRedacted` silently undefined.
assert.equal(
storedMetadata?.cacheVersion,
"v6",
"a cache entry computed under an older contract (pre-FU-05 v4, or pre-#12150 v5) must never match"
);
});