Chapter Four: Granting Access

The Access Model, @values & Permission Queries

Previously

In Chapter Three you populated Amasoft and promoted Alex from contractor to full-time employee. Chapter Four introduces the central concept of any RBAC system: access grants.

The Situation

Alex Chen has just joined Amasoft as a full-time employee in the engineering team. They need read access to the engineering platform. Sam Rivera, their manager, has admin access. Michelle Rocksmasher, as CISO, has read access across all systems for oversight.

The Access Grant Model

define
attribute permission_level, value string @values("read", "write", "admin");
attribute start_datetime, value datetime;
attribute end_datetime, value datetime;
relation access_grant,
  relates grantee @card(1),
  relates system @card(1),
  relates grantor @card(1),
  owns permission_level @card(1),
  owns start_datetime @card(1),
  owns end_datetime @card(0..1);
entity identity, plays access_grant:grantee;
entity system, plays access_grant:system;
entity permanent_staff, plays access_grant:grantor;

We use datetime instead of date - system access grants are typically time-sensitive; we should encode actual timestamps.

Notice also: access_grant doesn’t just connect two entities, it also has its own attributes (e.g. permission_level). TypeDB relations can natively own their own attributes!

💡 Two scopes for @values

@values can be scoped to the attribute type itself — as with permission_level here, constraining every instance — or to one type’s ownership of it, as with identity owns status. Status shows why both modelling options exist - it means different things in different contexts; a system’s status might be "healthy", "degraded", or "down". Read more in the @values annotation reference.

⭐ TypeDB Advantage: Schema-level data validation

In SQL, enforcing that permission_level can only be 'read', 'write', or 'admin' requires either a CHECK constraint (often ignored) or a lookup table with a foreign key. In TypeDB, @values enforces this at the schema level — it is impossible to insert an invalid permission level.

N-ary Relations — TypeDB’s Native Superpower

Look again at what access_grant connects: a grantee, a system, and a grantor - three participants. This is called an N-ary relation: a connection with more than two participants. The following two code blocks are for comparison only — do not run them. They show how the same concept looks across two other database types, so you can compare them against TypeDB.

In SQL, this requires a junction table and multiple JOINs just to answer a simple question:

-- SQL: requires a junction table with foreign keys
CREATE TABLE access_grants (
  grantee_id INT REFERENCES identities(id),
  system_id INT REFERENCES systems(id),
  grantor_id INT REFERENCES identities(id),
  permission_level VARCHAR(5),
  start_datetime DATETIME,
  end_datetime DATETIME,
  PRIMARY KEY (grantee_id, system_id, start_datetime)
);
-- To query who granted what: three tables, three JOINs
SELECT i.name, s.name, g.name
FROM access_grants ag
JOIN identities i ON ag.grantee_id = i.id
JOIN systems s ON ag.system_id = s.id
JOIN identities g ON ag.grantor_id = g.id;

In a property graph database, edges are binary - you cannot natively connect three nodes, so you are forced to introduce an artificial intermediate node:

# Property graph (e.g. Neo4j): edges are binary and second-class
# No native way to connect three nodes — requires an intermediate node
# (grantee)-[:HAS_GRANT]->(grant_node)-[:ON_SYSTEM]->(system)
# (grant_node)-[:GRANTED_BY]->(grantor)
# The grant_node is an artificial workaround

In TypeDB, the relation is first-class. No junction table, no intermediate node, no workaround - just the model that reflects reality. And the query is a single clean pattern:

# TypeDB: who granted what — one clean pattern
match
$g isa access_grant, links (grantee: $i, system: $s, grantor: $by);
fetch { "grantee": $i.name, "system": $s.name, "granted_by": $by.name };
⭐ TypeDB Advantage: N-ary relations are first-class

In SQL, any relationship that carries data requires a junction table — extra schema, extra JOINs, extra complexity. In property graph databases, edges are binary and second-class: you cannot natively connect three nodes, so you introduce artificial intermediate nodes that obscure the real meaning. In TypeDB, a relation can connect any number of role players and own any number of attributes. It is first-class, queried the same way as any other type.

Granting Access

# Alex gets read access to GitShed, granted by Sam, their manager
match
$alex isa full_time_employee, has email "alex.chen@amasoft.com";
$sam isa full_time_employee, has email "sam.rivera@amasoft.com";
$eng isa engineering_platform, has name "GitShed";
put
$_ isa access_grant, links (grantee: $alex, system: $eng, grantor: $sam),
  has permission_level "read",
  has start_datetime 2024-01-15T09:00:00;
end;

# Sam gets admin access to GitShed, granted by Michelle, the CISO
match
$sam isa full_time_employee, has email "sam.rivera@amasoft.com";
$michelle isa full_time_employee, has email "michelle.rocksmasher@amasoft.com";
$eng isa engineering_platform, has name "GitShed";
put
$_ isa access_grant, links (grantee: $sam, system: $eng, grantor: $michelle),
  has permission_level "admin",
  has start_datetime 2024-01-01T09:00:00;
end;

# Sam also gets read access to Workweek — managers see team HR records
match
$sam isa full_time_employee, has email "sam.rivera@amasoft.com";
$michelle isa full_time_employee, has email "michelle.rocksmasher@amasoft.com";
$hr isa hr_system, has name "Workweek";
put
$_ isa access_grant, links (grantee: $sam, system: $hr, grantor: $michelle),
  has permission_level "read",
  has start_datetime 2024-01-01T09:00:00;
end;

# Michelle gets read access across ALL systems, self-granted
match
$michelle isa full_time_employee, has email "michelle.rocksmasher@amasoft.com";
$s isa system;
put
$_ isa access_grant, links (grantee: $michelle, system: $s, grantor: $michelle),
  has permission_level "read",
  has start_datetime 2024-01-01T08:00:00;
end;

# The deploy bot gets admin access to EllipseCI, granted by Sam
match
$bot isa service_account, has service_name "ci-deploy-bot";
$sam isa full_time_employee, has email "sam.rivera@amasoft.com";
$eng isa engineering_platform, has name "EllipseCI";
put
$_ isa access_grant, links (grantee: $bot, system: $eng, grantor: $sam),
  has permission_level "admin",
  has start_datetime 2024-01-10T09:00:00;
💡 Matching multiple instances at once

The Michelle query matches every system in the database and inserts one access_grant per system. TypeDB runs the insert once per row returned by the match — a powerful pattern for bulk grants.

Querying Access

Who has access to which engineering platforms, and at what level?

match
$s isa engineering_platform;
$g isa access_grant, links (grantee: $i, system: $s);
fetch { "identity_name": $i.name, "platform": $s.name, "permission": $g.permission_level };

What does Sam have access to?

match
$sam has name "Sam Rivera";
$s has name $sys;
$_ isa access_grant, links (grantee: $sam, system: $s),
  has permission_level $pl;
fetch { "system": $sys, "permission": $pl };

Chapter Four Challenge

🎯 Challenge: Grant Access to Amasoft
  1. Define the access_grant relation as shown above.

  2. Grant Alex read access to GitShed, with Sam as grantor.

  3. Grant Sam admin access to GitShed and read access to Workweek, with Michelle as grantor.

  4. Grant Michelle read access to all systems using the bulk match pattern, self-granted.

  5. Grant the ci-deploy-bot admin access to EllipseCI, matching it by service_name, with Sam as grantor.

  6. Query: who has access to which engineering platforms?

  7. Query: what does Sam have access to?

  8. Bonus: grant a time-limited access grant using end_datetime. Then write a query to find all grants expiring within that date:

    match
    $i has name $name;
    $s has name $sys;
    $ag isa access_grant, links (grantee: $i, system: $s), has end_datetime $vu;
    $vu < 2024-02-15T00:00:00;
    fetch { "name": $name, "system": $sys, "expires": $vu };

What You Can Do Now

You can now define an access model with schema-level permission validation, grant access to systems, and query who has access to what. Chapter Five introduces level assignments and two kinds of organisational change an RBAC system must handle.