Capabilities

A capability is a single fact the runtime can answer: may this subject reach that resource, and in what role? Everything the platform lets one element do to another — a collaborator administering a circle, an API token invoking an action, an agent reading a teammate’s data, a federation peer calling across the airtight wall — is one capability. This page is the model behind the generated Capabilities catalog: what a capability is, and the single rule that keeps the whole access surface honest.

A capability is one directed edge

Access in Triform is not a scatter of per-feature permission checks. It is one directed edge between two elements:

   subject  ──[ relationship : role ]──▶  target
   (the grantee)     (credential?)        (the resource)

Read that edge from one end and you get every access concept the platform has:

ConceptThe same edge
Circle collaboratorperson → circle-root, collaborator:admin
Audience memberperson → sub-element, audience:audience
API tokenapi-token → element, service:execute (credential = token hash)
Federation peerexternal-agent → element, peer:read (credential = DID)
Agent on a teamagent → element, agent:member
Ownerthe implicit identity edge — synthesized, never stored

The trick that lets the subject always be an element is structural: a circle’s id is its circle-root element’s id — create_circle mints one uuid and uses it for both. So “this person, acting as their circle” and “this circle-root element” are the same addressable thing, with no translation layer. Owner access is the limit case: when the subject is the resource’s own host, the edge is synthesized in pure logic with zero lookups — you do not store a row saying you may touch your own data.

One owned writer, one read accessor

The rule that makes the model trustworthy is single owned writer. Exactly one place in the runtime may create, change, or revoke an access edge; exactly one place reads them. Nothing else touches the access store.

  • Writes go through one trio of functions (project_access / revoke_access / set_access_role). Every grant, every revocation, every role change funnels through them — idempotent, audited, and the only code permitted to mutate the edge table. Older grant/revoke helpers still exist but are compatibility shells that delegate inward; they never write directly.
  • Reads go through one accessor (grants_for). It synthesizes the owner hot-path in pure logic (no query when subject == host) and, for real grants, walks the element tree so a grant on a circle-root covers every descendant beneath it.

This is not a convention you have to remember — it is enforced. A CI guard fails the build on any INSERT/UPDATE/DELETE against the access edge table outside the one owned-writer module. A new read site that re-implements the membership query instead of calling the accessor is a defect, not a shortcut. One writer and one reader mean the answer to “who may reach this?” has exactly one source of truth, so it cannot drift between features.

Relationships compose; visibility is a separate axis

Two distinctions keep capabilities from collapsing into a tangle:

  • Roles form a hierarchy, declared in exactly one place. A stronger relationship satisfies the weaker ones it contains (collaborator ⊇ {agent, peer} ⊇ service ⊇ audience), and that ordering lives in a single function — not scattered across the call sites that check it. Grant someone collaborator and they satisfy a check that only required audience; the reverse fails closed.
  • “May see it at all” is a different question from “holds relationship X.” Visibility (an element’s broadcast posture — which class of subject may see it) and a grant (this specific subject holds this relationship) are separate axes. They compose — the class gate and the specific-edge lookup both run — but they are never merged into one muddy check.

Element-agnostic, like everything else

Capabilities obey the platform’s defining contract: relationship and role are free-form strings, the hierarchy lives in one resolver, and no physics or portal code branches on element type to decide access. The same edge model covers a Python action, a SQL table, a board, and a circle. That is what lets the Capabilities catalog be generated — the runtime walks data-driven tables rather than a pile of hand-written per-type rules, so the catalog and the enforcement can never disagree.

How this sits beside the catalog

The Capabilities catalog in this pillar is generated from the runtime’s own operation and access data — every element’s operations, their per-operation auth levels, and the forces each element draws on. This page is the model; the catalog is the enumeration. Read this to understand what a capability is, then browse the catalog to see which capabilities a given element actually exposes.

Related

  • Concept: auth — authenticating as a circle; per-operation auth levels; api-token vs auth-policy
  • Concept: tenancy-airtight — the boundary access edges respect
  • Concept: federation — capabilities that cross the circle boundary
  • Pillar: Runtime — the executor that checks a capability before it runs an operation