Files
lernbar/ENTSCHEIDUNGEN.md
T
Florian WildeandClaude Opus 5 ffbca01bdc Baustein 5: Praxisaufgaben mit Anhaengen und Abnahme
- Status offen / in Arbeit / zur Abnahme / abgenommen, mit Zeitstempeln
- Notizfeld fuer den Azubi, Dateianhaenge bis 25 MB
- Speicherpfad nur aus selbst erzeugter UUID, nie aus dem Dateinamen
- Auslieferung ueber /anhaenge/<kennung> mit Rechtepruefung bei jedem Abruf,
  CSP sandbox und nosniff; SVG ausdruecklich nicht erlaubt
- Anhaenge haengen an Aufgabe UND Azubi
- Ausbilder hakt Kriterien einzeln ab, Note und Rueckmeldung, Abnahme sperrt
- Zurueckgeben zur Nacharbeit
- "Was auf dich wartet" zeigt offene Abnahmen
- Abgenommene Aufgaben zaehlen im Wochenfortschritt mit

Behoben: Next.js deckelt Serveraktionen ab Werk bei 1 MB - damit waere kein
Handyfoto durchgekommen. bodySizeLimit jetzt 30 MB, Vorabpruefung im Browser,
client_max_body_size im Proxy-Beispiel auf 30m.

Geaendert: Seiten verweigern fremde Daten mit 404 statt mit einem Fehler.
Eigene Seiten fuer "Nicht gefunden" und Stoerfaelle.

24 neue Tests, insgesamt 157.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 12:18:54 +02:00

9.7 KiB
Raw Blame History

Entscheidungen

Kurze Notizen zu Festlegungen, die im Auftrag offen waren. Jede Notiz nennt die Lage, die Wahl und den Grund.


E1 · Stack: Next.js 15 mit App Router

Der Auftrag verweist auf eine Stackvorgabe im „Kopf des Prompts", die nicht mitgeliefert wurde. Auf Rückfrage: Next.js 15 (App Router), React 19, TypeScript. Datenzugriff über Drizzle ORM auf better-sqlite3.

Serveraktionen ersetzen eine eigene REST-Schicht. Das hält die Rechteprüfung dort, wo die Daten geholt werden, statt sie über eine API zu verteilen.

E2 · Zeitstempel als ISO-8601 in UTC

Der Auftrag verlangt „alle Zeitstempel mit Zeitzone". Gespeichert wird 2026-09-10T07:15:00.000Z, also ein eindeutiger Zeitpunkt in UTC. Angezeigt wird durchgehend in Europe/Berlin.

Grund: UTC-Strings sortieren sich lexikografisch richtig, was bei SQLite ohne echten Zeitstempeltyp entscheidend ist. Ein gespeicherter Ortsversatz (+02:00) würde beim Wechsel zwischen Sommer- und Winterzeit falsch sortieren.

Reine Kalenderdaten – Berichtszeitraum, Tagesnotiz, Ausbildungsbeginn – sind YYYY-MM-DD ohne Uhrzeit, weil sie auch keine haben.

E3 · Eigener Migrationslauf statt drizzle-kit

Migrationen sind handgeschriebene SQL-Dateien unter src/lib/db/migrationen/, die beim Start der Reihe nach genau einmal angewendet und mit SHA-256-Prüfsumme vermerkt werden.

Grund: volle Kontrolle über Indizes und CHECK-Bedingungen (Statuswerte sind Aufzählungen, keine freien Zeichenketten), und beim Start ist im Protokoll sichtbar, was passiert ist. Wird eine bereits angewendete Datei nachträglich verändert, bricht der Start mit klarer Meldung ab.

Der Preis: das Drizzle-Schema muss von Hand zu den SQL-Dateien passen. Dagegen läuft ein Test, der die migrierte Datenbank gegen das Schema abgleicht.

E4 · Schlüssel sind UUIDs, keine laufenden Nummern

Textspalten mit crypto.randomUUID(). Läuft auf SQLite und PostgreSQL gleichermaßen, und beim Zurückspielen einer Sicherung gibt es keine Kollisionen mit zwischenzeitlich vergebenen Nummern.

E5 · Debian-slim als Grundlage des Abbilds, nicht Alpine

better-sqlite3 und @node-rs/argon2 bringen für glibc fertige Binärdateien mit, für musl nicht. Auf Alpine müsste im Abbild übersetzt werden – längerer Bau, größere Bauschicht, mehr Fehlerquellen. Der Größenunterschied im Laufzeitabbild ist bei einem selbst gehosteten Dienst ohne Belang.

E6 · Passwortprüfung mit gleichbleibender Antwortzeit

Bei unbekannter E-Mail-Adresse wird trotzdem einmal gegen einen festen Argon2-Wert gerechnet. Sonst verrät die Antwortzeit, welche Adressen es gibt. Die Fehlermeldung ist in beiden Fällen dieselbe.

E7 · Anhänge liegen im Dateisystem, nicht in der Datenbank

Unter <daten>/anhaenge/, in der Datenbank steht nur der Verweis. Hält die Datenbankdatei klein und die Sicherung schnell; das Archiv nimmt beides mit.

E8 · Sicherung als Shell-Skript, nicht als Node-Werkzeug

scripts/im-container/sicherung.sh läuft im Laufzeitabbild, das kein TypeScript kennt, und auf dem Entwicklungsrechner unter Git Bash. Eine zweite Fassung in TypeScript wäre eine zweite Fehlerquelle. Die konsistente Kopie der Datenbank entsteht über die Sicherungsschnittstelle von SQLite – ein cp wäre bei aktivem WAL-Modus nicht verlässlich.

E9 · Kein main-Zweig ohne Freigabe

Die verbindliche Arbeitsweise verbietet Commits direkt auf main. Die Erstfassung entsteht deshalb vollständig auf feat/lernbar-erstfassung. main bleibt leer, bis eine Freigabe erfolgt.


E10 · Rahmenplan: A1–A10, B1–B3, C1–C5

Der Auftrag nennt die Berufsbildpositionen A 1 bis A 10, B 1 bis B 3 und C 1 bis C 4. Die Verordnung selbst gliedert anders: Sie deckt alle vier Fachrichtungen ab und hat deshalb sechs Abschnitte. Übersetzt:

Kürzel hier Abschnitt der FIAusbV Inhalt
A1 – A10 Abschnitt A fachrichtungsübergreifend
B1 – B3 Abschnitt C Fachrichtung Systemintegration
C1 – C5 Abschnitt F integrativ zu vermitteln

Abweichung: Abschnitt F hat fünf Positionen, nicht vier. Die fünfte („Vernetztes Zusammenarbeiten unter Nutzung digitaler Medien") ist 2020 neu dazugekommen. Sie wegzulassen würde ausgerechnet in der Abdeckungsansicht eine Lücke erzeugen, die gegenüber der IHK zählt. Deshalb sind alle fünf drin.

Jede Position trägt im Feld verordnung ihre amtliche Fundstelle, damit die Übersetzung nachvollziehbar bleibt.

E11 · Kennung einer Merkkarte: Handout-Kürzel plus laufende Nummer

Eine Karte heißt N3-1, N3-2 und so weiter – abgeleitet aus dem Handout und der Reihenfolge im YAML-Kopf. Damit steht die Kennung fest, ohne dass sie in der Datei gepflegt werden muss.

Der Preis: Wird eine Karte mittendrin eingefügt oder gelöscht, verschieben sich die Kennungen der folgenden, und ihr Lernstand rutscht mit. Neue Karten gehören deshalb ans Ende der Liste. Bei einem Handout mit ein bis drei Karten ist das zumutbar; eine gepflegte Kennung je Karte wäre mehr Aufwand für die Person, die Inhalte schreibt, als sie einbringt.

Der Auftrag lässt die Wahl zwischen automatischer Verlinkung und einer Erklärung beim Überfahren mit der Maus. Es wird beides, aber der Link führt: Ein reiner title-Hinweis ist am Handy nicht erreichbar und mit der Tastatur nur mit Mühe. Der Verweis zeigt auf die Stelle im Glossar und trägt die Erklärung zusätzlich im title.

Zurückhaltung ist eingebaut: Jeder Begriff wird höchstens einmal je Seite verlinkt, und nie innerhalb von Code, Überschriften, Tabellenköpfen oder bestehenden Verweisen. Ein Text, in dem jedes zweite Wort unterstrichen ist, liest sich schlechter als einer ganz ohne.

E13 · Kein gray-matter

Der YAML-Kopf einer Markdown-Datei wird selbst herausgelöst (zehn Zeilen). Gründe: gray-matter merkt sich Ergebnisse und liefert beim zweiten Einlesen desselben Textes ein Objekt ohne das Feld matter – im Betrieb fällt das nie auf, im Test sofort. Und der Zeilenversatz für die Fehlermeldungen ist so nachweislich richtig statt nachgebaut.

E14 · SM-2 mit drei Bewertungsstufen statt sechs

Das Original arbeitet mit einer Skala von 0 bis 5. In der Praxis unterscheidet niemand ehrlich zwischen „fast richtig" und „mit Mühe richtig" – und eine Selbstbewertung, die man nicht ehrlich abgeben kann, verfälscht die Rechnung.

Deshalb: sitzt / unsicher / nochmal. Die Leichtigkeit bewegt sich weiterhin zwischen 1,3 und 2,8, die Abstände folgen 1, 3, dann Abstand mal Leichtigkeit. Gedeckelt wird bei 180 Tagen – die Ausbildung dauert drei Jahre, weiter zu planen bringt nichts.

E15 · „nochmal" bringt die Karte in derselben Sitzung wieder

Erste Fassung legte sie nur auf den nächsten Tag. Das ist falsch: Wer „nochmal" drückt, will die Karte jetzt noch einmal sehen. Sie wird deshalb hinten an den laufenden Stapel gehängt.

E16 · Der tägliche Stapel wird bei 20 Karten gedeckelt

Ohne Deckel sitzt jemand nach drei Wochen Pause vor 52 fälligen Karten und macht es gar nicht. Sortiert wird nach Überfälligkeit; nie gelernte Karten kommen zuletzt, damit zuerst der Rückstand abgebaut wird.

E17 · Als „gelernt" gilt eine Karte ab Fach 4

Fach 4 heißt: Abstand über drei Tage, also mindestens dreimal in Folge gekonnt. Das ist die Schwelle, ab der die Karte im Wochenfortschritt als erledigt zählt. Wer sie einmal richtig hatte, hat sie noch nicht gelernt.

E18 · Anhänge: Pfad aus UUID, Auslieferung nur über eine geprüfte Route

Drei Regeln, die zusammengehören:

  1. Der Speicherpfad ist <jahr>/<uuid><endung>. Der übermittelte Dateiname landet nur als Anzeigename in der Datenbank und wird nie zum Bilden eines Pfades benutzt. speicherpfad() wirft, wenn die Kennung keine UUID ist.
  2. Ausgeliefert wird ausschließlich über /anhaenge/<kennung>. Es gibt keinen Weg, über die Adresse eine beliebige Datei anzufordern, und die Berechtigung wird bei jedem Abruf neu geprüft – nicht nur beim Hochladen.
  3. Der Antwortkopf trägt Content-Security-Policy: sandbox und X-Content-Type-Options: nosniff. Anhänge sind Daten, kein Programm.

SVG ist trotz image/ nicht erlaubt: Es ist XML und kann Skripte enthalten.

E19 · Anhänge hängen an Aufgabe und Azubi

Der Bezug lautet <aufgabeId>::<azubiId>. Beim ersten Entwurf hing er nur an der Aufgabe – bei zwei Azubis hätte der zweite die Fotos des ersten gesehen.

E20 · Kriterien werden über ihre Position gespeichert

kriterien_erfuellt ist eine JSON-Liste der Positionen in der Aufgabendatei, etwa [0,2,3]. Wird die Reihenfolge der Kriterien nachträglich geändert, verschiebt sich die Zuordnung. Neue Kriterien gehören deshalb ans Ende – dieselbe Regel wie bei den Merkkarten (siehe E11).

E21 · Fehlende Berechtigung ist ein 404, kein 403

Seiten rufen erfordereSichtAufAzubi() auf, das mit „nicht gefunden" endet. Ein 403 würde bestätigen, dass es die angefragten Daten gibt. Serveraktionen brechen weiterhin mit einem Fehler ab – dort wäre eine Weiterleitung sinnlos.

Nebeneffekt: Next.js schwärzt Fehlermeldungen im Produktionsbetrieb. Eine eigene „Kein Zugriff"-Seite ließe sich anhand des Fehlertextes gar nicht erkennen.

E22 · Der Deckel für Serveraktionen liegt über der eigenen Grenze

serverActions.bodySizeLimit steht auf 30 MB, die eigene Prüfung auf 25 MB. Der Abstand ist Absicht: Eine zu große Datei soll die eigene, verständliche Meldung bekommen und nicht den Rumpfgrößenfehler des Gerüsts, der nur im Serverprotokoll auftaucht. Zusätzlich prüft der Browser vorab, damit am Mobilfunk nicht erst 30 MB übertragen und dann abgelehnt werden.