Skip to content

Admin means "can manage the group" for _group records (#58)#97

Merged
cuibonobo merged 1 commit into
mainfrom
claude/issue-58-design-review-gtrtvm
Jul 16, 2026
Merged

Admin means "can manage the group" for _group records (#58)#97
cuibonobo merged 1 commit into
mainfrom
claude/issue-58-design-review-gtrtvm

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

Group roles were decorative — nothing distinguished member from admin, and membership rosters were governed by ordinary record write access: whoever could write a _group record could add or remove members, making membership self-amplifying.

  • groupRoleFromAssociations() (packages/core/src/access.ts) replaces the boolean isGroupMember() check, reporting which roster label matched ('admin' | 'member' | null; admin implies member).
  • Permission's group variant gains an optional role?: 'admin' (packages/core/src/types.ts), restricting an ACL entry to admins; absent role keeps today's any-member behavior.
  • ScopedStack gates update/associate/dissociate/delete/undelete/restoreVersion/setPermissions on _group records behind admin-or-owner instead of the generic write-bit/grant check (packages/core/src/stack.ts), closing the self-amplifying-roster hole.
  • ScopedStack.create() stamps the creator as a group's first admin at create time, so a group is never management-orphaned.
  • docs/spec.md updated: Group section states member/admin semantics, the role-gating rule, and the bootstrap rule; Permissions section documents the role field and updated group-resolution rule; write-bit trust-model section notes _group's carve-out.

No version bump for _group@1 — the role lives in associations and in the Permission type, both record-level, not schema-level (consistent with the no-install-base policy from #57).

Refs #51 (the general association-mutation gating this was meant to slot into — not yet implemented, so this lands as a self-contained _group-specific gate), #49 (identity — out of scope here), #57 (no-version-bump policy).

Test plan

  • packages/core typecheck clean
  • packages/core lint clean
  • Full packages/core test suite passing (387/387), including 15 new cases:
    • plain member cannot update/associate/dissociate/delete/undelete/setPermissions on a group
    • admin can do all of the above; stack owner always can regardless of roster
    • record-level write: true does not substitute for admin status (the vulnerability this closes)
    • setPermissions requires admin, not just record authorship
    • creator is stamped as admin at create time; bootstrap doesn't duplicate an explicitly supplied admin association
    • role: 'admin' ACL entry excludes a plain member; absent role behaves exactly as today

Generated by Claude Code

Group roles were decorative — nothing distinguished member from admin,
and membership rosters were governed by ordinary record write access, so
anyone who could write a _group record could add or remove members.

- groupRoleFromAssociations() replaces the boolean isGroupMember() check,
  reporting which roster label matched (admin implies member).
- Permission's group variant gains an optional role: 'admin', restricting
  an ACL entry to admins; absent role keeps today's any-member behavior.
- ScopedStack gates update/associate/dissociate/delete/undelete/
  setPermissions on _group records behind admin-or-owner instead of the
  generic write-bit/grant check, closing the self-amplifying-roster hole.
- ScopedStack.create() stamps the creator as a group's first admin so no
  group is ever management-orphaned.

Spec and tests updated accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDmUW3oNxPSCLf2ftqaF8q
@cuibonobo cuibonobo merged commit fb38349 into main Jul 16, 2026
5 checks passed
@cuibonobo cuibonobo deleted the claude/issue-58-design-review-gtrtvm branch July 16, 2026 14:19
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.

2 participants