# 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//function.toml` | one function: code + declaration | Air-Functions | | `app/containers//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/.json` | a built site's entry document, dependencies (engine-strict), build configuration and the @/ → src/ path; a top-level .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/.jsx` | one layout; application is the default | Air-Deploy (site) | | `app/site/src/components/.jsx, app/site/src/components/**/.jsx` | Builder components (.jsx or .js); nested directories admitted, mirrored by test/site/components/ | Air-Deploy (site) | | `app/site/src/helpers/.js` | one Builder helper | Air-Deploy (site) | | `app/site/src/pages//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/.toml` | one Flex entity | Air-Flex | | `db/seeds//.toml` | seed records of one environment | Air-Flex | | `db/seeds//nodes/