Scope: app/operator playbook for moving a verified Stoffel app from local MPC into a production-shaped network deployment. This is not a maintainer guide for protocol internals. Dependency assumption: use public CLI/SDK surfaces first. If a current SDK wrapper does not start the lower-layer service directly, state the lower-layer handoff instead of pretending local MPC is deployment.
Use when
Use this playbook when a Stoffel app already passes local CLI/SDK smoke tests and now needs deployable artifacts, node/client topology, off-chain coordinator config, or an operator runbook.Goal
Make deployment explicit enough that an agent does not guess hidden topology, identity, bytecode, or client-slot details. A deployment is complete only when a client can submit typed inputs to the intended network and receive the authorized output, or when the remaining lower-layer blocker is recorded with logs.Deployment model
Production-shaped multi-user Stoffel apps have four distinct roles:- Build artifacts:
.stflbbytecode, generated typed bindings, program manifest, and app release metadata. - Application control plane: public metadata, authorization, session lifecycle, client-slot/capability assignment, network discovery, non-sensitive receipts, and explicitly authorized opened aggregates.
- Participant MPC clients: participant-owned processes that load deployment config and bindings, validate their owner’s plaintext locally, select an assigned client slot, submit directly to the MPC service, and read authorized outputs.
- MPC service plane: a separately deployed coordinator plus long-running MPC parties/nodes with stable addresses and identity material.
.execute_local().await? as the deployment model. It is a trusted local development analogue that spawns several MPC nodes/processes on one machine and may expose every fixture input to one harness process.
Portability and provenance preflight
Label each command and artifact observation with one execution context: app checkout, framework checkout, deployment host, container, or CI. A host command does not prove what a container sees, and a framework workspace command does not prove that an external app can consume the public packages. Before building the release bundle, record:- app repository URL, commit, and clean/dirty state;
- framework repository URL, commit, and clean/dirty state when a framework checkout is intentionally involved;
- CLI installation source, resolved executable path, and reported version/build identity when available;
- SDK source as a public package version or git URL plus revision; record any intentional path source explicitly;
- committed lockfile path and hash, or the reason a lockfile does not apply;
- bytecode, generated binding, and program/deployment manifest hashes;
- immutable container image digest for every applicable build and runtime image.
Deployment inputs to collect
Before editing deployment code, fill this table:
If any field is unknown, stop and discover it from source, generated metadata, deployment config, or operator input. Do not invent it.
Artifact build step
Build artifacts before deploying services:.stfl independently on deployment hosts. Recheck hashes at distribution, service startup, and client execution.
Local gate before network work
Run the same program shape locally:- every ClientStore input slot used by deployment;
- expected output clients;
- backend/topology selection when configurable;
- typed output decoding when generated bindings are used.
Network config plan
Use SDK builders for topology validation when possible:- party id;
- bind address;
- peer addresses;
- expected parties;
- expected clients;
- threshold;
- backend/curve;
- preprocessing sizes;
- coordinator/off-chain settings when using ClientStore IO.
Server/node handoff
A node service needs:- the exact
.stflbbytecode; - the party’s network config;
- coordinator address if using off-chain ClientStore IO;
- node RPC bind address;
- party certificate/key material;
- expected client certificates;
- persistent state location when the backend/workflow stores shares or commitments;
- process supervisor and logs.
Container and host safeguards
- Pin every image by digest and record that digest with the node/client result.
- Record each host-to-container mount. Mount the immutable release bundle read-only where possible, keep state and identity mounts separate, and verify bundle hashes inside every runtime container.
- Never mount a framework checkout, developer
target/, or unpublished package cache to make a release work. Such a run is not clean-room tested. - Distinguish bind addresses from advertised/reachable addresses.
127.0.0.1andlocalhostrefer to the current network namespace and normally cannot identify a service across host/container boundaries. - Publish only required ports, and test coordinator, mesh, RPC, and client reachability from the actual source container/host. Record DNS resolution and the address used, not only a host-side health result.
Participant-client integration
Participant-owned client software should load deployment config and submit its owner’s typed input directly to the separately deployed MPC service. A remote application backend is not the participant client. In a desktop application, the local Tauri/Rust process may be the participant client when it uses the supported SDK path locally on the participant’s device.Operational checklist
Before calling deployment done:- Bytecode and bindings come from the same build.
- All nodes run the same bytecode hash and compatible configs.
- The coordinator address is reachable from every node and client.
- Node mesh ports and node RPC ports are reachable according to the topology.
- Party and client identity files are present, readable by the process, and not committed.
- Client slots and output slots match the generated manifest.
- Preprocessing capacity covers the expected workload shape.
- Application-service schemas, persistence, logs, traces, caches, queues, analytics, crash reports, and receipts contain no participant plaintext.
- A plaintext-canary test confirms the application control plane is outside the private-input path.
- Participant clients submit directly to the MPC service using verified runtime support.
- Logs redact identity material, tokens, certificates, shares, and private client values.
- Restart behavior is documented for the control plane, participant clients, coordinator, and nodes.
- Rollback means reverting bytecode, bindings, config, and app client code together.
- Repository, CLI, SDK, lockfile, artifact, and image provenance is recorded for each labeled execution context.
- The external clean-checkout/public dependency consumer proof passed.
- Every deployment host/container verified the unchanged release bundle hashes before startup.
- The real service processes started and an actual SDK client traversed the intended coordinator/node service path and decoded the authorized typed output.
.execute_local(), execute_local, or any other local-only executor is not deployment completion. If no live service path is available, report the deployment as blocked or incomplete with the exact handoff and logs. Do not rebuild the bundle merely to make the client smoke pass.
Verification commands
Use the commands that exist for the app. A typical ladder is:CI/release gates
Use separately visible gates for provenance capture, clean app build, public dependency consumer proof, lockfile/dependency inspection, bytecode/binding/manifest hash agreement, image digest capture, deployment config validation, service startup/health, cross-namespace reachability, and the real SDK client/service smoke. CI must fail on a dirty checkout, unexpected local/path/patched dependency, lockfile drift, mutable-only image identity, bundle mutation, or failed service/client flow. Never omit a gate from the report. Mark each onepassed, failed, or skipped. For a skip, record the exact reason, execution context, owner/handoff, and impact; for example, not clean-room tested, container provenance unknown, or deployment unverified. A skipped completion gate cannot produce a successful deployment status.
Failure report shape
When deployment fails, capture:- bytecode hash;
- branch/commit;
- party id or client slot;
- command run;
- redacted config path;
- first failing log line;
- whether local MPC passed;
- whether typed bindings matched;
- whether the failure is build, config validation, network reachability, identity, coordinator, protocol, or output decoding;
- labeled execution context and working directory;
- exact command, exit status, first actionable error, and unabridged log location;
- app/framework repository URLs, commits, and dirty state;
- CLI source, SDK source, lockfile hash, bundle member hashes, and applicable image digest;
- mount map and bind/advertised addresses when containers are involved;
- every skipped check and why it was skipped.
Common pitfalls
- Calling local MPC deployment because it returned the right answer on one machine.
- Treating the application backend, participant client, CLI, and browser as one
client/approle. - Accepting participant plaintext in control-plane APIs, persistence, queues, logs, or generic payload blobs.
- Using
.with_client_input(...)or.execute_local()in a production application-service path. - Replacing unsupported direct browser/client submission with an unapproved plaintext gateway.
- Letting each node compile source independently instead of distributing pinned bytecode.
- Running clients against bindings generated from stale bytecode.
- Forgetting node RPC addresses when using off-chain ClientStore IO.
- Committing client or party keys.
- Treating backend/curve changes as config-only changes without rebuilding and revalidating artifacts.
- Hiding a missing production server wrapper behind local SDK examples.