Skip to main content

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:

ConfigPurpose
tsconfig.base.jsonStrict options shared by everything
tsconfig.bun.jsonBun/Node scripts and tests — server globals allowed
tsconfig.browser.jsonBrowser code — no server globals
Root tsconfig.jsonProject-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.

ConsumerReads packages to…Explicitly does not…
Kernel browser installerperform a reviewed, state-preserving app transaction inside a live Neutronreplace provisioning
neutron-cli compileproduce Wasm and Candid on disk, offlinedeploy anything, or synthesize a local identity
neutron-provisionvalidate pins, compile the whole actor, install/reinstall, restore assets, verifybuild 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

ArtifactProduced byPurpose
dist/web/**each app's frontend buildpackage browser assets
dist/mo/<sha256>.momopackcontent-addressed Motoko modules
dist/neutron.jsonmopackpackaged manifest with hashed entries
neutron.lock.jsonmopackappend-only lineage lock when managed memory is declared
dist/schema.jsonan app's method_schema scriptJSON Schemas for non-internal app methods (developer aid)
<id>.v<x>.<y>.<z>.neutronpackthe universal package archive
apps/kernel/backend/_neutron.mokernel assembly buildgenerated 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.