Files
Benjamin Admin 9912997187
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 25s
CI / test-go-edu-search (push) Successful in 26s
CI / test-python-klausur (push) Failing after 1m55s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 18s
refactor: Jitsi/Matrix/Voice von Core übernommen, Camunda/BPMN gelöscht, Kommunikation-Nav
- Voice-Service von Core nach Lehrer verschoben (bp-lehrer-voice-service)
- 4 Jitsi-Services + 2 Synapse-Services in docker-compose.yml aufgenommen
- Camunda komplett gelöscht: workflow pages, workflow-config.ts, bpmn-js deps
- CAMUNDA_URL aus backend-lehrer environment entfernt
- Sidebar: Kategorie "Compliance SDK" + "Katalogverwaltung" entfernt
- Sidebar: Neue Kategorie "Kommunikation" mit Video & Chat, Voice Service, Alerts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:01:47 +01:00

150 lines
5.1 KiB
Python

"""
Audit Models - DSGVO-compliant logging
NO PII in audit logs - only references and metadata
Erlaubt: ref_id (truncated), content_type, size_bytes, ttl_hours
Verboten: user_name, content, transcript, email
"""
from datetime import datetime
from enum import Enum
from typing import Optional, Dict, Any
from pydantic import BaseModel, Field
import uuid
class AuditAction(str, Enum):
"""Audit action types."""
# Session actions
SESSION_CREATED = "session_created"
SESSION_CONNECTED = "session_connected"
SESSION_CLOSED = "session_closed"
SESSION_EXPIRED = "session_expired"
# Audio actions (no content logged)
AUDIO_RECEIVED = "audio_received"
AUDIO_PROCESSED = "audio_processed"
# Task actions
TASK_CREATED = "task_created"
TASK_QUEUED = "task_queued"
TASK_STARTED = "task_started"
TASK_COMPLETED = "task_completed"
TASK_FAILED = "task_failed"
TASK_EXPIRED = "task_expired"
# Encryption actions
ENCRYPTION_KEY_VERIFIED = "encryption_key_verified"
ENCRYPTION_KEY_INVALID = "encryption_key_invalid"
# Integration actions
BREAKPILOT_CALLED = "breakpilot_called"
PERSONAPLEX_CALLED = "personaplex_called"
OLLAMA_CALLED = "ollama_called"
# Security actions
RATE_LIMIT_EXCEEDED = "rate_limit_exceeded"
UNAUTHORIZED_ACCESS = "unauthorized_access"
class AuditEntry(BaseModel):
"""
Audit log entry - DSGVO compliant.
NO PII is stored - only truncated references and metadata.
"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
timestamp: datetime = Field(default_factory=datetime.utcnow)
# Action identification
action: AuditAction
namespace_id_truncated: str = Field(
...,
description="First 8 chars of namespace ID",
max_length=8,
)
# Reference IDs (truncated for privacy)
session_id_truncated: Optional[str] = Field(
default=None,
description="First 8 chars of session ID",
max_length=8,
)
task_id_truncated: Optional[str] = Field(
default=None,
description="First 8 chars of task ID",
max_length=8,
)
# Metadata (no PII)
content_type: Optional[str] = Field(default=None, description="Type of content processed")
size_bytes: Optional[int] = Field(default=None, description="Size in bytes")
duration_ms: Optional[int] = Field(default=None, description="Duration in milliseconds")
ttl_hours: Optional[int] = Field(default=None, description="TTL in hours")
# Technical metadata
success: bool = Field(default=True)
error_code: Optional[str] = Field(default=None)
latency_ms: Optional[int] = Field(default=None)
# Context (no PII)
device_type: Optional[str] = Field(default=None)
client_version: Optional[str] = Field(default=None)
backend_used: Optional[str] = Field(default=None, description="personaplex, ollama, etc.")
@staticmethod
def truncate_id(full_id: str, length: int = 8) -> str:
"""Truncate ID for privacy."""
if not full_id:
return ""
return full_id[:length]
class Config:
json_schema_extra = {
"example": {
"id": "audit-123",
"timestamp": "2026-01-26T10:30:00Z",
"action": "task_completed",
"namespace_id_truncated": "teacher-",
"session_id_truncated": "session-",
"task_id_truncated": "task-xyz",
"content_type": "student_observation",
"size_bytes": 256,
"ttl_hours": 168,
"success": True,
"latency_ms": 1250,
"backend_used": "ollama",
}
}
class AuditCreate(BaseModel):
"""Request to create an audit entry."""
action: AuditAction
namespace_id: str = Field(..., description="Will be truncated before storage")
session_id: Optional[str] = Field(default=None, description="Will be truncated")
task_id: Optional[str] = Field(default=None, description="Will be truncated")
content_type: Optional[str] = Field(default=None)
size_bytes: Optional[int] = Field(default=None)
duration_ms: Optional[int] = Field(default=None)
success: bool = Field(default=True)
error_code: Optional[str] = Field(default=None)
latency_ms: Optional[int] = Field(default=None)
device_type: Optional[str] = Field(default=None)
backend_used: Optional[str] = Field(default=None)
def to_audit_entry(self) -> AuditEntry:
"""Convert to AuditEntry with truncated IDs."""
return AuditEntry(
action=self.action,
namespace_id_truncated=AuditEntry.truncate_id(self.namespace_id),
session_id_truncated=AuditEntry.truncate_id(self.session_id) if self.session_id else None,
task_id_truncated=AuditEntry.truncate_id(self.task_id) if self.task_id else None,
content_type=self.content_type,
size_bytes=self.size_bytes,
duration_ms=self.duration_ms,
success=self.success,
error_code=self.error_code,
latency_ms=self.latency_ms,
device_type=self.device_type,
backend_used=self.backend_used,
)