Files
amethyst/commons
Claude c6e1bc4e5a fix(marmot): merge the founding Add locally instead of publishing it
`protocol-core/publish-lifecycle.md`: "When founding creation includes initial
invitees, the creator next prepares and locally merges one founding Add Commit
from epoch 0 to epoch 1. That Commit also has an empty group-message
publication obligation: the creator is the only pre-existing member, so no peer
can be forked by failure to publish it." And: "The empty-obligation exception
is limited to the epoch-0 creation and, when applicable, its immediately
following founding Add Commit."

We implemented the first half and missed the second. createGroup already
satisfied an empty obligation for epoch 0, but the Add that follows went down
the ordinary commitAndPublish path, which applies a commit only once a relay
acknowledges it. So creating a group with initial members against an
unreachable relay silently produced an empty epoch-0 group — the members were
never added and the Welcomes were never sent — where the spec makes the Add
canonical immediately and each Welcome an independent retryable delivery that
"does not affect canonical group state".

addMemberInvites now detects the founding case (epoch 0, creator the sole
member), merges the Add locally, and returns a null commit event with the
Welcomes. Nothing is published, which also stops spending a signature and an
outer encryption on bytes with no audience, and stops leaving a kind:445 on
relays that a joiner can receive before its Welcome — the reference calls that
a "welcome-before-commit AlreadyAtEpoch bounce" and drops the commit for the
same reason.

The commit event is nullable rather than absent so every call site had to be
looked at: the CLI reports founding_local_merge and publishes nothing, and the
Android action logs the case instead of dereferencing.

Test fallout was all one shape — suites that used create + addMember as SETUP
were testing the exception rather than the rule. MarmotPublishBeforeApplyTest
and MarmotPublishDurabilityTest now get past the founding add first, and gain
direct coverage that a founding add merges even when the publisher rejects
everything, and that the very next commit is ordinary.

publish-fail/v1 is refused rather than passed. It fails the FOUNDING creation's
outbound and expects epoch 0 with one member, which is the legacy lifecycle:
MDK resolves the profile from application_profile and `None | Some("legacy")`
means legacy, where create_group returns GroupCreated { pending }. Our old
behaviour happened to match that. Weakening the fix to keep the vector green
would reinstate the bug, so the runner refuses it by name via
LegacyOnlyScenario and the test asserts the refusal. invite-publish-fail/v1
still runs in full: a rejected later invite is an ordinary commit either way.

Also corrects this file's own README claim that create_group/N costs "one more
commit" than MDK. Both do exactly one; only the publishing differed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq
2026-09-10 15:03:38 +00:00
..