docs(19-06): complete participant-only send and GDPR reassignment plan

Record CR-01/CR-02 gap closure and mark the 19-06 ROADMAP checkbox. Plugin repos were committed separately and not pushed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-10-17 22:31:22 +02:00
parent 29d06bc70e
commit d1852f6cda
2 changed files with 168 additions and 2 deletions

View File

@@ -1198,7 +1198,7 @@ Plans:
3. Golden tests show that the serializers (`Message.toApiArray` with attachments, thread summaries, channel, category, pref, pin and mention arrays) and every realtime event (name, channel and payload) match PHP output captured from Wavepath. Route-level parity of `/_wave` is checked at the Wavepath port. 3. Golden tests show that the serializers (`Message.toApiArray` with attachments, thread summaries, channel, category, pref, pin and mention arrays) and every realtime event (name, channel and payload) match PHP output captured from Wavepath. Route-level parity of `/_wave` is checked at the Wavepath port.
4. The new code has unit tests, delivered in the phase's last plan. 4. The new code has unit tests, delivered in the phase's last plan.
**Plans:** 5/7 plans executed **Plans:** 6/7 plans executed
Plans: Plans:
**Wave 1** **Wave 1**
@@ -1215,7 +1215,7 @@ Plans:
- [x] 19-05-PLAN.md — Unit tests LAST: goldens, PHP method map (59 chat + 106 chatchannels), scripts/check-phase19.sh - [x] 19-05-PLAN.md — Unit tests LAST: goldens, PHP method map (59 chat + 106 chatchannels), scripts/check-phase19.sh
**Wave 5** *(gap closure)* **Wave 5** *(gap closure)*
- [ ] 19-06-PLAN.md — Participant-only canSend (CR-01) and created_by reassignment on user.Deleting (CR-02) - [x] 19-06-PLAN.md — Participant-only canSend (CR-01) and created_by reassignment on user.Deleting (CR-02)
**Wave 6** *(gap closure, tests last)* **Wave 6** *(gap closure, tests last)*
- [ ] 19-07-PLAN.md — TestTeammateDMWriteIDOR, TestDeletingCascade creator path, check-phase19.sh names - [ ] 19-07-PLAN.md — TestTeammateDMWriteIDOR, TestDeletingCascade creator path, check-phase19.sh names

View File

@@ -0,0 +1,166 @@
---
phase: 19-chat-and-chatchannels
plan: 06
subsystem: chat
tags: [chat, chatchannels, canSend, GDPR, user.Deleting, D-08, D-13, SC-19-1]
requires:
- phase: 19-02
provides: ChatService SendMessage, GetOrCreateDm, GetGroupChat
- phase: 19-04
provides: user.Deleting, FireDeletingEvent, ChannelService.WithDB analog
provides:
- "CR-01 / D-08 / SC-19-1: canSend is participant-only; teammate DM write is ErrNotParticipant"
- "CR-02 / D-13: chat-owned ReassignCreatedBy on user.Deleting so remaining members keep the conversation"
- "TestGapClosureSmoke (postgres, skipped under -short)"
affects: [19-07, Wavepath port]
actuals:
tokens: 3037
tasks: 3
commits: 1
plan_head_before: 29d06bc70e0e1e4f0d24dcb72a3dd304863994cb
plan_head_after: pending-summary-commit
tech-stack:
added: []
patterns:
- "canSend returns s.isParticipant after nil/zero-id guards; host ChatEnabled stays on GetGroupChat / GetOrCreateDm"
- "Service.WithDB clones like ChannelService.WithDB so the deleting event Tx is the session"
- "Chat owns user.Deleting reassignment so DMs survive when chatchannels is not mounted"
key-files:
created:
- ../sm-chat-plugin/gap_closure_test.go
modified:
- ../sm-chat-plugin/classes/service.go
- ../sm-chat-plugin/boot.go
- ../sm-chat-plugin/README.md
- ../sm-chatchannels-plugin/README.md
- .planning/ROADMAP.md
key-decisions:
- "Chat owns ReassignCreatedBy; chatchannels SeverUserFromContext is unchanged"
- "canSend is participation-only; ChatEnabled is not folded into canSend"
- "GetGroupChat still ignores addParticipant errors (IN-01 stays open)"
- "TestGapClosureSmoke is not added to check-phase19.sh (19-07)"
patterns-established:
- "Gap-closure smoke lives in the plugin repo and is skipped under -short via TestMain"
- "GDPR created_by reassignment is chat-owned so DMs and channel-backed groups both survive"
requirements-completed: [SC-19-1, D-08, D-13]
coverage:
- id: D1
description: "canSend is participant-only; a third teammate cannot SendMessage into a two-person DM (CR-01 / D-08 / SC-19-1)"
requirement: SC-19-1
verification:
- kind: integration
ref: "../sm-chat-plugin/gap_closure_test.go#TestGapClosureSmoke"
status: pass
human_judgment: false
- id: D2
description: "user.Deleting reassigns created_by to another remaining participant so the DM row survives GDPR delete (CR-02 / D-13)"
requirement: D-13
verification:
- kind: integration
ref: "../sm-chat-plugin/gap_closure_test.go#TestGapClosureSmoke"
status: pass
human_judgment: false
- id: D3
description: "READMEs document participant-only send and created_by reassignment; WR-01..WR-04 and IN-01..IN-02 stay open"
requirement: D-08
verification:
- kind: other
ref: "grep ReassignCreatedBy/participant/created_by in plugin READMEs"
status: pass
human_judgment: false
duration: 4min
completed: 2026-10-17
status: complete
---
# Phase 19 Plan 06: Chat/ChatChannels Gap Closure Summary
**Participant-only canSend and chat-owned created_by reassignment on user.Deleting so DMs and shared channels survive GDPR delete**
## Performance
- **Duration:** 4 min
- **Started:** 2026-10-17T20:28:09Z
- **Completed:** 2026-10-17T20:32:00Z
- **Tasks:** 3
- **Files modified:** 5
## Accomplishments
- `canSend` now returns only `s.isParticipant` after nil/zero-id guards. A teammate who is not a DM participant gets `ErrNotParticipant` (HTTP 404 mapping unchanged).
- `Service.WithDB` and `Service.ReassignCreatedBy` land; Boot Listens `user.Deleting` and reassigns `created_by` to another remaining participant before the user row is deleted. The IF NOT EXISTS baseline CASCADE is untouched.
- `TestGapClosureSmoke` proves both post-fix behaviours on one postgres path (Carol refused on Alice/Bob DM; Alice send succeeds; Carol GetGroupChat lazy-join then send succeeds; after Alice delete the DM remains with `created_by` = Bob).
## Task Commits
Each task was committed atomically in the plugin repos; this SUMMARY is the summercms.go metadata commit.
1. **Task 1 + 2 + 3 (sm-chat-plugin):** `0aee785` (feat) — participant-only send, ReassignCreatedBy, TestGapClosureSmoke, README
2. **Task 2 + 3 (sm-chatchannels-plugin):** `7d49c3c` (docs) — GDPR bullet that chat reassigns created_by
3. **Plan metadata (summercms.go):** this commit (docs)
_Plugin SHAs are local only. No remotes were pushed. fonoteka submodule pointers were not bumped._
## Files Created/Modified
- `../sm-chat-plugin/classes/service.go` — participant-only `canSend`; `WithDB`; `ReassignCreatedBy`
- `../sm-chat-plugin/boot.go` — `listeners` step; `Listen[*userclasses.DeletingEvent]`
- `../sm-chat-plugin/gap_closure_test.go` — `TestGapClosureSmoke`
- `../sm-chat-plugin/README.md` — send-rights, GetGroupChat lazy-join, user.Deleting / ReassignCreatedBy
- `../sm-chatchannels-plugin/README.md` — remaining members keep the channel because chat reassigns created_by
## Decisions Made
- Chat owns `user.Deleting` reassignment so DMs survive even when chatchannels is not mounted. chatchannels `SeverUserFromContext` still only removes that user's members/prefs/participants/personal categories.
- `canSend` is participation-only. Host `ChatEnabled` stays on `GetGroupChat` and `GetOrCreateDm`.
- `GetGroupChat` keeps `_ = addParticipant(...)` (IN-01 stays open).
- Named postgres tests `TestTeammateDMWriteIDOR` / `TestDeletingCascade` creator-delete assertions and `scripts/check-phase19.sh` list updates wait for 19-07.
## Deviations from Plan
None - plan executed exactly as written.
**Total deviations:** 0 auto-fixed
**Impact on plan:** None.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Verification Results
- `go -C ../sm-chat-plugin vet ./...` — PASS
- `go -C ../sm-chat-plugin test -count=1 -short ./...` — PASS (Task 1 and Task 2)
- `go -C ../sm-chatchannels-plugin test -count=1 -short ./...` — PASS
- `go -C ../sm-chat-plugin test -count=1 -run '^TestGapClosureSmoke$' -v .` — PASS (`--- PASS: TestGapClosureSmoke (0.72s)`)
- Baseline `golem15_chat_conversations_created_by_foreign` / `ON DELETE CASCADE` unchanged
- WR-01..WR-04 and IN-01..IN-02 confirmed unread: GetGroupChat still ignores addParticipant errors; download Content-Disposition unchanged; JoinChannel / PinMessage unchanged; FormAfterDelete remains the only production `FireDeletingEvent` site
- No remotes pushed (`sm-chat-plugin` and `sm-chatchannels-plugin` are `ahead 1`)
## Next Phase Readiness
Ready for 19-07: `TestTeammateDMWriteIDOR`, `TestDeletingCascade` creator-delete assertions, and `scripts/check-phase19.sh` required-name updates. Production CR-01 and CR-02 are closed.
---
*Phase: 19-chat-and-chatchannels*
*Completed: 2026-10-17*
## Self-Check: PASSED
- FOUND: `.planning/phases/19-chat-and-chatchannels/19-06-SUMMARY.md`
- FOUND: `../sm-chat-plugin/gap_closure_test.go`
- FOUND: `../sm-chat-plugin` commit `0aee785`
- FOUND: `../sm-chatchannels-plugin` commit `7d49c3c`