Skip to content
Air-Cloud docs

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).

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.

app/containers/<name>/container.toml

app/containers/<name>/container.toml (ADR-213 D9; the delivered Apps surface).

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).

app/site/site.toml

app/site/site.toml (ADR-216 D1). Absent file = every default.

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.

config/environments/preview.toml

config/environments/preview.toml and config/environments/<name>.toml (ADR-213 D5). Only overridable keys.

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.

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.

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.

config/secrets.toml

config/secrets.toml (ADR-213 D7): names and use, never values.

db/migrate/<YYYYMMDDHHMMSS>_<name>/migration.toml

db/migrate/<YYYYMMDDHHMMSS>_<name>/migration.toml — an expand-only data migration (ADR-217 D7).

db/schema/<entity>.toml

db/schema/<entity>.toml (ADR-217 D1). The entity name is the file name (a flex_name).

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.

lib/<name>/component.toml

lib/<name>/component.toml — a shared component of the product's functions (DX33).

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.

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.

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

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.

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.