Skip to main content

Capabilities

A capability plan is the canonical install-time account of an app's declared and compiler-derived authority. Some entries inject attenuated Motoko handles, some configure frontend or compiler policy, and broker-enforced resources are also projected into a durable runtime registry. The plan is a disclosure and authority ceiling; it is not evidence that every entry has the same runtime switch, counter, or lease semantics.

How the plan feeds enforcement

The shared rules are narrower:

  1. Declaration bounds authority. capabilities.X defines the install plan; backend.capabilities.X selects a Motoko handle only for capabilities that have one. Runtime resources explicitly distinguish declaration grants from later owner_runtime_grant decisions.
  2. Injected leaves are attenuated. A selected backend leaf captures the app scope and exposes the capability's verbs instead of a management actor or raw primitive. Exact exposed fields differ by capability.
  3. Stateful brokers use installation scope. Uninstall/reinstall allocates a fresh installation uid. Structural plan facts need not own stored state.
  4. Brokered asynchronous work revalidates its applicable authority. The exact lease, scope, and post-await behavior is capability-specific.
  5. Only runtime-projected resources are Settings switches. Structural facts such as dependencies and backend selectors, and session-only frontend grants, remain in the plan without a durable per-resource toggle or usage counter.

The generic registry does not replace each broker's typed policy — a call must satisfy both.

backend_calls — outbound canister calls

The app backend's route to an eligible external canister.

FieldMeaning
reservation_scopesWhich grant modes it may later request: exact, principal, method
install_reservationsUp to 64 reservations created when an authorized principal accepts installation
max_concurrency1–20 in-flight calls, and the size of one batch
max_cycles_per_call0–100 T gross cycles per call; 0 forbids transfer
max_cycles_per_day0–1,000 T charged plus unresolved gross per UTC day

Scope semantics, shown in the install dialog with kernel-written meanings:

KindCovers
exactOne method on one canister
principalAll current and future methods on one canister
methodOne method name on any eligible non-system canister

Broad scopes get stronger kernel-owned risk treatment.

Declaring the capability grants no destination beyond any accepted install_reservations. Additional reservations are approved by an authorized principal through source-bound kernel UI, either at install or later through a request dialog. Anonymous, management, and Neutron-self targets are blocked outright.

That self-target restriction applies to this Motoko broker. The separate frontend self-call path can call only the source app's own live non-internal method and applies its own one-shot/preapproval policy.

Every request carries a required cycles field. The kernel checks the installed ceilings, keeps a 250-billion-cycle canister reserve on top of platform call cost, and is the only code that performs the attachment. Batches reserve their sum atomically and return results in request order. Observed refunds reopen that day's headroom.

This is a financial ceiling, not a rate limit: zero-cycle calls are governed by reservations, size bounds, scheduled budgets, and concurrency — not an hourly counter.

Reservations survive compatible upgrades, are removed when a declared scope is removed, and are deleted at commit on uninstall. Settings lists every active reservation with a revoke action. An already-dispatched update cannot be cancelled; if authority changes in flight, Neutron suppresses the reply and reports an unknown outcome.

randomness — brokered entropy

Returns exactly 32 fresh bytes through a kernel-owned raw_rand broker, with per-installation and kernel-wide in-flight bounds, a cycle reserve, and scope checks before and after the await.

No hourly counter. Direct Base/Core randomness facades stay blocked because they hide management calls without these controls.

https_outcalls development

1–8 exact endpoints. Each fixes its canonical HTTPS URL prefix, its GET/HEAD/POST method subset, a request-header allowlist, request and reply bounds, and the sole V1 transform policy strip_headers.

At runtime the app supplies only: an endpoint id, a canonical relative path, structured query pairs, declared headers, a bounded body, and — for POST only — a mandatory idempotency key.

It cannot choose another host, port, transform, replication mode, or cycle amount. It receives no management actor and no transform callback.

The kernel owns replicated dispatch, cost quotation and attachment, a fixed header-stripping transform, concurrency, a per-call cost ceiling, a low-cycle reserve, redirect handling, and pre/post-await lease checks. No time-window call or cycle budget.

:::caution No confidentiality This broker provides no app-to-replica confidentiality boundary. Request and response values, including credentials, are processed by replicated execution; what a host operator can observe additionally depends on the subnet and platform trust assumptions. The fixed transform can remove nondeterministic headers but cannot make a nondeterministic body reach consensus. A revoked result after dispatch is explicitly outcome-unknown for a remote POST. :::

chain_key_signing development

One to four assertion slots. Each fixes an id, an ECDSA/BIP340/Ed25519 algorithm, an untrusted display purpose, and an assertion-byte ceiling.

The leaf exposes exactly two things: public_key(slot) and sign_assertion({ slot; assertion }).

Each slot is namespaced by canister, fresh kernel-install epoch, app installation, slot, algorithm, and a kernel-resolved threshold key. The kernel — not the app — owns the threshold key name, the one-component derivation path, the domain-separated SHA-256 digest, cycle attachment, per-call cost ceiling, concurrency, revocation, and the no-retry policy.

Apps receive no management actor, key name, path, raw or prehash signer, transaction encoder, BIP341 auxiliary data, cycle amount, or retry control.

An ambiguous management reject maps to an explicit unknown outcome and is never retried. Revocation after dispatch suppresses the signature bytes without pretending the paid operation did not happen. State and audit retain no assertion, digest, signature, or raw reject.

:::caution V1 is assertion-only V1 cannot directly encode or sign a raw chain transaction, and its install grant is not Neutron's one-shot value-moving consent. An external verifier can still treat a signed assertion as high-impact authority, so apps must constrain assertion semantics themselves. Any future transaction adapter needs its own transaction-shaped, one-shot authorized-principal confirmation immediately before signing. :::

stable_store development

The bounded replacement for raw stable memory and Regions. One to eight stores, each fixing an id, an untrusted purpose, a schema version, and entry/key/value/byte ceilings.

The synchronous leaf offers: get, conditional put, revision-checked delete, bounded live list, usage, and bounded clear_page.

Requests select only a declared store id and bounded binary key, prefix, or value. They contain no app id, installation uid, physical cursor, pointer, offset, page, Region, map, or allocator — the kernel derives the exact app/install namespace and a never-reused store uid.

Revisions support create-if-absent and compare-and-swap update or delete. Prefix pages are bounded live keyset scans; cursors are checked logical continuations, not bearer authority. Mutations reject non-replicated query execution.

Quota narrowing does not silently destroy data. Reads and cleanup continue; only target-valid, non-growing replacements are accepted until usage fits. Increasing the schema version stamps new writes while retaining old entry versions for lazy CAS migration.

:::caution Not confidential, not certified Replicas process plaintext keys and values, and stable_store performs neither encryption nor HTTP certification. An app that needs confidentiality must supply its own encryption and key-management design; merely encrypting with a key kept in the same canister does not create a replica-confidentiality boundary. Publishing a value requires the separate certified-assets broker. :::

vetkeys — app-isolated key slots

One to four slots, each with an id and an untrusted purpose. The kernel app cannot declare this capability.

Installation grants no usable key. A focused tile must later request a kernel-owned lifecycle decision to reserve a slot. A slot's key_holder is its lifecycle manager, not its only reader — every currently authorized Neutron principal may derive enabled retained generations.

Private derivation lives in the source-bound browser broker and is never injected into Motoko. derive.begin creates a 60-second single-use challenge bound to the originating endpoint object, id, and session, the authorized principal, the app and version, the slot uid/id/generation, a request nonce, and a hash of the transport public key. The originating endpoint confirms it immediately as an automatic protocol step — not a second user decision. Tray endpoints cannot derive.

After recovery, the app owns the usable browser-side secret and can disclose it. An app with persistent_browser_storage may opt into the SDK's bounded IndexedDB cache: AES-256-GCM ciphertext, a structured-cloned non-extractable wrapping key, at most eight records, and a fixed expiry of at most seven days. This protects against simple plaintext profile inspection, not code running on that app origin; such code can ask WebCrypto to use the key. Cleanup and expiry bound later honest reuse but do not prove physical browser/OS erasure.

Selecting the optional vetkeys_public backend interface adds a public-only leaf: slot summaries and public encryption material including the computed public derivation input. It cannot derive a private key, choose a namespace, nonce, curve, or canister, attach cycles, or call the management canister.

Install disclosure states plainly: no key is created on install; later lifecycle changes require kernel consent; derivation spends cycles; compatible updates inherit access; disabling cannot erase a browser-held key; and an app can disclose keys from its own slots.

Production uses key_1; local uses test_key_1. Runtime failure does not trigger a fallback.

public_ingress — receiving authority

1–32 exact public Candid routes. This is how another canister — including another person's Neutron — talks to your app.

{
"protocol": "example_v1",
"id": "submit",
"handler": "example_submit",
"mode": "update",
"caller": "canister",
"max_request_bytes": 8192,
"max_response_bytes": 4096,
"max_calls_per_hour": 120,
"max_calls_per_caller_per_hour": 30,
"required_cycles": 250000000
}

The compiler emits one physical dispatcher per app, protocol, and mode:

app_<app-id>__<protocol>_<query|update>

Route ids sharing that tuple are selected by the request's method field, so multiple routes share one stable Candid method. The wire is { method : Text; payload : Blob } -> { #ok : Blob; #err : … }, where the nested blob is the exact handler input/output Candid.

Query routes declare a caller class:

ClassAccepts
anyAnonymous users, signed users, and canisters
authenticatedEvery non-anonymous principal — not only Neutron owners
canisterOnly canister principals

Queries have byte, caller, lease, registration, and reply checks — no rate limit and no cycle charge.

Update routes choose exactly one of two disjoint admission classes:

ClassSemantics
caller: "authenticated"Direct IC ingress from a self-authenticating principal. Forbids required_cycles, rejects anonymous and canister principals, and accepts no payment.
caller: "canister"Inter-canister. Requires a positive required_cycles floor (≤ 100 T). Underpayment traps.

The sender-push-and-pay model

Cross-Neutron protocols do not poll. A canister-paid update route publishes one static required_cycles floor for its protocol version; the sending app discovers recipients, pushes the write, and attaches at least that floor. Mail pays when delivering an envelope; a social poster pays to fan out.

The floor is accepted and attributed immediately, before payload, reserve, concurrency, or rate admission — so a funded call retains that amount even when a later check rejects it. That is deliberate: the floor must cover all bounded receiving work and retention that can become irreversible.

An opting-in handler may inspect the still-unaccepted surplus and accumulate a supplemental request. But:

:::caution Supplemental cycles are best-effort The outer dispatcher attempts the accumulated acceptance only after the handler self-call has committed its app mutation, and only if the live route, lease, fingerprint, authority epoch, and completion record still validate. It is not atomic with the mutation and cannot roll it back. Set required_cycles high enough to cover every irreversible path; use a supplemental request only for opportunistic recovery. :::

Update ingress enforces per-route, per-app, and global fixed-hour windows, plus an optional per-caller limit no greater than the route limit. It also enforces one/two/eight route/app/global pending limits and the receiver's cycle reserve. It persists admission before the self-only handler message and rechecks request hash, route fingerprint, authority epoch, app scope, and registry lease at dispatch and at completion. A post-commit authority change returns #revoked_after_dispatch — an explicit unknown outcome, so protocols should use an idempotency key or reconcile with a read.

This capability grants no outbound calls. A Neutron app sending through its Motoko broker still needs backend_calls, a reservation approved by an authorized principal for the recipient principal and generated dispatcher method, and transfer ceilings covering the floor. External callers are governed by their own calling authority; receiving public_ingress does not grant it to them.

http_routes and certified_assets development

Two separate authored contracts that must not be confused:

DeclarationContents
certified_assets.api = 2Closed collection/storage policy. It requires backend.capabilities.certified_assets.api = 2; the compiler then derives an API-1 certified_read_routes plan entry and one runtime resource per grouped mount on the kernel-derived shared app path. Apps do not author a version-2 HTTP route.
http_routes.api = 1POST-only http_post_update_handler mounts. Any other http_routes API is rejected. A POST-only app does not declare or receive Certified Assets V2.

A POST mount claims exactly POST plus one compiler-bound internal synchronous handler. Request and reply are each at most 64 KiB.

The handler sees only a canonical relative path, declared headers, a bounded body, and a digest of the mandatory Idempotency-Key. It does not receive caller identity, raw Host, cookies, framing, its scope, the actor, or a durable capability handle.

For one hour, exact completed duplicates replay the stored reply without executing app code again; changed visible input is rejected; pending or unknown state never executes twice.

Header handling is fail-closed: each declared forwarded value is at most 4,096 bytes and unique. Cookie/Set-Cookie, duplicate declared headers, and duplicate or non-identity Content-Encoding reject the request rather than being silently stripped. Forwarded authorization remains anonymous untrusted app-protocol data unless the handler verifies its own signed token.

Anonymous gateway traffic consumes stable per-mount and app/global fixed-hour windows; a direct Candid call by a kernel-authorized principal is neither limited nor counted, though it still passes every other check.

See HTTP and assets for the certification detail and the release gate.

scheduled_tasks — unattended execution

At most two fixed callbacks per app and 64 per assembled actor.

FieldBound
idLowercase, 1–40 chars
methodA same-app internal async* function
interval_seconds10 seconds to 30 days
run_on_startAlso enqueue one run after install or upgrade
max_backend_calls1–100 calls while one callback is active

There is no runtime task-registration API. App code never receives Motoko's system timer capability; only the kernel creates timers, and it recreates them after every upgrade.

A target is zero-resource unless the app also declares backend_calls — then each invocation receives a fresh task-scoped record with its own budget and scheduler-owned lease. Different tasks have independent counters; foreground capability calls consume neither.

The scheduler rejects overlapping runs of one task and contains callback failures. Scheduled work cannot raise a frontend permission prompt.

:::caution This is elevated authority A declaration lets untrusted app code execute with no open tile and spend instructions on every interval. Neutron does not claim a general CPU quota beyond IC execution limits and the scheduler's frequency and overlap bounds. :::

Frontend-side capabilities

These produce no Motoko field. They are enforced by the trusted frontend and committed capability plan; only the dedicated-origin and connection resources in this group also have durable runtime-registry projections.

preapproved_self_calls — up to 32 exact query or update methods owned by the declaring app whose generated backend wrappers require authorization-set membership, callable from its own live tile, tray, or background endpoint with no per-call dialog. Missing, public, internal, duplicate, malformed, and wildcard entries are rejected.

This removes a frontend confirmation. It does not remove the generated backend authorization-set check. The kernel still source-binds the request, verifies query/update mode, validates arguments against live Candid, resolves the logical name through the registry's exact candid_name, fixes the destination to the current canister, and signs with the active authorized browser identity.

agent_entrypoints — up to 4 resident tools eligible for Agent Mode. Installation only discloses that the app can request it; an authorized principal must still enable one exact app version and entrypoint. The grant is session-only and revoked by reload, logout, authorization loss, app update, uninstall, endpoint replacement, or explicit disable.

background_ui_requests — which of frontend_tool, signed_canister_call, backend_access, and connection a resident may raise with the owner-attention broker. It does not preapprove anything or bypass liveness checks.

ethereum_provider — declared EVM chains (≤8) and an exact subset of the kernel's method catalog. Declaring eth_sendTransaction also requires eth_requestAccounts. The provider object is never exposed to the app iframe; only a focused tile may begin a short-lived session during transient user activation. Backgrounds and Agent Mode cannot use it.

connections — external credential providers and scopes. Provider URLs, callback URLs, client secrets, and credentials are deliberately not valid manifest fields. A resident_credential response returns only through the source-bound resident port; the kernel does not persist that returned value, but hostile app code can retain or disclose credentials it legitimately receives.

persistent_browser_storage / dedicated_resident_origin — mutually exclusive, both require a background, neither changes tile or tray isolation. The persistent mode currently has no kernel-enforced browser-storage quota or orphan cleanup, and disabling it cannot erase existing browser data. See Frame isolation.

What Settings shows

The runtime registry is read in bounded cursor pages and strictly reconciled against the complete active app-instance inventory, plan fingerprint, and canonical runtime projection.

A malformed, duplicate, stale, unknown, or missing resource fails the whole runtime view closed rather than rendering a partially-trusted list.

One-shot and session-only frontend grants are browser state and are never serialised into this durable registry.