diff --git a/docs/superpowers/plans/2026-09-26-docker-deployment.md b/docs/superpowers/plans/2026-09-26-docker-deployment.md
new file mode 100644
index 0000000..a43bb6d
--- /dev/null
+++ b/docs/superpowers/plans/2026-09-26-docker-deployment.md
@@ -0,0 +1,596 @@
+# Docker Deployment (Coolify) Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Run the Telegram host as one non-root container, built from this repo's `Dockerfile` and started by `docker-compose.yml` on Coolify or any Docker host. The container ships the agent's tools, Node 24, the `claude` CLI and optionally Playwright MCP + Chromium, and keeps workspaces, sessions and the Claude login in three named volumes.
+
+**Architecture:** A two-stage `Dockerfile` (`sdk:10.0-noble` publishes the host framework-dependent; `runtime:10.0-noble` adds apt tools, NodeSource Node, `@anthropic-ai/claude-code`, optional `@playwright/mcp` + Chromium). The image's default user stays root so that PID 1 (compose's `init: true`, i.e. `docker-init`) is root-owned; the entrypoint `setpriv` drops to `app` (uid 1654) and execs `dotnet Volcanic.AgenticCore.Telegram.dll`. `docker-compose.yml` passes configuration through environment variables only. No C# behaviour changes; the one new unit test pins the Dockerfile's `PLAYWRIGHT_MCP_VERSION` to `PlaywrightPreset.DefaultPackage`.
+
+**Tech Stack:** Docker (BuildKit, Compose v2), `mcr.microsoft.com/dotnet/sdk:10.0-noble` and `runtime:10.0-noble`, NodeSource Node.js 24, `@anthropic-ai/claude-code@2.1`, `@playwright/mcp@0.0.82`, `util-linux` `setpriv`, xunit 2. Spec: `docs/superpowers/specs/2026-09-26-docker-deployment-design.md`.
+
+## Global Constraints
+
+- Work only in the worktree `D:\Projects\Names\wt-docker-deployment` (branch `feat/docker-deployment`). Every Bash command starts with `cd /d/Projects/Names/wt-docker-deployment && ...`; Read/Edit/Write use absolute paths under `D:\Projects\Names\wt-docker-deployment`. Never switch branches, never `git worktree`/`checkout`/`reset --hard`/`push`. Never touch `D:\Projects\Names\VolcanicAgenticCore` or another `wt-*` directory.
+- Wave E runs alone, but keep edits to shared host files minimal. This plan changes **no** file under `src/`, `extensions/` or `hosts/`; if a task finds it has to, it edits the spec in the same commit and says so in the commit body.
+- `Directory.Build.props` sets `TreatWarningsAsErrors=true` for non-test projects; the image build runs `dotnet publish -c Release`, so a Release-only warning fails the image build too.
+- Tests: xunit 2. No test calls a paid API or any network endpoint, and no test runs Docker. Known baseline on this Windows machine: 3 tests in `Volcanic.AgenticCore.Tests` fail on `main` before any change (`SystemPromptBuilderTests.Loads_AGENTS_md_in_preference_to_CLAUDE_md_then_persona_and_memory`, `ExtensionLoaderTests.Discover_loads_a_real_assembly_from_the_user_directory`, `BuiltInToolTests.Ls_find_and_grep`). "Green" = no other failures.
+- Docker on this machine: Docker Desktop 29, Linux containers. Run docker commands from Git Bash with `MSYS_NO_PATHCONV=1` in front (otherwise Git Bash rewrites container paths such as `/data/...` into Windows paths), or from PowerShell. Foreground `sleep` is blocked in the Bash tool; wait with PowerShell `Start-Sleep -Seconds 20` or the Monitor tool.
+- Docker hygiene: never use real secrets (only `TELEGRAM_BOT_TOKEN=123:fake`, `TELEGRAM_ALLOWED_USER_IDS=1`); never push images; never `docker system prune`, `docker volume prune` or remove volumes/images you didn't create. Image tags this plan creates: `volcanic-agentic-core:test` (no Playwright), `volcanic-agentic-core:test-pw` (Playwright). Compose project name for test runs: `volcanic-test` (its volumes are `volcanic-test_workspaces`, `volcanic-test_agent`, `volcanic-test_claude`; its image `volcanic-test-bot`). Container names: `volcanic-probe`, `volcanic-probe-pw`. T6 removes all of them.
+- Never commit `.env` (a dummy one is created for `docker compose config`/`up`), `bin/`, `obj/`, `launchSettings.json`, `hosts/Volcanic.AgenticCore.Telegram/Properties/`.
+- Exact values from the spec and the code (copy verbatim):
+ - base images `mcr.microsoft.com/dotnet/sdk:10.0-noble`, `mcr.microsoft.com/dotnet/runtime:10.0-noble`;
+ - build args: `NODE_MAJOR=24`, `CLAUDE_CODE_VERSION=2.1`, `INSTALL_PLAYWRIGHT=false` (Dockerfile) / `true` (compose default), `PLAYWRIGHT_MCP_VERSION=0.0.82` (must equal `PlaywrightPreset.PackageVersion`);
+ - apt tools: `bash git ripgrep python3 python-is-python3 python3-venv curl ca-certificates tzdata jq less procps openssh-client` (+ `gnupg` for the NodeSource key);
+ - user `app`, uid/gid **1654**, `HOME=/home/app`; volumes `/data/workspaces`, `/data/agent`, `/data/claude`; `PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright`;
+ - image env: `Telegram__WorkspaceRoot=/data/workspaces`, `VOLCANIC_AGENT_DIR=/data/agent`, `CLAUDE_CONFIG_DIR=/data/claude`, `HOME=/home/app`, `DOTNET_CLI_TELEMETRY_OPTOUT=1`, `DOTNET_NOLOGO=1`, `TZ=UTC`, `DOTNET_EnableDiagnostics=0`;
+ - environment variables the host reads (verified in code): `TELEGRAM_BOT_TOKEN`, `TELEGRAM_ALLOWED_USER_IDS` (`TelegramOptions.ApplyEnvironment`), `OPENAI_API_KEY`, `STT_MODEL`, `DEFAULT_TIMEZONE` (same), `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` (`GoogleCalendarSetup.Options`), `PROCESS_ENV_ALLOW` (`TelegramProcessEnvironment.AllowVariable`), `PLAYWRIGHT_ENABLED`, `PLAYWRIGHT_HEADLESS`, `PLAYWRIGHT_BROWSER`, `PLAYWRIGHT_EXTRA_ARGS`, `PLAYWRIGHT_COMMAND` (`McpOptions.ApplyEnvironment`), `VOLCANIC_AGENT_DIR` (`VolcanicPaths.DefaultAgentDir`, `GoogleCalendarOptions`), plus every `Section__Key` form through `Host.CreateApplicationBuilder`'s unprefixed environment provider (so `Telegram__WorkspaceRoot` and `Telegram__Model` bind to `TelegramOptions` exactly as the spec's compose assumes; `TELEGRAM_MODEL`, `LOG_LEVEL`, `INSTALL_PLAYWRIGHT`, `CLAUDE_CODE_VERSION` are compose-level variables only);
+ - the `@playwright/mcp@0.0.82` package's binary is `playwright-mcp` (`npm view @playwright/mcp@0.0.82 bin` → `{ 'playwright-mcp': 'cli.js' }`); it depends on `playwright` `1.64.0-alpha-1789764292000`;
+ - startup log lines to expect (from `Program.cs`): `Process environment: 15 deny rules, allow: none`, `MCP: playwright disabled (set PLAYWRIGHT_ENABLED=true)` / `MCP: playwright enabled (headless, chromium)`, `Speech to text: off (no OPENAI_API_KEY)`, `Using Claude Code at /usr/bin/claude, model claude-code/claude-opus-5-5`, `Scheduler: /data/agent/scheduler/jobs.json`, `Default time zone: ...`, `Allowed users: 1. Workspaces under /data/workspaces`, and **no** `The .NET diagnostic IPC socket is on ...` warning.
+- **Deviation from the spec found while planning (T1 edits the spec):** the spec runs the image as `USER $APP_UID` with compose's `init: true`. Checked on Docker 29: `docker run --rm --init -u 1654 -e SECRET_PROBE=x mcr.microsoft.com/dotnet/runtime:10.0-noble sh -c 'tr "\0" "\n" \S+)\s*$", RegexOptions.Multiline);
+
+ var match = Assert.Single(matches);
+ Assert.Equal(PlaywrightPreset.PackageVersion, match.Groups["version"].Value);
+ }
+
+ /// A file in the repository root, found by walking up from the test output to VolcanicAgenticCore.slnx.
+ private static string RepoFile(string name)
+ {
+ for (var dir = new DirectoryInfo(AppContext.BaseDirectory); dir is not null; dir = dir.Parent)
+ {
+ if (File.Exists(Path.Combine(dir.FullName, "VolcanicAgenticCore.slnx"))) return Path.Combine(dir.FullName, name);
+ }
+ throw new InvalidOperationException("VolcanicAgenticCore.slnx not found above " + AppContext.BaseDirectory);
+ }
+```
+
+- [ ] **Step 2: Run it to verify it fails**
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && dotnet test tests/Volcanic.AgenticCore.Extensions.Tests --filter "FullyQualifiedName~PlaywrightPresetTests.Dockerfile_version_matches_DefaultPackage"`
+Expected: FAIL with `FileNotFoundException` for `...\Dockerfile`.
+
+- [ ] **Step 3: Create `.gitattributes`, `.dockerignore` and `Dockerfile`**
+
+`.gitattributes` (the machine has `core.autocrlf=true`; keep these LF everywhere):
+
+```
+Dockerfile text eol=lf
+.dockerignore text eol=lf
+docker-compose.yml text eol=lf
+.env.example text eol=lf
+```
+
+`.dockerignore` (Windows `obj/project.assets.json` in the context would break the Linux restore; `.env` must never reach the build):
+
+```
+**/bin
+**/obj
+**/TestResults
+**/*.user
+**/Properties/launchSettings.json
+.git
+.vs
+.idea
+.claude
+.volcanic
+.env
+.env.*
+tests
+samples
+docs
+*.md
+docker-compose.yml
+```
+
+`Dockerfile`:
+
+```dockerfile
+# The Telegram host as one container. User guide: docs/docker.md.
+# Spec: docs/superpowers/specs/2026-09-26-docker-deployment-design.md
+
+FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build
+WORKDIR /src
+
+# Restore layer: only the project files the host needs, cached until one of them changes.
+COPY Directory.Build.props VolcanicAgenticCore.slnx ./
+COPY src/Volcanic.AgenticCore.Abstractions/Volcanic.AgenticCore.Abstractions.csproj src/Volcanic.AgenticCore.Abstractions/
+COPY src/Volcanic.AgenticCore/Volcanic.AgenticCore.csproj src/Volcanic.AgenticCore/
+COPY extensions/Volcanic.AgenticCore.Extensions.ClaudeCode/Volcanic.AgenticCore.Extensions.ClaudeCode.csproj extensions/Volcanic.AgenticCore.Extensions.ClaudeCode/
+COPY extensions/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.csproj extensions/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge/
+COPY extensions/Volcanic.AgenticCore.Extensions.GoogleCalendar/Volcanic.AgenticCore.Extensions.GoogleCalendar.csproj extensions/Volcanic.AgenticCore.Extensions.GoogleCalendar/
+COPY extensions/Volcanic.AgenticCore.Extensions.Mcp/Volcanic.AgenticCore.Extensions.Mcp.csproj extensions/Volcanic.AgenticCore.Extensions.Mcp/
+COPY extensions/Volcanic.AgenticCore.Extensions.Scheduler/Volcanic.AgenticCore.Extensions.Scheduler.csproj extensions/Volcanic.AgenticCore.Extensions.Scheduler/
+COPY hosts/Volcanic.AgenticCore.Telegram/Volcanic.AgenticCore.Telegram.csproj hosts/Volcanic.AgenticCore.Telegram/
+RUN dotnet restore hosts/Volcanic.AgenticCore.Telegram/Volcanic.AgenticCore.Telegram.csproj
+
+COPY src/ src/
+COPY extensions/ extensions/
+COPY hosts/ hosts/
+# Framework-dependent: claude starts the bridge as `dotnet .dll`, so the loose dll and its runtimeconfig must exist.
+RUN dotnet publish hosts/Volcanic.AgenticCore.Telegram/Volcanic.AgenticCore.Telegram.csproj -c Release --no-restore -o /out \
+ && test -f /out/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.dll \
+ && test -f /out/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.runtimeconfig.json
+
+FROM mcr.microsoft.com/dotnet/runtime:10.0-noble
+ARG DEBIAN_FRONTEND=noninteractive
+ARG NODE_MAJOR=24
+ARG CLAUDE_CODE_VERSION=2.1
+ARG INSTALL_PLAYWRIGHT=false
+# Must equal the version in PlaywrightPreset.DefaultPackage (the single source of truth);
+# PlaywrightPresetTests.Dockerfile_version_matches_DefaultPackage fails when they differ.
+ARG PLAYWRIGHT_MCP_VERSION=0.0.82
+
+# What the agent's bash tool can run, then Node from NodeSource (Ubuntu noble's nodejs is too old).
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ bash git ripgrep python3 python-is-python3 python3-venv curl ca-certificates tzdata jq less procps openssh-client gnupg \
+ && mkdir -p /etc/apt/keyrings \
+ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends nodejs \
+ && rm -rf /var/lib/apt/lists/*
+
+# The global prefix stays root-owned, so claude's auto-updater can't replace it at runtime: an upgrade changes what
+# system/init validation sees and invalidates the prompt cache, so it only arrives with a rebuild.
+RUN npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
+ && claude --version \
+ && npm cache clean --force
+
+# Set even without Playwright, so an MCP server started with the host environment finds browsers here.
+ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright
+# INSTALL_PLAYWRIGHT block (T2)
+
+# The entrypoint hard-codes uid/gid 1654. Named volumes copy this ownership on first mount.
+RUN test "$(id -u app)" = 1654 && test "$(id -g app)" = 1654 \
+ && mkdir -p /data/workspaces /data/agent /data/claude /home/app \
+ && chown -R app:app /data /home/app
+
+COPY --from=build /out /app
+WORKDIR /app
+
+ENV Telegram__WorkspaceRoot=/data/workspaces \
+ VOLCANIC_AGENT_DIR=/data/agent \
+ CLAUDE_CONFIG_DIR=/data/claude \
+ HOME=/home/app \
+ DOTNET_CLI_TELEMETRY_OPTOUT=1 \
+ DOTNET_NOLOGO=1 \
+ TZ=UTC \
+ DOTNET_EnableDiagnostics=0
+
+# No USER: PID 1 (docker-init with compose's init: true) must stay root, or the agent could read every secret from
+# /proc/1/environ. setpriv drops to app and execs dotnet, so Environment.ProcessPath is the muxer the bridge needs.
+ENTRYPOINT ["setpriv", "--reuid=1654", "--regid=1654", "--init-groups", "--inh-caps=-all", "--", "dotnet", "Volcanic.AgenticCore.Telegram.dll"]
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && dotnet test tests/Volcanic.AgenticCore.Extensions.Tests --filter "FullyQualifiedName~PlaywrightPresetTests"`
+Expected: all `PlaywrightPresetTests` PASS. Sanity-check the test bites: temporarily change the ARG to `0.0.81`, re-run, expect FAIL (`Expected: 0.0.82 Actual: 0.0.81`), change it back.
+
+- [ ] **Step 5: Build the image without Playwright**
+
+Run (allow ~10 min; network needed for base images, apt, NodeSource and npm):
+`cd /d/Projects/Names/wt-docker-deployment && MSYS_NO_PATHCONV=1 docker build --build-arg INSTALL_PLAYWRIGHT=false -t volcanic-agentic-core:test .`
+Expected: success; the log shows the bridge `test -f` step and `claude --version` (2.1.x) passing. If `publish` drops the bridge runtimeconfig, fix the ClaudeCode csproj, not the Dockerfile (a local `dotnet publish` on Windows does produce `Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.runtimeconfig.json`). If `dotnet restore` misses a project, check every `ProjectReference` again (host → core, ClaudeCode, GoogleCalendar, Mcp, Scheduler; all → Abstractions; ClaudeCode → Bridge).
+
+- [ ] **Step 6: Image sanity checks (no secrets)**
+
+Run the orchestrator's check as given (the entrypoint shell runs as root now; that is expected):
+`MSYS_NO_PATHCONV=1 docker run --rm --entrypoint sh volcanic-agentic-core:test -c 'id; claude --version; test -f /app/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.runtimeconfig.json && echo bridge-ok; echo $DOTNET_EnableDiagnostics'`
+Expected: `uid=0(root)`, a `2.1.x` version, `bridge-ok`, `0`.
+
+Run the same as the bot's user, plus the agent tools:
+`MSYS_NO_PATHCONV=1 docker run --rm --entrypoint setpriv volcanic-agentic-core:test --reuid=1654 --regid=1654 --init-groups --inh-caps=-all -- sh -c 'id; echo HOME=$HOME; claude --version; git --version; rg --version | head -1; python --version; python3 -m venv /tmp/v && echo venv-ok; node --version; npx --version; jq --version; ssh -V; ps --version; less --version | head -1; date; ls -ld /home/app /data /data/*; touch /data/workspaces/x /data/agent/x /data/claude/x && echo data-writable'`
+Expected: `uid=1654(app) gid=1654(app)`, `HOME=/home/app`, node `v24.*`, `venv-ok`, `/home/app` and `/data/*` owned by `app`, `data-writable`.
+
+PID 1 check (the deviation above):
+`MSYS_NO_PATHCONV=1 docker run --rm --init -e SECRET_PROBE=x --entrypoint setpriv volcanic-agentic-core:test --reuid=1654 --regid=1654 --init-groups --inh-caps=-all -- sh -c 'ps -o pid,user,comm -p 1; cat /proc/1/environ >/dev/null && echo READABLE || echo denied'`
+Expected: PID 1 `root docker-init`, then `Permission denied` / `denied`.
+
+Record each command and its result for the report.
+
+- [ ] **Step 7: Edit the spec for the deviation**
+
+In `docs/superpowers/specs/2026-09-26-docker-deployment-design.md`:
+- Decisions table, `Entry` row → ``ENTRYPOINT ["setpriv", "--reuid=1654", "--regid=1654", "--init-groups", "--inh-caps=-all", "--", "dotnet", "Volcanic.AgenticCore.Telegram.dll"]``: `setpriv` (util-linux, already in the base image) drops to `app` and execs `dotnet`, so `Environment.ProcessPath` is still the muxer `BridgeLocator.FindDotnet` wants. No entrypoint script |
+- `User` row → The image's default user stays root so that PID 1 (compose's `init: true`, i.e. `docker-init`) is root-owned; the bot and everything it spawns run as the base image's non-root `app` user (1654, `$APP_UID`), `HOME=/home/app`. With `USER app`, `docker-init` runs as `app` and its `/proc/1/environ` (the whole container environment, secrets included) is readable by the agent's `bash` (checked on Docker 29) |
+- Dockerfile §Build stage step 1: replace "(Abstractions, core, ClaudeCode, ClaudeCode.Bridge, Telegram, plus the Scheduler and GoogleCalendar extensions once the host references them)" with "(Abstractions, core, ClaudeCode, ClaudeCode.Bridge, GoogleCalendar, Mcp, Scheduler, Telegram)".
+- Runtime step 5 → "`COPY --from=build /out /app`, `WORKDIR /app`. No `USER` (see the User row); an earlier step checks that `app` is uid/gid 1654, which the entrypoint hard-codes."
+- Env table: add a row `HOME` | `/home/app` | The image user is root, so Docker would otherwise set `/root`.
+- docker-compose §secrets bullet: "the bot (PID 1 in the container) makes itself non-dumpable" → "the bot (the only child of the root-owned PID 1) makes itself non-dumpable, and PID 1's own `/proc/1/environ` is root-only".
+- First run §Fallback: `docker exec -it claude auth login --claudeai` → `docker exec -u app -it claude auth login --claudeai` (`docker exec` defaults to root, whose credentials the bot couldn't read).
+
+- [ ] **Step 8: Build, full tests, commit**
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && dotnet build && dotnet test` → green (only the 3 baseline failures).
+
+```bash
+cd /d/Projects/Names/wt-docker-deployment && git add Dockerfile .dockerignore .gitattributes tests/Volcanic.AgenticCore.Extensions.Tests/Mcp/PlaywrightPresetTests.cs docs/superpowers/specs/2026-09-26-docker-deployment-design.md && git commit -m "feat(docker): Dockerfile for the Telegram host" -m "Two-stage build, agent tools, Node 24, claude CLI, data dirs owned by app, DOTNET_EnableDiagnostics=0. Adds the MCP spec's deferred test that the Dockerfile's PLAYWRIGHT_MCP_VERSION equals PlaywrightPreset.DefaultPackage.
+
+Spec change: the image keeps root as its user and the entrypoint drops to app with setpriv. With USER app, compose's docker-init (PID 1) runs as app and its /proc/1/environ exposes every secret to the agent's bash."
+```
+
+---
+
+### Task 2: Playwright in the image (`INSTALL_PLAYWRIGHT=true`)
+
+**Files:**
+- Modify: `Dockerfile` (replace the `# INSTALL_PLAYWRIGHT block (T2)` line)
+
+**Interfaces:**
+- Consumes: T1's `Dockerfile` (`ARG INSTALL_PLAYWRIGHT`, `ARG PLAYWRIGHT_MCP_VERSION=0.0.82`, `ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright`).
+- Produces: `playwright-mcp` on PATH (`/usr/bin/playwright-mcp`), Chromium under `/opt/ms-playwright` readable by everyone, image `volcanic-agentic-core:test-pw` (kept until T6).
+
+- [ ] **Step 1: Replace the placeholder comment with the install block**
+
+```dockerfile
+# Playwright MCP at the pinned version, and Chromium installed by that package's own playwright dependency, so browser
+# build and library version match. Roughly +400 MB.
+RUN if [ "$INSTALL_PLAYWRIGHT" = "true" ]; then \
+ npm install -g "@playwright/mcp@${PLAYWRIGHT_MCP_VERSION}" \
+ && pw="$(npm root -g)/@playwright/mcp/node_modules/playwright/cli.js" \
+ && test -f "$pw" \
+ && node "$pw" install --with-deps chromium \
+ && chmod -R a+rX "$PLAYWRIGHT_BROWSERS_PATH" \
+ && playwright-mcp --version \
+ && npm cache clean --force \
+ && rm -rf /var/lib/apt/lists/*; \
+ fi
+```
+
+If `test -f "$pw"` fails, list `$(npm root -g)/@playwright/mcp/node_modules` in a throwaway build step and point `pw` at the `playwright` package that `@playwright/mcp` itself resolves (never a separately installed `playwright`). If `playwright-mcp --version` isn't supported by 0.0.82, replace it with `npm ls -g @playwright/mcp` and grep for `@playwright/mcp@${PLAYWRIGHT_MCP_VERSION}`, and note that in the report.
+
+- [ ] **Step 2: Test stays green**
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && dotnet test tests/Volcanic.AgenticCore.Extensions.Tests --filter "FullyQualifiedName~PlaywrightPresetTests"` → PASS (the ARG line is untouched and still unique).
+
+- [ ] **Step 3: Build both variants**
+
+Run: `MSYS_NO_PATHCONV=1 docker build --build-arg INSTALL_PLAYWRIGHT=false -t volcanic-agentic-core:test .` (fast; confirms the `if` is a no-op).
+Run (background or with a long timeout; up to ~20 min): `MSYS_NO_PATHCONV=1 docker build --build-arg INSTALL_PLAYWRIGHT=true -t volcanic-agentic-core:test-pw .`
+Expected: both succeed; the second prints `0.0.82` from `playwright-mcp --version`. Record `docker image ls volcanic-agentic-core` sizes.
+
+- [ ] **Step 4: Offline checks as the bot's user**
+
+`MSYS_NO_PATHCONV=1 docker run --rm --network none --entrypoint setpriv volcanic-agentic-core:test-pw --reuid=1654 --regid=1654 --init-groups --inh-caps=-all -- sh -c 'command -v playwright-mcp; playwright-mcp --version; ls /opt/ms-playwright'`
+Expected: `/usr/bin/playwright-mcp`, `0.0.82`, a `chromium-*` directory (and `chromium_headless_shell-*` if this Playwright version installs it).
+
+Headless Chromium launch as uid 1654 with `--no-sandbox`, offline:
+`MSYS_NO_PATHCONV=1 docker run --rm --network none --entrypoint setpriv volcanic-agentic-core:test-pw --reuid=1654 --regid=1654 --init-groups --inh-caps=-all -- node -e 'const { chromium } = require("/usr/lib/node_modules/@playwright/mcp/node_modules/playwright"); (async () => { const b = await chromium.launch({ args: ["--no-sandbox"] }); const p = await b.newPage(); await p.setContent("ok
"); console.log(await p.textContent("h1")); await b.close(); })().catch(e => { console.error(e); process.exit(1); });'`
+Expected: `ok`. (Adjust the `require` path to what Step 1 found.)
+
+- [ ] **Step 5: Host startup with Playwright enabled, offline**
+
+`MSYS_NO_PATHCONV=1 docker run -d --name volcanic-probe-pw --init --network none -e TELEGRAM_BOT_TOKEN=123:fake -e TELEGRAM_ALLOWED_USER_IDS=1 -e PLAYWRIGHT_ENABLED=true -e PLAYWRIGHT_COMMAND=playwright-mcp -e PLAYWRIGHT_EXTRA_ARGS=--no-sandbox volcanic-agentic-core:test-pw`, wait ~15 s, `docker logs volcanic-probe-pw`.
+Expected: `MCP: playwright enabled (headless, chromium)` and the other startup lines from Global Constraints; afterwards `Telegram unreachable (attempt n)` warnings (no network), no crash. Then `docker rm -f volcanic-probe-pw`.
+
+- [ ] **Step 6: Commit**
+
+```bash
+cd /d/Projects/Names/wt-docker-deployment && git add Dockerfile && git commit -m "feat(docker): optional Playwright MCP and Chromium in the image"
+```
+
+---
+
+### Task 3: docker-compose.yml, .env.example and a real host start
+
+**Files:**
+- Create: `docker-compose.yml`, `.env.example`
+- Modify: `.gitignore` (add `.env`)
+
+**Interfaces:**
+- Consumes: T1's image contract (entrypoint, `/data/*` volumes, image env).
+- Produces: compose service `bot`, volumes `workspaces`, `agent`, `claude`; the variable names that T4's `docs/docker.md` documents.
+
+- [ ] **Step 1: `.gitignore`**
+
+Append:
+
+```
+# Local compose configuration (secrets)
+.env
+```
+
+- [ ] **Step 2: `docker-compose.yml`** (the spec's file verbatim, plus one comment on `init`)
+
+```yaml
+services:
+ bot:
+ build:
+ context: .
+ args:
+ INSTALL_PLAYWRIGHT: ${INSTALL_PLAYWRIGHT:-true}
+ CLAUDE_CODE_VERSION: ${CLAUDE_CODE_VERSION:-2.1}
+ restart: unless-stopped
+ # dotnet does not reap orphans; killed `claude` trees and bash tool children would pile up as zombies.
+ # docker-init runs as root (the image user), so the agent can't read its /proc/1/environ.
+ init: true
+ # Chromium crashes with Docker's 64 MB /dev/shm.
+ shm_size: 1gb
+ stop_grace_period: 30s
+ environment:
+ - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
+ - TELEGRAM_ALLOWED_USER_IDS=${TELEGRAM_ALLOWED_USER_IDS}
+ - Telegram__Model=${TELEGRAM_MODEL:-claude-code/claude-opus-5-5}
+ - TZ=${TZ:-UTC}
+ - Logging__LogLevel__Default=${LOG_LEVEL:-Information}
+ # Optional features; empty means off / default.
+ - OPENAI_API_KEY=${OPENAI_API_KEY:-} # speech to text (attachments spec)
+ - STT_MODEL=${STT_MODEL:-}
+ - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-} # Google Calendar (calendar spec)
+ - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
+ - DEFAULT_TIMEZONE=${DEFAULT_TIMEZONE:-} # chats' default zone (scheduler spec §7); empty → TZ
+ - PROCESS_ENV_ALLOW=${PROCESS_ENV_ALLOW:-} # deny-listed variables the agent may see, e.g. GH_TOKEN
+ # Playwright MCP preset (MCP spec). Enabled when the image has it; the preinstalled binary, no npx download.
+ - PLAYWRIGHT_ENABLED=${PLAYWRIGHT_ENABLED:-${INSTALL_PLAYWRIGHT:-true}}
+ - PLAYWRIGHT_HEADLESS=${PLAYWRIGHT_HEADLESS:-true} # no display in the container
+ - PLAYWRIGHT_BROWSER=${PLAYWRIGHT_BROWSER:-}
+ - PLAYWRIGHT_EXTRA_ARGS=${PLAYWRIGHT_EXTRA_ARGS:---no-sandbox}
+ - PLAYWRIGHT_COMMAND=${PLAYWRIGHT_COMMAND:-playwright-mcp}
+ volumes:
+ - workspaces:/data/workspaces
+ - agent:/data/agent
+ - claude:/data/claude
+
+volumes:
+ workspaces:
+ agent:
+ claude:
+```
+
+No `ports`, `expose`, `user:`, domain or `SERVICE_FQDN_*`; no `ANTHROPIC_*`, `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_USE_*` entries.
+
+- [ ] **Step 3: `.env.example`** (comments on their own lines; every value empty or a default)
+
+```dotenv
+# Copy to .env for `docker compose up -d --build`. On Coolify, set these as the resource's environment variables.
+# Never add ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, CLAUDE_CODE_OAUTH_TOKEN,
+# CLAUDE_CODE_USE_BEDROCK or CLAUDE_CODE_USE_VERTEX: the Claude Code provider is subscription-only and refuses to run.
+
+# Required. Allowed Telegram user ids, comma separated; the first one is the owner (re-login prompts, "Online").
+TELEGRAM_BOT_TOKEN=
+TELEGRAM_ALLOWED_USER_IDS=
+
+# Model every chat uses, log level, container time zone.
+TELEGRAM_MODEL=claude-code/claude-opus-5-5
+LOG_LEVEL=Information
+TZ=UTC
+
+# Build: Playwright MCP + Chromium in the image (~+400 MB); Claude Code version (2.1 = newest 2.1.x at build time).
+INSTALL_PLAYWRIGHT=true
+CLAUDE_CODE_VERSION=2.1
+
+# Speech to text for voice notes (empty = off).
+OPENAI_API_KEY=
+STT_MODEL=
+
+# Google Calendar (both set = on; each chat connects its own account with /calendar_connect).
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+
+# Default time zone for chats that never ran /timezone (IANA name, e.g. Europe/Berlin; empty = TZ).
+DEFAULT_TIMEZONE=
+
+# Deny-listed variables the agent's processes may see anyway, comma separated (e.g. GH_TOKEN).
+PROCESS_ENV_ALLOW=
+
+# Playwright MCP preset; empty = compose defaults (enabled if built in, headless, chromium, --no-sandbox, playwright-mcp).
+# An override of PLAYWRIGHT_EXTRA_ARGS must keep --no-sandbox.
+PLAYWRIGHT_ENABLED=
+PLAYWRIGHT_HEADLESS=
+PLAYWRIGHT_BROWSER=
+PLAYWRIGHT_EXTRA_ARGS=
+PLAYWRIGHT_COMMAND=
+```
+
+- [ ] **Step 4: `docker compose config` with a dummy `.env`**
+
+Create `.env` (never committed; `git status` must not list it):
+
+```dotenv
+TELEGRAM_BOT_TOKEN=123:fake
+TELEGRAM_ALLOWED_USER_IDS=1
+INSTALL_PLAYWRIGHT=false
+```
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && docker compose -p volcanic-test config`
+Expected: valid output with `INSTALL_PLAYWRIGHT: "false"` build arg, `PLAYWRIGHT_ENABLED: "false"` (nested default follows the build arg), `PLAYWRIGHT_EXTRA_ARGS: --no-sandbox`, `PLAYWRIGHT_COMMAND: playwright-mcp`, `Telegram__Model: claude-code/claude-opus-5-5`, `init: true`, `shm_size`, three named volumes, no `ports`. Also run it once with `.env.example` copied to `.env` plus the two required values, and check `PLAYWRIGHT_ENABLED: "true"`.
+
+- [ ] **Step 5: Real host start through compose (fake token)**
+
+Keep the dummy `.env` (`INSTALL_PLAYWRIGHT=false`). Run `docker compose -p volcanic-test up -d --build`, wait ~20 s, then `docker compose -p volcanic-test logs bot` and `docker compose -p volcanic-test ps -a`.
+Expected in the log: the startup lines from Global Constraints (`Using Claude Code at /usr/bin/claude, model claude-code/claude-opus-5-5`, `Allowed users: 1. Workspaces under /data/workspaces`, `Process environment: 15 deny rules, allow: none`, `MCP: playwright disabled (set PLAYWRIGHT_ENABLED=true)`), no diagnostic-socket warning, then a `401`/unauthorized failure and exit code 1; `restart: unless-stopped` restarts it (visible restart loop). Nothing else may fail.
+Then: `docker compose -p volcanic-test down -v --rmi local` (removes only this project's container, its three `volcanic-test_*` volumes and the `volcanic-test-bot` image).
+
+- [ ] **Step 6: Running-bot probe offline (`/proc` and diagnostics checks)**
+
+`MSYS_NO_PATHCONV=1 docker run -d --name volcanic-probe --init --network none -e TELEGRAM_BOT_TOKEN=123:fake -e TELEGRAM_ALLOWED_USER_IDS=1 -e OPENAI_API_KEY=sk-fake-probe-key volcanic-agentic-core:test`, wait ~10 s (no network → the poller backs off and keeps running), then:
+
+`MSYS_NO_PATHCONV=1 docker exec -u app volcanic-probe sh -c 'id; ps -o pid,user,comm -A; pid=$(pgrep -f Volcanic.AgenticCore.Telegram.dll); echo bot=$pid; cat /proc/1/environ >/dev/null 2>&1 && echo pid1-READABLE || echo pid1-denied; cat /proc/$pid/environ >/dev/null 2>&1 && echo bot-READABLE || echo bot-denied; ls /tmp/dotnet-diagnostic-* 2>/dev/null || echo no-diagnostic-socket'`
+Expected: `uid=1654(app)`; PID 1 `docker-init` as `root`, the bot's `dotnet` as `app`; `pid1-denied`; `bot-denied`; `no-diagnostic-socket`. `docker logs volcanic-probe` shows the startup lines, `Speech to text: gpt-4o-transcribe`, and `Telegram unreachable` warnings. (The env filter itself, i.e. `TELEGRAM_BOT_TOKEN`/`OPENAI_API_KEY` missing from the agent's `bash`, needs a real chat; `docker exec` doesn't go through the spawn policy. Put it in `openIssues`.)
+Then `docker rm -f volcanic-probe`, and delete the dummy `.env`.
+
+- [ ] **Step 7: Commit**
+
+```bash
+cd /d/Projects/Names/wt-docker-deployment && git status --short && git add docker-compose.yml .env.example .gitignore && git commit -m "feat(docker): compose file and .env.example for Coolify"
+```
+
+---
+
+### Task 4: `docs/docker.md`
+
+**Files:**
+- Create: `docs/docker.md`
+
+**Interfaces:**
+- Consumes: T1–T3's file names, build args, variable names, volume names, the `setpriv`/root PID 1 design.
+- Produces: `docs/docker.md` with the anchors `#configuration`, `#first-run-and-claude-login`, `#troubleshooting` that T5 links to.
+
+- [ ] **Step 1: Write the doc**
+
+Sections and required content (plain prose, short; use the unslop skill's rules: no filler, no "robust/seamless"):
+
+1. `# Running the Telegram bot in Docker` intro: one container from the repo's `Dockerfile` + `docker-compose.yml`; Coolify or any Docker host; long polling, so no ports and no domain. Local run: `cp .env.example .env`, fill the two required values, `docker compose up -d --build`. `dotnet run` (see `telegram-bot.md`) stays the development path.
+2. `## What's in the image`: .NET 10 runtime + the published host; agent tools (the apt list); Node 24 (NodeSource); `claude` (`@anthropic-ai/claude-code`); optionally `@playwright/mcp` 0.0.82 + Chromium under `/opt/ms-playwright`. No .NET SDK. The bot runs as `app` (uid 1654); PID 1 (`docker-init`) stays root so the agent can't read the container's environment from `/proc/1/environ`. Don't set `user:` in compose (`setpriv` needs root to switch users).
+3. `## Build arguments` table: `INSTALL_PLAYWRIGHT` (Dockerfile `false`, compose `true`), `CLAUDE_CODE_VERSION` (`2.1`; exact version to pin), `NODE_MAJOR` (`24`), `PLAYWRIGHT_MCP_VERSION` (`0.0.82`; must equal `PlaywrightPreset.DefaultPackage`, a unit test checks it; compose doesn't override it).
+4. `## Configuration`: environment only. Table `Variable | Default | Purpose | Agent's processes see it` with rows: `TELEGRAM_BOT_TOKEN` (required; no, redacted), `TELEGRAM_ALLOWED_USER_IDS` (required, comma separated, first = owner; yes), `TELEGRAM_MODEL` → `Telegram__Model` (`claude-code/claude-opus-5-5`), `TZ` (`UTC`), `LOG_LEVEL` → `Logging__LogLevel__Default` (`Information`), `OPENAI_API_KEY` (empty = speech to text off; no, redacted), `STT_MODEL` (`gpt-4o-transcribe`), `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` (both = calendar on; secret: no, redacted), `DEFAULT_TIMEZONE` (empty = `TZ`), `PROCESS_ENV_ALLOW`, `PLAYWRIGHT_ENABLED` (follows `INSTALL_PLAYWRIGHT`), `PLAYWRIGHT_HEADLESS` (`true`), `PLAYWRIGHT_BROWSER` (`chromium`), `PLAYWRIGHT_EXTRA_ARGS` (`--no-sandbox`, keep it when overriding), `PLAYWRIGHT_COMMAND` (`playwright-mcp`). Then: what the agent can't see: the default deny rules (`*_API_KEY`, `*_APIKEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD` and their `Section__Key` forms, plus the named secrets), any variable whose value is a configured secret, and `[REDACTED]` in tool output; `PROCESS_ENV_ALLOW=GH_TOKEN` lets one through (an explicit name on the deny list, such as `TELEGRAM_BOT_TOKEN`, can't be allowed). Other settings: any `Section__Key` (`Telegram__ReconnectMaxDelay=00:02:00`, `Scheduler__*`, `GoogleCalendar__*`, `ProcessEnvironment__*`, `Telegram__Speech__*`, `Telegram__InputDebounce`, `VOLCANIC_CLAUDE_CODE_IDLE_TIMEOUT_MS`/`..._TOTAL_TIMEOUT_MS`) added in Coolify and to `environment:` in the compose file. Set in the image, normally left alone: `Telegram__WorkspaceRoot=/data/workspaces`, `VOLCANIC_AGENT_DIR=/data/agent`, `CLAUDE_CONFIG_DIR=/data/claude`, `DOTNET_EnableDiagnostics=0` (unset it, with `ProcessEnvironment__HardenHostProcess=false`, only to attach `dotnet-dump`/`dotnet-trace`).
+ A warning block: **Never set `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_USE_BEDROCK` or `CLAUDE_CODE_USE_VERTEX`.** The provider is subscription-only and refuses to run. In Coolify, check shared/team variables too.
+5. `## Volumes`: `workspaces` → `/data/workspaces` (one directory per chat, `AGENTS.md`/`CLAUDE.md`, `PERSONA.md`, `MEMORY.md`, `inbox/`, `browser/`, project-level `.volcanic/extensions/`), `agent` → `/data/agent` (sessions, `settings.json`, `extensions/*.dll`, chat settings, `scheduler/jobs.json`, `google-calendar/` tokens), `claude` → `/data/claude` (Claude login). Named volumes survive redeploys; Coolify prefixes them with the resource uuid and deletes them only with the resource's volumes. Bind mounts work if the host directories are owned by uid 1654 (`chown -R 1654:1654 `). `HOME` (`~/.gitconfig`, `~/.ssh`, npm cache) isn't persisted.
+6. `## Deploying on Coolify`: new resource from this repo, build pack *Docker Compose*, set `TELEGRAM_BOT_TOKEN` and `TELEGRAM_ALLOWED_USER_IDS` (owner first) plus any optional ones, no domain, deploy. The log shows `Using Claude Code at /usr/bin/claude, ...` and `Allowed users: ...`. No healthcheck: the bot serves no HTTP; lost connectivity backs off, a `401` exits 1 and invalid config exits 2, which `restart: unless-stopped` turns into a visible restart loop.
+7. `## First run and Claude login`: owner sends `/start`, then `/claude-code-login`; the link and code prompt arrive as one message; paste the code; credentials land in `/data/claude`; `/claude-code-doctor` should report logged in, `claude.ai`, bridge ok. Later expiries use the owner re-login prompt. Fallback: `docker exec -u app -it claude auth login --claudeai` (`-u app` matters: `docker exec` defaults to root and root-owned credentials are unreadable by the bot).
+8. `## Updating`: redeploy (rebuild) to get the newest `claude` 2.1.x; the CLI can't update itself in place. Set `CLAUDE_CODE_VERSION` to an exact version to pin. Bumping Playwright means changing `PlaywrightPreset.DefaultPackage` and the Dockerfile ARG in one commit.
+9. `## Extensions and project context`: `docker cp my.dll :/data/agent/extensions/` + restart (built against the same `Volcanic.AgenticCore.Abstractions` version), or `/data/workspaces//.volcanic/extensions/`. Context files live in the chat workspace.
+10. `## Troubleshooting`: `[executable_missing]` → image built without `claude`? rebuild, check `claude --version` in the build log; `[auth]` → `/claude-code-login` (or the `docker exec -u app` fallback); `[mcp_startup]` → bridge files missing from `/app` (`Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.dll` + `.runtimeconfig.json`; the build guard should have caught it); `Permission denied` under `/data` with bind mounts → `chown -R 1654:1654`; `409 Conflict` in the log → another instance or a webhook polls the same bot token; browser errors → `INSTALL_PLAYWRIGHT=true` build, `shm_size`, `--no-sandbox` kept.
+11. `## Security`: short; the container is the isolation boundary; the process environment hook, non-dumpable bot, root PID 1, redaction; link to `telegram-bot.md#security`.
+
+- [ ] **Step 2: Check names against the code and the compose file**
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && grep -o '[A-Z][A-Z_]*[A-Z]' docs/docker.md | sort -u` and confirm every variable name exists in `docker-compose.yml`, the `Dockerfile`, or the Global Constraints list; fix typos.
+
+- [ ] **Step 3: Commit**
+
+```bash
+cd /d/Projects/Names/wt-docker-deployment && git add docs/docker.md && git commit -m "docs(docker): Docker and Coolify deployment guide"
+```
+
+---
+
+### Task 5: Cross-links and the sibling specs
+
+**Files:**
+- Modify: `README.md`, `docs/telegram-bot.md`, `docs/mcp.md`, `docs/superpowers/specs/2026-09-25-telegram-host-design.md`, `docs/superpowers/specs/2026-09-26-mcp-client-playwright-design.md`
+
+**Interfaces:**
+- Consumes: `docs/docker.md` from T4.
+- Produces: nothing code-facing.
+
+- [ ] **Step 1: `README.md`**
+
+After the Quick start code block (right after the line `TELEGRAM_BOT_TOKEN=123:abc ... dotnet run --project hosts/Volcanic.AgenticCore.Telegram` and the closing fence), add:
+
+```markdown
+To run the bot in a container (Coolify or any Docker host): `cp .env.example .env`, fill in the two required values,
+`docker compose up -d --build`. See [docs/docker.md](docs/docker.md).
+```
+
+In `## Documentation`, after the Telegram bot line, add: `- [Docker and Coolify](docs/docker.md): the container image, configuration, volumes, first login.`
+
+- [ ] **Step 2: `docs/telegram-bot.md`**
+
+In `## Security`, first paragraph: replace "Run the bot as a dedicated user or in a container, and treat the workspace root as writable by anyone on the allow-list." with "Run it in the container image ([docker.md](docker.md)), which is the recommended setup, or at least as a dedicated user, and treat the workspace root as writable by anyone on the allow-list."
+Replace "Set `DOTNET_EnableDiagnostics=0` in the bot's environment too (a container image should set it): otherwise" with "Set `DOTNET_EnableDiagnostics=0` in the bot's environment too (the Docker image, [docker.md](docker.md), sets it): otherwise".
+
+- [ ] **Step 3: `docs/mcp.md`**
+
+Replace "The Docker image (`docker.md`, when the docker-deployment spec lands) preinstalls both" with "The Docker image ([docker.md](docker.md), built with `INSTALL_PLAYWRIGHT=true`) preinstalls both".
+
+- [ ] **Step 4: Sibling specs**
+
+- `2026-09-25-telegram-host-design.md` line 23: "Out of scope: webhooks, Docker/Coolify, voice and ..." → "Out of scope: webhooks, Docker/Coolify (see `2026-09-26-docker-deployment-design.md`), voice and ...".
+- `2026-09-26-mcp-client-playwright-design.md` line 4: `Status: implemented (the Dockerfile version test arrives with 2026-09-26-docker-deployment-design.md)` → `Status: implemented (the Dockerfile version test, PlaywrightPresetTests.Dockerfile_version_matches_DefaultPackage, landed with 2026-09-26-docker-deployment-design.md)`. In the Testing bullet (around line 395), replace "(deferred: the Dockerfile and that test come with the docker-deployment spec)" with "(`Dockerfile_version_matches_DefaultPackage`, added with the docker-deployment spec)".
+
+- [ ] **Step 5: Verify links and commit**
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && grep -n "docker.md" README.md docs/*.md` (each link target exists) and `grep -rn "a container image should set\|when the docker-deployment spec lands\|deferred: the Dockerfile" docs README.md` → no hits.
+
+```bash
+cd /d/Projects/Names/wt-docker-deployment && git add README.md docs/telegram-bot.md docs/mcp.md docs/superpowers/specs/2026-09-25-telegram-host-design.md docs/superpowers/specs/2026-09-26-mcp-client-playwright-design.md && git commit -m "docs(docker): link the container setup from the README, bot and MCP docs"
+```
+
+---
+
+### Task 6: Final verification, spec status and cleanup
+
+**Files:**
+- Modify: `docs/superpowers/specs/2026-09-26-docker-deployment-design.md` (Status line, Verification section)
+
+**Interfaces:**
+- Consumes: everything above.
+- Produces: the verification record for the report.
+
+- [ ] **Step 1: Full .NET build and tests**
+
+Run: `cd /d/Projects/Names/wt-docker-deployment && dotnet build && dotnet test` → green (only the 3 baseline failures), including `PlaywrightPresetTests.Dockerfile_version_matches_DefaultPackage`.
+
+- [ ] **Step 2: Rebuild both images from the final tree**
+
+`MSYS_NO_PATHCONV=1 docker build --build-arg INSTALL_PLAYWRIGHT=false -t volcanic-agentic-core:test .` and `MSYS_NO_PATHCONV=1 docker build --build-arg INSTALL_PLAYWRIGHT=true -t volcanic-agentic-core:test-pw .` (mostly cached). Re-run T1 Step 6's three checks against `:test` and T2 Step 4's `playwright-mcp --version` against `:test-pw`. Any failure → fix in the owning file, commit as `fix(docker): ...`.
+
+- [ ] **Step 3: Spec Status and Verification**
+
+In `2026-09-26-docker-deployment-design.md`: `Status: draft.` → `Status: implemented (plan: docs/superpowers/plans/2026-09-26-docker-deployment.md).` Replace the Verification intro "No unit tests (nothing in C# changes). Manual checklist in the implementation plan:" with "One unit test (`PlaywrightPresetTests.Dockerfile_version_matches_DefaultPackage`); no C# behaviour changes. Checked during implementation without a real bot: both image variants build; bridge guard, `claude --version`, the agent tools, uid 1654, `/data` ownership; root-owned PID 1 whose `/proc/1/environ` and the bot's are unreadable to `app`; no `dotnet-diagnostic-*` socket; `playwright-mcp --version` offline and a headless Chromium launch as `app`; `docker compose config`; host startup log and the `401` → exit 1 path. The rest of this list needs a real bot and stays a manual checklist:" and keep items 1–5.
+
+- [ ] **Step 4: Cleanup (only what this plan created)**
+
+```bash
+cd /d/Projects/Names/wt-docker-deployment && docker rm -f volcanic-probe volcanic-probe-pw 2>/dev/null; docker compose -p volcanic-test down -v --rmi local 2>/dev/null; docker rmi volcanic-agentic-core:test volcanic-agentic-core:test-pw; docker volume ls --filter name=volcanic-test; docker image ls volcanic-agentic-core; rm -f .env; git status --short
+```
+
+Expected: no `volcanic-test_*` volumes, no `volcanic-agentic-core` images, no `.env`, a clean tree. Do not prune build cache or other images (base images pulled by the build may stay; list them in the report).
+
+- [ ] **Step 5: Commit**
+
+```bash
+cd /d/Projects/Names/wt-docker-deployment && git add docs/superpowers/specs/2026-09-26-docker-deployment-design.md && git commit -m "docs(docker): mark the deployment spec implemented"
+```
+
+Report every verification step with its result. `openIssues` must list at least: the chat-driven checks (Verification 2 env/`date`/`/timezone`, `/claude-code-login` + `/claude-code-doctor` green, Verification 3 "tell me the H1" in a chat, Verification 4 redeploy with a logged-in bot, Verification 5 `ANTHROPIC_API_KEY` refusal), a real Coolify deploy, and anything from T1–T3 that could not be checked.