feat: offer a new member the group's work alongside their welcome

Phase 6 of docs/member-archive.md, and deliberately the phase after the one that
makes it unnecessary. `deliveryWelcome` now queues an archive for the member
being invited, so in the ordinary case they have the group's signed work before
they think to ask for it.

**This is a latency optimisation, not the mechanism.** A page queued behind the
Welcome is not delivered after it: they are different transports -- a relay-borne
gift wrap and a kind:445 -- with no ordering between them, and a page that
overtakes the Welcome is from an epoch ahead of the invitee's, so
`MarmotInboundManager` drops it outright rather than deferring it. Nothing
retries and the inviter sees a success. That is the failure in
docs/marmot-membership.md wearing new clothes, and the only thing that closes it
is the invitee asking once they are demonstrably in the group, which Phase 5
already does on their first open of the room.

So nothing here reports failure to the inviter. A push that does not land is the
ordinary case the pull exists for, and it sits inside `deliveryWelcome`'s own
catch alongside the Welcome it rides behind. A room with nothing signed queues
nothing and still invites.

**One call, two occasions.** `ArchiveManager.answer` becomes `sendTo`: answering
a request and pushing behind a Welcome are the same operation and differ only in
who decided, so it is named for what it does rather than for either occasion.

Also corrects the plan. Phase 6 claimed the room had to be re-read between the
invite and the assembly, for the same reason sequential invites re-read it. It
does not -- that rule is about the MLS snapshot a commit is built on, and this
runs downstream of the commit over `Mantra*` rows, which no commit touches.

Two tests against a real `deliveryWelcome`: inviting into a room with signed work
queues exactly one archive page addressed to the invitee, and inviting into a
room with none queues no page while still writing the Welcome's gift wrap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kgothatso Ngako
2026-09-06 14:29:11 +02:00
parent 873203e4b4
commit 6d8866c2b0
5 changed files with 159 additions and 28 deletions

View File

@@ -502,18 +502,27 @@ written as one.
`DatabaseNostrRepository`. Assemble an archive for the invitee there and queue
its pages behind the Welcome.
Two things to be honest about at that call site, in a comment:
One thing to be honest about at that call site, in a comment: **queued behind the
Welcome is not delivered after it.** They are different transports -- a
relay-borne gift wrap and a kind:445 -- and a page that arrives before the
invitee has processed their Welcome is from an epoch ahead of theirs, so it is
dropped outright rather than deferred. The request is what recovers that, and
this push is worth having only because it usually wins.
- **Queued behind the Welcome is not delivered after it.** They are different
transports -- a relay-borne gift wrap and a kind:445 -- and pages that arrive
first are dropped for good. The request is what recovers that, and this push is
worth having only because it usually wins.
- **The room must be re-read between the invite and the assembly**, for the same
reason sequential invites re-read it: a snapshot taken before the commit
describes an epoch the group has left.
The first draft of this section also said the room must be re-read between the
invite and the assembly, for the same reason sequential invites re-read it. It
does not: that rule is about the MLS snapshot a commit is built on, and
`deliveryWelcome` is downstream of the commit and reads `Mantra*` rows, which no
commit touches.
Nothing here is allowed to report failure to the inviter. A push that does not
land is not an error; it is the ordinary case the pull exists for.
land is not an error; it is the ordinary case the pull exists for. It sits inside
`deliveryWelcome`'s own catch for that reason.
**One call, two occasions.** Answering a request and pushing behind a Welcome are
the same operation and differ only in who decided, so they are one function named
for what it does -- `ArchiveManager.sendTo` -- rather than two named for their
occasions.
---