From a2a34e568f09486e842c6d4daa83e8dfdb77d402 Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Wed, 12 Aug 2026 21:08:29 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20ADR-0013=20Ger=C3=A4te-Lebenszyklus-Akt?= =?UTF-8?q?ionen=20im=20Kundenportal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dokumentiert nachträglich eine bereits deployte, live-verifizierte Session-Arbeit: De-/Reprovisionieren (serverseitiger Credential-Entzug, Agent-Self-Heal mit deprovisioned_at-Gate), Löschen als Soft Delete/Archivierung, Hardware-/Log-Leseendpunkte. Bislang komplett undokumentiert in platform-docs gewesen. 09-data-model-v1.md um devices.deprovisioned_at/archived_at ergänzt. Co-Authored-By: Claude Sonnet 5 --- adr/0013-geraete-lebenszyklus-kundenportal.md | 37 +++++++++++++++++++ architecture/09-data-model-v1.md | 8 ++++ 2 files changed, 45 insertions(+) create mode 100644 adr/0013-geraete-lebenszyklus-kundenportal.md diff --git a/adr/0013-geraete-lebenszyklus-kundenportal.md b/adr/0013-geraete-lebenszyklus-kundenportal.md new file mode 100644 index 0000000..9642fb5 --- /dev/null +++ b/adr/0013-geraete-lebenszyklus-kundenportal.md @@ -0,0 +1,37 @@ +# ADR-0013: Geräte-Lebenszyklus-Aktionen im Kundenportal + +**Status:** Beschlossen +**Datum:** 10.08.2026 + +## Kontext + +Die Geräteliste im Kundenportal (`/geraete`, siehe ADR-0011) zeigte bislang nur Hostname, Kennung, letzten Check-in und einen Link zum Auftragskatalog. Kund:innen fehlte jede Möglichkeit, Hardware-Informationen oder die Installations-/Aktionshistorie eines Geräts einzusehen, oder ein Gerät zu deaktivieren beziehungsweise loszuwerden, ohne den Betreiber zu kontaktieren. + +Für „De-Provisionieren" und „Löschen" waren zwei Fragen zu klären, die reale Konsequenzen auf im Feld laufende Geräte haben: + +1. Was passiert technisch mit einem deprovisionierten Gerät — wird der laufende Agent aktiv ferngesteuert deinstalliert, oder wird ihm nur serverseitig der Zugriff entzogen? +2. Ist „Löschen" ein harter, unwiderruflicher Datenbank-Delete, oder eine Archivierung? + +## Entscheidung + +**De-Provisionieren ist ein rein serverseitiger Credential-Entzug**, keine aktive Fernabmeldung. Das Agent-Secret des Geräts wird ungültig gemacht (`agent_secret_hash = NULL`, `deprovisioned_at = now()`); der laufende Agent auf dem Gerät selbst wird nicht kontaktiert und deinstalliert sich nicht — er bekommt beim nächsten Check-in schlicht `401 unauthorized` und bleibt bis zur Reprovisionierung untätig liegen. Historie (Hardware-Snapshots, Aktions-Log) bleibt vollständig erhalten. Diese Variante wurde einer aktiven Fernabmeldung vorgezogen, weil letztere eine neue, deutlich invasivere Fähigkeit im `provisioning-agent` gebraucht hätte (Uninstall-Kommando auf echten Kundengeräten), mit entsprechend höherem Test- und Sicherheitsaufwand. + +Damit „Erneut provisionieren" ein deprovisioniertes, aber weiterhin laufendes Gerät ohne physischen Eingriff reaktivieren kann, wurde der Agent (`provisioning-agent/agent.py`) um einen **Self-Heal-Mechanismus** erweitert: bekommt der Agent beim Check-in `401`, ruft er einmalig `/api/v1/agent/bootstrap` erneut auf (dieser Endpoint kannte von Anfang an keine andere Authentifizierung als die `device_id` selbst — bestehendes Design, kein neues Risiko) und übernimmt bei Erfolg das neue Secret. Damit dieser Mechanismus die De-Provisionierung nicht selbst aushebelt — ein deprovisioniertes Gerät würde sich sonst beim nächsten Poll-Zyklus einfach automatisch selbst reaktivieren, unabhängig davon, ob „Erneut provisionieren" im Portal je ausgelöst wurde —, prüft `agent_bootstrap` seit dieser Änderung zusätzlich `deprovisioned_at IS NULL`. Erst das explizite Löschen von `deprovisioned_at` (die Reprovisionieren-Aktion) macht einen erneuten Bootstrap-Aufruf überhaupt erfolgreich. Dieses Gate wurde bei der Live-Verifikation nachträglich ergänzt, nachdem der ursprüngliche Entwurf diese Lücke übersehen hatte. + +**Löschen ist Soft Delete (Archivierung), kein Hard Delete.** Ein „gelöschtes" Gerät bekommt `archived_at = now()` gesetzt, verschwindet aus der aktiven Geräteliste (`fetch_devices_for_organization()`/`fetch_all_devices()` filtern `archived_at IS NULL`), Datensatz und Historie bleiben aber vollständig in der Datenbank erhalten. Kein separater „Wiederherstellen"-Endpunkt in dieser ersten Ausbaustufe — die Zeile bleibt bei Bedarf über die Datenbank erreichbar. + +Zusätzlich neu: Lese-Endpunkte für **Hardware-Informationen** (`GET /api/v1/devices/{id}/hardware`, jüngster `hardware_snapshots`-Eintrag samt Netzwerk-Interfaces/Storage-Devices) und **Installations-/Aktions-Log** (`GET /api/v1/devices/{id}/events`, bestehende `device_merkmal_events`-Tabelle aus ADR-0010, hier erstmals für Kund:innen sichtbar gemacht statt nur intern verwendet). + +Alle Aktionen sind über ein Pico-natives Dropdown (`