Blocking somebody now cancels their sealed passes too

§21.4 gave a lever to cut off one contact and nothing pulled it. That is
the failure this project has been fixing all day in other places — a
capability built and left unreachable.

Blocking is where it belongs, and the reason is stronger than
convenience. The relay CANNOT enforce a block against a sealed sender:
the request carries no sender to compare against a block list, and the
send route's own comment says so. So blocking stopped the identified
path and nothing else, and a blocked person's sealed messages were
dropped by the app after decryption rather than refused at the door.
Revoking their capabilities is what closes that.

The copy changes with it, because "buddies you block can't send you
messages" was not true and is the kind of overclaim a safety screen must
not make. It now says what blocking does, and names the gap left: the
shared key everybody holds is not that person's alone, so it takes
replacing to close completely — with the cost of that stated in the same
breath rather than in a different screen.

The sealed-sender section changes for the same reason. It said replacing
the key is "the only way" to cut somebody off, which was true when it
was written and is not now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-08-30 19:36:59 -04:00
co-authored by Claude Opus 4.8
parent 79c2734f86
commit 8061f54e96
3 changed files with 54 additions and 2 deletions
@@ -271,6 +271,37 @@ class DeliveryTokenTest {
assertEquals(first, vault.issuedTokens(42).toSet())
}
/**
* 🔴 **This is the act the whole section exists to make possible.** The
* relay cannot enforce a block against a sealed sender — the request
* carries no sender to compare against a block list — so before §21.4,
* blocking stopped the identified path and nothing else, and a blocked
* person's sealed messages were dropped by the app after decryption rather
* than refused at the door.
*
* The view model calls this alongside the block; asserted at this layer
* because it is where the behaviour lives.
*/
@Test
fun `revoking is what stops a sealed sender the relay cannot identify`() {
val c = withDeliveryKey()
c.send(toUserId = 42, text = "hello")
assertEquals(64, held.size)
c.revokeDeliveryTokens(forUserId = 42)
// Every capability that person held is gone from the relay, so their
// next sealed send is refused with no sender ever named.
assertEquals(0, held.size)
assertTrue(vault.issuedTokens(42).isEmpty())
}
/** Revoking a contact who never held any is not an error. */
@Test
fun `revoking somebody with no batch does nothing and says so`() {
assertEquals(0, withDeliveryKey().revokeDeliveryTokens(forUserId = 99))
}
// MARK: - Receiving a batch
@Test