From 217f672dddab16e174819ef90f0da66556d456da Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Mon, 3 Aug 2026 23:29:12 +0200 Subject: [PATCH] docs: design Auftragskatalog as ad-hoc, revocable Merkmal assignment ADR-0010 decides that an Auftrag is technically a Merkmal, not a new parallel entity: it reuses the existing Merkmal/Blueprint/ansible-role mechanism from ADR-0002, but adds a device-level, independently toggleable assignment path (device_merkmale) alongside the existing workspace-level one. Deselecting an Auftrag needs to actually revert it, so checkin will need to declare a full present/absent state per catalog entry rather than just an additive list; that role-level and API-level work is deferred, this only settles the data model. Also adds a single generic, append-only event log (device_merkmal_events) for both selection changes and agent-reported execution results, instead of several purpose-specific tables, so future event types don't require schema changes. Extends 09-data-model-v1.md and the glossary accordingly. Co-Authored-By: Claude Sonnet 5 --- 00-glossary.md | 14 ++ ...-auftragskatalog-geraeteweise-zuweisung.md | 41 +++++ architecture/09-data-model-v1.md | 149 +++++++++++++++++- 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 adr/0010-auftragskatalog-geraeteweise-zuweisung.md diff --git a/00-glossary.md b/00-glossary.md index 29f8716..5033935 100644 --- a/00-glossary.md +++ b/00-glossary.md @@ -64,6 +64,20 @@ Ein Blueprint ist im Regelfall eine Ansible-Rolle beziehungsweise -Aufgabe, die Details: `architecture/12-feature-blueprint-model.md`. +## Auftragskatalog + +Die Menge der Merkmale, die zusätzlich zur Workspace-Zugehörigkeit direkt einem einzelnen, bereits provisionierten Device zugewiesen und wieder entzogen werden können. + +Ein Auftrag ist fachlich und technisch ein Merkmal (`im_auftragskatalog = true`) — kein eigenständiges Modell. Der Unterschied zur gewöhnlichen Workspace-Zuweisung ist ausschließlich der Zuweisungsweg: geräteweise und jederzeit veränderbar, statt fix über die Bereitstellungsvorlage zum Installationszeitpunkt. + +Aufträge werden zur besseren Übersicht optional einer Kategorie zugeordnet. + +Details: `architecture/09-data-model-v1.md`, ADR-0010. + +## Kategorie + +Eine Gruppierung von Merkmalen im Auftragskatalog, ausschließlich zur Darstellung — ohne fachliche Wirkung auf die Anwendung eines Merkmals. + ## Runtime Blueprint Die zur Installationszeit erzeugte vollständige Zielbeschreibung. diff --git a/adr/0010-auftragskatalog-geraeteweise-zuweisung.md b/adr/0010-auftragskatalog-geraeteweise-zuweisung.md new file mode 100644 index 0000000..2f99b38 --- /dev/null +++ b/adr/0010-auftragskatalog-geraeteweise-zuweisung.md @@ -0,0 +1,41 @@ +# ADR-0010: Auftragskatalog als geräteweise Zuweisung bestehender Merkmale + +**Status:** Beschlossen +**Datum:** 03.08.2026 + +## Kontext + +Nach der Kernschleife des Provisioning Agent (Check-in, Ansible-Pull, siehe `13-live-provisioning-boot.md` und `06-backend-api.md`) sollte als nächster Ausbauschritt ein „Auftragskatalog" entstehen: eine Sammlung einzeln zuweisbarer Aufgaben, die ein Gerät auch nach der Erstinstallation zusätzlich zu seiner Bereitstellungsvorlage bekommen kann, gruppiert in Kategorien. + +Zwei Anforderungen an diesen Katalog waren zu Beginn der Diskussion offen: + +1. Ein Auftrag muss nach der Zuweisung auch wieder **abwählbar** sein, und das Abwählen muss real etwas zurückbauen, nicht nur zukünftige Anwendung verhindern. +2. Pro Gerät muss **transparent nachvollziehbar** sein, was wann wie durchgeführt wurde. + +Die naheliegende erste Frage war, ob dafür ein neues, eigenständiges „Auftrag"-Modell nötig ist, oder ob sich das bestehende Merkmal/Blueprint-Modell (ADR-0002, `12-feature-blueprint-model.md`) wiederverwenden lässt. Ein Auftrag im hier gemeinten Sinn ist fachlich nicht von einem Merkmal zu unterscheiden: eine einzelne, distributionsunabhängig benannte Eigenschaft, die pro Backend als Ansible-Rolle umgesetzt und über den bestehenden Ansible-Pull-Mechanismus angewendet wird. Der einzige Unterschied ist der Zuweisungsweg: ein Merkmal wird bisher ausschließlich über die Workspace-Zugehörigkeit einer Bereitstellungsvorlage zugewiesen (zum Zeitpunkt der Installation, für die Lebensdauer des Geräts fix). Ein Auftrag soll zusätzlich direkt einem einzelnen, bereits laufenden Gerät zugewiesen und wieder entzogen werden können. + +## Entscheidung + +Ein Auftrag ist technisch ein Merkmal. Es wird kein separates Auftrag/Auftrag-Blueprint-Modell parallel zu Merkmal/Blueprint eingeführt. + +Merkmale, die für die geräteweise Ad-hoc-Zuweisung freigegeben sind, werden über ein neues Attribut `im_auftragskatalog` markiert und optional einer `Kategorie` zugeordnet (neues, eigenständiges Kernobjekt, siehe `09-data-model-v1.md`). + +Die Ad-hoc-Zuweisung eines Merkmals zu einem einzelnen Gerät ist eine neue, von der Workspace-Zugehörigkeit (`workspace_merkmale`) unabhängige Beziehung (`device_merkmale`), mit einem Aktiv-Flag statt einer reinen Zeile-existiert-oder-nicht-Semantik. Das Deselektieren eines Auftrags setzt dieses Flag auf inaktiv, statt die Zeile zu löschen. + +Für jedes Gerät wird beim Check-in der vollständige Soll-Zustand aller katalogfähigen Merkmale (nicht nur die neu hinzugekommenen) an den Agenten übermittelt, je Merkmal mit explizitem Zustand `present` oder `absent`. Das ist rein aus dem aktuellen Auswahlstand ableitbar und erfordert keine serverseitige Historie. Damit ein Deselektieren real etwas zurückbaut, muss die zugehörige Ansible-Rolle einen `absent`-Zweig besitzen; das ist eine Erweiterung der Rollenkonvention aus `12-feature-blueprint-model.md`, keine Änderung an diesem Dokument selbst. + +Für die Nachvollziehbarkeit pro Gerät wird ein einziges, generisches, ausschließlich anhängendes Ereignisprotokoll (`device_merkmal_events`) eingeführt, statt mehrerer spezialisierter Log-Tabellen. Ein `event_type`-Feld unterscheidet sowohl Auswahländerungen (`selected`, `deselected`) als auch vom Agenten gemeldete Ausführungsergebnisse (`applied`, `apply_failed`, `removed`, `remove_failed`). Damit der Agent Ausführungsergebnisse überhaupt melden kann, braucht die Check-in-API einen neuen, additiven Report-Kanal; dessen genaue Form wird nicht in diesem ADR, sondern bei der Umsetzung in `08-provisioning-api.md` spezifiziert. + +Workspace-komponierte Merkmale (nicht im Auftragskatalog) bleiben von dieser Änderung unberührt: sie werden weiterhin ausschließlich additiv über `--tags` angewendet, ohne `absent`-Zustand. Die Frage, ob sich eine Workspace-Zusammensetzung nachträglich für ein bereits provisioniertes Gerät ändern und zurückbauen lassen soll, ist ein separates, hier bewusst nicht adressiertes Problem. + +## Konsequenzen + +Neue Merkmale, die in den Auftragskatalog aufgenommen werden sollen, brauchen zusätzlich zum bestehenden Blueprint pro Backend eine `absent`-Umsetzung in der jeweiligen Ansible-Rolle. Merkmale, die ausschließlich über Workspaces zugewiesen werden, sind davon nicht betroffen. + +Der Check-in-Response wird um einen expliziten `state`-Wert je Katalogeintrag erweitert; das ist rückwärtskompatibel additiv, kein Bruch der bestehenden, bereits gegen die QEMU-Testumgebung verifizierten Check-in-Antwort. + +Ein neuer Report-Endpoint wird nötig, damit Ausführungsergebnisse serverseitig sichtbar werden. Bis dieser existiert, bleibt die einzige Ausführungsevidenz lokal auf dem Gerät (`/run/tuxflotte/agent/applied/`), wie bereits heute. + +Das Datenmodell bleibt für künftige Auftragsarten erweiterbar, ohne neue Tabellen: neue Ereignisarten sind neue `event_type`-Werte, keine Schema-Änderung. Sollten künftig Aufträge nötig werden, die sich nicht als idempotenter Zielzustand beschreiben lassen (echte einmalige Aktionen ohne Present/Absent-Semantik), ist das ein eigenständiges, hier nicht getroffenes Entwurfsproblem. + +Details zum Datenmodell: `09-data-model-v1.md`. Details zum Merkmal/Blueprint-Grundmodell: `12-feature-blueprint-model.md`, ADR-0002. diff --git a/architecture/09-data-model-v1.md b/architecture/09-data-model-v1.md index 0fd4cd3..d0737cb 100644 --- a/architecture/09-data-model-v1.md +++ b/architecture/09-data-model-v1.md @@ -14,11 +14,14 @@ Die konkrete SQL-Implementierung und das Migrationssystem werden getrennt von di - Network Interface - Storage Device - Workspace +- Kategorie - Merkmal - Backend - Blueprint - Bereitstellungsvorlage - Assignment +- Auftragszuweisung +- Auftragsereignis - Activation Code - Network Profile - Secret Reference @@ -163,9 +166,11 @@ Beziehungen: **Notiz für später (30.07.2026):** Ein Workspace ist zwar distributionsunabhängig benannt, aber nicht jedes Merkmal hat für jeden Backend ein Blueprint (siehe unten). Für den noch nicht gebauten, selbst-bedienten Workspace-Creator (Kunde stellt sich einen eigenen Workspace aus Merkmalen zusammen) folgt daraus eine UX-Reihenfolge, keine Schema-Änderung: **erst Backend wählen, dann Merkmale** — die Merkmalsauswahl im Creator sollte auf `blueprints`-Zeilen für den bereits gewählten Backend gefiltert sein, damit kein Workspace entsteht, der auf der gewünschten Distribution gar nicht realisierbar ist. Nicht dringend, betrifft nur den noch nicht priorisierten Self-Service-Teil (siehe [[project-tuxflotte-pilot-roadmap]]). -## Merkmal +## Kategorie -Ein Merkmal ist eine einzelne fachliche Eigenschaft eines Workspace, distributionsunabhängig benannt (siehe `12-feature-blueprint-model.md`). +Eine Kategorie gruppiert Merkmale, die im Auftragskatalog (siehe ADR-0010) zur geräteweisen Ad-hoc-Auswahl stehen, für die Darstellung in einer Katalog-Übersicht. + +Eine Kategorie hat keine fachliche Wirkung auf die Anwendung eines Merkmals; sie dient ausschließlich der Organisation im Auftragskatalog. Attribute: @@ -173,11 +178,37 @@ Attribute: - key - name - description +- sort_order + +Beziehungen: + +- wird von mehreren Merkmalen referenziert + +Constraints: + +- key ist eindeutig + +## Merkmal + +Ein Merkmal ist eine einzelne fachliche Eigenschaft eines Workspace, distributionsunabhängig benannt (siehe `12-feature-blueprint-model.md`). + +Ein Merkmal kann zusätzlich zur Workspace-Zugehörigkeit für die geräteweise Ad-hoc-Zuweisung über den Auftragskatalog freigegeben sein (`im_auftragskatalog`, siehe ADR-0010, Auftragszuweisung unten). Das ist unabhängig davon, ob das Merkmal auch (oder ausschließlich) über einen Workspace zugewiesen wird. + +Attribute: + +- id +- key +- name +- description +- kategorie_id (optional; nur relevant, wenn `im_auftragskatalog = true`) +- im_auftragskatalog (Standard: false) Beziehungen: - gehört zu mehreren Workspaces - besitzt je Backend höchstens einen Blueprint +- gehört optional zu genau einer Kategorie +- wird optional mehreren Devices direkt zugewiesen (Auftragszuweisung) ## Backend @@ -263,6 +294,55 @@ Constraints: - ein Device besitzt höchstens ein Assignment +## Auftragszuweisung + +Eine Auftragszuweisung beschreibt, ob ein katalogfähiges Merkmal (`im_auftragskatalog = true`) für ein einzelnes Device aktuell ausgewählt ist — unabhängig von dessen Workspace-Zugehörigkeit über die Bereitstellungsvorlage (siehe ADR-0010). + +Anders als bei einem Assignment ist das Vorhandensein einer Zeile nicht gleichbedeutend mit „aktiv": Ab- und erneutes Zuwählen ändern das `aktiv`-Flag, statt die Zeile zu löschen und neu anzulegen, damit die Historie über das Ereignisprotokoll (Auftragsereignis) nachvollziehbar bleibt. + +Attribute: + +- id +- device_id +- merkmal_id +- aktiv +- created_at +- updated_at + +Beziehungen: + +- gehört zu genau einem Device +- referenziert genau ein Merkmal + +Constraints: + +- (device_id, merkmal_id) ist eindeutig +- merkmal_id muss auf ein Merkmal mit `im_auftragskatalog = true` verweisen (anwendungsseitig geprüft) + +## Auftragsereignis + +Ein Auftragsereignis ist ein unveränderlicher, anhängender Protokolleintrag zu einem Merkmal auf einem Device — sowohl für Auswahländerungen als auch für vom Provisioning Agent gemeldete Ausführungsergebnisse (siehe ADR-0010). + +Ein einziges, generisches Ereignisprotokoll deckt beide Fälle über das `event_type`-Feld ab, damit künftige Ereignisarten keine neue Tabelle erfordern. + +Attribute: + +- id +- device_id +- merkmal_id +- event_type (`selected`, `deselected`, `applied`, `apply_failed`, `removed`, `remove_failed`) +- detail (optional, strukturierte Zusatzinformation) +- occurred_at + +Beziehungen: + +- gehört zu genau einem Device +- referenziert genau ein Merkmal + +Constraints: + +- keine (append-only, kein Unique-Constraint über die fachlichen Spalten) + ## Activation Code Ein Activation Code autorisiert den initialen Provisionierungsvorgang eines Geräts und bestimmt die zugeordnete Organization (siehe `08-provisioning-api.md`, `11-device-enrollment.md`). @@ -389,6 +469,20 @@ Constraints: - (organization_id, key) ist eindeutig +### kategorien + +Spalten: + +- id +- key +- name +- description +- sort_order + +Constraints: + +- key ist eindeutig + ### merkmale Spalten: @@ -397,6 +491,12 @@ Spalten: - key - name - description +- kategorie_id (nullable) +- im_auftragskatalog (Standard: false) + +Beziehungen: + +- kategorie_id referenziert kategorien.id (optional) Constraints: @@ -493,6 +593,42 @@ Constraints: - device_id ist eindeutig +### device_merkmale + +Spalten: + +- id +- device_id +- merkmal_id +- aktiv +- created_at +- updated_at + +Beziehungen: + +- device_id referenziert devices.id +- merkmal_id referenziert merkmale.id + +Constraints: + +- (device_id, merkmal_id) ist eindeutig + +### device_merkmal_events + +Spalten: + +- id +- device_id +- merkmal_id +- event_type +- detail (nullable) +- occurred_at + +Beziehungen: + +- device_id referenziert devices.id +- merkmal_id referenziert merkmale.id + ### activation_codes Spalten: @@ -524,6 +660,10 @@ Wird ein Workspace, ein Backend oder ein Merkmal gelöscht, das noch von einer B Wird ein Device gelöscht, wird sein Assignment mitgelöscht. Wird eine Bereitstellungsvorlage gelöscht, auf die noch ein Assignment verweist, muss das gesperrt werden. +Wird ein Device gelöscht, werden seine Auftragszuweisungen und Auftragsereignisse mitgelöscht (konsistent mit dem Umgang mit Hardware Snapshots). Wird ein Merkmal gelöscht, das noch von einer Auftragszuweisung oder einem Auftragsereignis referenziert wird, muss das gesperrt werden. Wird eine Kategorie gelöscht, verlieren zugehörige Merkmale lediglich ihre Kategoriezuordnung; sie bleiben davon unabhängig im Auftragskatalog bestehen. + +**Notiz für später (03.08.2026):** Das Kaskadieren des Auftragsereignis-Protokolls bei Device-Löschung folgt bewusst dem bestehenden Muster (siehe Hardware Snapshot), ist aber im Sinne einer lückenlosen Historie eine Abwägung, keine zwingende Konsequenz — ein gelöschtes Device nimmt damit auch seine Ausführungshistorie mit. Sollte künftig eine Anforderung entstehen, Auftragshistorie auch nach Device-Löschung aufzubewahren (zum Beispiel für Audit-Zwecke über die Gerätelebensdauer hinaus), ist das eine Änderung der Löschregel für `device_merkmal_events.device_id` (z. B. auf `ON DELETE SET NULL` mit einer zusätzlichen, vom Device losgelösten Identifikationsspalte), keine Änderung an der Ereignis-Tabellenstruktur selbst. + Für die relationale Umsetzung gilt daher: - devices.organization_id → ON DELETE RESTRICT @@ -533,6 +673,7 @@ Für die relationale Umsetzung gilt daher: - workspaces.organization_id → ON DELETE CASCADE - workspace_merkmale.workspace_id → ON DELETE CASCADE - workspace_merkmale.merkmal_id → ON DELETE RESTRICT +- merkmale.kategorie_id → ON DELETE SET NULL - blueprints.merkmal_id → ON DELETE CASCADE - blueprints.backend_id → ON DELETE CASCADE - bereitstellungsvorlagen.organization_id → ON DELETE CASCADE @@ -540,6 +681,10 @@ Für die relationale Umsetzung gilt daher: - bereitstellungsvorlagen.backend_id → ON DELETE RESTRICT - assignments.device_id → ON DELETE CASCADE - assignments.bereitstellungsvorlage_id → ON DELETE RESTRICT +- device_merkmale.device_id → ON DELETE CASCADE +- device_merkmale.merkmal_id → ON DELETE RESTRICT +- device_merkmal_events.device_id → ON DELETE CASCADE +- device_merkmal_events.merkmal_id → ON DELETE RESTRICT - activation_codes.organization_id → ON DELETE CASCADE ## ID-Erzeugung