SpiceDB Schema Traversal: An Architecture Note on Centralized Authorization
SpiceDB evaluates permission checks against a declarative schema of relationships rather than application code. This architecture note covers the smallest viable design, trust boundaries, operational checks, and failure modes.
08 Nov 2026, 11:20 UTC

The Problem: Permission Logic Spread Across Services
Most systems begin with simple role checks inside application code: if user.isAdmin or if doc.ownerId == user.id. As products grow, these checks fragment. A billing service knows one rule, a document service knows another, and an admin dashboard guesses a third. The result is inconsistent access decisions, hard-to-audit policy, and a growing fear that changing one check will break another.
SpiceDB offers a different model. Instead of embedding permission logic in each service, you define a schema that describes object types and their relationships, then store individual facts—called tuples—that connect users to resources. Permission checks become deterministic graph traversals against that schema. The useful takeaway: you can centralize authorization into a single auditable engine, but the schema and tuple lifecycle must be treated as production infrastructure, not configuration.
Requirements for a Centralized Authorization Engine
Three requirements typically justify moving to SpiceDB:
- Determinism: The same schema and tuples must always produce the same decision. Application code with branching logic rarely guarantees this.
- Decoupling: Services should ask "can this subject access this resource?" without knowing how the answer is computed.
- Auditability: You must be able to explain why access was granted or denied by inspecting stored relationships, not reading code.
If your system does not need these properties—for example, a single-service app with three static roles—SpiceDB adds operational overhead without solving a real problem.
The Smallest Suitable Schema and Tuple Design
Start with the smallest model that covers one real access pattern. A common first step is a document-sharing feature with two relations: owner and viewer.
Defining the schema
A schema declares object types, the relations they support, and the permissions those relations imply. In SpiceDB's schema language, you might define:
definition user {}
definition document {
relation owner: user
relation viewer: user
permission view = owner + viewer
}
This says a document can have an owner and multiple viewers. The view permission is computed as the union of both relations. The schema is declarative: SpiceDB compiles it into an evaluation plan, and invalid definitions are rejected before they can affect queries.
Writing tuples
A tuple is a single relationship fact. To grant Alice viewer access to a budget document, you store:
document:budget#viewer@user:alice
Tuples are the unit of both authorization and lifecycle. If Alice should lose access, you delete that tuple. There is no hidden state.
Running a permission check
Your application (or an operator using the official Zed CLI) issues a check request rather than querying the datastore directly. From a terminal with the Zed CLI installed and an authenticated context configured:
# Write the schema (run from the directory containing schema.zed)
zed schema write schema.zed
# Create a relationship tuple
zed relationship create document:budget#viewer@user:alice
# Check whether Alice can view the document
zed permission check document:budget view user:alice
Replace document:budget, user:alice, and view with your own object IDs and permission names. These commands require a Zed context with write access to the SpiceDB datastore. A successful check returns a decision indicating whether the relationship path exists. If the schema was rejected or the object type is unknown, the command fails with an explicit error rather than silently denying access.
| Consideration | Minimal design | Over-modeled design |
|---|---|---|
| Object types | 1–2 core types | Every entity as a type |
| Relations | Only required edges | Every possible edge |
| Permission computation | Direct union or intersection | Deep nested traversals |
| Tuple volume | Proportional to sharing events | Proportional to every attribute |
Trust and Data Boundaries
The critical boundary in this architecture sits between your application and SpiceDB. Your services own identity, resource creation, and business logic. SpiceDB owns the relationship graph and the traversal logic that answers access questions.
This separation means an application bug cannot accidentally grant access by skipping a check—if the check is not sent to SpiceDB, the request should not proceed. Conversely, a SpiceDB outage does not corrupt your application data; it only prevents new authorization decisions. The datastore behind SpiceDB should be treated as an internal trust boundary: applications do not read tuple tables directly, because bypassing the API would skip schema validation and computed permissions.
Operational Checks and Lifecycle Management
Because tuples represent live access rights, they require the same operational rigor as database records.
- Schema versioning: Treat schema files as code. Store them in version control and review changes. An invalid schema can block all check requests until corrected, so deploy schema updates behind the same testing gates as application releases.
- Tuple garbage collection: When a user leaves a team or a resource is deleted, stale tuples must be removed. SpiceDB can garbage-collect tuples that reference deleted objects, but relationship removals driven by your application logic must be explicit.
- Policy matrix testing: Before rolling out a schema change, compare computed decisions against a known-good matrix of subjects, resources, and expected outcomes. This catches regressions that unit tests on individual tuples might miss.
- Log inspection: During deployment, watch server logs for schema validation errors. A rejected schema definition is an early signal that a trust boundary or type assumption has been violated.
Failure Modes and Design Limits
SpiceDB surfaces design violations as explicit errors, which is preferable to silent misbehavior, but those errors still require response.
- Cyclic relationships: If your schema allows a relation to reference itself through a path that loops, SpiceDB detects the cycle and returns an error rather than looping indefinitely. This usually indicates a modeling mistake, such as making a group both a member and a parent of itself.
- Missing schema definitions: A check request for an undefined object type or permission fails immediately. This is a guardrail, but it means a typo in a resource type string can break an entire feature.
- Deep traversal latency: Permissions that require many hops—user → team → project → folder → document—increase query latency. Model with cardinality in mind; if a relation will contain millions of tuples, consider whether an intermediate computed permission or a denormalized tuple is appropriate.
- Tuple verbosity: Highly granular permissions, such as per-field access control on thousands of records, can produce tuple counts that dwarf your user count. Storage and write amplification become real concerns.
Conditions That Would Change the Design
The minimal design above is suitable for straightforward resource sharing. You should revisit the model if:
- Traversal depth consistently exceeds three or four hops in production traces.
- Tuple write volume grows faster than your user base, suggesting the model is too granular.
- You need attribute-based rules (e.g., "allow access if request.ip is internal") that do not fit cleanly into relationship tuples.
- Multiple teams need conflicting interpretations of the same relation, indicating the schema has become a shared dependency that requires stricter governance.
In those cases, the answer is usually not to abandon SpiceDB, but to introduce computed permissions, caveats, or a secondary policy engine for rules that are not naturally expressed as relationships.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.