Ajo Engine
Seal and run
Install the native pair, seal a Kit build and run it.
This page takes a Kit application, here a small notes App, from kit build to a running engine process. Sealing and running happen on a verified Linux x64 host. UI development can happen elsewhere; only these steps need the native tools.
Install the native pair
Install the runtime and the compiler together, at the same exact version. They are development dependencies: the compiler runs on the build host, and the runtime binary is later copied into the App’s image.
npm install --save-dev --save-exact ajo-engine@0.1.0 ajo-engine-compiler@0.1.0With pnpm:
pnpm add -D --save-exact ajo-engine@0.1.0 ajo-engine-compiler@0.1.0npm rejects a host outside the packages’ declared OS and CPU. pnpm’s default configuration can install an incompatible package; enable engine-strict so it refuses as well:
engine-strict=trueInstallation never compiles or downloads anything. Neither package has lifecycle scripts, a JavaScript launcher, a downloader or runtime npm dependencies. Each contains one executable, its PIN, checksums, provenance record, the ISC license and the third-party notices. Keep the lockfile from this installation so later builds use the same pair.
Keep the toolchain separate
If the application is developed on macOS or Windows, do not make the native pair a mandatory dependency of the project: no support is claimed there, and npm rejects the installation. Install it instead in its own directory on a verified Linux host, such as a dedicated Linux environment or virtual machine, and point Kit at it. This is how this website is built.
mkdir -p /path/to/ajo-toolchain
cd /path/to/ajo-toolchain
npm install --save-dev --save-exact ajo-engine@0.1.0 ajo-engine-compiler@0.1.0The executables are then at /path/to/ajo-toolchain/node_modules/.bin/ajo-engine-compiler and /path/to/ajo-toolchain/node_modules/.bin/ajo-engine. The application’s own dependencies stay portable.
Build and seal
kit build has a single target, the engine. It writes a staging tree to .ajo/: the transformed client, the closed server graph, the compiled migration registry and the compiler.json descriptor. Every build rejects Node builtins and other imports that would break the engine’s closed module graph. Without a compiler, it stops there and prints the command to run next:
pnpm exec kit buildajo-engine-compiler --input .ajo/compiler.json --output dist/ajoWith --compiler, Kit runs that command itself. It removes any previous dist/ajo and seals the staging tree into a fresh one:
pnpm exec kit build --compiler /path/to/ajo-toolchain/node_modules/.bin/ajo-engine-compilerWhen the compiler is installed in the project, package scripts can name it directly, because they see node_modules/.bin on their path:
{
"scripts": {
"build": "kit build",
"artifact": "kit build --compiler ajo-engine-compiler"
}
}You can also run the compiler on the staged descriptor yourself. Give it an output path that does not exist yet:
pnpm exec kit build
npx --no-install ajo-engine-compiler --input .ajo/compiler.json --output .ajo/artifactThe descriptor is the handoff contract between Kit and the compiler: the module graph, the client assets, the compiled migrations and the App’s declared authority. The artifact explains what the compiler does with it.
Debug seals
Source text is stripped by default. Production chunks keep function names and line and column tables, so stack traces stay named and located, but carry no source. --keep-source produces a source-bearing debug seal:
npx --no-install ajo-engine-compiler --keep-source --input .ajo/compiler.json --output .ajo/debugRun the artifact
ajo-engine takes the artifact directory. Any further arguments are passed to the application. The process reads its configuration from the environment:
NODE_ENV=production HOST=127.0.0.1 PORT=8080 APP_URL=http://localhost:8080 \
/path/to/ajo-toolchain/node_modules/.bin/ajo-engine dist/ajoKit declares NODE_ENV and APP_URL as required in every descriptor, so the engine refuses to start without them. Kit then validates the values before it starts serving.
| Variable | Required | Meaning |
|---|---|---|
NODE_ENV | Yes | Must be production; Kit refuses any other value on the engine. |
APP_URL | Yes | An absolute http or https URL; outside local runs, the App’s public origin. With a host-managed origins manifest, an exact HTTPS origin listed there. |
HOST | No | Listen address, a hostname or numeric IP. Defaults to 0.0.0.0. |
PORT | No | An integer from 1 to 65535. Defaults to 8080. |
APP_SECRET | With ajo-kit-auth | A strong random secret; production refuses a missing, short or placeholder value. |
DATABASE_PATH | No | A relative file name inside the data root, or :memory:. Defaults to ./database.sqlite. |
AJO_DATA | When data is required | An existing directory inside a declared filesystem root. |
App environment
An App declares its own variables in package.json, under kit.engine.env. Names use uppercase letters, digits and underscores. A required name that is missing at startup stops the engine before any application code runs.
{
"kit": {
"engine": {
"env": { "required": ["MAIL_URL", "MAIL_TOKEN"], "optional": ["MAIL_FROM"] }
}
}
}Refusals are printed on standard error and name what is missing:
[ajo] [error] artifact requires environment variable 'MAIL_URL'The data root
An App that stores SQLite on disk needs a writable data root. Declare it as a filesystem root, then point AJO_DATA at it when you run the engine. Setting AJO_DATA alone grants nothing: the path must lie inside a declared root.
{
"kit": {
"engine": {
"fs": { "roots": ["/ajo/data"] }
}
}
}Kit marks the data root as required when the App uses its database. The engine then refuses to start unless AJO_DATA names an existing directory, and every declared root must be valid and available at startup. Database paths resolve beneath the data root; absolute paths and .. segments are rejected. Kit applies the App’s compiled migrations when it starts, before it listens.
NODE_ENV=production APP_URL=http://localhost:8080 \
AJO_DATA=/ajo/data DATABASE_PATH=notes.sqlite \
/path/to/ajo-toolchain/node_modules/.bin/ajo-engine dist/ajoBefore you package it
- The runtime and compiler come from the same release; the artifact’s pin matches the runtime’s.
- The artifact was sealed without
--keep-source. - Every required variable, the data root and each declared root exist where the App will run.
- The runtime’s ISC license and third-party notices travel with any image you redistribute. Ajo Server’s image guide shows one way to include them.
- The artifactManifest fields, feature evidence and fail-closed loading.
- Build and configurationThe Kit side: descriptor configuration and plugins.
- Deploy an AppPackage the artifact in an image and deploy it.
Source of truth: ajo-engine README.md and ajo-kit README.md