A signed event files its chat line unless the batch holds more than one event of
its kind, in which case the session's completion line speaks for them.
Needed by the previous commit. A wave of fifty dialects would otherwise post
fifty "Added X to dialects" lines into the group's conversation, one after
another, followed by a line saying the group signed fifty events -- a single act
burying the conversation it was announced in.
Every kind that could already appear many-per-batch returns null from
applyInnerEvent for precisely this reason: a chunk is part of a chapter and the
chapter's line speaks for it, and likewise a translation chapter, a contributor
list and both project memberships. DialectEvent is the first kind where a batch
holds many peers each of which wants a line. So checked against every batch the
app builds today, this changes nothing: the leading kind of each is one of its
kind and keeps its line.
It is not a rule about batch size, which is the mistake available here. A chapter
and its sixty-four chunks is one ChapterEvent, and gating on the batch being
larger than one event would have taken the chapter's line away.
Decided at the call site because that is the only place that can see the batch.
applyInnerEvent is untouched -- it still writes the row, which is the part that
matters -- and only the chatMessageDao upsert is skipped. ChronicleManager needed
nothing: it already drops the line a payload returns, and says why.
Three tests on SignedGroupKeyStateTest, which runs real two-device sessions: one
dialect still files its line, three file none and still land three rows, and a
dialect with the artifact that names it files both. The first two are the same
mechanism asserted at 1 and at 0, so neither can pass vacuously.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>