feat: slice 2 - all eleven decisions ported to docs/adr

Gate 4, slice 2: decisions/0001-0011 moved via git mv with schema
frontmatter prepended (status and date taken from each body's own
Status line - 0007 stays proposed, its decision is open in #20; bodies
unchanged except relative links gaining one directory level). The old
scheme's README and template retire - their rules already live in
AGENTS.md section 6 and the neckbeard ADR template. Every reference to
decisions/ across the tree retargeted (root files, not-yet-moved
verfahren/hosts/shared files, design doc and session ADR frontmatter).

Verified: validate 0 errors (11 ported + 2 session ADRs + duplicate-id
guard), gen_status --check current with all 13 ADRs listed, drift
check 0 findings, negative test shows a cloned id 0012 firing the
duplicate check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Thore Cimbal
2026-08-11 12:00:00 +00:00
co-authored by Claude Fable 5
parent e36ed337a7
commit 70e81e2ff1
25 changed files with 148 additions and 65 deletions
@@ -0,0 +1,40 @@
---
type: adr
id: "0001"
status: accepted
date: 2026-07-31
supersedes: null
superseded_by: null
related: []
---
# 0001 — git.lab ist kanonisch, Gitea wird per Push-Mirror beliefert
**Status:** akzeptiert · **Datum:** 2026-07-31 (rückwirkend dokumentiert 2026-08-01) · **Entscheider:** sorb
## Kontext
Build-CI brauchte mehr Ressourcen als CFGMON hat (webpack ~4 GB Heap auf einem
3,7-GiB-Host) und Gitea Actions zeigte mehrere echte Bugs. Das Homelab-GitLab
(`git.lab`, nur im Lab/VPN auflösbar) hat die Kapazität; Produktion (Flux) darf
aber nicht von der Lab-Verfügbarkeit abhängen.
## Entscheidung
`git.lab/axion1337.chat/*` ist die kanonische Heimat aller Repos; Gitea/rohana
wird über Push-Mirrors beliefert und bleibt Flux-Source, Container-Registry,
npm-Registry und Release-Download. **Direkte Pushes zu Gitea sind für gespiegelte
Repos verboten** — der Mirror überschreibt divergenten Stand per Force.
## Konsequenzen
- Bei Lab-Ausfall läuft Prod weiter; nur neue Builds pausieren.
- Commits, die doch auf Gitea landen (z. B. Cluster-CronJobs ohne Lab-Route),
brauchen das Kanonisierungs-Verfahren (`verfahren/deploy-uebergabe.md`):
`.patch` ziehen, `git am`, Push über git.lab. Zweimal live gebraucht.
- Arbeiten an den Repos setzt Lab-Zugang voraus (vor Ort oder VPN).
## Verworfene Alternativen
- CFGMON-CI aufrüsten (Swap/Limits): strukturell zu klein, verworfen mit CFGMON-10.
- git.lab öffentlich exponieren: Angriffsfläche fürs Lab, stattdessen VPN (→ 0004).
@@ -0,0 +1,46 @@
---
type: adr
id: "0002"
status: accepted
date: 2026-08-01
supersedes: null
superseded_by: null
related: []
---
# 0002 — Issues und Management-Repo ziehen ins Lab („das Lab ist die Quelle der Wahrheit")
**Status:** akzeptiert · **Datum:** 2026-08-01 · **Entscheider:** sorb
## Kontext
Nach dem Code-Umzug (ADR-0001) lagen Projektmetadaten (Issues, Backlogs-Repo)
weiter auf Gitea — zwei Wahrheiten, driftgefährdet. Erreichbarkeits-Blocker
LABNET-01 (WireGuard-Roadwarrior) wurde am 2026-08-01 gelöst.
## Entscheidung
Alle Projekt-Issues leben auf git.lab (62 migriert, Gitea-Issues geschlossen mit
Verweis); das Backlogs-Repo zieht als `axion1337.chat/management` ins Lab
(Push-Mirror → `sorb/management` auf Gitea). Das Lab ist die Quelle der Wahrheit.
## Konsequenzen
- ⚠️ gitops-Issue-Nummern haben sich verschoben (Gitea zählte PRs mit); die
verbindliche Zuordnung steht im Migrations-Fußtext jedes GitLab-Issues.
- ~~**Befristete Ausnahme:** Deploy-Übergabe-Issues laufen auf dem Gitea-Tracker
von `sorb/management`, weil CFGMON git.lab (noch) nicht erreicht.~~
**Zurückgebaut am 2026-08-02** (LABNET-03, [#13](https://git.lab/axion1337.chat/management/-/issues/13)):
Das Site-to-Site-VPN (ADR-0004) hat den Grund beseitigt; beide Übergabe-Issues
sind nach git.lab gewandert ([#25](https://git.lab/axion1337.chat/management/-/issues/25),
[#26](https://git.lab/axion1337.chat/management/-/issues/26)), der Gitea-Tracker ist
leer, die Vorlage liegt als `.gitlab/issue_templates/`. **Damit gilt diese ADR
ohne Ausnahme.** Die Ausnahme war seinerzeit der Auslöser für die ADR-Pflicht bei
Ausnahmen (siehe `README.md`) — dass sie befristet war und die Frist gehalten hat,
ist der Beleg, dass die Regel trägt.
- Releases bleiben auf Gitea (öffentlicher Download-Pfad), ebenso das gitops-Wiki.
## Verworfene Alternativen
- Issues auf Gitea belassen: dauerhafte Doppelführung, Roadmap/Boards unmöglich.
- git.lab exponieren statt VPN: siehe ADR-0001/0004.
+40
View File
@@ -0,0 +1,40 @@
---
type: adr
id: "0003"
status: accepted
date: 2026-08-01
supersedes: null
superseded_by: null
related: []
---
# 0003 — CVE-Meldeweg: aggregierte Alarme, eigener Security-Raum, gleicher Bot
**Status:** akzeptiert · **Datum:** 2026-08-01 · **Entscheider:** sorb
## Kontext
Der erste CVE-Alarmweg (eine Matrix-Nachricht pro CVE) flutete den Raum mit 126
Nachrichten und musste stummgeschaltet werden (gitops#51, AAR in `verfahren/aar/`).
Gleichzeitig war entschieden (CFGMON-13), Release-/Security-Meldungen von
Betriebsalarmen zu trennen.
## Entscheidung
CVE-Alarme werden **pro Scan-Ziel aggregiert** (`count by (target, target_type,
host)`, CRITICAL sofort / HIGH nach 24 h) und in den **Security-Raum**
(`!YRJvcEbVXtRlUIkNld`) geroutet; Details liegen im Grafana-CVE-Dashboard, die
Nachricht verlinkt nur dorthin. Absender bleibt der bestehende `@alerts`-Bot
(ein Bot, Trennung über Räume).
## Konsequenzen
- Raum bleibt lesbar; Einzel-CVE-Detail wandert in Dashboard/Metriken
(`trivy_vuln_info{...}` mit CVE-ID, Severity, fixed_version, first_seen).
- Pflichtfelder je Meldung: CVE-ID, Mitigation/Fix-Version, Zeitstrahl, Ort, Typ.
- Follow-up-Idee (sorb): Grafana-Dashboard als Widget direkt im Matrix-Raum.
## Verworfene Alternativen
- Eine Nachricht pro CVE: real erlebter Flood, Raum unbrauchbar.
- Eigener Bot je Meldungsklasse: mehr Betrieb ohne Erkenntnisgewinn, Räume trennen genügt.
@@ -0,0 +1,76 @@
---
type: adr
id: "0004"
status: accepted
date: 2026-08-01
supersedes: null
superseded_by: null
related: []
---
# 0004 — Site-to-Site-VPN Hetzner-Projektnetz ↔ Lab, schaltbar über die UDM
**Status:** akzeptiert (umgesetzt und abgenommen 2026-08-01, Testreihe 17 in [management#12](https://git.lab/axion1337.chat/management/-/issues/12)) · **Datum:** 2026-08-01 · **Entscheider:** sorb
## Kontext
Das Lab ist die Quelle der Wahrheit (ADR-0002), aber die Hetzner-Hosts erreichten
`git.lab` nicht — das erzwang beim Cutover die Übergabe-Issue-Ausnahme. Ein
dauerhafter Zugang von exponierten Servern ins Heimnetz soll es trotzdem nicht sein.
## Entscheidung
Ein **eigener WireGuard-Tunnel** (getrennt vom Roadwarrior) verbindet das
Hetzner-Projektnetz `10.0.0.0/24` mit dem Lab-VLAN `10.58.73.0/24`. Der An/Aus-Schalter
liegt in der UniFi-UI; die Hetzner-Seite ist passiv und kann den Tunnel nicht selbst
öffnen (Bedarfsfall-Prinzip, Kontrolle im Lab).
### Wie es tatsächlich gebaut wurde (Architektur v2)
Der ursprüngliche Entwurf sah die **UDM als Initiator** gegen einen WireGuard-Listener
auf CFGMON vor. UniFi bietet Site-to-Site jedoch nur als OpenVPN/IPsec an; WireGuard
existiert dort ausschließlich als **Server** — dafür mit der Option „Networks Behind
Client". Die Richtung wurde deshalb gedreht:
| | |
|---|---|
| **UDM** | zweiter WG-**Server** „Matrix", UDP **51841**, Tunnelnetz **10.58.75.0/24**, „Alternate Address for Clients" = `178.25.213.70` |
| **Client „Hetzner"** | `10.58.75.2`, **Networks Behind Client = `10.0.0.0/24`** |
| **CFGMON** | WG-**Client/Initiator**, dauerhaft aktiv (`enable`) + `PersistentKeepalive 25`; AllowedIPs nur `10.58.73.0/24, 10.58.75.1/32`; Split-DNS nur `~lab``10.58.73.1`; `ip_forward` + iptables-ACCEPT in `DOCKER-USER` (ufw ist dort inaktiv), **kein NAT** |
| **Fritzbox** | Portfreigabe UDP **51841**`192.168.178.20` |
| **Hetzner** | Netz-Range auf **`10.0.0.0/8`** erweitert, Route `10.58.73.0/24 → 10.0.0.3` — damit erreichen alle Server im Netz das Lab **ohne eigene Konfiguration** |
| **UniFi-Firewall** | Trennung vom Roadwarrior über **Quell-/Ziel-IP** (`10.58.75.0/24` + `10.0.0.0/24`), nicht über eine eigene Zone: erlaubt sind nur `10.58.73.17:443` (git.lab/Registry) und `10.58.73.1:53` (DNS); IoT und Arbeit sind für alle VPNs komplett gesperrt |
## Konsequenzen
-**Eingelöst am 2026-08-02 (LABNET-03, [#13](https://git.lab/axion1337.chat/management/-/issues/13)):**
Übergabe-Issues können nicht nur umziehen — sie sind umgezogen
([#25](https://git.lab/axion1337.chat/management/-/issues/25),
[#26](https://git.lab/axion1337.chat/management/-/issues/26)), der Gitea-Tracker ist
leer, die Vorlage liegt als GitLab-Issue-Template, und die Ausnahme aus ADR-0002 ist
zurückgebaut. Damit hat dieser Tunnel seinen ersten inhaltlichen Zweck erfüllt.
- Abschaltung wirkt in **unter 4 s**, Rückkehr nach dem Einschalten in **~8 s** ohne
Eingriff auf der Hetzner-Seite (gemessen 2026-08-01 23:2123:23).
- Ein kompromittierter Hetzner-Host sieht bei offenem Tunnel nur git.lab:443 und den
Lab-DNS; bei geschlossenem Schalter gar nichts.
- Preis der Richtungsdrehung: eine Portfreigabe am Heimanschluss (im Ursprungsentwurf
nicht nötig). Bewertung: akzeptabel, da nur UDP 51841 und WireGuard auf unbekannte
Peers still schweigt.
- Der TURN-Rotations-CronJob trifft nur bei eingeschaltetem Tunnel auf git.lab — seine
Gitea-PR-Ausnahme bleibt bewusst bestehen.
**Nachtrag 2026-08-02:** Die Ausnahme besteht weiter, wird aber nicht mehr von Hand
aufgelöst. Der Schedule-Job `canonize_rotation` im gitops-Repo holt die Rotation
täglich von git.lab aus nach. Damit hängt sie nicht mehr am Tunnelzustand — der
Job läuft im Lab und erreicht Gitea öffentlich. Das war der eigentliche Grund für
die Ausnahme; sie ist jetzt automatisiert statt terminiert.
- game.axion1337.de profitiert erst nach Aufnahme in den vSwitch (GAME-01).
## Verworfene Alternativen
- **UDM als Initiator** (Ursprungsentwurf): von UniFi nicht angeboten.
- Einzelne Roadwarrior-Profile pro Server: Schalter läge auf den exponierten Hosts,
Key-Streuung, kein Site-Routing.
- Tunnel dauerhaft an: widerspricht dem Bedarfsfall-Prinzip ohne echten Gewinn.
- git.lab öffentlich exponieren: größte Angriffsfläche, klar verworfen.
- Eigene UniFi-Zone für den Tunnel: technisch nicht möglich (VPN-Server bleiben in der
VPN-Zone), siehe [AAR Lab-Seite](../../verfahren/aar/2026-08-01-labnet02-lab.md).
+54
View File
@@ -0,0 +1,54 @@
---
type: adr
id: "0005"
status: accepted
date: 2026-08-01
supersedes: null
superseded_by: null
related: []
---
# 0005 — Projektmanagement: Kanban-Rückgrat mit leichten Scrum-Elementen
**Status:** akzeptiert · **Datum:** 2026-08-01 · **Entscheider:** sorb
## Kontext
Die Arbeit (Solo-Betrieb plus Claude-Sessions, unregelmäßige Abendblöcke,
incident-getrieben) fasert ohne Struktur aus. Artefakte existierten verstreut
(Markdown-Backlogs, Issues, AARs) ohne verbindenden Rahmen. sorb ist
zertifizierter Scrum Master (ohne Praxis in der Rolle).
## Entscheidung
**Kanban als Betriebssystem**, ergänzt um die Scrum-Artefakte, die solo tragen:
| Element | Umsetzung |
|---|---|
| Board | GitLab-Gruppen-Board (Gruppe `axion1337.chat`), Spalten über `status:`-Labels |
| Pull statt Commitment | `status:next` ist die einzige Zusage; gezogen wird nach Kapazität |
| WIP-Limit | **max. 2 × `status:doing`** (Konvention — CE hat keine Board-Limits) |
| Explizite Policies | ein `status:`-Label pro Issue; blockiert = `status:wartet` mit benanntem Grund |
| Product Goal / Vision | `vision/` (eine Datei je Linie) |
| Refinement | die etablierten Entscheidungsrunden (Vorlagen mit Optionen) |
| Review/Retro | AARs (`verfahren/aar/`) nach Deploys/Incidents |
| Definition of Done | Deploy-Übergabe-Verfahren (`verfahren/deploy-uebergabe.md`) |
| Roadmap/Meilensteine | Gruppen-Milestones + `roadmap.md` (CE: keine Epics/Roadmap-View) |
| Entscheidungen | ADRs in `decisions/` |
| Experimente | Lean-Startup-Muster (Hypothese → kleinstes Experiment → messen) für Produktideen wie Raidplaner/Invite-Workflow |
## Konsequenzen
- Das Backlogs-Repo wird zum Management-Repo `axion1337.chat/management`;
offene Punkte aus `hosts/`/`shared/` sind Issues mit `host:`-Labels,
die Markdown-Dateien halten Bestand und Historie.
- Ein Backlog-System statt zwei; das Gruppen-Board zeigt alles Offene.
- Disziplin-Pflichten: Status-Labels pflegen, WIP-Limit respektieren,
ADR bei Architekturentscheidungen und dauerhaften Ausnahmen.
## Verworfene Alternativen
- Scrum/Nexus: Sprints und Commitment brauchen planbare Team-Kapazität —
bei Solo-Betrieb mit unregelmäßigen Blöcken Theater; Nexus ist Multi-Team.
- Lean Startup / Design Thinking als Rahmen: Produktentdeckungs-Werkzeuge,
kein Betriebs-Framework — bleiben als Werkzeug im Einsatz.
@@ -0,0 +1,70 @@
---
type: adr
id: "0006"
status: accepted
date: 2026-08-02
supersedes: null
superseded_by: null
related: []
---
# 0006 — Wikis ins Lab konsolidieren, Docusaurus als gemeinsame Lesefläche
**Status:** akzeptiert · **Datum:** 2026-08-02 · **Entscheider:** sorb
## Kontext
Die Dokumentation lag an **drei Orten mit drei Ständen**, was beim Audit auffiel:
1. **Gitea-Wiki-Repo** `…gitops.wiki.git` — 15 Seiten, gepflegt bis 2026-07-31.
Vom Push-Mirror **nicht** erfasst: ein Wiki ist ein eigenes Repo, kein Branch.
2. **`wiki`-Branch im gitops-Repo** — Stand 2026-05-14, mitgezogen, weil der Mirror
alle Branches trägt. Inhalt: ein damaliger Abzug von `docs/`, kein gepflegtes Wiki.
3. **`docs/` im main-Branch** — die eigentliche, laufend gepflegte Repo-Doku.
Dazu waren die GitLab-Wikis aller Projekte **leer**, und die Wiki-Inhalte enthielten
sachlich falsche Aussagen (node-exporter-DaemonSet als aktive Komponente, obwohl am
2026-08-01 entfernt; Issues „in Gitea", obwohl migriert).
## Entscheidung
**Das Wiki zieht ins Lab** und wird Teil der git.lab-Wahrheit: Die 15 Seiten liegen
im GitLab-Wiki des gitops-Projekts (*Wiki*-Reiter). Damit entfällt die letzte
„direkt-zu-Gitea"-Ausnahme aus [ADR-0001](0001-gitlab-kanonisch-push-mirror.md).
**Eine gemeinsame Lesefläche statt einer gemeinsamen Struktur:** Das Repo
[`homelab/wiki`](https://git.lab/homelab/wiki) baut mit Docusaurus eine Seite unter
`axionwiki.lab`, die drei Quellen **nebeneinander** zeigt — Plattform (gitops-Wiki),
Homelab (`homelab/docs`), Arbeitsweise (`management`). Die Inhalte werden beim Bau
eingesammelt; das Wiki-Repo enthält selbst keinen Text.
## Konsequenzen
- **Änderungen gehören ins Quell-Repo**, nie ins Wiki-Repo — was dort in `content/`
landet, wird beim nächsten Bau überschrieben.
- Der Bau läuft in der **Lab-CI** (nur dort gibt es Lesezugriff auf die Quellen) und
legt ein statisches Image in der Lab-Registry ab; Dokploy deployt es. Ein
**Pipeline-Zeitplan** ist der eigentliche Aktualisierungsmechanismus: Das Wiki folgt
den Quellen, ohne dass jemand im Wiki-Repo committen muss.
- Voraussetzung: Jedes Quell-Repo muss das Wiki-Projekt in seinen
*Job token permissions* freigeben.
- Jede Quelle bleibt **ohne dieses Tool lesbar** (direkt im Repo oder in der
GitLab-Oberfläche). Deshalb bringen die Quellen keine Docusaurus-Metadaten mit und
`.md` wird als CommonMark statt MDX geparst.
- **Der `wiki`-Branch im gitops-Repo ist überholt.** Er bleibt vorerst als Historie
stehen, ist aber in README und CLAUDE.md ausdrücklich als „nicht die gepflegte
Fassung" markiert. Löschen wäre sauberer — Entscheidung dazu steht aus
([Issue #19](https://git.lab/axion1337.chat/management/-/issues/19)).
## Verworfene Alternativen
- **Alles in ein Repo verschmelzen:** Die Quellen haben unterschiedliche Leser und
Halbwertszeiten; eine gemeinsame Struktur hätte alle drei schlechter gemacht.
- **Wiki auf Gitea belassen:** widerspricht ADR-0002 und hielt eine Ausnahme am
Leben, die niemand mehr begründen konnte.
- **Inhalte per Submodule einbinden statt beim Bau klonen:** Submodules hätten die
Quellen an feste Commits gebunden — genau das Gegenteil von „das Wiki folgt den
Quellen".
- **MkDocs/Wiki.js:** Docusaurus gewählt wegen Multi-Instanz-Docs (die Bereiche
nebeneinander) und weil es rein statisch ausliefert — kein Server, keine Datenbank,
kein Betriebsaufwand.
@@ -0,0 +1,83 @@
---
type: adr
id: "0007"
status: proposed
date: 2026-08-02
supersedes: null
superseded_by: null
related: []
---
# 0007 — Wiki-Oberfläche: Docusaurus läuft, BookStack als Gegenentwurf
**Status:** vorgeschlagen (Entscheidung offen → [Issue #20](https://git.lab/axion1337.chat/management/-/issues/20)) · **Datum:** 2026-08-02 · **Entscheider:** sorb
## Kontext
[ADR-0006](0006-wikis-konsolidieren-docusaurus.md) hat die Wikis ins Lab geholt und
Docusaurus als Lesefläche gebaut — läuft seit 2026-08-02 unter `axionwiki.lab`.
Danach kam der Wunsch nach einer BookStack-Variante, angeregt von der
[ESS-Dokumentation von Element](https://ems-docs.element.io/), die selbst auf
BookStack läuft und sehr aufgeräumt wirkt.
Beide Werkzeuge lösen dieselbe Aufgabe grundverschieden — deshalb ist das keine
Werkzeugwahl, sondern eine Weichenstellung.
## Entscheidung
**Beide Varianten existieren parallel, bis an echten Inhalten entschieden wird —
und beide bleiben dabei ausschließlich im Homelab** (bestätigt von sorb am
2026-08-02: keine Exponierung, solange nicht entschieden ist, ob überhaupt eine
der beiden dauerhaft eingebaut wird).
Docusaurus bleibt vorerst der produktive Weg (`axionwiki.lab`, läuft), BookStack
kommt als Vergleichsentwurf daneben (`bookstack.lab`, Repo
[homelab/wiki-bookstack](https://git.lab/homelab/wiki-bookstack)).
Die eigentliche Entscheidung wird **vertagt** — bewusst, weil sie an einer Frage
hängt, die sich am Reißbrett nicht beantworten lässt: Soll Dokumentation künftig
**im Repo** entstehen oder **im Browser**?
## Der Unterschied, auf den es ankommt
| | Docusaurus | BookStack |
|---|---|---|
| Wo der Text lebt | in den Quell-Repos, beim Bau eingesammelt | **in einer Datenbank** |
| Wie geändert wird | Commit, Review, Git-Historie | WYSIWYG im Browser |
| Betrieb | statisches Image, zustandslos | Anwendung + MariaDB, **backup-pflichtig** |
| Suche | fehlt (Plugin nötig) | eingebaut |
| Rechte | alles oder nichts | je Buch/Regal |
| Quelle der Wahrheit | eindeutig das Repo | **die Datenbank — neben den Repos** |
Die letzte Zeile ist der Kern. Mit BookStack entsteht eine zweite Quelle der
Wahrheit neben git.lab — genau das, was [ADR-0002](0002-issues-und-management-ins-lab.md)
für Issues gerade aufgelöst hat. Das kann richtig sein (Dokumentation hat andere
Autoren und Lebenszyklen als Code), muss aber bewusst entschieden werden.
## Konsequenzen
- **Doppelter Betrieb auf Zeit.** Zwei Stacks, zwei Domains. Vertretbar, solange es
ein Vergleich ist; auf Dauer ist es Ballast — deshalb gehört zu dieser Entscheidung
ein Verfallsdatum.
- **BookStack braucht ein Backup, sobald es mehr als ein Versuch ist.** Eine
Datenbank ohne Sicherung ist eine Zeitbombe (vgl. CFGMON-09, wo genau das seit
2026-07-30 offen ist).
- **Ein Einweg-Import zum Befüllen, aber keine Synchronisation** (`import/` im
BookStack-Repo). Nachtrag 2026-08-02: Ohne Inhalte lässt sich nichts
vergleichen, deshalb legt `provision.py` dieselben drei Bereiche an wie das
Docusaurus-Wiki. Ein *wiederholter* Sync bleibt bewusst aus — BookStack führt
Inhalte in seiner Datenbank, ein zweiter Lauf würde dort Bearbeitetes
überschreiben und Synchronität vortäuschen, die es nicht gibt.
- Die **Themes sind farbgleich** zu den Element-Themes (Gruvbox Dark und Sunset
Boulevard), damit der Vergleich nicht an der Optik hängt, sondern am Arbeitsmodell.
- Die Verliererseite wird **abgeräumt**, nicht „für später" behalten.
## Verworfene Alternativen
- **Direkt auf BookStack wechseln:** Docusaurus läuft bereits und kostet nichts im
Betrieb; ein Wechsel ohne belastbaren Vergleich wäre Geschmacksentscheidung.
- **BookStack gar nicht ausprobieren:** Die Frage „im Repo oder im Browser
schreiben?" ist real und betrifft künftig jede Doku — sie lässt sich nur an
echten Inhalten beantworten.
- **Beides dauerhaft parallel:** zwei Quellen der Wahrheit für dieselben Inhalte,
garantierte Drift. Genau der Zustand, den der Wiki-Audit vom 2026-08-02 aufgedeckt
hat (drei auseinandergelaufene Stände).
@@ -0,0 +1,90 @@
---
type: adr
id: "0008"
status: accepted
date: 2026-08-06
supersedes: null
superseded_by: null
related: []
---
# 0008 — Agenten-Sessions auf CFGMON laufen root-äquivalent über die docker-Gruppe
**Status:** akzeptiert · **Datum:** 2026-08-06 (Struktur-Workshop [#17](https://git.lab/axion1337.chat/management/-/issues/17)) · **Entscheider:** sorb
## Kontext
`sudo` ist aus einer Agenten-Session nicht bedienbar: Es gibt kein TTY, also auch
keine Passworteingabe (*„a terminal is required to read the password"*). Die
LABNET-02-Nacht lief deshalb über die **docker-Gruppenmitgliedschaft** des Kontos
`rantanplan` — privilegierter Container plus `nsenter` in die Host-Namespaces.
Das ist **root-äquivalent**, und zwar ohne Umweg: Wer Docker steuern darf, darf
alles. Auf Linux ist das normales Verhalten der docker-Gruppe und kein
Konfigurationsfehler — aber es hat zwei Folgen, die benannt gehören:
1. Die **sudo-Passwortabfrage ist für dieses Konto keine Sicherheitsgrenze**. Sie
sieht aus wie eine, ist aber umgehbar, ohne dass jemand etwas umgehen wollte.
2. Dieser Weg hinterlässt **keinen Eintrag in `auth.log`**. Was über die
docker-Gruppe geschieht, ist im Nachhinein nicht aus den üblichen
Protokollen rekonstruierbar.
Aufgedeckt im [CFGMON-AAR](../../verfahren/aar/2026-08-01-labnet02-cfgmon.md)
(Befund 3, MEDIUM), erfasst als
[#14](https://git.lab/axion1337.chat/management/-/issues/14).
## Entscheidung
**Es bleibt so — ausdrücklich und dokumentiert.** Agenten-Sessions auf CFGMON
arbeiten root-äquivalent über die docker-Gruppe.
Daraus folgt unmittelbar: **Auf diesem Host gilt „sudo mit Passwort" nicht als
Kontrollmechanismus.** Wer dort Sicherheit gegen unbeabsichtigte oder
unerwünschte Änderungen braucht, muss sie woanders aufhängen — an der
Erreichbarkeit des Hosts, an der Vergabe der Zugangsdaten, an der Frage, wer
überhaupt eine Session starten kann.
**Ziel bleibt Option C** (eigenes Agenten-Konto mit definierter, protokollierter
Rechteerhöhung), aber **erst wenn ein zweiter Mensch mitarbeitet**. Der Nutzen von
C ist Zuordnung — nachvollziehen zu können, *wer* was getan hat. Im
Ein-Personen-Betrieb gibt es niemanden, gegen den diese Zuordnung schützen würde;
sie wäre Aufwand ohne Adressaten.
## Konsequenzen
- **Der Arbeitsweg bleibt funktionsfähig.** Host-Sessions können weiterarbeiten;
ohne diese Entscheidung wäre LABNET-02 gar nicht durchführbar gewesen.
- **Die Protokollierung fehlt und wird nicht ersetzt.** Wer im Nachhinein wissen
will, was eine Session auf CFGMON getan hat, findet es **nicht** in `auth.log`,
sondern nur in Git-Historie, Issues und AARs. Das ist der Grund, warum die
AAR-Pflicht und „alles Offene wird ein Issue" auf diesem Host besonders zählen —
sie sind hier der einzige Nachvollzug.
- **Ein kompromittiertes `rantanplan`-Konto ist ein kompromittierter Host.** Nicht
neu durch diese Entscheidung, aber jetzt ausgesprochen.
- **Auslöser für Option C** ist ein zweiter Mensch mit Zugang — nicht ein Datum.
Kommt einer dazu, wird diese ADR abgelöst.
## Offen, bewusst nicht vor der Entscheidung geklärt
Das Issue verlangte vorab zu klären, **welche anderen Konten in der docker-Gruppe
sind und ob dasselbe auf MATRIX gilt**. Beides ist ungeprüft — dafür braucht es
eine Host-Session, vom Mac aus ist es nicht einsehbar.
Das ändert an der Entscheidung nichts (sie betrifft den Weg, nicht seinen Umfang),
wohl aber an ihrer Reichweite: Sollte sich zeigen, dass weitere Konten in der
Gruppe sind, ist zu prüfen, ob die alle dort hingehören. Nachzuholen bei der
nächsten Host-Session, festgehalten in #14.
## Verworfene Alternativen
- **B — Konto aus der docker-Gruppe nehmen**, Docker-Zugriff über gezielte
sudo-Regeln. Auditierbar, aber es **legt Agenten-Sessions auf CFGMON lahm**:
`sudo` braucht ein TTY, das eine Session nicht hat. Löst ein Protokollproblem,
indem es den Arbeitsweg entfernt — vertretbar erst, wenn ein Ersatz steht.
- **C sofort** — getrenntes Agenten-Konto mit protokollierter Rechteerhöhung.
Sauberste Lösung, aber echte Arbeit für einen Nutzen, der im
Ein-Personen-Betrieb nicht anfällt. Bleibt das Ziel, siehe oben.
- **Stillschweigend so lassen.** Genau das ist der Fehler, den ADR-0002 und die
ADR-Pflicht für dauerhafte Ausnahmen adressieren: Eine Ausnahme nur zu
dokumentieren statt sie zu entscheiden, ist keine Entscheidung. Deshalb diese ADR
und nicht bloß ein Absatz in `hosts/cfgmon.md`.
@@ -0,0 +1,102 @@
---
type: adr
id: "0009"
status: accepted
date: 2026-08-07
supersedes: null
superseded_by: null
related: []
---
# 0009 — Commit-Konventionen und rückwirkende Anonymisierung der Historie
**Status:** akzeptiert · **Datum:** 2026-08-07 (Regel) / 2026-08-09 (Durchführung) · **Entscheider:** sorb
> Nachgetragen am 2026-08-09 in der [Retro](../../verfahren/retro/2026-08-09.md). Die
> Entscheidung war getroffen und ausgeführt, bevor sie als ADR vorlag — das ist
> genau der Fehler, den die ADR-Pflicht verhindern soll, und wird hier benannt
> statt geglättet.
## Kontext
Commit-Zeitstempel geben Arbeitszeiten preis. Bei einem Ein-Personen-Projekt mit
öffentlichem Gitea-Spiegel heißt das: Jeder, der die Repos liest, kann ablesen, an
welchen Abenden und bis wann gearbeitet wurde. Vor der Umstellung standen die
Commits eines Tages typischerweise zwischen 17:00 und 23:00 — ein Muster, das
nichts mit der Software zu tun hat.
Dazu kam eine uneinheitliche Sprache in den Commit-Nachrichten (Deutsch und
Englisch gemischt) und drei Autoren-Identitäten für dieselbe Person.
## Entscheidung
**Regel ab 2026-08-07**, gültig für alle Repos der Gruppe `axion1337.chat` und die
ThreadNet-Dienste:
- Nachrichten auf **Englisch**, Conventional-Commit-Stil
- Autor- **und** Committer-Datum auf **12:00:00 UTC des laufenden Tages**
**Rückwirkend angewandt am 2026-08-09** auf **251 Commits** — alles aus dieser
Zusammenarbeit, nicht die davor liegende Historie:
| Repo | umgeschrieben | Grenze |
|---|---|---|
| gitops | 117 von 264 | ab 2026-07-27 |
| management | 78 von 78 | vollständig |
| ThreadNet-Web | 47 von 50 | ab 2026-07-28 |
| threadnet-call | 9 von 9 | vollständig |
Dabei wurden 17 Tags mit umgezogen und die Autoren-Identitäten vereinheitlicht —
**mit Ausnahme des `turn-secret-rotation`-Bots**, dessen Commits maschinell
entstehen und deren Zuschreibung an einen Menschen falsch wäre.
Zeitstempel wurden auf **12:00 UTC des jeweils eigenen Tages** gesetzt, nicht auf
ein einheitliches Datum: Der Tag bleibt damit als Information erhalten, die
Uhrzeit verschwindet.
## Konsequenzen
- **Der Inhalt ist unverändert.** Alle Trees sind bitgleich, Nachrichten und
Merge-Struktur erhalten — geprüft über alle 251 Paare.
- **Alle SHAs im Bereich sind neu.** Verweise in Issues, Doku und Commit-Texten
zeigen ins Leere. Die Doku wurde nachgezogen (12 Stellen); für alles andere gibt
es die dauerhafte Zuordnungstabelle
[`shared/commit-zuordnung-2026-08-07.md`](../../shared/commit-zuordnung-2026-08-07.md).
- **Issue-Kommentare wurden bewusst NICHT umgeschrieben.** Eine Tabelle
nachzuschlagen ist zumutbar; nachträglich zu ändern, was jemand geschrieben hat,
beschädigt dieselbe Nachvollziehbarkeit ein zweites Mal.
- **Container-Images verweisen auf Commits, die es nicht mehr gibt.** Sie laufen
unverändert; ihre Herkunft ist nur über die Tabelle auflösbar.
- **Branch- und Tag-Schutz mussten vorübergehend gelockert werden.** Beide sind
wieder aktiv.
⚠️ **Der Nutzen ist begrenzt, und das gehört zur Entscheidung.** Geschützt ist
allein die Git-Historie. Push-Zeiten, Issue- und Kommentar-Zeitstempel,
Pipeline-Läufe und Paket-Veröffentlichungen tragen weiterhin die echte Uhrzeit,
liegen im selben GitLab und teilweise auf dem öffentlichen Spiegel — und sind
**feiner** als Commit-Zeiten, weil es mehr davon gibt. Wer wirklich kein Muster
ableitbar haben will, muss dort ansetzen. Diese ADR löst das nicht.
## Was dabei fast schiefging
Das Force-Push der umgezogenen Tags hat in ThreadNet-Web **drei Release-Pipelines
neu gestartet**. Ohne Sperre hätte `docker_web` das veröffentlichte Image
`v0.4.0` aus altem Quellcode gegen heutige Basis-Images neu gebaut und
überschrieben. Verhindert hat es allein, dass die geschützten Registry-Variablen
in diesem Fenster nicht verfügbar waren — **Zufall, nicht Planung**. Behoben in
ThreadNet-Web#14; die Sperre ist seit `3cb43f5` scharf.
## Verworfene Alternativen
- **Nur vorwärts, Historie unangetastet.** War die Empfehlung: kein Risiko, keine
toten Verweise, und der Nutzen des Rewrites ist durch die Plattform-Zeitstempel
ohnehin begrenzt. sorb hat sich bewusst dagegen entschieden — die Git-Historie
ist der Teil, der geklont und weitergegeben wird.
- **Alles umschreiben, auch die Vor-Historie.** Hätte fremde Autorenschaft
angefasst (`Scrublord@Mac.Bad`, 135 Commits aus der Zeit vor dieser
Zusammenarbeit) und den Nutzen nicht erhöht.
- **Ein einheitliches Datum für alle Commits.** Hätte die zeitliche Reihenfolge
unlesbar gemacht, ohne mehr zu schützen als der Tagesstempel.
- **Auch die Commit-Nachrichten rückwirkend übersetzen.** 251 Nachrichten neu zu
formulieren hieße, die darin festgehaltenen Begründungen umzuschreiben —
Inhaltsverlust für einen Stilgewinn, den nur die Vergangenheit hätte.
@@ -0,0 +1,50 @@
---
type: adr
id: "0010"
status: accepted
date: 2026-08-09
supersedes: null
superseded_by: null
related: []
---
# 0010 — Härtung ist ein eigener Meilenstein (M5); M1 misst nur Kaputtes
**Status:** akzeptiert · **Datum:** 2026-08-09 · **Entscheider:** sorb
## Kontext
M1 („Betrieb absichern") trug 33 von 70 offenen Issues — fast die Hälfte. Elf
davon waren zusätzliche Sicherheits-Werkzeuge (Falco, CrowdSec, Lynis, auditd,
WAF, PSA, Trivy …), keine kaputten Schutzmechanismen. Die roadmap.md hatte die
Frage seit 2026-08-06 explizit offen gestellt: eigener Meilenstein „Härtung"
oder M1 bewusst breit? Ein Meilenstein beantwortet „worauf zahlt es ein"
(CLAUDE.md) — ein M1, der Reparatur und Ausbau mischt, beantwortet das nicht
mehr.
## Entscheidung
Im Refinement vom 2026-08-09 (mit sorb, gemäß Board-Pflege-Regel nicht durch
eine Session allein): **M5 — Härtung** wird angelegt. Trennlinie:
> *Ist etwas Vorhandenes kaputt (M1) oder fehlt etwas, das wir noch nie
> hatten (M5)?*
14 Issues wurden aus M1 nach M5 verschoben (Verteilung danach laut Retro:
M1 18 · M2 21 · M3 4 · M4 13 · M5 14).
## Konsequenzen
- Der Meilenstein-Satz ist **M1M5**. `roadmap.md` (Kopf und „Stand"-Block)
und `CLAUDE.md` (Abschnitt Arbeitsframework, „M1M4") sind nachzuziehen —
beide behaupten Stand 2026-08-09 noch den alten Satz bzw. stellen die
Frage als offen dar (Analyse-Befund F-001).
- Neue Sicherheits-Issues werden an der Trennlinie einsortiert: fehlendes
Werkzeug → M5, kaputter Schutz → M1. Die Trennlinie steht damit hier und
nicht nur im Retro-Protokoll.
- Die Retro-Zahlen sind eine Momentaufnahme; verbindlich ist das Board.
## Verworfene Alternativen
- **M1 bewusst breit lassen:** verworfen — die Meilenstein-Frage „worauf
zahlt es ein" bekäme für die Hälfte des Backlogs zwei Antworten zugleich.
@@ -0,0 +1,69 @@
---
type: adr
id: "0011"
status: accepted
date: 2026-08-11
supersedes: null
superseded_by: null
related: []
---
# 0011 — Provisionierung verweigert Localpart-Kollisionen, statt an bestehende Konten zu verknüpfen
**Status:** akzeptiert · **Datum:** 2026-08-11 · **Entscheider:** sorb
## Kontext
Der MAS-Upstream-Provider für Authentik stand auf
`claims_imports.localpart.on_conflict: add`. MAS-Semantik: Kollidiert der aus dem
Authentik-Claim abgeleitete Localpart mit einem **bestehenden** Matrix-Konto,
verknüpft MAS die neue Upstream-Identität mit diesem Konto — ohne Abbruch, ohne
Warnung. Authentiks eigene Benutzernamen-Eindeutigkeit fängt das nicht ab: sie
gilt nur innerhalb von Authentik und ist case-sensitive (`boje` neben `Boje` ging
live durch). Folge: Ein Inhaber eines Einladungstokens konnte einen (auch nur in
der Schreibweise abweichenden) Namen eines bestehenden Kontos registrieren und
würde beim ersten Login in dessen Konto verknüpft — inklusive Dienstkonten ohne
Upstream-Link (`draupnir`, `alerts`, `maintenance-notify`). Das ist ein
Kontoübernahme-Vektor, entdeckt am 2026-08-11 beim Anlegen eines Testkontos
([gitops#61](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/61)).
## Entscheidung
**Identitäts-Provisionierung verknüpft eine neue Upstream-Identität niemals mit
einem bereits bestehenden lokalen Konto.** Konkret: `on_conflict: fail` im
`claims_imports.localpart`-Block des MAS-Upstream-Providers
(`gitops/apps/production/custom-configs/mas-secret.yaml`). Ein kollidierender
Localpart bricht die Provisionierung ab. Dies ist ab jetzt stehende Regel, nicht
nur der aktuelle Wert — jede künftige Änderung an diesem Verhalten braucht ein
ablösendes ADR.
## Konsequenzen
- **Besser:** Der Übernahme-Weg ist geschlossen. Bestehende Konten (besonders die
ohne Upstream-Link) können nicht mehr durch eine kollidierende Neuregistrierung
gekapert werden. Bestehende, korrekte Verknüpfungen bleiben unberührt.
- **In Kauf genommen:** Ein Nutzer, der einen bereits vergebenen Namen wählt,
erhält die Fehlermeldung erst **beim Login** (wenn MAS provisioniert), nicht
schon bei der Registrierung in Authentik. Das ist eine schlechtere UX, aber kein
Sicherheitsproblem.
- **Jetzt Pflicht:**
- Als offene Härtung eine **case-insensitive Eindeutigkeitsprüfung im
`matrix-invitation`-Prompt-Stage**, damit die Kollision schon bei der
Registrierung sichtbar wird (verfolgt in gitops#61).
- **Nach jeder Änderung an einem SOPS-verwalteten Values-Secret den
konsumierenden Dienst per `rollout restart` neu ausrollen und verifizieren**,
dass der Pod jünger als die Änderung ist. Beim Ausrollen dieses Fixes lief
MAS noch mit der alten Config im Speicher, obwohl das Secret bereits `fail`
zeigte — „committet" ≠ „deployed" ≠ „aktiv" (MAS liest Config nur beim Start).
## Verworfene Alternativen
- **`on_conflict: add` belassen und allein auf Authentiks Eindeutigkeit
vertrauen** — verworfen: die greift nur innerhalb Authentiks und
case-sensitive, deckt Kollisionen mit vorbestehenden Matrix-Konten also nicht ab.
- **Nur den Prompt-Stage-Check bauen, MAS auf `add` lassen** — verworfen: der
Client-seitige Check ist umgehbar (direkter Flow-Aufruf), der MAS-seitige
Abbruch ist die eigentliche Sicherheitsgrenze. Der Prompt-Check ist die
UX-Ergänzung, nicht der Schutz.
- **Betroffene Dienstkonten einfach mit Upstream-Links versehen** — verworfen:
behandelt nur das Symptom für heute bekannte Konten, nicht den Mechanismus.
@@ -7,8 +7,8 @@ supersedes: null
superseded_by: null
related:
- "docs/design/2026-08-11-neckbeard-migration.md"
- "decisions/0002-issues-und-management-ins-lab.md"
- "decisions/0005-pm-framework-kanban.md"
- "docs/adr/0002-issues-und-management-ins-lab.md"
- "docs/adr/0005-pm-framework-kanban.md"
---
# ADR-0012: Issues leben im Repo; GitLab wird deterministisch bespiegelt
@@ -7,7 +7,7 @@ supersedes: null
superseded_by: null
related:
- "docs/design/2026-08-11-neckbeard-migration.md"
- "decisions/0001-gitlab-kanonisch-push-mirror.md"
- "docs/adr/0001-gitlab-kanonisch-push-mirror.md"
---
# ADR-0013: Gruppenregeln kanonisch im management-Repo, Komponenten zeigen und werden geprüft
@@ -5,9 +5,9 @@ date: 2026-08-11
size: L
related:
- "PROJECT.md"
- "decisions/0005-pm-framework-kanban.md"
- "decisions/0009-commit-konventionen-und-historien-anonymisierung.md"
- "decisions/0010-haertung-eigener-meilenstein.md"
- "docs/adr/0005-pm-framework-kanban.md"
- "docs/adr/0009-commit-konventionen-und-historien-anonymisierung.md"
- "docs/adr/0010-haertung-eigener-meilenstein.md"
- "docs/adr/0012-issues-im-repo-gitlab-als-spiegel.md"
- "docs/adr/0013-gruppenregeln-kanonisch-mit-pruefung.md"
---