prepareSources applies every face, mark, and region protection before the compiler runs and fails closed when one cannot be honored: all face modes, tracking with interpolation and grown holds over lost detections, reframe_out crops with blur fallback, synthetic identity only through a licensed provider (blur fallback otherwise), inpainting with a quality floor that falls back to a cover, and person-bound marks re-found next to the face. The gate runs on the render output: classifier at the profile rate plus cuts, face tracking matched to people locally, pixel verification of every recorded protection, consent and age proof (always for paid video), text and technical checks. Auto-fix proposes only compliance repairs and stops after three rounds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NLSCTnhge4umMnemGXWr1w
98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
assertExportConsent,
|
|
ConsentRequiredError,
|
|
consentIssues,
|
|
consentRequirements,
|
|
enforceConsentFlags,
|
|
participantsOf,
|
|
} from "./media-consent.js";
|
|
import type { PrivacyHarness } from "./media-privacy-fixtures.js";
|
|
import { ACTOR, createPrivacyHarness, testEdl, testProfile } from "./media-privacy-fixtures.js";
|
|
|
|
const now = new Date("2026-06-01T12:00:00Z");
|
|
const past = new Date("2026-01-01T00:00:00Z");
|
|
|
|
let harness: PrivacyHarness | null = null;
|
|
afterEach(async () => {
|
|
await harness?.cleanup();
|
|
harness = null;
|
|
});
|
|
|
|
describe("consent rules", () => {
|
|
it("always requires consent and age proof for paid video", () => {
|
|
expect(consentRequirements(testProfile())).toEqual({ consent: false, age: false });
|
|
expect(consentRequirements(testProfile({ kind: "paid_video" }))).toEqual({
|
|
consent: true,
|
|
age: true,
|
|
});
|
|
expect(enforceConsentFlags(testProfile({ kind: "paid_video" })).consent).toEqual({
|
|
requirePerformerConsent: true,
|
|
requireAgeVerification: true,
|
|
});
|
|
});
|
|
|
|
it("accepts only recorded, past proof with a verification method", () => {
|
|
const paid = testProfile({ kind: "paid_video" });
|
|
const ok = {
|
|
id: "a",
|
|
consentRecordedAt: past,
|
|
ageVerifiedAt: past,
|
|
ageVerificationMethod: "platform_verified",
|
|
};
|
|
expect(consentIssues(paid, [ok], [], now)).toEqual([]);
|
|
const future = { ...ok, consentRecordedAt: new Date("2027-01-01T00:00:00Z") };
|
|
const noMethod = { ...ok, id: "b", ageVerificationMethod: "none" };
|
|
expect(consentIssues(paid, [future, noMethod], [{ atMs: 10 }], now).map((i) => i.code)).toEqual(
|
|
["consent_missing", "age_unverified", "unknown_performer"],
|
|
);
|
|
expect(consentIssues(testProfile(), [noMethod], [{}], now)).toEqual([]);
|
|
});
|
|
|
|
it("collects the people and unassigned faces inside the clip ranges", () => {
|
|
const edl = testEdl([{ id: "c1", assetId: "a1", inMs: 1000, outMs: 2000 }]);
|
|
expect(
|
|
participantsOf(edl, [
|
|
{ assetId: "a1", personId: "alice", startMs: 0, endMs: 1200 },
|
|
{ assetId: "a1", personId: "bob", startMs: 2500, endMs: 3000 },
|
|
{ assetId: "a1", personId: null, startMs: 1500, endMs: 1600 },
|
|
{ assetId: "a2", personId: "carol", startMs: 0, endMs: 5000 },
|
|
]),
|
|
).toEqual({
|
|
personIds: ["alice"],
|
|
unassigned: [{ assetId: "a1", startMs: 1500, endMs: 1600 }],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("export consent", () => {
|
|
it("blocks a paid export until every participant has consent and age proof", async () => {
|
|
harness = await createPrivacyHarness(() => null);
|
|
const alice = harness.addPerson("alice", "none");
|
|
harness.store.tracks.push({
|
|
id: "t1",
|
|
assetId: "a1",
|
|
personId: "alice",
|
|
startMs: 0,
|
|
endMs: 1000,
|
|
confidenceMin: 0.9,
|
|
keyframesPath: "x.json",
|
|
modelVersion: "v1",
|
|
});
|
|
const edl = testEdl([{ id: "c1", assetId: "a1", inMs: 0, outMs: 1000 }]);
|
|
const paid = testProfile({ kind: "paid_video" });
|
|
await expect(assertExportConsent(harness.deps, ACTOR, { edl, profile: paid })).rejects.toThrow(
|
|
ConsentRequiredError,
|
|
);
|
|
await expect(
|
|
assertExportConsent(harness.deps, ACTOR, { edl, profile: testProfile() }),
|
|
).resolves.toBeUndefined();
|
|
alice.consentRecordedAt = past;
|
|
alice.ageVerifiedAt = past;
|
|
alice.ageVerificationMethod = "document_on_file";
|
|
await expect(
|
|
assertExportConsent(harness.deps, ACTOR, { edl, profile: paid }),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
});
|