Skip to content

Group ownership transfer (CGS 0.6.0) - #250

Merged
holkexyz merged 4 commits into
mainfrom
staging
Sep 18, 2026
Merged

holkexyz merged 4 commits into
mainfrom
staging

Conversation

@holkexyz

@holkexyz holkexyz commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Promotes the group ownership-transfer work from staging.

Blocked: production CGS must be on 0.6.0 first

Deployment CGS Version (checked 2026-09-17)
staging.certified.app dev.groups.certified.app 0.6.0+5a9b38b4
certified.app groups.certified.app 0.5.0+4b002463

app.certified.group.ownershipTransfer.* landed in CGS 0.6.0. On 0.5.0 those four NSIDs answer 501 MethodNotImplemented, so merging this now would put a Transfer ownership page in front of every group owner on production that fails the moment it loads.

There is deliberately no runtime capability detection and no feature flag — this merge is the gate. Re-check before merging:

curl -s https://groups.certified.app/health

Merge only once that reports 0.6.0 or newer.

What's included

Four commits, all of it this one feature:

The owner proposes an existing member; ownership moves only when that member accepts, which proves they still control their DID. Either party can cancel, and an un-accepted proposal lapses after 7 days. The page sits in group settings between "Members & Roles" and "Activity Log".

  • New BFF route src/app/api/groups/[groupDid]/ownership-transfer/route.ts — GET status, POST propose, PUT accept, DELETE cancel. repo on the querystring for the query, in the body for the procedures, per the CGS fix: don't force OAuth consent screen on every email login #27 targeting rule. Propose validates the DID and is rate-limited 5/10min per caller; CSRF checked on every mutation.
  • Client seam + types in src/lib/groups/{api,types}.ts.
  • Settings UI in src/components/groups/org-settings.tsx, with transferViewerRole, eligibleTransferTargets and transferPendingNote as exported pure helpers. No new CSS.

Behaviours worth a careful look

  1. Non-parties never see the page. CGS returns pending: false to a member who isn't a party, identical to "nothing pending", and NoPendingTransfer for accept/cancel. The UI derives visibility from that alone and never tries to distinguish the two — the difference is not observable by design.
  2. Status is re-read after every member and role mutation, because CGS clears a pending proposal when ownership or a party's membership changes another way.

Testing

npx tsc --noEmit, npm run lint and npm test (1310 passing) are clean on staging; the CLAUDE.md pre-merge greps are silent. Covered by tests: per-method targeting, DID validation, CSRF and rate-limit gates, error-code passthrough, the visibility helpers including the non-party case, and the date-line spacing.

Not yet exercised as a full handshake in a browser — accept is callable only by the proposed member, so it needs two accounts. Worth doing on staging.certified.app before this is promoted.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added group ownership transfer management in settings.
    • Owners can propose or cancel transfers, while proposed recipients can accept or decline.
    • Added transfer status, expiry details, confirmation dialogs, loading states, and error feedback.
    • Ownership controls are shown only to eligible participants.
    • Group membership and role changes now refresh transfer information automatically.
  • Bug Fixes

    • Improved transfer notices with correct spacing and clearer status information.

holkexyz and others added 4 commits September 17, 2026 21:19
Surfaces the two-phase ownership handshake added in CGS 0.6.0
(app.certified.group.ownershipTransfer.*) as a "Transfer ownership"
page in group settings, between Members & Roles and Activity Log.

The owner proposes an existing member; ownership moves only once that
member accepts, which proves they still control their DID. Either party
can cancel, and an un-accepted proposal lapses after 7 days.

- BFF route: GET status, POST propose, PUT accept, DELETE cancel. Per
  the CGS #27 targeting rule, `repo` goes on the querystring for the
  status query and in the body for the three procedures. Propose
  validates the DID and is rate-limited 5/10min per caller.
- UI: the owner picks from eligible members and confirms; the proposed
  member gets Accept/Decline; either party can cancel. Transfer status
  is re-read after every member and role mutation, because CGS clears a
  pending proposal when ownership or a party's membership changes by
  another route.
- Only the two parties ever see the page. CGS reports `pending: false`
  to a member who is not a party, exactly as when no transfer exists,
  so the UI derives visibility from that response and never tries to
  tell the two cases apart.

Copy avoids claiming the underlying PDS account moves — per the CGS
integration guide, the owner role is not proof of account control.

Requires the group service to be on 0.6.0+. staging.certified.app
already is; production is on 0.5.0 and answers these NSIDs with 501,
so this must not reach main until that deployment is upgraded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The "Choose the new owner" block repeated what the page description
already introduced. Fold the terms — accept-before-effect, the
proposer's demotion to admin, and the 7-day expiry — into the page
description so they're read once, before the control, and drop the
duplicate paragraph.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sfer

feat(groups): transfer group ownership to another member
The pending-transfer line read "expires 2026-09-24if it isn't
accepted": JSX drops the space between an expression and the word
after it when the two straddle a line break, so the expiry date ran
into the following word.

Build the sentence as one string in `transferPendingNote` and cover the
spacing with a unit test, so reflowing the JSX can't reintroduce it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
certified-app Ready Ready Preview Sep 17, 2026 7:39pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: dfed55fa-728a-43dd-853b-fd9c5cd8350f

📥 Commits

Reviewing files that changed from the base of the PR and between d112c0d and ade65b3.

📒 Files selected for processing (6)
  • src/app/api/groups/[groupDid]/ownership-transfer/__tests__/route.test.ts
  • src/app/api/groups/[groupDid]/ownership-transfer/route.ts
  • src/components/groups/__tests__/org-settings-transfer-gate.test.ts
  • src/components/groups/org-settings.tsx
  • src/lib/groups/api.ts
  • src/lib/groups/types.ts
 ____________________________________________________________________________
< I've got bills to pay, so I'm gonna find, find, find those bugs every day. >
 ----------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@holkexyz
holkexyz marked this pull request as ready for review September 18, 2026 11:23
@holkexyz
holkexyz merged commit 99448e7 into main Sep 18, 2026
7 checks passed

This branch was successfully deployed

1 active deployment
staging — ade65b3d Deployed Sep 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant