Skip to content

M8-11 Thread, message, pin and file items of the import interface

No Date Ruling Reason
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.