| D-M8-11-1 |
2026-10-23 |
A batch with thread_origin_key concerns one thread: thread:<digits> (a thread or forum post) or channel:<digits> (the main thread of a posted text channel; a forum is 400 validation_failed at /thread_origin_key with the code no_main_thread). Only the first item may be the thread item, and only with the key of a thread: batch; every other item is a message or a pin, anything else 400 validation_failed at /items/<i>/item_kind (invalid), and a content item in a batch without thread_origin_key is refused the same way. A thread that is neither posted nor created by the batch, or that is skipped while the batch has further items, is 409 conflict thread_not_imported. Message items are appended in request order, never sorted. |
Design note 1; the tool sends ascending Discord IDs (22 6.2). |
| D-M8-11-2 |
2026-10-23 |
A new thread item creates the thread through chat.ImportThreads.Create: the channel of the posted channel item (a skipped channel skips the thread, a missing one is target_outside_source), the name cut to thread.title_max_chars at a code point and checked like a thread title, title_source = auto when the placeholder rule of M2-18 matches the cut name, else manual; operator_agent_id from planned_operator resolved through identity.ImportPeople (none is NULL, an agent that no longer exists target_outside_source); created_at, last_activity_at, closed with closed_at (required exactly when closed) from the data; parent_message_id only when the starter message item is posted, lies in the main thread of the same channel and started no thread yet; the posted forum_tag items of the same channel become thread_tags, at most thread.max_tags of them (further tags are dropped; a tag key of another channel is 400); creator the importing owner, search language of the instance; no thread.created. A posted thread item applies domain.MirrorChange (title only while auto, it stays auto; close with closed_at or reopen with closed_at and closed_by cleared; last_activity_at only forward) through ImportThreads.Lock and ApplyMirror, without thread.reopened. 22 7.2 says so in one sentence. |
Design note 3, 22 5.1.1, 12.2 step 3, R-IM-08, R-IM-11. |
| D-M8-11-3 |
2026-10-23 |
A message item needs the posted user item of author_origin_key (else 409 conflict author_not_imported); reply_to_origin_key must name a posted message item of the same thread (400 validation_failed with the code not_in_thread at the pointer otherwise); every attachment_file_ids entry must be the target of a posted file item of the source (400 validation_failed with the code not_imported at its pointer), and files checks channel, author and state as for a normal attachment (409 conflict file_not_attachable), at most message.max_attachments (limit_reached). messaging.ImportMessages.Append inserts with origin = import, kind = text, silent, no mentions, no trigger_state, a server client_msg_id, the request hash over the context import:<item id>, import_item_id, created_at and edited_at of the source without a revision, the instance search language (14 3.1), and the numbers of chat.ImportThreads.Reserve, which takes the row lock, never reopens and moves last_activity_at only forward; it publishes no MessagePosted and appends no message.created, so neither runs nor notify sees the message. content_md above message.max_content_bytes is 400 validation_failed with the code too_long (never split, never 413, so the item index is known). A pin item pins the message of a posted message item of the thread with the importing owner, under pin.max_per_thread, without pin.added; its target is the message (target_kind = message). |
Design note 4, 22 6.2, 6.4, 6.6, 05 5 rule 1. |
| D-M8-11-4 |
2026-10-23 |
The end of a thread batch calls chat.ImportThreads.FinishBatch once: when a thread, message, pin or mirrored change was written it sets unread_floor_seq = last_message_seq, raises the thread version once for the batch and appends one thread.updated; the counters and import.progress follow (also for a batch whose only change is a mirrored thread). A batch that only answered posted items writes no event. ImportBatchResult.unread_floor_seq carries the floor after every thread batch whose thread exists. |
Design note 5, 04 15.8. |
| D-M8-11-5 |
2026-10-23 |
createImportFile (import.apply on the running source) needs a channel that is the target of a posted channel item of the source (409 conflict target_outside_source, recorded as a denial) and an author that is the target of a posted user item (409 conflict author_not_imported); ImportFileCreate gains the required member sha256 for declared_sha256. A new key writes the item pending with target_kind = file and the file and session of files.ImportFiles.Reserve (no quota; size, name, denied types, open sessions and free space hold). A repeated pending key answers the same file ID: its open session, a session opened again (state, expiry, parts and multipart id reset, file pending) after an abort or expiry, or the item posted when the session completed meanwhile; a posted key answers without upload. uploadImportFilePart and completeImportFile (new, import.apply, rl.import) check the running source and that the file is the target of a file item of the source (404 otherwise), then use the part and completion rules of uploadPart and completeUpload without file.upload; the completion posts the item in its unit of work, so no subscriber of files is needed. The parts of the session name uploadImportFilePart. |
Design note 6, 22 6.5, 7.2 item 3, 05 rule 1.8 and 4.12. |
| D-M8-11-6 |
2026-10-23 |
Instead of a queue files_bulk, the scan and thumbnail jobs of an import file stand on the queue files with the lowest River priority: platform/jobs.InsertOptions gains Priority (PriorityHighest 1 for every other job, PriorityLowest 4), the completion of an import file marks its context with files/app.WithBulkJobs, and the job arguments carry bulk = 1 (an int64, because job arguments hold only IDs and numbers), which the scan handler puts back on its context, so retries and thumbnails inherit it. No new queue and no configuration key; 12 11 and 02 12.2 say so. |
Design note 6 c. |
| D-M8-11-7 |
2026-10-23 |
The ports are chat.ImportThreads (Create, MainThread, Lock, ApplyMirror, Reserve, FinishBatch; implemented by chat/app.ImportThreadsPort because Service.Create is the one of chat.Threads), messaging.ImportMessages (Append, ThreadOf, Pin; messaging/app.ImportMessagesPort) and files.ImportFiles (Reserve, Slot, UploadPart, Complete; files/app.ImportFilesPort), all inside the caller's unit of work but UploadPart. FinishBatch takes changed and returns the floor instead of a separate read; Reserve is the method that hands out the numbers, which messaging calls; Lock returns the stored thread for the mirror rule of importer/domain. internal/wire binds the files port lazily (LazyImportFiles), because the files step runs after the importer. |
Design note 7; the names of the brief may differ (D-W-100), the behaviour does not. |