Reference

File formats, hashes and the engine

The passive JSON formats Access writes, their hashes, and the engine's members.

Read as Markdown

File formats

Every file Access writes is passive JSON with a format string. A member the format does not define is refused, and so is a file of another format.

FormatMembersWritten by
aventara-access/policies@1format, namespace (optional), claims, schema, roles, govern, governModes (optional), grants, denies.contract(), avaccess pull
aventara-access/guardrails@1format, claims, deniesdefineGuardrails(…).contract(), avaccess pull
aventara-access/lock@1format, namespace, policies, guardrails, foreign (optional)avaccess pull
aventara-access/foreign@1format, namespace, schema (the owners' entries, each marked foreign)avaccess pull --foreign
aventara-access/beacons@1format, namespace, environment, beacons (owner to beacon hash)avaccess pull --env

A policies file's schema entry lists, beside its fields, the nullable ones (nullable) and the ones can compares by type (types: bigint, datetime or decimal). A rule's literal is written in its field's canonical form. The engine checks every member of a file it is handed, and refuses a number JSON cannot carry (NaN, Infinity).

Hashes

policiesHash(file) and guardrailsHash(file) return a file's canonical hash, "sha256:<hex>". It is the same for the same rules written in any order, and a formatter's re-indent does not change it.

Code
// scripts/policies.write.ts
import { readFileSync, writeFileSync } from "node:fs";
import { createAccessEngine, policiesHash } from "@aventara/access";
import { policies } from "../src/access.policies.js";

const file = policies.contract();
writeFileSync("policies.json", `${JSON.stringify(file, null, "\t")}\n`);
console.log(policiesHash(file)); // "sha256:…"

// Later, or in another process: the same engine, from the file alone.
const engine = createAccessEngine(
	JSON.parse(readFileSync("policies.json", "utf8")),
);
console.log(Object.keys(engine.governed)); // ["User"]

Run it from the project's root with npx tsx scripts/policies.write.ts.

The file's parts are exported as types (AccessPolicies, AccessGrantRule, AccessDenyRule, AccessGuardrails, AccessLock and the rest), so a project that emits declarations can export any part of a file. A pulled file imported as JSON fits the options that take one (AccessPoliciesJson, AccessGuardrailsJson), and the engine checks the rest when it is built.

The engine

policies.engine(options) builds the engine from the rules, and createAccessEngine(file, options) from a compiled file. Both take { guardrails, lock, requireLock }. Any other option is refused. The engine runs from its own frozen copy of the file.

MemberWhat it is
can(question)Whether the principal may do action on resource; on row when one is given
grantsOf(question)The update grants that cover a row before an update (Updates)
scopeOf(question)true, false, or an Aventara where
fieldsOf(question){ always, perRow }: always is "all" or a list of field names, perRow maps a field to the where of the rows it opens on
explain(question)Which rules took part in a can decision; throws in production
checkBeacons(reader, pins, environment)The startup beacon check (Beacons and foreign)
hosted({ held })Called by Aventara when it hosts the engine, with the Resources it holds a written reference to; warns ACCESS_REFERENCE_UNREADABLE (Rules) and ACCESS_FOREIGN_REFERENCE_UNCHECKED (Rules)
governedThe fields governed for reading ("read", "both" or the list form), by Resource
afterChecksThe Resources whose updates Aventara checks after the write
pathsEvery relation path and operator each rule filters on, by rule id
unheldThe Resources the rules read as foreign; Aventara holds no written reference to them to the caller's read scope (Rules)

Error codes

Refusals from createAccess, the registry and the engine are AccessError, exported from @aventara/access, each with a code. Error codes lists every code with its fix, and every case of ACCESS_OPTIONS_INVALID. The kiosk's codes are on The kiosk profile.