Skip to content

Identity and Scope

Plystra’s core invariant is that the login account is not the same thing as the business identity that acts. Authorization evaluates the full actor tuple:

User -> UserMember -> Member -> Space
ObjectMeaning in current Core
UserLogin account. It stores email, optional username and phone, status, metadata, and an internal password_hash. API responses never return password_hash.
MemberBusiness identity inside one Space. Roles are granted to Members, not directly to Users.
UserMemberExplicit bridge from User to Member in a Space. It records relation type, active/revoked status, primary flag, optional expiry, and revocation metadata.
SpaceTenant or workspace boundary. Actor, grants, groups, and target resources must belong to the same Space.

UserMember is security-critical. Revoked, inactive, or expired bindings deny authorization even when the Member has a matching role.

The API accepts the actor either as a nested object or legacy flattened fields:

{
"actor": {
"user_id": "user_alice",
"member_id": "member_finance_reviewer",
"user_member_id": "um_alice_finance_reviewer",
"space_id": "space_acme"
},
"resource_type": "invoice",
"resource_id": "invoice_001",
"action": "approve"
}

For HTTP requests, the server owns canonical request metadata:

  • request_id comes from middleware.
  • ip comes from the server-derived remote IP and trusted proxy logic.
  • user_agent comes from the HTTP header.

Body-provided request_id, ip, and user_agent are ignored by the HTTP authz handler.

ScopeRuleResult
selfresource.owner_member_id == actor.member_idCovers resources owned by the active Member.
grouptarget_group_id = scope_anchor_group_idCovers only the exact anchor Group.
group_tree`target_path = anchor_path OR target_path LIKE anchor_path
spaceresource.space_id == actor.space_idCovers resources in the active Space.
globalDisabled for ordinary MembersAlways denies with GLOBAL_SCOPE_DISABLED in v1.0.

The group_tree rule is deliberately strict. An anchor of finance covers finance and finance.apac, but not finance-old.

The current engine evaluates:

  1. Required input fields.
  2. Actor, UserMember, Member, and Space state.
  3. Resource Registry registration and action validity.
  4. Target resource and group snapshot.
  5. Same-Space invariant across actor, target, grants, and scope anchors.
  6. Matching permission candidates filtered by Member, resource type, and action.
  7. Scope coverage. Any covered candidate allows the action.
  8. Audit write for both allow and deny decisions.
CodeMeaning
ACTOR_USER_INACTIVEThe User is not active.
ACTOR_MEMBER_INACTIVEThe Member is not active.
USER_MEMBER_REVOKEDThe UserMember binding is not active.
USER_MEMBER_EXPIREDThe UserMember binding has expired.
SPACE_INACTIVEThe active Space is not active.
CROSS_SPACE_VIOLATIONActor, target, grant, or scope anchor crosses Space boundaries.
NO_MATCHING_PERMISSIONNo active role permission matched the resource/action pair.
SCOPE_ANCHOR_MISSINGA group-based grant is missing a scope anchor.
TARGET_GROUP_MISSINGThe target resource has no group for group-based scope resolution.
SCOPE_OUT_OF_BOUNDSMatching permissions exist but do not cover the target scope.
GLOBAL_SCOPE_DISABLEDglobal is reserved and disabled in v1.0.
INVALID_RESOURCE_TYPEThe resource type is not registered.
INVALID_RESOURCE_ACTIONThe action is not registered for the resource type.

Every authorization decision is written through the store as an AuditLog. The trace includes:

  • trace_version, currently 1.0.
  • actor snapshots for User, Member, and UserMember.
  • Space and target resource snapshots.
  • Resource Registry snapshot.
  • matched permission candidates and scope checks.
  • request metadata.
  • final decision, deny code, and reason.

AuditLog is append-only. Ent schema hooks and store hooks block updates and deletes.

Permissions are not evaluated as raw strings only. The engine first resolves:

  • registered resource_types
  • registered resource_actions
  • resource_mappings

For the Finance Reviewer demo, invoice is registered with actions such as read, create, approve, reject, and delete. approve and reject are high-risk actions; delete is critical.