The mistake most products make
Most products code permissions. Investor role gets these routes. Accountant gets those. The matrix lives in a switch statement, hard to audit, hard to edit, hard to test.
Lode lives the access model from the spec: the matrix is data. Ten
role templates — FOUNDER, OPERATOR, CREW, INVESTOR, PROSPECT_INVESTOR,
ADVISOR, ACCOUNTANT, PARTNER, CLIENT, PRESS — each one row in
grant_templates. Each row holds a JSON matrix mapping every chapter
(CH01..CH12, PORTFOLIO) to an altitude grant (none/S/SL/SLO/R).
One resolver, every RLS
Every row-level security policy on every Layer-2 table calls one Postgres
function: resolve_access(person, project, x_class). The function:
- finds the most permissive active relationship,
- looks up the role template's x_ceiling,
- caps by sensitivity class.
Want to give an advisor temporary Ops access to CH05? Add an entry to
their relationship's grant_overrides JSON. Want to freeze a relationship
during a dispute? Flip access_state to frozen. The resolver does the
rest. RLS responds within the next query.
No code change. No deploy. Just a row update.