Access model
The shared core of Arkveil — attributes, targets, policies, the formula language, and the semantics every decision follows.
The access model is the artifact Arkveil evaluates. Everything that determines who may do what lives in it, as data. This page is the shared vocabulary for the rest of the documentation: the pieces every policy uses, and the semantics every decision follows. What is specific to protecting operations or protecting data gets its own page.
What the model consists of
- Attributes describe a request: who is acting, what operation, which parameters, which data, under which circumstances.
- Actions are the operations your application asks about, named by
service:namecodes and optionally tagged. - Datasets are the protected tables, named by
datasource.schema.tablecodes. - Targets decide where policies apply. A target scopes either actions or data.
- Policies are the grants. Each policy attaches to one target and carries a condition.
- Tests are executable specifications of intended behavior.
Actions and datasets form the authorization surface. Targets select parts of it. Policies grant access within their target's scope. Tests pin the outcomes while the model evolves.
Attributes
Conditions reason over five namespaces:
| Namespace | Meaning | Schema lives |
|---|---|---|
user.* | who is making the request | workspace-wide — arkveil schemas get user |
action.* | the operation, including action.tags | workspace-wide — arkveil schemas get action |
context.* | runtime circumstances | workspace-wide — arkveil schemas get context |
request.* | parameters of this particular call | per action — its requestSchema |
data.* | columns of the protected table | per dataset — its dataSchema |
Attribute paths nest with dots, like user.profile.age. The exception is data.<column>, which names a single column and is available only where a dataset row is in scope — a data-policy filter, or the body of a dataset exists. request.* is available to action policies, following the schema declared on the action.
Schemas are enforced when a formula is saved. A condition that references an unknown attribute or a namespace not allowed in its context fails at parse time, not at runtime.
Roles are not special. user.role is an ordinary attribute, and access can turn on department, region, clearance, or anything else the schemas define.
Targets
A policy never applies globally. It attaches to a target, and the target decides the scope. Targets come in two types — ACTION and DATA — and three modes:
- INDIVIDUAL — exactly one action or dataset.
- CUSTOM — a group selected by a condition. For actions this usually means tags:
any action.tags where it = "payments"covers every action taggedpayments, and the group updates as actions are tagged and untagged. - ALL — everything of the target's type.
How each kind selects its scope in detail — action codes and tags on one side, dataset codes on the other — belongs to Action policies and Data policies.
Policies
A policy is a grant attached to one target.
- Type matches the concern: PERMISSION policies grant actions, READ and WRITE policies govern data.
- Status gates participation: a policy takes part in decisions while it is ENABLED. DRAFT and DISABLED policies are inert.
- Condition is a formula. The policy grants when its condition evaluates to true. A policy without a condition is inactive.
Policy semantics are permit-only. There is no DENY effect and no combination algorithm: access is granted when at least one applicable policy's condition is true, and denied otherwise. Adding a policy can only widen access, and disabling one can only narrow it.
The formula language
A condition is one boolean expression, stored in the model and evaluated against the request's attributes.
Comparisons and predicates. Equality is a single =, alongside !=, >, >=, <, <=. String predicates: contains, startsWith, matches (regular expression), each with an IgnoreCase variant. Presence and set checks: is null, is not null, in [...], not in [...], is empty, is not empty.
Literals. Strings are double-quoted. Decimals need digits on both sides of the dot. Arrays are non-empty, single-typed, and hold literals only.
Boolean logic. and, or, not, with parentheses for grouping. Precedence from lowest to highest: or, and, not.
Iteration. Array attributes are tested element by element, with it naming the current element:
any user.permissions where it startsWith "billing:"
none action.tags where it = "blocked"
all request.items as line where line.it != ""Dataset references. A PERMISSION condition may ask whether a matching row exists, looked up at decision time:
exists demo_billing.public.invoice where data.id = request.invoiceId and data.owner_id = user.idInside the body, data.<column> reads the referenced dataset's columns. Prefer the full datasource.schema.table code — a bare table name (exists invoice where …) binds when the policy is saved and must match exactly one dataset. Evaluating a dataset reference requires a connected runtime. Asked of Arkveil Cloud alone, such a condition does not grant — the decision stays denied, fail-safe.
exists is the text form of a wider family. The runtime also evaluates FETCH_ONE and FETCH references, which pull a matching row's values into a condition instead of only checking presence. Today those two are authored programmatically through the API. Text syntax for them is planned, with the same reference rules.
Validate any formula before using it with arkveil formula parse. The complete reference, including everything this page leaves out, is arkveil formula syntax.
Evaluation
A decision starts with a request: user, context, and request attributes plus the action or the data operation. From there:
- Targets select the candidate policies — every ENABLED policy whose target covers the request.
- Each candidate's condition evaluates with the request's attribute values substituted in.
- The decision is granted when at least one condition is true, and denied otherwise.
Two properties make the semantics predictable:
- Unknown collapses to deny. A missing attribute or a type-mismatched comparison normalizes to UNKNOWN, and UNKNOWN propagates so the policy does not grant. A policy can fail safe. It cannot fail open.
- Changes are monotonic. Adding a policy can only widen access, and removing or disabling one can only narrow it. Negative tests pin the boundaries that must stay closed while the model grows.
Every decision is explainable. arkveil eval explain -a <code> --user '<json>' names the granting policies and the candidates. --json returns the full per-node evaluation trace, in the form coding agents consume. Arkveil Studio shows the same traces visually, for humans.
One model, two responsibilities
Action policies decide whether an operation may happen. Data policies decide which rows it may see or change. The two are evaluated independently and enforced together, so a policy permitting an action never restates the rules that constrain its data. Everything on this page — attributes, targets, conditions, permit-only evaluation — is common to both.
Next steps
-
Action policies — this anatomy projected onto operations.
-
Getting started — the model in action, from seed to a protected endpoint.
-
CLI — the commands this page references.
-
Policy tests — the executable specifications that pin these semantics.