Build topology
Layout
neutron/
├── apps/
│ ├── kernel/ trusted shell, Motoko backend, generated actor source
│ ├── hello/ minimal example package
│ ├── kitchensink/ feature-reference example package
│ ├── vetkeys_fixture_test/ installable integration-test fixtures
│ └── … agent, chess, contacts, gemma, hullshift, jetcreeper,
│ mail, mysubnet, spreadsheet, vfs, wagyu, wallet
├── packages/
│ ├── neutron-tools/
│ ├── neutron-scripts/
│ ├── neutron-security/
│ ├── neutron-compiler/
│ ├── neutron-cli/
│ ├── neutron-provision/
│ ├── neutron-design-system/
│ ├── neutron-motoko-wasm/ vendored Motoko compiler
│ └── neutron-motoko-capabilities/ versioned capability leaf types
├── support/
│ ├── dispenser/ hosted canister-creation product
│ ├── repository/ static neutron-repo-v1 provider template
│ └── update-source/ reference certified app update source
└── doc/
The root package.json is private and owns the npm workspaces — packages/*,
apps/*, and support/* — plus the shared lockfile, cross-workspace checks,
and developer commands. The shared icblast client is installed from its
public npm package rather than linked as a sibling workspace. Each app owns its
own manifest, source, package scripts, and any app-specific tests.
All three current support/ directories have a package.json and are npm
workspaces. The dispenser owns the hosted creation UI/canisters, repository is
the example certified provider and generator, and update-source is the
certified release source plus publisher tooling.
TypeScript boundaries
Four root TypeScript configurations separate common options from the two main runtime type environments:
| Config | Purpose |
|---|---|
tsconfig.base.json | Strict options shared by everything |
tsconfig.bun.json | Bun/Node scripts and tests — server globals allowed |
tsconfig.browser.json | Browser code — no server globals |
Root tsconfig.json | Project-reference entrypoint for the projects explicitly listed in it |
App frontend code must be browser-safe. Relying on a Bun or Node global at runtime will typecheck under the wrong config and then fail inside an iframe.
The package pipeline
Every app follows one target-neutral package contract, in four stages:
The kernel uses the same single package command as any app.
Packages are deployment-target-neutral. No build writes a local/production
marker, gateway, identity provider, or destination canister id into the
archive, and the same bytes can feed local and production deployment. A
manifest may still carry distribution metadata such as update_source; that
is reviewed package content, not a deployment-environment marker. A package
containing the removed build-marker file is rejected on load.
Package bytes remain target-neutral. Trusted target context is applied during complete-actor compilation, and canister-specific runtime config is bound after the destination canister id is known.
Three separate consumers of package data
This separation is a deliberate architectural boundary.
| Consumer | Reads packages to… | Explicitly does not… |
|---|---|---|
| Kernel browser installer | perform a reviewed, state-preserving app transaction inside a live Neutron | replace provisioning |
neutron-cli compile | produce Wasm and Candid on disk, offline | deploy anything, or synthesize a local identity |
neutron-provision | validate pins, compile the whole actor, install/reinstall, restore assets, verify | build a workspace or execute an app script |
The provisioner in particular never invokes a package build. Workspace discovery, manifest generation, frontend compilation, Motoko packing, and archive construction belong to the trusted package/release workflow. Provision configuration has no workspace path, build command, or hook.
Generated artifacts
| Artifact | Produced by | Purpose |
|---|---|---|
dist/web/** | each app's frontend build | package browser assets |
dist/mo/<sha256>.mo | mopack | content-addressed Motoko modules |
dist/neutron.json | mopack | packaged manifest with hashed entries |
neutron.lock.json | mopack | append-only lineage lock when managed memory is declared |
dist/schema.json | an app's method_schema script | JSON Schemas for non-internal app methods (developer aid) |
<id>.v<x>.<y>.<z>.neutron | pack | the universal package archive |
apps/kernel/backend/_neutron.mo | kernel assembly build | generated complete-actor source, for build checks |
Build output (dist/, .neutron/, test results, Playwright reports) is
generated state and is ignored. Canister identity lives in the provisioning
session file, not in a mapping directory.
Compile caching
Local provisioning maintains a verified complete-actor compile cache. The rule is strict and worth stating plainly:
Caching changes work, never desired state.
Loading a path-only local config keeps only its relative declarations — no stat, read, or hash. A local reinstall resolves and reads each archive once, deriving its exact bytes, digest, package identity, version, and role. That digest feeds the cache key, the operation fingerprint, and the deployment receipt. Production preparation instead verifies every declared pin, and production operations do not use the rebuildable local cache at all.
Unknown artifact-set fields, duplicate ids or paths, archive drift during an operation, and symlink escapes all fail closed.
Trusted network identity
The assembler compiles a trusted immutable installation.network_id into the
actor. Production derives it from the compiler-pinned IC mainnet root-key SPKI
DER; a local deployment derives it from the exact status root key of the
attached PocketIC instance. Runtime config, manifests, and app scripts cannot
supply it.
Every node in a local fleet receives the same network identity but its own canister-bound runtime config.