# ADR-037: Setup-Time Definitions Seed Graph Types, Runtime Instances Are Separate Graphs ## Status Accepted ## Context Access control has two phases: 1. **Setup-time**: A hub/spoke administrator registers operations with `AccessControl` definitions. These define *what* is required (scopes, resource types, actions). They are schemas for authorization. 2. **Runtime**: When a call is made, the system evaluates *whether* the caller satisfies the operation's access control by traversing the ACL graph instance. When you set up a hub/spoke, you define which operations are available. Users/orgs then work within those boundaries. The ACL graph instance stores who has access to what, grounded in the operations' access control definitions. Three approaches to relating setup-time definitions and runtime instances: 1. **Single graph** — Both operation definitions and runtime permissions are nodes in the same graph 2. **Separate graphs, shared type** — Operation definitions are in the `operations` table (setup-time), runtime permissions are in a separate ACL graph instance (runtime), but they share the same graph type definition 3. **Separate graphs, separate types** — Operation definitions and runtime permissions are entirely different graph types ## Decision Setup-time definitions (`OperationSpec.accessControl`) seed the ACL graph type definition (what node and edge types are valid in an ACL graph), but runtime permission data (who delegates to whom, who has access to which resources) lives in separate ACL graph instances. The AclGraph Module defines the *type system* for authorization (PrincipalNode, ResourceNode, DelegatesEdge, ScopesEdge, BelongsToEdge). The `OperationSpec.accessControl` definitions inform the *specific operations* that will be evaluated against this graph. But the `AccessControl` data itself lives in the hub's `operations` table, not in the ACL graph. The ACL graph instance stores runtime facts: "Account A is a member of Org O", "Principal P delegates scopes S to Agent A", "Account A has read/write access to Project X". ## Consequences **Positive:** - Clean separation of concerns — operation definitions (what's available) and ACL instances (who has access) evolve independently - The AclGraph Module can be defined in `@alkdev/storage` without importing `@alkdev/operations` — it mirrors `Identity` and `AccessControl` shapes but doesn't depend on them - Multiple ACL graph instances are possible: one per org, one global, or one per spoke context. The type system is shared; the instances differ. - Consistent with the existing pattern: CallGraph Module defines the type system; call graph instances hold specific call data. SecretGraph Module defines the type system; secret graph instances hold specific secret data. **Negative:** - Two sources of truth for "what requires access control": `operations.accessControl` (setup-time) and the ACL graph type definition (structural). These must be kept consistent by convention, not by FK. - The evaluator must bridge both: given an operation's `AccessControl`, compute the caller's effective permissions from the ACL graph instance, then apply `enforceAccess`. This is application-layer logic, not stored in the database. ## References - [acl.md](../acl.md) — ACL graph architecture specification - ADR-034: ACL is a metagraph - Operations AccessControl: `/workspace/@alkdev/operations/src/types.ts` - Hub operations table: `/workspace/@alkdev/hub/docs/architecture/storage/spokes.md`