Commit Graph
124 Commits
Author SHA1 Message Date
florian.wilde 68dbc7cadb Merge branch 'phase/035-lokale-konten' 2026-10-12 22:36:30 +02:00
florian.wildeandClaude Opus 5.5 4219701b29 feat(anmeldung): lokale Benutzerkonten mit Pflicht-TOTP (#035)
Ohne authentik gab es nur den Notfall-Admin – ein geteiltes Konto ohne
Rollen und ohne erkennbare Akteure im Audit-Log. Admins legen jetzt
lokale Konten an; sshkv erzeugt ein Einmal-Passwort, das bei der ersten
Anmeldung ersetzt wird, danach ist TOTP Pflicht.

Rollen, Sperren (beendet Sitzungen sofort) und Zurücksetzen unter
Einstellungen → Benutzer, nur für Admins mit frischer Bestätigung.
SSHKV_LOKALE_KONTEN=0 schaltet lokale Konten ab; der Notfall-Admin
bleibt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:36:25 +02:00
florian.wilde d9eec3d991 Merge branch 'phase/034-devlog' 2026-10-12 20:11:40 +02:00
florian.wildeandClaude Opus 5.5 b3787f514c docs(devlog): Sitzung 7 mit #033 und #034 (#034)
Zeiten und Entscheidungen festhalten, bevor die Abnahme beginnt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:11:39 +02:00
florian.wilde 91c4880e40 Merge branch 'phase/034-abnahme-checkliste' 2026-10-12 20:11:16 +02:00
florian.wildeandClaude Opus 5.5 dc202594d3 docs(betrieb): Abnahme-Checkliste fuer 1.0.0 (#034)
Die automatischen Tests laufen gegen Mock-OIDC und Testcontainer.
Echter authentik, echte Hosts und der Betrieb brauchen eine eigene,
abhakbare Prüfung mit erwartetem Ergebnis je Punkt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:11:15 +02:00
florian.wilde 525eddb091 Merge branch 'phase/033-erneut-rotieren' 2026-10-12 20:09:48 +02:00
florian.wildeandClaude Opus 5.5 c06c051637 feat(rotation): nach Abbruch erneut rotieren (#033)
Bisher war vorgaengerId eindeutig: nach einer abgebrochenen Rotation
passte kein zweiter Nachfolger in die Kette, und der Schlüssel ließ sich
nur über Neuanlage, Zuweisen und Widerruf ersetzen.

Jetzt ist die Relation 1:n. Ein partieller Index erlaubt je Vorgänger
nur einen Nachfolger, der weder widerrufen ist noch gerade widerrufen
wird. Maßgeblich ist der jüngste. Erst wenn der Abbruch abgeschlossen
ist, beginnt die Rotation wieder bei Schritt 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:09:42 +02:00
florian.wilde c91e882159 Merge branch 'phase/032-abschluss-doku' 2026-10-12 18:08:05 +02:00
florian.wildeandClaude Opus 5.5 d34f8b6f54 docs(devlog): Sitzung 6, #031 und #032 nachgetragen (#032)
Zeiten und Entscheidungen der Sitzung festhalten, damit die Abnahme
von 1.0.0 den vollständigen Stand sieht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:08:03 +02:00
florian.wilde 844161dc1b Merge branch 'phase/032-nacharbeiten' 2026-10-12 17:56:33 +02:00
florian.wildeandClaude Opus 5.5 021e415fde docs(issues): #032 abgeschlossen, Entscheidungen notiert (#032)
Haelt fest, was die Nacharbeiten gefunden und entschieden haben:
den Fehler im Massenwiderruf (UUIDs fielen aus der Auswahl), das
Zustandsmodell des Rotationsabbruchs ohne neues Feld, warum es danach
keinen zweiten Nachfolger gibt, und wie die Protokoll-IDs ohne
Umnummerieren lueckenlos werden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:55:46 +02:00
florian.wildeandClaude Opus 5.5 f18fd4ec06 feat(rotation): laufende Rotation abbrechen (#032)
Bisher liess sich eine angefangene Rotation nur zu Ende fuehren. Wer
merkt, dass der neue Schluessel nicht passt, braucht einen Weg zurueck,
ohne den alten anzufassen.

"Rotation abbrechen" (Rotationsseite und Seite des Nachfolgers) merkt
den neuen Schluessel ueber den bestehenden Widerruf aus #024 vor; er
kann schon ausgerollt sein, deshalb folgen Dry-Run und Anwenden wie
sonst. Der alte bleibt aktiv und unveraendert. Wo beide denselben
Alias tragen, zeigt die config danach wieder auf die alte Datei, weil
privat.ts nur die verbleibenden Zuweisungen in den Block schreibt.

Zustandsmodell: kein neues Feld. Ein Nachfolger im Widerruf (oder
widerrufen) ist eine abgebrochene Rotation; vorgaengerId bleibt fuer
die Historie, der Abbruch steht als schluessel.rotation.abgebrochen im
Protokoll (in derselben Transaktion wie das Vormerken, dafuer nimmt
widerrufEinleiten optional einen Zusatzschritt an). Abbrechen geht nur,
solange der alte Schluessel noch nicht entfernt wird. widerrufAbschliessen
macht aus dem Widerruf des alten Schluessels kein ROTIERT mehr, wenn
der Nachfolger selbst im Widerruf ist. Weil vorgaengerId eindeutig
ist, gibt es nach einem Abbruch keinen zweiten Nachfolger; Seite und
Fehlermeldung sagen das.

Tests: DB-Tests fuer Abbruch, Idempotenz, Sperren und Status;
Integrationstest gegen echte Ziele (neuer Key ausgerollt, abbrechen,
neuer weg, alter in authorized_keys, ssh zu-b von A geht); E2E fuer
den Ablauf im Browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:45:11 +02:00
florian.wildeandClaude Opus 5.5 fb56fd36c5 fix(widerruf): Massenwiderruf nimmt erzeugte Schluessel an (#032)
Der neue E2E-Test (zwei im Browser erzeugte Schluessel mit Public Key
auf Ziel B, Auswahl in der Liste, frische Bestaetigung ueber den
Mock-OIDC mit auth_time, gemeinsamer Dry-Run, Anwenden) fand zwei
Fehler im Produkt:

- Die Massenwiderruf-Seite filterte die IDs aus ?id= mit
  /^[a-z0-9]{1,64}$/. Vom Worker erzeugte Schluessel tragen aber eine
  UUID mit Bindestrichen, nur Prisma-Defaults sind cuids. Damit fiel
  jeder erzeugte Schluessel still aus der Auswahl, die Seite meldete
  "Keine Schluessel gewaehlt". Der Filter steckt jetzt als
  idsAusSuchparameter in src/lib/schluessel.ts, erlaubt Bindestriche
  und hat einen eigenen Test.
- Der Hinweis nach verweigerter Bestaetigung sagte "erneut Widerruf
  vorbereiten waehlen", der Knopf heisst beim Massenwiderruf aber
  "2 Schluessel widerrufen". Er nennt jetzt den Knopf, den es gibt.

Der Test prueft danach, dass beide Keys aus authorized_keys auf B
verschwunden und WIDERRUFEN sind. datenbank() und oidcBestaetigen()
wandern dafuer aus schluessel.spec.ts nach tests/e2e/hilfen.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:44:46 +02:00
florian.wildeandClaude Opus 5.5 be9837ba50 feat(zuweisungen): Zum Dry-Run springt zur Dry-Run-Karte (#032)
Der Link im Zell-Dialog der Zuweisungsmatrix fuehrte nur auf den Kopf
der Schluesselseite; die Dry-Run-Karte liegt weit unten, und wer aus der
Matrix kommt, will genau dorthin. Die Karte traegt jetzt id="dry-run"
(Karte nimmt dafuer eine optionale id an), der Link zeigt auf
/schluessel/<id>#dry-run. scroll-mt haelt die Karte unter der festen
Kopfleiste der schmalen Ansicht.

E2E: der Matrix-Test klickt den Link und prueft Adresse und Sichtbarkeit
der Karte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:29:49 +02:00
florian.wilde 3e0176dc0c Merge branch 'phase/031-deploymentkey-rotation' 2026-10-12 17:27:25 +02:00
florian.wildeandClaude Opus 5.5 cab6f45510 feat(deploymentkey): geführte Rotation des Deployment-Keys (#031)
Bisher ließ sich der Instanz-Key nur von Hand auf jedem Host tauschen.
Die Rotation läuft jetzt in fünf Schritten: Nachfolger erzeugen, per
Dry-Run neben den alten verteilen, Anmeldung je Host prüfen, umstellen,
alten Key aus allen Blöcken entfernen und seinen Private Key verwerfen.

Der Nachfolger steht während der Rotation in Block und Pflichtliste,
damit kein Host ohne funktionierenden Key dasteht. Umgestellt wird nur
nach erfolgreicher Prüfung oder bewusstem Übergehen eines Hosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:27:09 +02:00
florian.wildeandClaude Opus 5.5 b4804390c4 fix(protokoll): IDs ohne Zweierschritte vergeben (#032)
Spalten-Default (BIGSERIAL) und Trigger protokoll_verketten zogen je
eine Nummer aus der Sequenz, jede Zeile verbrauchte zwei IDs. Die
Nummer aus dem Default darf der Trigger aber nicht einfach
uebernehmen, denn sie wird vor der Advisory-Sperre gezogen: Eine parallele
Transaktion mit groesserer ID koennte zuerst verketten, und die Kette
liesse sich in ID-Reihenfolge nicht mehr pruefen.

Deshalb zieht nur noch der Trigger unter der Sperre; der Default ist
ein Platzhalter 0 (Prisma: @default(0)), damit create ohne ID
funktioniert. Die Sequenz bleibt der Spalte zugeordnet. Bestehende
Zeilen werden nicht umnummeriert, weil die ID in die Hash-Eingabe
eingeht.

Tests: drei Eintraege nacheinander ergeben n, n+1, n+2; eine
mitgegebene ID wird ueberschrieben; 20 parallele Eintraege belegen
genau 20 aufeinanderfolgende IDs bei intakter Kette.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:19:28 +02:00
florian.wilde 75077f9101 Merge branch 'phase/030-gitea-abgleich-2' 2026-10-12 17:13:18 +02:00
florian.wildeandClaude Opus 5.5 87b733cc9f chore(gitea): Issues nach Gitea, Basic-Auth fuer Konto-Passwort (#030)
In den Git-Zugangsdaten steht das Konto-Passwort, kein API-Token. Gitea
nimmt es nur per Basic-Auth an; das Skript erkennt das jetzt selbst.
Alle 30 Issues mit Labels und Meilensteinen uebertragen. Neu: #031
Rotation des Deployment-Keys und #032 Nacharbeiten fuer 1.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:13:15 +02:00
florian.wilde 9759a91e34 Merge branch 'phase/release-0.5.0' v0.5.0 2026-10-12 17:08:19 +02:00
florian.wildeandClaude Opus 5.5 a531857e1f chore(release): 0.5.0 - Phase 5 abgeschlossen
Uebersicht, Audit-Log, Benachrichtigungen, Handbuch und Betrieb.
CHANGELOG, DEVLOG und README nachgetragen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:08:17 +02:00
florian.wildeandClaude Opus 5.5 ea6e44fced test(anmeldung): Namen gezielt im Benutzerkasten pruefen (#026)
Seit die Uebersicht echte letzte Aktionen zeigt, steht der Name des
Mock-Admins mehrfach auf der Seite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:08:06 +02:00
florian.wildeandClaude Opus 5.5 034a10b0b1 fix(protokoll): Texte fuer die Aktionen der Benachrichtigungen (#027)
Der Test, der jede Protokoll-Aktion im Code auf einen lesbaren Text
prueft, schlug nach dem Merge von #028 an - genau dafuer gibt es ihn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:02:54 +02:00
florian.wilde c8f4d8a386 Merge branch 'phase/029-handbuch-abschluss' 2026-10-12 16:56:53 +02:00
florian.wildeandClaude Opus 5.5 d9e218a6d3 docs(anleitung): Kapitel Einstellungen und Benachrichtigungen (#029)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:56:52 +02:00
florian.wilde ca05d6a58e Merge branch 'phase/029-handbuch'
# Conflicts:
#	src/worker/index.ts
2026-10-12 16:56:22 +02:00
florian.wilde b99d19ec8c Merge branch 'phase/028-benachrichtigungen'
# Conflicts:
#	docker-compose.yml
#	scripts/screenshots.ts
2026-10-12 16:55:57 +02:00
florian.wildeandClaude Opus 5.5 3e19b11a80 test(einstellungen): E2E, Screenshots und Issue-Abschluss (#028)
Der E2E-Lauf belegt im gebauten Stand, was die Unit- und DB-Tests nur
einzeln zeigen: ein Admin speichert SMTP, die Testnachricht kommt ueber
den Worker in Mailpit an, das Passwort kehrt nie in die Seite zurueck,
und ein Leser sieht keine Aendern-Knoepfe. Die Screenshots (1440/390 px)
halten das Layout der neuen Seite fuer die Anleitung fest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:56 +02:00
florian.wildeandClaude Opus 5.5 8f5b31c0cb feat(benachrichtigung): E-Mail und Webhook bei Drift und Ablauf (#028)
Drift, ablaufende und abgelaufene Schluessel, unerreichbare Hosts und
geaenderte Host-Keys sollen auffallen, ohne dass jemand taeglich in die
Oberflaeche schaut. Versand per SMTP (Microsoft 365, STARTTLS) und per
Webhook mit HMAC-Signatur, eingestellt unter /einstellungen.

Die Anlaesse liest der Worker aus der Datenbank, statt sie im Abgleich
auszuloesen: der Abgleich-Ablauf bleibt unberuehrt, und ein verpasster
Lauf holt sich der naechste. Ein Versandprotokoll (Zustellung) mit
Anlass-Fingerabdruck verhindert Doppelmeldungen und haelt Fehlschlaege
fuer begrenzte Wiederholungen fest. Taeglich eine Zusammenfassung,
geaenderte Host-Keys im Fuenf-Minuten-Takt sofort.

Versand und Testnachricht laufen im Worker, damit der Webprozess keinen
Weg zu Mailserver oder Webhook-Ziel braucht. Passwort und Geheimnis
liegen nur verschluesselt in der Datenbank; die Seite zeigt "gesetzt".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:43 +02:00
florian.wildeandClaude Opus 5.5 7721a9f764 docs(anleitung): Kapitel Audit-Log und Syslog-Variablen (#029)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:49:28 +02:00
florian.wilde 461cab1389 Merge branch 'main' into phase/029-handbuch 2026-10-12 16:49:27 +02:00
florian.wilde fb830dffa7 Merge branch 'phase/026-aktionstexte' 2026-10-12 16:49:11 +02:00
florian.wildeandClaude Opus 5.5 ad371b785a feat(uebersicht): lesbare Aktionstexte aus dem Audit-Log (#026)
Die letzten Aktionen nutzen dieselben Texte wie das Audit-Log (#027)
statt technischer Namen wie authorized_keys.geschrieben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:49:07 +02:00
florian.wilde 2a45d4cc46 Merge branch 'phase/027-protokoll-ansicht' 2026-10-12 16:48:27 +02:00
florian.wildeandClaude Opus 5.5 0154343fd2 feat(protokoll): Audit-Log mit Filtern, Kettenpruefung und CSV (#027)
Das Protokoll war bisher nur in der Datenbank lesbar. Die Ansicht zeigt es
jeder Rolle mit lesbaren Aktionstexten, aufklappbarem Diff, Details und
Hashes; ein Test haelt fest, dass jede Aktion im Code einen Text hat.

Geblaettert wird ueber die ID statt OFFSET, weil das Protokoll waehrend
des Lesens waechst. Die Kettenpruefung liest jede Zeile und laeuft deshalb
nur auf Knopfdruck, mit 5 Minuten Zwischenspeicher; ein Bruch wird selbst
protokolliert. Der CSV-Export streamt in Stapeln, entschaerft Formeln und
steht selbst im Protokoll. protokollieren() reicht jeden Eintrag nach dem
Schreiben an Syslog weiter, ohne Details und Diff.

Kein Weg aendert oder loescht Eintraege; ein struktureller Test sichert
das ab, zusaetzlich zum Trigger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:47:44 +02:00
florian.wildeandClaude Opus 5.5 7bb2ca6a51 feat(protokoll): Syslog-Sender nach RFC 5424 ohne Abhaengigkeit (#027)
Die Kopie beim Syslog-Server ist der vom sshkv-Host unabhaengige Beleg:
wer die Datenbank uebernimmt, kann die Hash-Kette neu rechnen, aber nicht
zurueckholen, was schon beim Syslog liegt. Eigene kleine Implementierung
mit node:dgram/node:net statt einer Bibliothek, weil das Format
ueberschaubar ist und jede Abhaengigkeit im Sicherheitskern zaehlt.

Strukturierte Daten werden nach RFC 5424 escaped und begrenzt (2048 Byte),
Severity folgt dem Ergebnis, Facility standardmaessig authpriv. Der Sender
wirft nie und meldet Fehler hoechstens einmal je Minute, damit ein
ausgefallener Syslog-Server weder Aktionen verhindert noch das Log flutet.
Web und Worker bekommen die Variablen, weil beide protokollieren.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:47:30 +02:00
florian.wildeandClaude Opus 5.5 3b28f73c95 docs(betrieb): Handbuch, Bedrohungsanalyse, geprobte Sicherung (#029)
Handbuch mit Bildern fuer alle Ablaeufe, darunter der Kern-
Anwendungsfall und der Deployment-Zugang fuer Claude Code.
Betriebsdoku zu Installation, authentik und Reverse Proxy sowie eine
Bedrohungsanalyse mit Notfallplan fuer den Fall, dass sshkv selbst
kompromittiert wird.

Sicherung und Wiederherstellung als Skripte, auf einem Wegwerf-Stapel
geprobt. Dabei fielen zwei Fehler auf: das Schema der Warteschlange
liess sich nicht zurueckspielen, und ein Abbruch ohne Transaktion
loeschte es halb. Jetzt nur Schema public, in einer Transaktion, mit
Pruefwert des Master-Keys. Der Worker startet nicht, wenn der
Master-Key nicht zur Datenbank passt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:46:38 +02:00
florian.wilde f791816e85 Merge branch 'phase/026-uebersicht' 2026-10-12 16:33:14 +02:00
florian.wildeandClaude Opus 5.5 ca61ca5b41 feat(uebersicht): echte Zahlen statt Beispieldaten (#026)
Kennzahlen, Hosts mit Abweichung, bald ablaufende Schluessel und letzte
Aktionen kommen aus der Datenbank; die Beispieldaten des Prototyps sind
entfernt. Befunde werden je Host und Benutzer in fester Reihenfolge
zusammengefasst, abgelaufene und noch ausgerollte Schluessel stehen in
Koralle. Anmeldungen bleiben dem Audit-Log vorbehalten, sonst
verdraengten sie auf der Uebersicht alles andere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:33:11 +02:00
florian.wilde 83bd9e0922 Merge branch 'phase/release-0.4.0' v0.4.0 2026-10-12 13:40:34 +02:00
florian.wildeandClaude Opus 5.5 18f72f9d03 chore(release): 0.4.0 - Phase 4 abgeschlossen
Abgleich und Drift-Erkennung, Import, Rotation, Widerruf und
Zuweisungsmatrix. CHANGELOG und DEVLOG nachgetragen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:40:32 +02:00
florian.wilde 6407a2815d Merge branch 'phase/023-024' (Rotation, Widerruf) 2026-10-12 13:31:47 +02:00
florian.wildeandClaude Opus 5.5 f6ed66fc7e feat(rotation): gefuehrte Rotation mit Test vor dem Widerruf (#023)
Einen Schlüssel zu tauschen soll keinen Ausfall riskieren: Der Nachfolger
übernimmt alle aktiven Zuweisungen, wird ausgerollt und per
Verbindungstest geprüft – erst wenn alle Tests seit dem letzten Ausrollen
grün sind, entfernt der Widerruf aus #024 den alten Schlüssel, der dann
als ROTIERT endet.

Private Keys bekommen einen neuen Pfad (-rJJJJMMTT) bei gleichem Alias.
Damit ssh den neuen nimmt, schreibt privat.ts je Alias nur noch einen
config-Eintrag; die jüngste Zuweisung gewinnt. Der Worker setzt
vorgaengerId schon beim Erzeugen, so ist die Kette auch nach einem
Abbruch vollständig und die Schrittfolge wiederaufnehmbar.

Personen-Keys ohne Private Key in sshkv bekommen nur einen Hinweis:
dafür braucht es einen neuen Public Key von der Person.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:30:39 +02:00
florian.wildeandClaude Opus 5.5 a2d4f8428a fix(widerruf): gesperrte Auswahl im Massenwiderruf erkennbar (#024)
Deployment-Key, widerrufene und rotierte Schlüssel sind nicht wählbar,
ihre Kästchen sahen aber genauso aus wie die wählbaren. Jetzt sind sie
sichtbar abgeblendet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:30:04 +02:00
florian.wildeandClaude Opus 5.5 c6361ff3c6 feat(widerruf): Widerruf und Massenwiderruf mit Dry-Run (#024)
Ein Schlüssel soll nach Projektende mit einem Klick von allen Hosts
verschwinden – aber nie als „widerrufen“ gelten, solange er irgendwo noch
liegt. Deshalb ein Zwischenzustand (widerrufEingeleitetAm): Verwendungen
werden zum Entfernen vorgemerkt, der bestehende Dry-Run mit Hash-Sperre
zeigt und schreibt die Änderungen, und erst wenn keine Verwendung mehr
offen ist, setzt der Abschluss WIDERRUFEN (bzw. ROTIERT für #023).
Scheitert ein Host, bleibt der Schlüssel im Zwischenzustand und die Seite
nennt die offenen Ziele.

Zielkonten lassen sich optional mit entfernen, aber erst nach den
Schlüsseln und nie, wenn ein anderer aktiver Schlüssel sie noch nutzt.
Massenwiderruf (Admin, frische Bestätigung) nutzt einen gemeinsamen
Dry-Run und protokolliert je Schlüssel. Abgelaufene Schlüssel erscheinen
im Filter „abgelaufen“ und bieten den Widerruf an – entfernt wird nie
automatisch.

Migration 20261012130000_widerruf_eingeleitet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:14:01 +02:00
florian.wilde 34f5b1f6df Merge branch 'phase/025-zuweisungsmatrix' 2026-10-12 13:06:33 +02:00
florian.wilde fd00ef6de8 Merge branch 'phase/030-gitea-abgleich' 2026-10-12 13:06:32 +02:00
florian.wildeandClaude Opus 5.5 8fadb84923 chore(gitea): Abgleich der Issue-Dateien nach Gitea vorbereiten (#030)
Skript aus ProxLedger uebernommen: legt Labels, Meilensteine und
Issues an bzw. aktualisiert sie, Token aus GITEA_TOKEN oder den
Git-Zugangsdaten. Ohne Zugang laeuft nur ein Trockenlauf - der Gitea-
Zugang fehlt noch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:06:28 +02:00
florian.wilde 656dcb3b4c Merge branch 'phase/021-abgleich' 2026-10-12 13:05:24 +02:00