Ein Zustand je Quelle haelt fest, wie weit gelesen wurde und ob es
klappt. Der Merker ist bewusst ein freier Text: Zammad braucht darin
einen Zeitstempel, Telegram den Zaehler der zuletzt geholten Nachricht.
Dazu die gespiegelte Belegung ohne Titel, das Adressbuch der erlaubten
Telegram-Chats und der Einmalcode, mit dem sich ein Chat anmeldet.
Welche Integration eine Instanz darf, entscheidet weiterhin INSTANZ. Ein
Zammad-Token in der Instanz daheim bleibt wirkungslos — das steht in den
Faehigkeiten, nicht in einer Einstellung, die sich verstellen liesse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Tabellen: die Einstellung je Benutzer, angemeldete Push-Geraete und
ein Nachweis je verschickter Meldung. Der Nachweis ist das Gedaechtnis
gegen Wiederholungen — ohne ihn kaeme dieselbe Sache bei jedem Durchlauf
erneut.
Kalenderkonten bekommen einen Zaehler fuer Fehlversuche in Folge, damit
ein Abgleichfehler erst nach mehreren Versuchen gemeldet wird.
VAPID- und SMTP-Werte sind freiwillig: fehlen sie, laeuft der Planer
ohne den jeweiligen Weg weiter und sagt es in den Einstellungen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Zeitblock ist eine Aufgabe, die zu einer bestimmten Zeit in einem
Kalender steht. Er lebt an zwei Orten — hier und beim Anbieter — und beide
Seiten dürfen ihn verschieben. Daraus entsteht die Gefahr einer
Endlosschleife: Die App schreibt, der Abgleich liest die eigene Änderung,
hält sie für fremd, schreibt zurück, der Abgleich liest wieder.
Der Ausweg ist ein Fingerabdruck über den Zeitraum, sha256(startISO|endeISO).
Beim Schreiben merkt sich der Zeitblock, was er in den Kalender gelegt hat;
kommt derselbe Fingerabdruck zurück, war es die eigene Änderung. Weicht er
ab, hat jemand anderes verschoben — und dann gewinnt der Kalender.
Bewusst nur über Beginn und Ende: Titel und Beschreibung ändern sich auch
aus harmlosen Gründen, und für die Planung zählt allein, wann der Block
liegt.
30 Tests, darunter zwei, die den eigentlichen Zweck prüfen: dass es sich
über mehrere Runden nicht hochschaukelt und nach einer übernommenen
fremden Verschiebung wieder zur Ruhe kommt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kalenderkonto mit Tokens und Abgleichverweis, Termin als örtlicher Spiegel
mit genau so viel, wie die Belegungsrechnung und die Wochenansicht
brauchen. Bereich bekommt sein Standard-Kalenderkonto für M4.
Die Tokens liegen verschlüsselt (AES-256-GCM). Wer die Datenbank oder eine
Sicherung in die Hände bekommt, hätte sonst direkten Zugriff auf fremde
Kalender — das wiegt schwerer als bei den OIDC-Tokens aus M1, die nur die
Anmeldung betreffen. Der Schlüssel wird aus AUTH_SECRET abgeleitet statt
separat konfiguriert: eine weitere Pflichtvariable wäre eine weitere
Stelle, an der die Einrichtung scheitert.
kalenderId ist nicht optional, sondern trägt den Wert "standard" für den
Standardkalender — Prisma lässt in einem zusammengesetzten Unique-Schlüssel
kein null zu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aufgabe mit Typ, Status, Priorität, Aufwand, Fälligkeit und Herkunft;
Projekt als eigene Klammer darum.
Zwei Entwurfsentscheidungen:
- Die Inbox ist kein eigener Zustand, sondern schlicht eine Aufgabe ohne
Bereich. Einsortieren heißt: einen Bereich setzen. Ein zusätzliches
Feld könnte mit der Zuordnung auseinanderlaufen.
- Projekt ist ein eigenes Modell, kein Aufgabentyp. Ein Projekt hat
selbst weder Aufwand noch Fälligkeit, sondern leitet beides aus dem
ab, was drinsteckt. ARBEITSPAKET und MEILENSTEIN sind Typen der
Aufgaben *innerhalb* eines Projekts.
@@unique([quelle, quelleRef]) ist die Grundlage für die Integrationen ab
M6: sie upserten darüber, statt zu doppeln. Von Hand erfasste Aufgaben
tragen quelleRef = null; PostgreSQL sieht NULL-Werte als verschieden an,
sie behindern sich also nicht.
Verworfene Aufgaben zählen bewusst gar nicht in den Fortschritt eines
Projekts — sonst sähe es fertig aus, weil die Hälfte gestrichen wurde.
Fälligkeit wird in Kalendertagen in Europe/Berlin gerechnet, nicht in
24-Stunden-Schritten: was heute um 23:00 fällig ist, ist heute fällig.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drei Wege hinein, je Instanz zuschaltbar: generischer OIDC-Anbieter
(Entra ID beziehungsweise authentik), Passkeys und wahlweise E-Mail mit
Passwort.
Entscheidungen:
- Passkeys laufen direkt über SimpleWebAuthn 14, nicht über den
experimentellen WebAuthn-Provider von Auth.js, der noch auf Fassung 9
festgelegt ist.
- Eigener Prisma-Adapter, weil der mitgelieferte fest auf prisma.user und
prisma.account zugreift, unsere Modelle aber Benutzer und Konto heißen.
- Sitzungen als JWT, weil Credentials-Provider das verlangen. Damit eine
Sperre trotzdem sofort wirkt, liest der session-Callback Rolle und
Status bei jeder Anfrage frisch aus der Datenbank. Die Tabellen sitzung
und verifizierungs_token entfallen deshalb wieder.
- allowDangerousEmailAccountLinking ist bewusst an: je Instanz gibt es
genau einen vertrauenswürdigen Anbieter, der die Adresse selbst prüft.
Anmeldung, Passwort und Passkey antworten bei Fehlschlag immer gleich,
damit sich nicht abfragen lässt, wer hier ein Konto hat. Die
WebAuthn-Herausforderung wird nach einem Versuch verworfen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Benutzer mit Rolle, OIDC-Konten, Sitzungen, Passkeys samt kurzlebiger
WebAuthn-Herausforderung, Bereiche mit Tageskapazität und Arbeitsfenster
sowie ein unveränderlicher Audit-Eintrag je schreibender Aktion.
Aufgabe und Zeitblock kommen mit M2 und M4, Kalenderkonto mit M3 — dann
mit den Feldern, die der jeweilige Provider wirklich braucht.
Prisma 7 nimmt die Verbindung nicht mehr aus dem Schema entgegen: die URL
steht in prisma.config.ts, der Client bekommt den pg-Treiberadapter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>