docs(20-02): demo backend for the chat family summary

This commit is contained in:
Jakub Zych
2026-10-30 19:11:58 +01:00
parent d6f490a9ab
commit 00607d165d

View File

@@ -0,0 +1,311 @@
---
phase: 20-demo-instance-with-every-shared-plugin
plan: 02
subsystem: sm-demo-app
tags: [chat, chatchannels, chatbots, chatvideo, forum, herald, userfriends, moderation, livekit, centrifugo, seed]
requires:
- phase: 20-01
provides: the eight chat-family tags (chat v0.1.5 with MessageSaving, forum v0.1.1 with PostSaving, moderation v0.1.0 Filter)
provides:
- "sm-demo-app mounts chat, chatchannels, chatbots, chatvideo, forum, herald, userfriends and moderation at their tags"
- "golem15.demo hosts the chat family: contexts demo/1 and channel/{id}, channel and forum access policies, moderation seams, slash commands /demo and /tour"
- "Host bootstrap contract GET /_demo/api/v1/chat and GET /_demo/api/v1/chat/members (for vue-chat-layer, plan 03)"
- "demo.realtime-access: every community member gets a realtime token"
- "demo:seed EN/PL showcase data for chat, forum and friends; demo:reset covers every chat-family table"
- "Local LiveKit in scripts/dev.sh; deploy/livekit.yaml.example and Centrifugo namespaces chat, user, video (for plan 05)"
affects: [20-03, 20-04, 20-05, 20-06]
actuals:
tokens: 36500 # chars/4 over the realized sm-demo-app diff 56c1141..f4ed2c2 without go.sum (146079 chars)
tasks: 4
commits: 1 # MEASURED in summercms.go: git rev-list --count plan_head_before..HEAD (the todo move); sm-demo-app: 12 (git rev-list --count 56c1141..f4ed2c2)
plan_head_before: 159cd5ecf4aac6e48c2579c68c19addb7633cfce # summercms.go
plan_head_after: d6f490a9abcaf4322aec5a072f7842b8f0d3d2c3 # summercms.go before this SUMMARY commit; sm-demo-app 56c1141 -> f4ed2c2
tech-stack:
added:
- "github.com/livekit/server-sdk-go/v2 v2.19.0 and github.com/SherClockHolmes/webpush-go v1.4.0 (transitive, through chatvideo and herald)"
patterns:
- "Channel-as-context adapter: the host registers chatchannels' `channel` alias on chat's registry so a channel's backing conversation resolves"
- "Host wiring in lagoon.OnDatabase after each plugin published its service (ChannelService, ForumService, chatbots Registry, moderation Filter); a missing service is a boot error naming the summer.yaml order"
- "Pre-save veto listeners (MessageSaving, PostSaving) look up the moderation Filter per event"
key-files:
created:
- ../sm-demo-app/plugins/demo/classes/chat_context.go
- ../sm-demo-app/plugins/demo/classes/forum_access.go
- ../sm-demo-app/plugins/demo/chat.go
- ../sm-demo-app/plugins/demo/chat_api.go
- ../sm-demo-app/plugins/demo/chatbots.go
- ../sm-demo-app/plugins/demo/moderation.go
- ../sm-demo-app/plugins/demo/realtime_access.go
- ../sm-demo-app/plugins/demo/console/seed_chat.go
- ../sm-demo-app/plugins/demo/console/seed_forum.go
- ../sm-demo-app/plugins/demo/console/seed_friends.go
- ../sm-demo-app/chat_smoke_test.go
- ../sm-demo-app/config/golem15.chatvideo.yaml
- ../sm-demo-app/config/golem15.chat.yaml
- ../sm-demo-app/config/env/production/golem15.chatvideo.yaml
- ../sm-demo-app/deploy/livekit.yaml.example
modified:
- ../sm-demo-app/summer.yaml
- ../sm-demo-app/go.mod
- ../sm-demo-app/go.work
- ../sm-demo-app/.gitmodules
- ../sm-demo-app/plugins.gen.go
- ../sm-demo-app/plugins/demo/plugin.go
- ../sm-demo-app/plugins/demo/routes.go
- ../sm-demo-app/plugins/demo/config/config.yaml
- ../sm-demo-app/plugins/demo/console/seed.go
- ../sm-demo-app/plugins/demo/console/reset.go
- ../sm-demo-app/boot_test.go
- ../sm-demo-app/realtime_test.go
- ../sm-demo-app/deploy/centrifugo.json.example
- ../sm-demo-app/deploy/env.example
- ../sm-demo-app/scripts/dev.sh
- ../sm-demo-app/README.md
- .planning/todos/done/2026-10-26-sm-demo-app-journal-oauthserver-pointers.md
key-decisions:
- "Folded oauthserver/journal todo closed without a commit in sm-demo-app: 2133309 and 5ec5941 had already moved oauthserver to v0.1.6 (contains b6a9586) and journal to v0.2.6; not moved back to v0.1.3"
- "Channel join path: GetGroupChat's lazy join holds (opening GET /_chat/api/v1/contexts/channel/{id}/conversation adds the participant row); the bootstrap joins nobody"
- "Bootstrap channels[].joined is 'has not left' (chatchannels prefs.has_left); left channels stay listed with joined false"
- "Chat attachments capped at 5 MiB per message (config/golem15.chat.yaml max_message_bytes 5242880), reported as limits.max_bytes"
- "Forum CanManage and CanManageContext both go to the moderator group, as the plan says; forum needs no RegisterContext for listing or posting"
- "Production livekit_url wss://demo.summercms.io/livekit lives in config/env/production/golem15.chatvideo.yaml"
patterns-established:
- "Seed steps write chat rows by raw SQL in created order and call chat's own jobs.Work to keep thread summaries consistent"
requirements-completed: [SC-1, D-05, D-06, D-07, D-08, D-09, D-10, D-12, D-14, D-17, D-20, D-22, D-23]
coverage:
- id: D1
description: "A registered member reads the bootstrap, opens seeded #general (lazy join), posts and reads back through the chat API; channel creation refused; members endpoint excludes the caller"
requirement: "D-06, D-07"
verification:
- kind: integration
ref: "../sm-demo-app/chat_smoke_test.go#TestDemoChatTracer"
status: pass
human_judgment: false
- id: D2
description: "Banned word in a chat message -> 422 errors.content, banned link in a forum title -> 422 errors.title, clean content stored; DM between two members; /demo and /tour listed and ephemeral"
requirement: "D-17, D-08, D-05"
verification:
- kind: integration
ref: "../sm-demo-app/chat_smoke_test.go#TestDemoModerationSmoke"
status: pass
human_judgment: false
- id: D3
description: "demo:seed EN/PL chat, forum, friends, moderator group, moderation lists; second run all exists; demo:reset --force wipes and reseeds, keeps moderation settings and the userfriends context"
requirement: "D-09, D-22"
verification:
- kind: integration
ref: "../sm-demo-app/chat_smoke_test.go#TestSeedChatSmoke"
status: pass
- kind: other
ref: "scripts/dev.sh seed twice against the local Postgres: every new step 'created', then 'exists'"
status: pass
human_judgment: false
- id: D4
description: "Every plugin activates, prefixes routed, go.mod tags only, pins match"
requirement: "SC-1"
verification:
- kind: integration
ref: "../sm-demo-app/boot_test.go#TestBootAllPlugins, go_mod_test.go#TestGoModRequiresTags, scripts/check-pins.sh (25 modules), scripts/build.sh dev"
status: pass
human_judgment: false
- id: D5
description: "Realtime token for any member (401 without JWT), Centrifugo namespaces, local LiveKit on 7890/7891/7892"
requirement: "D-10, D-06"
verification:
- kind: integration
ref: "../sm-demo-app/realtime_test.go#TestRealtimeRoutes"
status: pass
- kind: other
ref: "dev.sh start_livekit (curl 127.0.0.1:7890 200); new binary on 127.0.0.1:8197: GO_ADDR=127.0.0.1:8197 scripts/check-local.sh realtime ok; fresh registered member: token, /chat, /chat/members 200"
status: pass
human_judgment: false
duration: 33min
completed: 2026-10-30
status: complete
---
# Phase 20 Plan 02: Demo backend for the chat family Summary
**sm-demo-app mounts all eight chat-family plugins at their tags and golem15.demo hosts them as one community. Every registered member can post in the seeded EN/PL channels, DM anyone, use /demo and /tour, post in the forum and get a realtime token. Moderation refuses banned words and links with 422, and a local LiveKit runs from scripts/dev.sh.**
## Performance
- **Duration:** about 33 min
- **Started:** 2026-10-30T17:37:25Z
- **Completed:** 2026-10-30T18:10:23Z
- **Tasks:** 4/4
- **Files modified:** 40 in sm-demo-app (2902 insertions, 111 deletions), 1 in summercms.go
## Accomplishments
- Eight `feat: mount golem15.<id> <tag>` commits. `summer.yaml` lists chat, chatchannels, chatbots, chatvideo, forum, herald, userfriends and moderation after golem15.user and before golem15.demo. Every submodule describes as its tag, and `check-pins` reports ok for 25 modules.
- Host types in `plugins/demo/classes`:
- `DemoContext` (demo/1, `chat:context:1`).
- `ChannelContext` (alias `channel`, `chat:channel:{id}`).
- `ChatAccess`: members may access demo/1 and `CanManageContext` is always false.
- `ForumAccess`.
- `MemberIDs`, `IsMember` and `IsModerator`.
- Membership means activated, not deleted and not banned (`user_throttle.is_banned`).
- The bootstrap `GET /_demo/api/v1/chat` returns `{member, moderator, context, channels[], conversations[], limits}` and `/chat/members` returns `{members[]}`. Both need a JWT and are throttled at 120 a minute.
- Listeners on MessageSaving and PostSaving call the moderation Filter. `/demo` and `/tour` answer ephemerally, in EN or PL from `preferred_locale`.
- demo:seed adds eight steps. demo:reset clears 27 more tables in foreign-key order.
- `demo.realtime-access` guards the token route. Centrifugo gets the namespaces chat, user and video. `deploy/livekit.yaml.example` is added, dev.sh starts and stops a local LiveKit, and the README has a chat family section.
## Task Commits
| # | Task | Repo | Commit | Message |
|---|------|------|--------|---------|
| 1 | Folded todo | summercms.go | d6f490a | docs(20-02): close the sm-demo-app journal/oauthserver pointer todo |
| 1 | Mount chat | sm-demo-app | 7815001 | feat: mount golem15.chat v0.1.5 |
| 1 | Mount chatchannels | sm-demo-app | 6edfae5 | feat: mount golem15.chatchannels v0.1.0 |
| 1 | Tracer host wiring | sm-demo-app | 58ba678 | feat: demo chat context, channel adapter and bootstrap |
| 2 | Mount chatbots | sm-demo-app | 47c7833 | feat: mount golem15.chatbots v0.1.0 |
| 2 | Mount chatvideo | sm-demo-app | 5076d8c | feat: mount golem15.chatvideo v0.1.1 |
| 2 | Mount forum | sm-demo-app | 2dea441 | feat: mount golem15.forum v0.1.1 |
| 2 | Mount herald | sm-demo-app | 64d37a2 | feat: mount golem15.herald v0.1.1 |
| 2 | Mount userfriends | sm-demo-app | a78c543 | feat: mount golem15.userfriends v0.1.1 |
| 2 | Mount moderation | sm-demo-app | 864a14b | feat: mount golem15.moderation v0.1.0 |
| 2 | Wiring | sm-demo-app | b03fe83 | feat: demo hosts forum, friends, slash commands and moderation |
| 3 | Seed and reset | sm-demo-app | 4a94565 | feat: seed and reset the chat family (EN/PL) |
| 4 | Realtime, members, deploy, dev | sm-demo-app | f4ed2c2 | feat: realtime for every demo member, members endpoint, Centrifugo namespaces and local LiveKit |
Nothing was pushed, tagged or deployed. No file inside a plugin submodule was edited.
## Details for later plans
**Channel join path:** the lazy join holds. `GET /_chat/api/v1/contexts/channel/{channelId}/conversation` reaches chat's `GetGroupChat`, which adds the caller as a participant of the existing backing conversation. TestDemoChatTracer asserts the participant row. The bootstrap fallback (`AddUserToGroupChat`) was not needed.
**Personas:** Ada Hart and Ben Carter (en), Ola Nowak and Tomek Wiśniewski (pl). Emails are `<key>@personas.demo.invalid`. Each gets a random bcrypt hash of 32 random bytes, never printed, and a handle from the user plugin's `AssignHandle`.
**Channels:**
| Channel | Slug | Language | Messages | Thread roots | Pins |
|---------|------|----------|----------|--------------|------|
| #general (default) | `general` | en | 13 | 2 | 1 |
| #announcements | `announcements` | en | 5 | 1 | 1 |
| #ogólny | `ogolny` | pl | 15 | 1 | 1 |
All three channels have reactions. Ada sends the visitor a welcome DM.
**Forum:** "General discussion" (`general-discussion`) and "Show and tell" (`show-and-tell`) in English and "Rozmowy" (`rozmowy`) in Polish, with 2, 2 and 3 threads and `content_html` from `breakwater.Render`.
**Friends:** accepted friendships ada-ben, ada-ola, ben-tomek and ola-tomek. Ola has a pending request to the visitor. The group "Demo crew" has Ada as owner and Ben and Tomek as members.
**Moderation defaults** (only while both lists are empty): words `buy followers`, `casino bonus`, `free crypto` and `viagra`; link `bit.ly`.
**Reset table order** (inserted before `users`, children first):
1. chatvideo participants, sessions
2. chatchannels channel_mentions, channel_pins, channel_members, channel_user_prefs, user_context_prefs, personal_categories, channels, channel_categories
3. chat reactions, thread_mutes, thread_summaries, messages, participants, conversations
4. forum thread_reads, posts, threads, categories
5. userfriends group_bans, group_invites, group_members, groups, friendships
6. herald push_subscriptions
7. chatbots bot_flags
Chatchannels comes before chat because channels hang off their backing conversations. The reset keeps `golem15_moderation_settings`, `user_groups` (the moderator group) and `golem15_userfriends_contexts`. Chat attachments were already covered: the reset removes every `system_files` owner except the kept ones.
**Centrifugo option names** (v6.5.1, checked against `centrifugo defaultconfig`):
- `chat`: `presence`, `join_leave`, `force_push_join_leave`, `allow_presence_for_subscriber` and `subscribe_proxy_enabled`.
- `user` and `video`: `subscribe_proxy_enabled` only.
**Boot warnings left by plugins** (`dev.sh seed`):
- The chatchannels and forum deny-all warnings fire from the plugins' own database hooks before golem15.demo's hook installs the resolvers, so they are expected.
- `golem15.chatbots: BotAuthResolver is deny-all`. The demo has no bot persona.
- `golem15.herald: PresenceGate is deny-all` and `golem15.herald: VAPID keys are missing`. Plan 04 handles these.
## Decisions Made
See the key-decisions list in the frontmatter. The plan's own discretion items (aliases, personas, slugs, commands, ports) were applied as written.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Folded todo needed no sm-demo-app commit**
- **Found during:** Task 1 step 1
- **Issue:** go.mod already required sm-oauthserver-plugin v0.1.6 and sm-journal-plugin v0.2.6 (2133309, 5ec5941), and the oauthserver checkout contains b6a9586. Bumping to v0.1.3 would have been a downgrade.
- **Fix:** No `chore: bump sm-oauthserver-plugin to v0.1.3` commit. The todo moved to done with a note (d6f490a).
**2. [Rule 3 - Blocking] realtime_test.go namespaces updated in the mount commits**
- **Found during:** Task 1 and Task 2 (mount chatvideo)
- **Issue:** chat registers the `chat` namespace and chatvideo registers `video`, so the short suite failed.
- **Fix:** `wantNamespaces` was extended in 58ba678 and 5076d8c, so `go test -short` stays green at every commit.
**3. [Rule 3 - Blocking] boot_test.go realtime subtest expected the old guard**
- **Found during:** Task 4
- **Fix:** It now expects `demo.realtime-access`, in f4ed2c2.
**4. [Rule 2 - Missing critical] Chat attachment cap**
- **Issue:** chat's `max_message_bytes` defaults to 0 (no total limit) on a public demo, and the bootstrap reported 0.
- **Fix:** `config/golem15.chat.yaml` sets 5 MiB per message (5 × the 1 MiB upload body limit), in b03fe83.
**5. [Rule 2] Production LiveKit URL**
- **Fix:** Added `config/env/production/golem15.chatvideo.yaml` with `livekit_url: wss://demo.summercms.io/livekit`, so the committed `ws://127.0.0.1:7890` applies locally only (f4ed2c2).
**6. Scope order changes**
- The `chat personas` step landed in Task 1, because #general's welcome message needs a sender. `users` can't send as a backend admin.
- `/chat/members` assertions were added to TestDemoChatTracer in Task 4.
**7. The `pact.HasMiddleware` assertion sits in plugin.go**, as the plan asks. The middleware itself is in realtime_access.go.
**8. Forum RegisterContext not called.** Its only consumer is `forum:rewrite-context-types`. Listing and posting work without it.
### Verification adjustments
- **The Task 4 verify command `centrifugo checkconfig -c deploy/centrifugo.json.example` cannot pass as written.** Centrifugo picks the config type from the file extension ("Unsupported Config Type example"). I ran it on a `.json` copy instead, the way `scripts/check-deploy.sh` does. checkconfig passed, check-deploy passed, and every option name is in `centrifugo defaultconfig`.
- **`scripts/dev.sh up` was not run as a whole.** The dev ports are held by stale processes that dev.sh did not start in this session and cannot stop:
- Centrifugo on 8011 (pid 212066, Oct 22)
- Go on 8097, run as user golem (pid 3405714, Oct 24)
- Nuxt on 3020 from a cursor-agent session (pid 3776486, Oct 27)
Their pid files are stale, so `claim_port` would refuse, and killing other sessions' processes was out of bounds. What I verified instead:
- **LiveKit:** started with dev.sh's own `ensure_env` and `start_livekit` functions. `curl 127.0.0.1:7890/` answered 200, TCP 7890/7891 and UDP 7892 were listening, `logs/livekit.yaml` is mode 600 with no CHANGE_ME left and `use_external_ip: false`.
- **Realtime:** the new binary ran on 127.0.0.1:8197 against the local Postgres, and `GO_ADDR=127.0.0.1:8197 scripts/check-local.sh realtime` printed ok. A freshly registered member got 200 from the token route, `/_demo/api/v1/chat` and `/chat/members`.
- **Seed:** `scripts/dev.sh seed` ran twice. The new steps were `created`, then `exists`.
- Both of my processes were stopped afterwards.
- **Mistake made and fixed:** my first attempt sourced dev.sh's functions from the scratchpad. Its `cd "$(dirname "$0")/.."` then landed in the meta repo `..`, where it wrote `.env`, `logs/` and `storage/app/uploads/public`. All three had been created seconds earlier by that run, and I removed them. Nothing else in the meta repo was touched.
**Total deviations:** 8 plus 2 verification adjustments. **Impact:** none on the plan goal.
## Issues Encountered
- **g15office MCP unavailable** (connection timed out in this session). Task ssuyq2os (the folded todo) was **not** moved to Live and got no comment, and the Phase 20 task xo463rwk got no comment either. The orchestrator or the user needs to do both.
## Known Stubs
None. Every endpoint reads live data. The bootstrap reports `is_bot: false` for everyone on purpose, because the demo has no bot persona (D-05).
## Threat Flags
| Flag | File | Description |
|------|------|-------------|
| threat_flag: endpoint | ../sm-demo-app/plugins/demo/chat_api.go | `/chat/members` lists every member's name, surname and handle to any member (planned in T-20 scope; no email or id beyond the user id) |
| threat_flag: config | ../sm-demo-app/config/golem15.chat.yaml | Upload cap 5 MiB per message on the public demo (mitigation, not new surface) |
## User Setup Required
None for this plan. Plan 05 needs the LiveKit key pair on rome (`deploy/env.example`).
## Next Phase Readiness
- Plan 03 (vue-chat-layer) can build on `GET /_demo/api/v1/chat` and `/chat/members` as documented in sm-demo-app README "Chat family". Channels are opened with `GET /_chat/api/v1/contexts/channel/{id}/conversation`.
- Plan 05 deploys `deploy/livekit.yaml.example`, the Centrifugo namespaces (restart summercms-demo-centrifugo), nginx `/livekit/` and the Go allow-list, and the herald VAPID keys.
---
*Phase: 20-demo-instance-with-every-shared-plugin*
*Completed: 2026-10-30*
## Self-Check: PASSED
- All 15 created files listed under key-files exist, and the todo is under `.planning/todos/done/`.
- sm-demo-app commits 7815001, 6edfae5, 58ba678, 47c7833, 5076d8c, 2dea441, 64d37a2, a78c543, 864a14b, b03fe83, 4a94565, f4ed2c2 and summercms.go d6f490a exist.
- `git -C ../sm-demo-app submodule status` shows no `+` or `-`, and the sm-demo-app working tree is clean (`--ignore-submodules=none`).