Warden

Relations & Tuples

Zanzibar-style relationship-based authorization with graph traversal.

Relation Tuples

A relation tuple represents a relationship between a subject and an object. It is the fundamental building block of ReBAC.

Every config example on this page comes in two flavours: Go (programmatic — call the store directly) and DSL (declarative — .warden source applied via warden apply or dsl.ApplyFS). They produce identical tuples in the relations table.

What goes in source vs runtime? Schema declarations (resource types, relation definitions) are config — keep them in .warden. Individual relation tuples (document:welcome owner = user:alice) usually accumulate at runtime as users share documents, join teams, etc. — those are written via the store API. The relation declaration in .warden is for fixtures and bootstrap state, not high-volume tuple writes.

Tuple Fields

FieldTypeDescription
IDid.RelationIDTypeID (rel_...) — auto-assigned on Create when unset
ObjectTypestringType of the object (e.g., "document")
ObjectIDstringID of the object
RelationstringRelationship name (e.g., "viewer", "editor", "owner")
SubjectTypestringType of the subject (e.g., "user", "team")
SubjectIDstringID of the subject
SubjectRelationstringOptional: relation on the subject (for subject sets)

Notation

Tuples are often written in this notation:

object_type:object_id#relation@subject_type:subject_id

Examples:

document:doc-123#viewer@user:user-42
project:alpha#editor@team:engineering
team:engineering#member@user:user-42
folder:root#parent@document:doc-123

Creating Tuples

import "github.com/xraph/warden/relation"

// "user:42 is a viewer of document:123" — ID is auto-assigned by the store on Create.
t := &relation.Tuple{
    ObjectType:  "document",
    ObjectID:    "doc-123",
    Relation:    "viewer",
    SubjectType: "user",
    SubjectID:   "user-42",
}
err := store.CreateRelation(ctx, t)
warden config 1
tenant t1

// Form: relation OBJ_TYPE:OBJ_ID RELATION = SUBJ_TYPE:SUBJ_ID
relation document:doc-123 viewer = user:user-42

The DSL form is for fixture / bootstrap tuples — the kind you write once when seeding a tenant. Most tuple writes happen at runtime through store.CreateRelation, the API, or a custom plugin.

Graph Traversal

Warden uses BFS (breadth-first search) to traverse relation graphs and find transitive permissions.

Example: Team Membership

user:42 ──member──▶ team:eng ──editor──▶ project:alpha
// Create membership
store.CreateRelation(ctx, &relation.Tuple{
    ObjectType:  "team",
    ObjectID:    "eng",
    Relation:    "member",
    SubjectType: "user",
    SubjectID:   "user-42",
})

// Create team's access to project
store.CreateRelation(ctx, &relation.Tuple{
    ObjectType:  "project",
    ObjectID:    "alpha",
    Relation:    "editor",
    SubjectType: "team",
    SubjectID:   "eng",
})

// Check: can user:42 edit project:alpha?
result, _ := eng.Check(ctx, &warden.CheckRequest{
    Subject:  warden.Subject{Kind: "user", ID: "user-42"},
    Action:   warden.Action{Name: "write"},
    Resource: warden.Resource{Type: "project", ID: "alpha"},
})
// result.Allowed == true (via transitive relation through team:eng)
warden config 1
tenant t1

resource project {
  relation editor: user | team#member
  permission write = editor
}

resource team {
  relation member: user
}

// Tuples — could equally be created at runtime.
relation team:eng    member = user:user-42
relation project:alpha editor = team:eng#member

The team:eng#member subject set means "every member of team:eng" — the engine resolves the membership relation transitively at check time.

Example: Document Sharing

// Share document with specific user
store.CreateRelation(ctx, &relation.Tuple{
    ObjectType:  "document",
    ObjectID:    "doc-456",
    Relation:    "viewer",
    SubjectType: "user",
    SubjectID:   "user-99",
})

// Share entire folder with a team
store.CreateRelation(ctx, &relation.Tuple{
    ObjectType:  "folder",
    ObjectID:    "shared",
    Relation:    "viewer",
    SubjectType: "team",
    SubjectID:   "marketing",
    SubjectRelation: "member",
})

// Document belongs to folder
store.CreateRelation(ctx, &relation.Tuple{
    ObjectType:  "document",
    ObjectID:    "doc-456",
    Relation:    "parent",
    SubjectType: "folder",
    SubjectID:   "shared",
})
warden config 1
tenant t1

resource folder {
  relation viewer: user | team#member
  permission read = viewer
}

resource document {
  relation viewer: user
  relation parent: folder
  permission read = viewer or parent->read
}

// Direct share + folder share via team#member + document-in-folder.
relation document:doc-456 viewer = user:user-99
relation folder:shared    viewer = team:marketing#member
relation document:doc-456 parent = folder:shared

The parent->read traversal lets a document inherit its folder's read permission — anyone who can read the folder can read everything in it.

Graph Walker Configuration

The BFS graph walker has configurable limits:

eng, _ := warden.NewEngine(
    warden.WithStore(store),
    warden.WithConfig(warden.Config{
        MaxGraphDepth: 10, // Maximum BFS depth (default: 10)
    }),
)
  • Cycle detection — The walker tracks visited nodes to avoid infinite loops.
  • Max depth — Limits traversal depth to prevent runaway queries.
  • Returns ErrMaxDepthReached if the limit is hit without finding a path.
  • Returns ErrCycleDetected if a cycle is found.

Listing Tuples

tuples, _ := store.ListRelations(ctx, &relation.ListFilter{
    TenantID:   "t1",
    ObjectType: "document",
    ObjectID:   "doc-123",
    Relation:   "viewer",
    Limit:      50,
})

Deleting Tuples

err := store.DeleteRelationTuple(ctx, "t1",
    "document", "doc-123", "viewer", "user", "user-42")

On this page