Summary
Optional group mode: new joiners must complete a DM handshake with Bread Bot before they may stay in the group. Unverified joiners are kicked. Verification is stored on the live CVM (encrypted prefs volume), so it survives in-place upgrades and applies across groups that enable the mode.
This is the only Signal-shaped slice of the broader gated-entry request in #40. It is a bouncer, not a restricted role and not proof-of-humanity.
Why this shape
Signal groups have two roles: member and admin. There is no mute, no pending-in-group role, and no hold channel. Send permission is group-wide (everyone, or announcement-only). So we cannot “let them in but block posting” until they pass a check.
What Signal can do, if the bot is a group admin:
- Kick (
remove_members — already used for Language Thread !leave)
- DM a challenge and accept a text reply
- Persist a verified-identity set on
group-prefs-translation
Native group link + require admin approval is still the stronger gate (they never see the chat). This issue is for groups that want to keep the invite link open and bounce strangers automatically.
Proposed behavior
Happy path (preferred):
- User DMs the bot and completes the handshake before joining.
- Group description / kick copy points at that: “DM Bread Bot, then join.”
- They join an
!only-registered group; the bot sees a known UUID and does nothing.
Cold join:
- A group admin enables the mode (command name TBD; must not collide with TEE
!verify).
- Current members are grandfathered. Only identities that appear after the flag flips are gated.
- Bot detects a new member (poll
GET /v1/groups and diff members; signal-cli has no reliable join event).
- If unverified: DM them while they are still in the group (shared group helps the message request), then kick.
- Post one short group line, rate-limited per identity (e.g. once per hour):
<name> is unregistered — DM the bot, then rejoin.
- Do not paste phone numbers into the group. Prefer profile name; fall back to a generic “A new member”.
- On a correct DM reply, persist the Signal UUID (phone as a secondary alias only).
- They rejoin via the same open link, or the bot
add_members them back after unban if we used ban.
Sidecars: do not run the bouncer on Language Thread groups. The bot adds those people from main.
Design decisions (defaults)
| Decision |
Default |
Why |
| Who can enable/disable |
Signal group admins only |
Anyone with the command could lock or empty the group |
| Existing members when enabled |
Grandfather |
Mass-kick would empty the chat |
| Identity key |
UUID, plus phone alias via existing identities_match |
Many envelopes have no E.164 |
| Storage |
Global verified set in existing encrypted prefs (group_preferences_store) |
Same CVM volume we must not wipe; not a new disk |
| Join detection |
Poll + member diff (invite acceptor already polls every 5s) |
signal-cli does not emit a clean “member joined” event |
| DM vs kick order |
DM first, then kick |
After kick there is no shared group; the DM looks like spam |
| Challenge |
Rotating text prompt in DM |
Signal has no buttons / CAPTCHA UI |
| Fail / ignore |
Stay kicked; they can retry the DM |
No per-member mute, no timeout-mute |
| Kick/rejoin spam |
Rate-limit the group announcement; consider ban-until-pass then unban + re-add |
Open link means they can rejoin immediately |
| Command names |
Not !verify |
!verify is TEE attestation |
| Product claim |
“Completed a DM handshake with Bread Bot” |
A scripted signal-cli client can pass a text question |
Open (decide in plan, not here):
- Exact command names (
!register / !only-registered vs something else).
- Ban-until-pass vs kick-only (ban/unban is not wrapped in the client today).
- Whether a passed handshake auto-re-adds them, or they must tap the invite link again.
- Whether the prompt is global or configurable per group.
Caveats (do not paper over)
- Race: poll/diff means a few seconds of full access. They can post before the kick.
- Bot must be a group admin. On the main Breadchain group it may only be a member; the command should refuse unless
Group.admins includes the bot. Sidecars the bot created are fine.
- Message requests: if they have never chatted with the bot, Signal shows a message request. Links are not tappable until they accept. Enough “report as spam” hits can rate-limit or burn the bot phone.
- Not anti-bot. Text (or even an image-captcha attachment) only proves they can complete a 1:1. Do not design a pluggable
GateCheck framework for CAPTCHA buttons, wallet signatures, or per-invite-link rules — Signal cannot host those.
- Scope: this is moderation, not translation/transcription. Treat it as an explicit product expansion.
- CVM: in-place deploy only. Do not wipe
group-prefs-translation / signal-config-translation.
Out of scope
- Restricted-in-group state, mute, hold channel / “limited thread”
- Telegram-style CAPTCHA buttons, inline keyboards,
/ command picker
- Per-invite-link rules (Signal has one group link)
- Wallet / signature verification
- Pluggable onboarding-gate framework
- Running the bouncer on Language Thread sidecars
- Reusing or overloading
!verify
Origin
Split from #40 (Josh / @BreadrichEngels). #40 remains the place for the broader “gated entry like Telegram” request and the platform limits.
Summary
Optional group mode: new joiners must complete a DM handshake with Bread Bot before they may stay in the group. Unverified joiners are kicked. Verification is stored on the live CVM (encrypted prefs volume), so it survives in-place upgrades and applies across groups that enable the mode.
This is the only Signal-shaped slice of the broader gated-entry request in #40. It is a bouncer, not a restricted role and not proof-of-humanity.
Why this shape
Signal groups have two roles: member and admin. There is no mute, no pending-in-group role, and no hold channel. Send permission is group-wide (everyone, or announcement-only). So we cannot “let them in but block posting” until they pass a check.
What Signal can do, if the bot is a group admin:
remove_members— already used for Language Thread!leave)group-prefs-translationNative group link + require admin approval is still the stronger gate (they never see the chat). This issue is for groups that want to keep the invite link open and bounce strangers automatically.
Proposed behavior
Happy path (preferred):
!only-registeredgroup; the bot sees a known UUID and does nothing.Cold join:
!verify).GET /v1/groupsand diffmembers; signal-cli has no reliable join event).<name> is unregistered — DM the bot, then rejoin.add_membersthem back after unban if we used ban.Sidecars: do not run the bouncer on Language Thread groups. The bot adds those people from main.
Design decisions (defaults)
identities_matchgroup_preferences_store)!verify!verifyis TEE attestationOpen (decide in plan, not here):
!register/!only-registeredvs something else).Caveats (do not paper over)
Group.adminsincludes the bot. Sidecars the bot created are fine.GateCheckframework for CAPTCHA buttons, wallet signatures, or per-invite-link rules — Signal cannot host those.group-prefs-translation/signal-config-translation.Out of scope
/command picker!verifyOrigin
Split from #40 (Josh / @BreadrichEngels). #40 remains the place for the broader “gated entry like Telegram” request and the platform limits.