Skip to content

ACL.md §1a: role scope as the standard, not folklore - #373

Merged
WebTigers merged 1 commit into
mainfrom
docs/acl-role-scope-standard
Oct 25, 2026
Merged

WebTigers merged 1 commit into
mainfrom
docs/acl-role-scope-standard

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

The role graph said what a role may do. Nothing said how much of the install a role may do it to — so that question has been answered ad hoc, per screen, by whoever was looking at the time.

ACL.md §1a now states it as the standard. Inserted as 1a rather than renumbering, so no existing §N cross-reference breaks.

The tiers

Role Scope Reaches
guest — public only
user SELF their own rows, within the org(s) they belong to
manager ORG their own org
supermanager ORG TREE their org and its child orgs (parent_org_id)
admin APP-WIDE every org — administering orgs is the job
superadmin APP-WIDE every org, plus platform surfaces
developer APP-WIDE everything

The cut line is between supermanager and admin. "Tenant admin" is not a role in this model — if you mean someone bounded to one org, you mean manager or supermanager.

Two things the section is careful about

The unit of scope is the ORG, and how many people live in an org is the app developer's decision. One user per org, a team sharing one org, a tree of orgs — all valid configurations of the same model, and the ACL behaves identically under each. The platform's job is to scope by org correctly; populating those orgs is an application concern and is deliberately not specified here.

It reconciles with §1 instead of contradicting it. "The same person can be admin in one org and viewer in another" is about where a role is stored — on the membership, so it can differ per org. It is not a claim about reach.

What makes it usable as a standard

"What conforming code looks like" is the operative part:

  1. Scope is decided in the service, from the caller's role, and passed into the model finder. A finder that hard-codes org_id = <caller's org> has silently decided no app-wide role exists.
  2. A tenant-scoped role is scoped automatically; an app-wide role is not scoped at all — not "scoped with an opt-in".
  3. A missing org filter is not a defect by itself — only if a tenant-scoped role can actually reach that surface. Read the acl.ini grant first.
  4. Tiger_Model_Media::search() is the reference gate: it decides from role and org together.

Also

core/configs/acl.ini role descriptions now name their scope in that same vocabulary, so the cut line is visible by reading down the list rather than inferred — admin previously said just "Administrator" and superadmin "Platform administrator", which is where the ambiguity lived. Description strings only; no behaviour, and nothing asserts them.

AGENTS.md's Authorization section points at §1a before you write a finder.

Unit suite green: 1200 tests, 18318 assertions.

🤖 Generated with Claude Code

https://claude.ai/code/session_013vdyTeoDrGE4hKxt53TsE8

The role graph said what a role may DO. Nothing said how much of the install a role may do it TO —
so that question has been answered ad hoc, per screen, by whoever was looking at the time.

§1a now states it as the standard, with the tiers in one vocabulary (SELF / ORG / ORG TREE /
APP-WIDE) and the cut line named: `user` is self-scoped, `manager` is their org, `supermanager`
their org plus its children, and `admin`/`superadmin`/`developer` are APP-WIDE — administering orgs
is the job. "Tenant admin" is not a role in this model; if you mean someone bounded to one org you
mean manager or supermanager.

Two things the section is careful about:

- **The unit of scope is the ORG, and how many people live in an org is the APP DEVELOPER'S
  decision.** One user per org, a team in one org, a tree of orgs — all valid configurations of the
  same model, and the ACL behaves identically under each. The platform scopes by org; populating
  those orgs is an application concern and deliberately not specified here.
- **It reconciles with §1 rather than contradicting it.** "The same person can be `admin` in one org
  and `viewer` in another" is about where a role is STORED — on the membership, so it can differ per
  org. It is not a claim about reach.

"What conforming code looks like" is the part that makes this usable as a standard: scope is decided
in the SERVICE from the caller's role and passed into the model finder (a finder that hard-codes
`org_id = <caller's org>` has silently decided no app-wide role exists); a tenant role is scoped
automatically while an app-wide role is not scoped at all; and a missing org filter is a defect ONLY
if a tenant-scoped role can actually reach that surface — read the acl.ini grant first.
`Tiger_Model_Media::search()` is cited as the reference gate, since it already decides from role and
org together.

Also: the role descriptions in core/configs/acl.ini now name their scope in that same vocabulary, so
the cut line is visible by reading down the list instead of inferred. `admin` said "Administrator"
and `superadmin` said "Platform administrator", which is where the ambiguity lived. Description
strings only — no behaviour, and nothing asserts them.

AGENTS.md's Authorization section points at §1a before you write a finder.

Unit suite green (1200 tests, 18318 assertions).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013vdyTeoDrGE4hKxt53TsE8
@WebTigers
WebTigers merged commit bc527c1 into main Oct 25, 2026
14 checks passed
@WebTigers
WebTigers deleted the docs/acl-role-scope-standard branch October 25, 2026 14:23
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