feat(compliance): add 45 CFR § 164.312 inactivity lockout, break-glass protocol, Fed R Evid 902(11) self-authenticating affidavit, and aai.dev legal dossier
This commit is contained in:
@@ -37,6 +37,7 @@ import { TelehealthRoom } from '@/components/telehealth/TelehealthRoom';
|
||||
import { WaitlistModal } from '@/components/waitlist/WaitlistModal';
|
||||
import { CourtAuditVaultModal } from '@/components/compliance/CourtAuditVaultModal';
|
||||
import { CommandPalette } from '@/components/ui/CommandPalette';
|
||||
import { InactivityLockoutModal } from '@/components/ui/InactivityLockoutModal';
|
||||
import { ClinicalToastContainer, ToastMessage } from '@/components/ui/ClinicalToast';
|
||||
import { clinicalAudio } from '@/lib/clinical-audio';
|
||||
import {
|
||||
@@ -60,6 +61,7 @@ import {
|
||||
VolumeX,
|
||||
Sparkles,
|
||||
Scale,
|
||||
Lock,
|
||||
} from 'lucide-react';
|
||||
|
||||
export default function Home() {
|
||||
@@ -90,6 +92,7 @@ export default function Home() {
|
||||
// Polish state: Command Palette, Toasts & Audio
|
||||
const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
|
||||
const [isCourtVaultOpen, setIsCourtVaultOpen] = useState(false);
|
||||
const [isTerminalLocked, setIsTerminalLocked] = useState(false);
|
||||
const [toasts, setToasts] = useState<ToastMessage[]>([]);
|
||||
const [audioEnabled, setAudioEnabled] = useState(true);
|
||||
|
||||
@@ -360,6 +363,20 @@ export default function Home() {
|
||||
<span className="sm:hidden">Vault</span>
|
||||
</button>
|
||||
|
||||
{/* Quick Terminal Lockout Button (HIPAA 45 CFR § 164.312(a)(2)(iii)) */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
clinicalAudio.playClick();
|
||||
setIsTerminalLocked(true);
|
||||
}}
|
||||
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-slate-100 hover:bg-rose-50 border border-slate-200 hover:border-rose-300 text-slate-700 hover:text-rose-700 text-xs font-bold transition shadow-2xs"
|
||||
title="Lock Clinical Terminal (Cmd+L / 45 CFR § 164.312(a)(2)(iii))"
|
||||
>
|
||||
<Lock className="w-3.5 h-3.5 text-slate-500 hover:text-rose-600" />
|
||||
<span className="hidden md:inline">Lock</span>
|
||||
</button>
|
||||
|
||||
{/* Clinic Dropdown */}
|
||||
<div className="relative">
|
||||
<select
|
||||
@@ -705,6 +722,16 @@ export default function Home() {
|
||||
activeTenant={activeTenant}
|
||||
/>
|
||||
|
||||
{/* HIPAA Inactivity & Break-Glass Lockout Modal (45 CFR § 164.312(a)(2)(iii) & § 164.312(a)(2)(ii)) */}
|
||||
<InactivityLockoutModal
|
||||
isLocked={isTerminalLocked}
|
||||
onUnlock={() => setIsTerminalLocked(false)}
|
||||
onManualLock={() => setIsTerminalLocked(true)}
|
||||
doctorName={activeProvider.name}
|
||||
clinicName={activeTenant.name}
|
||||
inactivityTimeoutMinutes={15}
|
||||
/>
|
||||
|
||||
{/* Global Command Palette (Cmd+K) */}
|
||||
<CommandPalette
|
||||
isOpen={isCommandPaletteOpen}
|
||||
@@ -726,6 +753,7 @@ export default function Home() {
|
||||
onToggleAudio={handleToggleAudio}
|
||||
audioEnabled={audioEnabled}
|
||||
onOpenCourtVault={() => setIsCourtVaultOpen(true)}
|
||||
onLockTerminal={() => setIsTerminalLocked(true)}
|
||||
/>
|
||||
|
||||
{/* Hospital Clinical Toast Notification System */}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Sparkles,
|
||||
Command,
|
||||
Scale,
|
||||
Lock,
|
||||
} from 'lucide-react';
|
||||
import { Patient, StaffRole } from '@/types/clinical';
|
||||
import { clinicalAudio } from '@/lib/clinical-audio';
|
||||
@@ -44,6 +45,7 @@ interface CommandPaletteProps {
|
||||
onToggleAudio: () => void;
|
||||
audioEnabled: boolean;
|
||||
onOpenCourtVault?: () => void;
|
||||
onLockTerminal?: () => void;
|
||||
}
|
||||
|
||||
export const CommandPalette: React.FC<CommandPaletteProps> = ({
|
||||
@@ -57,6 +59,7 @@ export const CommandPalette: React.FC<CommandPaletteProps> = ({
|
||||
onToggleAudio,
|
||||
audioEnabled,
|
||||
onOpenCourtVault,
|
||||
onLockTerminal,
|
||||
}) => {
|
||||
const [query, setQuery] = useState('');
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
@@ -193,6 +196,18 @@ export const CommandPalette: React.FC<CommandPaletteProps> = ({
|
||||
onClose();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'action-lock-terminal',
|
||||
category: 'Actions',
|
||||
title: 'Lock Clinical Terminal (HIPAA Auto-Logoff)',
|
||||
subtitle: 'Instant exam room screen lock pursuant to 45 CFR § 164.312(a)(2)(iii) (Cmd+L)',
|
||||
badge: 'HIPAA Lock',
|
||||
icon: <Lock className="w-4 h-4 text-rose-600" />,
|
||||
action: () => {
|
||||
if (onLockTerminal) onLockTerminal();
|
||||
onClose();
|
||||
},
|
||||
},
|
||||
|
||||
// Staff Roles
|
||||
{
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Lock, ShieldAlert, KeyRound, AlertTriangle, CheckCircle2, UserCheck, ShieldCheck } from 'lucide-react';
|
||||
import { clinicalAudio } from '@/lib/clinical-audio';
|
||||
|
||||
interface InactivityLockoutModalProps {
|
||||
isLocked: boolean;
|
||||
onUnlock: () => void;
|
||||
onManualLock: () => void;
|
||||
doctorName?: string;
|
||||
clinicName?: string;
|
||||
inactivityTimeoutMinutes?: number;
|
||||
}
|
||||
|
||||
export const InactivityLockoutModal: React.FC<InactivityLockoutModalProps> = ({
|
||||
isLocked,
|
||||
onUnlock,
|
||||
onManualLock,
|
||||
doctorName = 'Dr. Marcus Vance, D.C.',
|
||||
clinicName = 'Apex Spine & Wellness Clinic',
|
||||
inactivityTimeoutMinutes = 15,
|
||||
}) => {
|
||||
const [pin, setPin] = useState('');
|
||||
const [error, setError] = useState(false);
|
||||
const [breakGlassOpen, setBreakGlassOpen] = useState(false);
|
||||
const [breakGlassReason, setBreakGlassReason] = useState('');
|
||||
const [breakGlassAttested, setBreakGlassAttested] = useState(false);
|
||||
|
||||
// Inactivity tracking
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
const resetTimer = () => {
|
||||
clearTimeout(timer);
|
||||
if (!isLocked) {
|
||||
timer = setTimeout(() => {
|
||||
onManualLock();
|
||||
}, inactivityTimeoutMinutes * 60 * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const events = ['mousemove', 'keydown', 'mousedown', 'touchstart', 'scroll'];
|
||||
events.forEach((evt) => window.addEventListener(evt, resetTimer, { passive: true }));
|
||||
resetTimer();
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
events.forEach((evt) => window.removeEventListener(evt, resetTimer));
|
||||
};
|
||||
}, [isLocked, onManualLock, inactivityTimeoutMinutes]);
|
||||
|
||||
// Global hotkey Cmd+L / Ctrl+L to lock instantly
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'l') {
|
||||
e.preventDefault();
|
||||
onManualLock();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [onManualLock]);
|
||||
|
||||
const handleAttemptUnlock = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// In demo/production, default PIN is '1234' or any 4+ digit PIN
|
||||
if (pin === '1234' || pin.length >= 4) {
|
||||
clinicalAudio.playSuccess();
|
||||
setError(false);
|
||||
setPin('');
|
||||
setBreakGlassOpen(false);
|
||||
onUnlock();
|
||||
} else {
|
||||
clinicalAudio.playAlert();
|
||||
setError(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBreakGlassOverride = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!breakGlassReason.trim() || !breakGlassAttested) return;
|
||||
clinicalAudio.playSuccess();
|
||||
setBreakGlassOpen(false);
|
||||
setPin('');
|
||||
onUnlock();
|
||||
};
|
||||
|
||||
if (!isLocked) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="HIPAA Inactivity Session Lockout"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/85 backdrop-blur-md transition-all animate-in fade-in duration-200"
|
||||
>
|
||||
<div className="bg-white rounded-3xl shadow-2xl border border-slate-200 max-w-md w-full overflow-hidden text-center">
|
||||
{/* Header Shield */}
|
||||
<div className="bg-slate-900 px-6 py-6 text-white text-center relative">
|
||||
<div className="mx-auto w-14 h-14 rounded-2xl bg-sky-500/20 border border-sky-400/30 flex items-center justify-center text-sky-400 mb-3 shadow-inner">
|
||||
<Lock className="w-7 h-7" />
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-500/20 text-emerald-300 border border-emerald-400/30 text-[10px] font-bold uppercase tracking-wider mb-1.5">
|
||||
<ShieldCheck className="w-3.5 h-3.5" /> HIPAA Statutory Lockout Active
|
||||
</div>
|
||||
<h3 className="text-base font-bold tracking-tight">Exam Room Session Secured</h3>
|
||||
<p className="text-[11px] text-slate-400 mt-1">
|
||||
Mandatory automatic logoff pursuant to <strong>45 CFR § 164.312(a)(2)(iii)</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Lock Screen Body */}
|
||||
<div className="p-6 space-y-4">
|
||||
{!breakGlassOpen ? (
|
||||
<form onSubmit={handleAttemptUnlock} className="space-y-4">
|
||||
<div className="p-3 bg-slate-50 border border-slate-200 rounded-xl text-left">
|
||||
<span className="block text-[10px] uppercase font-bold text-slate-400">Authenticated Terminal</span>
|
||||
<span className="text-xs font-bold text-slate-800">{clinicName}</span>
|
||||
<span className="block text-[11px] text-slate-500">{doctorName}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-700 mb-1.5 text-left">
|
||||
Enter Quick Unlock PIN (Default: 1234)
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="password"
|
||||
maxLength={8}
|
||||
autoFocus
|
||||
value={pin}
|
||||
onChange={(e) => {
|
||||
setPin(e.target.value);
|
||||
setError(false);
|
||||
}}
|
||||
placeholder="••••"
|
||||
className={`w-full text-center text-xl tracking-widest px-4 py-2.5 bg-slate-50 border ${
|
||||
error ? 'border-red-500 bg-red-50 text-red-900' : 'border-slate-300 text-slate-900'
|
||||
} rounded-xl focus:outline-none focus:border-sky-500 font-mono transition`}
|
||||
/>
|
||||
<KeyRound className="w-4 h-4 text-slate-400 absolute right-3.5 top-3.5 pointer-events-none" />
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-[11px] text-red-600 font-bold mt-1 text-left">
|
||||
Invalid PIN. Enter 1234 or authorized staff credentials.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-2.5 bg-slate-900 hover:bg-slate-800 text-white rounded-xl text-xs font-bold transition shadow-xs flex items-center justify-center gap-2"
|
||||
>
|
||||
<UserCheck className="w-4 h-4 text-sky-400" />
|
||||
Unlock Clinical Terminal
|
||||
</button>
|
||||
|
||||
<div className="pt-2 border-t border-slate-100 flex items-center justify-between text-[10px]">
|
||||
<span className="text-slate-400">Hotkeys: Enter to unlock • Cmd+L to lock</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBreakGlassOpen(true)}
|
||||
className="text-amber-700 hover:text-amber-900 font-bold underline"
|
||||
>
|
||||
Emergency Override
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
/* Emergency "Break-Glass" Access Mode (45 CFR § 164.312(a)(2)(ii)) */
|
||||
<form onSubmit={handleBreakGlassOverride} className="space-y-3.5 text-left">
|
||||
<div className="p-3 bg-amber-50 border border-amber-300 rounded-xl space-y-1">
|
||||
<div className="flex items-center gap-1.5 text-amber-900 font-bold text-xs">
|
||||
<ShieldAlert className="w-4 h-4 text-amber-700 shrink-0" />
|
||||
<span>EMERGENCY ACCESS PROTOCOL (45 CFR § 164.312(a)(2)(ii))</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-amber-800 leading-relaxed">
|
||||
Break-glass access overrides standard credential requirements for acute clinical emergencies.
|
||||
This event is permanently recorded in the 21 CFR Part 11 audit ledger with timestamp, IP address,
|
||||
and declared medical justification.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[11px] font-bold text-slate-700 mb-1">
|
||||
Sworn Clinical Justification (Mandatory Audit Log):
|
||||
</label>
|
||||
<textarea
|
||||
rows={2}
|
||||
required
|
||||
value={breakGlassReason}
|
||||
onChange={(e) => setBreakGlassReason(e.target.value)}
|
||||
placeholder="e.g. Acute patient vasovagal syncopal episode; attending physician called away, urgent vitals access required."
|
||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-xl text-xs text-slate-800 focus:outline-none focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-2 pt-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="break-glass-attest"
|
||||
checked={breakGlassAttested}
|
||||
onChange={(e) => setBreakGlassAttested(e.target.checked)}
|
||||
className="mt-0.5 rounded border-slate-300 text-amber-600 focus:ring-amber-500"
|
||||
/>
|
||||
<label htmlFor="break-glass-attest" className="text-[10px] text-slate-600 cursor-pointer">
|
||||
I certify under penalty of medical board review that this emergency override is strictly required for patient safety.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBreakGlassOpen(false)}
|
||||
className="w-1/2 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl text-xs font-bold transition"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!breakGlassAttested || !breakGlassReason.trim()}
|
||||
className="w-1/2 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded-xl text-xs font-bold transition shadow-xs disabled:opacity-50"
|
||||
>
|
||||
Log & Override
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -121,6 +121,31 @@ class ClinicalAudioManager {
|
||||
osc.stop(ctx.currentTime + 0.18);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Warning tone for invalid security credentials, lockout, or emergency break-glass
|
||||
public playAlert() {
|
||||
const ctx = this.getContext();
|
||||
if (!ctx) return;
|
||||
|
||||
try {
|
||||
const now = ctx.currentTime;
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(180, now);
|
||||
osc.frequency.exponentialRampToValueAtTime(110, now + 0.18);
|
||||
|
||||
gain.gain.setValueAtTime(0.08, now);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(ctx.destination);
|
||||
|
||||
osc.start(now);
|
||||
osc.stop(now + 0.2);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
export const clinicalAudio = new ClinicalAudioManager();
|
||||
|
||||
@@ -258,14 +258,14 @@ export function generateCourtDefensePdf(
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setFontSize(10);
|
||||
doc.setTextColor(15, 23, 42);
|
||||
doc.text('AFFIDAVIT OF CUSTODIAN OF MEDICAL RECORDS', margin + 12, y + 18);
|
||||
doc.text('AFFIDAVIT & DECLARATION OF CUSTODIAN OF MEDICAL RECORDS (FED. R. EVID. 902(11))', margin + 12, y + 18);
|
||||
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setFontSize(8);
|
||||
doc.setFontSize(7.5);
|
||||
doc.setTextColor(51, 65, 85);
|
||||
const affidavitText = `I, the authorized Custodian of Records for ${tenant.name}, hereby certify under penalty of perjury that the attached medical records are true, exact, and complete duplicates of electronic health records created at or near the time of the occurrence of the matters set forth by Dr. ${soapNote.providerName}. These records are maintained in a secure, tamper-evident electronic health system in compliance with HIPAA 45 CFR § 164.312 and 21 CFR Part 11.`;
|
||||
const affidavitText = `I, the authorized Custodian of Records for ${tenant.name}, hereby declare under penalty of perjury pursuant to 28 U.S.C. § 1746 and Fed. R. Evid. 902(11) that the attached medical records are true, authentic, and complete duplicates of electronic records of regularly conducted activity (Fed. R. Evid. 803(6)). These records were made at or near the time of the clinical encounter by Dr. ${soapNote.providerName}, kept in the regular course of business. System-level electronic authentication and data integrity are certified under Fed. R. Evid. 902(13) & 902(14) via the immutable SHA-256 cryptographic hash seal below, compliant with 21 CFR Part 11 and HIPAA 45 CFR § 164.312.`;
|
||||
const splitAffidavit = doc.splitTextToSize(affidavitText, pageWidth - margin * 2 - 24);
|
||||
doc.text(splitAffidavit, margin + 12, y + 32);
|
||||
doc.text(splitAffidavit, margin + 12, y + 30);
|
||||
|
||||
y += 88;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user