mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
FU-07/FU-09 promotion-evidence harness (Refs #11656): delivers the manifest schema, deterministic fixture recipes, metrics aggregator, and promotion-verdict evaluator #11656 asks for — deliberately does NOT deliver the promotion verdicts themselves (they require real models against real fixtures on a live host, HOLD with explicit reason instead of any fabricated result). New files only, no collision with sibling PRs.
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
assertNoRawPromotionPayloadLeak,
|
|
buildPersistablePromotionRecord,
|
|
digestPromotionText,
|
|
} from "../../../src/lib/guardrails/videoBridgePromotionDigest.ts";
|
|
|
|
test("digestPromotionText returns the sha256 hex digest of the raw text", () => {
|
|
const raw = "the quick brown fox";
|
|
assert.equal(digestPromotionText(raw), createHash("sha256").update(raw).digest("hex"));
|
|
});
|
|
|
|
test("digestPromotionText is deterministic for identical input", () => {
|
|
assert.equal(digestPromotionText("same input"), digestPromotionText("same input"));
|
|
});
|
|
|
|
test("buildPersistablePromotionRecord reduces a raw model response to metrics + digest only", () => {
|
|
const rawResponseText =
|
|
"The person's full name is Jane Doe and their private phone number is 555-0100.";
|
|
const record = buildPersistablePromotionRecord({
|
|
caseId: "close-events-1",
|
|
metrics: { factRetention: 0.92, latencyMs: 812 },
|
|
model: "vision-model-x",
|
|
rawResponseText,
|
|
});
|
|
|
|
assert.equal(record.responseDigest, digestPromotionText(rawResponseText));
|
|
assert.deepEqual(record.metrics, { factRetention: 0.92, latencyMs: 812 });
|
|
assert.equal(record.caseId, "close-events-1");
|
|
assert.equal(record.model, "vision-model-x");
|
|
|
|
const serialized = JSON.stringify(record);
|
|
assert.ok(!serialized.includes("Jane Doe"), "persisted record must never contain the raw response text");
|
|
assert.ok(!serialized.includes("555-0100"));
|
|
assert.ok(!("rawResponseText" in record), "persisted record must not carry a raw-text field at all");
|
|
});
|
|
|
|
test("buildPersistablePromotionRecord never mutates the metrics object it was given", () => {
|
|
const metrics = { latencyMs: 100 };
|
|
const record = buildPersistablePromotionRecord({
|
|
caseId: "c1",
|
|
metrics,
|
|
model: "m1",
|
|
rawResponseText: "response",
|
|
});
|
|
record.metrics.latencyMs = 999;
|
|
assert.equal(metrics.latencyMs, 100);
|
|
});
|
|
|
|
test("assertNoRawPromotionPayloadLeak passes for a properly digested record", () => {
|
|
const raw = "sensitive raw transcript content";
|
|
const record = buildPersistablePromotionRecord({
|
|
caseId: "c1",
|
|
metrics: {},
|
|
model: "m1",
|
|
rawResponseText: raw,
|
|
});
|
|
assert.doesNotThrow(() => assertNoRawPromotionPayloadLeak(record, raw));
|
|
});
|
|
|
|
test("assertNoRawPromotionPayloadLeak throws if a raw payload is ever smuggled into a persisted record", () => {
|
|
const raw = "sensitive raw transcript content";
|
|
const leakedRecord = {
|
|
caseId: "c1",
|
|
metrics: {},
|
|
model: "m1",
|
|
// Simulates a future refactor accidentally reintroducing the raw text.
|
|
rawResponseText: raw,
|
|
responseDigest: digestPromotionText(raw),
|
|
};
|
|
assert.throws(() => assertNoRawPromotionPayloadLeak(leakedRecord, raw), /raw response/);
|
|
});
|
|
|
|
test("assertNoRawPromotionPayloadLeak is a no-op for an empty raw payload (nothing to leak)", () => {
|
|
const record = buildPersistablePromotionRecord({
|
|
caseId: "c1",
|
|
metrics: {},
|
|
model: "m1",
|
|
rawResponseText: "",
|
|
});
|
|
assert.doesNotThrow(() => assertNoRawPromotionPayloadLeak(record, ""));
|
|
});
|