diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 1cd9c66f..0a1e84e6 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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. 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: **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 **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)* - [ ] 19-07-PLAN.md — TestTeammateDMWriteIDOR, TestDeletingCascade creator path, check-phase19.sh names diff --git a/.planning/phases/19-chat-and-chatchannels/19-06-SUMMARY.md b/.planning/phases/19-chat-and-chatchannels/19-06-SUMMARY.md new file mode 100644 index 00000000..b3a5370a --- /dev/null +++ b/.planning/phases/19-chat-and-chatchannels/19-06-SUMMARY.md @@ -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`