Linked apps
Another HighX application installed inside yours, contained by name and capability.
A linked app is source in your repository, built with yours and served under a
mount. Declare each one in highx.config.ts, keyed by the name its manifest
gives:
export default defineConfig({
apps: {
billing: { source: "./apps/billing", mount: "/billing", grant: ["net:api.stripe.com"] },
},
});highx generate reads each highx.app.json — parsed, never evaluated — and
refuses to write a deployment when a check fails. Configuration is the
reviewable half; the host still installs the app in code, because the tenant
projection is a function.
An inline app is not a sandbox. Its source runs in your isolate with whatever your imports can reach. Namespacing, grants, and the tenant projection hold, but there is no process boundary and none is claimed. Review an app's source as your own.
What is contained
Every table, queue, schedule, and route an app owns carries its namespace, and
the prefix is applied by the runtime rather than accepted from the caller — a
name an app assembles cannot resolve to yours. An app's manifest declares
requirements; the installation grants. Declaring exec:arbitrary gets an app
nothing.
The app receives ctx.db scoped to its declared tables, a projected request
with your credentials stripped, a tenant it did not choose, and a ctx.fetch
restricted to the hosts it declared. Its response passes an allowlist on the way
out, and a relative redirect gets the mount put back on.
Build errors
| Code | When |
|---|---|
mount_collision | Two mounts overlap. /billing and /billing/invoices collide; /billing and /billingx do not |
table_collision | Two apps claim one namespaced table |
ungranted_capability | A manifest requires what its installation does not grant |
cycle, unknown_dependency | Apps depend on each other in a loop, or on one that is not installed |
escapes_app | An import resolves outside the app's own directory |
forbidden_import | The app imports the runtime, a platform adapter, or a node: builtin |
ambient_global | The app calls fetch bare rather than through ctx.fetch |
The last three read imports and call expressions, not the full semantics of JavaScript, so a determined author can still defeat them. They raise the cost of an accident from zero to a build failure, which is the honest claim.
Route permissions
An app declares what a route needs; you decide who holds it. Permissions are evaluated host-side against your table — an app never sees roles.
{ "name": "billing", "routeAuth": { "/invoices/:id": "billing:read" } }Each must start with the app's own name, so an app gates on its own vocabulary rather than reaching into yours.
The manifest is one document
The installer reads highx.app.json; the app's module imports it. They must be
the same document — the host grants against one and the runtime enforces
routeAuth and the egress allowlist from the other, so a drift between them
would mean the copy that was reviewed is not the copy that runs. A disagreement
is refused at startup.
isolate: "worker" exists in the type and install refuses it: nothing deploys
or routes to an isolated app yet. The app-facing API is shaped so isolation can
be added without rewriting apps.