Locy Use Case: RBAC with Priority Rules¶
Resolve deny-vs-allow authorization conflicts with prioritized Locy clauses.
This notebook uses schema-first mode (recommended): labels, edge types, and typed properties are defined before ingest.
How To Read This Notebook¶
- Step 1 initializes an isolated local database.
- Step 2 defines schema (the recommended production path).
- Step 3 seeds a minimal graph for this use case.
- Step 4 declares Locy rules and query statements.
- Steps 5-6 evaluate and inspect command/query outputs.
- Step 7 tells you what to look for in the results.
1) Setup¶
Creates a temporary database directory so the example is reproducible and leaves no state behind.
import os
import shutil
import tempfile
from pprint import pprint
import uni_db
DB_DIR = tempfile.mkdtemp(prefix="uni_locy_")
print("DB_DIR:", DB_DIR)
db = uni_db.Database(DB_DIR)
DB_DIR: /tmp/uni_locy_uasuk123
2) Define Schema (Recommended)¶
Define labels, property types, and edge types before inserting data.
(
db.schema()
.label("User")
.property("name", "string")
.done()
.label("Resource")
.property("name", "string")
.done()
.edge_type("ALLOWED", ["User"], ["Resource"])
.done()
.edge_type("DENIED", ["User"], ["Resource"])
.done()
.apply()
)
print('Schema created')
Schema created
3) Seed Graph Data¶
Insert only the entities/relationships needed for this scenario so rule behavior stays easy to inspect.
db.execute("CREATE (:User {name: 'alice'})")
db.execute("CREATE (:User {name: 'bob'})")
db.execute("CREATE (:Resource {name: 'prod-db'})")
db.execute("MATCH (u:User {name:'alice'}), (r:Resource {name:'prod-db'}) CREATE (u)-[:ALLOWED]->(r)")
db.execute("MATCH (u:User {name:'bob'}), (r:Resource {name:'prod-db'}) CREATE (u)-[:ALLOWED]->(r)")
db.execute("MATCH (u:User {name:'bob'}), (r:Resource {name:'prod-db'}) CREATE (u)-[:DENIED]->(r)")
print('Seeded graph data')
Seeded graph data
4) Locy Program¶
CREATE RULE defines derived relations. QUERY ... WHERE ... RETURN ... reads from those relations.
program = r'''
CREATE RULE access PRIORITY 1 AS
MATCH (u:User)-[:ALLOWED]->(r:Resource)
YIELD KEY u, KEY r, 1 AS decision_code
CREATE RULE access PRIORITY 2 AS
MATCH (u:User)-[:DENIED]->(r:Resource)
YIELD KEY u, KEY r, 2 AS decision_code
'''
print(program)
CREATE RULE access PRIORITY 1 AS MATCH (u:User)-[:ALLOWED]->(r:Resource) YIELD KEY u, KEY r, 1 AS decision_code CREATE RULE access PRIORITY 2 AS MATCH (u:User)-[:DENIED]->(r:Resource) YIELD KEY u, KEY r, 2 AS decision_code
5) Evaluate Locy Program¶
Run the program, then inspect materialization stats (iterations, strata, and executed queries).
out = db.locy_evaluate(program)
print("Derived relations:", list(out["derived"].keys()))
stats = out["stats"]
print("Iterations:", stats.total_iterations)
print("Strata:", stats.strata_evaluated)
print("Queries executed:", stats.queries_executed)
Derived relations: ['access'] Iterations: 0 Strata: 1 Queries executed: 0
6) Inspect Command Results¶
Each command result can contain rows; this is the easiest way to verify your rule outputs and query projections.
print("Derived relation snapshots:")
for rel_name, rel_rows in out["derived"].items():
print(f"\\n{rel_name}: {len(rel_rows)} row(s)")
pprint(rel_rows)
if out["command_results"]:
print("\\nCommand results:")
for i, cmd in enumerate(out["command_results"], start=1):
print(f"\\nCommand #{i}:", cmd.get("type"))
rows = cmd.get("rows")
if rows is not None:
pprint(rows)
if not out["command_results"]:
print("\\nNo QUERY/EXPLAIN/ABDUCE command outputs in this program.")
Derived relation snapshots:
\naccess: 2 row(s)
[{'decision_code': 1,
'r': {'_id': '2', '_labels': ['Resource'], 'name': 'prod-db'},
'u': {'_id': '0', '_labels': ['User'], 'name': 'alice'}},
{'decision_code': 2,
'r': {'_id': '2', '_labels': ['Resource'], 'name': 'prod-db'},
'u': {'_id': '1', '_labels': ['User'], 'name': 'bob'}}]
\nNo QUERY/EXPLAIN/ABDUCE command outputs in this program.
7) What To Expect¶
Use these checks to validate output after evaluation:
- In
derived['access'],aliceshould havedecision_code = 1(ALLOW path). - In
derived['access'],bobshould havedecision_code = 2(DENY override). - Exactly one derived row per
(user, resource)should remain after priority filtering.
8) Cleanup¶
Delete the temporary database directory created in setup.
shutil.rmtree(DB_DIR, ignore_errors=True)
print("Cleaned up", DB_DIR)
Cleaned up /tmp/uni_locy_uasuk123