feat(enclave): graduated member trust + posting gate (LC-551) #521

Merged
longjacksonle merged 4 commits from feat/LC-551-trust-levels into main 2026-07-07 20:41:40 +02:00

LC-551: Graduated member trust (Round 4, Vein C - community-scale governance)

LC-551 bundles three ideas (trust levels, a join-approval queue, and an onboarding checklist). This PR ships the foundational, behavior-gating piece - graduated member trust - and flags the other two as follow-ups (see below).

What ships

  • Trust levels. Enclave members carry a trust level: new or trusted. A member joins a community enclave (via invite code / discover) as new and graduates to trusted automatically after posting enough (GRADUATE_AFTER_POSTS) in that enclave. Owners and admins are always effectively trusted.
  • Posting-rate gate. A new member (not owner/admin) is held to a minimum interval between posts (NEW_MEMBER_COOLDOWN_SECS, a NewMemberPost cooldown), which blunts drive-by spam from a freshly-joined account without throttling established members. DMs and trusted members pay nothing. Graduation runs right after a successful send and re-renders the settings member list live so the pill updates.
  • Manager UI. The enclave settings member list shows a "New" pill on ungraduated members and lets a manager Trust a member (lifting the cooldown) or reset them to new, via POST /enclave/{id}/members/{user_id}/trust.

Design notes

  • The general enclave (everyone's default home) is treated as trusted, not a gated community: the migration marks all pre-existing members trusted, and backfill_general_membership inserts general members as trusted. The new level is earned when joining a community enclave. (This also means the always-on cooldown never touches the town square.)
  • trust is kept OFF the widely-used EnclaveMembership struct / get_membership SELECT surface (a drift trap); it is read via dedicated helpers (is_new_member, trust_map) instead.

Tests

Integration routes_trust_levels.rs: the new-member rapid second post is refused over HTTP while the owner is exempt; graduation flips exactly once at the threshold; manual set_trust flips both ways.

Deferred (remaining LC-551 scope, follow-up PRs)

  • Join-approval queue. An optional per-enclave "approve new members" gate: post_join_by_code would enqueue a pending request instead of adding the member, with an admin approve/decline panel reusing the invitation-accept UI. The trust column + member-management plumbing here is the natural base for it.
  • First-run onboarding checklist. A separate first-run UI surface; independent of this change.

Both are called out so the issue's full scope is tracked; this PR is the coherent, self-contained core.

Gates: just check, full just test, just test-saas all green. (Note: adding an always-on new-member cooldown surfaced two anti-spam / slowmode tests that rapidly post as a member; the fix was semantic - established/general members are trusted - so those tests pass unmodified.)

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz

## LC-551: Graduated member trust (Round 4, Vein C - community-scale governance) LC-551 bundles three ideas (trust levels, a join-approval queue, and an onboarding checklist). This PR ships the **foundational, behavior-gating piece - graduated member trust** - and flags the other two as follow-ups (see below). ### What ships - **Trust levels.** Enclave members carry a `trust` level: `new` or `trusted`. A member joins a community enclave (via invite code / discover) as `new` and graduates to `trusted` automatically after posting enough (`GRADUATE_AFTER_POSTS`) in that enclave. Owners and admins are always effectively trusted. - **Posting-rate gate.** A `new` member (not owner/admin) is held to a minimum interval between posts (`NEW_MEMBER_COOLDOWN_SECS`, a `NewMemberPost` cooldown), which blunts drive-by spam from a freshly-joined account without throttling established members. DMs and trusted members pay nothing. Graduation runs right after a successful send and re-renders the settings member list live so the pill updates. - **Manager UI.** The enclave settings member list shows a "New" pill on ungraduated members and lets a manager Trust a member (lifting the cooldown) or reset them to new, via `POST /enclave/{id}/members/{user_id}/trust`. ### Design notes - The general enclave (everyone's default home) is treated as trusted, not a gated community: the migration marks all pre-existing members trusted, and `backfill_general_membership` inserts general members as trusted. The `new` level is earned when joining a *community* enclave. (This also means the always-on cooldown never touches the town square.) - `trust` is kept OFF the widely-used `EnclaveMembership` struct / `get_membership` SELECT surface (a drift trap); it is read via dedicated helpers (`is_new_member`, `trust_map`) instead. ### Tests Integration `routes_trust_levels.rs`: the new-member rapid second post is refused over HTTP while the owner is exempt; graduation flips exactly once at the threshold; manual `set_trust` flips both ways. ### Deferred (remaining LC-551 scope, follow-up PRs) - **Join-approval queue.** An optional per-enclave "approve new members" gate: `post_join_by_code` would enqueue a pending request instead of adding the member, with an admin approve/decline panel reusing the invitation-accept UI. The trust column + member-management plumbing here is the natural base for it. - **First-run onboarding checklist.** A separate first-run UI surface; independent of this change. Both are called out so the issue's full scope is tracked; this PR is the coherent, self-contained core. Gates: `just check`, full `just test`, `just test-saas` all green. (Note: adding an always-on new-member cooldown surfaced two anti-spam / slowmode tests that rapidly post as a member; the fix was semantic - established/general members are trusted - so those tests pass unmodified.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
Add a `trust` column ('new' | 'trusted', default 'new') to enclave_members. Existing members are marked trusted by the migration (they predate the feature), and backfill_general_membership now inserts the default-home general enclave's members as trusted too - the "new" level is meant for community enclaves you join, not the town square. Add is_new_member / set_trust / trust_map / count_enclave_messages_by_user and maybe_graduate (promotes a new member to trusted once they have posted GRADUATE_AFTER_POSTS messages in the enclave). Owners and admins are never "new".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
A "new" (ungraduated) enclave member who is not owner/admin is held to a minimum interval between posts (NewMemberPost cooldown, NEW_MEMBER_COOLDOWN_SECS), blunting drive-by spam from a freshly-joined account without throttling established members or DMs. After a successful send the poster is graduated to trusted once they have posted enough, and the settings member list is re-rendered live so the trust pill updates. The same boolean drives both the gate and the graduation check, so trusted members and DMs pay nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
Surface member trust in the enclave settings member list: a "New" pill on ungraduated members, and (for managers) a button to Trust a new member or reset a member to new, via POST /enclave/{id}/members/{user_id}/trust. resolve_member_views resolves trust in one batch lookup so both the settings page and the live OOB fragment render it. i18n in en and es.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
test(routes): graduated member trust coverage (LC-551)
All checks were successful
check-secrets / TruffleHog (push) Successful in 4s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / Kingfisher (push) Successful in 7s
check-secrets / Nosey parker (pull_request) Successful in 8s
check-secrets / TruffleHog (pull_request) Successful in 8s
check-secrets / Kingfisher (pull_request) Successful in 10s
Check / clippy + fmt + tests (pull_request) Successful in 7m39s
Create release / Create release from merged PR (pull_request) Has been skipped
bb017eed3f
Cover the new-member posting cooldown over HTTP (a new member's rapid second post is refused; the owner is exempt) and the graduation / manual-trust db logic (a new member becomes trusted after posting enough; set_trust flips both ways).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
longjacksonle deleted branch feat/LC-551-trust-levels 2026-07-07 20:41:40 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!521
No description provided.