mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-11 20:27:39 +00:00
fable review fixes
This commit is contained in:
1 parent
ee8d9263a6
commit
b4ad40ab24
1 file changed
+32
-15
@@ -62,12 +62,18 @@ collection of signatures and a deterministic local tally.
|
||||
A group can opt in to **governed mode**. Enabling converts all owners to admins; a governed group has no owners, and
|
||||
governed clients reject any event that would create or promote one. Governance parameters are fixed at enabling.
|
||||
|
||||
**The admin set then changes only by referendum.** No admin may promote a member to admin, demote an admin, or remove
|
||||
one. This is the core of the design, and it also closes a regression the design would otherwise introduce. The
|
||||
existing receiver check permits acting on a member of *equal* role (`senderRole < memberRole` is strict), so unilateral
|
||||
removal already reaches peers; what bounds it today is that only owners can touch owners, and most groups have one or
|
||||
two. Abolishing owners without this rule would widen that power from the owner set to every admin, leaving a rogue
|
||||
admin able to remove all its colleagues and then the membership.
|
||||
**No member becomes an admin except by certificate.** That covers four verbs, not three: no admin may promote a member
|
||||
to admin, demote an admin, remove an admin, or **admit a new member at admin role or above**. The last is the one an
|
||||
enumeration naturally misses, and it is not theoretical: `checkHostRole` is `memberRole < GRAdmin || memberRole <
|
||||
memRole` (`Subscriber.hs:3578-3580`), both comparisons strict, so an equal-role admission passes today and an admin
|
||||
can introduce accounts already carrying `GRAdmin` through `xGrpMemNew`, `xGrpMemIntro` or `xGrpMemFwd`. A governed
|
||||
client therefore caps any admitted or assigned role at `GRModerator`. Tier 2 does not close this, because
|
||||
enfranchisement gates voting and `E` rather than the role, so packed admins would keep forwarding, introduction and
|
||||
removal powers in a tier-2 group. This is the core of the design, and it also closes a regression the design would
|
||||
otherwise introduce. The existing receiver check permits acting on a member of *equal* role (`senderRole < memberRole`
|
||||
is strict), so unilateral removal already reaches peers; what bounds it today is that only owners can touch owners,
|
||||
and most groups have one or two. Abolishing owners without this rule would widen that power from the owner set to
|
||||
every admin, leaving a rogue admin able to remove all its colleagues and then the membership.
|
||||
|
||||
Any member may propose a new admin set. Members vote aye or nay with Ed25519 signatures over the proposal hash, sent
|
||||
over their direct connections. Anyone can assemble the votes into a **certificate**, a self-authenticating proof that
|
||||
@@ -331,7 +337,8 @@ bytes per vote, groups beyond ~120 members need the chunked-blob transport alrea
|
||||
4. Demotion and blocking never affect voting, since eligibility depends only on membership, and given the exemptions
|
||||
above they do not affect delivery either.
|
||||
5. **Removal deferral.** In a governed group, `XGrpMemDel` for a member does not tear down that member's connections
|
||||
for `maxReferendumDays`, and `x.grp.gov.*` continues to flow over them in both directions. Deferral is armed by the
|
||||
for `2 × maxReferendumDays`, and `x.grp.gov.*` continues to flow over them in both directions. Deferral is armed by
|
||||
the
|
||||
group being governed, *not* by holding a proposal, since arming on an unresolved proposal is defeated by ordering:
|
||||
purge first and the victims lose their connections before any proposal exists, so they can neither receive it nor
|
||||
vote, while still counting in `E`. Deferral lasts `2 × maxReferendumDays`, because a member removed at `t` is within
|
||||
@@ -402,7 +409,7 @@ applies or finally rejects a certificate, further votes for that proposal are ig
|
||||
On receiving `x.grp.gov.cert`, a member:
|
||||
|
||||
1. fetches the referenced proposal if it lacks it, and validates it: signature, `prevProposalHash`, and an action
|
||||
naming only electorate members;
|
||||
naming only members the receiver recorded at `firstSeenAt`;
|
||||
2. verifies every vote signature and discards annulled votes;
|
||||
3. checks `govVersion` **greater than** the stored version. A stale version is ignored as replay, except that a
|
||||
same-version certificate for a *different* proposal supersedes the applied one if it ranks higher in **mandate
|
||||
@@ -413,8 +420,11 @@ On receiving `x.grp.gov.cert`, a member:
|
||||
4. for a version gap greater than one, requires a **witness chain**: one bundle per intervening version whose
|
||||
certificate passes the tally on its own vote set, each linked by `prevProposalHash`. This is a cheap check and is
|
||||
not load-bearing; what binds is step 6;
|
||||
5. waits until `ripeAt`, then, unless the certificate is unconditional, runs the challenge window and re-evaluates
|
||||
over the union;
|
||||
5. waits until `ripeAt`, then runs the challenge window and re-evaluates over the union, unless the certificate is
|
||||
unconditional **and** its action is `GAReplaceAdmins`. `GARemoveMembers` and `GAEnfranchise` are required to be
|
||||
unconditional by step 7, so exempting unconditional certificates in general would mean the two irreversible actions
|
||||
never open a window at all; the window is where annulment evidence arrives, and at `2A = E + 1` one annulment flips
|
||||
the tally;
|
||||
6. if the certificate is stale, meaning for a version the receiver has already passed, or arrived by catch-up,
|
||||
requires **attestations**: `min(3, |non-aye members|)` distinct members, outside the certificate's aye set, must
|
||||
have signed `smpEncode ("SXGS", groupIdentity, governanceId, govVersion, proposalHash)` for *that proposal*, and a
|
||||
@@ -437,7 +447,12 @@ On receiving `x.grp.gov.cert`, a member:
|
||||
a display-only hint that peers verify by fetching the certificate. It exists because a same-version disagreement
|
||||
produces no version gap and so no catch-up trigger, and without it two halves of a group would never compare.
|
||||
|
||||
Because the action names a complete set rather than a delta, application is idempotent and order-independent. Clients
|
||||
`GAReplaceAdmins` names a complete set rather than a delta, so applying it is idempotent and order-independent.
|
||||
`GARemoveMembers` is neither: it is a delta with no inverse, so a receiver that applied a removal before a
|
||||
higher-ranked same-version `GAReplaceAdmins` superseded it keeps the members removed, while a receiver that saw them in
|
||||
the other order never removes them, and no action exists to reconcile. **An applied removal is therefore final and is
|
||||
not undone by supersede**, and clients state it that way rather than claiming order-independence they do not have.
|
||||
Clients
|
||||
should surface a "contested result" state while two same-version certificates are in play, and new admins should avoid
|
||||
destructive actions until their certificate's window has closed unchallenged.
|
||||
|
||||
@@ -611,7 +626,8 @@ anyone proposes is not bounded by anything here.
|
||||
since membership is admin-asserted; hold the key of any member whose introduction it MITMed, and annul that member's
|
||||
vote by signing a conflicting one. Both are visible to members but not prevented, and both are what tier 2 closes.
|
||||
|
||||
*cannot:* change the admin set outside a referendum, so it cannot remove its colleagues, pack the set, or entrench
|
||||
*cannot:* change the admin set outside a referendum, so it cannot remove its colleagues, pack the set by promotion or
|
||||
by admitting new members at admin role, or entrench
|
||||
itself; veto a referendum by procedure, since no admin signature appears in propose, vote or apply and candidacy is
|
||||
judged as of each receiver's first sight of the proposal; censor one, since governance events are forwardable by any
|
||||
member; silence a voter by demotion or blocking, or by removal within the recency period; rush a result, since speed
|
||||
@@ -679,7 +695,7 @@ authored by admins, so a colluding admin reaches any majority it likes.
|
||||
chosen subset permanently out of step at the cost of one identity and no admin powers. Retaining both signatures
|
||||
makes the equivocation provable, which is attribution rather than prevention.
|
||||
- **Deferral hands a removed member a channel the status quo does not.** Removals do not sever connections for
|
||||
`maxReferendumDays`, and governance events over them are exempt from every moderation lever, so a harasser removed
|
||||
`2 × maxReferendumDays`, and governance events over them are exempt from every moderation lever, so a harasser removed
|
||||
today keeps a live queue to a targeted member for weeks and can fill it against the 128-message quota, where today
|
||||
that member is disconnected in seconds. This is a capability the feature creates, and the clearest respect in which
|
||||
enabling governance is worse than not.
|
||||
@@ -731,8 +747,9 @@ authored by admins, so a colluding admin reaches any majority it likes.
|
||||
(checks 1 to 4); proposal validation including `prevProposalHash` on every path; ripeness from a persisted
|
||||
per-proposal `first_seen_at`, with `E` as a per-proposal running maximum; the challenge-window worker with late
|
||||
voting; the apply procedure above; catch-up serving with per-requester bounds; forwarder and `blockedByAdmin`
|
||||
exemptions; removal deferral; certificate removals applied without the recency allowance; **rejection of any admin-set
|
||||
change outside a certificate**, and of owner-role members in `xGrpMemNew`/`xGrpMemIntro`/`xGrpMemFwd`/`xGrpMemRole`.
|
||||
exemptions; removal deferral; certificate removals applied without the recency allowance; **capping any admitted or
|
||||
assigned role at `GRModerator`**; **rejection of any admin-set change outside a certificate**, and of owner-role
|
||||
members in `xGrpMemNew`/`xGrpMemIntro`/`xGrpMemFwd`/`xGrpMemRole`.
|
||||
- `Commands.hs`: relax the `GROwner` assertions for governed groups, not only the one in `runUpdateGroupProfile`.
|
||||
The sweep is `Commands.hs:1192` (root key), `1338-1341` (`APIDeleteChat`), `1911`, `2718` (`APIAddGroupRelays`),
|
||||
`4020` (`runUpdateGroupProfile`), `4088`, and `checkSendAsGroup` (`Subscriber.hs:1065-1067`). Channel-only sites are
|
||||
|
||||
Reference in new issue
Block a user