OpenFGA model
The shared FGA authorisation model used by the TS, Go, and Python OpenFGA adapters.
spec/openfga/schema.fga is the canonical authorisation model used by every SDK’s OpenFGA adapter. The three adapters (@jeffs-brain/memory-openfga, github.com/jeffs-brain/memory/go/aclopenfga, jeffs_brain_memory.acl_openfga) all encode subjects and resources with the same scheme and use the same default action -> relation map, so the model file is the single source of truth.
Schema
model
schema 1.2
type user
type workspace
relations
define owner: [user]
define admin: [user] or owner
define member: [user] or admin
define billing_manager: [user] or owner
type brain
relations
define workspace: [workspace]
define owner: [user] or owner from workspace
define admin: [user] or admin from workspace
define writer: [user] or admin
define reader: [user] or writer
define can_delete: admin
type collection
relations
define brain: [brain]
define reader: [user] or reader from brain
define writer: [user] or writer from brain
define admin: [user] or admin from brain
type document
relations
define collection: [collection]
define reader: [user] or reader from collection
define writer: [user] or writer from collection
define can_export: reader
type api_key
relations
define workspace: [workspace]
define owner: [user]
define scope_reader: [brain#reader, collection#reader, document#reader]
define scope_writer: [brain#writer, collection#writer, document#writer]
The authoritative file lives at spec/openfga/schema.fga. If this page diverges from the file, the file wins.
Resource hierarchy
workspace -> brain -> collection -> document
api_key sits beside user as a subject type and may carry scope tuples that bind it to brain, collection, or document readers/writers.
Encoding on the wire
Subjects and resources serialise as <kind>:<id> strings exactly as they do across all three adapters:
- Subject
user:alice->{ kind: "user", id: "alice" } - Resource
brain:notes->{ type: "brain", id: "notes" }
A check call sends:
POST {apiUrl}/stores/{storeId}/check
{
"tuple_key": {
"user": "user:alice",
"relation": "writer",
"object": "brain:notes"
},
"authorization_model_id": "<optional>"
}
The OpenFGA server returns { "allowed": true | false, "resolution": "<reason>" }. The adapter maps that to CheckResult.
Action -> relation map
The default the three adapters use:
| Action | Relation |
|---|---|
| read | reader |
| write | writer |
| delete | can_delete |
| admin | admin |
| export | can_export |
If your deployed model uses different relation names, drive Provider.write / Provider.read directly with pre-computed tuples that name your relations.
Loading the model
fga model write --store-id <STORE_ID> --file spec/openfga/schema.fga
Pin the returned model id in your provider configuration (modelId in TS, ModelID in Go, model_id in Py) so checks always evaluate against the version you tested.
Versioning
The schema file carries schema 1.2 at the top. If the model evolves in a backward-incompatible way (relation rename, type removal) we’ll bump it and call out the migration in the repo changelog. Adapters never silently re-map relation names; if a check needs a different relation today, drive it through the tuple API explicitly.
See also
- Authorisation concept - how the SDKs use this model.
spec/openfga/- the canonical file plus its README.