Interop driver: report the session version a responder settled on
`session_respond` returned no version at all, so the cross-engine test could not assert the one thing that mattered — that a session this device ANSWERS agrees with the initiator about which protocol it runs. `session_initiate` and `session_encrypt` report it too, so a version that changes across a store-and-reopen is visible rather than inferred. Sealed groups: file the message instead of dropping it in silence. `groupRoster` asked the relay for the membership of a group the relay cannot know — §8.9b exists so it holds opaque tags and no member list — so the sender was never "in the roster" and EVERY message in EVERY sealed group was discarded on arrival by the check meant to protect it. The sealed roster is the authoritative list and the only one that exists. A refusal now says which group and which sender. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ee17d773b7
commit
2402f7a665
@@ -762,6 +762,13 @@ class Session(
|
||||
if (gid != null) {
|
||||
val known = groupRoster(gid)
|
||||
if (known == null || message.fromUser !in known) {
|
||||
// ⚠️ Refusing is correct — but refusing in silence is what
|
||||
// made a sealed group look like a group nobody could talk
|
||||
// in. Say which group and which sender, never the message.
|
||||
System.err.println(
|
||||
"fnmsg-recv: NOT FILED into group ${gid.take(8)} — " +
|
||||
"sender ${message.fromUser} is not in the roster " +
|
||||
"(roster ${known?.size ?: -1} member(s))")
|
||||
continue
|
||||
}
|
||||
// ⚠️ Every group envelope carries the group's name, and this is
|
||||
@@ -1244,6 +1251,16 @@ class Session(
|
||||
@Volatile var groupNamesUpdated: Boolean = false
|
||||
|
||||
private fun groupRoster(gid: String): Set<Long>? {
|
||||
// 🔴 **A SEALED group's roster is not the relay's to give.** §8.9b
|
||||
// exists so the relay holds opaque tags and no membership at all, so
|
||||
// `listGroups()` returns an EMPTY member list for one — and the caller
|
||||
// reads that as "the sender is not a member" and drops the message.
|
||||
// Every message in every sealed group was discarded on arrival, in
|
||||
// silence, by the check meant to protect it. The sealed roster is the
|
||||
// authoritative list here and the only one that exists.
|
||||
if (vault.groupSecrets(gid) != null) {
|
||||
return sealedGroups().firstOrNull { it.id == gid }?.memberIds?.toSet()
|
||||
}
|
||||
val now = System.currentTimeMillis()
|
||||
val cached = groupRosterCache
|
||||
if (cached != null && now - cached.first < 30_000) return cached.second[gid]
|
||||
|
||||
Reference in New Issue
Block a user