Found by: the motir run MOTIR-3808 parent run, 2026-08-28, while executing child MOTIR-3813 (the to-do store) on branch parent/MOTIR-3808-work-item-todo-list, base origin/main @ ae490d52e.
Sibling instance in the same story: MOTIR-3820. Two cards of one seven-card plan asserted a rung-2 fact that shipped code contradicts. That recurrence is why this is a second card rather than a comment on the first.
MOTIR-3813's body instructs, under Service — where the two contended writes are locked:
"Side effects run OUTSIDE the transaction. The revision entry for a structural change (add / edit / delete — per the ADR, NOT for a tick) is written after the DB write commits; a failure there is logged and degrades, and must not roll back or fail the to-do write."
That is not implementable against the shipped API, and it inverts a documented contract.
lib/services/workItemRevisionsService.ts takes a REQUIRED Prisma.TransactionClient, and its own header says the parameter IS the contract:
"
recordRevisionto persist ONE audit row describing the mutation, INSIDE the same transaction as the mutation itself. The requiredtxparameter is the contract that enforces this: a revision commits atomically with the mutation it describes, or neither does. … The atomicity integration test (tests/integration/work-items/revisions.test.ts) exercises exactly this rollback."
Every call site agrees — git grep -n "recordRevision(" -- 'lib/**' returns ~20 hits across attachmentsService, backlogService, commentsService, componentsService, customFieldValuesService, estimationService, labelsService and plansService, and every one is inside a $transaction. There is no post-commit form to call.
motir-core/CLAUDE.md's side-effects-outside-the-transaction rule is about NOTIFICATIONS — sendEvent, email, anything with an outside observer. commentsService.addComment is the worked example: the work-item/comment.created event fires after the transaction commits, commented "Post-commit, never inside the tx — a rollback must not have notified."
The card generalised that onto a revision row, and the two cases are opposite:
| write | must not… | because |
|---|---|---|
| a notification | fire for a write that ROLLED BACK | somebody outside the system already believes it happened |
| an audit row | SURVIVE a write that rolled back | the trail would describe a change that never landed |
So "side effect" was doing the work of a category, and the two members of that category want opposite placements. A run following the card literally would have had to widen recordRevision's signature or skip the revision entirely.
The revision is written inside the transaction, per the shipped contract. lib/services/workItemTodosService.ts carries the divergence and its reasoning in a header block so the next reader of the card is not left to re-derive it, and MOTIR-3813 is amended on the record.
lib/services/workItemTodosService.ts writes every structural revision inside the same transaction as the write it describes, through workItemRevisionsService.recordRevision. (Landed in this run — closing this is a read.)