From 00607d165d8ea233265339c5c52b243cc06d21a5 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 30 Oct 2026 19:11:58 +0100 Subject: [PATCH] docs(20-02): demo backend for the chat family summary --- .../20-02-SUMMARY.md | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 .planning/phases/20-demo-instance-with-every-shared-plugin/20-02-SUMMARY.md diff --git a/.planning/phases/20-demo-instance-with-every-shared-plugin/20-02-SUMMARY.md b/.planning/phases/20-demo-instance-with-every-shared-plugin/20-02-SUMMARY.md new file mode 100644 index 00000000..1d407746 --- /dev/null +++ b/.planning/phases/20-demo-instance-with-every-shared-plugin/20-02-SUMMARY.md @@ -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. ` 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 `@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`).