Skip to content

feat(policy): add the observer role below member - #2543

Open
Makisuo wants to merge 1 commit into
mainfrom
observer/policy
Open

Makisuo wants to merge 1 commit into
mainfrom
observer/policy

Conversation

@Makisuo

@Makisuo Makisuo commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

First layer of the Observer role: a read-only org role for people who follow the product without operating it (support, product, finance, founders).

What changed

  • observer joins the role ladder below member, with the key org:observer.
  • A role string the code does not know now reads as observer, the least a session can hold. It read as member, which was the lowest role until now.
  • A tenant with no roles at all still reads as member. Only Maple's own agents (PR review, investigations, chat) arrive without one, and they need to keep writing.
  • Denials for a member-level permission now say "Only org admins, engineers and members can ...".

Nothing assigns the role yet and the Members page does not offer it, so this layer changes no behaviour for existing sessions. One edge: a membership moved to a role key the code does not know now retires that person's member-pinned credentials, where it kept them before.

Verified

  • packages/domain: vitest run src/policy/policy.test.ts, 16 passed. New cases: an observer holds no grants, stays an observer next to an unknown role, takes the higher of two roles, and a member-pinned credential outranks an observer.
  • packages/backend: MembershipRevocationService.test.ts, 10 passed.
  • apps/web: members page, org membership kit and ingest key tests, 20 passed.
  • @maple/domain typecheck and the CI lint config on packages/domain/src/policy: clean.

Next layers

  1. Enforcement: a central read-only gate on both APIs, endpoint classification with a contract test, MCP and chat read-only by role.
  2. Members page: invite and role picker.
  3. Observer shell: nav, palette, redirects, settings.
  4. Read-only pass on the pages observers reach.
  5. Observer home page.

Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Observer is a read-only org role for people who follow the product without operating it. It ranks below member, so every permission in the table already excludes it.

A role string this code does not know now reads as observer, the least a session can hold, where it read as member. A tenant with no roles still reads as member: only Maple's own agents arrive without one.

Nothing assigns the role yet, and the Members page does not offer it.
@maple-review-bot

maple-review-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Maple review

🟡 Confidence 6/10 · needs attention. Held at 6 because a correctness warning is open.
1 warning −1 · tests covered · risk high −1

Adds observer below member, makes an unknown role string read as observer instead of member, and rewords the member denial. The policy ladder itself is sound; the Members page's existing role reader reports an observer as a member.

  • observer joins Role and RANK below member under key org:observer
  • roleOf answers observer for any role string it does not know
  • highestRole keeps the member floor only for an empty role set
  • holdersPhrase for a member permission names admins, engineers and members

Before merge

  • Manual · Create the org:observer role in the Clerk development and production instances, or roleOf will never see the key

Findings

🟠 Warning · F1 · An observer member renders as Member in the Members page and cannot be promoted to member

correctness · packages/domain/src/policy/roles.ts:46-54

roleOf("org:observer") now answers observer, but observerRole stays out of assignableRoles, so entryOf (apps/web/src/page/settings/kit/org-membership.ts:13) finds no entry and falls back to the member one: the row reads "Member" with org:member preselected for someone who holds no grants. Picking Member then equals toOrgRole(member.role), so members/update.ts:585 drops it as a no-op.

Let `org-membership` resolve the observer entry (label "Observer") while keeping it out of `ROLE_ENTRIES`, so the row is not reported as a member and a Member pick still saves.
🤖 Prompt to fix this finding with an AI agent
Findings from an automated review of commit bdcd2bc9611403b9f22f0a148d8a0115a6643e27. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · correctness · packages/domain/src/policy/roles.ts:46-54
An `observer` member renders as Member in the Members page and cannot be promoted to member
`roleOf("org:observer")` now answers `observer`, but `observerRole` stays out of `assignableRoles`, so `entryOf` (`apps/web/src/page/settings/kit/org-membership.ts:13`) finds no entry and falls back to the member one: the row reads "Member" with `org:member` preselected for someone who holds no grants. Picking Member then equals `toOrgRole(member.role)`, so `members/update.ts:585` drops it as a no-op.
Suggested fix: Let `org-membership` resolve the observer entry (label "Observer") while keeping it out of `ROLE_ENTRIES`, so the row is not reported as a member and a Member pick still saves.

bdcd2bc · What was checked, coverage and telemetry details are on the Maple / review check. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@coderabbitai

coderabbitai Bot commented Nov 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 38 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5f6107df-fd2a-479d-ae40-1a838581eb51
Commits

Reviewing files that changed from the base of the PR and between 61dc8b6 and bdcd2bc.

Files selected for processing: 2

  • packages/domain/src/policy/policy.test.ts
  • packages/domain/src/policy/roles.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

Comment thread packages/domain/src/policy/roles.ts Resolved

This branch has not been deployed

No deployments
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