| D-M2-19-0 |
2026-10-22 |
Built on the old integration line on top of M2-09 and ported to main after M2-09 landed: only the ticket's own commits were brought over; the additions to the German chapter files of the old base go to their English files on main, and the changelog entry and these rulings are fragments. |
The rules of the landing replace the integration branches; main is the reference. |
| D-M2-19-1 |
2026-10-21 |
The check of run_id is a first branch of the runs subscriber of messaging.MessagePosted (Acceptor.OnMessagePosted, app/answer.go) that vetoes the unit of work, not a new source port; MessagePosted carries RunID. |
messaging may not import runs (02 4.4), runs already subscribes MessagePosted (02 4.3), and a subscriber may veto (02 4.1 point 4, 8.3). |
| D-M2-19-2 |
2026-10-21 |
A refused run_id answers 400 validation_failed on /run_id with errors[].code run_mismatch and one detail for a person or app, an unknown run, a run of another thread and one of another agent; an ended own run answers 409 conflict with reason run_not_active. Ownership and thread are decided before the state. |
run_id cannot probe runs outside the authorized thread (05 rule 1.2); run_not_active is the reason M2-10 uses for patches of an ended run. |
| D-M2-19-3 |
2026-10-21 |
No authorize call for the run named in run_id. |
A run's path is its thread (05 2.2), on which message.create was authorized; runs outside the thread answer like unknown ones. |
| D-M2-19-4 |
2026-10-21 |
createThread refuses first_message.run_id for every principal with 400 validation_failed on /first_message/run_id and run_mismatch, in the body rules before the unit of work. |
A thread created by the request has no run. |
| D-M2-19-5 |
2026-10-21 |
run_id is validated, carried in MessagePosted and never stored; it is not part of the request hash of client_msg_id. Spec gap: 04 5.1 point 3 does not say whether run_id is part of the canonical request; the core contract is not edited here. |
messages.run_id names only the live box anchor (03 check (kind = 'run') = (run_id IS NOT NULL)); a replay must work after the run ended (09 15), and a refused answer must be sendable again without run_id. |
| D-M2-19-6 |
2026-10-21 |
The answer check takes the run row FOR SHARE (LockRunForAnswer) after messaging holds the thread row; the ends of a run update it FOR UPDATE. A deadlock with a path that locks run then thread is retried by the unit of work (database.tx_retry_max). |
Thread then run, like the acceptance of M2-05: every answer stored with run_id commits while the run is active, before its end event (04 10). |
| D-M2-19-7 |
2026-10-21 |
Names and tests that follow the code: the body rules live in messaging/domain (CheckRunID, CheckFirstMessageRunID) and are called from app.prepare and app.CreateThread; the runs integration world of M2-09 (test/integration/runs) builds the brief's world, and it cannot subscribe a test handler (the txn registry needs a handler of an internal package), so the observation of MessagePosted.RunID is in messaging/adapters/httpapi (TestCreateMessage_RunIDCarriedNotStored); delivery in the integration tests uses runs.Delivery like the M2-09 tests instead of pollAgentEvents; the guest run of Texter is triggered by the owner, because a mention resolves only for an author who may read the agent (agent.read, 07 4.6) and Alex reads no channel Texter operates. |
The brief allows following merged names (D-W-100); each test still proves its rule. |