Skip to content

M1-55 Threads

No Date Ruling Reason
M1-55/0 2026-10-14 Base int/m1-wave16 at 1e260ad, branch feat/M1-55-chat-threads in its own worktree; the branch is not merged locally, the integration lane ports it. The migration takes the next free number of the base, 00028. Text this ticket adds is English. The methods of chat.Threads take the caller as a actor.Actor like chat.Structure (M1-52/5, M1-53/0); the brief's signatures leave it out. Instructions of the launcher, which override the brief's merge step and language; one convention for every chat port.
M1-55/1 2026-10-14 listChannelThreads (D6): a tag_id that is no tag of the channel, unknown or deleted, gives an empty page, not an error. The list authorizes thread.read on the channel as container (a thread resource without an id and with the channel as path hint), then each row in filter mode, in batches of the page size plus one. The cursor is the keyset (sort time, id) bound to caller, channel, state, tag and sort; another caller, filter or sort gives 400 cursor_invalid. The codec is httpserver.EncodeCursor behind the port app.Cursors, as in agents. The brief's ruling D6: an error for an unknown tag would let a caller probe the tags of a forum.
M1-55/2 2026-10-14 thread.placeholder_titles defaults to ["Neuer Chat", "New chat", "Chat", "Ohne Titel", "Untitled", "Test"] without the empty entry of 09; chapter 09 says so. The empty title is a placeholder through the rule itself, and a configured empty entry still matches only the empty title. A list element of the configuration is never empty (platform/config); 09 section 12 point 2 already makes an empty title auto.
M1-55/3 2026-10-14 setThreadTitle: a person sets the source manual; an agent or app keeps the source and gets 409 title_manual on a manual title, not only the operator agent. Who may call at all stays with authorize (write and creator, or manage, or the operator agent). 09 section 12 point 1 and brief requirement 12: a manual title is never overwritten automatically; agents and apps are the automatic writers.
M1-55/4 2026-10-14 D8 finding in authz: Path.ThreadOperator fell back to the channel's effective operator for a thread of kind thread without a fixed operator, so a later channel operator counted as the operator of such a thread (thread.title.update, agent.invoke). It now returns the fixed operator, zero for none; main threads and channels keep the channel's operator. The level rule is unchanged: the channel operator keeps write on every thread of the channel through the channel. One fixture of TestAgentInvoke_MentionRule gets the fixed operator its thread would have (R-DM-12). 09 section 3.1 and R-DM-12: the operator of a thread is threads.operator_agent_id, fixed at creation; "Without an operator no agent answers".
M1-55/5 2026-10-14 Thread actions are not audited (05 column Audit "no"); a call without effect appends no event. setThreadTags also sends ETag, which openapi.yaml declares. AppendToThread sets last_activity_at from the server clock instead of now(), so the tests control the order. Close, reopen, title and tags lock the thread row after authorize; AppendToThread locks first and authorizes thread.reopen only for an implicit reopen. 05 section 4.5; the contract of setThreadTags; the same value inside one transaction.
M1-55/6 2026-10-14 Test first held for the domain rules, the migration and the authz fix (red runs in the report); the HTTP and port tests over PostgreSQL were written after the use cases and checked against mutations of the tag deletion and of the operator fallback. Honest record of the order of work.
M1-55/7 2026-10-15 Port onto main: code and tests are taken byte for byte from the integration squash and the integration fixes INT-W17/1 to INT-W17/5, which resolve the merge with M1-54 (on main before this ticket): the M1-55 form of Thread in the store and the source, one Deps.Policies, the generator split of the REST package into models.gen.go, objects.gen.go and rest.gen.go, and the person test helper without its handle parameter. The integration base also holds M2-39 and M2-40 (terminal), which are not on main, so nothing of them is ported: the host route rows of api/routes/real_test.go, the host and terminal tables of migrations/privileges_test.go and the terminal.* keys of the configuration tests stay out. The migration is renumbered to follow 00026_grants_channel_fk_validate.sql without a gap: 00028_chat_thread_tags.sql becomes 00027_chat_thread_tags.sql, with the number in the changelog and the module README; TestChatThreadTagsMigration_Down migrates down to version 26, and the checksum is recorded. The comments of the migration, queries/threads.sql, queries/thread_tags.sql, the package comment of the chat repository and wire/configkeys_chat.go name the English chapter files and headings (03-data-model.md, 09-agents.md, 03 principle 12, "Configuration values of this chapter", "application"), and the new keys of wire/config_test.go name the chapters 03-data-model.md and 09-agents.md. The enabled operations of api/operations.yaml are sorted into the list of main and their later entries removed; the rule thread. of tools/configdoc/planned.yaml goes. The changelog entry, the API README, the module README (status line, code list and the new section on threads), the key row of chapter 09 and these rulings are written into the English text at the same places; the quote of 09 in M1-55/4 is the English sentence of 09 section 3.1. Generated files are regenerated with the generators of the ported tree, not merged. main is English and holds M1-54 but not M2-39 or M2-40; the migration numbers of main must follow without a gap.