Skip to content
v0.3

Build an app

An app is how new behaviour arrives without the kernel learning anything about you. It runs beside the kernel, drives it through the same port an external integration uses, and can be installed and removed without leaving a wound.

Read apps & extension for where apps sit among the four extension points.

A manifest, and it is the whole contract:

  • Scopes it wants consent for. Installing the app grants them; it reaches exactly those and gets a named forbidden for anything else. The check is the kernel’s, not the app’s honesty.
  • Slots it renders into, in the storefront or the admin.
  • Events it listens to.
  • Its own data, if it has any.

The scopes in the manifest are what an operator sees on the consent screen, so they are a promise as much as a permission. catalog.read and catalog.product.write are very different sentences to show someone.

An app that asks for more than it uses will be installed by fewer people, and correctly so.

When an app has data that belongs to it, it gets isolated storage. It does not add columns to the kernel’s tables and does not write to them.

That is what makes uninstall clean: the kernel is exactly as it was, and the app’s own data goes with it.

⚠️ Data that belongs to a kernel entity — an extra attribute on a product, a field the checkout collects — is not app data. That is a custom field, and it rides in the item’s metadata, travelling with the order snapshot like everything else.

An app’s code runs in-process with the kernel. That is what makes decisions and slots possible, and it means the trust boundary is installation, not runtime sandboxing.

So:

  • Consent is the gate. What an app may reach is fixed at install and enforced by the kernel on every call. It cannot widen its own grant at runtime.
  • Everything is on the record. An app’s calls appear in the audit trail under the app’s identity, the refusals included.
  • Installing an app is trusting its code, the way installing a plugin always is. Read the manifest, and install apps whose source you can see.
  • ⚠️ In-process also means sharing the process’s fate. The loader refuses an app that is incompatible, but a compatible app that is badly written can hurt the instance it runs in — hold a transaction open, leak memory, block the event loop. Consent bounds what an app may REACH; it does not bound how well it behaves. That is the honest limit of the model, and it is why the previous point is not a formality.

A seed script, a demo-data generator, an importer, a one-off migration driver — anything that drives the port. Write it as an app rather than a loose script.

Not for tidiness: the app model already gives it install, consent, scope and isolated storage, and a script that lives outside gets none of those and grows its own worse versions of all four.

  • Touch the database directly. There is one write path; an app uses it like everybody else.
  • Assume it is the only app. Slots are shared, events are broadcast, and another app may be doing something adjacent.
  • Hold a transaction open. If you are answering a decision, you are inside one — be quick and be deterministic.