Skip to content

Authorization (L3)

In XNet, authorization is data, not application logic: rules are declared on schemas and delegated through grant nodes and UCAN tokens, and enforced at the sync boundary. Because two implementations that disagree on policy make different read/write decisions on the same graph, the decision semantics are normative. Normative text: 04-authorization.md.

action ∈ { read, write, delete, share, admin }
can(subject: DID, action, nodeId) → { allowed, reasons }

A schema may carry an authorization block defining roles (how a subject earns a role on a node) and actions (which roles may perform each action):

interface AuthorizationDefinition {
roles: Record<string, RoleResolver>
actions: Record<AuthAction, AuthExpression>
publicProps?: string[]
fieldRules?: Record<string, { allow: AuthExpression; deny?: AuthExpression }>
}
KindSubject earns the role when…
creatorsubject == node.createdBy
propertyit appears in a named person/relation property (e.g. editors)
relationit holds a role on a related node (inheritance)
membershipa membership edge (e.g. SpaceMembership) links it to a container with a role ≥ minRole, cascading to nested containers

Resolution walks relations/memberships with a bounded depth and must terminate.

expr ::= allow(role…) | deny(role…) | roleRef(name)
| and(expr…) | or(expr…) | not(expr) | PUBLIC | AUTHENTICATED

Deny wins: if any matching deny is true, the action is denied regardless of any allow. Evaluation is total and deterministic.

For private nodes, the ability to decrypt is the read‑control mechanism: content keys are wrapped per recipient (X25519 + XChaCha20). A subject not in the recipient set can’t read encrypted properties even if it receives the bytes. publicProps and the four universal node fields stay readable for indexing and attribution.

  • Grant nodes — ordinary XNet nodes recording { issuer, grantee, resource, actions, expiresAt, revokedAt }. Active iff not revoked and not expired.
  • UCAN tokens — capability tokens (JWT/EdDSA) with a proof chain. Each capability must be an attenuation of its proof; child expiry ≤ parent; chains acyclic.
flowchart TB
  Q["can(subject, action, node)"] --> ND{node-level deny?}
  ND -->|yes| DENY
  ND -->|no| RR["resolve roles (depth-bounded)"]
  RR --> SE["evaluate schema expression (deny-wins)"]
  SE -->|allowed| ALLOW
  SE -->|no| GR{active grant / UCAN?}
  GR -->|yes| ALLOW
  GR -->|no| PUB{PUBLIC / publicProps?}
  PUB -->|yes| ALLOW
  PUB -->|no| DENY

Caching and indexing are private; the decisions must match the decision‑trace conformance vectors.

Next: Implement it in your language →