The product grammar
A product is a directory in a conventional shape: a path is an identity (a workload's name is its directory, an entity's name is its file), and every setting has one home. aircloud check validates the tree with the platform's own engine. The keys below are read from the grammar the validator compiles; a key outside them is refused (schema.unknown_key).
Layout
| path | holds | owning motor |
|---|---|---|
aircloud.toml |
grammar version, product slug, time_zone, [i18n], [release] and [generators] preferences | Air-Deploy |
AGENTS.md |
this agent map (generated; never edit) | Air-Deploy (generator) |
.tool-versions, .nvmrc |
the node version asdf and mise (.tool-versions), nvm and fnm (.nvmrc) switch to: the platform's pin, which every package.json engines range admits (written by aircloud new) | Air-Deploy |
.gitignore, .gitattributes |
what git never tracks — exactly what an upload leaves out: installed dependencies, build output, derived inputs, .env files — and the files git marks generated (written by aircloud new unless --skip-git) | Air-Deploy |
app/functions/<name>/function.toml |
one function: code + declaration | Air-Functions |
app/containers/<name>/container.toml |
one App: Dockerfile + declaration | Air-Deploy (Apps) |
app/site/ (optional site.toml) |
the product's single site | Air-Deploy (site) |
app/site/index.html, app/site/package.json, app/site/package-lock.json, app/site/.npmrc, app/site/vite.config.js, app/site/jsconfig.json, app/site/<name>.json |
a built site's entry document, dependencies (engine-strict), build configuration and the @/ → src/ path; a top-level <name>.json is an inert tooling manifest such as components.json, never dot-prefixed and never tsconfig.json — a dot-prefixed .json is a tool rc file the build reads implicitly (.postcssrc.json), a second carrier of build configuration beside vite.config.js, and tsconfig.json a second carrier of jsconfig.json's paths (TypeScript is not a site language): both refused | Air-Deploy (site) |
app/site/public/ |
static files published verbatim (the static template holds only this) | Air-Deploy (site) |
app/site/src/main.jsx |
the boot module: the CSP nonce stamp, then the application — side-effect imports only, @aircloudhq/web/csp-nonce first (site.boot_invalid) |
Air-Deploy (site) |
app/site/src/application.jsx, app/site/src/routes.js |
the application root (layouts, providers, router) and the named routes | Air-Deploy (site) |
app/site/src/layouts/<name>.jsx |
one layout; application is the default | Air-Deploy (site) |
app/site/src/components/<name>.jsx, app/site/src/components/**/<name>.jsx |
Builder components (.jsx or .js); nested directories admitted, mirrored by test/site/components/ | Air-Deploy (site) |
app/site/src/helpers/<name>.js |
one Builder helper | Air-Deploy (site) |
app/site/src/pages/<route>/index.jsx |
one page, one directory per route | Air-Deploy (site) |
app/site/src/styles/ |
styles and theme tokens | Air-Deploy (site) |
app/site/.aircloud/ |
derived inputs: resource projection, locale bundle, compiled schema, compiled entry model (aircloud test prepare: the routes and triggers the testing double invokes through) (git-ignored; never uploaded) |
Air-Reflect (projection), Air-Deploy (bundle, tests) |
.aircloud/locale-bundle.json |
the derived locale bundle the tests read (aircloud i18n bundle): at app/site/.aircloud/locale-bundle.json when app/site/ exists, else here (git-ignored; never uploaded) |
Air-Deploy (bundle) |
.aircloud/schema.json |
the compiled schema the unit tests converge the testing double to (aircloud test prepare, Rails' db/schema.rb; world.flex.declareFromSchema() reads it): at app/site/.aircloud/schema.json when app/site/ exists, else here (git-ignored; never uploaded) |
Air-Flex (schema), Air-Deploy (tests) |
app/flows/, app/workers/, app/batch/ |
reserved (#618, #333, #334): refused | their motors |
db/schema/<entity>.toml |
one Flex entity | Air-Flex |
db/seeds/<environment>/<entity>.toml |
seed records of one environment | Air-Flex |
db/seeds/<environment>/nodes/<template>/<entity>.toml |
a node seed template: records applied at each node created with it (a workspace's seed, the org-nodes create) |
Air-Flex, Air-Auth |
db/migrate/<YYYYMMDDHHMMSS>_<name>/migration.toml |
one expand-only data migration | Air-Flex |
config/routes.toml |
the product route table | Air-Deploy |
config/secrets.toml |
secret names, who may use each, egress allowlists (never values) | Air-Deploy (platform Vault) |
config/profiles.toml |
the base profiles: named sets of [authenticate] roles, granted to members by reference | Air-Auth |
config/environments/preview.toml |
overlay for every non-production environment | Air-Deploy |
config/environments/<environment>.toml |
overlay for one environment | Air-Deploy |
config/locales/<locale>.yml, config/locales/**/<name>.<locale>.yml |
translations of one locale, Rails keys | Air-Deploy (i18n) |
config/content_security_policy.toml |
the declared Content-Security-Policy | Air-Deploy (web-origin) |
config/permissions_policy.toml |
the declared Permissions-Policy | Air-Deploy (web-origin) |
lib/<name>/component.toml |
one shared component | Air-Functions |
modules/ |
reserved (#649): refused | Air-Deploy |
test/functions/<name>/ |
unit tests of function <name> | Air-Deploy |
test/lib/<name>/ |
unit tests of component <name> | Air-Deploy |
test/migrations/<name>/ |
unit tests of migration directory <name> | Air-Deploy |
test/site/helpers/<name>.test.js, test/site/components/**/<name>.test.jsx, test/site/layouts/<name>.test.jsx |
site unit tests (Vitest); test/site/ mirrors src/, so components/ nests as src/components/ does | Air-Deploy |
test/integration/<name>.toml |
one integration scenario (HTTP steps) | Air-Deploy |
test/principals.toml |
the end users the integration and system tests act as (as = "<name>", signInAs), verification-only |
Air-Deploy |
test/system/ |
system tests (Playwright) | Air-Deploy |
test/fixtures/<entity>.toml |
fixture records of ephemeral environments | Air-Deploy |
test/package.json, test/package-lock.json, test/.npmrc |
test-layer dependencies (engine-strict) | Air-Deploy |
test/layers.mjs |
the script npm test and its pretest run (prepare the layers, then run them) |
Air-Deploy |
Files
aircloud.toml
aircloud.toml — the product root (ADR-213 D1, D3, D4).
account(account_slug) — The Builder Account this product belongs to (ADR-278 decision 2), written withregionbyaircloud newandaircloud products authorize: a command run in the product's folder acts as this account in that region, with no network call to choose it. Absent = the account selected on the machine.generators(object) — Generation preferences (DX21): flag > this preference > the grammar default. Defaults only, never constraints.generators.css(one of "tailwind" | "css")generators.runtime(runtime)generators.site(one of "react" | "static" | "none")
grammar(required) — The grammar version this product was written against; pins the convention set (ADR-213 D4).i18n(i18n)name(string) — The product's human name (Rails names the application atrails new; #755 D16), written byaircloud new --name: the site's document title, in any script. The slug stays the identity. Absent = the humanized slug.product(required,product_slug)region(region_name) — The region the product lives in (ADR-278 decision 2): withaccount, the session the CLI acts with in this folder. Absent = the selected account's region.release(object)release.promote(one of "auto" | "manual") — Promotion of a verified release to production (ADR-218 D3). Default auto.
time_zone(string) — The product's time zone, an IANA zone name (Rails' config.time_zone; ADR-223 D2, D8). Rendering uses it from the locale bundle's rules; datetimes are stored in UTC. Absent = UTC. A well-formed name the vendored tzdata does not carry is i18n.config_invalid.
aircloud.toml [i18n]
aircloud.toml [i18n] (ADR-223 D2; spec S4 § [i18n]) — Rails' config.i18n.*. Absent section = default_locale en, available_locales [en], no fallbacks. available_locales includes default_locale, every locale a fallback chain names (as its key or in its list) is available, and chains do not cycle: i18n.config_invalid. At run time every chain ends implicitly at default_locale; the build-time completeness check applies only the declared entries. currency (ADR-255) is the product's ISO 4217 currency — the default display currency of a plain number, rendered with the viewer locale's CLDR symbol and pattern and the currency's own minor unit; it must be a code the pinned CLDR records as in tender use (i18n.config_invalid). Absent, no currency is assumed: a currency-formatting call without its own unit is refused at build time.
available_locales(required, array oflocale_id)currency(string) — An ISO 4217 alphabetic code ("BRL"), one of the pinned CLDR's active codes (ADR-255 decision 1).default_locale(required,locale_id)fallbacks(map of array oflocale_id) — An explicit fallback chain per locale: locale -> the locales tried after it, in order.
app/containers/<name>/container.toml
app/containers/<name>/container.toml (ADR-213 D9; the delivered Apps surface).
database(object) — Managed-DB attach (ADR-090 D2): the product's dedicated database for this container, provisioned and bound by the platform.database.attach(boolean)
dockerfile(string)env(map of string) — Non-secret environment values; overridable in environment overlays.health_path(string)migrate_command(string)port(integer)secrets(array ofsecret_name) — Declared secrets injected as env vars of the same name (DX32).
app/functions/<name>/function.toml
app/functions/<name>/function.toml (ADR-213 D1, D9). The function's name is its directory. HTTP exposure is not declared here: it is derived from config/routes.toml (ADR-214 D3).
components(array ofworkload_name) — Shared components of this product (lib/<name>/) composed into the function at build, by name (DX33).elevate(array of object) — Declared elevated aggregates (ADR-295 decision 8, Scope 12): each[[elevate]]lifts the owner scope of one entity for exactly the declared aggregate statement, signed per call by the functions-host and verified by Flex; row reads stay owner-scoped, and tenant, org_path, field-policy and deny isolation still apply. No identifying field — the record id, the entity's owner field, a unique or natural-key field — may appear in filter, group_by or fields (elevate.field_identifier), and each of them is a live field the entity declares (elevate.field_unknown).elevate[].entity(required,flex_name) — The entity whose owner scope the aggregate lifts: a live entity of db/schema (elevate.entity_missing).elevate[].fields(array offlex_name) — The value fields sum, min, max and avg may aggregate.elevate[].filter(array offlex_name) — The entity's fields the aggregate may filter on.elevate[].group_by(array offlex_name) — The entity's fields the aggregate may group by.elevate[].ops(required, array of string) — A non-empty set of $defs/elevate_op names, checked by the validator against that $def (elevate.op_invalid).elevate[].scope(required, string) — A $defs/elevate_scope value, checked by the validator against that $def (elevate.scope_invalid).
entry(string) — Build entry: js = the module file (default index.js); rust = the cargo package directory (default .).limits(limits)mcp(object) — MCP tenant-capability facet (ADR-106 f3–f5), carried over unchanged from the retired per-function manifest (ADR-213 D9).mcp.effect(required, one of "none" | "tenant-data" | "external")mcp.expose(boolean)mcp.org_path(required,org_path) — The org node the tool's catalog row is stored at and acts at (#384 D90, ADR-295 decision 1): declared, never defaulted —/only when written. Its absence is schema.required; the catalog never reads absence as/.mcp.required_roles(array of string)mcp.sandbox_mode(one of "capture" | "test-mode" | "deny")
org(object) — The tree operations this function may call (ADR-295 decision 6, D30): a function calls only the org-nodes, members, profiles, groups, denies and workspaces operations it declares, each with the authority of its acting node; the functions-host refuses an undeclared one with capability.forbidden without calling the gateway.org.operations(required, array of string) — Each a name of the closed vocabulary $defs/org_operation, checked by the validator against that $def: a name outside it is org.operation_unknown.
runtime(required,runtime)status(one of "draft" | "published")triggers(object)triggers.cron(array of object)triggers.cron[].org_path(org_path) — The org node this cron trigger's invocations act at (ADR-295 decision 1): declared, never defaulted —/only when written; a sweep over several child nodes is declared at their parent and narrows to each child at run time. Its absence is trigger.org_path_required (a dedicated rule, not schema.required).triggers.cron[].schedule(required, string) — 5-field cron expression, UTC, minimum interval one minute (semantic validation server-side).
triggers.events(array of object)triggers.events[].entity(flex_name) — Entity selector (#250): the trigger fires only for events about records of this entity — the dispatcher compares it with the delivered envelope's entity_type before it claims or invokes, so an event about another entity is never an invocation and never metered. Absent, the trigger fires for every entity. Valid only on a Flex record topic (source flex, a topic whose every action's data carries record_id — function.trigger_entity_unbound) and only for a live declared entity (db/schema/<entity>.toml — function.trigger_entity_missing).triggers.events[].org_path(org_path) — The org node this event trigger's invocations act at (ADR-295 decision 1): declared, never defaulted —/only when written. Its absence is trigger.org_path_required (a dedicated rule, not schema.required).triggers.events[].source(one of "app" | "comms" | "flex") — Trigger source domain (ADR-192 facet 4(b)). Absent means app — the Builder's own v1.app.<tenant_id>.<environment>.<l2>.<topic> space (#384 D40, contracts/bus/subjects-v1.json app_subject): <l2> is the first segment of the node the event was published at, or app_subject.root_l2_token for the root node. A platform domain binds v1.<domain>.<tenant_id>.<topic> in the production environment only (a preview activation skips the binding). The topic rule depends on this key and allOf expresses it: the app space by the dot-separated slug pattern, each platform domain by the closed set of its bindable subject tails — a projection of the ADR-162 event registry (contracts/bus/event-registry-v1.json) narrowed to tails every projecting action declares data for. Held equal to the registry, and this enum to app plus the admitted domains, by test/unit/deploy_contract_gates.bats (gate_deploy_trigger_source_parity).triggers.events[].topic(required, string) — own-tenant subject topic (dot-separated slug segments) — the grammar has no cross-tenant form (least privilege)
app/site/site.toml
app/site/site.toml (ADR-216 D1). Absent file = every default.
build— Build command; false = no build (publish the output directory as is). Default:npm run buildwhen app/site/package.json exists, else false.fallback(one of "spa" | "none")output(string) — Directory (relative to app/site/) to publish. Default: dist when a build runs, public otherwise.
config/content_security_policy.toml
config/content_security_policy.toml (ADR-224 D3–D5; spec S4 § Security headers) — Rails' content_security_policy initializer. [policy] keys are the CSP directives in snake_case (this object is their only carrier); the web-origin emits the policy on the site responses of the release it serves. 'unsafe-inline' or 'unsafe-eval' in the effective script source list (script_src, else default_src) without the matching opt-in is csp.unsafe_script.
allow_unsafe_eval(boolean) — The explicit opt-in that admits 'unsafe-eval' in the effective script source list (ADR-224 D5).allow_unsafe_inline_scripts(boolean) — The explicit opt-in that admits 'unsafe-inline' in the effective script source list (ADR-224 D5).nonce_directives(array of one of "script_src" | "style_src") — Rails' content_security_policy_nonce_directives (ADR-270 D4): the platform appends a fresh per-response 'nonce-…' source to each listed directive of the site HTML that carries the nonce slot. A listed directive [policy] does not declare (the default included), one holding 'unsafe-inline' or 'none', and any declared 'nonce-…' source are csp.policy_invalid; [] sends no nonce.policy(required, object)policy.base_uri(csp_source_list)policy.connect_src(csp_source_list)policy.default_src(csp_source_list)policy.font_src(csp_source_list)policy.form_action(csp_source_list)policy.frame_ancestors(csp_source_list)policy.frame_src(csp_source_list)policy.img_src(csp_source_list)policy.manifest_src(csp_source_list)policy.media_src(csp_source_list)policy.object_src(csp_source_list)policy.script_src(csp_source_list)policy.style_src(csp_source_list)policy.worker_src(csp_source_list)
report_only(boolean) — true = sent as Content-Security-Policy-Report-Only. The frame-ancestors floor of ADR-224 D2 is enforced regardless.report_uri(string) — Where violation reports go: an absolute https URL or a path on the product host.
config/environments/preview.toml
config/environments/preview.toml and config/environments/<name>.toml (ADR-213 D5). Only overridable keys.
containers(map of object)containers.<name>.env(map of string)
functions(map of object)functions.<name>.limits(limits)
config/permissions_policy.toml
config/permissions_policy.toml (ADR-224 D3, D4; spec S4 § Security headers) — Rails' permissions_policy initializer. Keys are the W3C Permissions Policy standardized features (this object is their only carrier, additive), each an allowlist.
accelerometer(permissions_allowlist)autoplay(permissions_allowlist)camera(permissions_allowlist)display-capture(permissions_allowlist)encrypted-media(permissions_allowlist)fullscreen(permissions_allowlist)geolocation(permissions_allowlist)gyroscope(permissions_allowlist)magnetometer(permissions_allowlist)microphone(permissions_allowlist)midi(permissions_allowlist)payment(permissions_allowlist)picture-in-picture(permissions_allowlist)publickey-credentials-get(permissions_allowlist)screen-wake-lock(permissions_allowlist)sync-xhr(permissions_allowlist)usb(permissions_allowlist)xr-spatial-tracking(permissions_allowlist)
config/profiles.toml
config/profiles.toml (ADR-293 decision 1): the product's base profiles, each a named set of [authenticate] roles keys (profile.role_undeclared refuses a key the vocabulary does not declare). The active production release is their one carrier: production activation converges them at /; a declared profile removed from the release is deleted when no grant references it and refused-and-reported when one does. Runtime profiles authored at a node below / live in the SSoT, never here.
profiles(map of object)profiles.<name>.roles(required, array ofrole_key) — The profile's roles: each a key of[authenticate] roles(profile.role_undeclared).
config/routes.toml
config/routes.toml (ADR-214 D3). The only public HTTP exposure of the product's workloads. [[route]] entries are PUBLIC; the [authenticate] block (#632, ADR-241, operator decision P22 — Rails authenticate do ... end) holds every route that requires an end-user session, which the platform enforces before any target runs. A route's posture is read off its position, never off an attribute.
authenticate(object) — The authenticated surface (#632, ADR-241, P22): every[[authenticate.route]]requires an end-user session. Without one, a site page answers a redirect tosign_in(with return_to) and every other request the published session_required refusal.authenticate.admin_org_path(org_path_below_root) — The node of the product's first admin, the account owner invited with the whole vocabulary and tenant:org at the first production activation declaringroles(ADR-254 decision 8); required withroles, refused without.authenticate.default_org_path(org_path_below_root) — The node a self-registered end user is placed at (registration = "open" only), strictly below admin_org_path (authenticate.default_not_below_admin).authenticate.default_roles(array ofrole_key) — The roles a self-registered end user receives (registration = "open" only), each a key ofroles.authenticate.nodes(array) — The product's further fixed org nodes (ADR-254 amendment 2026-10-29): each production activation creates every node listed, with its ancestors, beside admin_org_path, default_org_path and the workspaces parent, so the product may name it. An entry is the node's path, or an inline table{ path, display_name }naming it for people (#785): each production activation sets a declared display_name on its node and updates it when the declaration changes; an entry without one leaves the node's name as it is. A node removed from the list is never deleted by a release — archiving or erasing a node is the Builder's explicit act (ADR-295). Each path is listed once, in either form (schema.range). Every org_path and routeatthe product names is one of these posture nodes or an ancestor, the root/, or strictly below the workspaces parent (org.path_undeclared).authenticate.registration(one of "invite" | "open") — Who may become an end user (ADR-254 decision 1):invite— only the Builder's members API places end users;open— a stranger may self-register, placed at default_org_path with default_roles.authenticate.roles(array ofrole_key) — The product's end-user role vocabulary (ADR-254 decision 1), declared once: the only source default_roles, admin bootstrap, route roles and owner_bypass_roles draw from.authenticate.route(array of object)authenticate.route[].at(org_path) — The node this route'srolesare evaluated at (ADR-292 decision 1, operator decision D32): the selected membership passes only when one ofrolesis effective, in ADR-293's authority projection, at this node or at a node below it. Absent: any ofrolesheld by the selected membership passes (ADR-254 decision 4). Required (authenticate.route_at_required) on a route whoserolesinclude a role of the[authenticate.workspaces] admin_profileprofile.authenticate.route[].container(workload_name)authenticate.route[].flow(string)authenticate.route[].function(workload_name)authenticate.route[].module(string)authenticate.route[].prefix(required, string)authenticate.route[].roles(array ofrole_key) — Any-of narrowing of an authenticated route (ADR-254 decision 1): a session holding none of these keys is refused session_role_required; each a key of[authenticate] roles. Absent: any session holder passes.authenticate.route[].site— The site's pages under the prefix (the SPA's own routes) require the session.authenticate.route[].strip_prefix(boolean)
authenticate.sign_in(string) — The path a page inside the block redirects to without a session: the platform's hosted sign-in (default) or a public product path outside the block, the home page/included.authenticate.workspaces(object) — Workspace signup (ADR-292 decision 1): a post-login platform action (/_air/workspaces, theworkspaces.createoperation) creating one node strictly belowparentand one new membership for the caller homed there, with a home grant ofadmin_profileby reference and tenant:org. Not a registration posture: registration stays ADR-254's. Requires[authenticate] roles(schema.shape). Cross-key rules: workspaces.parent_not_below_admin, workspaces.parent_nests_default, workspaces.parent_too_deep, workspaces.profile_undeclared, workspaces.seed_undeclared.authenticate.workspaces.admin_profile(required,profile_name) — The base profile (config/profiles.toml) granted by reference to the workspace's creator at the new node (workspaces.profile_undeclared).authenticate.workspaces.name(required, one of "slug" | "token") — How the new node's segment is derived:slug— from a display name the signer supplies, folded to the org-path segment grammar, suffixed on a sibling collision, falling back totokenwhen it slugs to nothing;token— an opaque random segment, the display name never enters the path.authenticate.workspaces.parent(required,org_path_below_root) — The node every workspace is created under: strictly below/, at-or-below admin_org_path (workspaces.parent_not_below_admin), not at-or-below default_org_path (workspaces.parent_nests_default), and shallower than contracts/auth/v1/org-path.json max_depth so a child node exists (workspaces.parent_too_deep).authenticate.workspaces.seed(seed_template_name) — Optional: the node seed template db/seeds/<environment>/nodes/<seed>/ applied at each new workspace node (ADR-295 decision 7). A release declaring it must carry the template (workspaces.seed_undeclared).
route(array of object)route[].container(workload_name)route[].flow(string) — Reserved (#618): refused at validation.route[].function(workload_name)route[].module(string) — Reserved (#649): refused at validation.route[].org_path(org_path) — The org node this public route's invocations act at (ADR-295 decision 1): a public route has no end user, so its acting node is declared, never defaulted. Every public[[route]]that targets a function declares it —/acts at the tenant root and is valid only when written. Its absence is route.org_path_required (a dedicated rule, not schema.required); on a route that targets a container, which runs no invocation, its presence is route.org_path_not_invocation. The functions-host refuses an entry whose release model carries no node with functions.org_path_undeclared, never reading absence as/.route[].prefix(required, string) — Path prefix: one or more /-separated segments, no trailing slash, no "." or ".." segment.route[].strip_prefix(boolean) — Whether the target sees the path with the prefix removed. Default false: the target sees the request path as sent.
config/secrets.toml
config/secrets.toml (ADR-213 D7): names and use, never values.
secret(map of object)secret.<name>.description(string)secret.<name>.egress(array of string) — Destination hosts a function may send this secret to through the egress handle (ADR-098 D2). Absent or empty = no egress (HMAC use only).secret.<name>.header(string) — The header name when inject = header.secret.<name>.inject(one of "bearer" | "basic" | "header") — How the governed egress path attaches the secret to an outbound request (the ADR-098 credential types): bearer, basic, or a named header. Required when egress is declared.secret.<name>.key_encoding(one of "text" | "base64" | "hex" | "standard-webhooks") — How the stored value becomes the HMAC key of an hmac credential (a secret without egress; #645 DX143): text = the value's bytes; base64 / hex = the value decoded; standard-webhooks = the value without itswhsec_prefix, base64-decoded (the Standard Webhooks secret format). A value that does not decode is refused when it is set (secret_value_encoding_invalid) and, set before the declaration changed, at use (capability.credential_key_invalid). Default text. Not declarable beside egress: an egress secret is injected as its text.secret.<name>.used_by(required, array of string) — Workloads that may use the secret:function:<name>orcontainer:<name>.
db/migrate/<YYYYMMDDHHMMSS>_<name>/migration.toml
db/migrate/<YYYYMMDDHHMMSS>_<name>/migration.toml — an expand-only data migration (ADR-217 D7).
entry(string)runtime(required,runtime)
db/schema/<entity>.toml
db/schema/<entity>.toml (ADR-217 D1). The entity name is the file name (a flex_name).
display_name(string)fields(map offield)owner(flex_name) — The row owner (ADR-257 decision 1): a live string field of this entity (one line, ADR-283 decision 7) holding the owning end user's sub. Flex scopes every acting end user without a bypass role to the records they own, and forces the owner on create.owner_bypass_roles(array ofrole_key) — Role keys of[authenticate] roleswhose holders are not owner-scoped on this entity (ADR-257 decision 1); valid only withowner.policy(array) — Field policy (ADR-294 decision 1):[[policy]]tables, the base layer at/. The validator checks each entry whole against $defs/policy_rule — keys, effect, the condition grammar and its namespaces, ops and combinators — and against the entity (a field it declares, the op×type legality of a record.<field> leaf, the Flex filter caps), and reports any failure as schema.policy_invalid rather than a generic schema rule: there is no expression escape hatch.removed(boolean) — Tombstone (ADR-217 D5).shared(boolean) — Records stored at an ancestor node are readable (SELECT only) by a node's sessions (ADR-257 decision 7); never withowner(schema.owner_shared).write_roles(object) — The end-user roles each record write requires (ADR-279 decision 1): an any-of list per operation, each a key of[authenticate] roles. An absent list means the operation requires no role. Binds an acting end user only, with the roles it holds at the record's node (#384); a call with no acting end user acts at the node its entry declares or derives (ADR-295 decision 1) and is not checked against it, though a deny still binds it (ADR-293 decision 5). Never restricts reads.write_roles.create(write_role_list)write_roles.delete(write_role_list)write_roles.update(write_role_list)
db/seeds/<environment>/<entity>.toml
db/seeds/<environment>/<entity>.toml, db/seeds/<environment>/nodes/<template>/<entity>.toml and test/fixtures/<entity>.toml (ADR-217 D6): records keyed by a stable natural key; a relation value names the target record's key. A node seed template (ADR-295 decision 7) is the set of entity files under db/seeds/<environment>/nodes/<template>/ (<template> a seed_template_name): applied at a node when it is created with that template — by the org-nodes create operation or a workspace (ADR-292) — through the same seed surface acting at that node, idempotently on the natural keys, which are unique per org_path.
records(map of object)
lib/<name>/component.toml
lib/<name>/component.toml — a shared component of the product's functions (DX33).
entry(string)runtime(required,runtime)
test/integration/<name>.toml
test/integration/<name>.toml (ADR-218 D1, ADR-221 D7): ordered HTTP steps against the product host — a Rails integration test.
step(required, array of object)step[].as(test_principal_name) — The test principal this step acts as (ADR-262): a name declared in test/principals.toml. The request carries that principal's verification session; absent, the step is anonymous.step[].capture(map of string) — Name → JSON-path; a captured value is interpolated as {{name}} in later steps' path, headers and body.step[].expect(required, object)step[].expect.headers(map of string)step[].expect.json(object) — JSON-path (RFC 9535) → expected value.step[].expect.status(required, integer)
step[].name(required, string)step[].request(required, object)step[].request.bodystep[].request.headers(map of string)step[].request.method(required, one of "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS")step[].request.path(required, string)
test/principals.toml
test/principals.toml (ADR-262): the end users the release's integration and system tests act as. Each is minted by the platform only inside the release's ci-* verification environment, never in production.
principals(required, map of object)principals.<name>.grants(array of object) — Base profiles of config/profiles.toml the principal holds at nodes at or below its org node (ADR-293 decision 3): a grant's roles are effective at its node and below, never at a sibling or an ancestor, until it expires (test.principal_profile_undeclared, test.principal_grant_outside_home otherwise).principals.<name>.grants[].expires(string) — When the grant stops authorizing: a TOML offset date-time (RFC 3339 with Z or an offset). Absent, it never expires.principals.<name>.grants[].node(required,org_path_below_root) — The node the grant is placed at: the principal's org node or a node below it.principals.<name>.grants[].profile(required,profile_name) — The base profile granted by reference: a[profiles.<name>]of config/profiles.toml.
principals.<name>.org_path(org_path_below_root) — The org node the principal sits at; absent, the node an end user of that kind gets: [authenticate] default_org_path under open registration, admin_org_path under invite.principals.<name>.roles(required, array ofrole_key) — Keys of [authenticate] roles the principal holds (test.principal_role_unknown otherwise).
Shared shapes
account_slug
A Builder Account's slug (ADR-278 decision 2): the account_slug grammar of contracts/auth/v1/account-lifecycle.json — its pattern and length bounds, held equal by TestGrammarAccountSlugParity. Whether the account exists is the platform's to say, never the grammar's.
A string matching ^[a-z][a-z0-9]*(-[a-z0-9]+)*$.
csp_source_list
One CSP directive's source list (CSP Level 3): source expressions — keywords quoted ('self', 'none', 'unsafe-inline', ...), schemes (data:), hosts, nonces and hashes. A malformed expression, or 'none' beside another, is csp.policy_invalid. So is a declared 'nonce-…' source: the platform issues nonces per response to the directives in nonce_directives (ADR-270).
A array of string.
field
default— The field's default (ADR-259 decision 1): a literal — the field's I4 representation written as the TOML value of the same JSON type (a decimal as a string, a date or datetime as a quoted string) — or{ generator = "<name>" }from the closed set contracts/flex/v1/types.json default_generators declares. Flex applies it on create when the field's key is absent. Judged by schema.default_invalid; refused on relation, file and vector fields and on the owner field (schema.default_not_allowed).dims(integer) — Vector dimensions.display_name(string)removed(boolean)removed_values(array offlex_name) — An enum's tombstoned values, in order (ADR-258 decision 4): rows keep them, filters find them, no write sets them; a removed value is never dropped.required(boolean)target(flex_name) — Relation target entity, by name (Flex maps it to target_entity_id).type(required, one of "text" | "integer" | "decimal" | "boolean" | "datetime" | "date" | "json" | "relation" | "file" | "vector" | "enum" | "string") — The Flex v1 type set (contracts/flex/v1/types.json .types); held equal by gate_deploy_flex_type_parity.value_display_names(map of string) — An enum value's default-locale label (ADR-258 decision 7); absent means the value itself. Every key names a declared value (schema.enum_values_invalid). Presentation only: it never reaches Flex.values(array offlex_name) — An enum's live values, in declared (display and sort) order (ADR-258 decision 1). At most the Flex live-value cap (contracts/flex/v1/types.json types.enum.config_keys.values.max_items, held equal by gate_deploy_enum_caps_parity); a value appears at most once across values and removed_values, and the two lists together hold at most types.enum.declared_values_max values (schema.enum_values_invalid).
flex_name
Flex API name of an entity or field (contracts/flex/v1/identifiers.json .identifiers.slug.regex); held equal to it by gate_deploy_flex_name_parity.
A string matching ^[a-z][a-z0-9_]{0,62}$.
limits
Per-function limit overrides (heritage of the retired per-function manifest, ADR-213 D9): clamped to plan/platform ceilings; an over-ceiling value is refused, never silently clamped. Overridable in environment overlays.
deadline_ms(integer)memory_bytes(integer)
locale_id
A locale id (ADR-223 D2): BCP 47 language[-Script][-REGION] — e.g. pt-BR, en, zh-Hant-TW. A well-formed id outside the platform's supported-locale set (the vendored CLDR locales, vendor/i18n/PROVENANCE.json cldr_locales) is i18n.config_invalid.
A string matching ^[a-z]{2,3}(-[A-Z][a-z]{3})?(-([A-Z]{2}|[0-9]{3}))?$.
org_path
An org node, the tenant root included (ADR-295 decision 1, ADR-292 decision 1): exactly /, or an org_path_below_root. / is valid only where it is written: no grammar key defaults to it, so an entry that acts at the root says so in the repository. An invalid value is schema.shape.
org_path_below_root
An org node strictly below the tenant root (ADR-254 decisions 1 and 8): contracts/auth/v1/org-path.json without the root / — one to max_depth /-prefixed segments, each its segment_regex, at most max_length characters; held equal by TestGrammarOrgPathParity. No member is ever placed at /, the account owner's grain.
A string matching ^(/[a-z0-9][a-z0-9_-]{0,62}){1,8}$.
permissions_allowlist
One Permissions-Policy feature allowlist: [] = no origin, ["self"], ["*"], or origins (https://host[:port]). A malformed entry, or * beside another, is permissions.policy_invalid.
A array of string.
product_slug
Product slug (ADR-213 D3, ADR-214 D2): lowercase alphanumeric words joined by single hyphens; -- can never occur (it is the environment separator in product hosts, DX41). At most 30 characters so the host label (slug-token--env) fits the DNS 63-octet label.
A string matching ^[a-z][a-z0-9]*(-[a-z0-9]+)*$.
profile_name
A base profile's name (ADR-293 decision 1): the key of a [profiles.<name>] table in config/profiles.toml, and what [authenticate.workspaces] admin_profile names.
A string matching ^[a-z0-9][a-z0-9_-]{0,62}$.
region_name
A region's name (ADR-278 decision 2): a key of the region registry (infra/live/regions/<region>), a plain lower-case token starting with a letter; the CLI's session store reads the same pattern (cmd/aircloud regionPattern, held equal by TestRegionPatternIsTheGrammars). Which regions exist is the CLI's directory's to say, never the grammar's.
A string matching ^[a-z][a-z0-9]*$.
role_key
An end-user role key of the product (ADR-254 decision 1): a colon-free Builder role key — contracts/auth/v1/scope-registry.json .builder_role_key_grammar.regex (ADR-148 f4), held equal by TestGrammarRoleKeyParity.
A string matching ^[a-z0-9][a-z0-9_-]*$.
runtime
Guest toolchain of a function, shared component or data migration (heritage of the retired per-function manifest, ADR-213 D9; new runtimes additive).
A one of "js" | "rust".
secret_name
Secret name (ADR-213 D7): an upper-case POSIX identifier, so it doubles as the env var a container receives.
A string matching ^[A-Z][A-Z0-9_]{0,63}$.
seed_template_name
A node seed template's name (ADR-295 decision 7): the <template> directory of db/seeds/<environment>/nodes/<template>/, and what [authenticate.workspaces] seed and the org-nodes create operation name.
A string matching ^[a-z0-9][a-z0-9_-]{0,62}$.
test_principal_name
A test principal's name (ADR-262), the key a step's as names.
A string matching ^[a-z][a-z0-9_-]{0,62}$.
workload_name
Name of a function, container, shared component or data migration, taken from its directory (ADR-213 D2). The functions slug family (heritage of the retired per-function manifest, ADR-213 D9) narrowed to forbid -- and a trailing hyphen.
A string matching ^[a-z][a-z0-9]*(-[a-z0-9]+)*$.
write_role_list
The roles one record write requires (ADR-279 decision 1): any-of — an acting end user holding any one may perform it.
A array of role_key.