docs: Add BYOEH system documentation to Developer Portal and MKDocs
Some checks failed
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Adds comprehensive BYOEH (Bring-Your-Own-Expectation-Horizon) architecture documentation explaining the privacy-first KI exam correction workflow including pseudonymization, client-side encryption, and namespace isolation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
767
developer-portal/app/development/byoeh/page.tsx
Normal file
767
developer-portal/app/development/byoeh/page.tsx
Normal file
@@ -0,0 +1,767 @@
|
|||||||
|
import { DevPortalLayout, CodeBlock, InfoBox } from '@/components/DevPortalLayout'
|
||||||
|
|
||||||
|
export default function BYOEHDocsPage() {
|
||||||
|
return (
|
||||||
|
<DevPortalLayout
|
||||||
|
title="Wie funktioniert das Klausur-Namespace-System?"
|
||||||
|
description="Eine umfassende Erklaerung des BYOEH-Systems -- von der Anonymisierung bis zur sicheren KI-Korrektur."
|
||||||
|
>
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 1. EINLEITUNG */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="einfuehrung">1. Was ist das Namespace-System?</h2>
|
||||||
|
<p>
|
||||||
|
Das <strong>BYOEH-System</strong> (Bring Your Own Expectation Horizon) ist eine
|
||||||
|
Datenschutz-Architektur, die es Lehrern ermoeglicht, Klausuren <strong>anonym und
|
||||||
|
verschluesselt</strong> von einer KI korrigieren zu lassen -- ohne dass jemals der Name
|
||||||
|
eines Schuelers den Rechner des Lehrers verlaesst.
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
<em>“Die Klausuren gehen anonym in die Cloud, werden dort von KI korrigiert,
|
||||||
|
und kommen korrigiert zurueck. Nur der Lehrer kann die Ergebnisse wieder den
|
||||||
|
Schuelern zuordnen -- denn nur seine Hardware hat den Schluessel dafuer.”</em>
|
||||||
|
</blockquote>
|
||||||
|
<p>
|
||||||
|
Das System loest ein grundlegendes Problem: Klausurkorrektur mit KI-Unterstuetzung
|
||||||
|
<strong> ohne Datenschutzrisiko</strong>. Die Loesung besteht aus vier Bausteinen:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<strong>Pseudonymisierung:</strong> Namen werden durch zufaellige Codes ersetzt.
|
||||||
|
Niemand ausser dem Lehrer kennt die Zuordnung.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Verschluesselung:</strong> Alles wird <em>im Browser des Lehrers</em>
|
||||||
|
verschluesselt, bevor es den Rechner verlaesst. Der Server sieht nur unlesbaren
|
||||||
|
Datensalat.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Namespace-Isolation:</strong> Jeder Lehrer hat einen eigenen, abgeschotteten
|
||||||
|
Bereich (Namespace). Kein Lehrer kann auf die Daten eines anderen zugreifen.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>KI-Korrektur:</strong> Die KI arbeitet mit den verschluesselten Daten
|
||||||
|
und dem Erwartungshorizont (EH) des Lehrers. Korrekturvorschlaege gehen zurueck
|
||||||
|
an den Lehrer.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<InfoBox type="info" title="Kern-Designprinzip: Operator Blindness">
|
||||||
|
<strong>Breakpilot kann die Klausuren nicht lesen.</strong> Der Server sieht nur
|
||||||
|
verschluesselte Daten und einen Schluessel-Hash (nicht den Schluessel selbst). Die
|
||||||
|
Passphrase zum Entschluesseln existiert <em>nur</em> im Browser des Lehrers und
|
||||||
|
wird niemals uebertragen. Selbst ein Angriff auf den Server wuerde keine
|
||||||
|
Klausurtexte preisgeben.
|
||||||
|
</InfoBox>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 2. DER KOMPLETTE ABLAUF */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="ablauf">2. Der komplette Ablauf im Ueberblick</h2>
|
||||||
|
<p>
|
||||||
|
Der Prozess laesst sich in sieben Schritte unterteilen. Stellen Sie sich vor, der
|
||||||
|
Lehrer sitzt an seinem Rechner und hat einen Stapel gescannter Klausuren:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CodeBlock language="text" filename="Der komplette Workflow: Von der Klausur zur KI-Korrektur">
|
||||||
|
{`SCHRITT 1: KLAUSUREN SCANNEN & HOCHLADEN
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Lehrer scannt Klausuren ein (PDF oder Bild)
|
||||||
|
→ System erkennt automatisch den Kopfbereich mit Namen
|
||||||
|
→ Kopfbereich wird permanent entfernt (Header-Redaction)
|
||||||
|
→ Jede Klausur erhaelt einen zufaelligen Code (doc_token)
|
||||||
|
|
||||||
|
SCHRITT 2: VERSCHLUESSELUNG IM BROWSER
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Lehrer gibt eine Passphrase ein (z.B. "MeinGeheimesPasswort2025!")
|
||||||
|
→ Browser leitet daraus einen 256-Bit-Schluessel ab (PBKDF2)
|
||||||
|
→ Klausur wird mit AES-256-GCM verschluesselt
|
||||||
|
→ Nur der Hash des Schluessels wird an den Server gesendet
|
||||||
|
→ Passphrase und Schluessel verlassen NIEMALS den Browser
|
||||||
|
|
||||||
|
SCHRITT 3: IDENTITAETS-MAP SICHERN
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Die Zuordnung "doc_token → Schuelername" wird verschluesselt:
|
||||||
|
→ Tabelle: "a7f3c2d1... = Max Mustermann, b9e4a1f8... = Anna Schmidt"
|
||||||
|
→ Diese Tabelle wird mit dem gleichen Schluessel verschluesselt
|
||||||
|
→ Ohne Passphrase kann niemand die Zuordnung wiederherstellen
|
||||||
|
|
||||||
|
SCHRITT 4: UPLOAD IN DEN NAMESPACE
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Die verschluesselten Dateien gehen in den persoenlichen Namespace:
|
||||||
|
→ Jeder Lehrer hat eine eigene tenant_id
|
||||||
|
→ Daten werden in MinIO (verschluesselt) + Qdrant (Vektoren) gespeichert
|
||||||
|
→ Server sieht: verschluesselter Blob + Schluessel-Hash + Salt
|
||||||
|
|
||||||
|
SCHRITT 5: KI-KORREKTUR
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Der Lehrer startet die KI-Korrektur:
|
||||||
|
→ RAG-System durchsucht den Erwartungshorizont (EH)
|
||||||
|
→ KI generiert Korrekturvorschlaege pro Kriterium
|
||||||
|
→ Vorschlaege basieren auf dem EH, nicht auf Halluzinationen
|
||||||
|
|
||||||
|
SCHRITT 6: ERGEBNISSE ZURUECK
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Korrekturvorschlaege gehen an den Lehrer:
|
||||||
|
→ Lehrer gibt Passphrase ein
|
||||||
|
→ Browser entschluesselt die Ergebnisse
|
||||||
|
→ Lehrer sieht: Vorschlaege pro Kriterium + Gesamtnote
|
||||||
|
|
||||||
|
SCHRITT 7: ZUORDNUNG & FINALISIERUNG
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Lehrer ordnet Ergebnisse den Schuelern zu:
|
||||||
|
→ Identitaets-Map wird entschluesselt
|
||||||
|
→ doc_token wird wieder dem echten Namen zugeordnet
|
||||||
|
→ Lehrer ueberprueft/korrigiert KI-Vorschlaege
|
||||||
|
→ Fertige Korrektur + Gutachten koennen exportiert werden`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 3. PSEUDONYMISIERUNG */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="pseudonymisierung">3. Pseudonymisierung: Wie Namen verschwinden</h2>
|
||||||
|
<p>
|
||||||
|
Pseudonymisierung bedeutet: personenbezogene Daten werden durch <strong>zufaellige
|
||||||
|
Codes</strong> ersetzt, sodass ohne Zusatzinformation kein Rueckschluss auf die Person
|
||||||
|
moeglich ist. Im BYOEH-System passiert das auf zwei Ebenen:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>3.1 Der doc_token: Ein zufaelliger Ausweis</h3>
|
||||||
|
<p>
|
||||||
|
Jede Klausur erhaelt einen <strong>doc_token</strong> -- einen 128-Bit-Zufallscode im
|
||||||
|
UUID4-Format (z.B. <code>a7f3c2d1-4e9b-4a5f-8c7d-6b2e1f0a9d3c</code>). Dieser Code:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Ist <strong>kryptographisch zufaellig</strong> -- es gibt keinen Zusammenhang zwischen
|
||||||
|
Token und Schueler</li>
|
||||||
|
<li>Kann <strong>nicht zurueckgerechnet</strong> werden -- auch mit Kenntnis des Algorithmus
|
||||||
|
ist kein Rueckschluss moeglich</li>
|
||||||
|
<li>Wird <strong>auf der Klausur aufgedruckt</strong> (als QR-Code), damit die physische
|
||||||
|
Klausur spaeter wieder zugeordnet werden kann</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>3.2 Header-Redaction: Der Name wird entfernt</h3>
|
||||||
|
<p>
|
||||||
|
Bevor eine Klausur verarbeitet wird, entfernt das System den <strong>Kopfbereich</strong>
|
||||||
|
der gescannten Seite -- dort, wo typischerweise Name, Klasse und Datum stehen. Diese
|
||||||
|
Entfernung ist <strong>permanent</strong>: Die Originaldaten werden nicht gespeichert.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="not-prose my-6 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Methode</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Wie es funktioniert</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Wann verwendet</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Einfache Redaction</td>
|
||||||
|
<td className="px-4 py-3">Obere ~2,5 cm der Seite werden weiss ueberschrieben</td>
|
||||||
|
<td className="px-4 py-3">Standard bei allen Uploads</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Smarte Redaction</td>
|
||||||
|
<td className="px-4 py-3">OpenCV erkennt Textbereiche und entfernt gezielt den Kopf, verschont aber QR-Codes</td>
|
||||||
|
<td className="px-4 py-3">Wenn QR-Codes auf der Klausur sind</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>3.3 Die Identitaets-Map: Nur der Lehrer kennt die Zuordnung</h3>
|
||||||
|
<p>
|
||||||
|
Die Zuordnung <em>doc_token → Schuelername</em> wird als <strong>verschluesselte Tabelle</strong>
|
||||||
|
gespeichert. Das Datenbankmodell sieht so aus:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CodeBlock language="text" filename="Datenbank: ExamSession (vereinfacht)">
|
||||||
|
{`ExamSession
|
||||||
|
├── teacher_id = "lehrer-uuid-123" ← Pflichtfeld (Isolation)
|
||||||
|
├── encrypted_identity_map = [verschluesselte Bytes] ← Nur mit Passphrase lesbar
|
||||||
|
├── identity_map_iv = "a3f2c1..." ← Initialisierungsvektor (fuer AES)
|
||||||
|
│
|
||||||
|
└── PseudonymizedDocument (pro Klausur)
|
||||||
|
├── doc_token = "a7f3c2d1-..." ← Zufaelliger Code (Primary Key)
|
||||||
|
├── exam_session_id = [Referenz]
|
||||||
|
└── (Kein Name, keine Klasse, kein persoenliches Datum)`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
<InfoBox type="success" title="DSGVO Art. 4 Nr. 5 konform">
|
||||||
|
Die Pseudonymisierung erfuellt die Definition aus der DSGVO: Die personenbezogenen Daten
|
||||||
|
(Schuelernamen) koennen <strong>ohne Hinzuziehung zusaetzlicher Informationen</strong>
|
||||||
|
(der verschluesselten Identitaets-Map + der Passphrase des Lehrers) nicht mehr einer
|
||||||
|
bestimmten Person zugeordnet werden.
|
||||||
|
</InfoBox>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 4. VERSCHLUESSELUNG */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="verschluesselung">4. Verschluesselung: Wie Daten geschuetzt werden</h2>
|
||||||
|
<p>
|
||||||
|
Die Verschluesselung ist das Herzsstueck des Datenschutzes. Sie findet <strong>vollstaendig
|
||||||
|
im Browser</strong> statt -- der Server bekommt nur verschluesselte Daten zu sehen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>4.1 Der Verschluesselungsvorgang</h3>
|
||||||
|
<p>
|
||||||
|
Wenn der Lehrer eine Klausur oder einen Erwartungshorizont hochlaedt, passiert im
|
||||||
|
Browser folgendes:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CodeBlock language="text" filename="Client-seitige Verschluesselung (im Browser)">
|
||||||
|
{`┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Browser des Lehrers │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 1. Lehrer gibt Passphrase ein (z.B. "MeinGeheimesPasswort!") │
|
||||||
|
│ │ ↑ │
|
||||||
|
│ │ │ Passphrase bleibt hier -- wird NIE gesendet │
|
||||||
|
│ ▼ │
|
||||||
|
│ 2. Schluessel-Ableitung: │
|
||||||
|
│ PBKDF2-SHA256(Passphrase, zufaelliger Salt, 100.000 Runden) │
|
||||||
|
│ │ │
|
||||||
|
│ │ → Ergebnis: 256-Bit-Schluessel (32 Bytes) │
|
||||||
|
│ │ → Selbst bei Kenntnis des Salts sind 100.000 Runden │
|
||||||
|
│ │ noetig, um den Schluessel zu erraten │
|
||||||
|
│ ▼ │
|
||||||
|
│ 3. Verschluesselung: │
|
||||||
|
│ AES-256-GCM(Schluessel, zufaelliger IV, Datei-Inhalt) │
|
||||||
|
│ │ │
|
||||||
|
│ │ → AES-256: Militaerstandard, 2^256 moegliche Schluessel │
|
||||||
|
│ │ → GCM: Garantiert Integritaet (Manipulation erkennbar) │
|
||||||
|
│ ▼ │
|
||||||
|
│ 4. Schluessel-Hash: │
|
||||||
|
│ SHA-256(abgeleiteter Schluessel) → Hash fuer Verifikation │
|
||||||
|
│ │ │
|
||||||
|
│ │ → Der Server speichert nur diesen Hash │
|
||||||
|
│ │ → Damit kann geprueft werden ob die Passphrase stimmt │
|
||||||
|
│ │ → Vom Hash kann der Schluessel NICHT zurueckgerechnet │
|
||||||
|
│ │ werden │
|
||||||
|
│ ▼ │
|
||||||
|
│ 5. Upload: Nur diese Daten gehen an den Server: │
|
||||||
|
│ • Verschluesselter Blob (unlesbar ohne Schluessel) │
|
||||||
|
│ • Salt (zufaellige Bytes, harmlos) │
|
||||||
|
│ • IV (Initialisierungsvektor, harmlos) │
|
||||||
|
│ • Schluessel-Hash (zur Verifikation, nicht umkehrbar) │
|
||||||
|
│ │
|
||||||
|
│ Was NICHT an den Server geht: │
|
||||||
|
│ ✗ Passphrase │
|
||||||
|
│ ✗ Abgeleiteter Schluessel │
|
||||||
|
│ ✗ Unverschluesselter Klartext │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
<h3>4.2 Warum ist das sicher?</h3>
|
||||||
|
<div className="not-prose my-6 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Angriffsszenario</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Was der Angreifer sieht</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Ergebnis</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Server wird gehackt</td>
|
||||||
|
<td className="px-4 py-3">Verschluesselte Blobs + Hashes</td>
|
||||||
|
<td className="px-4 py-3 text-green-700 font-medium">Keine lesbaren Klausuren</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Datenbank wird geleakt</td>
|
||||||
|
<td className="px-4 py-3">encrypted_identity_map (verschluesselt)</td>
|
||||||
|
<td className="px-4 py-3 text-green-700 font-medium">Keine Schuelernamen</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Netzwerkverkehr abgefangen</td>
|
||||||
|
<td className="px-4 py-3">Verschluesselte Daten (HTTPS + AES)</td>
|
||||||
|
<td className="px-4 py-3 text-green-700 font-medium">Doppelt verschluesselt</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Betreiber (Breakpilot) will mitlesen</td>
|
||||||
|
<td className="px-4 py-3">Verschluesselte Blobs, kein Schluessel</td>
|
||||||
|
<td className="px-4 py-3 text-green-700 font-medium">Operator Blindness</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Anderer Lehrer versucht Zugriff</td>
|
||||||
|
<td className="px-4 py-3">Nichts (Tenant-Isolation)</td>
|
||||||
|
<td className="px-4 py-3 text-green-700 font-medium">Namespace blockiert</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 5. NAMESPACE / TENANT-ISOLATION */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="namespace">5. Namespace-Isolation: Jeder Lehrer hat seinen eigenen Bereich</h2>
|
||||||
|
<p>
|
||||||
|
Ein <strong>Namespace</strong> (auch “Tenant” genannt) ist ein abgeschotteter
|
||||||
|
Bereich im System. Man kann es sich wie <strong>separate Schliessfaecher</strong> in einer
|
||||||
|
Bank vorstellen: Jeder Lehrer hat sein eigenes Fach, und kein Schluessel passt in ein
|
||||||
|
anderes Fach.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>5.1 Wie die Isolation funktioniert</h3>
|
||||||
|
<p>
|
||||||
|
Jeder Lehrer erhaelt beim ersten Login eine eindeutige <code>tenant_id</code>. Diese ID wird
|
||||||
|
bei <strong>jeder einzelnen Datenbankabfrage</strong> als Pflichtfilter mitgefuehrt:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CodeBlock language="text" filename="Tenant-Isolation in der Vektordatenbank (Qdrant)">
|
||||||
|
{`Lehrer A (tenant_id: "school-A-lehrer-1")
|
||||||
|
├── Klausur 1 (verschluesselt)
|
||||||
|
├── Klausur 2 (verschluesselt)
|
||||||
|
└── Erwartungshorizont Deutsch LK 2025
|
||||||
|
|
||||||
|
Lehrer B (tenant_id: "school-B-lehrer-2")
|
||||||
|
├── Klausur 1 (verschluesselt)
|
||||||
|
└── Erwartungshorizont Mathe GK 2025
|
||||||
|
|
||||||
|
Suchanfrage von Lehrer A:
|
||||||
|
"Wie soll die Einleitung strukturiert sein?"
|
||||||
|
→ Suche NUR in tenant_id = "school-A-lehrer-1"
|
||||||
|
→ Lehrer B's Daten sind UNSICHTBAR
|
||||||
|
|
||||||
|
Jede Qdrant-Query hat diesen Pflichtfilter:
|
||||||
|
must_conditions = [
|
||||||
|
FieldCondition(key="tenant_id", match="school-A-lehrer-1")
|
||||||
|
]
|
||||||
|
|
||||||
|
Es gibt KEINE Abfrage ohne tenant_id-Filter.`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
<h3>5.2 Drei Ebenen der Isolation</h3>
|
||||||
|
<div className="not-prose my-6 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Ebene</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">System</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Isolation</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Dateisystem</td>
|
||||||
|
<td className="px-4 py-3">MinIO (S3-Storage)</td>
|
||||||
|
<td className="px-4 py-3">Eigener Ordner pro Lehrer: <code>/tenant-id/eh-id/encrypted.bin</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Vektordatenbank</td>
|
||||||
|
<td className="px-4 py-3">Qdrant</td>
|
||||||
|
<td className="px-4 py-3">Pflichtfilter <code>tenant_id</code> bei jeder Suche</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Metadaten-DB</td>
|
||||||
|
<td className="px-4 py-3">PostgreSQL</td>
|
||||||
|
<td className="px-4 py-3">Jede Tabelle hat <code>teacher_id</code> als Pflichtfeld</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<InfoBox type="warning" title="Kein Training mit Lehrerdaten">
|
||||||
|
Auf allen Vektoren in Qdrant ist das Flag <code>training_allowed: false</code> gesetzt.
|
||||||
|
Das bedeutet: Die Inhalte der Lehrer werden <strong>ausschliesslich fuer RAG-Suchen</strong>
|
||||||
|
(Abruf relevanter Textpassagen) verwendet und <strong>niemals zum Trainieren</strong> eines
|
||||||
|
KI-Modells eingesetzt.
|
||||||
|
</InfoBox>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 6. DER ERWARTUNGSHORIZONT (EH) */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="erwartungshorizont">6. Der Erwartungshorizont: Die Grundlage fuer KI-Korrektur</h2>
|
||||||
|
<p>
|
||||||
|
Ein <strong>Erwartungshorizont</strong> (EH) ist das Dokument, das beschreibt, was in
|
||||||
|
einer Klausur erwartet wird: welche Inhalte in welcher Qualitaet vorkommen sollen.
|
||||||
|
Im BYOEH-System laedt der Lehrer seinen eigenen EH hoch, und die KI nutzt ihn als
|
||||||
|
Referenz fuer Korrekturvorschlaege.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>6.1 Upload-Wizard (5 Schritte)</h3>
|
||||||
|
<div className="not-prose my-4 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Schritt</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Was passiert</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Warum</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr><td className="px-4 py-3 font-medium">1. Datei waehlen</td><td className="px-4 py-3">PDF per Drag & Drop hochladen</td><td className="px-4 py-3">Der EH als digitales Dokument</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">2. Metadaten</td><td className="px-4 py-3">Titel, Fach, Niveau (eA/gA), Jahr</td><td className="px-4 py-3">Fuer Filterung und Organisation</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">3. Rechtebestaetigung</td><td className="px-4 py-3">Checkbox: “Ich bin berechtigt”</td><td className="px-4 py-3">Rechtliche Absicherung (Urheberrecht)</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">4. Verschluesselung</td><td className="px-4 py-3">Passphrase eingeben (2x bestaetigen)</td><td className="px-4 py-3">Schluessel fuer Ende-zu-Ende-Verschluesselung</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">5. Zusammenfassung</td><td className="px-4 py-3">Pruefen und bestaetigen</td><td className="px-4 py-3">Letzte Kontrolle vor dem Upload</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>6.2 RAG-Pipeline: Wie der EH fuer die KI nutzbar wird</h3>
|
||||||
|
<p>
|
||||||
|
Nach dem Upload wird der EH fuer die KI-Suche vorbereitet. Dieser Vorgang heisst
|
||||||
|
<strong> Indexierung</strong> und funktioniert wie das Erstellen eines Stichwortverzeichnisses
|
||||||
|
fuer ein Buch:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CodeBlock language="text" filename="Indexierung: Vom PDF zum durchsuchbaren EH">
|
||||||
|
{`Erwartungshorizont (verschluesselt auf Server)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 1. Passphrase-Verifikation │ ← Lehrer gibt Passphrase ein
|
||||||
|
│ Hash pruefen │ Server vergleicht mit gespeichertem Hash
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 2. Entschluesselung │ ← Temporaer im Arbeitsspeicher
|
||||||
|
│ AES-256-GCM Decrypt │ (wird nach Verarbeitung geloescht)
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 3. Text-Extraktion │ ← PDF → Klartext
|
||||||
|
│ Tabellen, Listen erkennen │
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 4. Chunking │ ← Text in 1.000-Zeichen-Abschnitte zerlegen
|
||||||
|
│ Ueberlappung: 200 Zeichen │ (mit Ueberlappung, damit kein Kontext
|
||||||
|
│ │ verloren geht)
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 5. Embedding │ ← Jeder Abschnitt wird in einen
|
||||||
|
│ Text → 1.536 Zahlen │ Bedeutungsvektor umgewandelt
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 6. Re-Encryption │ ← Jeder Chunk wird ERNEUT verschluesselt
|
||||||
|
│ AES-256-GCM pro Chunk │ bevor er gespeichert wird
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 7. Qdrant-Indexierung │ ← Vektor + verschluesselter Chunk
|
||||||
|
│ tenant_id: "lehrer-123" │ werden mit Tenant-Filter gespeichert
|
||||||
|
│ training_allowed: false │
|
||||||
|
└────────────────────────────────┘`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
<h3>6.3 Wie die KI den EH nutzt (RAG-Query)</h3>
|
||||||
|
<p>
|
||||||
|
Wenn der Lehrer bei der Korrektur einen Vorschlag anfordert, passiert folgendes:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<strong>Frage formulieren:</strong> Das System erstellt eine Suchanfrage aus dem
|
||||||
|
Klausurtext und dem aktuellen Bewertungskriterium (z.B. “Wie gut ist die
|
||||||
|
Einleitung im Vergleich zum Erwartungshorizont?”).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Semantische Suche:</strong> Die Anfrage wird in einen Vektor umgewandelt und
|
||||||
|
gegen die EH-Vektoren in Qdrant gesucht -- <em>nur im Namespace des Lehrers</em>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Entschluesselung:</strong> Die gefundenen Chunks werden mit der Passphrase
|
||||||
|
des Lehrers entschluesselt.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>KI-Antwort:</strong> Die entschluesselten EH-Passagen werden als Kontext
|
||||||
|
an die KI uebergeben, die daraus einen Korrekturvorschlag generiert.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 7. KEY SHARING */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="key-sharing">7. Key Sharing: Zweitkorrektur ermoeglichen</h2>
|
||||||
|
<p>
|
||||||
|
Bei Abiturklausuren muss eine <strong>Zweitkorrektur</strong> durch einen anderen Lehrer
|
||||||
|
erfolgen. Das Key-Sharing-System ermoeglicht es dem Erstpruefer, seinen Erwartungshorizont
|
||||||
|
sicher mit dem Zweitpruefer zu teilen -- ohne die Verschluesselung aufzugeben.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>7.1 Einladungs-Workflow</h3>
|
||||||
|
<CodeBlock language="text" filename="Key Sharing: Sicheres Teilen zwischen Pruefern">
|
||||||
|
{`Erstpruefer Server Zweitpruefer
|
||||||
|
│ │ │
|
||||||
|
│ 1. Einladung senden │ │
|
||||||
|
│ (E-Mail + Rolle + Klausur) │ │
|
||||||
|
│─────────────────────────────────▶ │
|
||||||
|
│ │ │
|
||||||
|
│ │ 2. Einladung erstellt │
|
||||||
|
│ │ (14 Tage gueltig) │
|
||||||
|
│ │ │
|
||||||
|
│ │ 3. Benachrichtigung ──────▶│
|
||||||
|
│ │ │
|
||||||
|
│ │ 4. Einladung annehmen
|
||||||
|
│ │◀─────────────────────────────│
|
||||||
|
│ │ │
|
||||||
|
│ │ 5. Key-Share erstellt │
|
||||||
|
│ │ (verschluesselte │
|
||||||
|
│ │ Passphrase) │
|
||||||
|
│ │ │
|
||||||
|
│ │ 6. Zweitpruefer kann ──────▶│
|
||||||
|
│ │ jetzt RAG-Queries │
|
||||||
|
│ │ ausfuehren │
|
||||||
|
│ │ │
|
||||||
|
│ 7. Zugriff widerrufen │ │
|
||||||
|
│ (jederzeit moeglich) │ │
|
||||||
|
│─────────────────────────────────▶ │
|
||||||
|
│ │ Share deaktiviert │`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
<h3>7.2 Rollen beim Key-Sharing</h3>
|
||||||
|
<div className="not-prose my-4 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Rolle</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Wer</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Rechte</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Erstpruefer (EK)</td><td className="px-4 py-3">Kurslehrer</td><td className="px-4 py-3">Vollzugriff, kann teilen & widerrufen</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Zweitpruefer (ZK)</td><td className="px-4 py-3">Anderer Fachlehrer</td><td className="px-4 py-3">Nur Lesen, RAG-Queries, eigene Annotations</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Drittpruefer (DK)</td><td className="px-4 py-3">Bei Differenz ≥ 4 Punkte</td><td className="px-4 py-3">Nur Lesen, RAG-Queries</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Fachvorsitz</td><td className="px-4 py-3">Fachbereichsleitung</td><td className="px-4 py-3">Nur Lesen (Aufsichtsfunktion)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 8. BEWERTUNGSKRITERIEN */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="bewertung">8. KI-gestuetzte Bewertung: Wie die Korrektur funktioniert</h2>
|
||||||
|
<p>
|
||||||
|
Die KI bewertet jede Klausur anhand von <strong>fuenf Kriterien</strong>, die
|
||||||
|
zusammen 100% ergeben:
|
||||||
|
</p>
|
||||||
|
<div className="not-prose my-6 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Kriterium</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Gewichtung</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Was geprueft wird</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Rechtschreibung</td><td className="px-4 py-3">15%</td><td className="px-4 py-3">Orthographie, Zeichensetzung</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Grammatik</td><td className="px-4 py-3">15%</td><td className="px-4 py-3">Satzbau, Kongruenz, Tempus</td></tr>
|
||||||
|
<tr className="bg-blue-50"><td className="px-4 py-3 font-bold text-blue-800">Inhalt</td><td className="px-4 py-3 font-bold text-blue-800">40%</td><td className="px-4 py-3">Bezug zum EH, Vollstaendigkeit, Argumentation</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Struktur</td><td className="px-4 py-3">15%</td><td className="px-4 py-3">Gliederung, Einleitung/Schluss, roter Faden</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">Stil</td><td className="px-4 py-3">15%</td><td className="px-4 py-3">Ausdruck, Wortwahl, Fachsprache</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Die Bewertung folgt dem <strong>15-Punkte-System</strong> (0-15 Notenpunkte),
|
||||||
|
das fuer die gymnasiale Oberstufe gilt. Aus den Teilbewertungen wird automatisch
|
||||||
|
eine Gesamtnote berechnet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<InfoBox type="info" title="KI schlaegt vor, Lehrer entscheidet">
|
||||||
|
Alle KI-Bewertungen sind <strong>Vorschlaege</strong>. Der Lehrer hat bei jedem
|
||||||
|
Kriterium die volle Kontrolle: Er kann den Vorschlag annehmen, aendern oder
|
||||||
|
komplett ueberschreiben. Die finale Note setzt immer der Lehrer.
|
||||||
|
</InfoBox>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 9. AUDIT-TRAIL */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="audit">9. Audit-Trail: Alles wird protokolliert</h2>
|
||||||
|
<p>
|
||||||
|
Jede Aktion im System wird revisionssicher im <strong>Audit-Log</strong> gespeichert.
|
||||||
|
Das ist wichtig fuer die Nachvollziehbarkeit und fuer den Fall, dass Schueler oder
|
||||||
|
Eltern eine Korrektur anfechten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="not-prose my-4 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Aktion</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Was protokolliert wird</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr><td className="px-4 py-3 font-medium">upload</td><td className="px-4 py-3">EH hochgeladen (Dateigroesse, Metadaten, Zeitstempel)</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">index</td><td className="px-4 py-3">EH fuer RAG indexiert (Anzahl Chunks, Dauer)</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">rag_query</td><td className="px-4 py-3">RAG-Suchanfrage ausgefuehrt (Query-Hash, Anzahl Ergebnisse, Score)</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">share</td><td className="px-4 py-3">EH mit anderem Pruefer geteilt (Empfaenger, Rolle)</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">revoke_share</td><td className="px-4 py-3">Zugriff widerrufen (wer, wann)</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">link_klausur</td><td className="px-4 py-3">EH mit Klausur verknuepft</td></tr>
|
||||||
|
<tr><td className="px-4 py-3 font-medium">delete</td><td className="px-4 py-3">EH geloescht (Soft Delete, bleibt in Logs)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 10. API-ENDPUNKTE */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="api">10. API-Endpunkte (Technische Referenz)</h2>
|
||||||
|
<p>
|
||||||
|
Alle Endpunkte laufen ueber den <strong>klausur-service</strong> auf Port 8086.
|
||||||
|
Authentifizierung erfolgt ueber JWT-Token.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>10.1 Erwartungshorizont-Verwaltung</h3>
|
||||||
|
<div className="not-prose my-4 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Methode</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Endpunkt</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-blue-100 text-blue-800 text-xs font-bold">POST</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/upload</td><td className="px-4 py-3">Verschluesselten EH hochladen</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-green-100 text-green-800 text-xs font-bold">GET</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh</td><td className="px-4 py-3">Eigene EHs auflisten</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-green-100 text-green-800 text-xs font-bold">GET</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}</td><td className="px-4 py-3">Einzelnen EH abrufen</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-red-100 text-red-800 text-xs font-bold">DELETE</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}</td><td className="px-4 py-3">EH loeschen (Soft Delete)</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-blue-100 text-blue-800 text-xs font-bold">POST</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}/index</td><td className="px-4 py-3">EH fuer RAG indexieren</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-blue-100 text-blue-800 text-xs font-bold">POST</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/rag-query</td><td className="px-4 py-3">RAG-Suchanfrage ausfuehren</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>10.2 Key Sharing</h3>
|
||||||
|
<div className="not-prose my-4 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Methode</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Endpunkt</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-blue-100 text-blue-800 text-xs font-bold">POST</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}/share</td><td className="px-4 py-3">EH mit Pruefer teilen</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-green-100 text-green-800 text-xs font-bold">GET</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}/shares</td><td className="px-4 py-3">Geteilte Zugriffe auflisten</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-red-100 text-red-800 text-xs font-bold">DELETE</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}/shares/{'{shareId}'}</td><td className="px-4 py-3">Zugriff widerrufen</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-green-100 text-green-800 text-xs font-bold">GET</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/shared-with-me</td><td className="px-4 py-3">Mit mir geteilte EHs</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>10.3 Klausur-Integration</h3>
|
||||||
|
<div className="not-prose my-4 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Methode</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Endpunkt</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-blue-100 text-blue-800 text-xs font-bold">POST</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}/link-klausur</td><td className="px-4 py-3">EH mit Klausur verknuepfen</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-red-100 text-red-800 text-xs font-bold">DELETE</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/eh/{'{id}'}/link-klausur/{'{klausurId}'}</td><td className="px-4 py-3">Verknuepfung loesen</td></tr>
|
||||||
|
<tr><td className="px-4 py-3"><span className="px-2 py-0.5 rounded bg-green-100 text-green-800 text-xs font-bold">GET</span></td><td className="px-4 py-3 font-mono text-sm">/api/v1/klausuren/{'{id}'}/linked-eh</td><td className="px-4 py-3">Verknuepften EH abrufen</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 11. DATEISTRUKTUR */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="dateien">11. Dateistruktur im Code</h2>
|
||||||
|
|
||||||
|
<CodeBlock language="text" filename="Relevante Dateien im Repository">
|
||||||
|
{`klausur-service/
|
||||||
|
├── backend/
|
||||||
|
│ ├── main.py # API-Endpunkte + Datenmodelle
|
||||||
|
│ ├── qdrant_service.py # Vektordatenbank-Operationen (Tenant-Filter)
|
||||||
|
│ └── eh_pipeline.py # Chunking, Embedding, Verschluesselung
|
||||||
|
│
|
||||||
|
├── frontend/
|
||||||
|
│ └── src/
|
||||||
|
│ ├── components/
|
||||||
|
│ │ └── EHUploadWizard.tsx # 5-Schritt-Upload-Wizard
|
||||||
|
│ └── services/
|
||||||
|
│ ├── api.ts # API-Client
|
||||||
|
│ └── encryption.ts # Client-seitige Kryptographie
|
||||||
|
│
|
||||||
|
└── docs/
|
||||||
|
├── BYOEH-Architecture.md # Technische Architektur
|
||||||
|
└── BYOEH-Developer-Guide.md # Entwickler-Handbuch
|
||||||
|
|
||||||
|
backend/klausur/
|
||||||
|
├── db_models.py # ExamSession, PseudonymizedDocument
|
||||||
|
└── services/
|
||||||
|
└── pseudonymizer.py # QR-Codes, Header-Redaction, doc_tokens`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
{/* ============================================================ */}
|
||||||
|
{/* 12. ZUSAMMENFASSUNG */}
|
||||||
|
{/* ============================================================ */}
|
||||||
|
<h2 id="zusammenfassung">12. Zusammenfassung: Die Sicherheitsgarantien</h2>
|
||||||
|
|
||||||
|
<div className="not-prose my-6 overflow-x-auto">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Garantie</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Wie umgesetzt</th>
|
||||||
|
<th className="px-4 py-3 text-left font-medium text-gray-500">Regelwerk</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Kein Name verlaesst den Rechner</td>
|
||||||
|
<td className="px-4 py-3">Header-Redaction + verschluesselte Identity-Map</td>
|
||||||
|
<td className="px-4 py-3">DSGVO Art. 4 Nr. 5</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Betreiber kann nicht mitlesen</td>
|
||||||
|
<td className="px-4 py-3">Client-seitige AES-256-GCM Verschluesselung</td>
|
||||||
|
<td className="px-4 py-3">DSGVO Art. 32</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Kein Zugriff durch andere Lehrer</td>
|
||||||
|
<td className="px-4 py-3">Tenant-Isolation (Namespace) auf allen 3 Ebenen</td>
|
||||||
|
<td className="px-4 py-3">DSGVO Art. 25</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Kein KI-Training mit Schuelerdaten</td>
|
||||||
|
<td className="px-4 py-3"><code>training_allowed: false</code> auf allen Vektoren</td>
|
||||||
|
<td className="px-4 py-3">AI Act Art. 10</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Alles nachvollziehbar</td>
|
||||||
|
<td className="px-4 py-3">Vollstaendiger Audit-Trail aller Aktionen</td>
|
||||||
|
<td className="px-4 py-3">DSGVO Art. 5 Abs. 2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-3 font-medium">Lehrer behaelt volle Kontrolle</td>
|
||||||
|
<td className="px-4 py-3">KI-Vorschlaege, keine KI-Entscheidungen + jederzeitiger Widerruf</td>
|
||||||
|
<td className="px-4 py-3">DSGVO Art. 22</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<InfoBox type="success" title="Das Wichtigste in einem Satz">
|
||||||
|
Das BYOEH-System ermoeglicht KI-gestuetzte Klausurkorrektur, bei der
|
||||||
|
<strong> kein Schuelername den Rechner des Lehrers verlaesst</strong>, alle Daten
|
||||||
|
<strong> Ende-zu-Ende verschluesselt</strong> sind, jeder Lehrer seinen
|
||||||
|
<strong> eigenen abgeschotteten Namespace</strong> hat, und die KI
|
||||||
|
<strong> nur Vorschlaege macht</strong> -- die finale Bewertung trifft immer der Lehrer.
|
||||||
|
</InfoBox>
|
||||||
|
</DevPortalLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -72,6 +72,7 @@ const navigation: NavItem[] = [
|
|||||||
icon: <BookOpen className="w-4 h-4" />,
|
icon: <BookOpen className="w-4 h-4" />,
|
||||||
items: [
|
items: [
|
||||||
{ title: 'Compliance Service', href: '/development/docs' },
|
{ title: 'Compliance Service', href: '/development/docs' },
|
||||||
|
{ title: 'Klausur-Namespace (BYOEH)', href: '/development/byoeh' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
456
docs-src/services/klausur-service/byoeh-system-erklaerung.md
Normal file
456
docs-src/services/klausur-service/byoeh-system-erklaerung.md
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
# Wie funktioniert das Klausur-Namespace-System?
|
||||||
|
|
||||||
|
Eine umfassende Erklaerung des BYOEH-Systems -- von der Anonymisierung bis zur sicheren KI-Korrektur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Was ist das Namespace-System?
|
||||||
|
|
||||||
|
Das **BYOEH-System** (Bring Your Own Expectation Horizon) ist eine Datenschutz-Architektur, die es Lehrern ermoeglicht, Klausuren **anonym und verschluesselt** von einer KI korrigieren zu lassen -- ohne dass jemals der Name eines Schuelers den Rechner des Lehrers verlaesst.
|
||||||
|
|
||||||
|
> *"Die Klausuren gehen anonym in die Cloud, werden dort von KI korrigiert, und kommen korrigiert zurueck. Nur der Lehrer kann die Ergebnisse wieder den Schuelern zuordnen -- denn nur seine Hardware hat den Schluessel dafuer."*
|
||||||
|
|
||||||
|
Das System loest ein grundlegendes Problem: Klausurkorrektur mit KI-Unterstuetzung **ohne Datenschutzrisiko**. Die Loesung besteht aus vier Bausteinen:
|
||||||
|
|
||||||
|
1. **Pseudonymisierung:** Namen werden durch zufaellige Codes ersetzt. Niemand ausser dem Lehrer kennt die Zuordnung.
|
||||||
|
2. **Verschluesselung:** Alles wird *im Browser des Lehrers* verschluesselt, bevor es den Rechner verlaesst. Der Server sieht nur unlesbaren Datensalat.
|
||||||
|
3. **Namespace-Isolation:** Jeder Lehrer hat einen eigenen, abgeschotteten Bereich (Namespace). Kein Lehrer kann auf die Daten eines anderen zugreifen.
|
||||||
|
4. **KI-Korrektur:** Die KI arbeitet mit den verschluesselten Daten und dem Erwartungshorizont (EH) des Lehrers. Korrekturvorschlaege gehen zurueck an den Lehrer.
|
||||||
|
|
||||||
|
!!! info "Kern-Designprinzip: Operator Blindness"
|
||||||
|
**Breakpilot kann die Klausuren nicht lesen.** Der Server sieht nur verschluesselte Daten und einen Schluessel-Hash (nicht den Schluessel selbst). Die Passphrase zum Entschluesseln existiert *nur* im Browser des Lehrers und wird niemals uebertragen. Selbst ein Angriff auf den Server wuerde keine Klausurtexte preisgeben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Der komplette Ablauf im Ueberblick
|
||||||
|
|
||||||
|
Der Prozess laesst sich in sieben Schritte unterteilen. Stellen Sie sich vor, der Lehrer sitzt an seinem Rechner und hat einen Stapel gescannter Klausuren:
|
||||||
|
|
||||||
|
```text title="Der komplette Workflow: Von der Klausur zur KI-Korrektur"
|
||||||
|
SCHRITT 1: KLAUSUREN SCANNEN & HOCHLADEN
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Lehrer scannt Klausuren ein (PDF oder Bild)
|
||||||
|
→ System erkennt automatisch den Kopfbereich mit Namen
|
||||||
|
→ Kopfbereich wird permanent entfernt (Header-Redaction)
|
||||||
|
→ Jede Klausur erhaelt einen zufaelligen Code (doc_token)
|
||||||
|
|
||||||
|
SCHRITT 2: VERSCHLUESSELUNG IM BROWSER
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Lehrer gibt eine Passphrase ein (z.B. "MeinGeheimesPasswort2025!")
|
||||||
|
→ Browser leitet daraus einen 256-Bit-Schluessel ab (PBKDF2)
|
||||||
|
→ Klausur wird mit AES-256-GCM verschluesselt
|
||||||
|
→ Nur der Hash des Schluessels wird an den Server gesendet
|
||||||
|
→ Passphrase und Schluessel verlassen NIEMALS den Browser
|
||||||
|
|
||||||
|
SCHRITT 3: IDENTITAETS-MAP SICHERN
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Die Zuordnung "doc_token → Schuelername" wird verschluesselt:
|
||||||
|
→ Tabelle: "a7f3c2d1... = Max Mustermann, b9e4a1f8... = Anna Schmidt"
|
||||||
|
→ Diese Tabelle wird mit dem gleichen Schluessel verschluesselt
|
||||||
|
→ Ohne Passphrase kann niemand die Zuordnung wiederherstellen
|
||||||
|
|
||||||
|
SCHRITT 4: UPLOAD IN DEN NAMESPACE
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Die verschluesselten Dateien gehen in den persoenlichen Namespace:
|
||||||
|
→ Jeder Lehrer hat eine eigene tenant_id
|
||||||
|
→ Daten werden in MinIO (verschluesselt) + Qdrant (Vektoren) gespeichert
|
||||||
|
→ Server sieht: verschluesselter Blob + Schluessel-Hash + Salt
|
||||||
|
|
||||||
|
SCHRITT 5: KI-KORREKTUR
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Der Lehrer startet die KI-Korrektur:
|
||||||
|
→ RAG-System durchsucht den Erwartungshorizont (EH)
|
||||||
|
→ KI generiert Korrekturvorschlaege pro Kriterium
|
||||||
|
→ Vorschlaege basieren auf dem EH, nicht auf Halluzinationen
|
||||||
|
|
||||||
|
SCHRITT 6: ERGEBNISSE ZURUECK
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Korrekturvorschlaege gehen an den Lehrer:
|
||||||
|
→ Lehrer gibt Passphrase ein
|
||||||
|
→ Browser entschluesselt die Ergebnisse
|
||||||
|
→ Lehrer sieht: Vorschlaege pro Kriterium + Gesamtnote
|
||||||
|
|
||||||
|
SCHRITT 7: ZUORDNUNG & FINALISIERUNG
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Lehrer ordnet Ergebnisse den Schuelern zu:
|
||||||
|
→ Identitaets-Map wird entschluesselt
|
||||||
|
→ doc_token wird wieder dem echten Namen zugeordnet
|
||||||
|
→ Lehrer ueberprueft/korrigiert KI-Vorschlaege
|
||||||
|
→ Fertige Korrektur + Gutachten koennen exportiert werden
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Pseudonymisierung: Wie Namen verschwinden
|
||||||
|
|
||||||
|
Pseudonymisierung bedeutet: personenbezogene Daten werden durch **zufaellige Codes** ersetzt, sodass ohne Zusatzinformation kein Rueckschluss auf die Person moeglich ist. Im BYOEH-System passiert das auf zwei Ebenen:
|
||||||
|
|
||||||
|
### 3.1 Der doc_token: Ein zufaelliger Ausweis
|
||||||
|
|
||||||
|
Jede Klausur erhaelt einen **doc_token** -- einen 128-Bit-Zufallscode im UUID4-Format (z.B. `a7f3c2d1-4e9b-4a5f-8c7d-6b2e1f0a9d3c`). Dieser Code:
|
||||||
|
|
||||||
|
- Ist **kryptographisch zufaellig** -- es gibt keinen Zusammenhang zwischen Token und Schueler
|
||||||
|
- Kann **nicht zurueckgerechnet** werden -- auch mit Kenntnis des Algorithmus ist kein Rueckschluss moeglich
|
||||||
|
- Wird **auf der Klausur aufgedruckt** (als QR-Code), damit die physische Klausur spaeter wieder zugeordnet werden kann
|
||||||
|
|
||||||
|
### 3.2 Header-Redaction: Der Name wird entfernt
|
||||||
|
|
||||||
|
Bevor eine Klausur verarbeitet wird, entfernt das System den **Kopfbereich** der gescannten Seite -- dort, wo typischerweise Name, Klasse und Datum stehen. Diese Entfernung ist **permanent**: Die Originaldaten werden nicht gespeichert.
|
||||||
|
|
||||||
|
| Methode | Wie es funktioniert | Wann verwendet |
|
||||||
|
|---------|---------------------|----------------|
|
||||||
|
| **Einfache Redaction** | Obere ~2,5 cm der Seite werden weiss ueberschrieben | Standard bei allen Uploads |
|
||||||
|
| **Smarte Redaction** | OpenCV erkennt Textbereiche und entfernt gezielt den Kopf, verschont aber QR-Codes | Wenn QR-Codes auf der Klausur sind |
|
||||||
|
|
||||||
|
### 3.3 Die Identitaets-Map: Nur der Lehrer kennt die Zuordnung
|
||||||
|
|
||||||
|
Die Zuordnung *doc_token → Schuelername* wird als **verschluesselte Tabelle** gespeichert:
|
||||||
|
|
||||||
|
```text title="Datenbank: ExamSession (vereinfacht)"
|
||||||
|
ExamSession
|
||||||
|
├── teacher_id = "lehrer-uuid-123" ← Pflichtfeld (Isolation)
|
||||||
|
├── encrypted_identity_map = [verschluesselte Bytes] ← Nur mit Passphrase lesbar
|
||||||
|
├── identity_map_iv = "a3f2c1..." ← Initialisierungsvektor (fuer AES)
|
||||||
|
│
|
||||||
|
└── PseudonymizedDocument (pro Klausur)
|
||||||
|
├── doc_token = "a7f3c2d1-..." ← Zufaelliger Code (Primary Key)
|
||||||
|
├── exam_session_id = [Referenz]
|
||||||
|
└── (Kein Name, keine Klasse, kein persoenliches Datum)
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! success "DSGVO Art. 4 Nr. 5 konform"
|
||||||
|
Die Pseudonymisierung erfuellt die Definition aus der DSGVO: Die personenbezogenen Daten (Schuelernamen) koennen **ohne Hinzuziehung zusaetzlicher Informationen** (der verschluesselten Identitaets-Map + der Passphrase des Lehrers) nicht mehr einer bestimmten Person zugeordnet werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Verschluesselung: Wie Daten geschuetzt werden
|
||||||
|
|
||||||
|
Die Verschluesselung ist das Herzstueck des Datenschutzes. Sie findet **vollstaendig im Browser** statt -- der Server bekommt nur verschluesselte Daten zu sehen.
|
||||||
|
|
||||||
|
### 4.1 Der Verschluesselungsvorgang
|
||||||
|
|
||||||
|
Wenn der Lehrer eine Klausur oder einen Erwartungshorizont hochlaedt, passiert im Browser folgendes:
|
||||||
|
|
||||||
|
```text title="Client-seitige Verschluesselung (im Browser)"
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Browser des Lehrers │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 1. Lehrer gibt Passphrase ein (z.B. "MeinGeheimesPasswort!") │
|
||||||
|
│ │ ↑ │
|
||||||
|
│ │ │ Passphrase bleibt hier -- wird NIE gesendet │
|
||||||
|
│ ▼ │
|
||||||
|
│ 2. Schluessel-Ableitung: │
|
||||||
|
│ PBKDF2-SHA256(Passphrase, zufaelliger Salt, 100.000 Runden) │
|
||||||
|
│ │ │
|
||||||
|
│ │ → Ergebnis: 256-Bit-Schluessel (32 Bytes) │
|
||||||
|
│ │ → Selbst bei Kenntnis des Salts sind 100.000 Runden │
|
||||||
|
│ │ noetig, um den Schluessel zu erraten │
|
||||||
|
│ ▼ │
|
||||||
|
│ 3. Verschluesselung: │
|
||||||
|
│ AES-256-GCM(Schluessel, zufaelliger IV, Datei-Inhalt) │
|
||||||
|
│ │ │
|
||||||
|
│ │ → AES-256: Militaerstandard, 2^256 moegliche Schluessel │
|
||||||
|
│ │ → GCM: Garantiert Integritaet (Manipulation erkennbar) │
|
||||||
|
│ ▼ │
|
||||||
|
│ 4. Schluessel-Hash: │
|
||||||
|
│ SHA-256(abgeleiteter Schluessel) → Hash fuer Verifikation │
|
||||||
|
│ │ │
|
||||||
|
│ │ → Der Server speichert nur diesen Hash │
|
||||||
|
│ │ → Damit kann geprueft werden ob die Passphrase stimmt │
|
||||||
|
│ │ → Vom Hash kann der Schluessel NICHT zurueckgerechnet │
|
||||||
|
│ │ werden │
|
||||||
|
│ ▼ │
|
||||||
|
│ 5. Upload: Nur diese Daten gehen an den Server: │
|
||||||
|
│ • Verschluesselter Blob (unlesbar ohne Schluessel) │
|
||||||
|
│ • Salt (zufaellige Bytes, harmlos) │
|
||||||
|
│ • IV (Initialisierungsvektor, harmlos) │
|
||||||
|
│ • Schluessel-Hash (zur Verifikation, nicht umkehrbar) │
|
||||||
|
│ │
|
||||||
|
│ Was NICHT an den Server geht: │
|
||||||
|
│ ✗ Passphrase │
|
||||||
|
│ ✗ Abgeleiteter Schluessel │
|
||||||
|
│ ✗ Unverschluesselter Klartext │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Warum ist das sicher?
|
||||||
|
|
||||||
|
| Angriffsszenario | Was der Angreifer sieht | Ergebnis |
|
||||||
|
|------------------|-------------------------|----------|
|
||||||
|
| **Server wird gehackt** | Verschluesselte Blobs + Hashes | Keine lesbaren Klausuren |
|
||||||
|
| **Datenbank wird geleakt** | encrypted_identity_map (verschluesselt) | Keine Schuelernamen |
|
||||||
|
| **Netzwerkverkehr abgefangen** | Verschluesselte Daten (HTTPS + AES) | Doppelt verschluesselt |
|
||||||
|
| **Betreiber will mitlesen** | Verschluesselte Blobs, kein Schluessel | Operator Blindness |
|
||||||
|
| **Anderer Lehrer versucht Zugriff** | Nichts (Tenant-Isolation) | Namespace blockiert |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Namespace-Isolation: Jeder Lehrer hat seinen eigenen Bereich
|
||||||
|
|
||||||
|
Ein **Namespace** (auch "Tenant" genannt) ist ein abgeschotteter Bereich im System. Man kann es sich wie **separate Schliessfaecher** in einer Bank vorstellen: Jeder Lehrer hat sein eigenes Fach, und kein Schluessel passt in ein anderes Fach.
|
||||||
|
|
||||||
|
### 5.1 Wie die Isolation funktioniert
|
||||||
|
|
||||||
|
Jeder Lehrer erhaelt beim ersten Login eine eindeutige `tenant_id`. Diese ID wird bei **jeder einzelnen Datenbankabfrage** als Pflichtfilter mitgefuehrt:
|
||||||
|
|
||||||
|
```text title="Tenant-Isolation in der Vektordatenbank (Qdrant)"
|
||||||
|
Lehrer A (tenant_id: "school-A-lehrer-1")
|
||||||
|
├── Klausur 1 (verschluesselt)
|
||||||
|
├── Klausur 2 (verschluesselt)
|
||||||
|
└── Erwartungshorizont Deutsch LK 2025
|
||||||
|
|
||||||
|
Lehrer B (tenant_id: "school-B-lehrer-2")
|
||||||
|
├── Klausur 1 (verschluesselt)
|
||||||
|
└── Erwartungshorizont Mathe GK 2025
|
||||||
|
|
||||||
|
Suchanfrage von Lehrer A:
|
||||||
|
"Wie soll die Einleitung strukturiert sein?"
|
||||||
|
→ Suche NUR in tenant_id = "school-A-lehrer-1"
|
||||||
|
→ Lehrer B's Daten sind UNSICHTBAR
|
||||||
|
|
||||||
|
Es gibt KEINE Abfrage ohne tenant_id-Filter.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Drei Ebenen der Isolation
|
||||||
|
|
||||||
|
| Ebene | System | Isolation |
|
||||||
|
|-------|--------|-----------|
|
||||||
|
| **Dateisystem** | MinIO (S3-Storage) | Eigener Ordner pro Lehrer: `/tenant-id/eh-id/encrypted.bin` |
|
||||||
|
| **Vektordatenbank** | Qdrant | Pflichtfilter `tenant_id` bei jeder Suche |
|
||||||
|
| **Metadaten-DB** | PostgreSQL | Jede Tabelle hat `teacher_id` als Pflichtfeld |
|
||||||
|
|
||||||
|
!!! warning "Kein Training mit Lehrerdaten"
|
||||||
|
Auf allen Vektoren in Qdrant ist das Flag `training_allowed: false` gesetzt. Das bedeutet: Die Inhalte der Lehrer werden **ausschliesslich fuer RAG-Suchen** (Abruf relevanter Textpassagen) verwendet und **niemals zum Trainieren** eines KI-Modells eingesetzt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Der Erwartungshorizont: Die Grundlage fuer KI-Korrektur
|
||||||
|
|
||||||
|
Ein **Erwartungshorizont** (EH) ist das Dokument, das beschreibt, was in einer Klausur erwartet wird: welche Inhalte in welcher Qualitaet vorkommen sollen. Im BYOEH-System laedt der Lehrer seinen eigenen EH hoch, und die KI nutzt ihn als Referenz fuer Korrekturvorschlaege.
|
||||||
|
|
||||||
|
### 6.1 Upload-Wizard (5 Schritte)
|
||||||
|
|
||||||
|
| Schritt | Was passiert | Warum |
|
||||||
|
|---------|--------------|-------|
|
||||||
|
| **1. Datei waehlen** | PDF per Drag & Drop hochladen | Der EH als digitales Dokument |
|
||||||
|
| **2. Metadaten** | Titel, Fach, Niveau (eA/gA), Jahr | Fuer Filterung und Organisation |
|
||||||
|
| **3. Rechtebestaetigung** | Checkbox: "Ich bin berechtigt" | Rechtliche Absicherung (Urheberrecht) |
|
||||||
|
| **4. Verschluesselung** | Passphrase eingeben (2x bestaetigen) | Schluessel fuer Ende-zu-Ende-Verschluesselung |
|
||||||
|
| **5. Zusammenfassung** | Pruefen und bestaetigen | Letzte Kontrolle vor dem Upload |
|
||||||
|
|
||||||
|
### 6.2 RAG-Pipeline: Wie der EH fuer die KI nutzbar wird
|
||||||
|
|
||||||
|
Nach dem Upload wird der EH fuer die KI-Suche vorbereitet. Dieser Vorgang heisst **Indexierung** und funktioniert wie das Erstellen eines Stichwortverzeichnisses fuer ein Buch:
|
||||||
|
|
||||||
|
```text title="Indexierung: Vom PDF zum durchsuchbaren EH"
|
||||||
|
Erwartungshorizont (verschluesselt auf Server)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 1. Passphrase-Verifikation │ ← Lehrer gibt Passphrase ein
|
||||||
|
│ Hash pruefen │ Server vergleicht mit gespeichertem Hash
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 2. Entschluesselung │ ← Temporaer im Arbeitsspeicher
|
||||||
|
│ AES-256-GCM Decrypt │ (wird nach Verarbeitung geloescht)
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 3. Text-Extraktion │ ← PDF → Klartext
|
||||||
|
│ Tabellen, Listen erkennen │
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 4. Chunking │ ← Text in 1.000-Zeichen-Abschnitte zerlegen
|
||||||
|
│ Ueberlappung: 200 Zeichen │ (mit Ueberlappung fuer Kontext)
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 5. Embedding │ ← Jeder Abschnitt wird in einen
|
||||||
|
│ Text → 1.536 Zahlen │ Bedeutungsvektor umgewandelt
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 6. Re-Encryption │ ← Jeder Chunk wird ERNEUT verschluesselt
|
||||||
|
│ AES-256-GCM pro Chunk │ bevor er gespeichert wird
|
||||||
|
└──────────┬─────────────────────┘
|
||||||
|
|
|
||||||
|
v
|
||||||
|
┌────────────────────────────────┐
|
||||||
|
│ 7. Qdrant-Indexierung │ ← Vektor + verschluesselter Chunk
|
||||||
|
│ tenant_id: "lehrer-123" │ werden mit Tenant-Filter gespeichert
|
||||||
|
│ training_allowed: false │
|
||||||
|
└────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Wie die KI den EH nutzt (RAG-Query)
|
||||||
|
|
||||||
|
Wenn der Lehrer bei der Korrektur einen Vorschlag anfordert, passiert folgendes:
|
||||||
|
|
||||||
|
1. **Frage formulieren:** Das System erstellt eine Suchanfrage aus dem Klausurtext und dem aktuellen Bewertungskriterium.
|
||||||
|
2. **Semantische Suche:** Die Anfrage wird in einen Vektor umgewandelt und gegen die EH-Vektoren in Qdrant gesucht -- *nur im Namespace des Lehrers*.
|
||||||
|
3. **Entschluesselung:** Die gefundenen Chunks werden mit der Passphrase des Lehrers entschluesselt.
|
||||||
|
4. **KI-Antwort:** Die entschluesselten EH-Passagen werden als Kontext an die KI uebergeben, die daraus einen Korrekturvorschlag generiert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Key Sharing: Zweitkorrektur ermoeglichen
|
||||||
|
|
||||||
|
Bei Abiturklausuren muss eine **Zweitkorrektur** durch einen anderen Lehrer erfolgen. Das Key-Sharing-System ermoeglicht es dem Erstpruefer, seinen Erwartungshorizont sicher mit dem Zweitpruefer zu teilen -- ohne die Verschluesselung aufzugeben.
|
||||||
|
|
||||||
|
### 7.1 Einladungs-Workflow
|
||||||
|
|
||||||
|
```text title="Key Sharing: Sicheres Teilen zwischen Pruefern"
|
||||||
|
Erstpruefer Server Zweitpruefer
|
||||||
|
│ │ │
|
||||||
|
│ 1. Einladung senden │ │
|
||||||
|
│ (E-Mail + Rolle + Klausur) │ │
|
||||||
|
│─────────────────────────────────▶ │
|
||||||
|
│ │ │
|
||||||
|
│ │ 2. Einladung erstellt │
|
||||||
|
│ │ (14 Tage gueltig) │
|
||||||
|
│ │ │
|
||||||
|
│ │ 3. Benachrichtigung ──────▶│
|
||||||
|
│ │ │
|
||||||
|
│ │ 4. Annehmen
|
||||||
|
│ │◀─────────────────────────────│
|
||||||
|
│ │ │
|
||||||
|
│ │ 5. Key-Share erstellt │
|
||||||
|
│ │ │
|
||||||
|
│ │ 6. Zweitpruefer kann ──────▶│
|
||||||
|
│ │ RAG-Queries ausfuehren │
|
||||||
|
│ │ │
|
||||||
|
│ 7. Zugriff widerrufen │ │
|
||||||
|
│ (jederzeit moeglich) │ │
|
||||||
|
│─────────────────────────────────▶ │
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Rollen beim Key-Sharing
|
||||||
|
|
||||||
|
| Rolle | Wer | Rechte |
|
||||||
|
|-------|-----|--------|
|
||||||
|
| **Erstpruefer (EK)** | Kurslehrer | Vollzugriff, kann teilen & widerrufen |
|
||||||
|
| **Zweitpruefer (ZK)** | Anderer Fachlehrer | Nur Lesen, RAG-Queries, eigene Annotations |
|
||||||
|
| **Drittpruefer (DK)** | Bei Differenz ≥ 4 Punkte | Nur Lesen, RAG-Queries |
|
||||||
|
| **Fachvorsitz** | Fachbereichsleitung | Nur Lesen (Aufsichtsfunktion) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. KI-gestuetzte Bewertung: Wie die Korrektur funktioniert
|
||||||
|
|
||||||
|
Die KI bewertet jede Klausur anhand von **fuenf Kriterien**, die zusammen 100% ergeben:
|
||||||
|
|
||||||
|
| Kriterium | Gewichtung | Was geprueft wird |
|
||||||
|
|-----------|------------|-------------------|
|
||||||
|
| Rechtschreibung | 15% | Orthographie, Zeichensetzung |
|
||||||
|
| Grammatik | 15% | Satzbau, Kongruenz, Tempus |
|
||||||
|
| **Inhalt** | **40%** | Bezug zum EH, Vollstaendigkeit, Argumentation |
|
||||||
|
| Struktur | 15% | Gliederung, Einleitung/Schluss, roter Faden |
|
||||||
|
| Stil | 15% | Ausdruck, Wortwahl, Fachsprache |
|
||||||
|
|
||||||
|
Die Bewertung folgt dem **15-Punkte-System** (0-15 Notenpunkte) der gymnasialen Oberstufe.
|
||||||
|
|
||||||
|
!!! info "KI schlaegt vor, Lehrer entscheidet"
|
||||||
|
Alle KI-Bewertungen sind **Vorschlaege**. Der Lehrer hat bei jedem Kriterium die volle Kontrolle: Er kann den Vorschlag annehmen, aendern oder komplett ueberschreiben. Die finale Note setzt immer der Lehrer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Audit-Trail: Alles wird protokolliert
|
||||||
|
|
||||||
|
Jede Aktion im System wird revisionssicher im **Audit-Log** gespeichert. Das ist wichtig fuer die Nachvollziehbarkeit und fuer den Fall, dass Schueler oder Eltern eine Korrektur anfechten.
|
||||||
|
|
||||||
|
| Aktion | Was protokolliert wird |
|
||||||
|
|--------|------------------------|
|
||||||
|
| `upload` | EH hochgeladen (Dateigroesse, Metadaten, Zeitstempel) |
|
||||||
|
| `index` | EH fuer RAG indexiert (Anzahl Chunks, Dauer) |
|
||||||
|
| `rag_query` | RAG-Suchanfrage ausgefuehrt (Query-Hash, Anzahl Ergebnisse, Score) |
|
||||||
|
| `share` | EH mit anderem Pruefer geteilt (Empfaenger, Rolle) |
|
||||||
|
| `revoke_share` | Zugriff widerrufen (wer, wann) |
|
||||||
|
| `link_klausur` | EH mit Klausur verknuepft |
|
||||||
|
| `delete` | EH geloescht (Soft Delete, bleibt in Logs) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. API-Endpunkte (Technische Referenz)
|
||||||
|
|
||||||
|
Alle Endpunkte laufen ueber den **klausur-service** auf Port 8086.
|
||||||
|
|
||||||
|
### 10.1 Erwartungshorizont-Verwaltung
|
||||||
|
|
||||||
|
| Methode | Endpunkt | Beschreibung |
|
||||||
|
|---------|----------|--------------|
|
||||||
|
| `POST` | `/api/v1/eh/upload` | Verschluesselten EH hochladen |
|
||||||
|
| `GET` | `/api/v1/eh` | Eigene EHs auflisten |
|
||||||
|
| `GET` | `/api/v1/eh/{id}` | Einzelnen EH abrufen |
|
||||||
|
| `DELETE` | `/api/v1/eh/{id}` | EH loeschen (Soft Delete) |
|
||||||
|
| `POST` | `/api/v1/eh/{id}/index` | EH fuer RAG indexieren |
|
||||||
|
| `POST` | `/api/v1/eh/rag-query` | RAG-Suchanfrage ausfuehren |
|
||||||
|
|
||||||
|
### 10.2 Key Sharing
|
||||||
|
|
||||||
|
| Methode | Endpunkt | Beschreibung |
|
||||||
|
|---------|----------|--------------|
|
||||||
|
| `POST` | `/api/v1/eh/{id}/share` | EH mit Pruefer teilen |
|
||||||
|
| `GET` | `/api/v1/eh/{id}/shares` | Geteilte Zugriffe auflisten |
|
||||||
|
| `DELETE` | `/api/v1/eh/{id}/shares/{shareId}` | Zugriff widerrufen |
|
||||||
|
| `GET` | `/api/v1/eh/shared-with-me` | Mit mir geteilte EHs |
|
||||||
|
|
||||||
|
### 10.3 Klausur-Integration
|
||||||
|
|
||||||
|
| Methode | Endpunkt | Beschreibung |
|
||||||
|
|---------|----------|--------------|
|
||||||
|
| `POST` | `/api/v1/eh/{id}/link-klausur` | EH mit Klausur verknuepfen |
|
||||||
|
| `DELETE` | `/api/v1/eh/{id}/link-klausur/{klausurId}` | Verknuepfung loesen |
|
||||||
|
| `GET` | `/api/v1/klausuren/{id}/linked-eh` | Verknuepften EH abrufen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Dateistruktur im Code
|
||||||
|
|
||||||
|
```text title="Relevante Dateien im Repository"
|
||||||
|
klausur-service/
|
||||||
|
├── backend/
|
||||||
|
│ ├── main.py # API-Endpunkte + Datenmodelle
|
||||||
|
│ ├── qdrant_service.py # Vektordatenbank-Operationen (Tenant-Filter)
|
||||||
|
│ └── eh_pipeline.py # Chunking, Embedding, Verschluesselung
|
||||||
|
│
|
||||||
|
├── frontend/
|
||||||
|
│ └── src/
|
||||||
|
│ ├── components/
|
||||||
|
│ │ └── EHUploadWizard.tsx # 5-Schritt-Upload-Wizard
|
||||||
|
│ └── services/
|
||||||
|
│ ├── api.ts # API-Client
|
||||||
|
│ └── encryption.ts # Client-seitige Kryptographie
|
||||||
|
│
|
||||||
|
└── docs/
|
||||||
|
├── BYOEH-Architecture.md # Technische Architektur
|
||||||
|
└── BYOEH-Developer-Guide.md # Entwickler-Handbuch
|
||||||
|
|
||||||
|
backend/klausur/
|
||||||
|
├── db_models.py # ExamSession, PseudonymizedDocument
|
||||||
|
└── services/
|
||||||
|
└── pseudonymizer.py # QR-Codes, Header-Redaction, doc_tokens
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Zusammenfassung: Die Sicherheitsgarantien
|
||||||
|
|
||||||
|
| Garantie | Wie umgesetzt | Regelwerk |
|
||||||
|
|----------|---------------|-----------|
|
||||||
|
| **Kein Name verlaesst den Rechner** | Header-Redaction + verschluesselte Identity-Map | DSGVO Art. 4 Nr. 5 |
|
||||||
|
| **Betreiber kann nicht mitlesen** | Client-seitige AES-256-GCM Verschluesselung | DSGVO Art. 32 |
|
||||||
|
| **Kein Zugriff durch andere Lehrer** | Tenant-Isolation (Namespace) auf allen 3 Ebenen | DSGVO Art. 25 |
|
||||||
|
| **Kein KI-Training mit Schuelerdaten** | `training_allowed: false` auf allen Vektoren | AI Act Art. 10 |
|
||||||
|
| **Alles nachvollziehbar** | Vollstaendiger Audit-Trail aller Aktionen | DSGVO Art. 5 Abs. 2 |
|
||||||
|
| **Lehrer behaelt volle Kontrolle** | KI-Vorschlaege, keine KI-Entscheidungen + jederzeitiger Widerruf | DSGVO Art. 22 |
|
||||||
|
|
||||||
|
!!! success "Das Wichtigste in einem Satz"
|
||||||
|
Das BYOEH-System ermoeglicht KI-gestuetzte Klausurkorrektur, bei der **kein Schuelername den Rechner des Lehrers verlaesst**, alle Daten **Ende-zu-Ende verschluesselt** sind, jeder Lehrer seinen **eigenen abgeschotteten Namespace** hat, und die KI **nur Vorschlaege macht** -- die finale Bewertung trifft immer der Lehrer.
|
||||||
@@ -77,6 +77,7 @@ nav:
|
|||||||
- Architektur: services/ki-daten-pipeline/architecture.md
|
- Architektur: services/ki-daten-pipeline/architecture.md
|
||||||
- Klausur-Service:
|
- Klausur-Service:
|
||||||
- Uebersicht: services/klausur-service/index.md
|
- Uebersicht: services/klausur-service/index.md
|
||||||
|
- BYOEH Systemerklaerung: services/klausur-service/byoeh-system-erklaerung.md
|
||||||
- BYOEH Architektur: services/klausur-service/BYOEH-Architecture.md
|
- BYOEH Architektur: services/klausur-service/BYOEH-Architecture.md
|
||||||
- BYOEH Developer Guide: services/klausur-service/BYOEH-Developer-Guide.md
|
- BYOEH Developer Guide: services/klausur-service/BYOEH-Developer-Guide.md
|
||||||
- NiBiS Pipeline: services/klausur-service/NiBiS-Ingestion-Pipeline.md
|
- NiBiS Pipeline: services/klausur-service/NiBiS-Ingestion-Pipeline.md
|
||||||
|
|||||||
Reference in New Issue
Block a user