Skip to content

feat(rbac): two-axis role charter, P1/P2/P3 invariants, SSOT predicate derivation (TIN-2435)#17

Merged
Jess Sullivan (Jesssullivan) merged 1 commit into
mainfrom
feat/tin-2435-role-charter
Jul 4, 2026
Merged

feat(rbac): two-axis role charter, P1/P2/P3 invariants, SSOT predicate derivation (TIN-2435)#17
Jess Sullivan (Jesssullivan) merged 1 commit into
mainfrom
feat/tin-2435-role-charter

Conversation

@Jesssullivan

Copy link
Copy Markdown
Contributor

Encodes the operator-ratified TIN-2435 design (comment thread, 2026-07-04): two-axis RBAC — governance rank (total order, ROLE_HIERARCHY) x feature capability (intentional lattice, ROLE_PERMISSIONS). TIN-1606 precedent stands. Version 0.4.0. No npm publish, no Bazel registry release — release acts await the operator.

What changed

1. MEMBER_SELF_SERVICE_CORE

Exported constant defined as ROLE_PERMISSIONS.member by construction; pinned by test to the ratified triple admin.access, admin.content.view, admin.events.view.

2. P2 data reconciliation (view-level grants)

Every role ranked >= member now holds the core. Exact additions:

Role Gains
moderator admin.events.view
editor admin.events.view
contributor admin.events.view
event_manager admin.content.view

3. Deterministic invariants (replaces the flaky monotonicity property class)

tests/rbac-invariants.test.ts — exhaustive, zero randomness:

  • P1: canManageRole(a,b) === ROLE_HIERARCHY[a] > ROLE_HIERARCHY[b] over all 64 pairs.
  • P2: every role >= member holds the full core.
  • P3: PERMISSION_FEATURE_DOMAIN registry covers exactly the granted vocabulary (both directions), domains derived from admin.<domain>.<verb>.
  • Pinned lattice counterexamples (chain-monotonicity is NOT an invariant; do not "fix"): editor !> event_manager (admin.events.manage), admin !> moderator (admin.content.moderate), member !> viewer (admin.analytics.view), and the TIN-1606 pair (contributor never inherits event management).

4. Charter as data + docs

ROLE_CHARTER (governance-spine | specialist + rank), FEATURE_DOMAINS, PERMISSION_FEATURE_DOMAIN. Docs: docs/role-charter.md (two-axis model, lattice statement, P1/P2/P3, TIN-1606 precedent, the ratified role x feature charter table — operator-ratified 2026-07-04, TIN-2435) + README charter section + docs/rbac-matrix.md refresh.

5. Predicate derivation (kills the #628 anti-pattern class)

All hand-maintained role arrays in core/permissions/index.ts (ALL_ROLES/CONTENT_CREATORS/EDITORS/ADMINS/SUPER_ADMIN + inline lists, incl. canViewContent and getAllowedVisibilityOptions) are gone; every can* predicate derives from ROLE_PERMISSIONS. Full role x predicate matrix locked cell-by-cell in tests.

New permission strings (vocabulary additions so non-mapping predicates derive from data instead of arrays; none granted to member, so the core stays the ratified triple):

String Granted to Backs
admin.content.publish contributor, event_manager, editor, moderator, admin, super_admin canCreatePublicContent, visibility public tier
admin.content.media_create contributor, editor, admin, super_admin canCreateVideos
admin.content.delete admin, super_admin canDeletePosts/canDeleteVideos/canDeleteContent, visibility admin tier
admin.events.delete admin, super_admin canDeleteEvents

Intentional predicate deltas (P2 member-core flow-through; every other cell is behavior-identical to the pre-derivation arrays):

Predicate Role Before → After
canCreateEvents moderator false → true
canCreateEvents editor false → true
canCreateEvents contributor false → true
canDeleteOwnContent moderator false → true

Design notes / judgment calls

  • The ratified example canViewEvents = hasPermission(role,'admin.events.view') would flip viewer's canViewEvents true→false (viewer is below the member floor and holds no events.view). Per the explicit behavior-identity constraint, the all-roles list-view predicates (canViewPosts/canViewEvents/canViewProfiles/canViewVideos/canEditOwnProfile) derive from admin.access instead, which is set-identical to the historical ALL_ROLES behavior. If viewer should lose list-view access, that is a one-line ratification away.
  • P2 forces event_manager to hold admin.content.view, so post-reconciliation event_manager IS a superset of contributor. The TIN-1606 non-nesting survives in the contributor-does-not-inherit-events direction and as the general specialist principle; the old test asserting event_manager lacks content.view was updated accordingly.

Verification

  • Full suite: 18 files / 385 tests green; tsc --noEmit clean; publint clean.
  • Bite-verified: P1 (canManageRole >>= ⇒ red), P2 (editor loses events.view ⇒ red), P3 (unregister admin.events.delete ⇒ red); reverted ⇒ green.

Companion backport: tinyland.dev PR (vendored test-layer + P2 data mirror, interim until TIN-1721).

@Jesssullivan Jess Sullivan (Jesssullivan) merged commit 99ca45d into main Jul 4, 2026
4 checks passed
@Jesssullivan Jess Sullivan (Jesssullivan) deleted the feat/tin-2435-role-charter branch July 4, 2026 01:08
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