Scope: AI-agent-agnostic playbook for building complete Stoffel applications. Use this as the orchestrating path, then switch to the narrower skills for implementation details. Dependency assumption: use portable public dependencies by default. A local checkout is a separate, nonportable framework-development workflow and is allowed only when explicitly requested.
Use when
Use this playbook when the task is larger than one.stfl snippet: an app idea needs a runnable Stoffel program, Rust host code, typed client IO, local MPC smoke tests, and a path toward network deployment.
Goal
Make an AI agent build the smallest complete app first, verify every layer, then expand. Do not let the agent stop at plausible code, skip local MPC, or treatexecute_local() as deployment.
Milestone 0: establish portability
Complete this milestone before privacy design or implementation:- Discover and confirm the project root from the current working directory and repository markers such as
Stoffel.toml,Cargo.toml, or.git. Do not invent or require a machine-specific path such as/workspace/.... - Select Stoffel and related project dependencies from public, reproducible sources in this order: the current crates.io release, then the official GitHub repository pinned to a full immutable commit SHA when the needed change is not published.
- Reject floating branches and any default workflow that requires a local path, sibling checkout, or external filesystem checkout.
- Use a local Stoffel checkout only if the user explicitly requests framework development. Label that route nonportable, document it separately, and keep the app’s default build on public dependencies.
- If no suitable public dependency is available, stop and report the missing dependency and attempted public sources instead of substituting a local checkout.
End-to-end sequence
- Establish the dependency and filesystem portability contract.
- Resolve the application trust architecture and participant runtime.
- Define the privacy and output boundary.
- Choose backend and topology.
- Implement the minimal StoffelLang program.
- Validate with the CLI.
- Run local MPC with representative inputs.
- Build participant-client SDK integration around the compiled program.
- Generate typed client IO bindings from the exact bytecode.
- Add app, privacy-boundary, and network smoke tests.
- Prepare deployment artifacts and control-plane/client/network config.
- Prove the app in a clean-room environment.
- Hand off with real command output and remaining production assumptions.
1. Trust architecture and privacy boundary
For client-owned private input, the input owner’s device or process is the Stoffel MPC client. It submits the input through the Stoffel client protocol directly to the separately deployed MPC service. An application backend may provide public session configuration and receive non-sensitive lifecycle receipts or explicitly authorized opened aggregates, but it must not receive, deserialize, log, queue, cache, analyze, or persist participant plaintext. A backend gateway that accepts plaintext is a distinct, weaker trust model. Do not introduce it implicitly. Name it, document who can see the raw value, and require explicit approval before implementing it. Write this before coding:- Do not begin implementation while the plaintext location, submission process, forbidden components, persistence allowlist, or participant runtime support is unresolved.
- Treat
client,app,backend,gateway, and a local Tauri/Rust process as different roles. In a desktop app, the local Tauri/Rust process may be the participant client; a remote HTTP service is still an application backend. - Use HoneyBadgerMPC for ordinary private arithmetic over application values.
- Use AVSS only when commitments, curve-compatible artifacts, or threshold-cryptography outputs are part of the app boundary.
- Keep constants, thresholds, weights, encodings, and transcript bytes public unless they are genuinely private inputs.
- Name concrete unauthorized visibility: who would otherwise see which raw input, trace, losing order, support signal, or log value.
2. Repository shape
For a complete app, prefer this shape:.stfl modules. Validate private input shape in the participant client before submission. Keep public metadata validation, authorization, session lifecycle, non-sensitive receipts, and public result mapping in the application control plane. Do not define participant private fields in control-plane request or persistence schemas.
3. Build the smallest StoffelLang program
Start with one entrypoint and one representative output. Use typed secret annotations when the scalar shape is known:--input name=value. Do not combine named inputs and ClientStore inputs in one local run unless that exact CLI/runtime version has been verified to support the combination.
4. Rust SDK boundaries
Separate the participant client from the application control plane. The participant-owned Rust client should:- load pinned Stoffel bytecode, generated bindings, and public deployment config;
- use its assigned client slot and identity material;
- validate its owner’s private input shape locally;
- submit directly to the separately deployed coordinator/MPC parties;
- decode only outputs authorized for that client.
.with_client_input(...) with participant values.
A trusted local smoke harness may load or compile the program, inject representative fixture inputs, spawn local MPC only for development testing, and decode outputs for program-semantic assertions.
Local trusted-harness path:
.execute_local().await? as local testing where several MPC nodes/processes are spawned locally on the developer machine.
5. Typed client IO bindings
After the first passing bytecode build:.stflb using the project’s documented binding flow. In Rust, include the generated module and reference ProgramManifest when configuring typed clients. Rebuild bindings after any .stfl source or ClientStore shape change.
6. Verification ladder
Run the narrowest check at each layer:- control-plane request and persistence schemas contain no participant private fields or generic private payload blobs;
- a unique plaintext canary is absent from application-service requests, logs, traces, databases, caches, queues, analytics, crash reports, and receipts;
- production application-service code does not call
.with_client_input(...), spawn local MPC nodes, or use.execute_local(); - participant clients can submit to the MPC network after public session bootstrap without proxying the input through the control plane;
- each output is delivered only to its authorized recipient.
Clean-room completion evidence
Before declaring the app complete, test it from a fresh temporary directory or clean environment that has no Stoffel source checkout, sibling repository, or undeclared path dependency available. Starting from only the delivered app files, documented prerequisites, and network access to the selected public source:- install or resolve dependencies using the recorded public source;
- run
cargo metadata --locked --format-version 1and verify every external Stoffel package resolves from a registry or pinned official Git revision; - run the verification ladder for every delivered layer; and
- capture the clean environment description, dependency-resolution output, commands, exit status, and relevant test or smoke output.
7. Deployment handoff
Before handing the app to deployment work, produce:dist/program.stflbbuilt from the current source;- generated bindings from that bytecode;
- parties, threshold, backend, curve, preprocessing sizes, and expected client slots;
- coordinator address plan;
- node mesh addresses and node RPC addresses;
- client identity material requirements;
- expected client/output slots;
- exact local smoke command and output;
- known production assumptions or SDK gaps.
Common pitfalls
- Starting from Rust host code before the
.stflboundary is clear. - Treating a backend service, participant client, CLI, and Tauri Rust process as the same trust role.
- Sending participant plaintext to an application endpoint in the default client-owned-input architecture; a gateway that does this is a separately approved degraded-trust design.
- Using local fixture injection as evidence for the production private-input path.
- Silently replacing unsupported browser/client functionality with a plaintext backend gateway.
- Revealing intermediate secrets to make examples easier.
- Treating local MPC as production deployment.
- Compiling
.stflat runtime in production clients instead of loading pinned bytecode. - Forgetting to regenerate typed bindings after changing ClientStore shape.
- Reporting generated code without running
stoffel check,stoffel build, and a local MPC smoke. - Building only in a worktree that hides a required local or sibling Stoffel checkout.
- Claiming completion without clean-room dependency resolution and execution evidence.