55acc3c187
Task 8.3: checkMeetingConsent requires unanimous attendee GRANTED consent + CMP non-DENY before any recording artifact. generateTranscript → BLOCKED (no segments) unless the gate passes, else READY + speaker segments. summarize re-checks consent live (revoke blocks re-summary), reuses P7 AiJobService over a synthetic transcript interaction → IiosMeetingSummary(aiArtifactId) + action items + per-attendee MEETING_FOLLOWUP inbox items, excluding visibility=NONE. 6 tests cover the spine. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
31 lines
1.2 KiB
TypeScript
31 lines
1.2 KiB
TypeScript
import type { IiosPlatformPorts } from '@insignia/iios-contracts';
|
|
import type { IiosMeetingParticipant } from '@prisma/client';
|
|
|
|
export interface ConsentVerdict {
|
|
allowed: boolean;
|
|
reason?: string;
|
|
receiptRef?: string;
|
|
}
|
|
|
|
/**
|
|
* The P8 safety spine (Critics KG-14 / Scenario 6). A recording artifact
|
|
* (transcript / summary / action items) may exist ONLY when **every** participant
|
|
* has GRANTED recording consent AND the CMP purpose check does not DENY. Anything
|
|
* else fails closed. This is deliberately unanimous: one non-consenting attendee
|
|
* blocks the whole recording.
|
|
*/
|
|
export async function checkMeetingConsent(
|
|
ports: IiosPlatformPorts,
|
|
meetingId: string,
|
|
participants: Pick<IiosMeetingParticipant, 'recordingConsent'>[],
|
|
): Promise<ConsentVerdict> {
|
|
const cmp = await ports.cmp.checkPurpose({ purpose: 'meeting_recording', meetingId });
|
|
if (cmp.status === 'DENY') return { allowed: false, reason: 'CMP_DENIED' };
|
|
|
|
const nonConsenting = participants.filter((p) => p.recordingConsent !== 'GRANTED');
|
|
if (nonConsenting.length > 0) {
|
|
return { allowed: false, reason: 'ATTENDEE_CONSENT_MISSING' };
|
|
}
|
|
return { allowed: true, receiptRef: cmp.receiptRef };
|
|
}
|