diff --git a/.gitignore b/.gitignore
index a907f3b..79f7c52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,6 @@ routeros/ap1-wg-hub.privkey
routeros/tp15-core-cutover-intended.rsc
docs/tp6-marker/.current-run
caddy/caddy.env
+
+# Config-Backups, die Sessions vor destruktiven Edits anlegen (2026-08-30)
+*.bak-*
diff --git a/CLAUDE.md b/CLAUDE.md
index b76d258..c004842 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -32,7 +32,7 @@ Wenn während einer Session ein neues Teilprojekt, eine Abhängigkeit oder ein Q
verworfenen TPs bleiben frei).
- Ist-Stand-Block mitziehen (Datum, betroffene Zeilen).
-**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist** — und der **Ist-Stand-Block wird bei jeder Änderung mitgezogen**, nicht nur der betroffene TP-Abschnitt.
+**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist — und wieder zurück, sobald doch etwas dazukommt.** Ein archiviertes TP ist ein *geschlossenes Ticket, kein versiegeltes*: bei neuer Arbeit an derselben Baustelle wandert der **Volltext zurück** nach `teilprojekte.md` (Zeiger bleibt im Archiv), **kein** Zwillings-TP mit neuer Nummer (Christoph-Direktive 2026-08-30, erster Fall TP28/`ap3`) — und der **Ist-Stand-Block wird bei jeder Änderung mitgezogen**, nicht nur der betroffene TP-Abschnitt.
---
@@ -61,6 +61,7 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`,
| **crs326-core** (CRS326, Technik UG) | **10.40.99.1** (VLAN99-SVI = L3-GW; same-subnet-Fallback `10.40.10.1`) · Identity `crs326-core` | RouterOS **7.24** · `admin`/`~/.secrets` (`CRS326_CORE_*`) · MikroMCP routerId `crs326-core` ✅ **live** (2026-08-16 verifiziert) · **TP15/TP21.1 ✅ 2026-08-08: PRODUKTIVER L3-Router** (ersetzt ap1) — Routing/DHCP (7 Server + 54 Leases)/DNS (**DoH zum CF-Gateway mit `verify-doh-cert=yes`**, TP23 Stufe 1 ✅ 2026-08-18; `servers=1.1.1.1` nur noch Bootstrap) · **NTP-Server für den Standort**/NAT/WG-Hub (fm+dw live); WAN `vlan100`=Liwest `90.146.33.177` · sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76 |
| hAP ax² #1 (ap1) | **10.40.99.11** (VLAN99) · Identity `ap1` | RouterOS **7.24** · `admin`/`~/.secrets` (`ROUTER_AP1_*`) · REST `http://10.40.99.11/rest` · MikroMCP `ap1` ✅ live · **TP28 ✅ 2026-08-17: reiner CAP** (Manager = `crs326-core`, `cap enabled=yes`, `discovery-interfaces=vlan99`) — Uplink `ether1` (PoE-Port, `poe-out=off`) an crs326-core `ether15`; SSIDs/Kanäle kommen vom Manager, ch36/ch1 · MAC D0:EA:11:4D:40:E0 · Config-Referenz `routeros/.secrets/ap1-prov-base.rsc` |
| hAP ax² #2 (ap2) | **10.40.99.12** (VLAN99) · Identity `ap2` | RouterOS **7.24** · `admin`/`~/.secrets` (`ROUTER_AP2_*`) · REST `http://10.40.99.12/rest` · MikroMCP `ap2` · **TP28 ✅ 2026-08-17: reiner CAP** — Uplink `ether1` (PoE) an crs326-branch `ether24`; ch44/ch11 (TP25: ch149 war unter ETSI leistungsbegrenzt) · MAC D0:EA:11:4D:40:3A · Config `routeros/.secrets/ap2-prov-base.rsc` |
+| **cAP ax (ap3)** | **10.40.99.13** (VLAN99, Ziel) · Identity `ap3` | ⏳ **Bring-up offen (TP28-Nachtrag, 2026-08-30)** · Modell `cAPGi-5HaxD2HaxD`, Werkszustand · Uplink `ether1` → **core `ether21`** (Trunk seit 2026-08-30 ✅; Gerät hängt und hat Strom — `link-ok`, MAC `D0:EA:11:FF:16:9D` = Label-E01) · Kanäle **`5180/Ce` + `2437`** (TP25-Plan 08-24) · 🔴 **nur 2 Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5` wie am hAP ax² · Dateien `routeros/tp28-ap3-bringup.rsc` + `-lifeline-abbau.rsc` |
| **🔑 Einheitliches Admin-PW (2026-08-08)** | alle 4 MikroTik (core/branch/ap1/ap2) | user `admin`, gleiches PW (`~/.secrets/credentials.env` → `ROUTER_*_PASS`/`CRS326_*_PASS`; „Überlebensversicherung", User-Vorgabe). Werks-Defaults ap1/ap2 damit obsolet. |
| NAS (nas-bf, DS923) | 10.40.20.10 | SSH `adm.christoph@10.40.20.10` (kein PW nötig) · Docker (Portainer endpointId=14) |
| platform-pg (LXC 105, pve) | 10.40.20.20 | SSH via pve (`pct exec 105 --`) · **TP20 ✅ 2026-07-16: PG16, DB `nautobot`, Creds `postgres/.secrets`** |
@@ -103,6 +104,19 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`,
## Kritische Gotchas
- 📜 **~~ALLNET-Gotchas (1 Telnet-VTY, `save` nicht vergessen)~~ — gegenstandslos seit 2026-08-08** (Gerät ausgebaut). Nur noch historisch relevant, s. `switch/README.md`.
+- 🔌 **`crs326-core` hat KEIN PoE-out** (`/interface ethernet poe` existiert dort gar nicht,
+ CRS326-24G-2S+). Ein AP an einem Core-Port braucht eigene Stromversorgung. Der **cAP ax** bringt
+ dafür **Netzteil UND Gigabit-PoE-Injector im Karton** mit (Produktseite, 2026-08-30) — die
+ TP25-Notiz „kein Netzteil im Lieferumfang üblich" gilt für dieses Modell **nicht**. Symptom, wenn
+ man das übersieht: der Core-Port steht auf `no-link` und man sucht den Fehler im VLAN-Tagging.
+- 🧩 **Die AP-Flotte ist seit ap3 heterogen — Literal-Listen im GC-Template sind dadurch gefährlich
+ geworden.** Der AP-Zweig hatte `ether3/ether4/ether5` als Bridge-Ports **hartkodiert**; das war die
+ stille Annahme *„AP == hAP ax² mit 5 Ports"*. Der **cAP ax hat zwei**. Seit 2026-08-30 kommt die
+ Liste aus **`ap.extra_ports`** (ConfigContext `ap-common`, Default = hAP-ax²-Bestand; ap3
+ überschreibt sie per `local_config_context_data` mit `[]`). ⚠️ **Nautobot merged Dicts tief, Listen
+ ERSETZEND** — für diesen Fall genau richtig, bei `zones`/`radios` wäre es eine Falle.
+ Merkregel: **bevor ein zweites Modell derselben Rolle dazukommt, die Literal-Blöcke im Template
+ daraufhin lesen, welche Zeile eigentlich „Gerätetyp" meint und nicht „Rolle".**
- 🔵 **Container auf MikroTik — korrigiert 2026-08-25, die alte Pauschalregel war nie nachgemessen.**
Es hiess hier „hAP ax² hat 128 MB Flash, kein USB → Container NICHT auf dem Router". Live gelesen:
**ap1/ap2 sind `arm64` mit 95 MB FREIEM Flash und 690 MB freiem RAM** — das reicht für **einen**
diff --git a/docs/teilprojekte-archiv.md b/docs/teilprojekte-archiv.md
index d667c84..e71e48c 100644
--- a/docs/teilprojekte-archiv.md
+++ b/docs/teilprojekte-archiv.md
@@ -60,7 +60,7 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
| [TP22](#tp22) | Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) | ✅ DURCH (2026-08-13) — Compliance heute **104/104, 0 Drifts** (live 2026-08-23) |
| [TP26](#tp26) | `/ip firewall filter` unter Drift-Kontrolle | ✅ DURCH (2026-08-17) |
| [TP27](#tp27) | RouterBOOT-Firmware auf 7.24 nachziehen | ✅ DURCH (2026-08-17/18) |
-| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ✅ KOMPLETT (2026-08-18) |
+| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ↩️ **wieder geöffnet 2026-08-30** (ap3) — Volltext in [teilprojekte.md](teilprojekte.md#tp28) |
| [TP-R](#tp-r) | Router-Capabilities-Sweep | FERTIG (docs/research/routeros-capabilities.md) |
Dazu am Ende: **[TP25 (Langfassung)](#tp25-lang)** — die Messchronik und die Funde A–E im Volltext.
@@ -1261,126 +1261,13 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
-### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ✅ **KOMPLETT (2026-08-18)** — mit Nachtrag: **`slaves-datapath` fehlte, IOT+MEDIA lagen ~14 h**
+### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ↩️ **WIEDER GEÖFFNET 2026-08-30**
-- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):**
- Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath`
- war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten
- Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit,
- **der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`,
- `-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind
- **statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.
- - **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):**
- `/interface wifi datapath add name=dp-caps-slaves bridge=bridge` +
- `/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine
- `vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische
- Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder.
- - 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT-
- Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle:
- `torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**).
- Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall
- — der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
- - ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations
- (30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner
- dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine
- **frische DHCP-Lease pro SSID**, nicht die Registration-Table.
- - **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:**
- mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette
- zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
- - ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath`
- ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager
- durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP
- sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs
- kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s**
- wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8`
- `last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`.
- - ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API
- antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy
- gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@ "/system reboot"`.
- - ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in
- `nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync
- → Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra`
- auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier
- allein erreicht Nautobot nicht.
- - ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem
- Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das
- als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln.
-
-- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen
- jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft
- (`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`).
- - 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 —
- je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte
- sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als
- Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den
- defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`.
- - ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP.
- Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht
- bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und
- referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten
- `configuration=`-Verweis, der beim Loeschen folgenlos blieb.
- - 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos:
- `/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf
- `/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte
- **nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
-- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman`
- (der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden,
- welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt
- `discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr).
- **Ergebnis: 92/92 compliant, 0 Drifts.**
-- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<>`
- im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen).
- Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die
- Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht
- als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck
- (`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den
- drei Bring-up-Dateien, die voneinander abgeschrieben sind.
-- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** —
- wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.
-> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett,
-> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom
-> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**.
-
-- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen
- Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von
- TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:**
- das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite
- Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen
- waren die Funkparameter aus TP25.
-- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations ·
- 4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in
- TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27,
- media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals
- verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen,
- sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
-- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales
- Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert
- deshalb nicht — `monitor` bestätigt `traffic processing on CAP`.
-- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.**
- `/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN
- einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit
- vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.**
-- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für
- iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die
- drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.**
- Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**.
- ✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar**
- (`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert.
- `wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines
- sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
-- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die
- `wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die
- toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs.
- Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules).
- *(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon
- „KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session
- gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern
- die Punkte darunter mitziehen.**)*
-- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes
- Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert
- nur `admin logged in/out via api`).
-- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort).
- Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope).
+> Der Volltext steht seit dem 2026-08-30 wieder in
+> [`teilprojekte.md` → TP28](teilprojekte.md#tp28), weil mit **ap3** (cAP ax) ein dritter
+> Access-Point dazukommt. **Ein archiviertes TP ist ein geschlossenes Ticket — es darf wieder
+> geöffnet werden** (Christoph-Direktive 2026-08-30); der Abschnitt wandert zurück, nicht nur
+> ein Nachtrag. Hier bleibt dieser Zeiger stehen, damit alte Links nicht ins Leere gehen.
diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md
index 75b1703..7d76996 100644
--- a/docs/teilprojekte.md
+++ b/docs/teilprojekte.md
@@ -62,6 +62,12 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d
(an core `ether15`), ap2 **`10.40.99.12`** (an branch `ether24`), beide **reine CAPs** —
`cap enabled=yes`, `discovery-interfaces=vlan99`. Die APs halten **keine eigene WLAN-Config** mehr;
Kanäle, SSIDs, Security und Steering kommen vom Manager. 802.11r/k/v aktiv, `ft-over-ds=yes`.
+ 🆕 **Dritter AP `ap3` (cAP ax) in Arbeit seit 2026-08-30** — **TP28 dafür wieder geöffnet**.
+ Core-seitig fertig: **`ether21` ist Trunk** (10/20/30/40/50/90/99 tagged, Muster `ether15`) und im
+ GC-Template nachgezogen, Compliance weiter **104/104 / 0 Drifts**. ap3 hängt bereits am Kabel und hat Strom (`link-ok`, MAC `D0:EA:11:FF:16:9D` = Label-E01),
+ der Bring-up steht aber noch aus. Ziel-IP **`10.40.99.13`**, Kanäle **`5180/Ce` + `2437`** nach dem
+ TP25-Kanalplan vom 2026-08-24. ⚠️ **ap1s Radios gehen vorher aus** (steht bis zum Umzug neben ap3);
+ ap1 kehrt später auf `5260/Ce` (DFS) zurück, ap2 auf `5300/Ce`.
- **SSDP-Relay** (TP7.1, **nachgebessert 08-25**): LXC **108 `mcast-relay`** auf pve, zweibeinig
`10.40.10.21` (VLAN10) + `10.40.40.21` (VLAN40) — dafür trägt `core ether13` beide VLANs; mDNS macht
weiterhin der Core. **Läuft seit 08-25 ohne `--ssdpUnicastAddr`** — die Flagge verwarf die M-SEARCH
@@ -197,7 +203,11 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
> **Alles hier hat noch offene Arbeit.** Abgeschlossenes steht im
> **[Archiv](teilprojekte-archiv.md)** (Volltext, mit Inhaltsverzeichnis) — die Zuordnung ist
-> mechanisch: *ein TP wandert erst dann ins Archiv, wenn nichts mehr daran zu tun ist.* Wo nur ein
+> mechanisch: *ein TP wandert erst dann ins Archiv, wenn nichts mehr daran zu tun ist* — **und der
+> Weg zurück ist offen: ein archiviertes TP ist ein geschlossenes Ticket, kein versiegeltes.**
+> Kommt an derselben Baustelle neue Arbeit dazu, **wandert der Volltext zurück hierher** (Zeiger
+> bleibt im Archiv stehen, damit alte Links halten) — statt ein Zwillings-TP mit neuer Nummer
+> anzulegen. *(Christoph-Direktive 2026-08-30, erster Fall: **TP28** wegen `ap3`.)* Wo nur ein
> Sub-TP offen war, steht dieses hier und das erledigte Eltern-TP im Archiv (so bei
> ~~**TP17.2** ↔ TP17~~ *(TP17.2 ist am 2026-08-24 in **TP30** aufgegangen und kein eigenes TP mehr)*
> und **TP20.1** ↔ TP20).
@@ -223,6 +233,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
> | TP23 | ✅ | DNS-Policy (Stufe 1+2 ✅) | 2026-08-26 |
> | TP24 | 🔜 | DC-USV | — |
> | TP25 | ✅ | WLAN-Stabilisierung (Kern erledigt) | 2026-08-24 |
+> | TP28 | ⏳ | WLAN/CAPsMAN — **wieder geöffnet**: dritter AP `ap3` (cAP ax) | Kern ✅ 2026-08-18 |
> | TP29 | 💡 | Anwesenheitserkennung (Idee) | — |
> | TP30 | 🔜 | Credentials (Etappe 1 ✅, Rest gebündelt) | — |
> | TP31 | ✅ | Reverse Proxy Caddy (Schritt 1–4 ✅) | — |
@@ -1247,6 +1258,206 @@ die Redundanzfrage oben.
- **Abh.:** TP22 ✅ (GC-Deploy-Pipeline). Verwandt **TP3.2** (`docs/wifi-p750w-coverage-and-map.md`),
**TP29** (nutzt dieselbe Datenquelle als Sensor). **Kein Blocker für irgendetwas.**
+
+
+### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ⏳ **WIEDER GEÖFFNET 2026-08-30: dritter AP `ap3`**
+
+> **Der CAPsMAN-Kern ist und bleibt ✅ KOMPLETT (2026-08-18)** — der Abschnitt darunter ist
+> unverändert der Archivtext inkl. Post-Mortem. Wieder geöffnet wurde das TP am 2026-08-30,
+> weil mit **ap3** ein dritter CAP dazukommt; das ist dieselbe Baustelle, kein neues TP
+> (Christoph-Direktive: *ein archiviertes TP ist ein geschlossenes Ticket und darf wieder
+> geöffnet werden*). **Der offene Teil steht am Ende des Abschnitts unter „Nachtrag ap3".**
+
+- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):**
+ Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath`
+ war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten
+ Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit,
+ **der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`,
+ `-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind
+ **statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.
+ - **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):**
+ `/interface wifi datapath add name=dp-caps-slaves bridge=bridge` +
+ `/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine
+ `vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische
+ Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder.
+ - 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT-
+ Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle:
+ `torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**).
+ Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall
+ — der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
+ - ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations
+ (30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner
+ dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine
+ **frische DHCP-Lease pro SSID**, nicht die Registration-Table.
+ - **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:**
+ mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette
+ zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
+ - ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath`
+ ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager
+ durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP
+ sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs
+ kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s**
+ wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8`
+ `last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`.
+ - ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API
+ antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy
+ gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@ "/system reboot"`.
+ - ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in
+ `nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync
+ → Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra`
+ auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier
+ allein erreicht Nautobot nicht.
+ - ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem
+ Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das
+ als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln.
+
+- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen
+ jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft
+ (`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`).
+ - 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 —
+ je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte
+ sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als
+ Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den
+ defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`.
+ - ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP.
+ Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht
+ bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und
+ referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten
+ `configuration=`-Verweis, der beim Loeschen folgenlos blieb.
+ - 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos:
+ `/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf
+ `/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte
+ **nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
+- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman`
+ (der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden,
+ welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt
+ `discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr).
+ **Ergebnis: 92/92 compliant, 0 Drifts.**
+- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<>`
+ im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen).
+ Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die
+ Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht
+ als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck
+ (`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den
+ drei Bring-up-Dateien, die voneinander abgeschrieben sind.
+- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** —
+ wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.
+> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett,
+> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom
+> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**.
+
+- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen
+ Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von
+ TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:**
+ das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite
+ Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen
+ waren die Funkparameter aus TP25.
+- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations ·
+ 4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in
+ TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27,
+ media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals
+ verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen,
+ sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
+- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales
+ Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert
+ deshalb nicht — `monitor` bestätigt `traffic processing on CAP`.
+- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.**
+ `/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN
+ einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit
+ vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.**
+- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für
+ iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die
+ drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.**
+ Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**.
+ ✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar**
+ (`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert.
+ `wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines
+ sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
+- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die
+ `wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die
+ toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs.
+ Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules).
+ *(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon
+ „KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session
+ gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern
+ die Punkte darunter mitziehen.**)*
+- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes
+ Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert
+ nur `admin logged in/out via api`).
+- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort).
+ Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope).
+
+
+#### ↩️ Nachtrag ap3 — dritter Access-Point (cAP ax), offen seit 2026-08-30
+
+**Gerät:** MikroTik **cAP ax** (`cAPGi-5HaxD2HaxD`), Werkszustand, Uplink an **`crs326-core ether21`**.
+**Mgmt-IP `10.40.99.13/24`** (TP32: `.10–.29` = physische Hosts; ap1 `.11`, ap2 `.12`), statisch am
+Gerät wie ap1/ap2 — **keine** DHCP-Reservierung.
+
+**Warum überhaupt:** ap3 soll **ap1 in Technik UG ablösen**, ap1 wandert danach ans Sideboard in die
+gemessene Schwäche (Schlafzimmer/Bad/Küche). Das ist exakt **TP25 Variante 3** („cAP ax an die
+bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten").
+
+**Kanalplan = der aus TP25 (2026-08-24 gerechnet, DFS von Christoph genehmigt) — nicht neu erfunden:**
+
+| AP | 5 GHz | Band | DFS | 2,4 GHz | wann |
+|---|---|---|---|---|---|
+| **ap3** | `5180/Ce` (36+40) | A | nein | **2437 (ch6)** | **jetzt** |
+| ap2 | `5220/Ce` heute → **`5300/Ce`** (60+64) | A → B | → ja | 2462 (ch11) | beim ap1-Umzug |
+| ap1 | — (**Radios aus**) → **`5260/Ce`** (52+56) | → B | → ja | 2412 (ch1) | beim ap1-Umzug |
+| — | `5220/Ce` **Ausweichspur frei halten** | A | — | — | — |
+
+🔴 **ap1s Radios gehen AUS, bevor ap3s angehen** (User-Vorgabe 2026-08-30): beide stehen bis zum
+Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört. Das ist ein
+**Interim**, kein Endzustand — ap1 kommt auf `5260` zurück.
+
+> 📌 **Erratum 2026-08-30:** der Port ist **`ether21`**, nicht `ether22` (erste Annahme aus der
+> Fragerunde). Der Befund lag die ganze Zeit in den Daten: in der dynamischen `pvid`-VLAN1-Zeile
+> steht `current-untagged=…,ether21` — `ether22` taucht dort nicht auf, weil es keinen Link hat.
+> **Merkregel: bevor man einen Port konfiguriert, den behaupteten Port gegen `link-ok` + die
+> Bridge-Host-Tabelle stellen.** `/interface bridge host print where interface=etherN` nennt die
+> MAC des Gegenübers — hier `D0:EA:11:FF:16:9D`, exakt die `E01` vom ap3-Label.
+
+##### Stand 2026-08-30 (diese Session)
+- ✅ Backups core+ap1 (`pre-ap3-20260830.backup`/`.rsc` auf den Geräten), Compliance-Ausgangsstand
+ **104/104, 0 Drifts**.
+- ✅ **`core ether21` ist Trunk**: tagged in 10/20/30/40/50/90/99, Muster = `ether15` (ap1).
+ `pvid=1`/`frame-types=admit-all` unverändert; das VLAN1-Bein entsteht dynamisch aus dem pvid.
+- ✅ **GC-Template nachgezogen und gepusht** (Commit `e25d654b`): `ether21` im Core-L2-Block, und
+ der AP-Zweig rendert die Bridge-Ports jetzt aus **`ap.extra_ports`** statt hartkodiert
+ `ether3/4/5`. **Compliance danach wieder 104/104, 0 Drifts** — das beweist, dass ap1/ap2
+ zeichengleich rendern.
+- ✅ Bring-up-Dateien geschrieben: [`routeros/tp28-ap3-bringup.rsc`](../routeros/tp28-ap3-bringup.rsc)
+ + [`routeros/tp28-ap3-lifeline-abbau.rsc`](../routeros/tp28-ap3-lifeline-abbau.rsc).
+- 🔜 **Offen:** physischer Bring-up (Strom + `/import`), ap1-Radios aus, CAPsMAN-Objekte für ap3,
+ Nautobot-Adoption, PW-Rotation, MikroMCP-Registry, Messrunde.
+
+##### Was am cAP ax anders ist als am hAP ax² — und was daran wehtut
+- 🔴 **Nur ZWEI Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5`. Der
+ GC-Template-AP-Zweig hatte die **hartkodiert**; das war die stille Annahme *„AP == hAP ax²"*.
+ Deshalb `ap.extra_ports` im ConfigContext (Default = `["ether3","ether4","ether5"]`, ap3 = `[]`).
+ **Merkregel: eine Literal-Liste im Template ist so lange harmlos, bis die Flotte heterogen wird.**
+- **`crs326-core` hat kein PoE-out** (`/interface ethernet poe` existiert dort nicht). Der cAP ax
+ bringt aber **Netzteil *und* Gigabit-PoE-Injector** mit (Produktseite 2026-08-30) — die TP25-Notiz
+ „kein Netzteil im Lieferumfang üblich" trifft auf dieses Modell **nicht** zu.
+- `ether2` bleibt im Bring-up als **Lifeline** im Bridge (untagged VLAN1) und fliegt erst raus,
+ wenn ap3 über `10.40.99.13` steht — Zielzustand = wie ap2: `ether2` **kein** Bridge-Port
+ (VLAN1 bleibt leer, TP19 ist geschlossen).
+- **Radio-MACs nicht raten:** `/interface wifi radio print` **am Manager** listet CAP-Identity +
+ Radio-MAC ohne jede Passphrase. Das ist der richtige Read — **nicht** `list_wifi_interfaces`,
+ das gibt `security.passphrase` *aller* Profile im Klartext zurück (Leak 2026-08-24).
+
+##### Auflagen aus TP25 für den ap1-Umzug (nicht vergessen)
+1. **DFS-Channel-Objekte brauchen eine Frequenz-LISTE** (z.B. `frequency=5260,5280,5220`). Mit
+ genau einem Wert ist eine Radar-Erkennung ein **SPOF**: das Radio muss den Kanal verlassen und
+ hat keinen zweiten → 5 GHz dieses APs bleibt unten.
+2. **Vorher die *erreichte* `tx-power` messen** (`/interface wifi monitor`), ap2 testweise auf
+ `5260` gegen die heutigen 18 dBm auf `5220`. Fund B war genau das Muster: 18 dBm gesetzt,
+ 9 dBm gesendet.
+3. Uplink für ap1 danach: **`core ether3`** (`no-link`, frei) — vorher dort die Altlast abräumen,
+ der Port ist untagged-Member von VLAN40 bei `pvid=1`.
+
+
### TP29 — Anwesenheitserkennung fürs Energiesparen ▸ 💡 **IDEE, noch nicht auf dem kritischen Pfad** (angelegt 2026-08-21)
> **User-Formulierung (2026-08-21):** „Personen im Haus orten mit BT (Shellys), WiFi und Radar. So
> kommen wir dem Ziel der Energieersparnis ohne Komfortverlust groß näher."
@@ -1996,7 +2207,8 @@ den Weg, über den man den Fehler zurücknehmen wollte.
diese Datei ist von **2217 auf ~760 Zeilen**. Die Regel dahinter bleibt gültig:
⚠️ **während einer Doku-Session `teilprojekte.md` nicht parallel bearbeiten** (in der Nacht auf den
18. viermal committet, das kollidiert sonst) — und **ein TP wandert erst dann ins Archiv, wenn
- nichts mehr daran zu tun ist.**
+ nichts mehr daran zu tun ist.** *(Ergänzt 2026-08-30: und **zurück**, wenn doch wieder etwas
+ zu tun ist — das Archiv ist eine Ablage, kein Endlager.)*
### Nachgelagert / research-getrieben
diff --git a/mcp/INVENTORY.md b/mcp/INVENTORY.md
index 3069263..bc1de35 100644
--- a/mcp/INVENTORY.md
+++ b/mcp/INVENTORY.md
@@ -39,7 +39,7 @@
| MCP | Zweck / Geräte | Host | Transport / Endpoint | Image | Config | Status |
|---|---|---|---|---|---|---|
-| **MikroMCP** (AliKarami) | MikroTik hAP ax² ap1/ap2 (RouterOS 7) — WLAN/CAPsMAN, Router-Cutover | **jetzt: lokal (dieser Mac)** stdio / Ziel: HTTP-Container auf Mac mini `10.40.20.167` | jetzt **stdio** (`npx mikromcp serve`) / Ziel HTTP `:3000` | `ghcr.io/alikarami/mikromcp:latest` (HTTP) bzw. npm `mikromcp` (stdio) | `mcp/mikromcp/` + opencode/Claude-Code user-config | 🟢 **live (stdio), doctor 12/12, in opencode ✓Connected** |
+| **MikroMCP** (AliKarami) | MikroTik-Flotte: `crs326-core` `.99.1` · `crs326-branch` `.99.2` · ap1 `.99.11` · ap2 `.99.12` · **🆕 ap3 `.99.13` (cAP ax, 2026-08-30, Bring-up offen)** — alle RouterOS **7.24** | **jetzt: lokal (dieser Mac)** stdio / Ziel: HTTP-Container auf Mac mini `10.40.20.167` | jetzt **stdio** (`npx mikromcp serve`) / Ziel HTTP `:3000` | `ghcr.io/alikarami/mikromcp:latest` (HTTP) bzw. npm `mikromcp` (stdio) | **Registry = `bf-infra/mcp/mikromcp/config/routers.yaml`** (seit 2026-08-30 SSoT) + opencode/Claude-Code user-config | 🟢 **live (stdio), doctor 12/12, in opencode ✓Connected.** 🔴 **Zwei Drifts am 2026-08-30 gefunden und behoben:** (a) `MIKROMCP_CONFIG_PATH` in `~/.claude.json` zeigte auf **`~/dev/bf-network-r6`** (Archiv-Repo) — die identische Datei in `bf-infra` war **wirkungslos**; (b) in `opencode.jsonc` fehlte `MIKROMCP_CONFIG_PATH` **ganz** und es waren nur `ROUTER_AP1/AP2`-Creds hinterlegt → opencode kannte core/branch gar nicht. Beide Clients zeigen jetzt auf dieselbe Registry, `rosVersion` von stale 7.23/7.19.5 auf 7.24 korrigiert. ⚠️ **Neustart des MCP nötig** (Registry wird nur beim Start gelesen). ⚠️ opencode nutzt Env-Indirektion `{env:MCP_MIKROMCP_*}`, `~/.claude.json` **Klartext** — für ap3/core/branch fehlen noch `MCP_MIKROMCP_ROUTER_AP3_PASS`, `MCP_MIKROMCP_CRS326_CORE_PASS`, `MCP_MIKROMCP_CRS326_BRANCH_PASS` in `~/.secrets/exports.zsh` (TP30)** |
| **gitea-mcp** (eigenbau) | Gitea REST API (Repo-Verwaltung, Issues, Wiki, …) | **Mac mini** `10.40.20.167` (Portainer-Endpoint 20) — Docker-Bridge-IP `172.17.0.3:3000` — ⚠️ **sollte auf DS923 umziehen** (Performance-Restriktion: macmini ist User-Workstation, kein zentraler MCP-Host) | **StreamableHTTP** `https://gitea-mcp.emsenhuber.net/mcp` (Caddy + CF) bzw. `http://10.40.20.167:3000/mcp` (LAN-direkt) | `docker.io/node:22-alpine` (eigener Build) | Repo `mcp/` hatte **kein** Config-Verzeichnis — Container-Definition lebt auf macmini | 🟢 **live (seit unknown, gefunden 2026-08-28), war aus dem Inventar verschwunden. serverInfo: gitea-mcp 0.0.10. Umzug auf DS923 ausstehend (Performance-Restriktion)** |
| **n8n-mcp** (czlonkowski) | n8n-public-API (Workflows, Credentials, Executions) | **Mac mini** `10.40.20.167` (Portainer-Endpoint 20, **n8n-Host** — n8n `:5678`, n8n-redis `:6379`, n8n-mcp `:3001`) — Docker-Bridge-IP `172.19.0.4` → PublicPort `3001:3000` | **StreamableHTTP** `https://n8n-mcp.emsenhuber.net/mcp` (Caddy + CF) bzw. `http://10.40.20.167:3001/mcp` (LAN-direkt) | `ghcr.io/czlonkowski/n8n-mcp:latest` | keine im Repo (`mcp.json.example` in `blaueblueten-n8n/` ist alt, stdio-Modus) | 🟢 **live (gefunden 2026-08-28, war **nicht** im Inventar). FQDN → 401 ohne Bearer (Auth-Sanierung TP16.3). LAN-direkt :3001 antwortet korrekt mit 401 = Bearer fehlt, Server lebt. Bleibt auf macmini (zusammen mit n8n — n8n-Workflows lösen Tool-Calls aus, lokale Performance-Kopplung gewollt)** |
| **shelly-mcp** (eigenbau, FastMCP) | ~26 Shellys Gen3/4 — VLAN1→VLAN30-Migration (TP17) + Verwaltung | **geplant:** DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.180`** | **StreamableHTTP** `http://10.40.20.180:3030/mcp` (geplant) | eigenbau `bf/shelly-mcp:0.1` (DS923-lokal gebaut aus `mcp/shelly/Dockerfile`) | `mcp/shelly/` ✓ (`Dockerfile` + `BUILD.md` + `requirements.txt`) · Stack gitignored in `docs/nas-vlan20/stacks/shelly-mcp.yml` | 🟡 **staged — Image-Build + Stack-Deploy auf DS923 ausstehend. Vorher-Stand war localhost `127.0.0.1:3030` mit `enabled=false` (Handoff 2026-08-28). Live-Verifikation 23 Shellys auf `10.40.30.180-250` (VLAN30) bestätigt 2026-08-28** |
diff --git a/mcp/mikromcp/config/routers.yaml b/mcp/mikromcp/config/routers.yaml
index c3f4fda..74c5354 100644
--- a/mcp/mikromcp/config/routers.yaml
+++ b/mcp/mikromcp/config/routers.yaml
@@ -7,6 +7,10 @@
# TP21.1-Stand (2026-08-08): crs326-core IST der L3-Router (Routing/DHCP/DNS/NAT/WG-Hub live).
# core mgmt = 10.40.99.1 (vlan99-SVI, aus VLAN10-Client via core geroutet erreichbar;
# Same-Subnet-Fallback = 10.40.10.1). branch = 10.40.99.2 (L2, .10.2 temp).
+# 2026-08-30 (TP28-Nachtrag): ap3 (cAP ax, .99.13) aufgenommen, Uplink core ether21.
+# Diese Datei ist seit 2026-08-30 die EINZIGE gelesene Registry: MIKROMCP_CONFIG_PATH
+# zeigte bis dahin auf ~/dev/bf-network-r6 (Archiv-Repo) -- Aenderungen hier waren
+# wirkungslos. rosVersion war auf allen Eintraegen stale (7.23/7.19.5), live ist 7.24.
# ap1/ap2 (.99.11/.99.12) = Zielwerte — ap1 ist derzeit Werksreset/getrennt und kommt
# erst mit der WLAN-Neuprovisionierung (eigene Session) zurück.
@@ -21,7 +25,7 @@ routers:
source: "env"
envPrefix: "ROUTER_AP1"
tags: ["bf", "wifi", "access-point"]
- rosVersion: "7.23"
+ rosVersion: "7.24"
ap2:
host: "10.40.99.12"
@@ -33,7 +37,7 @@ routers:
source: "env"
envPrefix: "ROUTER_AP2"
tags: ["bf", "wifi", "access-point"]
- rosVersion: "7.23"
+ rosVersion: "7.24"
crs326-core:
host: "10.40.99.1"
@@ -45,7 +49,7 @@ routers:
source: "env"
envPrefix: "CRS326_CORE"
tags: ["bf", "switch", "core", "l3-router"]
- rosVersion: "7.19.5"
+ rosVersion: "7.24"
crs326-branch:
host: "10.40.99.2"
@@ -57,4 +61,16 @@ routers:
source: "env"
envPrefix: "CRS326_BRANCH"
tags: ["bf", "switch", "branch"]
- rosVersion: "7.19.5"
+ rosVersion: "7.24"
+
+ ap3:
+ host: "10.40.99.13"
+ port: 80
+ tls:
+ enabled: false
+ rejectUnauthorized: true
+ credentials:
+ source: "env"
+ envPrefix: "ROUTER_AP3"
+ tags: ["bf", "wifi", "access-point"]
+ rosVersion: "7.24"
diff --git a/nautobot/ingest-ap-configcontext.py b/nautobot/ingest-ap-configcontext.py
index 9c9732d..6655e72 100644
--- a/nautobot/ingest-ap-configcontext.py
+++ b/nautobot/ingest-ap-configcontext.py
@@ -51,6 +51,13 @@ AP_COMMON = {
"country": "Austria",
"bridge": "bridge",
"uplink": "ether1", # PoE-Port, poe-out=off
+ # Bridge-Ports AUSSER Uplink und Radios. Bis 2026-08-30 standen ether3/4/5
+ # hartkodiert im Template — das galt still fuer "AP == hAP ax2 mit 5 Ports".
+ # ap3 ist ein cAP ax mit ZWEI Ports (ether1 PoE-in, ether2 PoE-out); dort ist
+ # die Liste leer. Default hier = hAP-ax2-Bestand, ap3 ueberschreibt per
+ # local_config_context_data. (Nautobot merged Dicts tief, LISTEN ERSETZEND —
+ # genau das ist hier gewollt.)
+ "extra_ports": ["ether3", "ether4", "ether5"],
"mgmt": {"vlan": 99, "gateway": "10.40.99.1"},
"native_vlan": 1,
# Liste (nicht Dict): Reihenfolge ist garantiert und diffbar.
diff --git a/nautobot/mikrotik_routeros.j2 b/nautobot/mikrotik_routeros.j2
index 5cb42d2..9862ba3 100644
--- a/nautobot/mikrotik_routeros.j2
+++ b/nautobot/mikrotik_routeros.j2
@@ -83,13 +83,16 @@
# ether13 (pve) traegt VLAN10 + VLAN40 seit 2026-08-18: der SSDP-Relay-LXC fuer die
# macOS-Sonos-App braucht ein Bein in beiden (TP7.1). SSDP ist link-lokal mit TTL 1 —
# ein Relay muss Mitglied beider Broadcast-Domaenen sein, Routing hilft nicht.
-/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15 vlan-ids=10
-/interface bridge vlan add bridge=bridge tagged=ether8,ether13,ether15 vlan-ids=20
-/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 untagged=ether14 vlan-ids=30
-/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15 untagged=ether2,ether3,ether4,ether5 vlan-ids=40
-/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 vlan-ids=50
-/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 vlan-ids=90
-/interface bridge vlan add bridge=bridge tagged=ether8,ether15,bridge vlan-ids=99
+# ether21 = ap3 (cAP ax, Technik UG) seit 2026-08-30, TP28-Nachtrag: gleicher AP-Trunk
+# wie ether15/ap1. Der Port traegt pvid=1 + frame-types=admit-all wie ether15 -- das
+# VLAN1-untagged-Bein entsteht dynamisch aus dem pvid und steht deshalb nicht hier.
+/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15,ether21 vlan-ids=10
+/interface bridge vlan add bridge=bridge tagged=ether8,ether13,ether15,ether21 vlan-ids=20
+/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15,ether21 untagged=ether14 vlan-ids=30
+/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15,ether21 untagged=ether2,ether3,ether4,ether5 vlan-ids=40
+/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15,ether21 vlan-ids=50
+/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15,ether21 vlan-ids=90
+/interface bridge vlan add bridge=bridge tagged=ether8,ether15,ether21,bridge vlan-ids=99
/interface bridge vlan add bridge=bridge tagged=ether1 untagged=ether16 vlan-ids=100
# [1] INTERFACE-LISTS
@@ -450,9 +453,12 @@
# [4b] BRIDGE-PORTS — ether2 fehlt bewusst: seit der Uplink-Umstellung auf ether1 (PoE)
# ist er KEIN Bridge-Port mehr. ether1 traegt kein comment=defconf (beim Rebuild
# neu angelegt), die uebrigen schon — deshalb hier nicht ueber einen Kamm geschoren.
-/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether3
-/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether4
-/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether5
+# Die Liste kommt seit 2026-08-30 aus `ap.extra_ports` statt hartkodiert: ap1/ap2
+# sind hAP ax2 mit ether3/4/5, ap3 ist ein cAP ax mit nur zwei Ports (Liste leer).
+# Die alte Hartkodierung war die stille Annahme "AP == hAP ax2".
+{% for p in ap.extra_ports %}
+/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface={{ p }}
+{% endfor %}
{% for r in ap.radios %}
/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface={{ r.iface }}
{% endfor %}
diff --git a/opencode.jsonc b/opencode.jsonc
index 0acf540..bcdb3c8 100644
--- a/opencode.jsonc
+++ b/opencode.jsonc
@@ -5,7 +5,7 @@
"mcp": {
"gitea-mcp": {
"type": "remote",
- "url": "https://gitea-mcp.emsenhuber.net/mcp",
+ "url": "http://10.40.20.167:3000/mcp",
"enabled": true
},
"mikromcp": {
diff --git a/routeros/tp28-ap3-bringup.rsc b/routeros/tp28-ap3-bringup.rsc
new file mode 100644
index 0000000..eecf344
--- /dev/null
+++ b/routeros/tp28-ap3-bringup.rsc
@@ -0,0 +1,109 @@
+# =====================================================================================
+# ap3 — Bring-up als reiner CAP unter dem CAPsMAN-Manager crs326-core.
+# TP28-Nachtrag, 2026-08-30. Geraet: MikroTik cAP ax (cAPGi-5HaxD2HaxD), Werkszustand.
+#
+# WEG: Laptop an ap3 ether2 (cAP ax HAT einen zweiten Port), Datei per scp hoch,
+# dann `/import file=tp28-ap3-bringup.rsc`. ether1 dabei NOCH NICHT am Netz.
+# Danach ether1 -> crs326-core ether21 (ist seit 2026-08-30 Trunk).
+#
+# 🔴 ether2 bleibt in DIESER Datei als LIFELINE im Bridge (untagged VLAN1), damit die
+# Sitzung das abschliessende `vlan-filtering=yes` ueberlebt. Der Abbau ist eine
+# EIGENE Datei (tp28-ap3-lifeline-abbau.rsc) und laeuft ERST, wenn ap3 ueber
+# 10.40.99.13 erreichbar ist. Zielzustand = wie ap2: ether2 kein Bridge-Port.
+#
+# ⚠️ NICHT von routeros/.secrets/ap*-prov-base.rsc abgeleitet — die sind als
+# historisch/nicht-deploybar markiert und tragen veraltete PSKs. Vorlage ist der
+# LIVE-Stand von ap2 (2026-08-30 gelesen).
+#
+# DELTAS ZU ap1/ap2 (hAP ax2): cAP ax hat nur ZWEI Ethernet-Ports (ether1 PoE-in,
+# ether2 PoE-out) — es gibt kein ether3/4/5. Der GC-Template-AP-Zweig hat die
+# hartkodiert; dort kommt `ap.extra_ports` dazu (fuer ap3 = leere Liste).
+# =====================================================================================
+
+/system identity set name=ap3
+
+# --- L3/WAN-Reste weg. Ein Werks-cAP bringt DHCP-Client/Server + NAT mit; beides hat
+# im BF-Netz nichts verloren (der Core ist DHCP- und NAT-Instanz).
+/ip dhcp-client remove [find]
+/ip dhcp-server remove [find]
+/ip dhcp-server network remove [find]
+/ip pool remove [find]
+/ip firewall nat remove [find]
+/ipv6 dhcp-client remove [find]
+
+# --- Interface-Lists: ether1 ist Uplink, NICHT WAN.
+:if ([:len [/interface list find where name=LAN]]=0) do={ /interface list add name=LAN }
+:if ([:len [/interface list find where name=WAN]]=0) do={ /interface list add name=WAN }
+/interface list member remove [find where interface=ether1]
+
+# --- Bridge sicherstellen, VLAN-Filter waehrend des Umbaus AUS.
+:if ([:len [/interface bridge find where name=bridge]]=0) do={ /interface bridge add name=bridge }
+/interface bridge set [find name=bridge] vlan-filtering=no
+# ether2 traegt PoE-out. Nichts haengt dran -> aus, wie ap1/ap2 es mit ihrem PoE-Port halten.
+/interface ethernet set [find name=ether2] poe-out=off
+
+# --- Werks-WLAN abraeumen. REIHENFOLGE IST PFLICHT: `configuration` referenziert
+# security/datapath/channel; wer die Referenzierten zuerst loescht, bekommt einen
+# Fehler und laesst einen Teil-Zustand zurueck (Lehre aus tp28-ap-wifi-reste-abraeumen.rsc).
+# `[find !dynamic]` waere hier ein SYNTAXFEHLER — diese Objekte haben gar keine
+# dynamic-Eigenschaft. Richtig ist schlicht `[find]`.
+/interface wifi capsman set enabled=no
+/interface wifi remove [find where !(name=wifi1 || name=wifi2)]
+/interface wifi configuration remove [find]
+/interface wifi steering remove [find]
+/interface wifi security remove [find]
+/interface wifi datapath remove [find]
+/interface wifi channel remove [find]
+# Werks-Inline-Overrides auf den Master-Radios loeschen, sonst gewinnen sie gegen den Manager.
+/interface wifi set [find name=wifi1] !configuration.ssid !security.authentication-types !security.passphrase !security.ft-over-ds
+/interface wifi set [find name=wifi2] !configuration.ssid !security.authentication-types !security.passphrase !security.ft-over-ds
+
+# --- Bridge-Ports idempotent. ether2 = Lifeline (faellt spaeter weg).
+:if ([:len [/interface bridge port find where interface=ether1]]=0) do={ /interface bridge port add bridge=bridge interface=ether1 }
+:if ([:len [/interface bridge port find where interface=ether2]]=0) do={ /interface bridge port add bridge=bridge interface=ether2 }
+:if ([:len [/interface bridge port find where interface=wifi1]]=0) do={ /interface bridge port add bridge=bridge comment=defconf interface=wifi1 }
+:if ([:len [/interface bridge port find where interface=wifi2]]=0) do={ /interface bridge port add bridge=bridge comment=defconf interface=wifi2 }
+
+# --- Bridge-VLAN-Tabelle. Muster = ap2 live, plus ether2 in VLAN1 (Lifeline).
+# IOT (30) nur auf wifi2: die Zone hat bands=["2g"] (ConfigContext ap-common).
+# MGMT (99) liegt auf der Bridge, NICHT auf den Radios — sonst waere Management
+# over-the-air.
+/interface bridge vlan remove [find]
+/interface bridge vlan add bridge=bridge untagged=bridge,ether1,ether2 vlan-ids=1
+/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=10
+/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=20
+/interface bridge vlan add bridge=bridge tagged=ether1,wifi2 vlan-ids=30
+/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=40
+/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=50
+/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=90
+/interface bridge vlan add bridge=bridge tagged=ether1,bridge vlan-ids=99
+
+# --- Mgmt-Layer. IP nach TP32-Konvention (.10-.29 = physische Hosts): ap1 .11, ap2 .12, ap3 .13.
+:if ([:len [/interface vlan find where name=vlan99]]=0) do={ /interface vlan add name=vlan99 interface=bridge vlan-id=99 }
+/ip address remove [find where interface=vlan99]
+/ip address add address=10.40.99.13/24 interface=vlan99
+:if ([:len [/interface list member find where list=LAN and interface=vlan99]]=0) do={ /interface list member add list=LAN interface=vlan99 }
+:if ([:len [/interface list member find where list=LAN and interface=bridge]]=0) do={ /interface list member add list=LAN comment=defconf interface=bridge }
+/ip route remove [find where comment="mgmt default via core"]
+/ip route add dst-address=0.0.0.0/0 gateway=10.40.99.1 comment="mgmt default via core"
+/ip dns set allow-remote-requests=yes servers=10.40.99.1
+# Zeitquelle ist der Core (seit 2026-08-18). Ohne das war ap2 einmal 17 h versetzt und
+# seine Logs fuer jede geraeteuebergreifende Korrelation wertlos.
+/system ntp client set enabled=yes
+/system ntp client servers remove [find]
+/system ntp client servers add address=10.40.99.1
+
+# --- CAP-Modus. 🔴🔴 `slaves-datapath` IST NICHT OPTIONAL.
+# Der Manager schickt `datapath.bridge=bridge` zwar mit, der CAP uebernimmt davon
+# aber NUR die vlan-id — die Bridge-Mitgliedschaft bleibt lokal. Fehlt das Objekt,
+# assoziieren Clients auf iot/media/untrusted/guest sauber und bekommen NIE DHCP,
+# waehrend dirtyvader-main (Master-Radios = statische Bridge-Ports) weiterlaeuft.
+# Genau so lagen IOT+MEDIA am 2026-08-18 rund 14 Stunden. KEINE vlan-id hier —
+# die liefert der Manager pro VAP.
+/interface wifi datapath add name=dp-caps-slaves bridge=bridge comment="TP28-fix: bridge fuer CAPsMAN-Slave-VAPs"
+/interface wifi cap set enabled=yes discovery-interfaces=vlan99 slaves-datapath=dp-caps-slaves
+
+# --- ZULETZT: Bridge VLAN-scharf schalten. Vorher waere der Rest der Datei nicht angekommen.
+/interface bridge set [find name=bridge] vlan-filtering=yes
+
+:log info "tp28-ap3: bring-up fertig, ip=10.40.99.13 cap=yes slaves-datapath=dp-caps-slaves"
diff --git a/routeros/tp28-ap3-lifeline-abbau.rsc b/routeros/tp28-ap3-lifeline-abbau.rsc
new file mode 100644
index 0000000..0c236e5
--- /dev/null
+++ b/routeros/tp28-ap3-lifeline-abbau.rsc
@@ -0,0 +1,10 @@
+# ap3 — Lifeline-Abbau. ERST AUSFUEHREN, wenn ap3 ueber 10.40.99.13 (via ether1 am
+# core ether21) erreichbar ist und `remote-cap print` am Core `ap3 state=Ok` zeigt.
+# Vorher gefahren = Aussperrung.
+#
+# Zielzustand danach = wie ap2: ether2 ist KEIN Bridge-Port. Damit bleibt VLAN1 leer
+# (TP19 ist geschlossen) und ap3s Bridge-Port-Liste ist genau ether1+wifi1+wifi2
+# -> `ap.extra_ports = []` im Nautobot-ConfigContext.
+/interface bridge vlan set [find where vlan-ids=1] untagged=bridge,ether1
+/interface bridge port remove [find where interface=ether2]
+:log info ("tp28-ap3: lifeline ab, bridge-ports=" . [:tostr [:len [/interface bridge port find]]])