Nordhavn / specs / convene / core / Converse

convene / corecontext

Converse

Purpose

The conversation — speaking into it and reading it. Sending composes the writing into a Message on the member's device — its identity, its author, its signature, and the ids of the tips the device has seen as its parents — and then, for each member of the epoch, the sender included, seals a fresh copy under the current key and drops it into that member's own inbox. The sender's own copy is not an echo but the record: a reading is computed from a member's own inbox and nothing else, so what the sender said belongs in their inbox exactly as it belongs in everyone else's — the same rule the membership ceremonies follow with their sealed records. There is no shared feed a sender and a recipient both touch, and nothing outside the seal says who sent. A member whose ring does not hold the current epoch's key cannot send — a removed member's words have nowhere to go — and the refusal is their own device declining; no server ever judged it. A message identity is minted once: a correction fans the same identity again with the new writing, never a rewrite, and because every copy is sealed afresh, no two recipients' copies match byte for byte.

Reading is the same decoupling from the other side. A member reads exactly one feed — their own inbox — so their reading reveals nothing about who sent to them or to whom anyone else sent. The copies that open under a key the ring holds for one of the group's epochs are folded through the timeline — deduplicated by message id, ordered by the ancestry inside the messages, a parent that never arrived standing as a missing entry, the fold's "before your time". A copy that opens under no such key is noise on a public dead-drop and is dropped without ceremony — there is no store to trust, so nothing to raise a tampering alarm about; order rides inside the seal, where no carrier can rewrite it.

Role Players

Props

Role Methods

ring

relay

Functionalities

send

Given the message id MessageId, the parents MessageIds, and the writing Writing, takes the current epoch's key from the ring — the epoch the group's current revision names — composes the message — that id, the member as author, the member's signature, those parents, that writing — and for each member of the current revision, the sender among them, seals a fresh copy under the epoch key and fans it to that member's inbox through the relay — the sender's own inbox gains their record like every other. It yields the posted EncryptedMessages, one per member of the epoch. It fails when the ring does not hold the current epoch's key, and when the group holds no revisions — words need an epoch to travel under. Result: the posted EncryptedMessages, one per member of the epoch; fails when the ring does not hold the current epoch's key or the group holds no revisions

read

Fetches the member's own inbox from the relay, keeps the EncryptedMessages there that open under a key the ring holds for one of the group's epochs, and yields the member's readable TimelineEntries through Timeline — the fold that deduplicates by message id, orders parents before followers with ties by message id, and stands a named parent that never arrived as a missing entry. A copy that opens under no such key is dropped, not presented and not alarmed — noise, not evidence. Result: the member's readable TimelineEntries, in the fold's order

Scenarios

Anne sends to the others' inboxes, not a shared feed

Given: a Converse with member anne.bruun, a relay, the Conversation "conv-7421", a group whose current revision "g-7421.3" has members anne.bruun, sofie.lind, and karin.dahl under key "k-0163", and anne's opened ring holding the GroupKey "k-0163" When: send is performed with the MessageId "m-8001", no parents, and a Writing of "The hearing moved to Thursday." Then: a sealed copy is dropped into sofie.lind's inbox, another into karin.dahl's, and one into anne.bruun's own — hers is the record too — and onto no feed the three of them share; each copy carries "anne.bruun" nowhere readable and no two copies' payloads match

A removed member cannot send

Given: a Converse with member karin.dahl, a relay, the Conversation "conv-7421", a group whose current revision "g-7421.4" has members anne.bruun and sofie.lind under key "k-0264", and karin's opened ring holding only the GroupKey "k-0163" When: send is performed with the MessageId "m-8004", no parents, and a Writing of "Am I still in this case?" Then: the sending fails because her ring does not hold the current epoch's key, and nothing is dropped into any inbox

A correction fans the same identity to each inbox

Given: a Converse with member anne.bruun, a relay, the Conversation "conv-7421", a group whose current revision "g-7421.3" has members anne.bruun, sofie.lind, and karin.dahl under key "k-0163", and anne's opened ring holding "k-0163" When: send is performed with the MessageId "m-8001" — the identity of her first message — a parent MessageId "m-8002", and a Writing of "The hearing moved to Friday." Then: a fresh copy carrying id "m-8001" again is dropped into sofie.lind's, karin.dahl's, and anne.bruun's own inboxes — the same identity, a new writing, delivered per member

Sofie reads her own inbox, in the fold's order

Given: a Converse with member sofie.lind, a relay whose feed for sofie.lind's inbox holds two copies fetched follower first — the follower sealing Message "m-8003" by sofie.lind carrying "I'll bring the file." naming parent "m-8001", the opener sealing Message "m-8001" by anne.bruun carrying "The hearing moved to Thursday." with no parents — both under "k-0163", the Conversation "conv-7421", a group holding the revision "g-7421.3" under key "k-0163", and sofie's opened ring holding "k-0163" When: read is performed Then: two readable entries are yielded, "The hearing moved to Thursday." then "I'll bring the file." — the order came from inside the messages, and she fetched only her own feed

Karin's inbox holds nothing from after her removal

Given: a Converse with member karin.dahl, a relay whose feed for karin.dahl's inbox holds one copy sealing Message "m-8001" carrying "The hearing moved to Thursday." under "k-0163" and nothing sealed under "k-0264", the Conversation "conv-7421", a group holding the revision "g-7421.3" under key "k-0163", and karin's opened ring holding only "k-0163" When: read is performed Then: one readable entry is yielded, carrying "The hearing moved to Thursday." — the later epoch was never dropped into her inbox, so it is an absence, not a sealed placeholder

A parent before her time stands as awaited

Given: a Converse with member susan.holm, a relay whose feed for susan.holm's inbox holds a copy sealing Message "m-8010" that names a parent "m-8005" no copy in her inbox carries, under "k-0231", the Conversation "conv-7421", a group holding the revision "g-7421.5" under key "k-0231", and susan's opened ring holding only "k-0231" When: read is performed Then: the timeline is a missing entry awaiting "m-8005" followed by the readable entry — the parent is before her time, and the hole is visible without being an accusation

Another conversation's copies stay out of this reading

Given: a Converse with member sofie.lind, a relay whose feed for sofie.lind's inbox holds two copies — one sealing Message "m-8001" by anne.bruun carrying "The hearing moved to Thursday." under "k-0163", and one sealing Message "m-9101" by karin.dahl carrying "Second case, second room." under "k-0301" — the Conversation "conv-7421", a group holding the revision "g-7421.3" under key "k-0163", and sofie's opened ring holding the GroupKeys "k-0163" and "k-0301" When: read is performed Then: one readable entry is yielded, carrying "The hearing moved to Thursday." — "k-0301" is another conversation's epoch, and a copy the ring could open but this group's epochs do not name is not this reading's to show

An empty inbox is an empty reading

Given: a Converse with member sofie.lind, a relay whose feed for sofie.lind's inbox holds nothing, the Conversation "conv-7421", a group holding the revision "g-7421.3" under key "k-0163", and sofie's opened ring holding "k-0163" When: read is performed Then: no entries are yielded