diff --git a/services/homelab-architecture.md b/services/homelab-architecture.md
index fe1e5a6..ea08932 100644
--- a/services/homelab-architecture.md
+++ b/services/homelab-architecture.md
@@ -378,3 +378,135 @@ must target *exactly these silent-failure classes*.
`/mnt/pve/unas` (catches a regression of the tier-1 rule).
4. Container unhealthy/restart-looping > 5 m (the gluetun pattern).
5. local-zfs `rpool` or NFS pool > 85 %; 6. TLS cert < 14 d.
+
+## Topology — at a glance
+
+### Physical / CT layout
+
+```mermaid
+flowchart TB
+ subgraph Host["Proxmox host · 192.168.1.20 · Intel Core Ultra 7 155H · 64 GiB"]
+ direction TB
+ haos["VM 100 · haos
(.60)
Home Assistant"]
+ shepard["CT 101 · shepard
(.49)
Shepard product stack"]
+ dns["CT 102 · dns
(.2)
AdGuard Home"]
+ backrest["CT 103 · backrest
(.3)
Backrest / restic"]
+ docker104["CT 104 · docker
(.40) · 16c/48G/200G
~65 containers · Intel Arc passthrough"]
+ nc["CT 105 · nextcloud
(.41)
Nextcloud AIO (CIFS to UNAS)"]
+ zoraxy["CT 108 · zoraxy
(.4)
reverse proxy + ACME"]
+ obs(["CT 109 · observe
(.6) · planned
Prom / Graf / Arcane / Homarr / Tinyauth"])
+ id["CT 110 · id
(.5)
Pocket-ID (moved here 2026-05-20)"]
+ dev["CT 111 · dev
(.42) · 12c/32G/60G
Coder + Gitea + workspaces · Intel Arc"]
+ db(["CT 113 · db
(.?) · planned
shared Postgres + pgbouncer"])
+ end
+ UNAS[("UNAS
192.168.1.31
NFSv3 + CIFS")]
+ UDM[["UDM-SE · 192.168.1.1
UniFi gateway · DNS → AdGuard"]]
+ Inet([Internet · ACME challenges · jottacloud · LiteLLM upstreams])
+
+ UDM <--> Host
+ UDM <--> Inet
+ UNAS <--> shepard
+ UNAS <--> backrest
+ UNAS <--> docker104
+ UNAS <--> nc
+ UNAS <--> dev
+
+ classDef planned stroke-dasharray:5 5,fill:#222,stroke:#aaa,color:#aaa
+ class obs,db planned
+```
+
+### Auth plane — Pocket-ID is the universal IdP
+
+Every web service that supports OIDC federates against Pocket-ID. Coder/Gitea/Vaultwarden access through Zoraxy; Zoraxy + Tinyauth fronts the non-OIDC-native ones.
+
+```mermaid
+flowchart LR
+ user(["fkrebs · browser / VS Code / Claude"]) --> zx[Zoraxy
CT 108]
+ zx --> coder[Coder · CT 111]
+ zx --> gitea[Gitea · CT 111]
+ zx --> nc2[Nextcloud · CT 105]
+ zx --> immich[Immich · CT 104]
+ zx --> lobe[LobeChat · CT 104]
+ zx --> n8n[n8n · CT 104]
+ zx --> mcp[MCP gateway · CT 104]
+ zx --> vw[Vaultwarden · CT 104]
+ coder --> pid[(Pocket-ID
CT 110)]
+ gitea --> pid
+ nc2 --> pid
+ immich --> pid
+ lobe --> pid
+ n8n --> pid
+ mcp --> pid
+ vw -.->|via Tinyauth
when CT 109 lands| pid
+ classDef pending stroke-dasharray:4 4,color:#888
+ class vw pending
+```
+
+### MCP plane — gateway control, child workers
+
+The MCP gateway on CT 104 is the OIDC-gated front; ~23 child MCP servers run on the same docker network (`ai-internal`). When CT 109 "observe" is built, the gateway moves to CT 109 and reaches CT 104's docker daemon via a socket-proxy; the children stay put.
+
+```mermaid
+flowchart LR
+ client[["Claude Code / Cursor / LobeChat"]]
+ gw["MCP gateway
(CT 104 → CT 109 when 109 lands)
mcp.nuclide.systems"]
+ client -- Bearer / OIDC --> gw
+ subgraph "CT 104 · ai-internal docker net"
+ direction TB
+ coderm[coder-mcp]
+ immm[mcp-immich]
+ n8nm[mcp-n8n]
+ fetch[mcp-fetch]
+ time[mcp-time]
+ cw[mcp-crawl4ai]
+ seq[mcp-sequential-thinking]
+ ham[home-assistant-mcp]
+ kr[kroki-mcp]
+ others[...18 more]
+ end
+ gw --> coderm
+ gw --> immm
+ gw --> n8nm
+ gw --> fetch
+ gw --> time
+ gw --> cw
+ gw --> seq
+ gw --> ham
+ gw --> kr
+ gw --> others
+ coderm -.spawns/controls.-> CoderWS[(Coder workspaces
CT 111)]
+ ham -.bridges to.-> HAOS[(Home Assistant
VM 100)]
+```
+
+### Data plane — what lives where
+
+```mermaid
+flowchart TB
+ subgraph Tier1["Tier-1 / latency-sensitive · local NVMe"]
+ pid_d[Pocket-ID sqlite · CT 110]
+ immich_pg[Immich Postgres · CT 104]
+ shared_pg[shared-postgres · CT 104]
+ coder_pg[coder-db · CT 111]
+ gitea_pg[gitea-db · CT 111]
+ garage["Garage S3 · CT 104
(moved off NFS 2026-05-19)"]
+ n8n_local["n8n data · CT 104
(reverted from NFS 2026-05-19)"]
+ end
+ subgraph Bulk["Bulk · UNAS NFSv3"]
+ media["Immich media, Paperless docs,
arr-stack media, audiobooks"]
+ coder_homes[Coder workspace homes · /mnt/pve/unas/services/coder]
+ gitea_data[Gitea repos · /mnt/pve/unas/services/gitea]
+ vw_data["Vaultwarden data
(tier-1 leak — plan to move local)"]
+ end
+ subgraph BulkCIFS["Bulk · UNAS CIFS (Nextcloud only)"]
+ nc_data[Nextcloud user files]
+ end
+ subgraph Backup["Off-host backup"]
+ jottacloud[(jottacloud
via Backrest)]
+ end
+ Tier1 -. WAL-G .-> garage
+ Bulk -. only media/data-dir .-> jottacloud
+ classDef gap fill:#5a2a2a,stroke:#c44,color:#fcc
+ class vw_data,jottacloud gap
+```
+
+The red blocks above are gaps: Vaultwarden is on NFS when it shouldn't be; off-host backup currently covers only one UNAS path, not service data. See `stacks/storage.md` for the verified state and the cleanup TODO list.