| M1-62/0 |
2026-10-15 |
Base int/m1-wave25 at 719cfaa, branch feat/M1-62-chat-thread-move in its own worktree; the branch is not merged locally and not pushed, the integration lane ports it. The brief, spec and contract were read from origin/main; the base holds every rule the ticket needs (03 threads, thread_tags, "I1 check", "Lock order", 05 thread.move and the events thread.moved and thread.moved_out, 04 "Basic rule", 07 4.2, the OpenAPI operation and schemas are the same apart from translation), so no contract commit was brought in. make check runs with SONAR_SKIP=1 as the launcher sets it. |
Instructions of the launcher, which override the brief's merge step. |
| M1-62/1 |
2026-10-15 |
thread.moved_out is appended before thread.moved, both in the transaction of the move with consecutive seq. |
A client that receives both removes the thread from the source first and then shows it in the target, so it ends with the thread in the target (brief note 1g). |
| M1-62/2 |
2026-10-15 |
A thread without operator moves only into a channel without effective operator; a thread with operator only into a channel whose effective operator is that agent (IS NOT DISTINCT FROM). |
03 threads: the target's effective operator must equal operator_agent_id, and NULL equals only NULL; otherwise the move would give an operated thread to a channel run by nobody or another agent, against R-DM-12 and I1. |
| M1-62/3 |
2026-10-15 |
The source as target is 400 validation_failed on /target_channel_id with code same_channel; an archived target (or a target in an archived space) is 409 conflict. The domain checks run after authorize in the order main thread, same channel, archived target, dm channel, operator; the reason check (CheckReason) follows them. |
The same channel is a malformed request, not a state conflict; an archived channel accepts no management change (07 4.5). The order is brief note 1b. |
| M1-62/4 |
2026-10-15 |
The domain rule is CanMove(th Thread, source, target MovePlace, targetOperator ids.ID) error with the zero ID for "no operator", not the signature CanMove(ThreadState, ChannelState, ChannelState, *uuid) of the brief. |
domain.ThreadState already names the thread state (open, closed, M1-55); the existing types Thread and the zero ids.ID express the same facts without a second thread type or a pointer. |
| M1-62/5 |
2026-10-15 |
After the row lock the use case compares the locked thread's channel with the one it authorized; a concurrent move that committed in between makes it authorize thread.move again on the new place before any check or write. |
07 4.2 and brief note 4: no decision may use the old location. The read before the lock is needed so an unknown and an invisible thread give the same 404 before any lock. |
| M1-62/6 |
2026-10-15 |
sync keeps one subscriber object: syncapp.NewSubscribers(drafts, moves) takes the new port ThreadMoveRows next to DraftRows; messaging gains messagingapp.Subscribers with ThreadMoveRows, and wire.Modules.MessagingSubscribers is required. The registrations are chat.ThreadMoved -> messaging and then -> sync. |
Brief scope; the subscriber of MessagePosted keeps its draft-only port (M1-61 note 5). |
| M1-62/7 |
2026-10-15 |
Test code only: the cross-module world of test/integration registers the three subscriptions of internal/wire, binds the subscriber of sync, serves the personal handlers of sync and inserts a devices row for every session, because read states and drafts name the writing device (foreign key). |
The move tests need read states and drafts through the real HTTP chain in the world with Support, Allgemein, Eskalation and Marketing of 07 sections 10 and 11.1. |
| M1-62/8 |
2026-10-15 |
authz treats a zero TargetChannelID of thread.move as an unknown, invisible target (not_found), no longer as an internal error. |
Red team: the nil UUID passes the request validator as target_channel_id; the internal error answered 500 for an existing thread and 404 for an unknown one, an existence oracle for invisible threads against R-AZ-01 and brief note 1a. |
| M1-62/9 |
2026-10-18 |
Port onto main: code and tests as in the ticket range (719cfaa..014e5ac) with the M1-62 side of INT-W28/1, except the points named here. M3-18 is not on main, so the ticket keeps its own ShareChannel and ShareSpace (query, app.Store method, postgres method and fake) and INT-W28/2 is not ported. M3-13 is not on main and threads has no search columns, so MoveThread returns the whole threads row and the adapter converts it with toThread directly instead of through threadRow. The cross-module world of test/integration on main built neither messaging nor the thread and message handlers (they came with M3-13), so it now builds the messaging module as internal/wire does and serves the thread, message and personal handlers; the authorizer has no Hosts (M2-39 is not on main). The txn registry subscribes messaging.MessagePosted -> sync and then chat.ThreadMoved -> messaging -> sync; the notify subscribers of M3-18 are not on main, so txn_registrations.golden gains only the two ThreadMoved lines. Catch-up (M1-63) delivers thread.moved and thread.moved_out through authorize.ResourceFromEvent like every other thread event and needs no change. Generated files are regenerated, not merged. |
main holds M1-55 to M1-61 and M1-63 but not M2-39, M3-13 or M3-18; the ticket keeps its behaviour. |
| M1-62/10 |
2026-10-20 |
Port onto main after M1-63 and M3-18: M3-18 brought the queries ShareChannel (channels.sql) and ShareSpace (spaces.sql) with the same SQL, the same app.Store methods and the same Postgres methods that this ticket had added for thread.move, so the copies of this ticket are removed and the move uses those of M3-18, as INT-W28/2 did on the integration line; the fake keeps one ShareChannel and ShareSpace (with the fault hooks of the move tests). The txn registry subscribes chat.ThreadMoved to messaging, then to sync, after messaging.MessagePosted -> sync and before the two archive subscriptions of notify, the order of the integration line. Unit tests over the fakes cover the failure paths of the move and the sync subscriber of ThreadMoved. M1-62/9 describes the earlier base; where it differs, this row wins. |
The PR lane ports only the ticket and the integration fixes it needs; no behaviour of either ticket changes. |