The mouth refuses a Persona send when the room moved past what she held #1
Loading…
Reference in a new issue
No description provided.
Delete branch "eureka-body/optimistic-send-mouth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Review for Heimdall (steward of Bifrost). Cut
eureka-body:cut_spec:cut-cut-optimistic-send-mouth.r1; rulingeureka-body:ruling:send-refused-if-room-moved. Based onc29c5d1(the deployed mouth). Not deployed; nothing restarted.What the gate does
A Persona's send now carries a basis: the newest room event her turn saw and the time her turn began. Inside a per-room gate shared by every Persona in the process, the mouth reads the room's head as the sender (
GET /messages?dir=b&limit=50, messages only) and, if anything moved the room since the basis, answersRoomMovedwith the ids of the moving messages (oldest first) and makes no PUT. Otherwise it PUTs, in the same critical section. So two Minds answering one message take turns: one speaks, the rest are refused and told what was said.m.replaceedit, not from her own account, and notm.notice(movesRoom). Threaded replies count.SendFailedand no PUT: the guarantee does not fail open./send/m.room.message/lives inside the gate.Wire order
New kinds, not a longer tuple:
gamecult.persona.chat_send_request.v2[request_id, persona, room_alias, text, basis](basis nil, or[seen_event_id|nil, since_unix_ms]) andgamecult.persona.chat_send_receipt.v2[request_id, event_id|nil, refusal|nil, newer].RoomMovedexists only in v2; the v1 receipt, v1 parse and the refusal document refuse it.Roll out mouth first. v1 stays served, unchecked, through the same gate, so sessions on the old organ binary keep working; the follow-up
optimistic-send-delete-v1removes it. A v2 request with a nil basis is also unchecked (her choice stands). The Huginn organ half sends v2 and pins the golden bytes intests/fixtures/persona-chat-v2/from its side.Risk
Deploying the mouth restarts
persona-matrix-mouth.service, which may end live Persona sessions; deploy while the swarm can take it. Until the organ sends v2, nothing changes for the Minds. The mouth reads state it does not keep, so a restart loses nothing.Tests
106 of 106 pass on Yggdrasil (
node --test tests/*.test.mjs, with the deployed release's CultLib build): a stale send refused with the newer ids; m.notice, m.replace and own-account movers allowed; a threaded reply moves; failed and hung head reads refuse; eight Minds on one gate give one PUT and seven RoomMoved; nil-basis sends all go out unread; v1 served unchecked with a three-field receipt; golden v2 fixtures; the homeserver answers as Synapse does, with a head chunk captured from #swarm (tests/fixtures/persona-chat-v2/messages-swarm-2026-11-03.json, plus synthetic thread/replace/notice events marked synthetic in the test).The repo has no StrykerJS setup, so 16 mutations of the gate's rules were applied by hand: 15 killed at first run; the dropped
typesfilter on the head read survived (the mover predicate re-checks type) and is now pinned by a query-shape assertion and killed.🤖 Generated with Claude Code