diff --git a/.planning/STATE.md b/.planning/STATE.md
index fc164e5e..85a9805d 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -5,10 +5,10 @@ current_phase: "19.1"
current_phase_name: Chat satellites
status: executing
stopped_at: Completed 19.1-05-PLAN.md
-last_updated: "2026-10-18T12:02:40.895Z"
+last_updated: "2026-10-18T12:55:02.551Z"
last_activity: 2026-10-18
-last_activity_desc: Phase 19.1 execution started
-state_head: 782a6720bf2bcdce1dcaa3c1c9a98c69f1d44998
+last_activity_desc: "Completed quick task 261018-k9s: add the D-17 module in summercms.go (modules/breakwater); 19.1-02 forum plan unblocked"
+state_head: 98bc6de8f57fda4bd3ccc1880ba22b9ce1d6582a
progress:
total_phases: 41
completed_phases: 33
@@ -32,7 +32,7 @@ See: .planning/PROJECT.md (updated 2026-10-12)
Phase: 19.1 (Chat satellites) — EXECUTING
Plan: 4 of 6
Status: Ready to execute
-Last activity: 2026-10-18 — Phase 19.1 execution started
+Last activity: 2026-10-18 — Completed quick task 261018-k9s: add the D-17 module in summercms.go (modules/breakwater); 19.1-02 forum plan unblocked
Progress: [████████░░] 80%
@@ -891,7 +891,6 @@ Recent decisions affecting current work:
- Phase 8 UI gate: scripts/check-phase8-ui.mjs --final-gate's real Playwright browser matrix (32 UI-SPEC scenarios) is unimplemented (deliberate fatal() at check-phase8-ui.mjs:458, never authored by 08-05); stage_ui_harness must keep failing closed until a Playwright spec is authored. User approved Phase 8 closure on 2026-09-24 with this gap carried forward -- see 08-10-SUMMARY.md and .planning/phases/08-oauth2-1-authorization-server/deferred-items.md.
- pending: push sm-user-plugin — 12.1-05 Task 3 condition (c) failed (`git ls-remote --tags origin v0.1.3` empty). Push summercms.go master and annotated tag v0.1.3 (local on df5cace) first, then the plugin, then fonoteka.go. Recorded in 12.1-05-SUMMARY.md.
- 16.2-05 open UAT items: re-save HTML post 1 untouched (D-03 evidence vacuous), D-19 category edit check, root vhost install (frame-src), unpublish/delete test post 105
-- D-17 Render/Purify missing: 19.1-02 halted after the D-16 forum-policy note (2740ab2). modules/ has cabana.RenderMarkdown only. Run /msd-quick in summercms.go for a markdown module exporting Render(markdown) and Purify(html) with the named forum policy, then re-run /msd-execute-phase 19.1. Do not create sm-forum-plugin until that lands.
### Quick Tasks Completed
@@ -935,6 +934,7 @@ Recent decisions affecting current work:
| 36 | summercms.io copy Alpha 0.1 → 0.2 (vue b5d6dbb, app 6a13a87), deployed | 2026-10-15 | 853ea1f | — |
| 261016-0fm | Admin minors for v0.2.1: theme switch, language switch, settings nav on form pages | 2026-10-16 | 66fd4ae | [261016-0fm-admin-minors-for-v0-2-1-theme-switch-lan](./quick/261016-0fm-admin-minors-for-v0-2-1-theme-switch-lan/) |
| 261016-s8x | PaymentGateway: banner bottom margin and hide disabled sections from admin navigation | 2026-10-16 | ab9f385 | [261016-s8x-paymentgateway-banner-bottom-margin-and-](./quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/) |
+| 261018-k9s | add the D-17 module in summercms.go: modules/breakwater Render(markdown)/Purify(html) with the forum bluemonday policy | 2026-10-18 | 98bc6de | [261018-k9s-add-the-d-17-module-in-summercms-go](./quick/261018-k9s-add-the-d-17-module-in-summercms-go/) |
## Deferred Items
diff --git a/.planning/quick/261018-k9s-add-the-d-17-module-in-summercms-go/261018-k9s-PLAN.md b/.planning/quick/261018-k9s-add-the-d-17-module-in-summercms-go/261018-k9s-PLAN.md
new file mode 100644
index 00000000..f358a19d
--- /dev/null
+++ b/.planning/quick/261018-k9s-add-the-d-17-module-in-summercms-go/261018-k9s-PLAN.md
@@ -0,0 +1,217 @@
+---
+phase: quick-261018-k9s
+plan: 01
+quick_id: 261018-k9s
+slug: add-the-d-17-module-in-summercms-go
+type: execute
+mode: quick
+wave: 1
+depends_on: []
+files_modified:
+ - modules/breakwater/breakwater.go
+ - modules/breakwater/breakwater_test.go
+ - modules/breakwater/README.md
+ - README.md
+ - docs/architecture/introduction.md
+ - docs/services/markdown.md
+autonomous: true
+requirements: [D-16, D-17]
+source:
+ - .planning/phases/19.1-chat-satellites/19.1-CONTEXT.md (D-16, D-17, D-18)
+ - .planning/notes/2026-10-18-bluemonday-forum-policy.md
+ - .planning/STATE.md blocker "D-17 Render/Purify missing"
+
+estimate:
+ tokens: 70000
+ raw_tokens: 70000
+ tasks: 2
+ confidence: low
+
+must_haves:
+ truths:
+ - "A plugin can import one framework package and turn user Markdown into HTML that contains no script, iframe, img, event handler or javascript:/data: URL, while GFM tables, fenced code, strikethrough, links and autolinks render (D-16, D-17)."
+ - "The same package sanitizes already-HTML with the identical allow-list, so trainings-style stored HTML goes through the policy without a Markdown pass (D-17 purify callers)."
+ - "Blank or whitespace-only input renders and purifies to the empty string, as PHP MarkdownRenderer does."
+ - "Every link the policy keeps carries rel nofollow; every fully-qualified http/https link also carries target _blank (PHP HTML.Nofollow + HTML.TargetBlank)."
+ - "The 19.1-02 forum gate finds `func Render(` and `func Purify(` under modules/ outside cabana.RenderMarkdown, so forum execution unblocks."
+ - "go vet ./..., go test -short ./... and summer docs:build --check are green; go.mod and go.sum are unchanged."
+ artifacts:
+ - "modules/breakwater/breakwater.go — package breakwater: unexported goldmark GFM engine, unexported bluemonday forum policy, exported Render and Purify"
+ - "modules/breakwater/breakwater_test.go — PHP ForumMarkdownRenderTest corpus, links/autolinks, blank input, Purify allow-list"
+ - "modules/breakwater/README.md — standard module README structure"
+ - "README.md — module table row for breakwater"
+ - "docs/architecture/introduction.md — breakwater in the framework modules table"
+ - "docs/services/markdown.md — docs page: Markdown and HTML sanitizing for user content"
+ key_links:
+ - "Render = goldmark (extension.GFM, default safe renderer) -> Purify; Purify = forum policy Sanitize. Both return \"\" on blank input."
+ - "The policy is built once at package init and shared; bluemonday policies are read-only once built (seaglass precedent)."
+ - "Docs: every `breakwater.X` code span in README/docs must exist in the package (docs checker)."
+---
+
+
+Add the D-17 framework module: `modules/breakwater`, a Markdown renderer and HTML sanitizer for user-submitted content, exporting `Render(markdown string) string` and `Purify(html string) string` with the named forum policy from the 2026-10-18 decision note (1:1 with the PHP `golem15/forum` HTMLPurifier config). It is the gate the forum plans (19.1-02) and later the Wavepath `purify()` callers consume; `cabana.RenderMarkdown` stays untouched as the admin preview gate and `seaglass` stays the admin rich-text policy.
+
+Purpose: 19.1-02 is halted on the STATE.md blocker "D-17 Render/Purify missing". Per D-17 the renderer is a framework module delivered by a quick task, not forum code.
+Output: the `breakwater` package with tests, its README, a root README row, the introduction.md table row and a `docs/services/markdown.md` page; one commit.
+
+
+
+@~/.claude/msd-core/workflows/execute-plan.md
+@~/.claude/msd-core/templates/summary.md
+
+
+
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/CLAUDE.md
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/.planning/notes/2026-10-18-bluemonday-forum-policy.md
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/.planning/notes/2026-10-11-framework-html-sanitizer-bluemonday.md
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/modules/seaglass/seaglass.go
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/modules/seaglass/README.md
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/modules/cabana/field_markdown.go
+@/media/nvme/dev/golem15/summercms.io/summercms/summercms.go/internal/docsite/render.go
+@/media/nvme/dev/jz/wavepath.org/plugins/golem15/forum/classes/markdown/MarkdownRenderer.php
+@/media/nvme/dev/jz/wavepath.org/plugins/golem15/forum/tests/security/ForumMarkdownRenderTest.php
+
+Facts verified at planning time (2026-10-18, live reads):
+- `go.mod` already pins `github.com/microcosm-cc/bluemonday v1.0.27` (line 24) and `github.com/yuin/goldmark v1.8.6` (line 34). No dependency is added or bumped.
+- `modules/` has no `breakwater`; the word appears nowhere in `modules/`, `docs/`, `README.md` or `.planning/notes/`.
+- goldmark v1.8.6 default renderer (no unsafe option): raw HTML blocks and inline raw HTML are written as the comment `` (renderer/html/html.go:390,398,656); a link or autolink whose destination fails `IsDangerousURL` is written with an empty href (html.go:518,591,620).
+- bluemonday v1.0.27: `AllowURLSchemes` turns on parseable-URL checking and leaves relative URLs disallowed; an `a` whose only attribute was dropped is not in the default no-attribute element set, so the tag is removed and its text kept (seaglass smoke test documents the same); `AddTargetBlankToFullyQualifiedLinks(true)` also adds `noopener` to `rel` (sanitize.go:717-752); comments are dropped unless allowed.
+- 19.1-02-PLAN.md line 174-175 gate: `grep -n 'func Render(' modules --glob '*.go' | grep -v RenderMarkdown` and `grep -n 'func Purify(' modules --glob '*.go'` must each print a line.
+- Docs checker (`internal/docsite/check_identifiers.go`): code spans of the form `pkg.Ident` / `pkg.Ident(...)` must name an identifier that exists in `modules/`; `src=` snippets and links/anchors are checked too. Module READMEs are ingested as API reference pages automatically.
+- Root README module table is alphabetical (`bouncer` row precedes `cabana`); introduction.md "The framework modules" table has rows Plugins and the container / HTTP / Data / Admin / Services / Console and tooling / Utilities.
+- `docs/services/*.md` front-matter: `title`, `description`, `section: services`, `order` (highest existing order is 160, frontend-and-ajax.md).
+
+
+
+
+
+ Task 1: breakwater package — Render and Purify with the named forum policy, end-to-end under test
+ modules/breakwater/breakwater.go, modules/breakwater/breakwater_test.go
+
+ - .planning/notes/2026-10-18-bluemonday-forum-policy.md (the locked policy: D-16/D-17)
+ - modules/seaglass/seaglass.go (package doc style, package-level policy var, bluemonday builder calls)
+ - modules/cabana/field_markdown.go (package-level goldmark engine precedent; do not modify this file)
+ - internal/docsite/render.go lines 26-31 (in-tree `goldmark.New(goldmark.WithExtensions(extension.GFM), ...)` form)
+ - /media/nvme/dev/jz/wavepath.org/plugins/golem15/forum/classes/markdown/MarkdownRenderer.php and tests/security/ForumMarkdownRenderTest.php (reference behavior and corpus)
+
+ `grep -n 'bluemonday v1.0.27\|goldmark v1.8.6' go.mod` prints two lines and `ls modules/breakwater` fails (the package does not exist yet).
+
+ Test file `modules/breakwater/breakwater_test.go`, package `breakwater_test`, importing `git.golem15.com/golem15/summercms/modules/breakwater`; table-driven where natural; `strings.ToLower` the output before negative checks as the PHP test does:
+ - TestRenderXSS (the PHP `test_xss_sanitization` corpus verbatim: "\n\n[x](javascript:alert(1))\n\n\n\n"): the lowercased output does not contain `")` and `Render("text bold more")` contain no `alert(1)`, no `onclick`, no `")` returns a string that contains `
plain
` and none of `` marker. The package imports neither seaglass nor cabana nor `golang.org/x/net/html`; `cabana.RenderMarkdown` and `modules/seaglass` are untouched (`git diff --exit-code HEAD~1 -- modules/cabana modules/seaglass` is empty).
+
+## Gate output
+
+- `go vet ./...` — exit 0, no output.
+- `go test -short ./...` — exit 0 (all `ok` / `no test files`; the only failures seen during the task were the docs-tree tests before the `src=` fix below, resolved).
+- `go install ./cmd/summer && summer docs:build --check` — `docs:build: no problems found` (run as `$(go env GOPATH)/bin/summer`; `~/go/bin` is not on this shell's PATH).
+- `git diff --exit-code go.mod go.sum` — clean; bluemonday v1.0.27 and goldmark v1.8.6 were already pinned.
+- `go test -short -count=1 ./modules/breakwater -v` — PASS: TestRenderXSS, TestRenderStripsRawHTML, TestRenderGFM, TestRenderLinks, TestBlankInput, TestPurifyAllowList, TestConcurrentUse, ExampleRender, ExamplePurify (0.012s).
+- `git show --stat HEAD` lists the plan's six files plus `modules/breakwater/example_test.go` (7 files, 430 insertions, 1 deletion — the rewritten introduction.md row).
+
+## Deviations from Plan
+
+### Auto-fixed Issues
+
+**1. [Rule 3 - Blocking] docs/ Go fences must carry `src=`; added `modules/breakwater/example_test.go`**
+- **Found during:** Task 2, gate `go test -short ./...` (cmd/summer docs-tree tests) and `summer docs:build --check`: `docs/services/markdown.md:13: snippet: go code block has no src= reference`.
+- **Issue:** The plan assumed the docs checker only verifies `src=` snippets, so it prescribed a plain fenced Go block on the docs page. `internal/docsite/check_policy.go` rejects every Go fence in a `docs/` page without `src=` (module READMEs are exempt; the README's plain block passed).
+- **Fix:** Following the in-tree convention (`docs/services/events.md` → `modules/festival/example_test.go#ExampleBus_Fire`), added `modules/breakwater/example_test.go` with `ExampleRender` and `ExamplePurify` (their `// Output:` lines make them real tests of the documented output), split the docs page block into two `src=modules/breakwater/example_test.go#Example…` blocks and ran `summer docs:sync`. The checker was not weakened.
+- **Files modified:** modules/breakwater/example_test.go (new), docs/services/markdown.md.
+- **Commit:** 98bc6de (the plan's single commit; it therefore has seven files, not six).
+
+**2. [Rule 1 - Bug] Corrected a docs claim about inline raw HTML**
+- **Found during:** Task 2, before the gates.
+- **Issue:** The first draft of `docs/services/markdown.md` said `text bold more` renders to `
text more
`. goldmark replaces each raw tag with an omitted-HTML comment but keeps the inner text, so the real output is `
text bold more
` (verified with a throwaway test, since removed).
+- **Fix:** Docs text corrected to the real output. The plan's own test (`TestRenderStripsRawHTML`) only asserts `text` and `more` survive, which is still true.
+- **Files modified:** docs/services/markdown.md.
+- **Commit:** 98bc6de.
+
+## Tracer feedback gate
+
+Task 1 is `type="tracer"`; interactive mode with `human_verify_mode` at its default (`end-of-phase`) and an automated-only ``: re-ran the verify (vet, module tests, go.mod clean, no `WithUnsafe`/`UGCPolicy`/seaglass/cabana imports, both gate greps) — passed, so execution continued to Task 2 without a checkpoint.
+
+## Known Stubs
+
+None. Both exported functions are fully wired; no placeholder values.
+
+## Threat Flags
+
+None beyond the plan's register. T-k9s-01/02/04 are pinned by TestRenderXSS, TestRenderStripsRawHTML, TestRenderLinks and TestPurifyAllowList as the plan assigned. No new network endpoint, auth path, file access or schema surface was introduced.
+
+## Not done by the executor (orchestrator's part)
+
+- Docs commit of this SUMMARY and the quick-task PLAN (`.planning/quick/261018-k9s-…` left untracked on purpose).
+- STATE.md: blocker "D-17 Render/Purify missing" to be cleared; ROADMAP.md untouched (quick task).
+- Nothing pushed, nothing tagged.
+
+## Self-Check: PASSED
+
+All five created files exist on disk; commit 98bc6de is in the log; `modules/cabana/field_markdown.go` and `modules/seaglass/` are byte-identical to HEAD.