Introduction
Telltale is a session-typed execution system for building protocol-critical concurrent and distributed programs. It is designed as a conservation system over protocol semantics: all semantically meaningful behavior is expressed in terms of six conserved quantities, and all other behavior is erased or reduced to those quantities. See Conservation Framework for the full design philosophy.
The system enables writing distributed protocols from a global perspective with automatic projection to local implementations. The Lean 4 formalization provides mechanized proofs of preservation, progress, coherence, and harmony. The same choreography compiles to native and WASM targets.
Content addressing assigns cryptographic identities to protocol artifacts. Asynchronous subtyping uses SISO decomposition with orphan-free deadlock checks. Endpoint transfer semantics support ownership handoff at runtime with progress token migration.
Core Concepts
Session types encode communication protocols as types. A type like !String.?Int.end sends a string, receives an integer, then closes the channel. The compiler checks that implementations follow the protocol contract.
Multiparty session types extend this to protocols with three or more participants. Global types describe the full protocol. Projection transforms global types into local types for each participant.
Choreographic programming builds on global types. A choreography describes computations and message flow from a neutral perspective. Endpoint projection generates the local implementation for each role.
Design Philosophy
Telltale enforces conservation over six properties: evidence, authority, identity, commitment, structure, and premise. These properties are mutually constitutive. A coherent protocol state is a simultaneous assignment of all six. See Conservation Framework for the conservation laws, erasure and reduction principles, and the closed semantic core object set.
Within that conservation framework, protocol-critical capability semantics are first class. The runtime and Lean model distinguish four capability classes: admission, ownership, evidence, and transition. This taxonomy covers protocol-critical authority, finalization, and handoff/reconfiguration semantics. It does not attempt to absorb general host application authorization.
See Capability Model for the full taxonomy.
Runtime Architecture
The execution architecture has three layers. The protocol machine is a deterministic small-step reducer that commits all protocol-visible truth. The guest runtime wraps the protocol machine with typed effect interfaces and concrete handlers. The host runtime is the surrounding application that provides time, storage, network, and process lifecycle.
Typed effect interfaces are the operational vocabulary between the protocol machine and the world. Internal handlers realize scheduling, dispatch, and replay. External handlers realize storage, network, and domain-specific integrations. See Architecture and Protocol Machine Architecture for details.
The protocol machine also derives a first-class finalization subsystem from its semantic objects. Observed reads, authoritative reads, publications, materialization proofs, canonical handles, receipts, and semantic handoffs are not just helpers. They are explicit replay-visible objects in the runtime and the Lean model.
Effect Handlers
Communication operations are algebraic effects. Sending and receiving messages are abstract operations that handlers implement concretely. Programs specify what to communicate.
Handlers determine how messages are delivered. In-memory channels serve testing scenarios. TCP connections serve deployment scenarios. The protocol logic remains unchanged across execution strategies.
Protocol Machine
The protocol machine compiles local types to bytecode instructions. It manages scheduling, message buffers, and session lifecycle. The concurrency parameter N controls how many coroutines advance per round.
Lean Verification
The Lean 4 formalization spans roughly 653 files and 133k lines in the core libraries (generated metrics in lean/CODE_MAP.md). It covers global types, local types, projection, and operational semantics. Deadlock-freedom claims are assumption-scoped with explicit premises for well-typedness, progress reachability, and fair scheduling.
The telltale-bridge crate provides JSON export and import for cross-validation between Rust and Lean. See Lean Verification for the verification pipeline.
Document Index
Paths by Role
Library Users
Start with Getting Started. Then read Choreographic DSL. Continue with Examples and API Reference.
Protocol-Machine Integrators
Start with Architecture. Then read Effect Handlers and Session Types and Protocol Machine Architecture. Continue with Protocol-Machine Bytecode Instructions and Session Lifecycle. See Simulation Overview for testing.
Paper Reviewers
Start with Conservation Framework and Architecture. Then read Theory, Lean Verification, and Rust-Lean Bridge and Parity. Continue with Capability Admission and Theorem Program. See Theorem Pack Inputs and Glossary and Notation Index for reference.
Further Reading
For MPST theory, see A Very Gentle Introduction to Multiparty Session Types. For asynchronous subtyping, see Precise Subtyping for Asynchronous Multiparty Sessions.
For choreographic programming, see Introduction to Choreographies. For integration with session types, see Applied Choreographies.
See Glossary and Notation Index for shared terminology.
Conservation Framework
Telltale is a conservation system over protocol semantics. All semantically meaningful behavior is expressed in terms of conserved quantities. All other behavior is erased or reduced to those quantities.
This framework defines the six properties that Telltale conserves, the principles that constrain the programming model, and the semantic objects through which conservation is realized at runtime.
Conserved Properties
Telltale enforces conservation over six properties. Each property has a conservation law stating what the system guarantees across all protocol evolution.
Evidence
Evidence is the integrity of what has been established. Witnesses, proofs, and attestations are the concrete forms of evidence. Evidence backs protocol claims and gates protocol progression.
Conservation law: the evidential backing of any protocol claim cannot increase except through explicit witness production. It cannot decrease except through explicit invalidation. Evidence cannot be fabricated from weaker evidence.
Authority
Authority is the exclusivity of who may act. Ownership is the concrete form of authority. Authority determines which actor may produce evidence, commit state, or transfer control.
Conservation law: exactly one semantic owner holds authority over a given resource at any time. Authority cannot be duplicated. Transfer is explicit and invalidates prior authority.
Identity
Identity is the definiteness of object references. Identity distinguishes one unit of semantic work from another across retries, effects, and handoffs. Definite references are what make ownership meaningful and commitments trackable.
Conservation law: identity is stable across all transformations of an operation. It cannot be reconstructed from ambient context. It is assigned once and carried, never re-derived.
Commitment
Commitment is the account of outstanding obligations. Outstanding effects are the concrete form of commitment. A commitment is a promise that the system will produce a resolution for a given operation.
Conservation law: every commitment must resolve to exactly one of success, failure, timeout, cancellation, or invalidation. Commitments cannot silently disappear. The total number of unresolved commitments is always known.
Structure
Structure is the essential shape of the protocol. Multiparty session types are the concrete form of structure. Structure defines the topology of interaction, the composition of roles, and the coherence between local and global behavior.
Conservation law: the compositional structure of the protocol is defined entirely by its type. Local behavior must remain a valid projection of the global protocol. No runtime behavior can alter the protocol shape. See Theory for the session type foundations.
Premise
Premise is the explicitness of environmental assumptions. Failure models, fairness assumptions, and progress expectations are the concrete forms of premise. Premise determines what the protocol can rely on from its environment.
Conservation law: the system operating assumptions are explicit and fixed for a given execution context. Premise violation triggers explicit escalation rather than silent degradation. Escalation classes include blocked, degraded, and failed.
Property Relations
The six properties are mutually constitutive. They do not layer into a dependency hierarchy. A coherent protocol state is a simultaneous assignment of all six. A violation in any one can manifest as a failure in any other.
Erasure Principle
Any behavior that is not part of the conserved quantities is not part of the programming model. Scheduling, task execution, callback timing, retry loops, thread interleaving, and background task structure are implementation details. They must reduce to the conserved system.
Developers reason about evidence, authority, identity, commitment, structure, and premise. They extend the system through typed effect interfaces that enforce the conservation laws at the boundary. Domain logic plugs into contracts, not into concurrency.
Reduction Principle
All runtime behavior must reduce to the conservation framework.
| Runtime phenomenon | Reduced form |
|---|---|
| async execution | commitment lifecycle |
| race condition | authority violation |
| retry logic | identity + commitment |
| stale callback | invalidated commitment |
| state bug | invalid evidence or identity |
| timeout | premise escalation |
| background task | detached or structured commitment |
This table maps common runtime phenomena to their conservation-framework equivalents. If a runtime behavior cannot be expressed in these terms, it does not belong in the model.
Eliminated Bug Classes
The system eliminates these classes of bugs by construction. Each maps to a violation of specific conserved properties.
| Bug class | Description | Violated property | Eliminated by |
|---|---|---|---|
| Hidden concurrency | Work outside structured control | commitment | Explicit commitments, no ambient async |
| Authority ambiguity | Multiple actors advancing same state | authority | Single semantic owner, explicit handoff |
| Weakening of evidence | Fallback from strong to weak backing | evidence | Monotonic witness strengthening, canonical handles |
| Silent failure | Work disappearing or failing invisibly | commitment | Commitment conservation, typed outcomes |
| Late result races | Stale results applied after handoff | authority, identity | OutstandingEffect tracking, identity + owner validation |
| Observational state becoming evidence | Cache or UI state treated as authoritative | evidence | Separation of AuthoritativeRead and ObservedRead |
| Best-effort work defining success | Optional work affecting terminal state | commitment | Work classification (required vs best-effort) |
| Unbounded waiting | Operations that never resolve | commitment, premise | ProgressContract, required escalation |
| Hidden reentrancy | Same domain re-entered unexpectedly | structure | Explicit reentrancy rules |
| Multiple evidence sources | Multiple publication paths | evidence, authority | Canonical publication path, proof-bound publication |
Semantic Core Objects
The conservation framework is realized through a closed set of semantic objects. All protocol-visible language features must lower to these objects or to pure structural session terms. If a feature cannot be reduced to this semantic core, it is not admitted as part of the model.
| Object | Role | Capability class |
|---|---|---|
Region | Locality and framing domain for structured coordination | structural context |
OperationInstance | Stable identity for one semantic operation across retries, handoff, and replay | identity carrier |
OutstandingEffect | One unresolved capability invocation with explicit lifecycle state | commitment/evidence support |
SemanticHandoff | Explicit transfer of responsibility and authority between actors | transition |
AuthoritativeRead | Proof-carrying read surface that may support semantic commitment | evidence |
ObservedRead | Non-authoritative read surface for rendering, diagnostics, or advisory logic | evidence |
MaterializationProof | Typed proof that a canonical artifact has been materially established | evidence |
CanonicalHandle | Non-forgeable reference obtained from authoritative materialization | evidence |
ProgressContract | Explicit liveness and budget contract governing waits and escalation | premise/commitment support |
These objects are first-class in the Lean formalization, represented canonically in Rust, and referenced in the protocol machine semantic audit surface. See Protocol Machine Architecture for the runtime accessors and Lean Verification for the proof surfaces.
Implementation Techniques
Each implementation technique serves one or more conserved properties.
| Technique | Description | Properties conserved |
|---|---|---|
| Protocol machine | Single component that commits state | authority, structure |
| Operation instances | Explicit identity for all semantic work | identity |
| Outstanding effects | All deferred work tracked to resolution | commitment |
| Semantic handoff | Explicit authority transfer between actors | authority, identity |
| Witness system | All evidence is proof-bearing | evidence |
| Capability algebra | Authority is explicit and constrained | authority |
| Effect system | Boundary interactions are typed and classified | commitment, evidence |
| Progress contracts | Liveness is explicit and enforced | commitment, premise |
| Structured session decomposition | All concurrency follows session structure | structure, commitment |
| Enforcement | Type-level where possible, runtime fail-closed otherwise | all |
Debugging Model
Every failure is a conservation failure. Every failure must be explainable as a violation of one or more conserved properties.
Instead of “the system hung,” a developer sees: which operation, which owner, which commitment, which required witness, which premise, and which escalation state. Every failure answers whether the protocol was correct, whether authority was modeled correctly, whether the premise was correct, whether the effect classification was correct, and whether the progress contract was correct.
Related Docs
- Architecture
- Theory
- Effect Handlers and Session Types
- Protocol Machine Architecture
- Authority Language Surface
Theory
This document explains the theory that Telltale builds on. It is organized by audience relevance. Developer-facing protocol concepts come first. Proof-oriented topics come last.
Foundations
Session types describe communication protocols as types. They let the type system check message order, branch structure, and termination behavior. Telltale uses them as the structure layer of its protocol model.
Why Session Types
Distributed programs fail when participants disagree about message order or branch choice. Ordinary data types do not describe those communication obligations. Session types add that missing protocol structure.
With session types, communication mistakes become type errors rather than runtime surprises. This is the main reason they matter in practice. They let developers state protocol shape directly and check it mechanically.
Binary Session Types
Binary session types describe one conversation between two parties. The core actions are send, receive, choice, and termination.
| Syntax | Meaning |
|---|---|
!T.S | send a value of type T, then continue as S |
?T.S | receive a value of type T, then continue as S |
⊕{...} | choose one labeled branch |
&{...} | accept one labeled branch |
end | terminate the session |
Binary types also rely on duality. If one side sends, the other side must receive. If one side chooses, the other side must branch on that choice.
Client: !Request.?Response.end
Server: ?Request.!Response.end
This pair is dual. The client sends then receives. The server receives then sends.
Multiparty Session Types
Multiparty session types extend the same idea to three or more roles. They separate the protocol into a global view and one local view per participant. This is the core abstraction behind Telltale choreographies.
Global types describe the whole protocol from a neutral perspective. Local types describe only what one role must send and receive. Projection computes those local types from the global type.
Global:
Alice -> Bob: Request.
Bob -> Carol: Forward.
Carol -> Bob: Result.
Bob -> Alice: Response.
end
This global type describes the whole conversation. Projection gives Alice a two-step local view, Bob a four-step local view, and Carol a two-step local view.
Projection and Merging
Projection extracts one role-local type from the global type. When a message does not involve the target role, projection skips it. When a message does involve the target role, projection turns it into a local send or receive action.
Branching requires one extra operation called merge. If a participant is not the branch chooser, its projected local view must combine the possible continuations. Merge succeeds only when those continuations remain compatible from that participant’s viewpoint.
Protocol Constraints
Not every syntactically valid protocol is meaningful. Telltale relies on a set of constraints that a protocol must satisfy before projection and execution are well behaved. These are often called well-formedness conditions in the literature.
For global protocols, the main rules are:
- recursion variables must be bound
- communication branches must be non-empty
- a role must not send to itself
- recursion must pass through communication
For local protocols, the main rules are:
- the type must be closed
- recursion must unfold to observable communication or termination
These constraints are developer-facing. They are the rules a protocol must satisfy in order to project cleanly and support the later guarantees.
Recursion
Session types support recursive protocols through fixed points. This is how a protocol expresses repetition or long-running interaction.
μX. Alice -> Bob: Ping. Bob -> Alice: Pong. X
This protocol repeats the same request-response cycle forever. Recursion is valid only when unfolding eventually reaches observable communication.
Subtyping
Subtyping determines when one protocol can replace another. This matters when a component offers a more specific behavior than the caller requires.
In synchronous settings, subtyping follows the usual session-typing variance rules. In asynchronous settings, buffering allows some outputs to move earlier relative to inputs. Telltale implements asynchronous subtyping using the Bravetti et al. POPL 2021 algorithm.
Protocol Guarantees
This section describes the guarantees developers usually care about once a protocol satisfies the foundational constraints. These guarantees are still premise-scoped. They depend on explicit assumptions about execution and delivery.
Coherence
Coherence is the central MPST invariant. It states that the collective local state of the protocol still matches the global protocol state. Without coherence, projection would not give a trustworthy local view.
In Telltale, coherence is tracked per communication edge. Each edge relates the sender state, the receiver state, the relevant buffer contents, and the remaining global structure. This is the invariant that lets local endpoint behavior stay aligned with the choreography.
Preservation
Preservation states that a well-typed and coherent protocol state remains well-typed and coherent after a valid step. A correct protocol execution does not silently fall out of the typing discipline. This is the usual subject-reduction property.
Preservation is mainly a meta-level guarantee. Developers benefit from it because protocol execution stays inside the checked state space once it starts there.
Progress
Progress states that a well-typed protocol can continue unless it has already terminated. A participant does not get stuck waiting for behavior the protocol itself forbids.
This guarantee is premise-scoped. Telltale states those premises explicitly. They include well-typedness, reachability of communication, and fair scheduling.
Deadlock Freedom
Deadlock freedom is the developer-facing consequence of preservation plus progress under the declared premises. A well-typed protocol cannot reach a state where all participants are permanently blocked by protocol structure alone.
This guarantee does not ignore the environment. If the delivery model or scheduler violates the stated premises, the theorem does not apply. That separation is part of Telltale’s premise discipline.
Harmony
Harmony is a compilation-correctness property rather than a direct developer-facing liveness property. It states that global protocol evolution and projected local evolution stay in correspondence. This is what makes endpoint projection trustworthy.
project(step(G)) = localStep(project(G))
This equation says that stepping the choreography and then projecting gives the same result as projecting first and stepping locally. Developers benefit indirectly because generated endpoint behavior stays aligned with the choreography they wrote.
Operational Extensions
This section covers theory that matters once the protocol interacts with realistic runtime concerns. These topics are still user-relevant, but they are more specialized than the core session-typing model.
Delegation and Reconfiguration
Delegation transfers a session endpoint from one participant to another. This allows the active participant set to change during execution. It is the main extension beyond fixed-role MPST.
Delegation is safe only when the transferred endpoint state matches what the receiver can legally assume.
Telltale captures those conditions with DelegationWF.
The important developer-facing point is that endpoint transfer is governed by explicit protocol-side conditions, not by ambient host authority.
Delivery Models
Asynchronous session reasoning depends on the delivery model. Different delivery models allow different message orderings and therefore support different theorems.
Telltale parameterizes theorems over DeliveryModel.
Important examples are FIFO, causal, and lossy delivery.
This keeps the theorem shape stable while making delivery assumptions explicit.
Host-Runtime Ownership Contract
The Rust runtime adds an ownership contract around delegation, reconfiguration, and host-visible mutation. This layer uses concepts such as current owner capability, ownership generation, transfer receipts, rollback, and stale-owner rejection. It is an implementation hardening layer.
This runtime contract is aligned with theory-side predicates such as DelegationWF.
It should not be read as a separate theorem family.
It is the host/runtime realization of the protocol boundaries that the theory already identifies.
Verification Topics
This section is mainly for readers of the Lean formalization. These concepts matter for proofs and mechanization. They are not the first concepts a protocol author usually needs.
Bisimulation
Bisimulation compares two types by observable behavior rather than by literal syntax. It is useful when recursive types differ syntactically but unfold to the same behavior. This is a proof and equivalence technique.
In practice, bisimulation matters most when reasoning about proof identities, recursive encodings, and normalization arguments. It is less central for day-to-day protocol authoring.
De Bruijn Indices
The Lean formalization uses de Bruijn indices in foundational proof layers. This avoids name-capture problems during substitution and recursion reasoning. Rust-facing protocol APIs keep named recursion variables for usability.
If you are reading the proof code, expect de Bruijn forms in the foundational libraries. If you are writing protocols, this detail is mostly invisible.
Lean Formalization
The Lean formalization proves the core metatheory behind projection and protocol execution. Important theorem families include coherence, preservation, progress, and harmony. Current library structure and metrics are tracked in Lean Verification Code Map.
At a high level, SessionTypes and SessionCoTypes cover syntax and equivalence.
Choreography covers projection and harmony.
Protocol covers coherence and typing.
Runtime covers the protocol-machine model and runtime correctness surfaces.
Theory in Telltale
Telltale implements the theory across Rust and Lean. The Rust crates provide executable data types and algorithms. The Lean libraries provide mechanized proofs and proof-facing models.
On the Rust side, telltale-types defines GlobalType and LocalTypeR.
telltale-theory implements projection, merge, duality, and subtyping.
The runtime and machine layers then realize those structures operationally.
On the Lean side, the formalization provides the theorem libraries that justify those structures. See Lean Verification for the proof pipeline and source layout. See Introduction for the system-level overview.
Conservation and Session Structure
Session types are Telltale’s concrete realization of structure conservation. The protocol type fixes the allowed interaction shape. Local behavior must remain a valid projection of that shape.
The other conserved properties extend the guarantee surface beyond session structure alone. Evidence governs witnesses and attestations. Authority governs exclusive ownership. Identity governs stable references. Commitment governs outstanding effects. Premise governs environmental assumptions.
In the runtime and Lean model, these properties are carried by explicit typed objects. They are not left as ambient host assumptions. See Conservation Framework for the full conservation model.
Further Reading
For deeper study of session type theory:
- A Very Gentle Introduction to Multiparty Session Types
- Precise Subtyping for Asynchronous Multiparty Sessions
- Applied Choreographies
Within this documentation:
Architecture
Overview
Telltale compiles global protocol specifications into local session types for each participant. The system is organized as a conservation system over protocol semantics. All protocol-critical behavior reduces to six conserved properties: evidence, authority, identity, commitment, structure, and premise. See Conservation Framework for the full design philosophy.
The architecture has three compile-time stages and two runtime paths:
- DSL and parsing (choreographic syntax to AST)
- Projection (global protocol to local types)
- Code generation (local types to Rust code and effect programs)
- Effect handler execution (async interpreter with pluggable transports)
- Protocol-machine execution and simulation (protocol machine with scheduler and deterministic middleware)
The current formal-verification claim is narrower than this full architecture diagram. The Rust parser, lowering, projection, code generation, and macro entry paths are operationally checked and Lean-cross-validated, but they remain outside the current formal claim until a mechanized compiler-correctness proof exists for the claimed DSL subset.
Runtime Layering
The execution architecture separates three concerns. The protocol machine is a deterministic small-step reducer that is the sole authority over semantic progression. The guest runtime wraps the protocol machine with typed effect interfaces and concrete handlers. The host runtime is the surrounding system that provides time, storage, network, and process lifecycle.
Typed effect interfaces form the operational vocabulary between the protocol machine and the world. Internal handlers realize scheduling, dispatch, replay, and simulation. External handlers realize storage, network, and domain-specific host integrations. Both handler domains interpret the same typed interfaces.
Component Diagram
graph TB
subgraph Input["Developer Input (Compile-Time)"]
DSL["Choreography DSL<br/>Global Protocol Specification"]
end
subgraph Layer1["Layer 1: Parsing & AST Construction"]
Parser["Parser<br/>(Pest Grammar)"]
AST["AST<br/>Choreography + Protocol Tree"]
end
subgraph Layer2["Layer 2: Projection"]
Proj["Projection Algorithm"]
LT["Local Types<br/>(Per Role)"]
end
subgraph Layer3["Layer 3: Code Generation"]
CodeGen["Code Generator"]
Session["Generated Session Types"]
Effects["Generated Effect Programs"]
end
subgraph Layer4["Layer 4: Effect Runtime"]
Handler["Effect Handler<br/>(InMemory / Telltale)"]
Transport["Transport Layer<br/>(Channels / Network)"]
Exec["Running Protocol"]
end
subgraph Layer5["Layer 5: Protocol Machine + Simulation"]
VMCompiler["Protocol-Machine Compiler<br/>(LocalTypeR → Bytecode)"]
PM["Protocol Machine"]
Scheduler["Scheduler<br/>(Policy-Based)"]
Sessions["Session Store"]
Buffers["Bounded Buffers"]
Middleware["Simulator Middleware<br/>(Latency / Faults / Properties / Checkpoints)"]
end
DSL --> Parser
Parser --> AST
AST --> Proj
Proj --> LT
LT --> CodeGen
CodeGen --> Session
CodeGen --> Effects
Session --> Handler
Effects --> Handler
Handler --> Transport
Transport --> Exec
LT --> VMCompiler
VMCompiler --> PM
PM --> Scheduler
Scheduler --> Sessions
Sessions --> Buffers
Middleware --> PM
This diagram summarizes the compile time flow from DSL input to runtime execution. It also highlights the boundary between compilation, the guest runtime, and host-runtime effect handling.
Core Components
AST Module
The AST module is located in rust/language/src/ast/. It represents choreographies as data structures.
The main type is Choreography.
#![allow(unused)]
fn main() {
pub struct Choreography {
pub name: Ident,
pub namespace: Option<String>,
pub roles: Vec<Role>,
pub protocol: Protocol,
pub attrs: HashMap<String, String>,
}
}
This struct holds the protocol name and optional namespace. It contains participating roles and the protocol tree. Metadata attributes are stored in the attrs field.
The Protocol enum defines all protocol actions.
The Protocol enum in rust/language/src/ast/protocol.rs is a recursive tree structure with variants for all DSL constructs. Key variant families include:
- Communication:
Send,Broadcast - Branching:
Choice,Case - Authority:
Let(withAuthorityBindingMode),Publish,PublishAuthority,Materialize,Handoff - Commitment lifecycle:
Begin,Await,Resolve,Invalidate - Control flow:
Rec,Var,Loop,Parallel,Timeout,DependentWork - Extensibility:
Extension - Termination:
End
NonEmptyVec is used where the DSL enforces at least one branch. See the source file for the full definition.
Parser Module
The parser module is located in rust/language/src/compiler/parser/. It converts DSL text into AST using the Pest parser generator.
The parser validates role declarations and builds the protocol tree from the input text. It runs a layout preprocessor before the grammar parse. This enables layout-sensitive syntax with -> message arrows, => branch arrows, choice Role at branches with leading |, and par blocks.
Two entry points are available.
#![allow(unused)]
fn main() {
pub fn parse_choreography_str(input: &str) -> Result<Choreography, ParseError>
pub fn parse_choreography_file(path: &Path) -> Result<Choreography, ParseError>
}
The parser performs syntactic validation and basic semantic checks. The file-based
entry point expects the canonical .tell source extension.
Projection Module
The projection module is located in rust/language/src/compiler/projection.rs. Projection transforms a global protocol into a local view for each role.
The algorithm determines what each participant should do.
#![allow(unused)]
fn main() {
pub fn project(choreography: &Choreography, role: &Role) -> Result<LocalType, ProjectionError>
}
Projection handles merging parallel branches. It also detects conflicts between branches.
Code Generation Module
The codegen module is located in rust/language/src/compiler/codegen/. It converts local types into Rust session types and effect programs.
The generator creates compile-time type-safe protocol implementations.
#![allow(unused)]
fn main() {
pub fn generate_session_type(role: &Role, local_type: &LocalType, protocol_name: &str) -> TokenStream
pub fn generate_choreography_code(name: &str, roles: &[Role], local_types: &[(Role, LocalType)]) -> TokenStream
pub fn generate_choreography_code_with_extensions(
choreography: &Choreography,
local_types: &[(Role, LocalType)],
extensions: &[Box<dyn ProtocolExtension>],
) -> TokenStream
pub fn generate_choreography_code_with_dynamic_roles(
choreography: &Choreography,
local_types: &[(Role, LocalType)],
) -> TokenStream
pub fn generate_choreography_code_with_namespacing(
choreo: &Choreography,
local_types: &[(Role, LocalType)],
) -> TokenStream
pub fn generate_choreography_code_with_annotations(
name: &str,
roles: &[Role],
local_types: &[(Role, LocalType)],
choreo: &Choreography,
) -> TokenStream
pub fn generate_choreography_code_with_topology(
choreography: &Choreography,
local_types: &[(Role, LocalType)],
inline_topologies: &[InlineTopology],
) -> TokenStream
pub fn generate_dynamic_role_support(choreography: &Choreography) -> TokenStream
pub fn generate_role_implementations(
role: &Role,
local_type: &LocalType,
protocol_name: &str,
) -> TokenStream
pub fn generate_topology_integration(
choreography: &Choreography,
inline_topologies: &[InlineTopology],
) -> TokenStream
pub fn generate_helpers(name: &str, messages: &[MessageType]) -> TokenStream
}
The generator creates session types and role structs. It supports dynamic roles including parameterized roles and runtime management.
Effect System
The effect system is located in rust/runtime/src/effects/. It decouples protocol logic from transport.
Protocols are represented as effect programs. Handlers interpret these programs.
#![allow(unused)]
fn main() {
pub trait ChoreoHandler: Send {
type Role: RoleId;
type Endpoint: Endpoint;
async fn send<M: Serialize + Send + Sync>(
&mut self, ep: &mut Self::Endpoint, to: Self::Role, msg: &M
) -> ChoreoResult<()>;
async fn recv<M: DeserializeOwned + Send>(
&mut self, ep: &mut Self::Endpoint, from: Self::Role
) -> ChoreoResult<M>;
async fn choose(
&mut self, ep: &mut Self::Endpoint, who: Self::Role, label: <Self::Role as RoleId>::Label
) -> ChoreoResult<()>;
async fn offer(
&mut self, ep: &mut Self::Endpoint, from: Self::Role
) -> ChoreoResult<<Self::Role as RoleId>::Label>;
async fn with_timeout<F, T>(
&mut self, ep: &mut Self::Endpoint, at: Self::Role, dur: Duration, body: F
) -> ChoreoResult<T>
where
F: Future<Output = ChoreoResult<T>> + Send;
}
}
Handlers implement this trait to provide different execution strategies. This async handler is distinct from the synchronous telltale_machine::model::effects::EffectHandler used by the protocol machine.
Use Effect Handlers and Session Types for protocol-machine integration guidance.
Protocol-Machine Execution Layer
The protocol machine provides a bytecode execution model for local types. The telltale-machine crate compiles LocalTypeR into bytecode and executes it with a policy-based scheduler. The telltale-simulator crate wraps guest runtimes around that protocol machine with deterministic middleware for latency, faults, property monitoring, and checkpointing.
The protocol machine maintains session state with bounded message buffers. Each coroutine references its assigned program by ID. The scheduler policies are observationally equivalent per the Lean model. Nested protocol machines can be hosted inside a coroutine for hierarchical simulation.
The choreography runtime also includes a deterministic heap utility for explicit resource tracking and heap commitments. That heap now uses a versioned canonical encoding boundary for resource identity and Merkle inputs. Its ordering and replay rules are part of the documented contract. See Resource Heap for the heap-side contract.
At the embedding boundary, the protocol machine distinguishes current host ownership from protocol typing and capability admission. Production host integrations use load_choreography_owned(...) and OwnedSession for explicit session-local authority after open. Guest runtimes embed the protocol machine inside a host runtime with explicit external handlers.
Delegation and reconfiguration paths emit explicit receipts and audit records. See Protocol Machine Architecture for details on the underlying bytecode protocol-machine architecture.
Data Flow
This section demonstrates the transformation of a choreography through each layer.
Input choreography:
protocol RequestResponse =
roles Alice, Bob
Alice -> Bob : Request of api.Request
Bob -> Alice : Response of api.Response
The choreography specifies a request-response pattern.
After parsing, the AST contains a nested send structure.
#![allow(unused)]
fn main() {
Protocol::Send {
from: Alice, to: Bob, message: Request,
continuation: Protocol::Send {
from: Bob, to: Alice, message: Response,
continuation: Protocol::End
}
}
}
This represents the global protocol tree.
After projection for Alice, the local type shows send then receive.
#![allow(unused)]
fn main() {
LocalType::Send {
to: Bob, message: Request,
continuation: LocalType::Receive {
from: Bob, message: Response,
continuation: LocalType::End
}
}
}
Alice sends a request and waits for a response.
After projection for Bob, the local type shows receive then send.
#![allow(unused)]
fn main() {
LocalType::Receive {
from: Alice, message: Request,
continuation: LocalType::Send {
to: Alice, message: Response,
continuation: LocalType::End
}
}
}
Bob waits for a request and sends a response.
After code generation for Alice, a session type is created.
#![allow(unused)]
fn main() {
type Alice_Protocol = Send<Bob, Request, Receive<Bob, Response, End>>;
}
This session type enforces the protocol at compile time.
At runtime, the protocol-machine and generated effect boundary drive actual communication. The older choreography-layer effect-program builder remains an internal implementation technique, not the canonical public architecture story.
Design Decisions
Why Conservation
Telltale treats protocol semantics as conserved quantities rather than emergent properties. Every runtime phenomenon reduces to the conservation framework. Async execution reduces to commitment lifecycle. Race conditions reduce to authority violations. Retry logic reduces to identity and commitment. See the full reduction table in Conservation Framework.
This design eliminates classes of bugs by construction. Hidden concurrency, authority ambiguity, silent failure, late result races, and unbounded waiting all map to violations of specific conserved properties. The erasure principle removes everything that is not part of the conserved system from the programming model.
Why Choreographic Programming
Choreographies specify the global protocol once and projection generates local code for each role. This realizes structure conservation: the compositional structure of the protocol is defined entirely by its type, and no runtime behavior can alter the protocol shape.
Why Effect Handlers
Effect handlers are the typed operational vocabulary between the protocol machine and the world. They realize commitment conservation: every effect is a tracked commitment that must resolve to a terminal class. Internal handlers realize scheduling, dispatch, and replay. External handlers realize storage, network, and domain-specific integrations. See Choreography Effect Handlers for the async surface and Effect Handlers and Session Types for the protocol-machine boundary.
Why Capability Semantics Are First Class
Telltale treats protocol-critical capability semantics as part of the model, not as an application policy layer bolted on later. Admission gates, live ownership, evidence/finalization artifacts, and transition artifacts all change protocol-visible truth or determine who may change it. They therefore belong in the runtime/Lean boundary.
The important non-goal is scope control. Telltale does not try to become a general-purpose host authorization framework. Product-specific user/resource policy remains outside the model unless it changes protocol-critical semantics and can be represented as typed runtime/Lean objects.
Why Session Types
Session types provide compile-time guarantees about protocol compliance. The Rust type system enforces that each role follows their protocol correctly. Type checking prevents message ordering and payload-shape violations at compile time. Global deadlock claims remain assumption-scoped in the theory results.
Platform Abstraction
The runtime module provides platform-specific async primitives. Native targets use tokio. WASM uses wasm-bindgen-futures. The same code runs on servers and in browsers.
Extension Points
Custom Handlers
Implement ChoreoHandler for choreography-layer transports. Implement EffectHandler for protocol-machine host integration. See Choreography Effect Handlers and Effect Handlers and Session Types for details.
Middleware
Wrap handlers with middleware for cross-cutting concerns. Logging, metrics, and retry logic can be added as middleware. Multiple middleware layers can be stacked on a single handler.
Implementation Organization
This page focuses on conceptual architecture: compilation stages, runtime execution paths, and why those boundaries exist.
For concrete workspace layout, crate dependency edges, per-crate responsibilities, and Lean constructor correspondence, see Code Organization.
Code Organization
This document describes the current implementation layout of the repository. It covers workspace structure, crate responsibilities, and the main dependency directions between crates. For the conceptual system design, see Architecture.
Workspace Layout
The repository root contains the facade crate, the Cargo workspace definition, the Lean formalization, and the documentation tree.
Most Rust implementation crates live under rust/.
telltale/
├── Cargo.toml
├── rust/
│ ├── src/ root facade crate source
│ ├── types/ telltale-types
│ ├── theory/ telltale-theory
│ ├── language/ telltale-language
│ ├── macros/ telltale-macros
│ ├── runtime/ telltale-runtime
│ ├── machine/ telltale-machine
│ ├── simulator/ telltale-simulator
│ ├── viewer/ telltale-viewer
│ ├── ui/ telltale-ui
│ ├── web/ telltale-web
│ ├── bridge/ telltale-bridge
│ ├── transport/ telltale-transport
│ └── lints/ telltale-lints
├── lean/
├── docs/
└── examples/
This layout reflects the current workspace member list in the root Cargo.toml.
external-demo and external-demo-macros are excluded from the workspace.
They live at the repository root as separate example crates.
Dependency Shape
The workspace is organized around a small set of stable directions.
telltale-types is the shared foundation crate.
telltale-language is the shared choreography frontend.
The main direct dependency directions are:
telltale-typesis used bytelltale-theory,telltale-language,telltale-machine,telltale-simulator,telltale-runtime,telltale-transport, and the roottelltalecrate.telltale-theoryis used bytelltale-machine,telltale-runtime,telltale-bridge, and the roottelltalecrate behind features.telltale-languageis used bytelltale-macros,telltale-runtime, and the roottelltalecrate.telltale-macrosis used bytelltale-runtimeand the roottelltalecrate.telltale-machineis used bytelltale-simulator,telltale-bridge, and the roottelltalecrate.telltale-simulatoris used bytelltale-vieweras the authoritative source of simulation artifacts.telltale-vieweris used bytelltale-uias the pure artifact/query/command layer.telltale-uiis used bytelltale-webas the shared portable Dioxus UI core.- The root
telltalecrate is used bytelltale-runtimeas a facade dependency. telltale-runtimeis used bytelltale-bridgethrough optional features and bytelltale-transport.
The important organizational change is that the shared frontend now lives in telltale-language.
telltale-runtime is no longer the right crate to describe as the main DSL parser and projection layer.
YAML is not a supported user configuration boundary today. If a future crate accepts
user-provided YAML, the parser must cap input bytes and recursion depth before
deserialization and should prefer serde_yaml_ng over the increasingly stale
serde_yaml crate.
Crate Responsibilities
telltale-types
telltale-types is the foundational data-model crate.
It defines GlobalType, LocalTypeR, Label, PayloadSort, fixed-point numeric support, and content-addressing infrastructure.
It has no dependencies on other workspace crates.
The content-addressing layer is also here.
DefaultContentHasher is the central policy alias for content hashing and currently resolves to BLAKE3.
The sha256 feature enables explicit SHA-256 helper types as an alternative.
telltale-theory
telltale-theory contains pure session-type algorithms.
Its core responsibilities are projection support, merge, duality, well-formedness, bounded recursion strategies, subtyping, semantics, and coherence predicates.
It does not own parsing or runtime execution.
This crate depends only on telltale-types.
It is the algorithm layer consumed by the machine, runtime, bridge, and optional root-crate theory features.
Projection memoization uses the content-addressing facilities from telltale-types.
telltale-language
telltale-language is the shared choreography frontend.
It owns the DSL AST, parsing, validation, projection to frontend LocalType, code generation helpers, effect-interface scaffolding support, and integration helpers such as compile_choreography(...) and CompiledChoreography.
Downstream integrations that need validated ASTs, projected locals, ordered annotation records, or theory-facing artifact export should start here.
This crate is now the canonical frontend layer for both first-party and downstream integrations.
It depends on telltale-types and no other workspace crate.
telltale-macros
telltale-macros provides the procedural macro surface.
It exports tell!, session, Role, Roles, and Message.
It depends on telltale-language for the shared frontend behavior used by the macro expansion path.
The tell! macro is the main compile-time DSL entry point for inline choreographies.
It generates the protocol module, typed effect surfaces, and projectable session surfaces where that correspondence exists.
Macro correctness is guarded by operational tests rather than a mechanized proof.
telltale-machine
telltale-machine provides the protocol machine and guest-runtime surfaces.
It compiles LocalTypeR into bytecode and executes it with bounded buffers, scheduler policies, observability, and protocol-machine semantic objects.
It is the canonical semantic core used by the simulator and by direct host embeddings.
The main public front doors are telltale_machine::model, telltale_machine::runtime, and telltale_machine::semantics.
telltale_machine::model exposes execution, effect, scheduler, state, semantic-object, and transition vocabulary.
telltale_machine::runtime exposes loading, admission, and runner surfaces such as CodeImage, ProtocolMachine, GuestRuntime, and OwnedSession.
telltale_machine::semantics exposes the higher-level semantic interpretation and analysis surface layered over the protocol machine core.
telltale-simulator
telltale-simulator wraps the protocol machine with deterministic testing and simulation infrastructure.
It provides runner entry points, SimulationHarness, scenario parsing, field/environment hooks, fault injection, network modeling, property checks, typed persisted replay/checkpoint artifacts, replay artifacts, and distributed simulation reports.
This crate depends directly on telltale-machine and telltale-types.
It is the preferred test path for third-party implementations of protocol-machine EffectHandler.
Its generated-effect helpers exist as adjacent APIs behind a narrower helper boundary, but the main integration path is still SimulationHarness.
telltale-viewer
telltale-viewer is the pure artifact/model layer for the simulator webapp work.
It owns viewer artifact envelopes, schema/version handling, branch patch types, search/query models, and the first application-service boundary for run inspection.
This crate is intentionally renderer-free.
Browser APIs and Dioxus component concerns belong in the future telltale-web and telltale-ui layers, not here.
The goal is to keep artifact loading, branch mutation requests, and historical inspection state deterministic and reusable across shells.
telltale-search
telltale-search is the generic weighted-graph search substrate for the
workspace.
It owns canonical search-machine semantics, replay artifacts, determinism and
fairness capability vocabulary, explicit graph-epoch boundaries, and
fairness-aware scheduler artifacts for weighted graph search. The crate is
target-agnostic. The optional multi-thread feature only enables native
speculative proposal execution.
This crate is intentionally downstream-oriented and domain-generic.
Application-specific routing, RF, mesh, or topology policies should live in
downstream crates that implement its domain traits rather than in
telltale-search itself. The stable import posture is:
- fail-closed
SearchQuery::try_multi_goal(...)andSearchQuery::try_candidate_set(...)constructors - generic selected-result surfaces such as
SelectedSolution,SearchResultBoundArtifact, andSearchResultSummary SearchDomain::selected_result_candidates(...)when result admissibility is narrower than the raw built-in query adapterSearchClaimClassand theorem-pack problem-class exports for admission checks- the validated
SearchExecutionPolicymatrix rather than ad hoc scheduler knobs
Legacy route/incumbent aliases remain available through the narrow
telltale_search::compat surface for migration, but they are no longer the
recommended generic API boundary. SearchCachingProfile::IncrementalReuse
also remains outside the stable import posture until it is implemented.
telltale-ui
telltale-ui is the portable Dioxus UI core for shared simulator tooling.
It owns the global shell layout, reusable panels/cards/toolbars/badges, graph rendering primitives, viewer route state, readiness/publication diagnostics, and the task-owner helper used by long-lived UI work.
This crate is intentionally browser-free.
It consumes telltale-viewer through the typed query/command boundary and renders observed projections over authoritative artifacts.
telltale-web
telltale-web is the thin browser/WASM shell around telltale-ui.
It owns Dioxus.toml, index.html, Tailwind packaging, browser bootstrap, and any future browser-specific integration.
The goal is to keep platform interop here and keep the reusable UI core portable.
telltale-runtime
telltale-runtime is the choreography-layer runtime crate.
It provides the async ChoreoHandler path, effect interpretation, topology and transport abstractions, testing utilities, formatting tooling, and other runtime support for generated choreography code.
This crate also re-exports parts of the shared frontend for convenience, but it does not replace telltale-language as the canonical frontend layer.
Its util/ module now contains platform-facing helpers such as spawn, clock, RNG, and synchronization support.
Its heap/ module now includes a dedicated encoding.rs layer for canonical heap bytes and tagged hashing preimages, plus published conformance vectors in rust/runtime/tests/data/heap_vectors_v1.json.
telltale-bridge
telltale-bridge is the Rust↔Lean bridge crate.
It provides export, import, schema handling, protocol-machine trace normalization, semantic-object conversion, and runner-driven cross-validation surfaces.
Optional features enable CLI and runner workflows.
This crate depends directly on telltale-types and telltale-machine.
It also uses telltale-theory and telltale-runtime behind optional features and in development workflows.
telltale-transport
telltale-transport provides a first-party reference TCP transport implementation.
Today it is centered on TCP transport support over the choreography-layer transport abstractions from telltale-runtime.
It depends on telltale-runtime and telltale-types.
This crate should be read as a transport implementation layer, not as the main protocol frontend or runtime semantic core. Its job is to demonstrate how concrete transports realize runtime transport contracts for choreography-layer systems.
telltale-lints
telltale-lints contains custom linting support built on syn, quote, and proc_macro2.
It is a small support crate and does not currently define central protocol semantics.
Its role is repository-specific static analysis rather than protocol execution.
telltale
The root telltale crate is the facade crate at the repository root.
It re-exports session-core types, the macro surface, selected shared-frontend APIs, and optional theory features.
It also exposes the root session-type library and DSL support used by many examples.
Notably, the root crate now re-exports compile_choreography(...), compile_choreography_ast(...), CompiledChoreography, ordered annotation helpers, and parse_choreography_str(...).
This makes the facade crate a reasonable entry point for smaller integrations, while telltale-language remains the authoritative frontend crate.
Selected Workspace Binaries
Some important tooling surfaces are shipped as binaries inside workspace crates.
effect-scaffoldintelltale-runtimeexports generated effect-family files and simulator scaffolds.choreo-fmtintelltale-runtimeformats choreography sources and can expose lowering explanations.lean-bridge,lean-bridge-exporter, andgoldenintelltale-bridgesupport validation and bridge workflows.runandreplayintelltale-simulatorsupport scenario execution and replay.
These binaries are generated surfaces & tooling. They are not the primary conceptual entry points for the architecture itself.
Lean Correspondence
The strongest direct constructor correspondence is between telltale-types and the foundational Lean session-type definitions.
That shared core covers GlobalType, LocalTypeR, Label, and PayloadSort.
This is the main type-level bridge between Rust and Lean.
The current correspondence table for the shared core is:
| Lean Type | Rust Type | File |
|---|---|---|
GlobalType.end | GlobalType::End | rust/types/src/global.rs |
GlobalType.comm p q bs | GlobalType::Comm { sender, receiver, branches } | rust/types/src/global.rs |
GlobalType.mu t G | GlobalType::Mu { var, body } | rust/types/src/global.rs |
GlobalType.var t | GlobalType::Var(String) | rust/types/src/global.rs |
LocalTypeR.end | LocalTypeR::End | rust/types/src/local.rs |
LocalTypeR.send q bs | LocalTypeR::Send { partner, branches } | rust/types/src/local.rs |
LocalTypeR.recv p bs | LocalTypeR::Recv { partner, branches } | rust/types/src/local.rs |
LocalTypeR.mu t T | LocalTypeR::Mu { var, body } | rust/types/src/local.rs |
LocalTypeR.var t | LocalTypeR::Var(String) | rust/types/src/local.rs |
PayloadSort.unit | PayloadSort::Unit | rust/types/src/global.rs |
Label | Label { name, sort } | rust/types/src/label.rs |
Lean still includes a delegate constructor in GlobalType that is not exposed in the Rust core GlobalType.
That remains a known parity gap in the shared foundational type layer.
The larger proof and runtime correspondence story is documented in Lean Verification and Rust-Lean Bridge and Parity.
Related Docs
- Architecture
- Theory
- Choreographic DSL
- Content Addressing
- Lean Verification
- Rust-Lean Bridge and Parity
Getting Started
Installation
Choose dependencies based on the integration path you need. For most application code using the compile-time DSL, start with the facade crate.
[dependencies]
telltale = "11.3.0"
This gives you the root session-type surface and the tell! macro.
Add other crates only when you need a lower-level integration path.
Which Crate Should You Use
Use this table to pick the right entry point.
| If you need | Use |
|---|---|
facade APIs plus the tell! macro | telltale |
| runtime parsing, validated ASTs, projection, and integration artifacts | telltale-language |
| async choreography handlers and topology/runtime support | telltale-runtime |
| pure theory algorithms | telltale-theory |
| protocol-machine execution in a host runtime | telltale-machine |
| deterministic simulation and host-handler testing | telltale-simulator |
| Lean JSON import, export, and validation tools | telltale-bridge |
| reference choreography transport | telltale-transport |
The important split is between the shared frontend and the execution layers.
telltale-language is the shared frontend.
telltale-runtime and telltale-machine are different execution paths.
Understanding the Crates
telltale-types contains the core protocol data model.
It defines GlobalType, LocalTypeR, Label, PayloadSort, and content-addressing support.
Lean still includes a delegate constructor that is not exposed in the Rust core GlobalType.
telltale-language is the shared choreography frontend.
It owns the AST, parser, validation, projection to frontend LocalType, ordered annotation extraction, and integration helpers such as compile_choreography(...).
Use this crate when you need to parse or inspect DSL at runtime without going through macros.
telltale-runtime is the choreography-layer runtime.
It provides async ChoreoHandler integration, topology support, testing helpers, and tooling such as choreo-fmt and effect-scaffold.
telltale-machine provides the protocol machine and guest-runtime execution surfaces.
telltale-simulator wraps the protocol machine with deterministic middleware for testing.
telltale-bridge supports Rust↔Lean conversion and validation.
telltale-transport provides a first-party reference TCP transport for choreography-layer systems.
Feature Flags
The workspace uses feature flags to control optional algorithms and target support. The root crate keeps its default surface small.
telltale
| Feature | Default | Description |
|---|---|---|
test-utils | no | testing utilities |
wasm | no | WebAssembly support |
theory | no | enable telltale-theory re-exports |
theory-async-subtyping | no | enable asynchronous subtyping helpers |
theory-bounded | no | enable bounded recursion helpers |
full | no | enable all optional root features |
telltale-theory
| Feature | Default | Description |
|---|---|---|
projection | yes | global-to-local projection |
duality | yes | dual type computation |
merge | yes | local merge operations |
well-formedness | yes | theory validation predicates |
bounded | yes | bounded recursion strategies |
async-subtyping | yes | asynchronous subtyping |
sync-subtyping | yes | synchronous subtyping |
semantics | yes | async step semantics |
coherence | yes | coherence predicates |
full | no | enable all optional theory features |
telltale-runtime
| Feature | Default | Description |
|---|---|---|
test-utils | no | runtime testing utilities |
wasm | no | WebAssembly support |
native-cli | no | build choreo-fmt and effect-scaffold |
native-examples | no | build runtime examples that require native tooling |
telltale-bridge
| Feature | Default | Description |
|---|---|---|
runner | yes | enable Lean runner support |
cli | no | build the main bridge CLI |
exporter | no | build the choreography exporter binary |
golden | no | build the golden-file CLI |
Minimal Root Dependency
telltale = { version = "11.3.0", default-features = false }
This keeps the dependency surface small while preserving the core facade crate.
Root Crate With Theory Support
telltale = { version = "11.3.0", features = ["theory"] }
This adds the theory re-exports to the facade crate.
Runtime CLI Tooling
telltale-runtime = { version = "11.3.0", features = ["native-cli"] }
Enable this when you want binaries such as choreo-fmt or effect-scaffold.
It is not required for normal library usage.
First Protocol With tell!
For compile-time protocol definitions, tell! is the main public entry point.
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
protocol PingPong =
roles Alice, Bob
Alice -> Bob : Ping
Bob -> Alice : Pong
}
assert!(PingPong::proof_status::SESSION_PROJECTABLE);
}
This defines a simple two-role protocol. The macro generates the protocol module and, when the protocol is projectable, the session surfaces for that protocol.
Runtime Parsing Without Macros
Use the shared frontend when protocol text is loaded at runtime or when another tool needs validated artifacts.
#![allow(unused)]
fn main() {
use telltale_language::compile_choreography;
let compiled = compile_choreography(
"protocol PingPong =\n roles Alice, Bob\n Alice -> Bob : Ping\n Bob -> Alice : Pong\n",
)?;
let local_types = compiled.try_local_type_r_map()?;
let global_type = compiled.try_global_type()?;
}
This path parses, validates, and projects the choreography once. It is the right entry point for downstream integrations that need AST access, ordered annotations, or theory-facing artifacts.
Core Concepts
Choreographies
A choreography describes the protocol from a global viewpoint. Projection turns that global description into per-role local behavior. This is the main abstraction behind the Telltale DSL.
Roles
Roles are the participants in the protocol. Each role sends and receives messages according to its projected local type. Generated session surfaces enforce those obligations at the type level when projection succeeds.
Messages
Messages are the data exchanged between roles.
At the choreography layer, message payloads typically use serde serialization.
At the protocol-machine layer, host integration works through typed effect requests and outcomes instead.
Handler Boundaries
Telltale exposes two main handler surfaces.
Use ChoreoHandler for generated choreography code in telltale-runtime.
Use EffectHandler for protocol-machine host integration in telltale-machine.
If you implement protocol-machine EffectHandler, validate it through SimulationHarness in telltale-simulator.
That is the supported deterministic test path for host integrations.
Related Docs
- Architecture
- Code Organization
- Choreographic DSL
- Choreography Effect Handlers
- Effect Handlers and Session Types
- Simulation Overview
Choreographic DSL
Overview
The parser translates the layout-sensitive Telltale DSL into the internal AST
(Choreography and Protocol). The DSL is direct style. Statements are
newline separated. Indentation defines blocks. Records keep braces, but
structural blocks do not.
The parser lives in rust/language/src/compiler/parser/. It uses Pest plus a
layout preprocessor. The canonical source-file extension for Telltale source
files is .tell.
The preferred surface style mixes MPST operators with a small functional-language layout:
- keep
->,->*,|,rec, andcontinuefor protocol structure - reserve
=>for branch/result bodies - use indentation to make control flow visually obvious
- keep braces for record/data layout instead of general structural blocks
- format standalone records and effect-semantics records as multiline brace blocks with
key : valuefields - use sender records like
Role { priority : high } - use
Message of module.Typewith dotted paths instead of Rust-style::
Scope
The DSL describes protocol structure, role-local communication obligations, and protocol-critical authority checks. It does not model arbitrary host state or general application ownership. Session and fragment ownership boundaries are still derived at runtime from composition and link metadata when available. Protocol-visible authority queries and evidence flow are part of the language surface.
The current DSL includes an authority-oriented surface for protocol-critical host queries and typed local branching:
- top-level
typeandtype aliasdeclarations - nominal
effectinterface declarations - protocol-level
usesdeclarations authoritative let/observe letbindings for effect queriesletbindings for linear transfer receiptscase ... ofoverResult/Maybe-style constructorstimeout ... on timeout ... on cancel ...- evidence guards of the form
when check Effect.op(...) yields witness
These forms participate in explicit language tiers:
- full spec: the DSL can express the construct and the semantic object model justifies it
- session projectable: the construct admits MPST/session projection
- protocol-machine executable: the construct is executable through the protocol-machine path even when it is not session projectable
- proof only: the construct parses, but lacks the metadata required for executable lowering
The generated Rust surface exposes this status through
Protocol::proof_status.
DSL Syntax
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
protocol PingPong =
roles Alice, Bob
Alice -> Bob : Ping
Bob -> Alice : Pong
}
}
tell! is the canonical public DSL entrypoint. String-literal macro input and
legacy brace-based structural syntax are rejected. The canonical surface is
token-form, indentation-based, and proof-directed.
tell! is outside the current formal-verification claim. The formal claim
currently covers the Lean models/theorems plus strict correspondence and
operational compiler gates, not Rust macro expansion itself.
When a protocol projects cleanly to sessions, the generated module exposes
Protocol::sessions. Every generated protocol also exposes
Protocol::proof_status, which reports the strongest supported tier and any
projection blocker.
Namespaces
Namespaces are expressed via a module declaration (rather than attributes) in source files:
module threshold_ceremony exposing (ThresholdProtocol)
protocol ThresholdProtocol =
roles Coordinator, Signers[*]
Coordinator -> Signers[*] : Request
Multiple modules can coexist in separate files. Inside tell! you typically
omit the module header and define one protocol-oriented spec directly in Rust.
The parser recognizes import declarations for completeness. Import resolution is not currently applied during compilation.
Supported Constructs
1) Send Statement
Buyer { priority : high }
-> Seller : Request of shop.Order
Seller
-> Buyer : Quote of shop.Price
The send statement transfers a message from one role to another. Sender metadata is written as a record attached to the sender term. Typed message attachment is written as Message of module.Type.
2) Broadcast Statement
Leader ->* : Announcement
Broadcast sends a message to all other roles.
3) Choice Statement (explicit decider)
choice Client at
| Buy when (balance > price) =>
Client
-> Server : Purchase of shop.Order
| Cancel =>
Client
-> Server : Cancel
The deciding role (Client) selects a branch. Guards are optional. Guard expressions are parsed through a typed predicate IR and must be boolean-like. Valid examples include ready, balance > price, and is_open().
4) Loop Statement
loop decide by Client
Client -> Server : Request
Server -> Client : Response
This loop continues while the deciding role chooses to continue. The decide by form is
desugared to a choice+recursion pattern at parse time. The first message in the loop body
serves as the “continue” signal - when the deciding role sends it, the loop continues.
A synthetic Done message is added as the termination signal.
The example above desugars to:
rec RoleDecidesLoop
choice Client at
| Request =>
Client -> Server : Request
Server -> Client : Response
continue RoleDecidesLoop
| Done =>
Client -> Server : Done
Requirement: The first statement in a loop decide by Role body must be a send from
the deciding role. This ensures the decision to continue is fused with the first message,
saving a round trip compared to sending a separate “continue” signal.
This desugaring converts the RoleDecides loop into standard multiparty session type
constructs (choice + recursion), keeping the surface inside the Lean-modeled theory
fragment and compatible with standard MPST projection algorithms.
loop repeat 5
A -> B : Ping
B -> A : Pong
This loop repeats a fixed number of times. The compiler records the iteration count in the AST.
loop while "has_more_data"
A -> B : Data
This loop parses the string content through the same typed predicate IR used by guards. The parser rejects non-boolean predicates such as "count + 1" before building the AST.
loop forever
A -> B : Tick
This loop has no exit condition. Use it for persistent background protocols.
5) Parallel Statement
par
| A -> B : Msg1
B
-> A : Ack
| C -> D : Msg2
Parallel composition is expressed by par with leading | branches. A solitary branch is a parse error.
6) Recursion and Calls
rec Loop
A -> B : Tick
continue Loop
This defines a recursive label Loop and uses continue Loop to jump back, modeling unbounded recursion.
call Handshake
This calls another protocol that is in scope. The call target must be defined in the same file or where block.
The continue keyword is for recursive back-references within a rec block. The call keyword is for invoking sub-protocols defined in where blocks.
7) Protocol Composition (where block)
Define and reuse local protocol fragments inside a where block.
protocol Main =
roles A, B, C
call Handshake
call DataTransfer
A -> C : Done
where
protocol Handshake =
A -> B : Hello
B -> A : Hi
protocol DataTransfer =
A -> B : Data
B -> A : Ack
Local protocols can call each other and can be used within choices, loops, and branches.
8) Message Types and Payloads
The preferred typed message form is Message of module.Type. Dotted paths are preferred in DSL surface syntax.
A
-> B : Request of api.Request
B
-> A : Response of api.Response
A
-> B : Msg { data : String, count : Int }
B
-> A : Result(i : Int, ok : Bool)
This sequence demonstrates payload and result handling patterns with mixed message shapes.
9) Dynamic Role Count Support
Dynamic role counts are supported via wildcard and symbolic parameters.
protocol ThresholdProtocol =
roles Coordinator, Signers[*]
Coordinator -> Signers[*] : Request
Signers[0..threshold] -> Coordinator : Response
This example uses a wildcard role family. It also uses a symbolic bound in the role index.
protocol ConsensusProtocol =
roles Leader, Followers[N]
Leader -> Followers[*] : Proposal
Followers[i] -> Leader : Vote
The consensus protocol above demonstrates a symbolic role count (N) with an index variable (i) iterating over the follower set.
10) Profiles, Progress, And Proof Status
Profiles and explicit progress attachment are part of the proof-backed surface.
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
profile Replay fairness strong
agreement_profile MembershipSoftSafe
visibility pending
rule threshold_participants
usable_at provisional
finalized_at finalized
evidence commit_fact
operation MembershipCheck at Coordinator progress MembershipProgress requires Replay within bounded agreement MembershipSoftSafe compose first_success =
publish MembershipQueued
protocol Membership under Replay =
roles Coordinator, Member
Coordinator -> Member : Invite
}
assert_eq!(Membership::proof_status::STRONGEST_TIER, "session_projectable");
assert!(Membership::proof_status::PROTOCOL_MACHINE_EXECUTABLE);
}
Profiles, theorem-pack requirements, and language-tier diagnostics are surfaced as generated metadata because they are part of the verified model, not an afterthought layered on top.
For effect interfaces, the canonical Rust import is use Protocol::effects.
Traits, request/outcome enums, effect-domain data, and per-operation semantic
metadata all live under that one boundary. Interface metadata is exposed under
snake-case effect modules such as Protocol::effects::runtime.
This example mixes a named count with index variables. It enables parameterized protocols.
protocol PartialBroadcast =
roles Broadcaster, Receivers[*]
Broadcaster -> Receivers[0..count] : Message
Receivers[0..threshold] -> Broadcaster : Ack
This example shows bounded ranges for role indices. It models partial broadcasts and threshold acknowledgments.
10) Timing Patterns
Timing patterns provide constructs for building time-aware protocols. They are part of the protocol-machine surface today, but they are not all session-projectable yet.
Timed Choice
A protocol-visible timeout branches the protocol on deadline expiry:
protocol TimedRequest =
roles Client, Server
timeout 5s Client at
Server -> Client : Response
on timeout =>
Client -> Server : Cancel
Use timeout duration Role at when deadline expiry is a protocol-visible
outcome. The runtime enforces the deadline. Projection and theory conversion
currently reject this form until the authority-sensitive lowering rules are
complete, so it is protocol-machine executable but not part of the MPST/common
theory subset yet.
Durations support: ms (milliseconds), s (seconds), m (minutes), h (hours).
Liveness Pattern
The old heartbeat surface is removed from the proof-backed DSL. Model
connection liveness with explicit protocol-visible timeouts plus ordinary
messages:
protocol Liveness =
roles Alice, Bob
Alice -> Bob : Ping
timeout 1s Bob at
Bob -> Alice : Pong
on timeout =>
Bob -> Alice : Disconnect
This keeps liveness handling inside the current verified surface. If you only
need an operational transport hint rather than a protocol branch, use
runtime_timeout sender metadata instead.
Runtime Timeout Metadata
Record metadata can carry transport-level timeout hints:
protocol TimedOps =
roles Client, Server
Client { runtime_timeout : 5s }
-> Server : Request
Server -> Client : Response
Unlike timeout ... on timeout ..., this metadata does not change the session
type. It is a hint to the transport layer and is not verified in Lean. Use it
for operational timeouts when you do not want the protocol to branch on
timeout.
11) Proof Bundles and Semantic Statements
proof_bundle declarations define capability sets. protocol ... requires ...
selects the bundles required by a protocol.
proof_bundle DelegationBase version "1.0.0" issuer "did:example:issuer" constraint "fresh_nonce" requires [delegation, guard_tokens]
proof_bundle KnowledgeBase requires [knowledge_flow]
protocol TransferFlow requires DelegationBase, KnowledgeBase =
roles Coordinator, Worker
let receipt = transfer Session from Coordinator to Worker
publish receipt as DelegationRecorded
materialize delegationProof from DelegationRecorded
handoff acceptInvite to Worker with receipt
Coordinator -> Worker : Commit
The parser stores bundle declarations and required bundle names in typed choreography metadata. Bundle records include optional version, issuer, and repeated constraint fields.
Validation fails on duplicate bundles and missing required bundles. If
requires is omitted and bundle coverage is unambiguous, the parser can infer
required bundles from the semantic statements that remain in the DSL surface.
Legacy protocol-machine-core statements such as acquire, fork, join,
delegate, and tag are removed from the proof-backed DSL and are rejected
fail closed. Keep those instructions in the runtime model rather than in
choreography source.
The accepted DSL still includes protocol-visible semantic statements such as
publish, materialize, handoff, dependent work, begin, await, and
resolve.
Runtime upgrade or reconfiguration statements are not part of the choreography DSL today. They remain explicit runtime/model surfaces until they have a real shared Rust and Lean semantics.
12) Authority and Failure Surface
The authority/failure additions keep the language expression-oriented and Elm/PureScript-like while making protocol-critical host decisions explicit.
Nominal Effect Interfaces and uses
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
observe watchPresence : Session -> PresenceView
{
class : observational
progress : immediate
region : session
agreement_use : forbidden
reentrancy : allow
}
command cancel : Session -> Result CancelError CancelReceipt
{
class : best_effort
progress : immediate
region : session
agreement_use : required
reentrancy : allow
}
effect Audit
command record : AuditEvent -> Unit
{
class : best_effort
progress : immediate
region : session
agreement_use : forbidden
reentrancy : allow
}
protocol CommitFlow uses Runtime, Audit =
roles Coordinator, Worker
authoritative let readiness = check Runtime.ready(session)
Coordinator -> Worker : Commit
Effects are nominal declarations. uses makes protocol dependencies explicit.
Only declared effects named in uses may be referenced by check.
Result and Maybe with case/of
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(TimedOut) =>
Coordinator -> Worker : Cancel
Result and Maybe are first-class sum-style forms for protocol-visible
authority checks. Exhaustiveness is required for Ok/Err and
Just/Nothing.
Custom Unions and Type Aliases
type CommitError =
| TimedOut
| Cancelled
| InvalidWitness
type alias ReadyWitness =
{ epoch : Int, issuedBy : Role }
Use custom unions for typed failure surfaces and aliases for structured evidence payloads.
Evidence Binding with let
observe let presence = observe Runtime.watchPresence(session)
authoritative let readiness = check Runtime.ready(session)
let receipt = transfer Session from Coordinator to Worker
handoff acceptInvite to Worker with receipt
Authority queries use explicit observe let or authoritative let bindings.
Linear transfer receipts still use ordinary let, but the resulting receipt is
a first-class transition artifact rather than an untyped helper value.
Typed Timeout and Cancellation Blocks
timeout 5s Coordinator at
Worker -> Coordinator : Ready(readyWitness)
on timeout =>
Coordinator -> Worker : Cancel
on cancel =>
Coordinator -> Worker : Cancelled
Use timeout ... on timeout ... on cancel ... when timeout and cancellation are
protocol-visible outcomes rather than ambient runtime metadata.
Evidence Guards
choice Coordinator at
| Commit when check Runtime.ready(session) yields witness =>
Coordinator -> Worker : Commit(witness)
| Abort =>
Coordinator -> Worker : Abort
Evidence guards are the concise path for “take this branch only if a typed authoritative query succeeds and binds evidence”.
Linear Single-Use Bindings
let receipt = transfer Session from Coordinator to Worker
handoff acceptInvite to Worker with receipt
Bindings produced by transfer-like authority operations are linear. The compiler rejects duplicate use and implicit discard.
The first-class capability/finalization mapping for this DSL surface is:
- authoritative and observational reads: evidence/finalization inputs
- transfer receipts and handoffs: transition artifacts
- publications, materialization proofs, and canonical handles: evidence-side finalization objects
Scoped Bindings with let ... in
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(reason) =>
Coordinator -> Worker : Abort(reason)
let ... in introduces an indented statement block. Use it to keep authority
logic readable without pushing protocol-critical branching back into untyped
host code.
No Implicit Default Branches
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(TimedOut) =>
Coordinator -> Worker : Cancel
Protocol-critical authority flows do not allow implicit wildcard/default
masking for Result and Maybe. Missing cases are validation errors.
Typed External Query Surface
authoritative let readiness = check Runtime.ready(session)
check Effect.op(...) is the canonical language-level form for typed host
queries, and it must be bound with authoritative let. Observation-only
queries use observe let value = observe Effect.op(...). These user-facing
forms lower to the same typed runtime/effect boundary used by the host bridge.
Choosing Between Timeout Metadata and Timeout Branching
Use runtime_timeout : 5s sender metadata for operational transport hints that
do not change the protocol. Use timeout ... on timeout ... on cancel ... when
the timeout result must be explicit in the protocol and replay/audit surface.
This lowering preserves statement order and continuation structure. Projection skips extension-local behavior for now and continues projecting the remaining protocol.
13) Child-Effect Aggregation
compose ... is a secondary child-effect aggregation clause on an operation.
It does not define distributed agreement by itself. Agreement lives in named
agreement profiles. Child-effect aggregation only says how sibling child
effects roll up underneath that parent agreement.
agreement_profile PendingPublication
visibility pending
rule threshold_participants
usable_at provisional
finalized_at finalized
evidence publication
operation syncMembership(channel : ChannelId) at Worker progress MembershipProgress requires Replay within bounded agreement PendingPublication prestate ChannelMembership compose threshold_success(2) =
publish SyncQueued(channel)
The compose threshold_success(2) clause attaches a child-effect rollup policy to the operation without overriding the parent agreement profile.
Reusable Domain Agreement Profiles
Domain systems should define their own agreement vocabulary as named profiles
over Telltale’s generic core instead of expecting core built-ins such as
quorum or fallback to carry all the meaning.
agreement_profile ContactCeremonySoftSafe
visibility pending
rule aura_soft_safe
usable_at soft_safe
finalized_at finalized
evidence commit_fact
operation addContact(contactId : String) at Coordinator progress ContactProgress requires Replay within bounded agreement ContactCeremonySoftSafe prestate ContactContext compose threshold_success(3) =
publish ContactQueued(contactId)
This keeps Telltale generic:
visibility,usable_at,finalized_at, andevidenceare the core semantic vocabularyContactCeremonySoftSafeandaura_soft_safeare domain-defined namescompose ...only describes child-effect rollup below the agreement contract, not the agreement contract itself
The generated Rust surface preserves those domain names through
Protocol::agreements and Protocol::proof_status, so downstream host code
can keep a natural domain vocabulary without forking the core language model.
14) Role Sets and Topologies
Role sets and topologies are declared at the top level and stored as typed metadata.
role_set Signers = Alice, Bob, Carol
role_set Quorum = subset(Signers, 0..2)
cluster LocalCluster = Signers, Quorum
ring RingNet = Alice, Bob, Carol
mesh FullMesh = Alice, Bob, Carol
These declarations do not change core protocol semantics. They provide structured topology context for tooling and simulation setup.
15) Lowering Diagnostics
Use choreo-fmt --explain-lowering to inspect canonical lowering output.
choreo-fmt --explain-lowering protocol.tell
The report includes proof-bundle metadata, inferred requirements, lowered protocol shape, and lint suggestions.
16) String-based Protocol Definition
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::parser::parse_choreography_str;
let protocol = r#"
protocol PingPong =
roles Alice, Bob
Alice -> Bob : Ping
Bob -> Alice : Pong
"#;
let choreography = parse_choreography_str(protocol)?;
}
This parses a string into a Choreography AST. It is the entry point for runtime parsing.
Namespaced protocols are expressed with a module header.
#![allow(unused)]
fn main() {
let protocol = r#"
module secure_messaging exposing (EncryptedProtocol)
protocol EncryptedProtocol =
roles Sender, Receiver
Sender -> Receiver : SecureMessage
"#;
let choreography = parse_choreography_str(protocol)?;
}
The parser builds the AST for projection, validation, and code generation.
Implementation Details
Parser Stack
- Layout preprocessor converts indentation into explicit block delimiters.
- Pest grammar parses the canonical indentation-based syntax after layout normalization.
- Parser module constructs the AST and runs validation.
Parse Pipeline
- Preprocess layout (indentation ->
{}/()). - Parse with Pest grammar.
- Build statements and normalize
parbranches intoParallel. - Resolve
callreferences and lower protocol-machine-core statements toProtocol::Extension. - Build
Choreographyand attach typed proof-bundle metadata. - Run semantic checks with
choreography.validate()when required by your integration.
API
Primary Functions
parse_choreography_str parses a DSL string into a Choreography AST.
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::parser::parse_choreography_str;
let choreo = parse_choreography_str(r#"
protocol Example =
roles A, B
A -> B : Hello
"#)?;
}
This example parses a DSL string into a Choreography. It uses parse_choreography_str directly.
parse_choreography_file parses a DSL file from disk.
It expects the canonical .tell source extension.
#![allow(unused)]
fn main() {
use std::path::Path;
use telltale_runtime::compiler::parser::parse_choreography_file;
let choreo = parse_choreography_file(Path::new("protocol.tell"))?;
}
This example parses a file path into a Choreography. The file must contain a valid DSL definition.
parse_dsl is an alias for parse_choreography_str.
Error Handling
#![allow(unused)]
fn main() {
match parse_choreography_str(input) {
Ok(choreo) => { /* use choreography */ }
Err(ParseError::UndefinedRole { role, .. }) => {
eprintln!("Role '{}' used but not declared", role);
}
Err(ParseError::DuplicateRole { role, .. }) => {
eprintln!("Role '{}' declared multiple times", role);
}
Err(ParseError::Syntax { span, message }) => {
eprintln!("Syntax error at {}:{}: {}", span.line, span.column, message);
}
Err(e) => {
eprintln!("Parse error: {}", e);
}
}
}
This pattern matches common parse errors. It formats diagnostics with the reported context.
Grammar Details
Tokens and Keywords
- Identifiers:
[a-zA-Z][a-zA-Z0-9_]* - Integers:
[0-9]+ - Strings:
"..."(used inloop while) - Keywords:
protocol,roles,case,of,choice,at,loop,decide,by,repeat,while,forever,par,rec,continue,call,where,module,import,exposing,proof_bundle,requires,acquire,release,fork,join,abort,transfer,delegate,tag,check,using,into,as,to,from,with,bundle,version,issuer,constraint,role_set,subset,cluster,ring,mesh,let,in,type,alias,effect,uses,timeout,on,cancel,when,yields,heartbeat,every,on_missing,body,observe,authoritative,command,Ok,Err,Just,Nothing,Result,Maybe,Unit,publish,handoff,dependent,work,required,for,fragment,operation,within,guest,runtime,entry - Operators:
->,->*,<->,:,=>,=,..,{},(),[],,,|
Comments
Single-line comments use --. Multi-line comments use {- ... -}.
-- This is a single-line comment
{- This is a
multi-line comment -}
These comments are ignored by the parser. They use the same syntax as Haskell and PureScript.
Whitespace and Layout
Indentation defines blocks. Use {} to force an empty block or to opt out of layout. Parenthesized blocks must be non-empty.
Validation
The parser validates role declarations and parallel branch structure. choreography.validate() also validates proof-bundle declarations, required bundle references, and protocol-machine-core capability coverage.
Error Messages
Example undefined role error:
Undefined role 'Charlie'
--> input:5:14
|
5 | Alice -> Charlie : Hello
^^^^^^^
This error indicates a role that was not declared in roles. The location points to the undefined identifier.
Example duplicate role error:
Duplicate role declaration 'Alice'
--> input:3:33
|
3 | roles Alice, Bob, Charlie, Alice
^^^^^
This error indicates that a role name appears more than once. The location points to the duplicate entry.
Examples
Simple Two-Party Protocol
protocol PingPong =
roles Alice, Bob
Alice
-> Bob : Ping
Bob
-> Alice : Pong
This example shows a simple two role protocol. It uses a single send and reply.
Protocol with Choice
protocol Negotiation =
roles Buyer, Seller
Buyer
-> Seller : Offer
choice Seller at
| Accept =>
Seller
-> Buyer : Accept
| Reject =>
Seller
-> Buyer : Reject
This example shows an explicit choice decided by Seller. Each branch starts with a send from the deciding role.
Complex E-Commerce Protocol
protocol ECommerce =
roles Buyer, Seller, Shipper
Buyer
-> Seller : Inquiry of commerce.Inquiry
Seller
-> Buyer : Quote of commerce.Quote
choice Buyer at
| Order =>
Buyer
-> Seller : Order of commerce.Order
Seller
-> Shipper : ShipRequest of logistics.ShipRequest
Shipper
-> Buyer : Tracking of logistics.Tracking
loop decide by Buyer
Buyer
-> Shipper : StatusCheck
Shipper
-> Buyer : StatusUpdate
Shipper
-> Buyer : Delivered
Buyer
-> Seller : Confirmation
| Cancel =>
Buyer
-> Seller : Cancel
This example combines choice and looping. It models a longer interaction with a buyer controlled loop.
Parallel Example
protocol ParallelDemo =
roles A, B, C, D
par
| A -> B : Msg1
| C -> D : Msg2
This example uses par with leading | branches. Each branch defines a parallel sub protocol.
par remains protocol-machine executable and session-projectable, but it is not
part of the theory-convertible subset tracked by language_tier_status().
Integration
With Projection
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::{parser, projection};
let choreo = parser::parse_choreography_str(input)?;
for role in &choreo.roles {
let local_type = projection::project(&choreo, role)?;
println!("Local type for {}: {:?}", role.name(), local_type);
}
}
This projects the global protocol to a local type for each role. The result can be used for type driven code generation.
With Code Generation
#![allow(unused)]
fn main() {
use telltale_runtime::compiler::{parser, projection, codegen};
let choreo = parser::parse_choreography_str(input)?;
let mut local_types = Vec::new();
for role in &choreo.roles {
let local_type = projection::project(&choreo, role)?;
local_types.push((role.clone(), local_type));
}
let code = codegen::generate_choreography_code(
&choreo.name.to_string(),
&choreo.roles,
&local_types,
);
}
This generates Rust code for the choreography. The generated code includes session types and role APIs.
Testing
Run parser tests with:
cargo test --package telltale-runtime parser
This runs the parser test suite for the choreography crate. It exercises grammar and layout handling.
Choreographic Projection Patterns
The projection algorithm transforms global choreographic protocols into local session types. Each participating role receives their own local session type. The algorithm supports patterns beyond basic send and receive operations.
Supported Patterns
The snippets below are schematic. The AST uses NonEmptyVec and Ident values, which are omitted for readability.
1. Basic Send/Receive
The global protocol specifies a send operation.
#![allow(unused)]
fn main() {
Protocol::Send {
from: alice,
to: bob,
message: Data,
continuation: End,
}
}
This defines Alice sending to Bob.
Alice’s projection shows a send operation.
#![allow(unused)]
fn main() {
LocalType::Send {
to: bob,
message: Data,
continuation: End,
}
}
Alice sends Data to Bob.
Bob’s projection shows a receive operation.
#![allow(unused)]
fn main() {
LocalType::Receive {
from: alice,
message: Data,
continuation: End,
}
}
Bob receives Data from Alice.
Charlie’s projection shows no participation.
#![allow(unused)]
fn main() {
LocalType::End
}
Charlie is uninvolved in this exchange.
2. Communicated Choice
The global protocol specifies a choice.
#![allow(unused)]
fn main() {
Protocol::Choice {
role: alice,
branches: vec![
Branch {
label: "yes",
protocol: Send { from: alice, to: bob, ... },
},
Branch {
label: "no",
protocol: Send { from: alice, to: bob, ... },
},
],
}
}
Alice chooses between yes and no branches.
Alice’s projection shows selection.
#![allow(unused)]
fn main() {
LocalType::Select {
to: bob,
branches: vec![
("yes", ...),
("no", ...),
],
}
}
Alice selects one branch and communicates it to Bob.
Bob’s projection shows branching.
#![allow(unused)]
fn main() {
LocalType::Branch {
from: alice,
branches: vec![
("yes", ...),
("no", ...),
],
}
}
Bob waits for Alice’s choice.
3. Local Choice
Local choice supports branches that do not start with send operations. This allows for local decisions without communication.
The global protocol defines a local choice.
#![allow(unused)]
fn main() {
Protocol::Choice {
role: alice,
branches: vec![
Branch {
label: "option1",
protocol: End,
},
Branch {
label: "option2",
protocol: End,
},
],
}
}
No send operation appears in the branches.
Alice’s projection shows local choice.
#![allow(unused)]
fn main() {
LocalType::LocalChoice {
branches: vec![
("option1", End),
("option2", End),
],
}
}
Alice makes a local decision.
The key difference is between LocalChoice and Select. The Select variant indicates communicated choice where the selection is sent to another role. The LocalChoice variant indicates local decision with no communication.
The standard DSL requires choices to start with a send from the deciding role, so LocalChoice does not appear in normal projections. It is available when building the AST programmatically or via extensions.
4. Loop with Conditions
Loop conditions are preserved in the projected local types.
The global protocol includes a loop.
#![allow(unused)]
fn main() {
Protocol::Loop {
condition: Some(Condition::Count(5)),
body: Send { from: alice, to: bob, ... },
}
}
The loop executes 5 times.
Alice’s projection preserves the condition.
#![allow(unused)]
fn main() {
LocalType::Loop {
condition: Some(Condition::Count(5)),
body: Send { to: bob, ... },
}
}
The count condition is maintained.
Supported conditions include Condition::Count(n) for fixed iteration count. Custom boolean expressions use Condition::Custom(expr). Infinite loops use None and must be terminated externally.
The AST also includes Fuel, YieldAfter, and YieldWhen for tooling or extensions.
Condition::RoleDecides(role) loops specify that a role controls iteration. During projection, the condition is preserved in the local type and merge comparisons check that both sides use the same deciding role.
5. Parallel Composition
Parallel merging includes conflict detection.
Compatible parallel composition has no conflicts.
#![allow(unused)]
fn main() {
Protocol::Parallel {
protocols: vec![
Send { from: alice, to: bob, ... },
Send { from: alice, to: charlie, ... },
],
}
}
Different recipients avoid conflicts.
Alice’s projection merges the operations.
#![allow(unused)]
fn main() {
LocalType::Send {
to: bob,
continuation: Send {
to: charlie,
continuation: End,
},
}
}
Operations are merged into one local type in a deterministic traversal order.
This merged order is a projection artifact rather than a scheduling guarantee.
Branch labels in merged Branch nodes are canonicalized in label order to keep output deterministic.
Conflicting parallel composition causes errors.
#![allow(unused)]
fn main() {
Protocol::Parallel {
protocols: vec![
Send { from: alice, to: bob, ... },
Send { from: alice, to: bob, ... },
],
}
}
Same recipient creates a conflict.
Alice’s projection fails.
#![allow(unused)]
fn main() {
Err(ProjectionError::InconsistentParallel)
}
Cannot send to same recipient in parallel.
Conflict detection rules prevent invalid projections. Multiple sends to the same role create a conflict. Multiple receives from the same role create a conflict.
Multiple selects to the same role create a conflict. Multiple branches from the same role create a conflict. Operations on different roles are allowed.
Projection Rules Summary
Chooser’s View
When all branches start with send operations, the projection is Select. This indicates communicated choice. When branches do not start with send operations, the projection is LocalChoice. This indicates local decision.
Receiver’s View
When the role receives the choice, the projection is Branch. When the role is not involved, continuations are merged.
Merge Semantics
When a role is not involved in a choice, the projections of all branches must be merged. The merge algorithm follows precise semantics based on whether the role sends or receives.
Send merge requires identical label sets. A non-participant cannot choose which message to send based on a choice they did not observe. If a role sends different messages in different branches, projection fails. Receive merge unions label sets because a non-participant can receive any message regardless of which branch was taken.
This distinction is critical for protocol safety. The Rust implementation matches the Lean formalization in lean/Choreography/Projection/. Projection parity checks are exercised from rust/bridge/tests/projection_runner_tests.rs.
Parallel Composition
When a role appears in zero branches, the projection is End. When a role appears in one branch, that projection is used. When a role appears in two or more branches, projections are merged if compatible. An error occurs if conflicts exist.
Dynamic Role Projection
The default project() entry point does not resolve runtime role families on its own.
Unresolved symbolic, wildcard, or runtime role parameters return projection errors.
The projection error surface includes:
ProjectionError::DynamicRoleProjectionProjectionError::UnboundSymbolicProjectionError::WildcardProjectionProjectionError::RangeProjection
Projection still enforces core structural rules before interpretation. If a choreography uses parameterized roles, bind those roles to a concrete participant set before building executable effect programs.
Authority-Oriented Projection Status
The authority/failure additions in the DSL are intentionally split between language validation and MPST projection.
| Surface | Current projection status | Reason |
|---|---|---|
let binding = expr | projected through to continuation | local helper form with no direct session action |
linear let receipt = transfer ... | validated before projection | single-use guarantees are enforced before local type generation |
nominal effect declarations | not projected directly | describe external obligations, not local communication actions |
protocol ... uses ... | validation-only metadata | constrains which effects may be referenced |
check Effect.op(...) in local expressions | validation-only until lowered | remains a typed external query, not a local type action by itself |
case ... of | projected directly | constructor labels become case-local branch labels; divergent senders become LocalChoice, receivers become Branch |
timeout ... on timeout ... on cancel ... | projected directly | timeout owners/participants receive LocalType::Timeout { body, on_timeout, on_cancel } |
when check ... yields witness guards | projected directly | evidence guards constrain validation, then projection proceeds structurally through the guarded branch bodies |
publish ..., publish ... as ..., materialize ..., handoff ..., dependent work ... | projected through to continuation | semantic wrapper forms do not add a session action of their own |
Current fail-closed rule:
- if a choreography relies on authority constructs that do not yet have an
explicit projection rule such as
begin,await,resolve, orinvalidate, projection stops with a validation error instead of guessing a local-session encoding
Current linearity rule:
- linear transfer/receipt bindings must be consumed exactly once before projection continues
- duplicate use and implicit discard are rejected before local type generation
This keeps the parser/AST surface ahead of the proven MPST subset without creating silent projection behavior.
Implementation Notes
LocalType Variants
The enhanced projection algorithm uses these LocalType variants.
#![allow(unused)]
fn main() {
pub enum LocalType {
Send { to, message, continuation },
Receive { from, message, continuation },
Select { to, branches },
Branch { from, branches },
LocalChoice { branches },
Loop { condition, body },
Rec { label, body },
Var(label),
Timeout { duration, body, on_timeout, on_cancel },
End,
}
}
Each variant represents a different local type pattern. For authority-level timeout choreography, the projection now preserves the happy-path body plus the explicit timeout and cancellation branches in the local AST. Rust session-type code generation still erases those timeout arms to the body because timeout handling remains a runtime concern.
Code Generation
The generate_type_expr function in rust/language/src/compiler/codegen/mod.rs handles all variants. This includes LocalChoice, Loop, and Timeout. Code generation transforms local types into Rust session types. Timeout annotations are currently ignored in the type expression.
Dynamic roles use specialized code generation via generate_choreography_code_with_dynamic_roles. This function includes runtime role binding. Validation occurs at choreography initialization. Generated code supports dynamic role counts.
DSL Extensions
This document explains how to extend the choreography DSL with runtime effects and syntax extensions.
Overview
The extension system has two parts. Runtime extensions add type-safe effects that can be inserted into Program sequences. Syntax extensions add new grammar rules and statement parsers to the DSL.
Runtime effect extensions are projected to roles during compilation and dispatched by interpret_extensible at runtime.
Syntax extensions are registered through the parser and grammar composition APIs.
The repository contains two extension registries with the same name:
effects::registry::ExtensionRegistry<E, R>for runtime extension effect handlers.extensions::ExtensionRegistryfor DSL grammar and parser extensions.
Simulator Integration for Extensions
Extension projects often need protocol-machine-level regression tests in addition to parser tests.
Use telltale-simulator harness APIs to run projected local types under scenario middleware.
This keeps extension validation aligned with protocol-machine effect contracts.
#![allow(unused)]
fn main() {
let harness = SimulationHarness::new(&DirectAdapter::new(&handler));
let result = harness.run(&spec)?;
assert_contracts(&result, &ContractCheckConfig::default())?;
}
This pattern makes extension runtime checks reusable across projects. See Simulation Overview for harness config files and preset constructors.
Runtime Effect Extensions
ExtensionEffect Trait
Extensions implement ExtensionEffect and specify which roles participate.
#![allow(unused)]
fn main() {
pub trait ExtensionEffect<R: RoleId>: Send + Sync + Debug {
fn type_id(&self) -> TypeId;
fn type_name(&self) -> &'static str;
fn participating_roles(&self) -> Vec<R> { vec![] }
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn clone_box(&self) -> Box<dyn ExtensionEffect<R>>;
}
}
The default participating_roles implementation returns an empty vector, which makes the extension global. A non-empty vector limits the extension to specific roles.
Defining an Extension
#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
pub struct ValidateCapability<R> {
pub role: R,
pub capability: String,
}
impl<R: RoleId> ExtensionEffect<R> for ValidateCapability<R> {
fn type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
fn type_name(&self) -> &'static str {
"ValidateCapability"
}
fn participating_roles(&self) -> Vec<R> {
vec![self.role]
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn clone_box(&self) -> Box<dyn ExtensionEffect<R>> {
Box::new(self.clone())
}
}
}
This extension only appears in the projection for role. Use an empty vector to make it global.
Extension Registry
Handlers register extension logic in an ExtensionRegistry.
#![allow(unused)]
fn main() {
let mut registry = ExtensionRegistry::new();
registry.register::<ValidateCapability<Role>, _>(|_ep, ext| {
Box::pin(async move {
let validate = ext
.as_any()
.downcast_ref::<ValidateCapability<Role>>()
.ok_or_else(|| ExtensionError::TypeMismatch {
expected: "ValidateCapability",
actual: ext.type_name(),
})?;
tracing::info!(cap = %validate.capability, "checked capability");
Ok(())
})
})?;
}
The register method returns an error on duplicate handlers. Use ExtensionRegistry::merge to compose registries across modules.
Error Handling
Extension errors surface as ExtensionError.
#![allow(unused)]
fn main() {
pub enum ExtensionError {
UnknownExtension { type_name: &'static str, type_id: TypeId },
HandlerNotRegistered { type_name: &'static str },
ExecutionFailed { type_name: &'static str, error: String },
TypeMismatch { expected: &'static str, actual: &'static str },
DuplicateHandler { type_name: &'static str },
MergeConflict { type_name: &'static str },
}
}
Handlers should register all required extensions before interpretation. This keeps failures at startup instead of in the middle of protocol execution.
Interpreter Integration
Use interpret_extensible with handlers that implement ExtensibleHandler.
#![allow(unused)]
fn main() {
let mut handler = MyHandler::new();
let mut endpoint = ();
let result = interpret_extensible(&mut handler, &mut endpoint, program).await?;
}
The non-extensible interpret function does not dispatch extensions. That
runtime is now an internal choreography-layer surface, not the primary public
entrypoint for application code.
Using Extensions in Programs
Extensions should be introduced from the DSL/parser side so they remain part of
the validated semantic surface. Do not teach or depend on manual
Program::ext construction as the public modeling path.
Syntax Extensions
GrammarExtension Trait
Grammar extensions provide Pest rules and a list of statement rules they handle.
#![allow(unused)]
fn main() {
pub trait GrammarExtension: Send + Sync + Debug {
fn grammar_rules(&self) -> &'static str;
fn statement_rules(&self) -> Vec<&'static str>;
fn priority(&self) -> u32 { 100 }
fn extension_id(&self) -> &'static str;
}
}
The priority value resolves conflicts when multiple extensions define the same rule. Higher priority wins and conflicts are recorded by the registry.
StatementParser and ProtocolExtension
Statement parsers translate matched rules into protocol extensions.
#![allow(unused)]
fn main() {
pub trait StatementParser: Send + Sync + Debug {
fn can_parse(&self, rule_name: &str) -> bool;
fn supported_rules(&self) -> Vec<String>;
fn parse_statement(
&self,
rule_name: &str,
content: &str,
context: &ParseContext,
) -> Result<Box<dyn ProtocolExtension>, ParseError>;
}
}
ProtocolExtension instances participate in validation, projection, and code generation.
#![allow(unused)]
fn main() {
pub trait ProtocolExtension: Send + Sync + Debug {
fn type_name(&self) -> &'static str;
fn mentions_role(&self, role: &Role) -> bool;
fn validate(&self, roles: &[Role]) -> Result<(), ExtensionValidationError>;
fn project(&self, role: &Role, context: &ProjectionContext) -> Result<LocalType, ProjectionError>;
fn generate_code(&self, context: &CodegenContext) -> proc_macro2::TokenStream;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn type_id(&self) -> TypeId;
}
}
Use these traits to attach new DSL constructs to projection and codegen.
ExtensionRegistry for Syntax
The ExtensionRegistry stores grammar extensions and statement parsers.
#![allow(unused)]
fn main() {
let mut registry = ExtensionRegistry::new();
registry.register_grammar(MyGrammarExtension)?;
registry.register_parser(MyStatementParser, "my_parser".to_string());
}
The registry tracks rule conflicts and supports dependency checks. Use get_detailed_conflicts for human-readable conflict reports.
GrammarComposer
GrammarComposer combines the base grammar with registered extension rules and caches the result.
#![allow(unused)]
fn main() {
let mut composer = GrammarComposer::new();
composer.register_extension(MyGrammarExtension)?;
let grammar = composer.compose()?;
}
The cache avoids recomposing the grammar when the extension set has not changed.
ExtensionParser
ExtensionParser wires the grammar composer into the parsing pipeline.
#![allow(unused)]
fn main() {
let mut parser = ExtensionParser::new();
parser.register_extension(MyGrammarExtension, MyStatementParser)?;
let choreography = parser.parse_with_extensions(source)?;
}
The parse_with_extensions method now composes the grammar metadata and routes
registered statement rules through the shared extension registry. Supported
extension statements are parsed deterministically into Protocol::Extension
nodes instead of falling back to the standard parser unchanged.
Extension Discovery
The extensions::discovery module provides metadata and dependency management.
#![allow(unused)]
fn main() {
let mut discovery = ExtensionDiscovery::new();
discovery.add_search_path("./extensions");
let registry = discovery.create_registry(&["timeout".to_string()])?;
}
Discovery assembles an ExtensionRegistry in dependency order and performs basic validation.
Complete Workflow Example
This section shows an end-to-end workflow for building and running a runtime extension.
Step 1: Add Dependencies
Add the choreography crate and an async runtime.
[dependencies]
telltale-runtime = "11.3.0"
tokio = { version = "1", features = ["full"] }
Use a path dependency only for local workspace development. External projects should pin to a release version.
Step 2: Define Roles and Messages
Define roles, labels, and messages for your protocol.
#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};
use telltale_runtime::{LabelId, RoleId, RoleName};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Role { Client, Server }
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Label { Ok }
impl LabelId for Label {
fn as_str(&self) -> &'static str {
match self { Label::Ok => "ok" }
}
fn from_str(label: &str) -> Option<Self> {
match label { "ok" => Some(Label::Ok), _ => None }
}
}
impl RoleId for Role {
type Label = Label;
fn role_name(&self) -> RoleName {
match self {
Role::Client => RoleName::from_static("Client"),
Role::Server => RoleName::from_static("Server"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum Message { Ping, Pong }
}
Roles implement RoleId. Labels implement LabelId. Messages are serializable.
Step 3: Build an Extensible Handler
Register extension handlers and implement ExtensibleHandler.
#![allow(unused)]
fn main() {
struct DomainHandler {
registry: ExtensionRegistry<(), Role>,
}
impl DomainHandler {
fn new() -> Self {
let mut registry = ExtensionRegistry::new();
registry
.register::<ValidateCapability<Role>, _>(|_ep, ext| {
Box::pin(async move {
let validate = ext
.as_any()
.downcast_ref::<ValidateCapability<Role>>()
.ok_or(ExtensionError::TypeMismatch {
expected: "ValidateCapability",
actual: ext.type_name(),
})?;
tracing::info!(cap = %validate.capability, "validated");
Ok(())
})
})
.expect("register extension");
Self { registry }
}
}
}
ExtensionRegistry stores handlers for each extension type. The interpret_extensible entry point uses this registry during execution.
Step 4: Build and Run a Program
Extensions remain available in the low-level choreography runtime via
interpret_extensible, but that path is internal. The public modeling path is
to parse/validate extension-bearing DSL and lower it through the canonical
semantic surface rather than hand-assembling extension programs.
Testing
Unit tests can validate registry setup and projection behavior.
#![allow(unused)]
fn main() {
#[test]
fn test_registry() {
let handler = DomainHandler::new();
assert!(handler
.extension_registry()
.is_registered::<ValidateCapability<Role>>());
}
}
Use integration tests to check end-to-end protocol behavior with interpret_extensible.
Built-In Extensions
The extensions::timeout module contains a sample grammar extension, parser, and protocol extension. It is intended as a reference implementation and currently uses simplified parsing logic.
Related Docs
Examples
This document points to the example programs and common usage patterns. Each example demonstrates a specific protocol shape or runtime feature. The repo intentionally has two example families:
- projection examples, where
tell!demonstrates global protocol structure and projected session surfaces - generated-interface examples, where
effectdeclarations are the source of truth for the Rust host boundary
Example Index
All examples compile against the workspace. Use the projection examples when you want to inspect session projection and endpoint code. Use the generated-interface examples when you want to follow the target architecture: protocol-visible orchestration in Telltale, host realization in Rust.
Protocol examples in examples/protocols/:
adder.rs: recursive two-party adderalternating_bit.rs: reliable delivery with ACK branchingdouble_buffering.rs: producer-consumer coordination viatell!fft.rs: eight-role FFT butterfly networkring.rs: three-node ring topology viatell!ring_choice.rs: ring with per-hop branching and infinite typesring_max.rs: ring maximum with broadcast announcementthree_adder.rs: three-party sum viatell!
Effect-boundary examples in examples/effects/:
client_server_log.rs: logging decisions at the host boundarycommitment_lifecycle.rs: commitment, profile-driven progress, and agreement/finality metadataelevator.rs: authority handoff and publication metadatagenerated_effect_interfaces.rs: canonical generated Rust effect traits and semantic metadatamap_reduce.rs: structured fan-out/fan-in work with host compute boundariesoauth.rs: authentication and authorization decisions at the effect boundaryreactive_signal.rs: reactive signal subscription/current-value/publish interfacethree_buyers.rs: pricing and affordability decisions at the host boundarytravel_agency.rs: evidence binding, typed failure with case/of, and timeoutwasm/: browser integration via generated effects
Theory examples in examples/theory/:
async_subtyping.rs: async-subtyping checks and subtype relation examplesbounded_recursion.rs: bounded recursion strategies with configurable depth
Runtime examples in rust/runtime/examples/:
authority_surface.rs: inspecteffectdeclarations and proof-backed parser metadatachoreography_tour.rs: comprehensive DSL feature tour (modules, choice, dynamic roles, ranges, recursion, projection)macro_choreography.rs: programmatic choreography construction with the algebraic effect APIextension_example.rs: DSL extension system with custom grammar rules and parsersextension_capability.rs: role-specific capability validation extensionextension_logging.rs: basic logging extension for choreography executionextension_workflow.rs: complete workflow with multiple extension types (native-examples)topology_integration.rs: local and explicit placement topology configurationparameterized_roles_full.rs: parameterized roles test suite (concrete arrays, indexed access, symbolic params)roles_parameterized.rs: parameterized roles parsing without proc macrodebug_param_roles.rs: debugging utility for parameterized role parsingerror_demo.rs: enhanced error reporting with span informationtcp_ping_pong.rs: TCP transport for real network communication (native-examples)telltale_client_server.rsandthree_party_negotiation.rs: runtime-oriented examples (native-examples)
Common Patterns
The following patterns cover the core protocol constructs for projection
examples. tell! parses the DSL at compile time, validates the proof-backed
surface, and emits sessions only when the protocol is session-projectable.
Request Response
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
protocol RequestResponse =
roles Client, Server
Client -> Server : Request of api.Request
Server -> Client : Response of api.Response
}
}
Each message line declares a sender, receiver, label, and optional payload type.
Projection produces one local session type per role when
RequestResponse::proof_status::SESSION_PROJECTABLE is true.
Choice
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
protocol ChoicePattern =
roles Client, Server
choice Server at
| Accept =>
Server -> Client : Confirmation
| Reject =>
Server -> Client : Rejection
}
}
Only the deciding role selects the branch. Other roles react to that choice. The choice at clause names the selecting participant. Each branch label must be distinct within the choice block.
Loops
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
protocol LoopPattern =
roles Client, Server
loop repeat 5
Client -> Server : Request
Server -> Client : Response
}
}
Use bounded loops for batch workflows or retries. The repeat count sets an upper iteration limit. The body runs sequentially within each iteration.
Parallel Branches
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
protocol ParallelPattern =
roles Coordinator, Worker1, Worker2
par
| Coordinator -> Worker1 : Task
| Coordinator -> Worker2 : Task
}
}
Parallel branches must be independent in order to remain well formed. Each branch operates on a disjoint set of roles. The runtime executes branches concurrently when possible.
Generated Effect Interfaces
Use the generated-interface path when the example needs typed effect boundaries or richer host/runtime integration. In these examples, the DSL remains the source of truth and Rust supplies handler implementations directly against the generated traits.
#![allow(unused)]
fn main() {
use telltale::tell;
tell! {
type CommitError =
| NotReady
| TimedOut
type alias ReadyWitness =
{
epoch : Int
issuedBy : Role
}
type alias CommitReceipt =
{
commitId : String
publishedBy : Role
}
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
command publish : ReadyWitness -> Result CommitError CommitReceipt
{
class : best_effort
progress : immediate
region : session
agreement_use : required
reentrancy : allow
}
protocol CommitFlow uses Runtime =
roles Coordinator, Worker
Coordinator -> Worker : Commit
}
use CommitFlow::effects;
struct Host;
impl effects::Runtime for Host {
fn ready(&mut self, _input: effects::Session) -> Result<effects::ReadyWitness, effects::CommitError> {
Ok(effects::ReadyWitness { epoch: 7, issued_by: effects::Role::new("Coordinator") })
}
fn publish(&mut self, witness: effects::ReadyWitness) -> Result<effects::CommitReceipt, effects::CommitError> {
Ok(effects::CommitReceipt {
commit_id: format!("commit-{}", witness.epoch),
published_by: witness.issued_by,
})
}
}
let mut host = Host;
assert_eq!(CommitFlow::proof_status::STRONGEST_TIER, "session_projectable");
let ready = effects::runtime::operation("ready").unwrap();
assert!(ready.architecturally_legal());
let presence = effects::Runtime::handle(
&mut host,
effects::RuntimeRequest::Ready(effects::Session::new("commit-7")),
);
assert!(matches!(presence, effects::RuntimeOutcome::Ready(Ok(_))));
}
This produces canonical host-facing request/outcome enums, handler traits, and
generated semantic metadata directly from the declared effect surface. Use
Protocol::effects as the single import boundary. Per-interface metadata lives
under Protocol::effects::<effect_name_in_snake_case>. Protocol::proof_status
reports which generated proof-backed surfaces are available. File export
remains tooling-only and is no longer the primary developer path.
Semantic Highlights
When you want a concrete semantic feature, start here:
- commitment:
examples/effects/commitment_lifecycle.rs - authority, publication, and materialization:
examples/effects/generated_effect_interfaces.rs - structured concurrency:
examples/effects/map_reduce.rs - profile-driven progress:
examples/effects/commitment_lifecycle.rs - reusable domain agreement profiles:
examples/effects/commitment_lifecycle.rs - reactive host boundaries:
examples/effects/reactive_signal.rs
commitment_lifecycle.rs is the repo’s explicit example of a domain-defined
agreement scheme similar in shape to Aura’s provisional / soft-safe /
finalized model. It demonstrates how downstream systems can keep their own
agreement vocabulary while lowering onto Telltale’s generic agreement,
evidence, visibility, and progress core.
Testing Patterns
For canonical tests, exercise the generated tell! surface directly:
- compile-time checks:
assert on
Protocol::proof_status - effect-boundary checks:
implement
Protocol::effects::*traits against deterministic host fixtures - semantic checks:
assert over
Protocol::commitments,Protocol::agreements, andProtocol::authority - protocol-machine checks: use the protocol-machine and bridge test suites for replay/parity/runtime behavior
Low-level choreography-interpreter tests still exist inside
rust/runtime/tests/ as implementation coverage for that crate, but they
are not the public testing model that examples should teach.
Running Examples
Run a single example with Cargo. Each example is a standalone binary that demonstrates the protocol end to end.
cargo run --example adder
This compiles and runs the adder example, which demonstrates a simple two-party request response protocol.
The wasm example crate uses its own harness. It can build the browser package, run a deterministic Node smoke check, and serve the demo locally.
cd examples/wasm
./harness.sh run
See the comments in each example file for setup requirements. For WASM-specific guidance, see WASM Guide.
WASM Guide
This guide explains how to build and run the choreography runtime on wasm32. It covers feature flags, build tooling, handler compatibility, and testing.
Overview
The telltale-runtime crate supports WASM targets. Core effects, handlers, and timeouts compile under wasm32 using wasm-bindgen-futures and wasm-timer. The same Program and interpret API used on native targets works without modification. Platform differences are handled internally by the runtime module.
What Works
In WASM builds you can use Program, interpret, and effect handlers. InMemoryHandler and TelltaleHandler are WASM compatible for local or custom transports. Middleware such as Trace, Metrics, and Retry is WASM compatible. FaultInjection is available with the test-utils feature.
Protocol definitions written with tell! produce the same projected types on
both platforms when the protocol is session-projectable. The proof-status and
effect surfaces are platform-agnostic. Only the lowest-level spawn and timer
calls differ between native and WASM.
Limitations
WASM is single threaded. Concurrency is async only. Direct std::net sockets are not available. Network transports must use browser APIs or host provided bindings.
Tokio-specific features such as tokio::spawn are not available. Use the runtime::spawn abstraction instead. File system access is also unavailable unless the host provides a binding.
Enable WASM
Enable the wasm feature on the choreography crate.
[dependencies]
telltale-runtime = { version = "11.3.0", features = ["wasm"] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
The wasm feature enables getrandom support and pulls in the WASM runtime dependencies. Use a path dependency only for local workspace development.
Build with wasm-pack or cargo targets. wasm-pack produces a ready-to-use JavaScript package. Direct cargo build --target wasm32-unknown-unknown works for library crates that do not need JS bindings.
For reproducible local setup, install the same tool version used in CI.
cargo install wasm-pack --version 0.14.0 --locked
This installs the same wasm-pack version used in CI for reproducible builds.
wasm-pack build --target web
This produces a pkg directory with JavaScript bindings.
Minimal Example
This example runs a simple request response program using InMemoryHandler. It defines the role, label, and message types needed by the effect system. It then builds two programs and runs them concurrently with shared channels.
The first section defines the Role and Label enums. LabelId requires string round-tripping via as_str and from_str. RoleId associates a label type and provides canonical names through role_name.
#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};
use telltale_runtime::{interpret, InMemoryHandler, LabelId, Program, RoleId, RoleName};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Role {
Client,
Server,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Label {
Ok,
}
impl LabelId for Label {
fn as_str(&self) -> &'static str {
match self {
Label::Ok => "ok",
}
}
fn from_str(label: &str) -> Option<Self> {
match label {
"ok" => Some(Label::Ok),
_ => None,
}
}
}
impl RoleId for Role {
type Label = Label;
fn role_name(&self) -> RoleName {
match self {
Role::Client => RoleName::from_static("Client"),
Role::Server => RoleName::from_static("Server"),
}
}
}
}
The Message enum carries the payload variants exchanged between roles. Both variants use String here, but any Serialize + Deserialize type works.
#![allow(unused)]
fn main() {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
enum Message {
Ping(String),
Pong(String),
}
}
The canonical WASM example now lives in examples/wasm/ and uses
tell! plus generated effect traits as the public surface. Browser-facing
code should keep protocol structure in the DSL and host integration in the
generated Protocol::effects boundary, rather than manually assembling
low-level effect programs. Use ./harness.sh build to emit the browser
package, ./harness.sh smoke for a deterministic Node-based end-to-end
check, and ./harness.sh run to build, verify, and serve the demo locally.
For multi role tests, share channels by using InMemoryHandler::with_channels and a shared channel map. The WASM test suite in rust/runtime/tests/wasm_integration.rs shows larger examples. Each handler must reference the same Arc<Mutex<BTreeMap>> instances for messages to route correctly. The unit endpoint () is sufficient when no external state is needed.
TelltaleHandler in WASM
TelltaleHandler works in WASM with TelltaleSession pairs or custom sessions. This handler manages typed endpoints and routes messages through registered sessions. It is the recommended handler for integration-level WASM tests.
#![allow(unused)]
fn main() {
use telltale_runtime::{TelltaleEndpoint, TelltaleHandler, TelltaleSession};
let (alice_session, bob_session) = TelltaleSession::pair();
let mut alice_ep = TelltaleEndpoint::new(Role::Client);
let mut bob_ep = TelltaleEndpoint::new(Role::Server);
alice_ep.register_session(Role::Server, alice_session);
bob_ep.register_session(Role::Client, bob_session);
let mut handler = TelltaleHandler::<Role, Message>::new();
}
Use your protocol message type for Message. The role must implement both telltale::Role and RoleId. For browser transports, build a TelltaleSession from a sink and stream. Register it with TelltaleEndpoint::register_session.
Runtime Utilities
The runtime provides WASM aware task spawning helpers. These abstractions let the same protocol code run on both native and browser targets without conditional compilation at the call site.
#![allow(unused)]
fn main() {
use telltale_runtime::runtime::spawn;
spawn(async move {
// task body
});
}
spawn uses Tokio on native targets and wasm_bindgen_futures::spawn_local on WASM.
Testing
Use wasm-bindgen-test for WASM tests. Import the crate and annotate test functions with #[wasm_bindgen_test]. Tests marked with #[wasm_bindgen_test(unsupported = test)] run as standard #[test] on native targets and as WASM tests on wasm32.
#![allow(unused)]
fn main() {
use wasm_bindgen_test::*;
}
Repository-managed tests run under Node, not a browser driver. This avoids the need for a headless browser in CI.
just wasm-test-all
For ad hoc crate-level runs, use wasm-pack test --node. This compiles the crate for wasm32 and executes tests in a Node environment.
See Choreography Effect Handlers for handler details. See Using Telltale Handlers for the channel based API.
Choreography Effect Handlers
Overview
This page documents the choreography-layer handler surface in telltale-runtime.
This surface is ChoreoHandler.
For protocol-machine host integration, see Effect Handlers and Session Types.
Effect interfaces are the typed operational vocabulary between the protocol machine and the world. They realize commitment conservation: every effect is a tracked commitment that must resolve to a terminal class. See Conservation Framework for the full design philosophy.
ChoreoHandler decouples protocol logic from transport implementation at the choreography layer. Handlers interpret choreographic effects into concrete communication operations. Protocol code remains unchanged across handlers. The effect runtime normalizes Parallel effects to deterministic declaration order.
Handler Domains
The effect system distinguishes two handler domains. Internal handlers are Telltale-owned and realize scheduling, dispatch, batching, replay, and simulation. External handlers are guest-runtime-facing and realize storage, network, domain checks, and other host integrations. Both domains interpret the same typed effect interfaces. Handlers may realize operational behavior, but they do not directly mutate semantic state.
Boundary Selection
Choose the handler surface by integration level.
| Use case | Handler surface |
|---|---|
| Generated choreography code over typed messages | ChoreoHandler |
| Protocol-machine bytecode execution in a host runtime | EffectHandler |
EffectHandler is the integration boundary for third-party runtimes.
ChoreoHandler is the integration boundary for async choreography transports.
Protocol-Machine Handler Test Path
Projects that implement EffectHandler should validate behavior in telltale-simulator.
Use SimulationHarness with DirectAdapter for host handlers.
Use FieldAdapter when the scenario should select the handler from built-in field parameters.
#![allow(unused)]
fn main() {
let adapter = DirectAdapter::new(&handler);
let harness = SimulationHarness::new(&adapter);
let result = harness.run(&spec)?;
}
This test path runs the same protocol-machine callback surface that production execution uses. Add assert_contracts checks to make replay and trace guarantees explicit in CI.
ChoreoHandler Trait
All handlers implement this trait.
#![allow(unused)]
fn main() {
pub trait ChoreoHandler: Send {
type Role: RoleId;
type Endpoint: Endpoint;
async fn send<M: Serialize + Send + Sync>(
&mut self, ep: &mut Self::Endpoint, to: Self::Role, msg: &M
) -> ChoreoResult<()>;
async fn recv<M: DeserializeOwned + Send>(
&mut self, ep: &mut Self::Endpoint, from: Self::Role
) -> ChoreoResult<M>;
async fn choose(
&mut self, ep: &mut Self::Endpoint, who: Self::Role,
label: <Self::Role as RoleId>::Label
) -> ChoreoResult<()>;
async fn offer(
&mut self, ep: &mut Self::Endpoint, from: Self::Role
) -> ChoreoResult<<Self::Role as RoleId>::Label>;
async fn with_timeout<F, T>(
&mut self, ep: &mut Self::Endpoint, at: Self::Role, dur: Duration, body: F
) -> ChoreoResult<T>
where
F: Future<Output = ChoreoResult<T>> + Send;
}
}
The trait defines send, receive, choice, and timeout operations.
It also provides default broadcast and parallel_send helpers.
The send method transmits a message to another role. The recv method waits for a message from another role. The choose method makes a branch selection. The offer method receives a branch selection.
Nested branch, loop, timeout, and parallel interpretation returns non-duplicated receive traces.
The Endpoint associated type holds connection state. Different handlers use different endpoint types.
Send bounds and portability
The trait requires messages to be Send. The send method requires Serialize + Send + Sync. The recv method requires DeserializeOwned + Send. Handler futures require F: Future + Send in with_timeout.
This matches the requirements of target runtimes. Native targets use tokio. WASM targets use single-thread executors. The bounds keep middleware stacks interchangeable between single-threaded and multi-threaded deployments.
Code written for browsers compiles unchanged for native binaries. Work can move across threads transparently.
Built-in Handlers
InMemoryHandler
The InMemoryHandler is located in rust/runtime/src/effects/handlers/in_memory.rs. It provides fast local message passing for testing. The implementation uses futures channels internally.
Basic usage creates a handler for a single role.
#![allow(unused)]
fn main() {
use telltale_runtime::InMemoryHandler;
let mut handler = InMemoryHandler::new(Role::Alice);
}
This creates an Alice handler.
For coordinated testing between roles, use shared channels.
#![allow(unused)]
fn main() {
let channels = Arc::new(Mutex::new(BTreeMap::new()));
let choice_channels = Arc::new(Mutex::new(BTreeMap::new()));
let alice = InMemoryHandler::with_channels(Role::Alice, channels.clone(), choice_channels.clone());
let bob = InMemoryHandler::with_channels(Role::Bob, channels.clone(), choice_channels.clone());
}
The shared channels enable communication between handlers in the same process.
TelltaleHandler
The TelltaleHandler is located in rust/runtime/src/effects/handlers/telltale.rs. It provides production-ready session-typed channels. The implementation uses the core Telltale library for type-safe communication.
This handler enforces session types at runtime. It provides strong guarantees about protocol compliance.
See Using Telltale Handlers for complete documentation.
RecordingHandler
The RecordingHandler is located in rust/runtime/src/effects/handlers/recording.rs. It records all operations for verification and testing. The handler stores a log of send, recv, choose, and offer calls.
Basic usage creates a recording handler.
#![allow(unused)]
fn main() {
use telltale_runtime::RecordingHandler;
let mut handler = RecordingHandler::new(Role::Alice);
// ... execute protocol ...
let events = handler.events();
assert!(matches!(events[0], RecordedEvent::Send { from: Role::Alice, to: Role::Bob, .. }));
}
The recorded events can be inspected in tests to verify protocol behavior.
NoOpHandler
The NoOpHandler is located in rust/runtime/src/effects/handler.rs. It implements send and choose as no-ops and returns errors for recv and offer. This is useful for testing protocol structure without actual communication.
#![allow(unused)]
fn main() {
let handler = NoOpHandler::<MyRole>::new();
}
Send and choose succeed immediately without side effects. Recv and offer return transport errors.
Middleware
Middleware wraps handlers to add cross-cutting functionality. Multiple middleware can compose around a single handler.
Trace
The Trace middleware is located in rust/runtime/src/effects/middleware/trace.rs. It logs all operations for debugging. The middleware outputs send, recv, choose, and offer calls with role and message details.
Usage example shows wrapping a handler.
#![allow(unused)]
fn main() {
use telltale_runtime::Trace;
let base_handler = InMemoryHandler::new(role);
let mut handler = Trace::with_prefix(base_handler, "Alice");
}
Each operation logs before delegating to the inner handler.
Metrics
The Metrics middleware is located in rust/runtime/src/effects/middleware/metrics.rs. It counts operations for monitoring. The middleware tracks send_count, recv_count, and error_count.
Usage example shows metrics collection.
#![allow(unused)]
fn main() {
use telltale_runtime::Metrics;
let base_handler = InMemoryHandler::new(role);
let mut handler = Metrics::new(base_handler);
// ... execute protocol ...
println!("Sends: {}", handler.send_count());
}
Metrics accumulate over the handler lifetime.
Retry
The Retry middleware is located in rust/runtime/src/effects/middleware/retry.rs. It retries failed operations with exponential backoff. Only send operations are retried since recv changes protocol state.
Usage example configures retry behavior.
#![allow(unused)]
fn main() {
use telltale_runtime::Retry;
use std::time::Duration;
let base_handler = InMemoryHandler::new(role);
let mut handler = Retry::with_config(base_handler, 3, Duration::from_millis(100));
}
The handler retries up to 3 times. Delays are 100ms, 200ms, 400ms using exponential backoff.
FaultInjection
The FaultInjection middleware is located in rust/runtime/src/effects/middleware/fault_injection.rs. It requires the test-utils feature. The middleware injects random failures and delays for testing fault tolerance.
Usage example configures fault injection.
#![allow(unused)]
fn main() {
use telltale_runtime::effects::middleware::FaultInjection;
use std::time::Duration;
let base_handler = InMemoryHandler::new(role);
let mut handler = FaultInjection::new(base_handler, 0.1)
.with_delays(Duration::from_millis(10), Duration::from_millis(100));
}
Send operations randomly fail 10% of the time. Delays range from 10ms to 100ms. Fault injection only affects send operations. The recv, choose, and offer methods delegate directly to the inner handler.
Composing Middleware
Middleware can stack in layers.
#![allow(unused)]
fn main() {
let handler = InMemoryHandler::new(role);
let handler = Retry::with_config(handler, 3, Duration::from_millis(100));
let handler = Trace::with_prefix(handler, "Alice");
let handler = Metrics::new(handler);
}
Operations flow through the stack. The order is Metrics to Trace to Retry to InMemory.
Creating Custom Handlers
Implement ChoreoHandler for your transport.
#![allow(unused)]
fn main() {
pub struct MyHandler {
role: MyRole,
connections: HashMap<MyRole, Connection>,
}
#[async_trait]
impl ChoreoHandler for MyHandler {
type Role = MyRole;
type Endpoint = MyEndpoint;
async fn send<M: Serialize + Send + Sync>(
&mut self, ep: &mut Self::Endpoint, to: Self::Role, msg: &M
) -> ChoreoResult<()> {
let conn = self.connections.get_mut(&to)?;
let bytes = bincode::serialize(msg)?;
conn.send(bytes).await?;
Ok(())
}
async fn recv<M: DeserializeOwned + Send>(
&mut self, ep: &mut Self::Endpoint, from: Self::Role
) -> ChoreoResult<M> {
let conn = self.connections.get_mut(&from)?;
let bytes = conn.recv().await?;
let msg = bincode::deserialize(&bytes)?;
Ok(msg)
}
// Implement choose and offer...
}
}
The handler manages connection state and serialization. The endpoint type holds per-role state if needed.
Handler Selection Guide
Use InMemoryHandler for local testing and simple protocols.
Use TelltaleHandler for production deployments with session type guarantees.
Use RecordingHandler for test verification and debugging.
Use NoOpHandler for protocol structure testing.
Use middleware to add logging, metrics, retries, or fault injection. Middleware works with any handler.
WASM Considerations
InMemoryHandler and TelltaleHandler both work in WASM environments. They use futures channels for communication.
For WASM network communication, implement a custom handler. Use web-sys WebSocket or fetch APIs. See WASM Guide for details.
Parameterized Roles
Parameterized roles remain a choreography-level feature of the DSL and AST. Projection and interpretation still require concrete role values at execution time. Wildcard, symbolic, and unresolved range forms are not interpreted directly by ChoreoHandler. Resolve participant sets during choreography construction or initialization, then build effect programs over concrete RoleId values.
Topology constraints validate concrete deployments against role family bounds.
#![allow(unused)]
fn main() {
use telltale_runtime::topology::{Topology, parse_topology};
let config = r#"
topology Prod for Protocol {
role_constraints {
Witness: min = 3, max = 10
}
}
"#;
let topology = parse_topology(config)?.topology;
topology.validate_family("Witness", 5)?;
}
Run this check before execution to ensure the concrete participant set matches deployment expectations.
Effect Interpretation
Handlers interpret the protocol-machine and generated effect boundary. For
normal application code, start from tell!, implement the generated
Protocol::effects::* traits, and let the protocol-machine/runtime drive the
requests. The lower-level choreography interpreter remains an internal
implementation surface.
Using Telltale Handlers
Overview
TelltaleHandler implements choreographic effects over session-typed channels. This guide documents setup patterns, API surface, and operational behavior.
The handler is the primary integration point for applications that execute protocols at runtime. It wraps session-typed channel pairs, manages endpoint lifecycle, and dispatches send and receive operations according to the choreographic projection for each participant role.
Quick Start
Basic Two-Party Protocol
use serde::{Deserialize, Serialize};
use telltale::{Message, Role};
use telltale_runtime::{
ChoreoHandler, LabelId, RoleId, RoleName, TelltaleEndpoint, TelltaleHandler, TelltaleSession,
};
// Define roles
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum MyRole {
Alice,
Bob,
}
// Define a label type for choices (unused in this example)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum Choice {
Default,
}
impl LabelId for Choice {
fn as_str(&self) -> &'static str {
"Default"
}
fn from_str(label: &str) -> Option<Self> {
match label {
"Default" => Some(Choice::Default),
_ => None,
}
}
}
impl RoleId for MyRole {
type Label = Choice;
fn role_name(&self) -> RoleName {
match self {
MyRole::Alice => RoleName::from_static("Alice"),
MyRole::Bob => RoleName::from_static("Bob"),
}
}
}
impl Role for MyRole {
type Message = MyMessage;
fn seal(&mut self) {}
fn is_sealed(&self) -> bool {
false
}
}
// Define messages
#[derive(Debug, Clone, Serialize, Deserialize)]
struct MyMessage {
content: String,
}
impl Message<Box<dyn std::any::Any + Send>> for MyMessage {
fn upcast(msg: Box<dyn std::any::Any + Send>) -> Self {
*msg.downcast::<MyMessage>().unwrap()
}
fn downcast(self) -> Result<Box<dyn std::any::Any + Send>, Self> {
Ok(Box::new(self))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create endpoints
let mut alice_ep = TelltaleEndpoint::new(MyRole::Alice);
let mut bob_ep = TelltaleEndpoint::new(MyRole::Bob);
// Setup sessions
let (alice_session, bob_session) = TelltaleSession::pair();
alice_ep.register_session(MyRole::Bob, alice_session);
bob_ep.register_session(MyRole::Alice, bob_session);
// Create handlers
let mut alice_handler = TelltaleHandler::<MyRole, MyMessage>::new();
let mut bob_handler = TelltaleHandler::<MyRole, MyMessage>::new();
// Send and receive
let msg = MyMessage {
content: "Hello!".to_string(),
};
alice_handler.send(&mut alice_ep, MyRole::Bob, &msg).await?;
let received: MyMessage = bob_handler.recv(&mut bob_ep, MyRole::Alice).await?;
println!("Received: {}", received.content);
Ok(())
}
This example creates two endpoints and connects them with a TelltaleSession pair. It demonstrates direct send and recv calls through the handler.
Simulator Regression Lane
Use telltale-simulator to regression test projects that mix TelltaleHandler style protocol logic with protocol-machine execution.
The harness API gives one stable path for scenario setup and contract checks.
This avoids project-specific guard boilerplate for common replay and trace assertions.
#![allow(unused)]
fn main() {
let harness = SimulationHarness::new(&DirectAdapter::new(&handler));
let result = harness.run(&spec)?;
assert_contracts(&result, &ContractCheckConfig::default())?;
}
This check complements endpoint unit tests. It validates protocol-machine behavior under the same scenario middleware used by simulator parity lanes.
Core Concepts
Roles
Roles represent participants in the choreography. They must implement:
telltale::RoleRoleId(fromtelltale_runtime::effects)Clone,Copy,Debug,PartialEq,Eq,Hash
RoleId requires an associated label type that implements LabelId. You only need labels if your protocol uses choice.
Messages
Messages are the data exchanged between roles. They must:
- Implement
SerializeandDeserialize(via serde) - Implement
telltale::Message - Be
SendandSync
Endpoints
TelltaleEndpoint<R> manages the channels and session state for a role:
- One endpoint per role in the protocol
- Contains channels to all peers
- Tracks session metadata (operation counts, state descriptions)
Sessions
TelltaleSession provides the canonical communication boundary:
- Created in pairs for in-process tests:
TelltaleSession::pair() - Or built from custom sink/stream transports with
TelltaleSession::from_sink_stream() - The handler serializes and deserializes messages with bincode
Handlers
TelltaleHandler<R, M> interprets choreographic effects:
- Stateless (can be shared across operations)
- Implements
ChoreoHandlertrait - Provides send, recv, choose, offer operations
API Reference
TelltaleEndpoint
#![allow(unused)]
fn main() {
impl<R: Role + Eq + Hash + Clone + Debug> TelltaleEndpoint<R>
}
This shows the generic bounds required by the endpoint type.
Constructor
#![allow(unused)]
fn main() {
pub fn new(local_role: R) -> Self
}
Create a new endpoint for a role.
Role Access
#![allow(unused)]
fn main() {
pub fn local_role(&self) -> &R
}
Get a reference to the local role.
Session Management
#![allow(unused)]
fn main() {
pub fn register_session(&mut self, peer: R, session: TelltaleSession)
}
Register a session (for example one produced via TelltaleSession::pair or
TelltaleSession::from_sink_stream). Use this when you need additional
transport logic such as WebSockets, recording, or custom middleware stacks.
#![allow(unused)]
fn main() {
pub fn has_channel(&self, peer: &R) -> bool
}
Check if a session exists for a peer.
#![allow(unused)]
fn main() {
pub fn close_channel(&mut self, peer: &R) -> bool
}
Close a specific session.
#![allow(unused)]
fn main() {
pub fn close_all_channels(&mut self) -> usize
}
Close all sessions and return count.
#![allow(unused)]
fn main() {
pub fn active_channel_count(&self) -> usize
}
Get number of active sessions.
#![allow(unused)]
fn main() {
pub fn is_all_closed(&self) -> bool
}
Check if all sessions are closed.
Metadata Access
#![allow(unused)]
fn main() {
pub fn get_metadata(&self, peer: &R) -> Option<&SessionMetadata>
}
Get session metadata for a peer.
#![allow(unused)]
fn main() {
pub fn all_metadata(&self) -> Vec<(R, &SessionMetadata)>
}
Get metadata for all sessions.
TelltaleHandler
#![allow(unused)]
fn main() {
impl<R, M> TelltaleHandler<R, M>
}
This shows the handler type parameters. The handler is generic over role and message types.
Constructor
#![allow(unused)]
fn main() {
pub fn new() -> Self
}
Create a new handler.
ChoreoHandler Implementation
#![allow(unused)]
fn main() {
async fn send<Msg>(&mut self, ep: &mut Endpoint, to: Role, msg: &Msg) -> ChoreoResult<()>
where Msg: Serialize + Send + Sync
}
Send a message to a role.
#![allow(unused)]
fn main() {
async fn recv<Msg>(&mut self, ep: &mut Endpoint, from: Role) -> ChoreoResult<Msg>
where Msg: DeserializeOwned + Send
}
Receive a message from a role.
#![allow(unused)]
fn main() {
async fn choose(&mut self, ep: &mut Endpoint, who: Role, label: Label) -> ChoreoResult<()>
}
Make a choice (internal choice).
#![allow(unused)]
fn main() {
async fn offer(&mut self, ep: &mut Endpoint, from: Role) -> ChoreoResult<Label>
}
Offer a choice (external choice).
#![allow(unused)]
fn main() {
async fn with_timeout<F, T>(&mut self, ep: &mut Endpoint, at: Role, dur: Duration, body: F) -> ChoreoResult<T>
where F: Future<Output = ChoreoResult<T>> + Send
}
Execute operation with timeout.
TelltaleSession Builders
#![allow(unused)]
fn main() {
TelltaleSession::pair()
}
Builds a connected in-process session pair.
#![allow(unused)]
fn main() {
TelltaleSession::from_sink_stream(sender, receiver)
}
Accepts any async sink and stream pair carrying Vec<u8> payloads. It exposes the pair to the handler. Use this for custom transports, then call endpoint.register_session(peer, session).
SessionMetadata
#![allow(unused)]
fn main() {
pub struct SessionMetadata {
pub state_description: String,
pub is_complete: bool,
pub operation_count: usize,
}
}
This struct records session state for a peer. It is updated as operations run.
Tracks session progression:
state_description: Human-readable current stateis_complete: Whether session has completedoperation_count: Number of operations performed
Usage Patterns
Pattern 1: Request-Response
#![allow(unused)]
fn main() {
// Client side
let request = Request { query: "data".to_string() };
handler.send(&mut endpoint, Role::Server, &request).await?;
let response: Response = handler.recv(&mut endpoint, Role::Server).await?;
}
This pattern sends a request and waits for a response. It is the simplest round trip flow.
Pattern 2: Choice with Branches
#![allow(unused)]
fn main() {
// Sender
let decision = if condition {
Choice::Accept
} else {
Choice::Reject
};
handler.choose(&mut endpoint, Role::Other, decision).await?;
// Receiver
let choice = handler.offer(&mut endpoint, Role::Other).await?;
match choice {
Choice::Accept => {
// Handle accept branch
}
Choice::Reject => {
// Handle reject branch
}
}
}
This pattern uses choose and offer to coordinate a branch. The chosen label drives the receiver logic.
Pattern 3: Sequential Messages
#![allow(unused)]
fn main() {
for item in items {
handler.send(&mut endpoint, Role::Peer, &item).await?;
let ack: Ack = handler.recv(&mut endpoint, Role::Peer).await?;
}
}
This pattern sends a batch of items with acknowledgments. Each step waits for the peer response.
Pattern 4: Multi-Party Coordination
#![allow(unused)]
fn main() {
// Coordinator
let offer: Offer = handler.recv(&mut endpoint, Role::Buyer).await?;
handler.send(&mut endpoint, Role::Seller, &offer).await?;
let response: Response = handler.recv(&mut endpoint, Role::Seller).await?;
handler.send(&mut endpoint, Role::Buyer, &response).await?;
}
This pattern relays messages between two peers. It keeps the coordinator role in control of ordering.
Pattern 5: Timeout Protection
#![allow(unused)]
fn main() {
let result = tokio::time::timeout(
Duration::from_secs(5),
handler.recv::<Response>(&mut endpoint, Role::Server),
)
.await;
match result {
Ok(Ok(msg)) => {
// Process message
}
Ok(Err(e)) => {
// Handle handler errors
}
Err(_) => {
// Handle timeout
}
}
}
This pattern wraps a receive in a runtime timeout. Generated effect programs use
with_timeout internally when sender metadata includes runtime_timeout or
when a protocol-level timeout ... on timeout ... branch is present.
Best Practices
1. Resource Management
Recommended approach:
#![allow(unused)]
fn main() {
// Close channels explicitly when done
endpoint.close_all_channels();
}
This closes channels explicitly when the protocol is complete.
Recommended alternative:
#![allow(unused)]
fn main() {
// Use Drop to ensure cleanup
{
let mut endpoint = TelltaleEndpoint::new(role);
// ... use endpoint ...
} // Drop ensures cleanup
}
This relies on drop to clean up resources at scope end.
Avoid:
#![allow(unused)]
fn main() {
// Don't forget to clean up resources
let mut endpoint = TelltaleEndpoint::new(role);
// ... use endpoint ...
// Forgot to close!
}
This leaves channels open after the protocol.
2. Error Handling
Recommended approach:
#![allow(unused)]
fn main() {
match handler.send(&mut ep, role, &msg).await {
Ok(()) => { /* success */ }
Err(ChoreographyError::Transport(e)) => {
// Handle transport error
tracing::error!("Send failed: {}", e);
}
Err(e) => {
// Handle other errors
}
}
}
This handles transport errors explicitly. It keeps other errors visible.
Avoid:
#![allow(unused)]
fn main() {
// Don't ignore errors
handler.send(&mut ep, role, &msg).await.unwrap();
}
This panics on failures and hides transport details.
3. Session Setup
Recommended approach:
#![allow(unused)]
fn main() {
// Setup all sessions before starting protocol
let (alice_session, bob_session) = TelltaleSession::pair();
alice_ep.register_session(Role::Bob, alice_session);
bob_ep.register_session(Role::Alice, bob_session);
// Then start protocol
protocol_run().await?;
}
This ensures sessions exist before the first send.
Avoid:
#![allow(unused)]
fn main() {
// Don't register sessions mid-protocol
handler.send(&mut ep, role, &msg).await?; // Might not have session!
ep.register_session(role, session); // Too late!
}
This can cause send failures when a session is missing.
4. Metadata Usage
Recommended approach:
#![allow(unused)]
fn main() {
// Use metadata for debugging and monitoring
if let Some(meta) = endpoint.get_metadata(&peer) {
tracing::info!(
peer = ?peer,
operations = meta.operation_count,
state = %meta.state_description,
"Session status"
);
}
}
This reports progress and state for each peer. It is useful for debugging.
5. Testing
Recommended approach:
#![allow(unused)]
fn main() {
#[tokio::test]
async fn test_protocol() {
// Setup test environment
let mut alice_ep = TelltaleEndpoint::new(Role::Alice);
let mut bob_ep = TelltaleEndpoint::new(Role::Bob);
let (alice_session, bob_session) = TelltaleSession::pair();
alice_ep.register_session(Role::Bob, alice_session);
bob_ep.register_session(Role::Alice, bob_session);
// Test protocol
let msg = TestMessage { data: vec![1, 2, 3] };
handler.send(&mut alice_ep, Role::Bob, &msg).await.unwrap();
let received: TestMessage = handler.recv(&mut bob_ep, Role::Alice).await.unwrap();
assert_eq!(received.data, vec![1, 2, 3]);
}
}
This sets up a local session pair and exercises a full send and receive. It validates handler wiring in tests.
Operational Notes
TelltaleHandler is intentionally thin. Most correctness comes from the session-typed state in TelltaleEndpoint and from protocol generation. Treat the handler as a transport adapter with stable behavior. Keep role definitions and message definitions close to each protocol module to make type drift easy to detect during review.
For production integrations, prefer explicit lifecycle management even though drop-based cleanup exists. Close sessions when a protocol is complete. Inspect endpoint metadata after important milestones such as branch commits or retries. Metadata captures operation counts and completion status per peer.
When introducing custom transports, keep serialization and framing deterministic. Use one canonical message encoding in both tests and production. Add at least one integration test that runs both endpoints concurrently and one scenario test that exercises failure handling.
Effect Handlers and Session Types
This document defines the integration boundary between Telltale and a host runtime.
The boundary is the protocol-machine EffectHandler interface.
It is centered on typed EffectRequest and EffectOutcome values.
Scope
Use this document when integrating Telltale into another execution environment. Use Choreography Effect Handlers when implementing async handlers for generated choreography code.
This bridge is for protocol-critical capability and evidence flow. It is not a general host authorization framework. Arbitrary application auth, policy, and resource-permission systems remain outside this boundary.
Three-Layer Contract
Telltale uses three layers.
| Layer | Artifact | Role |
|---|---|---|
| Global protocol layer | choreography and projection | defines role-local protocol obligations |
| Effect layer | handler interfaces | defines runtime action behavior |
| Protocol-machine layer | bytecode and monitor checks | enforces instruction-level typing and admission rules |
Projection and runtime checks preserve obligations across these layers.
This document describes a host-runtime contract. It is normative for Rust embedders. It is not itself a theorem statement. The theorem-backed protocol properties remain in projection, coherence, and harmony. The host ownership rules below are implementation contracts enforced by the protocol-machine boundary.
Rust Handler Surfaces
Rust exposes two handler interfaces.
| Interface | Location | Purpose |
|---|---|---|
ChoreoHandler | rust/runtime/src/effects/handler.rs | async typed API for generated choreography code |
EffectHandler | rust/machine/src/effect.rs | sync protocol-machine API over bytecode values |
Third-party runtime integration should use EffectHandler.
Why the Protocol-Machine Boundary
EffectHandler is synchronous.
This matches deterministic host execution models.
It avoids futures and runtime-specific scheduling inside handler calls.
EffectHandler operates on protocol-machine values and labels.
This keeps the wire and storage boundary host-defined.
It avoids coupling to generated Rust message types.
The protocol machine enforces session typing before and during execution. The boundary keeps typing logic in Telltale. It keeps host logic focused on effect interpretation.
Canonical Ingress and Ownership
External host events must enter the protocol machine through canonical typed effect requests.
| Typed ingress | Purpose | Ownership rule |
|---|---|---|
EffectRequestBody::TopologyEvents | inject crash, partition, heal, corruption, and timeout events | must not mutate session-local host state directly |
EffectRequestBody::SendDecision | compute outbound delivery behavior | may inspect request-local state only |
EffectRequestBody::Receive | apply receive-side host effects | may mutate request-local state only |
EffectRequestBody::InvokeStep | perform Invoke-scoped integration work | may mutate request-local state only |
EffectRequestBody::OutputConditionHint | provide authoritative commit metadata for proof-bearing success and canonical handle issuance | must not be synthesized from observational snapshots |
Session-local host mutation outside these request-local values flows through an explicit ownership capability such as OwnedSession.
This is the host integration path for mutating edge traces, handler bindings, or other session-local host metadata.
Protocol-critical host decisions should also use explicit witnesses where available:
- readiness checks should issue and later consume a
ReadinessWitness - ownership-failure cancellation paths issue a
CancellationWitness - topology timeout ingress emits a
TimeoutWitness
Host rules:
EffectHandlermethods are synchronous. Async work must happen outside request handling and feed results back through a later ingress call.- Admission and ownership are distinct. Passing admission/runtime gates does not imply the caller is the current session owner.
- Ownership is generation-bearing. Reusing the same owner label after transfer does not preserve authority.
- Fragment-scoped capabilities do not imply full-session ingress rights.
- Stale owners fail closed with typed ownership diagnostics before host mutation is applied.
Boundary Ownership
The boundary separates protocol semantics from host materialization.
| Concern | Protocol machine owns | Host EffectHandler owns |
|---|---|---|
| Session typing | Local type checks and continuation advancement | none |
| Buffer and signature discipline | enqueue, dequeue, and signature verification | none |
| Scheduler and commit | coroutine scheduling and atomic step commit | none |
| Send policy | call point and branch label context | send_decision return value |
| Receive side effects | receive timing and source payload | register and host state mutation in handle_recv |
| Invoke integration | when invoke runs | integration state mutation in step |
| Guard transitions | protocol-machine guard instruction flow | grant or block in handle_acquire, validation in handle_release |
| Topology metadata | event ingestion order and application | produced events in topology_events |
| Output metadata | commit-time query point | optional hint from output_condition_hint |
Additional ownership split:
| Concern | Protocol machine owns | Host runtime owns |
|---|---|---|
| current owner identity and generation | validation and stale-owner rejection | choosing owner labels and transfer policy |
| transfer receipts and rollback | staged transfer enforcement and audit artifacts | when to request transfer |
| session-local mutation gate | capability and scope checks | operations performed through OwnedSession |
Typed Effect Boundary
The protocol-machine dispatch path is in rust/machine/src/engine/.
The trait surface is in rust/machine/src/effect.rs.
The normative contract is documented in that trait module.
| Surface | protocol-machine call point | Runtime behavior | Integration note |
|---|---|---|---|
handle_effect(EffectRequest) | all host-facing instruction sites | one canonical request/outcome surface | new runtime code must use this path |
EffectRequest.metadata | all request construction sites | carries EffectInterfaceMetadata fields: interface name, operation name, authority class, admissibility, totality, timeout, reentrancy, handler domain | validated before dispatch |
EffectRequestBody::SendDecision | step_send, step_offer | called before enqueue | receives send context plus optional precomputed payload |
EffectRequestBody::Receive | step_recv, step_choose | called after dequeue and verification | use for state updates and host-side effects |
EffectRequestBody::Choose | trait helper / custom runners | branch-selection helper | not part of the canonical default dispatch path |
EffectRequestBody::InvokeStep | step_invoke | called during Invoke instruction | use for integration steps and persistent deltas |
EffectRequestBody::Acquire | step_acquire | grant, block, or fail acquire | return evidence in EffectResponse::Acquire |
EffectRequestBody::Release | step_release | release validation | return EffectOutcome::failure(...) to reject invalid evidence |
EffectRequestBody::TopologyEvents | ingest_topology_events | called once per scheduler tick | events are sorted by deterministic ordering key before apply |
EffectRequestBody::OutputConditionHint | post-dispatch pre-commit | queried only when a step emits events | return None to use protocol-machine default |
handler_identity | trace and commit attribution | stable handler id in traces | defaults to DEFAULT_HANDLER_ID when not overridden |
Helper-method compatibility notes:
handle_sendandhandle_choosemust not become hidden side channels for session metadata mutation.- helper methods remain compatibility helpers for default
handle_effectimplementations. They are not separate ingress paths. - Bridge traits in
rust/machine/src/bridge.rsare deterministic lookup/projection surfaces, not mutation surfaces. - Public host integrations open sessions through
load_choreography_owned(...)and mutate session-local host metadata throughOwnedSession.
Typed Effect Outcomes
The protocol machine now uses a typed effect boundary.
EffectHandler::handle_effect returns an EffectOutcome.
Helper methods use typed EffectResult<T> rather than Result<T, String>.
| Outcome surface | Purpose |
|---|---|
EffectOutcome::success(EffectResponse) | request completed successfully |
EffectOutcome::blocked() / EffectResult::Blocked | request requested a clean scheduler-visible block |
EffectOutcome::failure(EffectFailure) / EffectResult::Failure(EffectFailure) | request failed with typed diagnostics |
EffectFailureKind | coarse failure taxonomy including denied, timeout, cancellation, stale authority, invalid evidence, unavailable, invalid input, determinism, topology disruption, and contract violation |
Host guidance:
- Use
Blockedonly for genuinely resumable conditions such as acquire deferral. - Use
Failurefor typed terminal or policy-visible failures. - Do not encode timeout, cancellation, or ownership failure in ad hoc strings when a specific
EffectFailureKindexists. - Replay, recording, bridge export, and effect-exchange serialization preserve these typed outcomes directly.
Language-Declared Effect Invocation
The choreography language now has nominal effect declarations, protocol-level uses clauses, and check Effect.op(...) expressions.
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
protocol CommitFlow uses Runtime =
roles Coordinator, Worker
authoritative let readiness = check Runtime.ready(session)
Coordinator -> Worker : Commit
Host-boundary rule:
- this does not create a second integration channel beside the protocol-machine effect layer
- language-declared effect operations still lower to the same typed protocol machine
invokeboundary and handler-obligation model - missing or ambiguous authority must surface as typed failure or explicit evidence rejection, never as fallback success
Operational consequence:
- embedders implement one typed host boundary in
EffectHandler - language-level authority checks become explicit effect observations and semantic-audit records once lowered into the protocol machine
Authoritative Reads, Materialization, and Publication
The protocol machine now distinguishes observational reads from semantic-path authoritative reads.
| Surface | Meaning |
|---|---|
ObservedRead | handler/effect observation only. Must not authorize semantic truth. |
AuthoritativeRead | witness-bearing semantic input accepted on parity-critical paths |
MaterializationProof | proof-bearing success artifact derived from canonical output-condition checks |
CanonicalHandle | strong runtime handle that later parity-critical paths must require |
PublicationEvent | one sealed canonical publication path for lifecycle-visible semantic state |
Host-runtime rules:
- observational effect results must not be promoted into semantic truth
- proof-bearing success is mandatory when an operation declares
requires_proof - canonical publication is derived from sanctioned runtime state. Embedders must not bypass it with parallel publish helpers.
- parity-critical follow-on work should require a
CanonicalHandle, not a weak id reconstructed from observational state
Authority Witnesses
The ownership boundary now includes explicit witness objects for protocol-critical authority flow.
| Witness | Issued by | Consumed by | Purpose |
|---|---|---|---|
ReadinessWitness | SessionStore::issue_readiness_witness or OwnedSession::issue_readiness_witness | consume_readiness_witness | prove a readiness/admission-style check under a specific live owner generation |
CancellationWitness | owner death or abandoned-transfer handling | observational/audit surface | make cancellation-triggering ownership failure explicit |
TimeoutWitness | topology timeout ingress | observational/audit surface | make timeout issuance explicit and replay-visible |
Readiness witnesses are generation-bound and single-use. They fail closed if the owner becomes stale, scope attenuates, the witness is forged, or a second consume is attempted. These runtime witnesses are the evidence objects that back language-level authority checks and evidence-bearing branches after lowering.
Integration Workflow
- Use
telltale-theoryat setup time to project global types to local types. - Compile local types to protocol-machine bytecode and load with
CodeImage. - Open sessions with
load_choreography_owned(...)so the host authority boundary is explicit from the first step. - Implement
EffectHandlerwith deterministic host operations. - Map each typed effect request to host primitives without reimplementing protocol typing.
- Run
run_loaded_protocol_machine_record_replay_conformanceto validate record and replay behavior on a loaded protocol machine. - Run Lean bridge lanes for parity and equivalence checks.
Integration Tooling
When repository tooling needs exported files on disk, use
just effect-scaffold to generate host integration stubs. This is not the
normal application path. Normal code should implement the typed effect
interfaces emitted directly by tell!.
The command reads DSL effect declarations and emits:
generated_effects.rswith canonical Rust request/outcome enums and host-handler traitsgenerated_simulator.rswith first-class simulator traits, state, and scenario buildersgenerated_effect_manifest.jsonwith the exported effect-family schema- a local generated
README.mdwith next-step instructions
just effect-scaffold path/to/protocol.tell
This command writes files under artifacts/effect_handler_scaffold by default. The direct form is:
cargo run -p telltale-runtime --bin effect-scaffold -- --out artifacts/effect_handler_scaffold --dsl path/to/protocol.tell
Use --no-simulator when you want only the Rust effect boundary without simulator artifacts.
Use just sim-run <config> to execute a simulator harness config file.
This command runs the protocol machine with scenario middleware and contract checks.
It is the fastest path for CI validation in third party host projects.
just sim-run artifacts/sim_integration.toml
This command prints a JSON report. The process exits with code 2 when contract checks fail.
Simulator Validation Lane
Use telltale-simulator harness types for integration tests.
HarnessSpec carries local types, global type, scenario, and optional initial states.
SimulationHarness runs the spec with one HostAdapter.
#![allow(unused)]
fn main() {
let adapter = DirectAdapter::new(&my_effect_handler);
let harness = SimulationHarness::new(&adapter);
let result = harness.run(&spec)?;
assert_contracts(&result, &ContractCheckConfig::default())?;
}
This lane validates runtime behavior without reimplementing protocol-machine checks in the host project. See Simulation Overview for harness config fields and preset helpers.
Performance and Diagnostics Controls
ProtocolMachineConfig.effect_trace_capture_mode controls effect trace overhead.
Default mode is full.
ProtocolMachineConfig.payload_validation_mode controls runtime payload hardening.
Use off for trusted benchmarks, structural for standard deployments, and strict_schema for adversarial inputs.
ProtocolMachineConfig.max_payload_bytes bounds payload size in protocol-machine message validation.
Default is 65536.
ProtocolMachineConfig.host_contract_assertions enables runtime checks for handler identity stability, topology ordering inputs, and transfer-event auditability.
Default value is false.
Integration Checklist
- Determinism: use stable ordering and deterministic serialization.
- Atomicity: ensure a failed effect does not leave partial host state.
- Isolation: keep handler state scoped to the active endpoint and session.
- Ownership: route session-local host mutation through a current ownership capability, not ad hoc session-store access.
- Canonical ingress: surface async work by later ingress calls rather than performing it inside synchronous request handling.
- Replayability: validate traces with
RecordingEffectHandlerandReplayEffectHandler. - Admission: keep protocol-machine runtime gates and profile settings explicit in
ProtocolMachineConfig.
Lean Correspondence
Lean splits effect execution and typing.
This split is in lean/Runtime/ProtocolMachine/Model/TypeClasses.lean and the typed
request/outcome model in lean/Runtime/ProtocolMachine/Model/Effects.lean.
| Rust or protocol-machine surface | Lean surface | Purpose |
|---|---|---|
EffectHandler execution boundary | EffectRuntime.exec | executable effect semantics |
| handler typing obligation | EffectSpec.handlerType | typing-level effect contract |
| typed request/outcome model | Runtime/ProtocolMachine/Model/Effects.lean | shared effect-interface metadata plus request/outcome correspondence |
| invoke typing | WellTypedInstr.wt_invoke in lean/Runtime/ProtocolMachine/Runtime/Monitor.lean | ties invoke to handler type |
| behavioral equivalence | Runtime/Proofs/EffectBisim/* | observer-level bisimulation bridge |
| config equivalence bridge | Runtime/Proofs/EffectBisim/ConfigEquivBridge.lean | links protocol quotient and effect bisimulation |
| composed effect domains | Runtime/Proofs/ProtocolMachine/DomainComposition.lean | sum and product composition instances |
Glossary
| Term | Meaning |
|---|---|
Program and Effect | choreography free algebra in telltale-runtime |
ChoreoHandler | async typed handler for generated choreography code |
EffectHandler | sync protocol-machine host interface for runtime integration |
EffectRuntime | Lean executable effect action and context transition |
EffectSpec | Lean typing obligation for effect actions |
telltale_types::effects | shared deterministic clock and RNG traits for simulation support |
Related Docs
- Choreography Effect Handlers
- Protocol Machine Architecture
- Lean Verification
- Rust-Lean Bridge and Parity
Protocol Machine Architecture
This document defines the protocol-machine architecture, scheduling semantics, and concurrency envelope. These surfaces are used by Rust runtime targets and Lean conformance surfaces.
Transport security boundary: the runtime topology TCP helper is
trusted-network-only. The reference telltale-transport crate supports
pre-shared-key peer authentication and can export a semantic transport contract
for theorem-pack admission, but it is still plaintext TCP. Use trusted-network
TCP only on localhost, a trusted host network, or a trusted VPN unless a
deployment provides an authenticated transport profile that satisfies the
protocol machine’s documented transport contract.
Architecture Overview
The protocol machine is the sole authority over semantic progression. It realizes structure conservation and authority conservation from the Conservation Framework. All protocol-visible truth is committed through the protocol machine. Handlers may stage and return effect outcomes, but they do not mutate semantic state directly.
The canonical semantic authority is ProtocolMachineKernel. The cooperative ProtocolMachine and threaded ThreadedProtocolMachine (backed by NativeThreadedDriver) are the guest-runtime execution surfaces that call kernel-owned step entrypoints. Both implement the KernelMachine trait, which provides kernel_step_round for executing scheduler rounds.
The runtime keeps a single state model across targets. Core state includes coroutines, sessions, scheduler queues, observable trace, and effect trace. It also includes live operation-instance state, live outstanding-effect state, delegation audit records, and failure-topology snapshot fields.
The canonical exported semantic surface is the semantic-object family:
OperationInstance, OutstandingEffect, SemanticHandoff,
AuthoritativeRead, ObservedRead, MaterializationProof,
CanonicalHandle, PublicationEvent, ProgressContract, and
ProgressTransition.
Canonicalization is not implicit in those object lists. The runtime derives a
first-class finalization subsystem from them in
rust/machine/src/semantic_objects.rs:
ProtocolMachineFinalizationFinalizationPathFinalizationReadClassFinalizationStage
That subsystem is the explicit runtime boundary between provisional observation, authoritative evidence, proof-bearing materialization, canonical publication/handle issuance, invalidation after handoff, and rejected publication paths.
The first-class protocol-critical capability boundary is intentionally split into four classes:
- admission capability surfaces
- ownership capability surfaces
- evidence/finalization capability surfaces
- transition capability surfaces
The source-of-truth Rust and Lean boundaries for that taxonomy are
rust/machine/src/capabilities.rs and lean/Runtime/Proofs/Capabilities.lean.
The bridge-facing strict correspondence surface for the first-class
capability/finalization model is rust/bridge/tests/capability_model_correspondence.rs
against inspectCapabilityModel in the Lean runner.
The canonical round model is one semantic step when concurrency is nonzero. Threaded execution is admitted as an extension only when the wave certificate gate is satisfied.
Engine Roles
| Engine | Role | Contract Surface |
|---|---|---|
ProtocolMachine (protocol machine) | Canonical cooperative protocol machine | Exact reference for parity at concurrency 1 |
ThreadedGuestRuntime (NativeThreadedDriver) | Parallel wave executor | Certified-wave execution with fallback to canonical one-step |
| WASM guest runtime | Single-thread deployment | Cooperative schedule only |
Scheduler Semantics
Canonical scheduling is one semantic step when concurrency is nonzero. ProtocolMachineKernel owns the selection and step contract. For cooperative execution, this gives exact behavior for deterministic replay and baseline parity.
The canonical Lean runner is runScheduled in Runtime/ProtocolMachine/Runtime/Runner.lean. For nonzero concurrency, canonical round semantics normalize to one scheduler step. This model is the semantic reference for parity at concurrency 1.
Scheduler Policies
Scheduler policy controls selection order. Policy does not change instruction semantics.
| Policy | Primary Runtime Use |
|---|---|
Cooperative | Canonical single-step execution and WASM deployment |
RoundRobin | Fair queue progression in native runs |
Priority | Explicit priority maps or token weighting |
ProgressAware | Token-biased pick behavior |
Threaded Wave Execution
Threaded execution selects compatible picks per wave. Compatibility is lane-disjoint, session-disjoint, and footprint-disjoint for picks in the same wave.
The threaded extension is defined in Runtime/ProtocolMachine/Runtime/ThreadedRunner.lean. Concurrency n = 1 is theorem-equal to canonical execution. For n > 1, execution is admitted through certified waves. Invalid certificates degrade to canonical one-step behavior.
Each wave is checked against a WaveCertificate. If certificate validation fails, the runtime degrades to canonical one-step behavior for that round.
Refinement Envelope
| Concurrency Regime | Required Contract | Enforcement Lane |
|---|---|---|
n = 1 | Exact canonical parity | threaded_equivalence.rs |
n > 1 | Envelope-bounded parity with declared EnvelopeDiff | parity_fixtures_v2.rs |
| Invalid wave certificate | Mandatory fallback to canonical one-step | threaded_lane_runtime.rs |
| Undocumented deviation | Active deviation coverage required | just check-parity --types |
The regression lane validates both regimes. The Rust test canonical_parity_is_exact_at_concurrency_one in rust/machine/tests/parity_fixtures_v2.rs enforces exact equality. The Rust test envelope_bounded_parity_holds_for_n_gt_1 in the same file enforces envelope-bounded behavior. These are executable regression tests, not Lean theorems.
Runtime Gates
Runtime mode admission and profile selection are capability gated.
| Gate | Runtime Surface | Current Rust Path |
|---|---|---|
| Advanced mode admission | requires_protocol_machine_runtime_contracts and admit_protocol_machine_runtime | rust/machine/src/runtime_contracts.rs, rust/machine/src/composition.rs |
| Envelope execution-profile admission | execution_profile_supported against a Lean-produced LeanEvidenceInventory (default: not admitted) | rust/machine/src/runtime_contracts.rs, rust/machine/src/composition.rs |
| Determinism profile validation | request_determinism_profile | rust/machine/src/runtime_contracts.rs, rust/machine/src/composition.rs |
| Threaded certified-wave fallback | WaveCertificate check with one-step degrade | rust/machine/src/threaded.rs |
| Deviation registry enforcement | Undocumented parity drift rejection | just check-parity --types |
Runtime Hardening Contracts
The guest-runtime adapters now enforce explicit runtime hardening at load and startup boundaries.
ThreadedProtocolMachine(backed byNativeThreadedDriver) provideswith_workersfor initialization. The inner driver also providestry_with_workersfor fallible initialization.- Cooperative and threaded
load_choreographypaths validate trustedCodeImageruntime shape before session allocation. - Preferred host integration uses
load_choreography_owned(...)andOwnedSessionwhen the embedding runtime needs explicit session ownership after open. - Register-bound violations are fail-closed through
Fault::OutOfRegistersrather than unchecked index panic in executable instruction paths. - Language-level
effectdeclarations andcheckexpressions lower through the sameInvoke/EffectHandlerboundary rather than introducing a second host execution channel.
Host Ownership Contract in the Runtime
The protocol-machine architecture now distinguishes three runtime concepts:
| Runtime concept | Purpose |
|---|---|
| protocol typing | monitor/local-type correctness |
| capability admission | whether a runtime mode/profile is allowed |
| current ownership | which host capability may mutate session-local runtime state right now |
Ownership is a host-runtime contract, not a replacement for typing or admission.
Runtime ownership details:
- session-local host mutation flows through an ownership capability carrying owner label, generation, and scope
- transfer is staged with explicit receipts
- readiness, cancellation, and timeout witnesses are first-class runtime objects with explicit lifecycle transitions
- delegation emits auditable transfer records
- host assertion mode can reject transfer events that do not have matching committed audit records
- language-level authority/evidence constructs must lower to these runtime ownership and audit surfaces instead of bypassing them with host-local heuristics
Failure and Timeout Event Surface
Failure-visible behavior now uses explicit observable events rather than relying on host-side inference from final state alone.
| Event | Runtime role |
|---|---|
TimeoutIssued | records deterministic timeout activation and timeout-witness issuance |
CancellationRequested | records the start of an explicit cancellation path |
Cancelled | records successful cancellation completion |
FailureBranchEntered | records typed failure visibility before coroutine fault finalization |
SessionTerminal | records the terminal reason for close, cancel, abort, or fault |
For canonical ordering, the cooperative runtime emits the explicit event sequence before any coarser terminal summary. Threaded execution must preserve the same per-session ordering in its observable trace envelope.
Semantic Audit Surface
Replay-visible auditability now has one canonical surface derived from existing semantic artifacts rather than ad hoc logging-only streams. Effect observations now flow from the runtime’s live outstanding-effect state rather than being reconstructed only from generic trace order.
| Source surface | Canonical semantic record |
|---|---|
| authority witness audit log | SemanticAuditRecord::Authority |
| delegation audit log | SemanticAuditRecord::Delegation |
| explicit failure/timeout/cancellation/session-terminal events | FailureBranchEntered, TimeoutIssued, CancellationRequested, Cancelled, SessionTerminal |
| outstanding-effect runtime state | EffectObservation with nominal interface/operation classification |
Runtime accessors:
ProtocolMachine::semantic_audit_log()ThreadedGuestRuntime::semantic_audit_log()canonical_replay_fragment().semantic_audit_logProtocolMachine::capability_lifecycle_audit_log()ThreadedGuestRuntime::capability_lifecycle_audit_log()
This keeps replay, simulator harnesses, and parity checks aligned on one derived semantic audit vocabulary.
Language-level authority checks are expected to arrive at this same audit
surface after lowering, with the nominal effect interface and operation name
retained in EffectObservation.
Canonical Semantic Objects
The protocol machine now exports one higher-level semantic object bundle derived from live operation/effect runtime state plus authority, delegation, and output-condition surfaces.
Runtime accessors:
ProtocolMachine::semantic_objects()ThreadedGuestRuntime::semantic_objects()canonical_replay_fragment().semantic_objects
This bundle is the canonical bridge-facing and replay-facing representation of operation instances, outstanding effects, semantic handoffs, authoritative and observed reads, materialization proofs, canonical handles, publication events, progress contracts, and progress transitions.
OperationInstance and OutstandingEffect are first-class runtime objects.
They carry owner id, budget ticks, retry policy, invalidation token,
dependent-operation edges, and terminal publication state. Replay export and
bridge payloads consume those runtime objects directly instead of deriving them
later from raw trace order.
ProgressContract is now live runtime state, not a post-hoc summary. It carries
bounded-wait metadata, last-progress tick, escalation timestamp, and the
current explicit state (pending, blocked, no_progress, degraded,
timed_out, and the existing terminal states). ProgressTransition makes each
escalation replay-visible. This allows late-result invalidation and degraded behavior to be
compared across cooperative, threaded, and wasm targets.
PublicationEvent is the one canonical runtime publication surface. It carries
publication id, operation id, observer class, publication status, and optional
proof/handle references. Proof-bearing success and publication-path
uniqueness are replay-visible through this surface.
Delegation and Reconfiguration Path
Runtime delegation uses one sanctioned manager-style path rather than scattered owner mutation.
| Step | Runtime behavior |
|---|---|
| decode transfer | extract endpoint and target coroutine |
| coherence validation | ensure source, target, and delegated endpoint stay within the intended session boundary |
| issue receipt | allocate an explicit delegation receipt with endpoint-scoped authority |
| apply transfer | move endpoint bundle and associated runtime state |
| post-check | validate resulting owner state |
| audit or rollback | record committed transfer or roll back and emit rollback audit |
This path is the runtime realization of delegation/reconfiguration. It should not be read as the theorem statement itself. The theorem-level side remains DelegationWF and related harmony results.
Reconfiguration is no longer only a single-step membership swap. The runtime now supports deterministic multi-step reconfiguration plans with:
- canonical per-phase
ReconfigurationEventartifacts - placement observations and derived transport-boundary summaries per phase
- atomic plan execution, so invalid later steps do not partially commit earlier transitions
- serializable reconfiguration snapshots that preserve membership history and executed plan artifacts across recovery
Typesafe runtime upgrade is now modeled as a specialized transition family inside that same reconfiguration subsystem rather than as an ambient host story. The canonical upgrade surfaces are:
RuntimeUpgradeRequestRuntimeUpgradeExecutionRuntimeUpgradeArtifactTransitionArtifactPhase
Each runtime upgrade records an explicit staged, admitted, committed-cutover, rolled-back, or failed artifact sequence. The admitted cutover contract is explicit about:
- execution-profile compatibility
- ownership continuity across the first cutover
- pending-effect treatment
- canonical publication continuity
Those upgrade artifacts are serialized inside
ReconfigurationRuntimeSnapshot.runtime_upgrades, so replay, recovery, and
bridge-facing export can distinguish committed cutover from rollback/failure
instead of inferring it from final membership alone.
These artifacts stay transport agnostic. They record resolved placement facts
and boundary classes (in_process, shared_memory, network) rather than any
deployment-product-specific backend details.
Capability Gate Architecture
| Capability Gate | Lean Surface | Rust Surface |
|---|---|---|
| Advanced mode admission | requiresVMRuntimeContracts, admitVMRuntime | requires_protocol_machine_runtime_contracts, admit_protocol_machine_runtime |
| Live migration switch | requestLiveMigration | Runtime contracts capability booleans in composition admission |
| Autoscale/repartition switch | requestAutoscaleOrRepartition | Runtime contracts capability booleans in composition admission |
| Placement refinement switch | requestPlacementRefinement | Runtime contracts capability booleans in composition admission |
| Relaxed reordering switch | requestRelaxedReordering | Runtime contracts capability booleans in composition admission |
Determinism Profiles
ProtocolMachineConfig.determinism_mode includes Full, ModuloEffects, ModuloCommutativity, and Replay.
| Profile | Lean Profile | Rust Profile | Gate Requirement |
|---|---|---|---|
| Full | full | DeterminismMode::Full | Profile artifact support |
| Modulo effect trace | moduloEffectTrace | DeterminismMode::ModuloEffects | Mixed-profile capability plus artifact support |
| Modulo commutativity | moduloCommutativity | DeterminismMode::ModuloCommutativity | Mixed-profile capability plus artifact support |
| Replay | replay | DeterminismMode::Replay | Mixed-profile capability plus artifact support |
Communication Consumption Identity
Communication replay-consumption uses one canonical identity schema across send and receive checks.
Canonical identity fields:
- Domain tag:
telltale.comm.identity.v1 - Session id:
sid - Directed edge endpoints:
sender,receiver - Protocol step context:
step_kind(send,recv,offer,choose) and local label context - Message label:
label - Payload digest: domain-separated digest of serialized payload bytes
- Sequence number:
seq_no(used bysequencemode, carried for all modes)
Replay semantics by mode:
off: no replay-consumption checks are enforced.sequence: receive must consume exactly the expected nextseq_noper(sid, sender, receiver).nullifier: receive computes a nullifier over the canonical identity and rejects already-consumed identities.
Replay outcomes:
- Duplicate delivery: reject in
sequenceandnullifier, accept inoff. - Reordered delivery: reject in
sequence, accepted when unseen innullifier, accept inoff. - Cross-session reuse: reject in
sequenceandnullifierbecausesidis part of canonical identity.
Proved Theorem Surfaces
| Area | Lean Surface | Status |
|---|---|---|
| Canonical round normalization | Runtime/Proofs/Concurrency.lean | Proved |
Threaded equality at n = 1 | sched_round_threaded_one_eq_sched_round_one, run_scheduled_threaded_one_eq_run_scheduled | Proved |
Per-session trace equality at n = 1 | per_session_trace_threaded_one_eq_canonical | Proved |
| Scheduler theorem exports | Runtime/Proofs/protocol machine/Scheduler.lean | Proved |
Premise-Scoped Interface Surfaces
| Area | Lean Surface | Interface Type |
|---|---|---|
Threaded n > 1 refinement | ThreadedRoundRefinementPremises | Premise-scoped |
| Runtime admission/profile gates | Runtime/Proofs/Contracts/RuntimeContracts.lean | Interface consumed by runtime |
| Theorem-pack capability inventory APIs | Runtime/Proofs/TheoremPack/API.lean | Interface consumed by runtime |
These interfaces are intentionally explicit. They are not claimed as unconditional global theorems. Canonical one-step normalization and n = 1 refinement are theorem-backed in Lean. Higher-concurrency threaded refinement is modeled as a certified interface with premise-scoped refinement obligations.
Rust uses executable certificate checking and parity fixtures as release guards.
Release and CI Conformance
Release conformance surfaces are exported through theorem-pack APIs and enforced by just check-release-conformance. Parity and drift governance are enforced by just check-parity --all.
Targeted Runtime Retirement
The exclusive machine owner can acknowledge targeted close and reap on either cooperative or threaded backends. Successful retirement removes the target’s live execution and scheduler custody while preserving unrelated sessions and stable coroutine identifiers. Threaded acknowledgment follows completion of the owned worker scope. Required preflight faults return typed lifecycle errors before mutation. Session Lifecycle defines the terminal epoch, archival, and acknowledgment contracts.
Related Docs
- Protocol-Machine Bytecode Instructions
- Session Lifecycle
- Simulation Overview
- Rust-Lean Bridge and Parity
- Effect Handlers and Session Types
- Lean Verification
- Capability Admission
Live Effect Custody and History Retention
The existing outstanding-effect, operation-instance and progress-contract fields are canonical resident state used by reentrancy and bounded progress enforcement. They retain all live records independently of optional diagnostic retention on both backends. Disabled retention removes retired terminal history, and Capped retention keeps at most its configured number of terminal records per stream while preserving live obligations and all records of resident sessions. Oldest eligible retired terminal records are pruned first. A live obligation is never evicted merely because a later topology exchange or other observation was recorded. Total resident records can exceed the history cap. That cap is not semantic admission control.
Actual completion, timeout/invalidation, targeted session disposal and ownership handoff update the same original fields before terminal history pruning. These are protocol-machine lifecycle transitions. They do not acknowledge teardown of an arbitrary host future or native worker, and audit serialization cannot reconstruct live authority. The shared actual-producer tests exercise owned Acquire, subsequent topology ingress, reentrancy/progress and targeted settlement with Capped1 and Disabled. Declarations alone are not execution evidence.
Terminal status alone does not make a record diagnostic: the actual commit path
uses resident operation/publication records to derive the next WalSyncRequest.
All three canonical streams therefore also retain every record associated with
an actually resident session, including terminal effects and operations. Only
after the original session store acknowledges its removal can terminal records
become history eligible for pruning. Ordinary closed-but-resident sessions keep
that evidence. This retains the original records, not a reconstructed summary or
parallel WAL ledger. A busy/poisoned threaded registry cannot prove nonresidency,
so pruning conservatively leaves records resident.
Finite simulated-clock admission
The original SimClock advances once per admitted scheduler round, including a
round that then observes all coroutines terminal. This is simulated model time.
Worker waves do not create extra ticks and physical host time is not substituted.
Both Rust backends preflight the tick successor and simulated Duration
successor together. ClockExhaustion propagates as the source-bearing
ProtocolMachineError::ClockExhausted before ingress, callbacks, progress,
scheduler mutation or cooperative round-observation resets. Refusal changes
neither original counter and does not acknowledge any pending operation.
Finite canonical effect-occurrence custody
Fresh canonical effect IDs require complete capacity admission before their host
work or mutation. The finite counter never allocates its exhausted u64::MAX
sentinel. The same selected threaded wave holds its summed bound before issuance.
Actual producers retain deterministic allocation order. Already-issued original
nominal Failure/Blocked observations use their original occurrence, while fresh
issuance and positive publication retain their complete capacity obligations.
Actual returned values and errors remain with the original continuation.
A topology poll and its dynamically returned batch have separate boundaries: admit the poll before calling it, then retain its actual outcome and admit the whole event batch before applying its first mutation. Timeout-witness capacity is independently required. Capacity refusal does not undo an already admitted round’s SimClock increment or scheduler selection. It is distinct from atomic clock-exhaustion admission above and supplies no asynchronous cleanup receipt. The prepared finite-representation deviation and executable acceptance scope are recorded in Rust-Lean parity.
The bounded authoritative nominal readiness path retains its actual typed result in the original coroutine continuation. Output/WAL gates and destination checks precede the canonical same-session witness consumer. Success publication and branch installation follow that consumer without a foreign callback or clock admission. Registry issuance and live original owner/generation/scope checks are mandatory even when register data has a readiness type. The Lean session model stores the corresponding semantic owner and issued/consumed evidence in its canonical session record. This is not a Rust host-resource proof.
Protocol-Machine Bytecode Instructions
This document defines the current protocol-machine instruction set in rust/machine/src/instr.rs.
Instruction Families
The protocol machine groups instructions by execution concern.
| Family | Instructions |
|---|---|
| Communication | Send, Receive, Offer, Choose |
| Session lifecycle | Open, Close |
| Effect and guard | Invoke, Acquire, Release |
| Speculation | Fork, Join, Abort |
| Ownership and knowledge | Transfer, Tag, Check |
| Control | Set, Move, Jump, Spawn, Yield, Halt |
Instruction Reference
All register operands are bounds-checked at runtime. Out-of-range reads or writes fail with Fault::OutOfRegisters.
Communication
| Instruction | Fields | Runtime effect |
|---|---|---|
Send | chan, val | Emits a send on the endpoint in chan. Payload routing is decided through the effect handler path. |
Receive | chan, dst | Reads one message from the partner edge and writes the payload to dst. |
Offer | chan, label | Performs internal choice and emits the selected label. |
Choose | chan, table | Reads a label and branches by jump table entry. |
Session lifecycle
| Instruction | Fields | Runtime effect |
|---|---|---|
Open | roles, local_types, handlers, dsts | Allocates a new session, initializes local type state per role, binds edge handlers, and writes endpoint handles to destination registers. |
Close | session | Closes the referenced session and emits close and epoch events. |
Effect and guard
| Instruction | Fields | Runtime effect |
|---|---|---|
Invoke | action | Executes an effect step through the bound handler for the session. |
Acquire | layer, dst | Attempts guard acquisition and writes evidence to dst when granted. |
Release | layer, evidence | Releases a guard layer using previously issued evidence. |
Speculation
| Instruction | Fields | Runtime effect |
|---|---|---|
Fork | ghost | Enters speculation scope tied to a ghost session identifier. |
Join | none | Commits speculative state when reconciliation checks pass. |
Abort | none | Restores scoped checkpoint state and exits speculation. |
Ownership and knowledge
| Instruction | Fields | Runtime effect |
|---|---|---|
Transfer | endpoint, target, bundle | Transfers endpoint ownership and associated proof bundle to a target coroutine. |
Tag | fact, dst | Tags a local knowledge fact and returns the result in dst. |
Check | knowledge, target, dst | Checks a fact under the active flow policy and writes the check result to dst. |
Control
| Instruction | Fields | Runtime effect |
|---|---|---|
Set | dst, val | Writes an immediate value to a register. |
Move | dst, src | Copies a register value. |
Jump | target | Performs an unconditional jump. |
Spawn | target, args | Spawns a child coroutine with copied argument registers. |
Yield | none | Yields back to the scheduler. |
Halt | none | Terminates coroutine execution. |
Compilation From Local Types
rust/machine/src/compiler.rs exposes compile(local_type: &LocalTypeR) -> Vec<Instr>.
The compiler emits communication instructions, Invoke, and control-flow instructions. It does not emit session lifecycle, guard, speculation, or ownership opcodes.
LocalTypeR node | Emission pattern |
|---|---|
single-branch Send | Send then Invoke then continuation |
multi-branch Send | deterministic Offer on first branch then continuation |
single-branch Recv | Receive then Invoke then continuation |
multi-branch Recv | Choose with patched jump table then branch blocks |
Mu | records loop target then compiles body |
Var | Jump to loop target if bound, otherwise Halt |
End | Halt |
The compiler is intentionally simple. Full ISA coverage is provided by direct bytecode construction and runtime loaders.
Related Docs
Session Lifecycle
This document defines session state and lifecycle behavior in rust/machine/src/session/mod.rs and rust/machine/src/engine/.
Session State Model
A session stores role membership, per-endpoint local types, directed buffers, edge handler bindings, trace data, lifecycle metadata, and host-runtime ownership state.
| Field group | Purpose |
|---|---|
sid, roles | Session identity and participant set |
local_types | Current and original local type for each endpoint |
buffers | Signed directed edge buffers |
edge_handlers | Per-edge runtime handler binding |
edge_traces, auth fields | Coherence and authenticated trace material |
status, epoch | Lifecycle phase and close epoch counter |
| ownership state | current owner capability, transfer-in-progress state, issued readiness/cancellation/timeout witnesses, consumed witness ids, and terminal ownership reason |
The protocol machine also tracks communication replay-consumption state at runtime scope (off, sequence, nullifier). This state is keyed by session-qualified edges and contributes to canonical replay artifacts.
Ownership fields are a runtime hardening contract rather than a theorem surface. They govern who may drive session-local host mutation and how ownership transfer is staged and audited.
OwnedSession uses the sealed owned::OwnedSessionMachine interface for both
cooperative and threaded machines. Its public operations require an actual issued
OwnedSession, rather than serializable capability fields. Both storage adapters borrow the original
resident SessionState and execute the same ownership transitions. Threaded
entry is exclusive with respect to machine execution. No worker wave remains
active across that mutable entry. Host code does not receive a raw mutable
threaded session store.
Owned host-frame ingress uses OwnedSession::inject_message_with_sequence.
Both backends validate the live capability and full-session mutation scope in
the same resident-session entry that signs and enqueues the frame. The supplied
edge sequence is preserved. A stale, terminal, missing or fragment-scoped owner
cannot enqueue through this boundary. Ownership rejections retain their typed
source and the existing authority journal. Ownership and enqueue refusals remain
distinct variants of OwnedSessionEnqueueError. Backpressure or drop results
retain their actual EnqueueResult. This new API does not extend the existing exhaustive
ProtocolMachineError enum.
This entry does not advance endpoint local types, manufacture send-completion
evidence, issue authority or acknowledge host-resource cleanup. It adds a host
adapter for the existing signed-buffer operation, not a choreography instruction
or a new Lean ownership theorem. The raw simulation injection API remains a
separate testing surface.
Owned commit, rollback, owner-death and abandoned-transfer operations validate the calling handle’s live capability. Receipt operations also require the receipt’s session to match the calling handle, so possession of an unrelated session’s valid receipt cannot redirect an operation. Rollback removes only the matching staged claim and preserves its current owner. It does not undo a previously committed transfer or acknowledge application resource settlement.
Scope attenuation and transfer may preserve or reduce the actual owner’s scope. A fragment owner cannot recover session-wide authority or add other fragments. Session-local edge mutations reject an edge naming another session. Generations remain monotonic across release/reclaim, including restored ownership history. Generation, claim and witness counters fail before mutation when exhausted. Owner-death and abandoned-transfer paths allocate their required witness before changing terminal state. Exhaustion therefore leaves the original owner and staged transfer available for explicit recovery.
Authority records come from the canonical transition producer on both backends. The store-owned journal records actual cross-session emission order and stamps owned operations with the current machine tick. It retains genuine rejection records even when the attempted operation returns an error. Disposal order does not reorder that history. The threaded journal lock is acquired before mutation. Poisoning it cannot commit an ownership change without its required audit. Older cooperative snapshots with only per-session observations retain that representation without inventing historical emission times or stronger evidence. Targeted disposal retains those records as historical audit evidence, including across cooperative snapshot restoration. Repeated disposal does not duplicate them. Semantic objects, capability lifecycle exports and replay fragments include the retained records. They cannot reconstruct a live owner or resume a session.
Owned-loader ownership failures retain OwnershipError as the source of
ProtocolMachineError::OwnershipContract, rather than formatting the cause into
a string. Existing callers matching that variant must handle the typed payload.
Native worker-pool construction failures
Threaded construction preserves the original Rayon failure and its native I/O source in ProtocolMachineError::ThreadPoolBuild. Diagnostic text does not replace the original error chain. This host-construction error precedes admitted protocol sessions and changes no instruction, capability or protocol outcome.
A failed pool constructor can follow partial native worker startup. Rayon requests
termination on failure but returns no worker-join acknowledgment. The production
owned constructor therefore retains its original native handles alongside the
actual Rayon cause. Only explicit NativeWorkerPoolBuildFailure::settle_original_workers
issues the retained join receipt. Observing the constructor error or dropping a
pool does not acknowledge settlement. Targeted session disposal leaves a live
shared pool available and remains a separate acknowledgment.
The original direct-Rayon error fixtures establish their own host-owned joins. The canonical owned-constructor fixtures additionally exercise the production handle custodian. Neither fixture family proves signed protocol termination or settlement of unrelated application resources.
Session Status Values
SessionStatus includes Active, Draining, Closed, Cancelled, and Faulted.
Draining is currently a declared status only. The current SessionStore::close path sets Closed directly and clears buffers.
Required Targeted Disposal
ProtocolMachine::close_and_reap_session and
ThreadedProtocolMachine::close_and_reap_session acknowledge removal of exactly
one resident session and its runnable or blocked coroutine custody. A successful
return leaves no target-session coroutine in live storage or scheduler queues.
Session resources, pending handoffs, and communication-consumption state are
retired, and outstanding target effects are invalidated. Unrelated sessions and
the shared worker pool remain available.
The caller holds exclusive mutable machine ownership. Threaded rounds join every owned worker job before returning, so disposal acknowledges completed worker execution as well as removal of scheduling custody. This API neither interrupts an active handler from another thread nor creates an independent teardown worker.
Residency, stable coroutine indices, required locks, terminal count and epoch
checks precede mutation. Failures return SessionDisposalError or
ThreadedSessionLifecycleError, without a successful acknowledgment or partial
target removal. Active closure requires a checked epoch increment. An exhausted
active epoch fails closed. Disposal preserves the existing epoch and status of a
naturally Closed, Cancelled or Faulted session. Repeated acknowledgment returns
the original compact archived summary. Reaped coroutine identifiers remain stable
for surviving sessions and are never interpreted as compacted array positions.
Compact summaries and historical observations follow the machine’s existing lifetime archival policy. They are diagnostic history, not resumable sessions or protocol authority. Target disposal does not clear shared guard resources or establish successful protocol completion, authenticated cancellation, or application-level terminal publication. Those obligations remain with the owner that drives the protocol before disposal.
Ownership Lifecycle
Session ownership is tracked separately from capability admission.
| Ownership element | Meaning |
|---|---|
| current owner capability | the live owner label, generation, and authorized scope |
| ownership generation | increments on transfer or scope attenuation so stale handles fail closed |
| pending transfer receipt | explicit staged transfer that must commit or roll back |
| readiness witness | single-use proof that a protocol-critical check succeeded under one live owner generation |
| authority audit log | deterministic issuance, consumption, invalidation, rollback, rejection, and expiry history for first-class ownership/receipt/witness objects |
| terminal ownership reason | recorded reason when owner death or transfer failure forces cancellation or fault |
Default runtime rules:
- at most one current owner exists for one active session ownership unit
- transfer is explicit and uses a receipt
- readiness witnesses are single-use and generation-bound
- timeout activation stores and later expires the exact issued timeout witness rather than reconstructing timeout evidence from ambient clock state
- rollback is claim-specific, so failing one staged transfer does not tear down unrelated ownership state
- fragment-scoped ownership attenuates authority and does not imply full-session mutation rights
In the canonical capability taxonomy, this lifecycle section spans:
ownershipcapability state for the live ownertransitionlifecycle state for transfer receipts and semantic handoffevidencelifecycle state for readiness, cancellation, and timeout witnesses
Ownership Failure Mapping
Ownership failures map into session terminal behavior as follows.
| Ownership failure | Runtime behavior |
|---|---|
| owner death | Faulted { reason = "ownership owner ... died" } |
| abandoned transfer | Cancelled |
| failed transfer commit | Faulted { reason = "ownership transfer commit failed: ..." } |
| stale owner use | typed ownership error, fail-closed, no status change unless policy escalates |
| forged or reused readiness witness | typed ownership error, fail-closed, audit rejection record |
These mappings are implementation policy. They are not claims that the Lean theory proves host-runtime ownership outcomes directly.
Explicit Failure and Timeout Observability
Failure, timeout, and cancellation behavior is now explicit at the semantic-audit surface rather than inferred only from final status.
| Observable event | Meaning |
|---|---|
TimeoutIssued | a timeout occurrence became active for one site and issued a timeout witness |
CancellationRequested | a cancellation path was requested for one session with one cancellation witness |
Cancelled | the explicit cancellation path completed for one session using the same witness |
FailureBranchEntered | a typed failure branch became visible before terminal fault handling |
SessionTerminal | a session reached an explicit terminal state with a deterministic terminal reason |
Deterministic ordering rules:
- timeout activation is recorded as
TimeoutIssuedat the tick where the topology timeout becomes active - explicit cancellation emits
CancellationRequested, thenCancelled, thenSessionTerminal { Cancelled { ... } } - explicit abort emits
Aborted, thenSessionTerminal { Aborted { ... } } - explicit close emits
Closed, thenSessionTerminal { Closed { ... } }, thenEpochAdvanced - coroutine fault handling emits
FailureBranchEnteredbeforeFaulted
These events are part of replay-visible observability. Host integrations should not reconstruct this ordering indirectly from final statuses.
Canonical replay artifacts retain these lifecycle-visible events through semantic_audit_log. That surface also includes authority witness issuance/consumption and delegation completion records. The stricter lifecycle export for protocol-critical ownership, receipt, and witness objects is capability_lifecycle_audit_log(). Embedders and simulator harnesses should prefer these canonical audit surfaces over custom post-hoc reconstruction from raw logs.
Choreography-level timeout ... on timeout ... on cancel ... lowering maps its
protocol-visible timeout and cancellation outcomes to this same explicit lifecycle
event family.
Semantic Handoff and Transformation Obligations
Committed delegation is now exported as a first-class semantic handoff surface, not only as a low-level transfer receipt.
| Semantic object | Meaning |
|---|---|
SemanticHandoff | replay-visible owner transition with explicit revoked and activated owners |
TransformationObligation | bundle of fragment, operation, effect, witness, and publication obligations induced by the handoff |
Runtime rules for committed handoff:
- old-owner revocation and new-owner activation are explicit semantic events
- publication authority transfers from
revoked_owner_idtoactivated_owner_id - pending outstanding effects in the affected session are rebound to the new owner
- blocked outstanding effects are invalidated and become late-result-rejecting semantic artifacts
- affected
OperationInstancevalues gain an explicit dependency edge on the handoff receipt and are rebound or failed closed as required by status
TransformationObligation is the canonical export for the resulting
transformation-local obligations. Each obligation records:
- transformed fragments
- affected operation ids
- affected effect ids
- transported effect ids
- invalidated effect ids
- witness transport or invalidation policy
- publication revocation and activation owners
- scope, tick, and status
Late results arriving after invalidation or post-handoff revocation must fail closed. Embedders and simulator harnesses should treat these invalidation artifacts as canonical semantic audit data rather than reconstructing them from raw effect trace order.
Open Path
Open is executed by protocol machine::step_open. The instruction carries roles, local_types, handlers, and dsts.
Open admission checks enforce role uniqueness and full handler coverage across the opened role set. Arity must match between local_types and dsts.
On success the protocol machine allocates a fresh session, initializes buffers and local type entries, stores edge handlers, writes endpoint values to destination registers, and emits an Opened event.
Preferred host integration path:
- ownership-bearing open:
load_choreography_owned(...)
The open path immediately claims session ownership. It is the public integration route for hosts that will mutate session-local runtime metadata.
Hosts that need to materialize protocol-critical checks should issue explicit readiness witnesses through that ownership-bearing path.
Language-level check Effect.op(...) forms are expected to lower into this same
readiness/evidence machinery rather than inventing a separate host-visible
authority cache.
Type Advancement
The session store is the source of truth for endpoint type state. Runtime step logic calls lookup_type, update_type, update_original, and remove_type.
Recursive progression uses unfold_mu, unfold_if_var, and unfold_if_var_with_scope. This keeps Var continuations aligned with the active recursive body.
Buffers and Backpressure
Each directed edge has a BoundedBuffer configured by BufferConfig.
| Config axis | Values |
|---|---|
| Mode | Fifo, LatestValue |
| Backpressure policy | Block, Drop, Error, Resize { max_capacity } |
Signed send and receive paths use endpoint-specific verification keys. Auth tree fields track per-edge authenticated history.
Communication Replay-Consumption Lifecycle
Communication replay-consumption is separate from resource nullifiers and is enforced at communication boundaries.
| Phase | Runtime location | Behavior |
|---|---|---|
| Create sequence identity | send path (step_send) | Allocates sequence_no per session-qualified edge and writes it into signed transport payloads |
| Verify and consume | receive path (step_recv) | Verifies signature first, then applies replay policy (off, sequence, nullifier) on canonical identity |
| Record proof artifact | receive commit path | Appends pre-root/post-root artifact entries for recursive proof composition |
| Finalize on close | session close path | Prunes per-session replay counters from in-memory tracker state |
Policy semantics:
off: replay-consumption checks disabled.sequence: requires exact next sequence number per edge.nullifier: rejects duplicate canonical identities via consumed nullifier set.
Nullifier identity (ProtocolMachineConfig.communication_nullifier_identity):
sequence_bound(default): the nullifier covers the full canonical identity, includingsequence_no. A resent message with a fresh sequence number derives a fresh nullifier and is accepted.content_only: the nullifier covers the domain tag,sid, sender, receiver, step kind, label, and payload digest.sequence_nois excluded, so a resend of identical content is rejected withDuplicateIdentity. Protocols that legitimately repeat identical content on one edge and label must distinguish those payloads.
Hash model (ProtocolMachineConfig.communication_hash_model):
HashModel::DEFAULTuses the built-in portable pseudo-hash, which is deterministic but not collision resistant.- Security-sensitive embedders supply a cryptographic hash with
HashModel::new(id, hash_fn)orHashModel::from_verification_model::<V>(id). The model computes payload digests, receive nullifiers, and the replay root. - Serialized configs and machines record only the model id. Deserialization rejects any id other than the default, so a custom model is never silently replaced by the pseudo-hash. Re-supply custom models programmatically after restore.
Consumed nullifiers are never pruned. Session close drops only per-edge sequence counters, so closing a session cannot re-admit a consumed identity.
Close Path
Close is executed by protocol machine::step_close and then SessionStore::close.
The protocol machine first checks endpoint ownership for the closing coroutine. If ownership is valid, the store sets status = Closed, clears buffers and edge traces, and increments epoch.
Close emits Closed, SessionTerminal { Closed { ... } }, and EpochAdvanced observable events. There is no automatic draining loop in the current close implementation.
The close path is distinct from host-runtime ownership transfer. Endpoint/coroutine ownership for bytecode execution remains part of normal protocol-machine execution semantics. Host-runtime ownership governs who may mutate session-local host state at the embedding boundary.
Migration and Operations
Default behavior:
ProtocolMachineConfig.communication_replay_modedefaults tooff, preserving prior runtime behavior.- Existing workloads continue to run without replay-consumption enforcement until mode is changed.
Opt-in guidance:
- Local development: set
communication_replay_mode = sequenceto catch reorder/duplicate transport issues early. - Strict zk-oriented traces: set
communication_replay_mode = nullifierto force one-time identity consumption checks. - Content replay protection: combine
nullifiermode withcommunication_nullifier_identity = content_onlyand a cryptographiccommunication_hash_model.
Why this matters for consensus protocols:
- Protocol-level BFT logic can tolerate many faults. However, zk proofs of full executions require transport-level one-time consumption to prevent replay-equivalent transcript ambiguity.
- Enabling replay-consumption closes this gap between protocol safety and proof transcript soundness.
Configuration examples:
- Local test config:
ProtocolMachineConfig { communication_replay_mode: Sequence, ..ProtocolMachineConfig::default() } - CI parity fixture config: set
communication_replay_modein both cooperative and threaded runners and compare canonical replay fragments.
Risk notes:
- Performance overhead: replay checks add per-receive hashing and state updates.
- State growth: sequence maps and nullifier sets grow with message volume, session close prunes per-session sequence counters.
- Rollback/restart behavior: replay roots and consumption artifacts are included in canonical replay fragments and should be persisted with other replay evidence.
Related Docs
Required host integration coverage is part of just check-session-disposal: the
Rust-native inventory requires all 35 nonignored declarations, actual harness
discovery and successful execution across the two binary targets. The ownership
aggregate reuses that gate. Opaque-handle and metadata-substitution compile-fail
doctests additionally protect the public owned API. CI acceptance runs with
CARGO_INCREMENTAL=0. This does not change the host’s independent cleanup duties.
Explicit private worker-pool settlement
ThreadedProtocolMachine::shutdown_worker_pool consumes the original machine,
requests termination of its private Rayon pool and joins every original native
worker handle retained by the production spawn handler. The returned opaque
NativeWorkerPoolDisposal acknowledges those joins, including workers that
terminated with a retained original panic payload. It does not certify protocol
success, signed-terminal publication, or host resource settlement outside that
pool. Callers settle their session obligations before consuming the machine.
This host operation blocks until the actual workers exit. It cannot interrupt a
handler. Ordinary pool Drop remains an unacknowledged termination request.
Construction failures retain the actual Rayon error and partial native worker
handles in NativeWorkerPoolBuildFailure. Explicit settle_original_workers
joins those original handles and retains the first actual disposal receipt.
Repeated receipt observation neither restarts workers nor consumes panic payloads.
Both normal shutdown and failed construction use the same resident native-worker settlement custodian. It removes only the next original handle at the consuming join boundary, without holding its state mutex across the join. Actual worker panic results acknowledge their completed joins and retain original payloads. A panic of the join operation itself instead returns retained custody and the exact panic payload. Its consumed handle remains permanently unproven. Untouched original handles, completed-join counts and chronological independent causes survive for same-original retry. Settling those remaining workers cannot erase the earlier uncertainty or issue a whole-pool acknowledgment.
NativeWorkerPoolShutdownFailure::OriginalWorker retains the untouched machine.
NativeWorkerPoolShutdownFailure::Unproven retains the native settlement owner.
Failed-constructor settlement returns that same resident owner by reference on
refusal. Every settlement entry rejects an actual original-worker caller, including a still-live worker whose consumed handle has\nunproven settlement, or a
concurrent settler before consuming another handle. Progress snapshots expose
observations and retained causes, never reconstructed authority or cleanup ACK.
No external callback runs under the resident state mutex. The host operation is
still blocking and cannot interrupt a stuck worker or provide async Drop cleanup.
Eleven native component fixtures are required. Five additions exercise injected unwind at the canonical consuming join boundary, preservation of earlier and later worker panic causes with real pending workers, same-original remaining joins, actual-worker refusal, retained consumed-worker identity across unwind, and\nobservation during a pending join. Injection does not establish an actual operating-system deadlock refusal or settlement of the handle whose join unwound. Actual compilation and execution remain required.
Shutdown checks the actual original worker handles before requesting termination. A caller executing on one of those workers receives the unchanged original machine in the refusal result and must transfer it to an external owning context for same-original retry. This prevents self-join without discarding custody.
CARGO_INCREMENTAL=0. This does not change the host’s independent cleanup duties.
Typed Host Outcome State Commit
InvokeStep and Receive results update coroutine registers only after successful status and exact response-kind acceptance. The failed instruction does not advance its continuation, and an attached payload on a failure or blocked result does not replace the original register file. A raw successful exchange with the wrong response kind remains observable but is a Failed lifecycle result and cannot publish effect.succeeded.
This state-commit rule is not a receive-buffer rollback guarantee. It does not undo a dequeued message, validate a domain label, acknowledge a host future, or implement a generation-bound asynchronous completion. Those are separate protocol and host-resource obligations.
VM Invoke and Receive requests retain their actual configured register width as
optional instruction context. All VM state-replacing responses, including branch
Choose/Receive, must preserve that width before register replacement or effect-
success publication. This enforces the canonical Rust wf_coroutine_state
register-width invariant, not an inferred equal-length convention. Receive also
retains its actual destination and requires that register to exist. Branch receive
omits the destination because it performs no destination write, but retains width.
After existing signature, replay and payload validation, both VM receive backends reject an out-of-range instruction destination with OutOfRegisters before invoking the host receive handler. This preserves the VM fault taxonomy and prevents host receive mutation for that invalid destination. It does not roll back earlier dequeue or replay work. Handler-only wrappers omit both instruction contexts. Missing fields on older
serialized requests decode as absent and do not invent historical VM context. The original raw outcome remains observational evidence. This check
is not rollback of earlier buffer dequeue, replay consumption, clock or type work.
Optional observability retention cannot replace an original host failure: the
canonical recorded exchange supplies lifecycle status without a second synthetic
issue/complete cycle. Blocked remains a Blocked observation while the synchronous
instruction reports its existing blocked-contract fault.
Host-response acceptance is distinct from whole-instruction commit. In particular, Invoke may apply its accepted register state and host-success observation before persistence-delta application faults. No rollback of that state, emitted events, or earlier communication work is implied by the response-kind/destination guard. The prior cooperative owner could evict live enforcement inputs under diagnostic retention, while the prior threaded owner used untrimmed vectors. The canonical live/resident retention rule below repairs that storage boundary in both existing owners. Response/error preservation alone was insufficient. Neither correction claims nominal authority, a disposal acknowledgment or a Lean correspondence proof.
Serialized request context is an observation used to validate response shape, not proof of original request ownership or nominal authority. The replay handler selects by expected entry kind. It does not establish full incoming request identity. Actual VM producers attach current configured width before execution, including when replay supplies the response. Lean’s abstract effect-context step and spawn register copying are not a proof of this concrete Rust register ABI.
Canonical host exchanges also supply Acquire/Release/SendDecision outcome observations directly. Duplicate synthetic issue/complete cycles must not replace the original typed failure under optional diagnostic retention. Existing policy is preserved: cooperative Acquire Blocked waits on AcquireDenied, threaded Acquire Blocked reports its synchronous blocked-contract fault, and Release/SendDecision Blocked remain synchronous faults. The separate Send buffer WouldBlock producer retains its actual live capacity obligation: successful host decision is not queue admission, so that issue/complete pair is not a duplicate host outcome.
Live outstanding effects and their operation/progress records survive Disabled and Capped observability settings in the same canonical owner fields. Targeted session invalidation updates those originals before terminal-history pruning. Other live sessions remain untouched. Retention pruning is not a completion, cleanup acknowledgment, owner transfer or proof that a host future stopped.
Resident-session terminal records remain canonical inputs to agreement/WAL gates and cannot be discarded as history. Successful targeted reap or actual closed session reap establishes their nonresidency before terminal-history pruning. The next real WalSync gate must see the same original operation and evidence under Full, Capped1 and Disabled retention. Loss must not silently select the synthetic fallback operation or its default agreement level.
The composed required native ownership/disposal inventory contains 59 unique fixtures: the original 35 plus 17 strict-outcome/error, six live/resident retention and one cooperative full-queue/Disabled regression. The existing canonical checker requires their nonignored declarations, actual harness discovery and successful execution. Declarations alone are not passing evidence. Module filters can also execute required fixtures appearing in a narrower sub-suite, without changing the unique required count. Authority guard DSL/compiler/bridge fixtures and formal examples remain separate targets and obligations.
Authoritative-read reference identity
Readiness and cancellation witness numbers are local to a session. New semantic exports use opaque content references covering every exported AuthoritativeRead field except read_id itself: session, owner, kind, lifecycle, predicate, witness number, generation and reason. Fields have explicit None/Some discriminants and UTF-8 byte-length framing. Equal complete content may alias. Distinct complete content does not, including distinct rejected submissions for one witness. Operation and region read references use those same canonical producer IDs. These observations do not replace the actual issued witness or its consumption checks and confer no additional runtime authority.
The v1 semantic-object field schema remains readable: existing unambiguous serialized references keep their original bytes and can still be looked up. Lookup accepts repeated identical read records, but rejects different records under one reference rather than choosing the first. This includes ambiguous historical cross-session references. No migration reconstructs witness authority from a reference. Consumers must not parse read-reference strings as a stable wire grammar. Newly generated references are deliberately session-qualified. Output-condition references remain content-addressed by predicate and output digest: identical reobservations are allowed. Contradictory records refuse lookup. This does not assign an occurrence identity or claim that every check has a distinct audit event.
Timeout-witness identifier exhaustion
The cooperative topology timeout producer allocates witness numbers within its original machine, separately from session-local readiness/cancellation numbers. It refuses an exhausted successor before replacing an existing timeout, changing eligibility, or publishing timeout evidence. A returned topology batch is preflighted for all required witness numbers before any event in that batch is applied. The host callback’s already-observed outcome remains retained. Refusal does not undo that observation, renew a window or acknowledge pending work. The existing contract-violation error reports exhaustion. The threaded topology path currently records timeout horizons without issuing these TimeoutWitness objects. This correction does not invent threaded witness parity or new physical-time behavior. SimClock admission and original timeout horizons remain unchanged.
Finite session-identifier admission
Rust session identifiers are finite usize values. Fresh open reserves the maximum
value as its exhausted high-water mark, and rejects that boundary before inserting
a session or replacing an existing resident. Explicit-id open is fresh allocation,
not restoration: it rejects an id below the original high-water mark or already in
the resident map. Restore/resume retains the original complete store and counter.
It must not replay fresh open with a previous session id.
Both stores use the same checked admission rule. The cooperative exclusive borrow
and threaded existing map write lock cover counter admission plus insertion.
Failure preserves the original counter, resident session, capability and original
outcomes. Host loading returns the typed SessionAllocationError. Source Open
retains it in Fault::SessionAllocation, classified under the existing close fault
category and propagated through the canonical fault/trace path. No external handler
is called by this allocation boundary. Existing arity, role and handler checks
precede allocation as before. A public round can already have advanced its selected
clock and run topology ingress before dispatch. This refusal does not rewind those
actual observations. No Opened occurrence is produced for a refused allocation.
OwnedSession private construction prevents callers from fabricating a handle,
but its resident capability check is not an independent per-machine nonce. Fresh
session ids must therefore remain fresh. Resetting a session by overwriting its id
could reset its resident ownership generation, and must not be treated as a safe
way to retire or restore original custody. This finite-admission preparation is not
a proof of arbitrary serialized snapshot provenance or cross-machine owner scope.
Simulation Overview
This page is the top-level guide for telltale-simulator.
It describes the supported simulator surface at a high level.
Detailed behavior lives in the focused pages linked below.
Scope
The simulator runs projected local types on telltale-machine.
It adds deterministic middleware for budgeted adversaries, network behavior, property monitoring, checkpointing, and replay artifacts.
It also provides a harness API for external integration testing.
The authoritative replay lane is explicit: fresh exact runs use run_with_scenario(...), canonical exact reproduction uses run_canonical_replay(...), exact non-durable checkpoint resume uses resume_with_checkpoint_artifact(...), and durable scenarios resume through resume_with_durable_checkpoint_artifact(...) with typed WAL and evidence artifacts. The simulator also exposes a dedicated durability assurance surface for fault-injecting WAL backends, deterministic crash/recovery comparisons, and durable property monitoring.
On-disk checkpoint and replay bundles now use the typed PersistedReplayArtifact contract rather than ad hoc raw CBOR machine dumps.
Key Concepts
The simulator wraps the protocol machine defined in telltale-machine.
The protocol machine owns scheduling and session-type enforcement.
The simulator adds middleware layers (adversaries, network, properties, checkpoints) and environment dynamics (fields) around that core.
See Protocol Machine Architecture for the underlying execution model.
Durable agreement WALs and evidence-scoped recovery metadata remain authoritative machine/runtime artifacts.
Simulator reports and viewer projections should consume typed durable artifacts rather than minting a parallel simulator-local durability state model.
ObsEvent is the protocol machine’s trace of communication actions such as sends, receives, choices, and offers.
Scenario execution order, property monitoring, and replay artifacts all operate over this event stream.
FixedQ32 is the fixed-point numeric type (Q32.32) used for all simulator state values including field state, network parameters, and property thresholds.
Quoted decimal strings like "1.5" are the safest TOML representation.
ProtocolMachineSemanticObjects is the typed introspection snapshot the protocol machine exports after execution.
It contains operation instances, outstanding effects, semantic handoffs, authoritative reads, materialization proofs, canonical handles, publication events, and progress contracts.
Replay artifacts and post-run analysis consume this bundle directly.
Field and Environment Models
Fields are the simulator’s abstraction for deterministic environment-dynamics evolution.
A field model defines how role-local numeric state changes when the protocol machine invokes EffectHandler::step, how an effect handler is constructed, and how default per-role initial states are derived.
See Simulation Fields for the FieldModel trait, built-in field families, and environment extension hooks.
Topology, medium behavior, mobility, capability limits, and link admission live beside the field layer as separate environment hooks.
The shared execution core consumes EnvironmentSnapshot and emits EnvironmentTrace without baking domain-specific naming into core Scenario.
Execution and Theorem Profiles
Execution policy is explicit through Scenario.execution, which separates backend choice from scheduler policy, scheduler concurrency, and worker-thread count.
The simulator also exposes a separate theorem/profile layer through Scenario.theorem.
That separation lets one execution be interpreted under different theorem-side contracts without changing the runtime behavior itself.
See Simulation Scenarios for the full schema and backend resolution rules.
Reporting and Analysis
ScenarioStats includes theorem-native progress, reconfiguration accounting, and adversary budget summaries as separate fields.
Replay artifacts retain the resolved adversary program and budget-consumption history so theorem-side assumption failures are inspectable after the fact.
ScenarioResult.analysis.normalized_observability provides the companion envelope-aware analysis view for order-insensitive and footprint-aware comparison.
Batch, sweep, and approximation manifests now also record execution-regime classification instead of requiring downstream tools to infer it indirectly.
The shared viewer stack sits directly on top of those artifacts.
telltale-viewer owns the pure model/query/command layer, telltale-ui owns the portable Dioxus shell and reusable components, and telltale-web owns the browser packaging.
The preferred human-facing inspection path is now the shared viewer rather than ad hoc textual replay output.
That viewer now exposes:
- a graph workspace for choreography, instantiated protocol, execution timeline, and branch-lineage projections
- deterministic step-forward, step-backward, and jump-to-step time travel
- typed branch create/update/delete flows
- an insight workspace for regime display, watch expressions, run diff, causality, provenance, bookmarks, and archive reload
- semantic comparison and first-divergence analysis over generic simulator artifacts
- theorem-aware counterexamples for comparison and theorem-eligibility failures
- deterministic sweep and suite exploration with drill-down into runs and branches
- typed effect inspection and mocked rerun requests
- deterministic minimization summaries and downstream extension slots
Decision and Approximation Modules
The decision module provides offline theorem-facing checks that return structured certificates and counterexamples for coherence, subtyping, capacity predicates, and theorem-profile eligibility.
The approximation module provides non-authoritative analysis runs for batched_stochastic, mean_field, and continuum_field families.
Approximation artifacts declare an approximation family, theorem-side scope, and explicit non-goals.
Nested distributed simulation now publishes its own explicit observed-only manifest classification rather than remaining unclassified.
That observed-only classification now travels through DistributedRunResult, not a manifest-only side accessor.
Quick Start
Use SimulationHarness with a HostAdapter implementation and a HarnessSpec.
#![allow(unused)]
fn main() {
let adapter = FieldAdapter::from_scenario(&spec.scenario)?;
let harness = SimulationHarness::new(&adapter);
let result = harness.run(&spec)?;
assert_contracts(&result, &ContractCheckConfig::default())?;
}
This path runs protocol-machine execution, scenario middleware, replay capture, and post-run contract checks. It is the recommended integration lane for external projects.
DirectAdapter is for hosts that already own the EffectHandler.
FieldAdapter::from_scenario(...) constructs the handler and initial states from built-in scenario field parameters.
See Simulation Fields for custom FieldModel integration via FieldAdapter::new(...) and FieldAdapter::from_boxed_model(...).
Generated Effect Helpers
The simulator exposes generated effect-family helper types under telltale_simulator::generated, such as GeneratedEffectScenario.
Callers obtain a builder via GeneratedEffectScenario::builder() and chain outcome declarations before running.
These helper APIs sit beside the harness API rather than inside it, and their helper reports intentionally do not expose theorem profiles, checkpoints, normalized observability, or other authoritative simulator-only fields.
Document Map
- Simulation Runner: execution mechanics, stats, harness API, batch/sweep, distributed simulation
- Simulation Scenarios: TOML schema, adversaries, reconfiguration, network, properties, checkpointing
- Simulation Fields: field model trait, built-in families, environment extension hooks
- Simulation Viewer: pure artifact envelopes, query/command boundary, branch patch model
- Simulation Viewer Webapp: Dioxus shell, graph/time-travel workspace, run-insight workspace, ownership markers, testing split, local development
CLI
Use the simulator runner binary through just for CI-friendly JSON output.
just sim-run artifacts/sim_integration.toml
This command runs one scenario through the simulator entrypoint and emits the same authoritative artifacts the harness APIs produce.
Related Docs
Simulation Runner
This page documents runner behavior in telltale-simulator.
It covers traces, runner entry points, stats, harness API, and scenario round order.
Core Data Types
Trace is the sampled role-state output container.
Each StepRecord stores one role snapshot at one sampled step.
ScenarioResult adds property violations, replay artifacts, and run statistics.
#![allow(unused)]
fn main() {
pub struct StepRecord {
pub step: u64,
pub role: String,
pub state: Vec<FixedQ32>,
}
pub struct Trace {
pub records: Vec<StepRecord>,
}
pub struct ScenarioResult {
pub trace: Trace,
pub violations: Vec<PropertyViolation>,
pub replay: ScenarioReplayArtifact,
pub stats: ScenarioStats,
}
}
step is a simulator sampling index.
state contains the numeric portion of the coroutine register file.
The runner skips the first two reserved registers and samples field-backed state starting at register 2.
ScenarioReplayArtifact contains the resolved adversary schedule, observable events, effect traces, semantic audit records, ProtocolMachineSemanticObjects, the canonical reconfiguration trace, and the canonical EnvironmentTrace.
These artifacts support deterministic replay and post-run validation.
PersistedReplayArtifact is the typed on-disk wrapper for replayable run data and exact checkpoint artifacts.
Runner Entry Points
The runner exposes four main entry points.
run(...)executes one choreography and returns one sampled trace.run_multi_session_canonical(...)executes multiple choreographies on one canonical protocol machine and returns one trace per input choreography.run_with_scenario(...)executes one choreography with scenario middleware and returnsScenarioResult.run_canonical_replay(...)re-executes one scenario through the authoritative canonical replay lane even when the original scenario was run under another exact backend.
Use run_with_scenario(...) when adversaries, network behavior, properties, checkpoints, or replay artifacts are required.
Use run_canonical_replay(...) when exact artifact reproduction through the canonical lane matters.
Use the smaller entry points when only sampled state traces are needed.
ScenarioStats.execution_regime records the proof-side concurrency class for the resolved run.
canonical_exact means single-step cooperative execution.
threaded_exact means threaded execution at concurrency 1, which is theorem-equal to canonical.
threaded_envelope_bounded means threaded execution at concurrency greater than 1, which is only authoritative modulo the declared envelope.
Stats and Summaries
Theorem Progress
ScenarioStats.theorem_progress reports theorem-native quantities that summarize the run in terms of the weighted-measure and scheduling-bound proofs.
Depth is the sum of remaining session-type steps to end across all active sessions.
Buffer is the total pending message count across all session buffers.
The weighted measure is W = 2 * depth + buffer, a composite termination metric that strictly decreases on productive steps.
The reported fields are initial_weighted_measure, initial_depth_budget, productive_step_count, remaining_weighted_measure, weighted_measure_consumed, and critical_capacity.
Critical-capacity phase classifies whether productive steps stayed below, at, or above the initial depth budget.
This classification is unsupported for recursive protocols.
Other Summaries
ScenarioStats.scheduler_profile records the scheduler-facing report including implementation policy, productive exactness, total-step mode, and envelope status.
ScenarioStats.reconfiguration_summary reports applied, pure, and transition operations with transition budget consumed.
ScenarioStats.adversary_summary and ScenarioStats.assumption_diagnostics report adversary activation, budget consumption, and theorem-side assumption failures.
Observability Comparison
compare_observability(...) reports one of three relations: exact_raw_match, equivalent_under_normalization, or safety_visible_divergence.
Normalization is order-insensitive over session-normalized observable events and canonical reconfiguration footprints.
Harness API
SimulationHarness is the stable integration path for external projects.
It runs HarnessSpec or HarnessConfig through a HostAdapter.
HostAdapter provides an EffectHandler, optional initial states, optional environment models, and a result validation callback.
DirectAdapter wraps an existing EffectHandler.
FieldAdapter derives initial states and constructs the handler from scenario field parameters.
See Simulation Fields for field adapter variants and custom FieldModel integration.
Batch and Sweep
run_batch(...) and run_batch_with(...) run many HarnessSpec values concurrently while preserving result order.
BatchRunResult.manifest records one resolved execution-regime plus theorem-profile entry per input spec.
run_sweep(...) extends batch execution into deterministic parameter sweeps over seed, capacity_budget, scheduler_profile, reconfiguration_program, and adversary_budget.
SweepRunResult.manifest records parameter bindings, execution-regime classification, theorem profiles, eligibility witnesses, and capacity-predicate reports per expanded run.
Use compare_sweep_results(...) to diff experiment families by theorem eligibility and productive-step deltas.
Distributed Simulation
DistributedSimBuilder::execution_contract(...) accepts a NestedExecutionContract for outer scheduler concurrency plus inner rounds-per-step.
That outer/inner VM contract is part of simulation semantics, not a worker-pool tuning knob.
DistributedSimulation::run(...) now returns DistributedRunResult, which carries the observed-only classification manifest plus outer-VM trace data and per-site nested results in one aligned report surface.
Sampling and Step Mapping
The simulator uses explicit round-based sampling.
If steps > 0, the runner records an initial sample at step 0 before the first protocol-machine round.
Each subsequent completed round records one additional sample.
If no samples were produced during execution, the runner emits one fallback sample at the last requested step index.
Scenario Execution Order
Scenario runs and replay share the same execution core and use a fixed per-round order for determinism.
- Compute
next_tickfrom the protocol-machine clock. - Activate due simulator reconfiguration operations from newly visible observable events.
- Advance the adversary program from newly visible observable events.
- Deliver due delayed adversary messages.
- When network middleware is active, route adversary-delayed messages through the network policy stage before they enter protocol-machine buffers.
- Deliver due network middleware queues.
- Update paused roles from active crash adversaries.
- Execute one protocol-machine round with the selected handler domain.
- Record one round-based trace sample when sampling is enabled.
- Run online property checks.
- Attempt checkpoint persistence when the interval policy triggers.
Checkpoint persistence is best-effort.
Serialization and file-write failures do not fail the run.
Persisted checkpoint files are typed PersistedReplayArtifact CBOR payloads.
Checkpoint resume through resume_with_checkpoint_artifact(...) restores middleware state, so adversary, network, and reconfiguration behavior continue exactly from the captured tick.
Helper Boundary
Generated effect-family helpers remain non-authoritative support APIs.
GeneratedEffectSimulationReport is intentionally narrow: it exposes the scripted helper scenario, semantic objects, and semantic audit log through helper accessors, but not theorem profiles, replay contracts, normalized observability, or checkpoint data.
Local Workflow
Repository-wide CI remains just ci-dry-run.
For staged diffs that are restricted to the simulator subsystem, the narrower local gate is now just check-simulator-subsystem-staged.
That path still enforces Rust formatting, simulator compile/test coverage, and simulator-doc link checks without being blocked by unrelated pre-existing breakage elsewhere in the workspace.
Determinism and Reproducibility
Simulator randomness is scoped to SimRng, seeded from scenario.seed and currently backed by ChaCha8.
The protocol machine remains deterministic given the same handler outcomes and scheduler inputs.
The canonical backend remains the authoritative replay and debugging lane.
Record ordering is stable within each sampling pass. Replay artifacts preserve the observable, semantic, and reconfiguration data needed for deterministic post-run inspection.
Related Docs
Simulation Scenarios
This page documents scenario configuration and middleware behavior. It covers the TOML schema, budgeted adversary declarations, first-class reconfiguration, network modeling, properties, checkpointing, and current limits.
Scenario Schema
Scenario is parsed from TOML and drives run_with_scenario(...).
seed defaults to 0.
field is optional and is only required by built-in field-driven surfaces such as FieldAdapter::from_scenario(...), derive_initial_states(&Scenario), and the simulator CLI binaries.
Execution defaults are resolved through Scenario.execution.
backend = "auto" resolves to the authoritative canonical lane with scheduler_concurrency = 1 and worker_threads = 1.
Theorem-facing interpretation is configured separately through Scenario.theorem.
#![allow(unused)]
fn main() {
pub struct Scenario {
pub name: String,
pub roles: Vec<String>,
pub steps: u64,
pub execution: ExecutionSpec,
pub seed: u64,
pub network: Option<NetworkSpec>,
pub field: Option<FieldSpec>,
pub reconfigurations: Vec<ReconfigurationSpec>,
pub adversaries: Vec<AdversarySpec>,
pub properties: Option<PropertiesSpec>,
pub checkpoint_interval: Option<u64>,
pub theorem: TheoremProfileSpec,
pub durability: DurabilitySpec,
pub extensions: BTreeMap<String, toml::Value>,
}
}
network, field, reconfigurations, adversaries, properties, checkpoint_interval, durability, and extensions are optional.
#![allow(unused)]
fn main() {
pub struct ExecutionSpec {
pub backend: ExecutionBackend,
pub scheduler_policy: SchedulerPolicySpec,
pub scheduler_concurrency: Option<u64>,
pub worker_threads: Option<u64>,
}
}
scheduler_policy selects the underlying protocol-machine scheduling discipline.
scheduler_concurrency controls how much scheduler work one simulator round may admit.
worker_threads controls physical parallelism for the threaded backend only.
Canonical execution requires both values to resolve to 1.
Threaded execution with scheduler_concurrency = 1 remains exact with respect to the canonical lane, while scheduler_concurrency > 1 is only authoritative modulo the declared concurrency envelope.
#![allow(unused)]
fn main() {
pub struct TheoremProfileSpec {
pub scheduler_profile: TheoremSchedulerProfile,
pub envelope_profile: TheoremEnvelopeProfile,
pub assumption_bundle: TheoremAssumptionBundle,
}
}
The theorem block does not change runtime execution.
It declares which theorem-side contract the caller wants the run to be interpreted under.
The simulator resolves that declaration against the actual execution regime and reports eligibility in ScenarioStats.theorem_profile and ScenarioReplayArtifact.theorem_profile.
#![allow(unused)]
fn main() {
pub struct DurabilitySpec {
pub mode: DurabilityMode,
}
}
durability.mode = "wal" declares that checkpoint resume is only valid when the caller also supplies typed durable WAL and evidence-cache artifacts through resume_with_durable_checkpoint_artifact(...).
Generic checkpoint resume fails closed for those scenarios so the simulator cannot silently ignore the durable contract.
For recursion-free scenarios, the simulator also derives theorem-native progress quantities in ScenarioStats.theorem_progress.
See Simulation Runner for definitions of the weighted measure, depth, buffer, and critical-capacity phase.
Scenario Example
name = "mean_field_adversary_window"
roles = ["A", "B"]
steps = 200
seed = 42
checkpoint_interval = 25
[durability]
mode = "wal"
[execution]
backend = "threaded"
scheduler_policy = "round_robin"
scheduler_concurrency = 2
worker_threads = 4
[field]
layer = "mean_field"
[field.params]
beta = "1.5"
species = ["up", "down"]
initial_state = ["0.6", "0.4"]
step_size = "0.01"
[network]
base_latency_ms = 20
latency_variance = "0.10"
loss_probability = "0.02"
[[reconfigurations]]
trigger = { at_tick = 50 }
action = { type = "link", from = "A", to = "B", enabled = false }
[[adversaries]]
id = "loss_window"
trigger = { at_tick = 75 }
action = { type = "withholding" }
budget = { total = 16, mode = "windowed", window_ticks = 8, max_per_window = 2 }
[properties]
invariants = ["no_faults", "simplex", "buffer_bound(0,16)"]
Quoted decimal strings are the safest TOML form for FixedQ32 values.
The parser also accepts compatible numeric representations.
Adversary Middleware
AdversaryInjector wraps the inner handler.
It manages trigger activation, budget accounting, delayed delivery, corruption, withholding, correlated bursts, and crash state.
Supported actions are withholding, timing_disturbance, corruption, crash, and byzantine_interference.
Supported triggers are Immediate, AtTick, AfterStep, Random, and OnEvent.
If a trigger declaration sets no trigger field, it defaults to Immediate.
An adversary must not set more than one trigger field at once.
The parser rejects multi-trigger declarations.
Trigger::AfterStep is evaluated against logical round count rather than raw tick count.
Adversary Budgets
Every adversary must declare a budget.
#![allow(unused)]
fn main() {
pub struct AdversarySpec {
pub id: Option<String>,
pub trigger: TriggerSpec,
pub action: AdversaryActionSpec,
pub budget: AdversaryBudgetSpec,
}
}
budget.total is the total disturbance or activation budget.
budget.mode can be activation (one-shot), independent (per-message Bernoulli), windowed (per-window correlated quotas), or correlated (burst-style windows).
budget.assumption_failure optionally states which theorem-side clause should fail if the budget exhausts.
Use adversaries for transport disruption and runtime failure only. Do not encode topology change, handoff, federation cutover, or mode change as adversaries.
Environment Extensions
Scenario.extensions is a domain-neutral namespace map for downstream environment config.
Core telltale-simulator does not interpret those values directly.
Instead a HostAdapter may inspect one namespace, build external environment models, and return them through HostAdapter::environment_models(...).
This is the supported path for downstream projects that need geometry, radio, mobility, or device-capability config without adding vertical-specific fields to core Scenario.
Reconfiguration Program
Scenario.reconfigurations is the first-class surface for simulator-visible topology and authority change.
Each entry has the same trigger vocabulary as adversaries, but it activates a semantic reconfiguration operation instead of a transport disturbance.
#![allow(unused)]
fn main() {
pub struct ReconfigurationSpec {
pub trigger: TriggerSpec,
pub action: ReconfigurationAction,
pub effect: ReconfigurationEffect,
}
}
Supported actions are:
link: update one directed link policydelegation: record an explicit delegation scope transfer between two roleshandoff: record an explicit handoff identifier plus revoked/activated rolesfederation: activate or clear a named communication grouping policymode_transition: record a mode change for one or more roles
effect.kind = "pure" records a pure reconfiguration and must not consume transition budget.
effect.kind = "transition_choreography" records a separate cutover budget and must declare budget_cost > 0.
The simulator validates reconfiguration footprints before execution. Unknown roles, duplicated federation members, same-source/same-target delegation, empty handoff ids, and zero-cost transition choreography are rejected at parse time.
Network Middleware
NetworkModel wraps AdversaryInjector when network simulation is enabled.
It applies federation connectivity checks, link overrides, latency sampling, loss sampling, and deferred delivery.
Per-link policies match directed (from, to) pairs.
network.links now describes the static baseline policy for a run.
Dynamic link updates happen through reconfigurations.
Reconfiguration-applied link overrides shadow the baseline policy for the affected directed edge only.
Loss is evaluated before latency scheduling. Dropped messages never enter the in-flight queue. Zero effective latency produces immediate delivery.
The three middleware surfaces serve distinct purposes: network defines baseline transport parameters, reconfigurations handles topology and federation cutovers, and adversaries injects budgeted disturbances with theorem-facing assumption diagnostics.
Property Monitoring
PropertyMonitor performs online checks by scanning newly appended observable events and machine state each round.
Built-in checks are:
NoFaults: verifies that no coroutine has entered a faulted state during execution.Simplex: verifies that all coroutine state vectors remain on the probability simplex (all components non-negative, sum to 1 within tolerance).SendRecvLiveness(sid, bound): verifies that every send in sessionsidis matched by a receive withinboundsession-local ticks. Uses per-session event counters rather than raw global ticks.TypeMonotonicity(sid): verifies that session-type depth never increases for non-recursive local types in sessionsid. Recursive types are skipped.BufferBound(sid, max): verifies that no buffer in sessionsidexceedsmaxpending messages.Liveness(name, precondition, goal, bound): a custom liveness check. Once the precondition predicate becomes true, the goal predicate must become true withinboundsteps. Predicates can beno_faults,simplex, tick comparisons, ordistance_to_equilibriumcomparisons.
Invariant strings are parsed by parse_property.
Predicate strings are parsed by parse_predicate.
Checkpointing and Replay
CheckpointStore snapshots protocol-machine state through the typed persisted replay contract at configured intervals.
When checkpoint_interval is set, run_with_scenario(...) writes PersistedReplayArtifact checkpoint files under artifacts/<scenario.name>/.
Replay loads one of those persisted artifacts and re-executes the same shared middleware loop used by fresh scenario runs.
resume_with_checkpoint_artifact(...) is the exact resume path because it restores both protocol-machine state and simulator middleware state from the captured artifact.
Scenario replay artifacts also retain the resolved adversary schedule, budget-consumption history, assumption diagnostics, and the canonical reconfiguration trace.
Checkpoint snapshots currently require the canonical backend.
Threaded scenario runs still emit replay artifacts but checkpoint serialization remains canonical-only, and scenarios that request both threaded execution and checkpoints fail validation instead of degrading later at run time.
Checkpoint persistence is best-effort: serialization or file-write failures do not fail the run.
CheckpointStore::last_persist_error() exposes the last recorded persistence error.
Current Limits
Generated effect-family helpers (e.g. record_return(), with_delay_ms(), record_stale_late_result()) are separate programmatic APIs.
They are not a TOML scenario feature.
They are also not yet wired into SimulationHarness.
Related Docs
Simulation Fields
This page documents the simulator’s field/environment-dynamics layer and the generic external environment boundary.
Field Layer
Telltale now treats field dynamics as one layer inside a broader environment model. The field layer is responsible for evolving shared fields, latent environment state, or node-local physical state when protocol-visible effects occur. It does not by itself decide topology, medium contention, mobility, or admission.
The simulator-facing Rust abstraction is FieldModel in rust/simulator/src/field.rs.
A FieldModel supplies a stable layer name, an EffectHandler via build_handler(), and default per-role initial states via derive_initial_states(roles).
The built-in scenario schema uses the serde-tagged FieldSpec catalog through Scenario.field.
Current built-in families are mean_field, hamiltonian, and continuum_field.
Adapter Integration
Use FieldAdapter::from_scenario(...) when built-in scenario field parameters should drive handler construction and default state derivation.
Use FieldAdapter::new(...) or FieldAdapter::from_boxed_model(...) when a host integration wants the harness to own a custom FieldModel.
The harness provides derive_initial_states(&Scenario) for the built-in schema and derive_initial_states_for_field_model(&dyn FieldModel, roles) for arbitrary implementations.
Simulator field handlers implement deterministic EffectHandler::step updates over fixed-point state.
The runner stores field state in coroutine registers starting at register 2.
Initial State Derivation
derive_initial_states(&Scenario) builds default per-role state vectors from the built-in field when present.
mean_field broadcasts one concentration vector to every role.
hamiltonian maps each role index to [position, momentum].
continuum_field assigns one scalar field value per role.
If a HostAdapter returns explicit initial states, the simulator never consults the built-in field catalog.
Environment Boundary
External projects should extend the simulator through the domain-neutral environment hooks in rust/simulator/src/environment.rs.
The shared execution core accepts five external model traits: TopologyModel for graph structure and reachability, MediumModel for link-level delay/loss/collision behavior, MobilityModel for node position updates, NodeCapabilityModel for per-node resource or radio constraints, and LinkAdmissionModel for connection-level admission decisions.
These hooks all receive the same EnvironmentSnapshot each round.
That snapshot exposes current tick, logical step, participating roles, field-backed node state, node poses, node capabilities, and potential links.
The shared execution core emits one canonical EnvironmentTrace of EnvironmentArtifact records for mobility updates, capability snapshots, reachability decisions, admission outcomes, and medium outcomes.
Extension Configuration
Scenario.extensions is the generic config hook for domain-owned environment configuration.
It is a namespaced map of arbitrary TOML values so external projects can carry their own config without adding vertical-specific fields to core Scenario.
Vertical-specific logic belongs in downstream crates, not in the core simulator schema.
Lean Mirror
Lean mirrors the executable field boundary under lean/Runtime/Simulation/Field.lean.
That module includes a Lean-native FieldModel, FieldHandler, built-in FieldParams catalog, and default initial-state derivation for the shipped field families.
It remains an executable parity layer rather than a mirror of Rust trait objects or serde-based scenario parsing.
Coverage
The main parity and extension lanes are:
rust/simulator/tests/field_handler_parity.rsrust/simulator/tests/environment_models.rsrust/simulator/tests/lean_reference_parity.rslean/Runtime/Tests/SimulatorParity.lean
environment_models.rs exists specifically to prove that topology, medium, mobility, capability, and admission hooks work through an external adapter without built-in domain knowledge.
Related Docs
Simulation Viewer
This page describes the typed model and service boundary for the simulator
webapp work.
It is intentionally about artifacts and application-service contracts first,
with only the minimum crate-split notes needed to explain how telltale-ui
and telltale-web consume it.
Scope
The simulator web stack is split into three layers:
telltale-viewer: pure artifact loading, schema/version handling, branch patch types, query/command models, and the application-service boundarytelltale-ui: the portable Dioxus UI core that consumes thetelltale-viewermodel layertelltale-web: the thin WASM/browser shell aroundtelltale-ui
telltale-viewer keeps browser APIs and renderer-local state out of the
authoritative artifact boundary.
telltale-ui and telltale-web are now present, but they are intentionally
downstream of this crate split rather than new semantic authorities.
The viewer layer now also owns the typed command builders used by the
graph/time-travel workspace.
That keeps branch patch construction in telltale-viewer and lets
telltale-ui stay an Observed command-emission shell instead of directly
authoring patch details.
Artifact Families
The viewer is built around the simulator artifacts that already exist in
telltale-simulator.
The initial typed envelope supports these families:
- scenario bundles
- decision reports
- environment traces
- reconfiguration traces
- sweep manifests
- sweep diff reports
- scheduler comparisons
- observability comparisons
- approximation manifests
- contract-check reports
- semantic comparison results
- theorem-aware counterexamples
- deterministic sweep reports
- experiment-suite reports
- effect-trace artifacts
- minimization results
Each saved artifact uses one stable format marker and one explicit schema version so later UI layers can reject unsupported files cleanly.
Query and Command Split
The model layer uses an explicit command/query split.
Queries:
- load artifact inventories
- load one artifact
- load scenario summaries
- load branch-lineage projections
- request graph projections
- search loaded artifacts
- select one historical inspection position
- compare two artifacts semantically
- find the first meaningful divergence between two artifacts
- load theorem-aware counterexamples
- load sweep explorer and experiment-suite reports
- load typed effect traces
- load deterministic minimization results
- load downstream extension manifests
Commands:
- import one artifact
- create a branch
- update a branch
- delete a branch
- request a rerun for a branch
- execute a deterministic archived-artifact sweep
- execute a baseline-vs-candidate experiment suite
- request a mocked rerun with typed effect overrides
- request deterministic minimization for a branch
- register downstream extension manifests
This is deliberate. The UI should inspect authoritative artifacts through typed queries and emit typed branch/scenario patch commands rather than mutate run state directly.
ViewerReport is the first canonical report model built from that query
surface.
It lets the UI render artifact inventories and scenario summaries without
opening downstream-specific assumptions in the Dioxus layer.
The newer reusable tooling surfaces build on that same boundary:
SemanticComparisonResultfor exact, normalization-equivalent, and safety-visible divergence classificationTheoremAwareCounterexamplefor reusable theorem and divergence witnessesDeterministicSweepReportandExperimentSuiteReportfor archived sweep and baseline/candidate execution familiesEffectTraceArtifactandEffectOverrideSpecfor effect inspection and mocked rerunsMinimizationRequestandMinimizationResultfor deterministic witness reductionViewerExtensionManifestfor downstream overlays, graph annotations, and time-travel extension panels
Branch Patches
Branch editing is patch-based.
The model layer defines ScenarioBranchPatch and ScenarioPatchOperation
instead of exposing direct mutable scenario state to the UI.
The current patch surface includes:
- scenario metadata updates such as name, seed, and steps
- execution policy updates
- theorem-profile replacement
- extension updates
- reconfiguration replacement
- full scenario replacement when the caller already owns a canonical scenario
Later phases can extend the patch language, but the UI should continue to emit typed deltas rather than mutate simulator state in place.
Comparison and Counterexamples
Semantic comparison is now a first-class viewer concern.
The pure model layer exposes SemanticComparisonRequest,
SemanticComparisonResult, and SemanticDivergencePoint.
Those types deliberately sit above raw mismatch blobs:
- exact raw match stays distinct from normalization-equivalent match
- classification-only changes remain visible without being misreported as raw divergence
- the first semantically meaningful divergence is queryable directly
TheoremAwareCounterexample composes with that comparison surface.
It can be derived either from theorem-eligibility failure on one artifact or
from a comparison that leaves the expected semantic regime.
The viewer crate marks the helper constructors for these artifacts as
authoritative sources so the browser shell does not mint them locally.
Sweeps, Effects, and Minimization
The pure model layer now also owns:
- deterministic archived-artifact sweep execution through
ExecuteSweep - baseline-vs-candidate experiment suites through
ExecuteExperimentSuite - typed effect inspection via
EffectTraceArtifact - typed mocked rerun requests via
EffectOverrideSpec - deterministic branch minimization via
MinimizationRequest
These are still command/query surfaces, not browser-owned mutation. The browser shell issues requests, and the application service owns the resulting artifacts and summaries.
Application Service
ViewerApplicationService is the stable boundary between the pure model layer
and the future UI/web layers.
The first implementation is InMemoryViewerService.
Even though the initial service is in-process, the boundary is already shaped like a real application service:
- queries return typed model projections
- commands mutate branch/workspace state explicitly
- the browser shell does not become the semantic owner of simulation state
Ownership and Portability Notes
The shared viewer stack borrows Aura’s split and ownership discipline:
telltale-viewerremains pure and renderer-freetelltale-uiis anObservedsurface over authoritative artifactstelltale-webis the thin browser shell and ownsDioxus.toml,index.html, Tailwind packaging, and future browser-only APIs
The first shared ownership-marker set comes from telltale-macros:
#[observed_only]#[actor_owned("...")]#[authoritative_source("...")]#[strong_reference("...")]#[weak_identifier("...")]
These markers are intentionally lightweight. They validate the declared boundary shape and are backed by compile-fail tests and repo-level boundary checks.
Downstream Integration
Downstream projects should consume the shared viewer stack by importing the stable artifact and extension contracts instead of forking the shell. The supported downstream integration surface is now:
- viewer artifacts and query/command types from
telltale-viewer - extension manifests through
ViewerExtensionManifest - extension slots for overview panels, graph annotations, time-travel panels, and insight panels
- the existing ownership markers where they define public viewer/webapp boundaries, while narrower repo-local lint scripts remain internal
Related Docs
Simulation Viewer Webapp
This page describes the current shared Dioxus webapp surface for simulator inspection. It covers the crate split, global layout, graph and run-insight workspaces, ownership discipline, testing split, and local development workflow.
Crate Split
The viewer web stack is intentionally split into three crates:
telltale-viewer: pure artifact envelopes, report/query/command models, branch patching, and the application-service boundarytelltale-ui: portable Dioxus UI core with the shared shell, reusable components, graph rendering, route/view state, and task-owner helperstelltale-web: thin browser shell withDioxus.toml,index.html, Tailwind packaging, and future browser-only integration
Only telltale-web may own browser APIs.
telltale-ui and telltale-viewer must stay portable.
App Ownership Model
The viewer is an Observed surface over authoritative simulator artifacts.
It may shape presentation, selection, and browser-local layout state, but it
must not become the semantic owner of simulation truth.
The current ownership markers used by the shared viewer stack are:
#[observed_only]for render/projection helpers#[actor_owned("...")]for long-lived task/runtime helpers#[authoritative_source("...")]for the narrow surfaces that publish authoritative demo or derived outputs#[strong_reference("...")]and#[weak_identifier("...")]for typed reference/identifier boundaries
These are intentionally structural. The real semantic authority still lives in simulator artifacts and the application-service boundary.
Graph Workspace and Time Travel
The shared webapp now includes a graph workspace for deterministic structure and replay browsing. The graph page can switch between these projection families from the same authoritative artifact set:
- choreography structure
- instantiated protocol structure
- execution timeline
- branch lineage
Time travel is explicit. Users can:
- step forward and backward through deterministic execution history
- jump directly to a historical step
- switch projections without losing typed selection state
- search nodes, branches, and labels through the pure
telltale-viewerindex
Branch management is command-driven rather than mutation-driven. The graph workspace emits typed viewer commands for:
- branch creation from the active historical step
- branch update through a typed patch
- branch deletion
Those commands are minted in telltale-viewer, while telltale-ui remains an
Observed shell over the resulting command stream and graph projections.
Run Insight Workspace
The insight page is the companion debugging surface for one active run or branch. It currently renders:
- execution regime and theorem scheduler profile
- watch expressions over sampled semantic facts
- branch provenance and narrative annotations
- a run-diff summary for the active branch
- semantic comparison classification and the first divergence summary
- theorem-aware counterexamples
- causality/explanation rows derived from the execution timeline projection
- bookmarks for historical steps and branches
- archive reload status for exact artifact-set re-entry
- deterministic minimization summaries for reduced witnesses
This gives the viewer a first serious run-analysis lane instead of only a graph browser.
The archive controls are intentionally part of the insight workspace rather than a separate browser-owned workflow. Reloading an archived artifact set preserves the deterministic viewer state, and any subsequent branch fork still goes back through the typed command path instead of mutating the archived run in place.
Sweep and Effect Workspaces
The shared webapp now also exposes:
- a sweep explorer page with filtering, outlier highlighting, and drill-down into the selected case
- a suite page section for baseline-vs-candidate regression thresholds and counterexample presence
- an effect page that renders typed effect traces and queued mocked rerun overrides
These pages still consume the same pure telltale-viewer query/command
surface.
The browser shell does not own sweep execution, suite comparison, effect
mocking, or minimization semantics.
It only drives the typed requests and renders the returned artifacts.
Downstream Handoff
Aura is unblocked to start integrating once it consumes these shared surfaces:
telltale_viewer::ViewerArtifactFileandtelltale_viewer::ViewerArtifactas the stable artifact envelopetelltale_viewer::ViewerReport,ViewerQuery,ViewerCommand, andViewerApplicationServiceas the query/command boundarytelltale_viewer::GraphProjectionandGraphProjectionKindas the graph projection contracttelltale_ui::TelltaleUiRoot,ViewerFrame,ViewerPage, andViewerWorkspaceas the portable Dioxus shell and shared workspace model
The intended downstream extension points are:
- artifact and insight overlays layered on top of the shared pages
- graph annotations and domain-specific badges that do not replace the authoritative projection contract
- downstream application-service implementations that feed the same query/command surface
- extension manifests that target overview panels, graph annotations, time-travel panels, and insight panels without requiring a shell fork
Downstreams should not fork the core shell just to add overlays. The shared Telltale crates now provide the minimum handoff surface Aura needs.
Testing Split
The viewer work uses an explicit three-way testing split:
- pure model tests in
telltale-viewer - ownership/compile-fail/boundary checks in
telltale-macrosandjust check-viewer-tooling-boundaries - Dioxus shell integration tests in
telltale-ui
The canonical local/CI recipe is just check-viewer-tooling.
That recipe runs the pure model tests, the UI shell tests, the thin web-shell
compile, and the boundary check.
Local Development
For the current shared web shell:
cd rust/web
npm install
npm run tailwind:watch
dx serve --platform web
The Dioxus watcher is configured to watch:
rust/web/srcrust/ui/srcrust/web/stylesrust/web/public
The browser shell currently loads the demo workspace through the typed application-service path so the shared shell, graph, and insight surfaces are all exercised from one entrypoint. That same demo path now also exercises sweep exploration, effect inspection, mocked rerun commands, minimization summaries, and downstream extension slots.
Simulator CLI Note
Textual replay output remains available for compatibility and quick terminal checks, but it is now explicitly a compatibility summary path. The shared viewer is the preferred human-facing inspection surface for graph navigation, time travel, branch management, and run insights.
Related Docs
Session Graph Search
telltale-search implements ARA*-style weighted graph search over Telltale
session graphs. It provides canonical search-machine semantics, deterministic
batch extraction, proposal normalization, and replay artifacts. The crate is
intentionally generic and free of simulator, viewer, browser, or
application-specific routing dependencies.
Scope
The crate owns the search algorithm, its determinism contract, and the capability vocabulary for scheduler profiles, fairness assumptions, and observable classes. Downstream crates provide domain-specific node and edge semantics, heuristic and cost policies, graph snapshots, and epoch updates.
Execution policy is explicit and separate from downstream search-problem semantics. Downstream control policy may choose scheduler profile, batch width, caching mode, and effort regime, but that execution-side choice does not redefine the search objective or candidate meaning.
telltale-simulator can project search runs through
project_search_run(...). telltale-viewer can project search artifacts
through project_search_artifacts(...). These integration layers are optional
and live above the crate boundary.
Allowed downstream variation includes:
- node and edge meaning
- heuristic and cost policy
- success/selection criterion
- reconstruction witness
- execution profile
Non-goal: the crate does not define downstream domain truth, candidate eligibility, or objective semantics.
Core Surface
SearchDomain defines the contract a downstream domain must implement:
successor generation, heuristic evaluation, snapshot identity, and optionally
custom authority-surface derivation through the blanket
SearchAuthorityPolicy hook.
SearchCost and its concrete implementation EpsilonMilli define the cost
algebra. EpsilonMilli uses fixed-point milli-precision with a u32 backing
store.
SearchQuery generalizes the built-in problem surface:
SingleGoalfor classic one-goal path searchMultiGoalfor any-of-N terminal searchCandidateSetfor selector-style best-candidate search
Release-facing callers should prefer the fail-closed constructors
SearchQuery::try_multi_goal(...) and
SearchQuery::try_candidate_set(...), which return SearchQueryError
instead of panicking on empty accepted sets. The panicking convenience
constructors remain available for internal happy-path call sites.
SearchDomain also owns the selected-result boundary explicitly:
accepts_terminal(...)for the built-in query adaptersselected_result_candidates(...)when admissibility or winner eligibility depends on discovered machine stateselected_result_semantics_class(...)to declare whether selected-result semantics are query-derived or domain-defined from discovered statereconstruct_selection_witness(...)andcompare_selected_solutions(...)for optional witness/export ordering
SearchMachine owns the frontier, parent map, selected solution, and budget
state. The historical incumbent vocabulary is still present for compatibility,
but the crate also exports selected-result aliases and accessors.
SearchMachine exposes step_once(...) for single canonical steps and
explicit invariant checks for partition discipline, parent-score coherence,
selected-result coherence, and batch legality. CanonicalBatch and Proposal
represent the min-key batch window and its normalized expansion results. The
built-in path-search adapters still reconstruct canonical paths.
Runtime Surface
run_with_executor(...) is the canonical host execution entry point.
SearchExecutionPolicy is the explicit execution-side runtime policy surface:
it carries scheduler profile, batch width, caching profile, and effort
profile. SearchRunConfig packages that execution policy together with the
declared fairness assumptions. validate_run_config(...) enforces fail-closed
checks for unsupported execution-policy variants, executor-kind mismatches,
fairness mismatches, and batch-width mismatches before execution begins. The
executor strategy is supplied separately via the ProposalExecutor
implementation and does not define search-problem semantics.
Current execution-policy status:
- stable and implemented:
EphemeralPerStepcaching andRunToCompletion - stable and implemented with a weaker result contract:
SchedulerStepBudget(n), where one budget unit is one full canonical batch/service step and runs stop only at batch barriers - explicitly rejected and outside the stable import posture:
IncrementalReuse
The supported execution-policy matrix is:
| Scheduler profile | Executor kind | Batch width | Required fairness | Approximation contract |
|---|---|---|---|---|
CanonicalSerial | serial | 1 | DeterministicSchedulerConfluence | Exact |
ThreadedExactSingleLane | native parallel | 1 | DeterministicSchedulerConfluence | Exact |
BatchedParallelExact | native parallel | > 1 | DeterministicSchedulerConfluence | CertifiedWindowExact |
BatchedParallelEnvelopeBounded | native parallel | > 1 | EventualLiveBatchService, NoStarvationWithinLegalWindow | EnvelopeBounded |
SchedulerStepBudget(n) overlays that matrix as a stable effort mode and
changes the result contract to BudgetedAnytime. IncrementalReuse remains
outside the stable import posture and is rejected fail-closed.
ProposalExecutor is the trait for batch expansion strategies.
SerialProposalExecutor runs proposals sequentially.
NativeParallelExecutor uses rayon when the multi-thread feature is
enabled. Constructing NativeParallelExecutor without the feature fails
explicitly rather than silently degrading.
The runtime emits SchedulerArtifact for scheduler classification and the
declared execution policy,
SearchFairnessArtifact for profile-scoped fairness evidence,
SearchResultBoundArtifact for generic selected-result discovery and quality
reporting, with SearchRouteBoundArtifact retained as the path-search
compatibility alias, and
packages them together with state traces and progress summary in
SearchExecutionReport. SearchStateArtifact provides per-round state and
certificate traces for exact-profile correspondence checks. SearchFullStateArtifact
is the Rust-facing extraction boundary for the full-machine refinement lane:
it includes OPEN, CLOSED, INCONS, g_score, parent, incumbent,
epsilon, epoch/snapshot, last-batch nodes, and full commit/publication traces.
SearchFairnessArtifact is the proof-claim carrier: it records the profile
claim class, theorem-side certificate, and whether exact
commit-trace/state-slice/observation equivalence to canonical serial is part
of the claimed surface for that profile.
Generic selected-result bounds no longer carry a distinguished goal anchor.
Optional path-goal discovery helpers are now split out under
SearchPathProblemDiscoveryArtifact (route_bounds.path_problem), which is
present only for built-in single-goal path queries.
SearchResultSummary is the generic selected-result summary exported in result
bounds. Path-search-specific summary data now lives under its optional
path_summary helper, with SearchRouteSummary retained as the compatibility
alias for that path-specific wrapper rather than the primary generic summary.
The route-oriented compatibility aliases are intentionally narrowed to
telltale_search::compat (and telltale_search::runtime::compat for
runtime-only imports).
The theorem-pack surface is also classified conceptually into:
- generic machine/refinement/fairness theorems
- generic selected-result/result-bound theorems
- path-problem-specific completeness/discovery theorems
Rust currently exposes helper classification for this split through
classify_theorem_problem_class(...) and
theorem_inventory_problem_classes(). The theorem-pack artifact now exports
that split directly in inventory_problem_classes alongside
inventory_support_classes. Lean mirrors the same distinction in the search
theorem inventory docs.
Admission and Capabilities
SearchDUser and SearchCertifiedCapability define the admission vocabulary.
check_capability_containment(...) verifies that a user’s certified
capabilities satisfy the requirements of a given theorem/claim class,
scheduler profile, fairness assumption set, and observable class list. The
check is fail-closed and returns
structured AdmissionRejectionReason values on mismatch.
The capability vocabulary covers five dimensions: SearchClaimClass,
SearchSchedulerProfile, SearchFairnessAssumption,
SearchDeterminismMode, and SearchObservableClass. These are first-class
observable artifacts and comparison inputs throughout the runtime and replay
surfaces.
Replay and Epoch Reconfiguration
Replay semantics are fail-closed. SearchReplayArtifact captures the canonical
round commits, epoch schedule, snapshot schedule, phase schedule, and batch
schedule from a prior run. replay_observation(...) re-derives the final
observation from canonical round commits and rejects drift against the stored
artifact when selected-result semantics are query-derived. For domains that
declare DomainDefinedFromDiscoveredState selected-result semantics, replay
fails closed with UnsupportedSelectedResultSemantics instead of silently
falling back to any compatibility goal anchor.
EpochReconfigurationRequest and commit_epoch_reconfiguration(...) handle
graph-epoch transitions. Reconfiguration now carries an explicit
SearchReseedingPolicy:
StartOnlyresets the frontier and reseeds only from the canonical startPreserveOpenAndInconspreserves the live frontier plus the retained parent closure needed for witness reconstruction
Replay and state artifacts record the applied reseeding policy on runs that cross epoch barriers. Runtime failures during proposal generation do not consume the canonical batch.
Observation and Comparison
SearchObservationArtifact captures the observable surface of a completed
search run. compare_observations(...) compares two artifacts against a
determinism mode and observable class list. Under ModuloCommutativity mode,
normalized commit traces are compared as multisets rather than sequences.
Observable classes still use the historical incumbent/path terms for backward
compatibility on deserialization, but the primary exported names are now the
generic selected-result terms. Observable classes include
SelectedResultCost, SelectedResultWitnessIdentity,
SelectedResultPublicationTrace, NormalizedCommitTrace,
GraphEpochTrace, SchedulerProfileTrace, and ProgressAccounting.
Profiling Surface
The repo now includes an explicit profiling matrix for the generalized search surface:
- end-to-end Criterion workloads in
rust/search/benches/search_profiles.rs - phase microbenchmarks in
rust/search/benches/search_machine_phases.rs - runtime-overhead microbenchmarks in
rust/search/benches/search_runtime_overheads.rs - structural counters in
rust/search/benches/support.rs - saved-profile helpers in
scripts/ops/profile-search-bench.sh - convenience entry points:
just profile-search-chainjust profile-search-rebuildjust profile-search-threadedjust profile-search-machine-onlyjust profile-search-artifactjust profile-search-invariants
The benchmark harness reports frontier growth, batch count, proposal churn, duplicate elimination, commit count, rebuild count, and publication count so performance work can separate generic machine cost from executor and artifact overhead. The profiling surface should be used before making search-structure optimizations, and hotspot assumptions should not be treated as stable until after the generalized query/result/authority surfaces are in place.
The profiling split is intentionally layered:
- algorithm/search work:
search_profilesand the machine-only runtime-overhead bench - executor/scheduler overhead:
compare
runtime_machine_only_chain_256againstruntime_executor_serial_chain_256 - theorem/replay/artifact work:
runtime_observation_export_chain_256andruntime_full_state_export_chain_256 - invariant-checking cost:
runtime_invariant_check_frontier_512
Current review findings:
- replacing
next_batch(...)full-frontier sorting with a naive two-pass min-score scan regressed bothphase_next_batch_frontier_512and the end-to-endserial_chain_256workload, so that change was rejected - duplicate-heavy and rebuild-heavy workloads still show normalization churn and rebuild work as stronger optimization candidates than batch extraction
normalize_proposals(...)was then changed from full sort-plus-dedup to per-target best-selection after the duplicate-pressure phase bench isolated that stage as the stronger hotspot- on
phase_normalize_proposals_duplicate_pressure_64x32, that change reduced the measured phase time from roughly120.75-132.44 µsto44.59-56.46 µs - the improved workload class is generic machine normalization cost on duplicate-heavy frontiers, and it did not require theorem or artifact-schema changes
- any further optimization should be justified against those measured workload classes, not assumed from the frontier implementation alone
The check-search-bench-tooling just target is the regression guard for this
surface. It syntax-checks the saved-profile script and compiles the search
bench targets in both default and multi-thread configurations without
running them. just check-search-tooling now calls it, so the benchmark and
saved-profile surface is part of the normal search verification lane rather
than an optional local-only check.
Lean Proof Support
The Lean formalization lives in Runtime.Proofs.Search. The public proof
surface is now split explicitly into a curated generic-machine barrel
(Generic) and the path-problem-specific completeness/publication family
(PathProblems), both re-exported through API. The supporting modules build
from frontier vocabulary through one-step fairness and artifact refinement into
full-machine semantics, liveness, completeness, and approximation-contract
inventory rows.
The theorem inventory now mirrors the Rust three-way claim split:
generic_machinegeneric_selected_resultpath_problem_specific
The per-profile fairness claim taxonomy determines what the runtime can assert on behalf of each scheduler profile:
| Profile | FairnessClaimClass | Proof basis |
|---|---|---|
canonicalSerial | exactOneStep | proved unconditionally |
threadedExactSingleLane | exactOneStepUnderRefinement | proved via merge-refinement to canonical (any lane width, any lane completion order) |
batchedParallelExact | premisedWindowBounded | proved under BatchedExactWindowCertificate |
batchedParallelEnvelopeBounded | premisedWindowBounded | proved under BatchedEnvelopeWindowCertificate |
The batched profiles carry certified-window fairness, not end-to-end replay equivalence. What each profile claims is exactly what Lean justifies.
Proof Vocabulary (Core)
Runtime.Proofs.Search.Core establishes the foundational vocabulary:
FrontierEntry- a(node : Nat, priority : Nat)pair, the unit of frontier membership.IsMinPriority frontier entry-entryis present infrontierand no other entry has strictly lower priority.canonicalBatch frontier- the list of all entries satisfyingIsMinPriority. These are exactly the entries serviced in the next step.frontierAfterCanonicalStep frontier- the frontier with the canonical batch removed.OneStepFair frontier- everyIsMinPriorityentry is absent fromfrontierAfterCanonicalStep frontier.CanonicalSerialTrace- a dynamic frontier trace whose successive states are related byfrontierAfterCanonicalStep.EventuallyServicedWithin trace start bound entry-entryis removed fromtrace startwithinboundsteps.ContinuouslyEligible,NoStrictlyBetterEntryAppears- predicates used in the dynamic liveness theorem to constrain how the frontier evolves.
The reduced artifact structures StepArtifact, StateSlice, ObservationSlice,
and StateArtifactSchema define the boundary at which Lean and Rust search
states are compared. BatchedExactWindowCertificate is a proof-carrying record
whose fields provide evidence that a batched window covers the current canonical
batch and produces a commit for each covered entry. Downstream fairness theorems
are conditioned on holding one of these certificates.
Fairness Proofs (Fairness)
Runtime.Proofs.Search.Fairness proves unconditional one-step fairness for
canonicalSerial at the current min-key batch. Broader eventual-service
results for non-min entries live in Liveness under explicit premise bundles,
there is no unconditional all-entry fairness theorem in this module.
mem_canonicalBatch_iff_isMinPriority- membership in the canonical batch is equivalent to satisfyingIsMinPriority. This connects the computational filter to the logical predicate.canonical_batch_entries_removed_after_step- every entry in the canonical batch is absent after the step. This is the core removal fact.canonical_serial_one_step_fair_for_min_priority_entries- ifentrysatisfiesIsMinPriority, it is absent after one canonical step. This is the main one-step fairness result.canonical_serial_batch_is_one_step_fair- the full frontier isOneStepFair.currently_min_priority_eventually_serviced_within_one_step- in anyCanonicalSerialTrace, a currentIsMinPriorityentry satisfiesEventuallyServicedWithin ... 1. The service bound is exactly one step.continuously_eligible_without_strictly_better_entries_eventually_serviced- the dynamic liveness result: if an entry remains eligible and no strictly better entry arrives across a horizon, it is serviced within one step.
Refinement Proofs (Refinement)
Runtime.Proofs.Search.Threaded models the threaded step independently of
the canonical step, following NativeParallelExecutor::generate: the frozen
canonical batch is tagged with batch indices, split into contiguous lanes of
non-zero width (laneChunks, mirroring par_chunks), each lane computes its
commits independently, lanes may complete in any order (a LaneSchedule
carries an arbitrary permutation of the lanes), and results are merged
deterministically by batch index. threadedMergedResults_eq_canonical proves
the merged result equals the canonical indexed batch for every width and
completion order. threaded_step_artifact_eq_canonical lifts this to the step
artifact. The model does not cover successor proposals or authority conflicts,
which the reduced artifact does not carry.
Runtime.Proofs.Search.Refinement then derives equality at every reduced
artifact boundary, each quantified over the LaneSchedule:
threaded_exact_single_lane_step_artifact_refines_canonical- the fullStepArtifactis equal.threaded_exact_single_lane_commit_trace_refines_canonicalandthreaded_exact_single_lane_batch_nodes_refine_canonical- thenormalizedCommitsandbatchNodesfields match individually.threaded_exact_single_lane_state_slice_refines_canonicalandthreaded_exact_single_lane_observation_slice_refines_canonical- the reducedStateSliceandObservationSliceprojections are equal.threaded_exact_single_lane_multi_step_state_trace_refines_canonical,..._observation_trace_..._canonical, and..._state_artifact_trace_..._canonical- all three multi-step trace functions agree across arbitraryFrontierTraceinputs and per-step lane schedules.
Executable Semantics (Executable)
Runtime.Proofs.Search.Executable is the semantic center of the search proof
lane: an executable reduced machine whose transitions and artifact projections
are all explicitly proved.
SearchMachineState- reduced proof-relevant machine state carrying frontier, closed nodes, inconsistent nodes, score table, parent witnesses, incumbent, phase, and progress accounting.MachineInvariants- reduced machine invariant bundle covering open/closed disjointness,incons ⊆ closed, parent-score coherence, incumbent coherence, and nodup conditions.executableCanonicalStep- concrete reduced step function implementing canonical batch extraction, batch removal, normalized commit production,open/closed/inconsmovement, score updates, and reduced incumbent refresh.stateSliceOfMachineState,observationSliceOfMachineState, andstateArtifactOfMachineState- explicit Rust-facing reduced extraction functions from executable machine states into the reduced artifact vocabulary.
Key theorems:
executable_canonical_step_preserves_invariants- executable reduced stepping preserves the reduced machine invariant bundle.executable_step_artifact_refines_canonical_step_artifact- the executable step refines exactly to the existing reduced step-artifact vocabulary.runtime_state_artifact_of_machine_state_is_projection- the reduced runtime-state artifact is an explicit projection of executable machine state.
Machine Vocabulary (Machine)
Runtime.Proofs.Search.Machine packages the executable semantics into the
machine-facing theorem surface.
CanonicalMachineStep- packaged executable reduced step plus current-state invariant bundle.CanonicalMachineTrace- machine trace version of canonical serial search.
Key theorems:
canonical_machine_step_services_current_min_priority_entries- the machine-level step relation preserves the same one-step service fact as the frontier-only fairness layer.canonical_machine_step_preserves_invariants- machine invariants are preserved by the executable reduced step.canonical_machine_trace_currently_min_priority_eventually_serviced- machine traces inherit the frontier-facing one-step eventual-service theorem through frontier projection.executable_trace_refines_canonical_machine_trace- executable machine traces refine directly into the packaged machine-step theorem surface.canonical_machine_step_artifact_refines_runtime_boundaryandcanonical_machine_state_artifact_is_runtime_projection- explicit refinement back into the Rust-facing reduced artifact vocabulary.
Full-Machine Semantics (FullMachine)
Runtime.Proofs.Search.FullMachine lifts the search lane above the reduced
frontier model into an executable proof-side machine that mirrors the
proof-relevant Rust state more closely.
FullSearchMachineState- executable full-machine state carryingOPEN,CLOSED,INCONS,g_score, parent table, incumbent, epsilon, phase, epoch, snapshot, last batch, and commit/publication traces.fullStepOnce,fullDecreaseEpsilonAndRebuild, andfullCommitEpochReconfiguration- executable full-machine transitions for canonical stepping, rebuild, and epoch changes.FullMachineInvariants- full-machine invariant bundle covering open/closed discipline,incons ⊆ closed, parent-score coherence, publication coherence, incumbent coherence, and nodup conditions.FullStateArtifactSchemaandfullStateArtifactOfFullState- explicit full-state artifact projection aligned with the Rust-sideSearchFullStateArtifactexport.
Key theorems:
full_state_artifact_of_full_state_is_runtime_projection- the exported full-state artifact is exactly the projection of the Lean full-machine state.reduced_state_of_full_state_preserves_machine_invariants- the richer full-machine invariant bundle implies the existing reduced machine invariants.full_activate_batch_preserves_invariants,full_apply_proposal_preserves_invariants,full_commit_proposals_preserves_invariants,full_decrease_epsilon_and_rebuild_preserves_invariants,full_commit_epoch_reconfiguration_preserves_invariants, andfull_step_once_preserves_invariants- the full-machine transition families have explicit invariant-preservation theorems.full_activate_batch_refines_reduced_service_windowandfull_step_once_refines_reduced_executable_step- the richer machine semantics refine back into the reduced executable step surface.
Liveness Proofs (Liveness)
Runtime.Proofs.Search.Liveness extends the search lane beyond the current
min-key batch. All results are premise-scoped: termination is proved only for
the named premise bundles in the theorem statements, and completeness
assumptions are made explicit in the theorem premises rather than left as
documentation-only caveats.
Termination:
closed_nodes_length_le_reachable_length- under the fixed-phase premise bundle, closed-node cardinality stays bounded by a finite reachable-node universe.fixed_phase_canonical_serial_terminates_under_finite_reachable_bound- fixed-phase canonical serial search terminates under an explicit finite reachable-node premise bundle.rebuild_aware_canonical_serial_terminates_under_phase_work_measure- rebuild-aware ARA-style termination under a lexicographic phase/work progress measure encoded into a natural descent argument.
Non-min fairness:
bounded_strict_preemption_eventually_becomes_min- a continuously present non-min-priority entry eventually reaches the min-key window when the count of strictly better entries is bounded and decreases strictly while it remains non-min.canonical_serial_nonmin_entry_eventually_serviced_under_bounded_strict_preemption- composes the previous theorem with one-step fairness to obtain a service bound for non-min entries.
finite_better_entry_exhaustion_eventually_becomes_minandcanonical_serial_nonmin_entry_eventually_serviced_under_finite_better_entry_exhaustion- stronger variants using a finite better-entry universe rather than a numeric preemption bound.
canonical_serial_nonmin_entry_eventually_serviced_under_scheduler_fairness- public scheduler-facing non-min fairness theorem. Its only premises are that the trace follows the canonical serial scheduler and the entry is present. The service horizon (number of strictly better competitors at the start) is computed, and the strict drain of better work is proved (strictlyBetterEntryCount_canonical_step_lt), not assumed. The reduced frontier model has no new arrivals, so preemption is automatically bounded.
Completeness:
canonical_serial_goal_reached_from_ready_witness_path- from an explicit ready witness path whose successive frontier entries become service-ready in order, the goal entry is eventually serviced.canonical_machine_goal_reached_from_ready_witness_path- machine-level version: executable canonical stepping closes the goal node within the witness-path horizon.canonical_machine_goal_reached_from_graph_reachability- completeness driven by an explicit graph-reachability premise bundle naming a reachable-node path and its ready entry path.canonical_machine_goal_reached_from_raw_successor_semantics- public completeness theorem phrased over the raw successor contract, with ready-entry progress internalized behind an explicit refinement premise.goal_reachability_connects_to_incumbent_publication- machine-level bridge from bounded goal reachability to incumbent publication under an explicit publication premise bundle.eventual_optimal_goal_publication_under_admissible_consistent_heuristic(AStarOptimality) - A*-style optimality.HeuristicConsistent(h n ≤ c(n,n') + h n') andHeuristicAdmissible(h n ≤cost of any path to the goal) are concrete. Admissibility is proved from consistency andh goal = 0. Optimal cost of the published incumbent is proved from the machine’s min-priority expansion order, g-score soundness of goal entries, andoptimalCostbeing a lower bound on source-to-goal path costs. The standard A* frontier invariant (before publication some frontier entry lies on an optimal path with exactg) remains an explicit premise, because the reduced machine does not model successor expansion.
Envelope Surface (Envelope)
Runtime.Proofs.Search.Envelope carries the certified-window theorem surface
for batchedParallelEnvelopeBounded. It provides the same certificate
structure as batchedParallelExact but scoped to the envelope-bounded service
window.
BatchedEnvelopeWindowCertificate- theorem-side certificate object for one legal envelope-bounded service window.certified_batched_envelope_window_is_fair- every currentIsMinPriorityentry is removed within the certified envelope window.certified_batched_envelope_window_eventually_services_min_priority_entries- restates the one-window fairness theorem as
EventuallyServicedWithin ... 1.
- restates the one-window fairness theorem as
certified_envelope_window_trace_is_valid- validates exported envelope certificate traces against the legal current min-key batch.
Profile Claims (ProfileClaims)
Runtime.Proofs.Search.ProfileClaims derives the per-profile theorem surface
from the upstream proof modules, indexing each result by SearchSchedulerProfile
and FairnessClaimClass. The claim table at the top of this section shows what
each profile carries.
canonical_serial_profile_has_exact_one_step_fairness- pairs theexactOneStepclaim tag withOneStepFair frontierfor every frozen frontier (fromcanonical_serial_batch_is_one_step_fair).canonical_serial_goal_window_service_has_exact_suffix_bound- every min-priority entry is gone after one canonical step.threaded_exact_single_lane_has_exact_one_step_fairness- derives one-step fairness for the threaded profile by composing theRefinementequalities.threaded_exact_single_lane_has_exact_observation_equivalence- observation slices of the lane-partitioned, merge-normalized threaded step match canonical serial exactly for everyLaneSchedule.certified_batched_exact_window_is_fair- given aBatchedExactWindowCertificate, everyIsMinPriorityentry is removed within the certified window.certified_batched_exact_window_eventually_services_min_priority_entries- restates the above as anEventuallyServicedWithinbound of 1.certified_batched_exact_window_bounded_dynamic_starvation_freedom- given certificates for each step in a horizon, aContinuouslyEligibleentry underBoundedPreemptionWindowis serviced withinboundsteps.certified_window_trace_is_valid_for_exact_batch_service- validates that a certificate sequence matches the canonical batch at every step with a service bound of 1.batched_parallel_envelope_claim_is_premised- states formally thatbatchedParallelEnvelopeBoundedcarries the theorem-backed certified-window claim class.
Theorem Inventory (Inventory and TheoremPack)
Runtime.Proofs.Search.Inventory records every theorem by name together with
a Bool indicating whether it is proved. Runtime.Proofs.Search.TheoremPack
exports a support classification for every row: executableSemantics,
refinementCorollary, or premiseScoped.
The inventory has 50 proved theorems and 0 unproved. Coverage includes the full-machine artifact and projection surface, full-step refinement contracts back into the reduced executable lane, a raw-successor completeness theorem surface, scheduler-fair non-min service, and the certified-window envelope profile theorems.
Runtime.Proofs.Search.TheoremPack wraps the inventory, support classes, and
two numeric bounds into a SearchFairnessTheoremPack structure. The mirrored
Rust SearchTheoremPackArtifact carries those same rows plus the release-gate
name:
inventorySupportClasses
canonicalServiceBoundSteps := 1
canonicalGoalWindowDiscoverySuffixBoundSteps := 1
Both bounds are exactly one step, matching the EventuallyServicedWithin ... 1
statements in Fairness and ProfileClaims. The dedicated local verification
gate is just check-search-fairness. The theorem-pack is exported through the
runtime API and written to target/search-theorem-pack/search-theorem-pack.json
for release and provenance checks. On the Rust side this appears in
SearchTheoremPackArtifact as both inventory and
inventory_support_classes, so downstream checks can distinguish executable
semantics theorems from refinement corollaries and premise-scoped theorems.
Artifact Vectors
Stable source-controlled search artifact vectors are exported through
cargo run -p telltale-search --example export_vectors and checked by the
search_vectors conformance test. This covers theorem-pack, final-state,
fairness, and replay artifact drift.
A separate recovery vector surface is exported through
cargo run -p telltale-search --example export_recovery_vectors and checked by
search_recovery_vectors. This covers epoch-transition recovery bounds and
route summaries.
The release and provenance lane records both the theorem-pack JSON and the
generated canonical vector artifact at
target/search-artifacts/search-vectors-v1.json, plus the recovery vector
artifact at target/search-artifacts/search-recovery-vectors-v1.json.
Target Support
The core serial machine is target-agnostic. WASM builds use the same canonical
serial and replay surface without rayon. The optional multi-thread feature
enables the native parallel executor for native targets.
Lean/Rust Parity
Reduced Lean/Rust parity covers canonical batch-window extraction, proposal independence over declared authority surfaces, theorem-pack support-class parity, the richer Rust full-state artifact boundary, and epoch-barrier semantics with fairness-bundle fixtures. See Rust-Lean Bridge and Parity for the deviation registry and parity policy.
Migration Note
For downstream selector-style imports such as Jacquard:
- prefer
SearchQuery::try_multi_goal(...)andSearchQuery::try_candidate_set(...)over panicking query builders - prefer
SelectedSolution,SearchResultBoundArtifact, andSearchResultSummary - use
SearchDomain::selected_result_candidates(...)when winner eligibility depends on discovered machine state - consume
SearchClaimClassandinventory_problem_classesrather than scraping theorem names - avoid the route/incumbent compatibility aliases unless migrating legacy
code, because the intended compatibility surface is
telltale_search::compat - treat
IncrementalReuseas out of the stable import posture until it is implemented
Content Addressing
Content addressing assigns stable hashes to protocol artifacts. The hash is computed from a canonical serialization, which enables memoization and structural sharing.
ContentId and Hasher
ContentId wraps a hash digest produced by a Hasher.
#![allow(unused)]
fn main() {
pub trait Hasher: Clone + Default + PartialEq + Send + Sync + 'static {
type Digest: AsRef<[u8]> + Clone + PartialEq + Eq + Hash + Send + Sync + 'static;
const HASH_SIZE: usize;
fn digest(data: &[u8]) -> Self::Digest;
fn algorithm_name() -> &'static str;
}
pub type DefaultContentHasher = Blake3Hasher;
pub struct ContentId<H: Hasher = DefaultContentHasher> {
hash: H::Digest,
}
}
DefaultContentHasher is the central policy alias for content addressing and currently resolves to Blake3Hasher. SHA-256 remains available as an explicit alternative behind the sha256 crate feature when compatibility matters. A custom Hasher implementation can still trade off speed or proof system constraints.
The runtime heap reuses the same Hasher abstraction, but it does not share the same byte-level encoding contract.
Heap resources use the runtime-local canonical format described in Resource Heap.
That separation is intentional.
Contentable
Types that can be serialized canonically implement Contentable.
#![allow(unused)]
fn main() {
pub trait Contentable: Sized {
fn to_bytes(&self) -> Result<Vec<u8>, ContentableError>;
fn from_bytes(bytes: &[u8]) -> Result<Self, ContentableError>;
fn from_bytes_verified<H: Hasher>(
bytes: &[u8],
expected: &ContentId<H>,
) -> Result<Self, ContentableError>;
fn to_template_bytes(&self) -> Result<Vec<u8>, ContentableError>;
}
}
GlobalType, LocalTypeR, Label, and PayloadSort implement this trait. The serializer converts types to a de Bruijn representation and normalizes branch ordering. Alpha equivalent types share the same content ID.
Callers loading bytes from a content-addressed store should prefer from_bytes_verified, which hashes the bytes first and rejects a content-ID mismatch before deserializing.
Closed vs Open Terms
content_id is defined for closed terms only. Open terms use template_id, which includes an explicit free-variable interface in the serialized template envelope.
#![allow(unused)]
fn main() {
use telltale_types::{GlobalType, Label};
use telltale_types::contentable::Contentable;
let open = GlobalType::send("A", "B", Label::new("msg"), GlobalType::var("free_t"));
assert!(open.content_id_default().is_err());
let template_id = open.template_id_default()?;
let template_bytes = open.to_template_bytes()?;
}
Template bytes and template IDs enable caching and comparing partially specified protocols before all binders are resolved.
Serialization Formats
JSON is the default format. DAG-CBOR is available with the dag-cbor feature and produces compact binary output.
#![allow(unused)]
fn main() {
use telltale_types::{GlobalType, Label};
use telltale_types::contentable::Contentable;
let g = GlobalType::send("A", "B", Label::new("msg"), GlobalType::End);
let json_bytes = g.to_bytes()?;
let cid = g.content_id_default()?;
}
JSON and DAG-CBOR produce different content IDs for the same value. One format should be used consistently within a system. Switching formats is a cache-boundary event that requires regenerating persisted IDs.
ContentStore
ContentStore maps content IDs to cached values and tracks cache metrics.
#![allow(unused)]
fn main() {
use telltale_types::content_store::ContentStore;
use telltale_types::{GlobalType, Label, LocalTypeR};
let mut store: ContentStore<GlobalType, LocalTypeR> = ContentStore::new();
let global = GlobalType::send("A", "B", Label::new("msg"), GlobalType::End);
let local = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
store.insert(&global, local.clone())?;
let cached = store.get(&global)?;
}
KeyedContentStore adds an additional key alongside the content ID for composite lookups.
Memoization Pattern
Content IDs are useful for caching projection results.
#![allow(unused)]
fn main() {
use std::collections::HashMap;
use telltale_types::{ContentId, GlobalType, Label, LocalTypeR};
use telltale_theory::project;
let global = GlobalType::send("Alice", "Bob", Label::new("ping"), GlobalType::End);
let mut cache: HashMap<(ContentId<_>, String), LocalTypeR> = HashMap::new();
let cid = global.content_id_default()?;
let key = (cid, "Alice".to_string());
if let Some(cached) = cache.get(&key) {
// use cached
} else {
let local = project(&global, "Alice")?;
cache.insert(key, local);
}
}
This avoids repeated projection work when the same global type appears multiple times.
See Choreographic Projection Patterns for the projection pipeline and Resource Heap for higher-level storage patterns.
Resource Heap
The resource heap provides explicit runtime state tracking for choreography-layer resources.
It stores resources under deterministic identifiers, tracks consumed resources in a nullifier set, and can derive Merkle commitments for the current heap state.
This module lives in telltale_runtime::heap.
Scope
The heap is a telltale-runtime utility.
It is not the same system as the type-level content addressing in telltale-types.
The runtime heap uses the shared Hasher abstraction with its own canonical heap encoding and commitment logic.
The heap now has a focused Lean parity mirror.
lean/Runtime/Resources/HeapModel.lean mirrors canonical bytes, tagged preimages, ordering rules, proof-index semantics, and deterministic replay for the published heap corpus.
Digest computation remains Rust-side and is checked against published vectors rather than reimplemented in Lean.
Resource Identifiers
Resources are identified by ResourceId.
Each ID stores a hasher digest and an allocation counter.
The current default uses the shared BLAKE3 policy through DefaultContentHasher.
The identifier is computed by hashing the canonical resource bytes plus the allocation counter.
#![allow(unused)]
fn main() {
pub struct ResourceId<H: Hasher = DefaultContentHasher> {
hash: H::Digest,
counter: u64,
}
}
This means the identifier is allocation-unique rather than pure-content-stable.
Two identical resources allocated with different counters produce different ResourceId values.
That behavior is intentional in the current runtime heap.
Resource Types
The heap stores four resource kinds.
#![allow(unused)]
fn main() {
pub enum Resource {
Channel(ChannelState),
Message(Message),
Session { role: String, type_hash: u64 },
Value { tag: String, data: Vec<u8> },
}
}
ChannelState stores sender, receiver, and an in-memory queue of MessagePayload.
Message stores source, destination, label, payload bytes, and a sequence number.
Session stores a role name and a type_hash value.
Heap Structure
The heap uses deterministic ordered containers.
#![allow(unused)]
fn main() {
pub struct Heap<H: Hasher = DefaultContentHasher> {
resources: BTreeMap<ResourceId<H>, Resource>,
nullifiers: BTreeSet<ResourceId<H>>,
counter: u64,
}
}
BTreeMap and BTreeSet give stable iteration order.
That stable order is important for reproducible commitments and proofs.
The nullifier set records consumed resource IDs without deleting them from the resource map.
Heap APIs
The heap supports both functional and mutable update styles. The functional APIs return a new heap value. The mutable APIs exist for efficient in-place construction and updates.
#![allow(unused)]
fn main() {
use telltale_runtime::heap::{DefaultHeapHasher, Heap, Resource};
let heap = Heap::<DefaultHeapHasher>::new();
let msg = Resource::message("Alice", "Bob", "Ping", vec![1, 2, 3], 0);
let (msg_id, heap) = heap.alloc(msg);
let heap = heap.consume(&msg_id)?;
}
alloc(...), consume(...), remove(...), alloc_many(...), and consume_many(...) are the main functional operations.
alloc_mut(...) and consume_mut(...) provide mutable variants.
consume_many(...) validates the whole batch first and then nullifies all requested resources atomically.
read(...) only succeeds for active resources.
If a resource has been consumed, read(...) returns HeapError::AlreadyConsumed.
Use contains(...), is_consumed(...), is_active(...), active_resources(...), and consumed_ids(...) when you need explicit state inspection.
Size and Retention Semantics
size() returns the number of entries in the resource map.
Consumed resources still count toward that number because consume(...) keeps them in the heap and only adds a nullifier.
nullified_count() reports the number of consumed resource IDs.
remove(...) is the operation that actually deletes a resource from the heap.
It also removes the resource from the nullifier set if it was already consumed.
This makes remove(...) a cleanup operation rather than a normal consumption operation.
Resource ids include the monotonic allocation counter, so a removed id is never reissued by the same heap.
The heap nullifier set is independent of protocol-machine communication replay nullifiers, which remove(...) does not affect.
Merkle Commitments
The heap can derive Merkle commitments over its current state.
MerkleTree::from_heap(...) builds a Merkle tree from active resources only.
HeapCommitment::from_heap(...) combines the active-resource root, the nullifier root, and the allocation counter.
#![allow(unused)]
fn main() {
use telltale_runtime::heap::{HeapCommitment, MerkleTree};
let tree = MerkleTree::from_heap(&heap);
let proof = tree.prove(0);
let commitment = HeapCommitment::from_heap(&heap);
}
prove(...) generates an inclusion proof by leaf index in the current active-resource ordering.
That ordering follows BTreeMap iteration over active ResourceId keys.
resource_id_preimage(...), resource_leaf_preimage(...), nullifier_leaf_preimage(...), merkle_node_preimage(...), and heap_commitment_preimage(...) expose the tagged preimage rules directly.
resource_leaf_hash(...), nullifier_leaf_hash(...), and merkle_node_hash(...) then hash those preimages.
HeapCommitment::hash() then hashes a tagged preimage that contains the two roots and the counter.
Error Types
Heap operations use HeapError.
#![allow(unused)]
fn main() {
pub enum HeapError<H: Hasher = DefaultContentHasher> {
NotFound(ResourceId<H>),
AlreadyConsumed(ResourceId<H>),
AlreadyExists(ResourceId<H>),
TypeMismatch { expected: String, got: String },
Other(String),
}
}
In the current implementation, NotFound and AlreadyConsumed are the main operational cases returned by heap methods.
AlreadyExists and TypeMismatch are part of the public error vocabulary but are not heavily exercised by the basic heap operations shown above.
Encoding and Hashing Guarantees
The heap uses a versioned canonical binary format for resources.
Every canonical heap value starts with the TTHP magic prefix, a little-endian encoding version, and a one-byte type tag.
Strings and byte slices use explicit little-endian u32 length prefixes.
Nested heap values are encoded as full canonical child values.
The heap also uses tagged preimages for ResourceId, resource-leaf hashes, nullifier-leaf hashes, Merkle internal nodes, and HeapCommitment::hash().
Those tagged preimages are part of the public heap contract.
Canonical Encoding Model
The heap uses a runtime-local canonical binary format.
It does not reuse the type-level artifact encoding in Content Addressing.
The current format is versioned by HEAP_ENCODING_VERSION.
Every canonical heap value starts with the same prelude.
The prelude is the four-byte magic value TTHP, followed by the little-endian encoding version, followed by a one-byte type tag.
The remainder of the byte sequence is the canonical body for that tagged value.
Strings and byte slices use explicit little-endian u32 length prefixes.
Counters and numeric fields use little-endian integer encoding.
Nested heap values are encoded as full canonical child values.
Covered Types
ChannelState encoding includes sender, receiver, queue length, and every queued MessagePayload in order.
Message encoding includes source, destination, label, full payload bytes, and sequence number.
Session and Value resources encode all of their current semantic fields.
The current canonical encoding also covers MessagePayload, ChannelState, Message, and Resource.
Resource::Session includes role and type_hash.
Resource::Value includes tag and full data.
Resource Identity
ResourceId remains allocation-unique in the current design.
The heap computes it from a tagged preimage that contains the canonical resource bytes and the little-endian allocation counter.
The digest uses the selected heap hasher, which defaults to DefaultHeapHasher.
This design keeps repeated allocations of identical semantic resources distinct. It also ties the ID contract directly to the canonical heap encoding boundary.
Merkle Leaves and Commitments
Active-resource leaves hash a tagged preimage that contains the ResourceId digest and the canonical resource bytes.
Nullifier leaves hash a distinct tagged preimage that contains the ResourceId digest bytes.
Internal Merkle nodes hash a tagged preimage that contains the left child digest and the right child digest.
HeapCommitment stores the active-resource root, the nullifier root, and the allocation counter.
HeapCommitment::hash() hashes a tagged preimage that contains those three values.
The allocation counter remains part of the authoritative commitment contract. This keeps the commitment aligned with allocation history rather than only current live content. The current heap model treats that history as semantically relevant.
Cross-Implementation Expectations
Another implementation should reproduce canonical bytes exactly.
It should also preserve active-resource ordering and nullifier ordering exactly.
The published heap test vectors are the conformance target for that behavior.
The current published vector file is rust/runtime/tests/data/heap_vectors_v1.json.
The runtime heap now has a Lean parity mirror for canonical bytes and tagged preimages.
That mirror lives in lean/Runtime/Resources/HeapModel.lean and is exercised through lean/Runtime/Tests/HeapParityRunner.lean.
The digest algorithm itself is still treated as an operational conformance target. Rust and Lean must agree on the published heap vectors exactly, but Lean does not currently reimplement BLAKE3. This section therefore defines both the boundary that an external embedder must match and the boundary that the current Rust↔Lean heap lane enforces.
See Rust-Lean Bridge and Parity for the Lean mirror boundary.
Determinism Contract
Heap operations are deterministic by contract.
Active-resource ordering follows ResourceId ordering in the heap BTreeMap.
Consumed-resource ordering follows the same ResourceId ordering in the nullifier BTreeSet.
Merkle proof indices refer to that exact active-resource order.
Ordering Rules
Active resources are ordered by ResourceId.
ResourceId ordering compares digest bytes first and allocation counter second.
Heap::active_resources() uses that order because the heap stores resources in a BTreeMap.
Consumed resource IDs are ordered by the same ResourceId ordering.
Heap::consumed_ids() uses that order because the heap stores nullifiers in a BTreeSet.
Merkle leaves follow the iteration order of active_resources().
Merkle proof indices refer to that exact active-leaf order.
Replay Contract
Repeated executions of the same heap operation sequence must yield the same ResourceId values, proof paths, and HeapCommitment values.
This contract is sequence-sensitive.
If allocation order changes, the allocation counter changes.
That changes ResourceId values and therefore changes commitments.
Commitment Authority
HeapCommitment is the authoritative cryptographic summary of heap state.
HeapCommitment::hash() is the only supported single-value digest for that state.
The heap no longer exposes a separate debug-only state fingerprint.
Order-Insensitive Operations
Some operations are deterministic even when their input lists are permuted.
consume_many(...) is one example.
If the same set of resource IDs is consumed successfully, the resulting nullifier set and heap commitment must match regardless of argument order.
Scope
This contract now has a focused Lean mirror.
lean/Runtime/Resources/HeapModel.lean mirrors active/nullifier ordering, proof-index semantics, and deterministic replay for the published heap corpus.
The published vectors and the strict Rust↔Lean heap parity suite are the current conformance boundary for other implementations.
Use Content Addressing for the type-level artifact identity system. Use Choreography Effect Handlers for choreography runtime integration. See Rust-Lean Bridge and Parity for the Rust↔Lean conformance lane.
Topology
Topology separates protocol logic from deployment configuration. Choreographies remain location free while topology definitions specify where roles execute.
Core Types
Locations capture how a role is deployed.
#![allow(unused)]
fn main() {
pub enum Location {
Local,
Remote(TopologyEndpoint),
Colocated(RoleName),
}
}
The Local variant means in-process execution. The Remote variant stores a network endpoint. The Colocated variant ties a role to another role location.
Topology state holds role mappings, constraints, and an optional local-only shorthand.
#![allow(unused)]
fn main() {
pub struct Topology {
pub mode: Option<TopologyMode>,
pub locations: BTreeMap<RoleName, Location>,
pub channel_capacities: BTreeMap<(RoleName, RoleName), ChannelCapacity>,
pub constraints: Vec<TopologyConstraint>,
pub role_constraints: BTreeMap<String, RoleFamilyConstraint>,
}
}
TopologyMode is intentionally narrow. The runtime only exposes a local shorthand. Deployment-specific discovery or orchestration belongs in transport or integration crates, not in telltale-runtime. TopologyConstraint and RoleFamilyConstraint add placement and role family rules.
#![allow(unused)]
fn main() {
pub enum TopologyMode {
Local,
}
pub enum TopologyConstraint {
Colocated(RoleName, RoleName),
Separated(RoleName, RoleName),
Pinned(RoleName, Location),
Region(RoleName, Region),
}
pub struct RoleFamilyConstraint {
pub min: u32,
pub max: Option<u32>,
}
}
TopologyMode is parsed from the DSL value local. Older deployment-specific mode: values like per_role, kubernetes(...), or consul(...) now reject explicitly because runtime topology is transport agnostic. Role family constraints are used to validate wildcard and range roles.
RoleFamilyConstraint provides helper constructors and a validation method:
#![allow(unused)]
fn main() {
use telltale_runtime::RoleFamilyConstraint;
// Minimum-only constraint (unbounded max)
let unbounded = RoleFamilyConstraint::min_only(3);
// Bounded constraint with min and max
let bounded = RoleFamilyConstraint::bounded(2, 5);
// Validate a role count against the constraint
bounded.validate(4)?; // Ok
bounded.validate(1)?; // Err(BelowMinimum)
bounded.validate(6)?; // Err(AboveMaximum)
}
Role-family constraints are explicit validation data. Parsed topologies and
builder-created topologies preserve them, and generated inline topology
helpers retain them, but the caller still supplies the resolved family count
at runtime via validate_family.
DSL Syntax
Topologies are defined in .topology files or parsed from strings.
topology TwoPhaseCommit_Prod for TwoPhaseCommit {
Coordinator: coordinator.prod.internal:9000
ParticipantA: participant-a.prod.internal:9000
ParticipantB: participant-b.prod.internal:9000
role_constraints {
Participant: min = 2, max = 5
}
channel_capacities {
Coordinator -> ParticipantA: 4
Coordinator -> ParticipantB: 4
}
constraints {
separated: Coordinator, ParticipantA
separated: Coordinator, ParticipantB
region: Coordinator -> us_east_1
}
}
The role_constraints block controls acceptable family sizes. The channel_capacities block sets per-edge capacity in bits (used for branching feasibility checks). The constraints block encodes separation, pinning, and region requirements. region: Role -> region_name is now executable topology data: validated roles resolve a concrete region, colocated roles inherit the peer region unless they declare the same region explicitly, and conflicting colocated region declarations reject deterministically.
Rust API
You can build topologies programmatically and validate them against choreography roles.
#![allow(unused)]
fn main() {
use telltale_runtime::{RoleFamilyConstraint, RoleName, Topology, TopologyEndpoint};
let topology = Topology::builder()
.local_role(RoleName::from_static("Coordinator"))
.remote_role(
RoleName::from_static("ParticipantA"),
TopologyEndpoint::new("localhost:9001").unwrap(),
)
.remote_role(
RoleName::from_static("ParticipantB"),
TopologyEndpoint::new("localhost:9002").unwrap(),
)
.role_family_constraint("Participant", RoleFamilyConstraint::bounded(2, 5))
.build();
let roles = [
RoleName::from_static("Coordinator"),
RoleName::from_static("ParticipantA"),
RoleName::from_static("ParticipantB"),
];
let validation = topology.validate(&roles);
assert!(validation.is_valid());
}
Topology::builder produces a TopologyBuilder with fluent helpers. Topology::validate checks that all roles referenced in the topology and constraints exist in the choreography.
Topology::region_for_role(&role) resolves the effective region after colocated inheritance and reports conflicting region declarations as validation errors.
For reconfiguration and recovery flows, the same topology can export canonical placement and transport-boundary artifacts for an active member set:
#![allow(unused)]
fn main() {
let placements = topology.placement_observations_for_roles(["Coordinator", "ParticipantA"])?;
let boundaries = topology.transport_boundaries_for_roles(["Coordinator", "ParticipantA"])?;
}
placement_observations_for_roles(...) returns transport-agnostic facts per
member: local/remote/colocated kind, optional endpoint, and resolved region.
transport_boundaries_for_roles(...) derives canonical in-process,
shared-memory, and network boundaries plus cross-region markers. These are the
runtime-facing inputs used by deterministic multi-step reconfiguration plans and
their recovery artifacts.
Explicit family-cardinality checks use the same topology object:
#![allow(unused)]
fn main() {
topology.validate_family("Participant", 3)?;
assert!(topology.validate_family("Participant", 1).is_err());
}
Topologies can also be loaded from DSL files.
#![allow(unused)]
fn main() {
let parsed = Topology::load("deploy/prod.topology")?;
let topology = parsed.topology;
}
Topology::load returns a ParsedTopology that includes both the topology and the target protocol name.
TopologyHandler
TopologyHandler wraps transport selection and routing for a role.
#![allow(unused)]
fn main() {
use telltale_runtime::{RoleName, TopologyHandler};
let handler = TopologyHandler::local(RoleName::from_static("Alice"));
handler.initialize().await?;
}
The local constructor sets TopologyMode::Local and creates in-process transports. For custom layouts, use TopologyHandler::new or the builder with a Topology. If you need Kubernetes, Consul, or another discovery system, implement that in a transport or integration crate and feed the runtime explicit Location::Remote endpoints or another transport-facing adapter boundary.
Generated protocols include helpers under Protocol::topology, including Protocol::topology::handler(role) and Protocol::topology::with_topology(topology, role). These return a TopologyHandler for the selected role.
Generated Topology Helper Surface
tell! emits a topology helper module per protocol. The generated surface follows this shape:
#![allow(unused)]
fn main() {
pub mod topology {
pub fn handler(role: Role) -> TopologyHandler;
pub fn with_topology(topology: Topology, role: Role) -> Result<TopologyHandler, String>;
pub mod topologies {
pub fn dev() -> Topology;
pub fn dev_handler(role: Role) -> Result<TopologyHandler, String>;
pub fn prod() -> Topology;
pub fn prod_handler(role: Role) -> Result<TopologyHandler, String>;
}
}
}
handler(role) builds a local topology handler. with_topology(topology, role)
validates role coverage, placement constraints, and branch-capacity constraints
and returns a role-bound handler. Inline named topologies preserve declared
role-family constraints as topology data, but callers still invoke
validate_family(...) explicitly because family counts are runtime inputs.
Usage pattern:
#![allow(unused)]
fn main() {
let local = MyProtocol::topology::handler(Role::Alice);
let prod = MyProtocol::topology::topologies::prod_handler(Role::Alice)?;
let custom = MyProtocol::topology::with_topology(custom_topology, Role::Alice)?;
}
This pattern shows local default setup, named topology setup, and custom topology setup in one place.
Transport Selection
Transport selection is based on role locations.
#![allow(unused)]
fn main() {
use telltale_runtime::topology::{TransportFactory, TransportType};
let transport_type = TransportFactory::transport_for_location(
&RoleName::from_static("Alice"),
&RoleName::from_static("Bob"),
&topology,
)?;
assert!(matches!(transport_type, TransportType::Tcp));
}
TransportFactory::create realizes loopback Location::Remote endpoints through a deterministic TCP transport on native targets. TopologyHandler uses the same remote slice for with_topology(...) helpers, so generated topology public-path tests exercise real loopback message delivery instead of an intent-only placeholder. The runtime does not encode discovery products or managed deployment backends directly. Those belong outside the runtime API.
Those transport decisions are also visible through
transport_boundaries_for_roles(...), which means topology-aware
reconfiguration tests compare the same canonical boundary summary across direct
execution, snapshot/restore, and bridge-mediated recovery runs.
Lean Correspondence
The Lean formalization for topology is in lean/Protocol/Spatial.lean. Projection correctness does not depend on topology data, so location checks are enforced during deployment instead of compilation.
See Choreographic DSL for role declarations and Choreography Effect Handlers for choreography handler usage patterns.
Generated public-path validation
Generated topology helpers use fully qualified public runtime types and import
only their containing protocol’s Role (plus the parent helper for named
presets). Optional placement, region, capacity and role-family definitions do
not produce unused imports in protocols that omit them. The generated topology
public-path smoke compiles and executes both its temporary library and test
under #![deny(warnings)], including basic, branching and named presets. It
retains the caller’s Cargo flags so validation shares existing artifact settings.
Capability Model
This page is the canonical reference for Telltale’s first-class protocol-critical capability model.
The model is intentionally narrow. It covers protocol-critical authority, evidence, finalization, handoff, and reconfiguration/runtime-upgrade semantics. It does not define a general-purpose application authorization framework.
Scope Boundary
In scope:
- protocol-critical capability semantics that change protocol-visible truth
- replay-visible evidence and finalization objects
- ownership and receipt/handoff/reconfiguration transition artifacts
- theorem-pack and runtime admission gates
Out of scope:
General host application authorization is out of scope.
- arbitrary user/resource policy languages
- product-specific auth delegation systems unrelated to protocol semantics
- host-only policy decisions that cannot be modeled as typed Rust/Lean objects
Four Capability Classes
| Class | Main question | Representative objects |
|---|---|---|
admission | may this runtime/profile/configuration execute at all? | theorem-pack eligibility, runtime contracts, determinism-profile gates |
ownership | who may currently mutate one live session or fragment? | OwnershipCapability |
evidence | why is a protocol-critical branch or canonicalization step justified? | ReadinessWitness, AuthoritativeRead, ObservedRead, MaterializationProof, CanonicalHandle, PublicationEvent |
transition | what explicit object authorizes transfer, handoff, cutover, or reconfiguration? | OwnershipReceipt, SemanticHandoff, DelegationReceipt, RuntimeUpgradeArtifact, reconfiguration transition artifacts |
The canonical Rust boundary is rust/machine/src/capabilities.rs.
The canonical Lean boundary is lean/Runtime/Proofs/CapabilityModel.lean
together with the theorem-pack and conservation proof surfaces.
Canonical Lifecycle States
| State | Meaning |
|---|---|
issued | object exists and may be consumed or transitioned later |
consumed | object was used exactly once on its sanctioned path |
rejected | object was denied as invalid, stale, or insufficient |
invalidated | object became unusable because later semantic state revoked it |
committed | transition object became canonical |
rolled_back | transition object failed and did not become canonical |
expired | object aged out of its validity window |
Not every class uses every lifecycle state. For example, ownership uses
issued, invalidated, expired, and rejected, while transition
artifacts commonly use issued, committed, rolled_back, rejected, and
invalidated.
Finalization State Machine
Finalization is the explicit bridge from protocol observation to canonical truth.
| Stage | Meaning |
|---|---|
observed | only observational input exists |
authoritative | authoritative evidence exists, but proof-bearing success is incomplete |
materialized | proof-bearing success exists, but canonical publication/handle pairing is incomplete |
canonical | publication/materialization/handle conditions are satisfied and the result may be consumed as protocol truth |
invalidated | a handoff or transition revoked the path before canonical reuse |
rejected | proof-bearing publication or repair failed |
The key runtime/Lean objects are:
ProtocolMachineFinalizationFinalizationPathFinalizationReadClassFinalizationStage
Observed input never becomes canonical directly. It must pass through the explicit authoritative/materialization/publication path.
Language Mapping
| DSL form | Capability class | Resulting semantic object family |
|---|---|---|
authoritative let x = check ... | evidence | AuthoritativeRead, readiness/evidence-bearing path |
observe let x = observe ... | evidence | ObservedRead |
let receipt = transfer ... | transition | OwnershipReceipt / transfer receipt path |
publish witness as Publication | evidence | PublicationEvent |
materialize proof from Publication | evidence | MaterializationProof, CanonicalHandle |
handoff op to Role with receipt | transition | SemanticHandoff |
Runtime upgrade and reconfiguration are first-class transition semantics in the runtime and Lean model, but they are not choreography DSL statements today.
Runtime Meaning
The runtime uses one canonical lifecycle audit and one canonical semantic-object bundle:
ProtocolMachine::capability_lifecycle_audit_log()ThreadedGuestRuntime::capability_lifecycle_audit_log()ProtocolMachine::semantic_objects()ProtocolMachine::protocol_machine_finalization()
Timeout expiry is modeled strictly: the runtime stores and later expires the
exact issued TimeoutWitness, rather than reconstructing expiry from a bare
clock map later.
Transfer/handoff and runtime upgrade/reconfiguration are also explicit. They emit typed transition artifacts rather than relying on hidden host-local state.
ProtocolMachineConfig.host_contract_assertions is a typed
HostContractMode. The default Enforced mode checks handler identity
stability, deterministic topology-event ordering, and transfer-event audit
coverage before those host-supplied facts are accepted into the machine. The
RelaxedTestOnly mode is reserved for targeted tests that intentionally violate
those contracts. It is rejected with DeterminismMode::Full.
Lean and Bridge Surfaces
The Lean model exports this surface through:
lean/Runtime/Proofs/CapabilityModel.leanlean/Runtime/Tests/ProtocolMachineRunner.lean
The strict Rust↔Lean correspondence lane checks the capability/finalization surface through:
rust/bridge/tests/capability_model_correspondence.rstoolkit/xtask/src/checks/lean_bridge_strict.rs
That lane keeps canonical vs invalidated finalization paths and committed-cutover vs rolled-back runtime-upgrade artifacts executable and claim-visible.
Scope Decision Criteria
A concern is eligible to move into the protocol-machine/effect/DSL boundary when all of the following hold:
- It changes protocol-critical behavior rather than only presentation or app-internal behavior.
- It can be represented as typed outcomes, evidence, receipts, or obligations rather than opaque host internals.
- It is observable at the replay/audit/effect-trace boundary.
- The protocol machine can enforce or validate it at a meaningful boundary.
- Lean/Rust correspondence can model it without inventing a separate host-language semantics.
A concern should stay host-only when any of the following hold:
- It is mainly UI/view/reduction logic.
- It depends on implementation-specific nondeterministic runtime internals rather than typed protocol observations.
- It cannot be expressed as typed evidence/outcomes without dragging in a general-purpose application semantics.
- It is not part of the safety-visible or replay-visible protocol interface.
Classification
| Boundary | Examples |
|---|---|
| Move into protocol machine/effect layer | typed effect outcomes, authority/evidence/receipt objects, explicit timeout/cancellation/failure semantics, structured authority traces |
| Move into DSL | case/of pattern matching, unions and type alias, let-bound evidence values, check syntax, effect/uses declarations |
| Keep host-only | UI/view/reduction architecture, frontend state taxonomies, storage/retry/transport internals, rendering-oriented observed state |
Host Runtime Disposal Boundary
Targeted close and reap is an operation of the exclusively owned Rust runtime. Its acknowledgment proves retirement of execution custody after any owned worker scope completes. The compact summary grants no admission, ownership, evidence, or transition capability and cannot authorize a protocol outcome. Application owners retain their protocol terminal publication and authenticated evidence obligations. Session Lifecycle defines the runtime retirement contract. Rust-Lean Parity records the host-lifecycle scope.
Related Docs
- Capability Admission
- Authority Language Surface
- Protocol Machine Architecture
- Verification Inventory
Typed Invocation Acceptance
A host response is an observation until the machine accepts it against the original request. Register state may change only for a successful outcome with the exact response family required by that request. Failure and blocked outcomes cannot authorize a state update by attaching a success-shaped payload. A malformed successful outcome is a failed contract, not a successful publication or a successful child in effect composition. The raw exchange remains available as an observation.
Nominal authority invocation additionally requires a declared operation, role-scoped execution under its actual session owner and generation, typed arguments and outcome, and one live continuation. Unknown declarations, stale owners, duplicate completion and invalidated continuations must fail before publication or continuation advancement. The generic runtime.step boundary does not yet implement this nominal contract. Naming an action is not dispatch or proof that the contract was checked.
Capability Admission
This document defines the capability gates that control runtime admission and profile selection.
Admission is not the same thing as current ownership. Admission answers whether a runtime/profile/configuration is allowed in principle. Ownership answers who may currently drive session-local host mutation for one live session or fragment.
Within the broader first-class protocol-critical capability model, admission is only one class beside ownership, evidence, and transition capability surfaces. Admission decides whether execution is allowed in principle. It does not itself prove current session authority or canonical semantic truth. It also does not provide a general-purpose application authorization system.
Gate Layers
Admission is enforced across Lean theorem-pack surfaces and Rust runtime checks.
| Layer | Main API |
|---|---|
| Lean theorem-pack facade | Runtime/Proofs/TheoremPack/API.lean |
| Lean capability inventory | Runtime/Proofs/TheoremPack/Inventory.lean |
| Lean admission logic | Runtime/Adequacy/EnvelopeCore/AdmissionLogic.lean |
| Rust runtime gates | rust/machine/src/runtime_contracts.rs |
| Rust composition admission | rust/machine/src/composition.rs |
The canonical cross-cutting capability taxonomy now lives in:
rust/machine/src/capabilities.rslean/Runtime/Proofs/Capabilities.lean
Runtime Gate Flow
Rust runtime admission uses a fixed gate sequence.
| Step | Function | Result class |
|---|---|---|
| advanced mode check | requires_protocol_machine_runtime_contracts | boolean requirement |
| runtime admission | admit_protocol_machine_runtime | Admitted or RejectedMissingContracts |
| profile request | request_determinism_profile | selected profile or None |
| unified gate | enforce_protocol_machine_runtime_gates | Admitted, RejectedMissingContracts, or RejectedUnsupportedDeterminismProfile |
The unified gate is the admission decision used by higher-level runtime loaders.
Admission vs Ownership
These concepts are intentionally separate.
| Question | Admission answers | Ownership answers |
|---|---|---|
| can this runtime mode/profile be used? | yes | no |
| does this theorem-pack/runtime-contract inventory admit the feature? | yes | no |
| may this caller mutate session-local host state right now? | no | yes |
| does this caller hold current fragment/session authority? | no | yes |
Practical consequence:
- passing
enforce_protocol_machine_runtime_gates(...)does not authorize session-local mutation - hosts still need a live ownership capability such as
OwnedSession - stale-owner rejection can occur even when admission remains valid
- a choreography
uses Runtime, Auditdeclaration states external protocol dependencies. It does not replace runtime admission or ownership checks
Effect Interfaces vs Admission
Language-level effect interfaces and runtime admission solve different problems.
| Surface | Main question |
|---|---|
effect declaration | what typed host operation does the protocol depend on? |
uses clause | which named external interfaces may this protocol call? |
| admission gate | may this runtime/profile/configuration run at all? |
| ownership capability | who may currently drive one live session or fragment? |
Nominal effect interfaces therefore narrow protocol dependencies without weakening theorem-pack admission or ownership enforcement.
Determinism Profiles
Determinism profiles are validated against artifacts and capability switches.
| Profile | Rust enum | Artifact flag |
|---|---|---|
| full | DeterminismMode::Full | determinism_artifacts.full |
| modulo effect trace | DeterminismMode::ModuloEffects | determinism_artifacts.modulo_effect_trace |
| modulo commutativity | DeterminismMode::ModuloCommutativity | determinism_artifacts.modulo_commutativity |
| replay | DeterminismMode::Replay | determinism_artifacts.replay |
Non-full profiles also require can_use_mixed_determinism_profiles.
Theorem-Pack Capability Inventory
The theorem-pack inventory publishes machine-readable capability keys. Keys are strings defined in lean/Runtime/Proofs/TheoremPack/Inventory.lean, not Lean theorem names. A key is present when the corresponding profile artifact is attached. For example, quorum_geometry_safety is backed by Distributed.QuorumGeometry.no_conflicting_commits_of_protocol, fork_exclusion_of_protocol, and safe_finality_of_protocol.
| Inventory family | Example keys |
|---|---|
| protocol safety and liveness | termination, output_condition_soundness |
| distributed impossibility and safety | flp_impossibility, cap_impossibility, quorum_geometry_safety |
| distributed deployment families | partial_synchrony_liveness, reconfiguration_safety, atomic_broadcast_ordering |
| advanced envelope families | consensus_envelope, failure_envelope, protocol_machine_envelope_adherence, protocol_machine_envelope_admission, protocol_envelope_bridge |
| classical transport families | classical_foster, classical_maxweight, classical_ldp, classical_functional_clt |
Rust capability snapshots should align with this inventory for release and admission claims.
Transported Theorem Boundary
Not every transported theorem family influences shipped runtime admission. Telltale now tracks that boundary explicitly in Lean and Rust.
| Usage class | Meaning | Current examples |
|---|---|---|
runtime_critical_instantiated_premise | materially influences a shipped runtime gate/guarantee surface | protocol_machine_envelope_adherence, protocol_machine_envelope_admission, protocol_envelope_bridge |
black_box_premise_only | used by verifier/reporting surfaces, but not consumed directly by shipped Rust runtime admission | flp_impossibility, quorum_geometry_safety, failure_envelope |
documentation_background_only | carried for theorem-program inventory/docs only | classical_foster, classical_functional_clt |
The canonical ledger lives in:
lean/Runtime/Proofs/TheoremPack/AdmissionBoundary.leanrust/machine/src/runtime_contracts.rs
One runtime-critical theorem family is intentionally marked as a boundary rather than a shipped Rust admission dependency today:
byzantine_safety_characterizationis consumed by a Lean theorem-pack gate alias, but Rust runtime admission does not currently use it. That gap is now explicit rather than implicit.
Source-Derived Rows
The following rows are source-derived and checked against
telltale_machine::transported_theorem_boundary() by
rust/bridge/tests/docs_contract_tests.rs.
| Key | Usage class | Rust runtime admission | Lean runtime gate | Assumption boundary |
|---|---|---|---|---|
byzantine_safety_characterization | runtime_critical_instantiated_premise | no | yes | Lean theorem-pack gate only. Rust runtime admission does not currently consume this key. |
protocol_machine_envelope_adherence | runtime_critical_instantiated_premise | yes | yes | none |
protocol_machine_envelope_admission | runtime_critical_instantiated_premise | yes | yes | none |
protocol_envelope_bridge | runtime_critical_instantiated_premise | yes | yes | none |
Rust admission of these three envelope keys requires Lean-produced evidence. execution_profile_supported consults the profile’s LeanEvidenceInventory (loaded from the Lean theoremInventory export) rather than self-asserted eligibility flags. Default profiles carry no evidence and are not admitted. assume_envelope_evidence_for_testing() is the explicit test-only opt-in. See docs/804_theorem_pack_inputs.md.
Composition Admission
Composed runtime admission in rust/machine/src/composition.rs enforces both proof artifacts and runtime gates.
| Requirement | Failure mode |
|---|---|
link_ok_full compatibility evidence | MissingCompatibilityProof |
| runtime contracts for advanced mode | MissingRuntimeContracts |
| required scheduler capability | MissingCapability |
| required determinism capability | MissingCapability |
| output-condition gating capability | MissingCapability |
| memory budget | BudgetExceeded |
This path guarantees that admitted bundles carry both semantic and operational evidence.
When composition metadata defines fragment or bundle boundaries, runtime ownership should derive from that metadata rather than from ad hoc host-side reconstruction. Admission decides whether the bundle may run. Ownership decides who may currently drive it.
Runtime Upgrade Admission
Typesafe runtime upgrade is treated as a specialized transition family layered on top of composition admission, not as an unmodeled host escape hatch.
The runtime-upgrade admission contract is carried by:
RuntimeUpgradeRequestRuntimeUpgradeCompatibilityRuntimeUpgradeArtifact
An admitted upgrade must make the following transition requirements explicit:
| Requirement | Current Rust enforcement |
|---|---|
| execution-profile compatibility | RuntimeUpgradeExecutionConstraint validated in rust/machine/src/composition.rs |
| ownership continuity across the first cutover | overlap check against the currently active member set |
| pending-effect treatment | PendingEffectTreatment must be explicit and fail closed on invalidated/blocked obligations |
| canonical publication continuity | CanonicalPublicationContinuity rejects invalidation when continuity is required |
The runtime records staged, admitted, committed-cutover, rolled-back, and
failed artifacts explicitly. Those artifacts are replay-visible through
ReconfigurationRuntimeSnapshot.runtime_upgrades.
Admission Diagnostics
Lean and Rust both expose structured rejection categories.
| Surface | Rejection classes |
|---|---|
| Lean admission logic | maxDiffExceeded, eqSafeNotSupported, missingRequiredProfiles |
| Rust runtime gate | RejectedMissingContracts, RejectedUnsupportedDeterminismProfile |
| Rust composition | typed CompositionError variants with capability key strings |
These categories are intended for machine-visible reporting and CI gate failures.
Governance and CI
Admission and capability drift are controlled by repository lanes.
| Check | Command |
|---|---|
| runtime capability gate shape | just check-capability-gates |
| theorem-pack release conformance | just check-release-conformance |
| Protocol-machine parity suite | just check-parity --suite |
| parity type and schema policy | just check-parity --types |
| consolidated parity policy | just check-parity --all |
Related Docs
Authority Language Surface
This page records the current authority/failure-oriented DSL surface added on top of the choreography parser.
It is the current reference page for language-level authority checks, typed failure branching, and nominal effect interfaces. It is intentionally narrower than a full generalized effect language. General host application authorization is out of scope.
Current Surface
Top-level declarations:
type Name = | Ctor | Ctor(Payload)type alias Name ={field : Type}effect Runtimeauthoritative ready : Session -> Result CommitError ReadyWitnessprotocol Flow uses Runtime =roles Coordinator, Workerauthoritative let witness = check Runtime.ready(session)
Protocol-local forms:
authoritative let binding = check Runtime.op(args)observe let binding = observe Effect.op(args)let receipt = transfer Session from A to Blet binding = ... in ...case expr of | Ok(value) => ... | Err(reason) => ...timeout 5s Coordinator at ... on timeout => ... on cancel => ...choice Coordinator at | Commit when check Runtime.ready(session) yields witness => ...
Review Sketches
These are the short canonical examples for the current reviewed language additions.
Nominal Effects and uses
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
observe watchPresence : Session -> PresenceView
{
class : observational
progress : immediate
region : session
agreement_use : forbidden
reentrancy : allow
}
protocol Flow uses Runtime =
roles Coordinator, Worker
authoritative let witness = check Runtime.ready(session)
Coordinator -> Worker : Commit
This is the smallest effect declaration plus dependency declaration that the language accepts.
Result with case/of
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(reason) =>
Coordinator -> Worker : Cancel(reason)
This is the canonical typed branching form for authority checks.
Maybe
case maybeReceipt of
| Just(receipt) =>
Coordinator -> Worker : UseReceipt(receipt)
| Nothing =>
Coordinator -> Worker : MissingReceipt
This keeps absence explicit instead of falling back to implicit defaults.
Custom Union and Alias
type CommitError =
| TimedOut
| Cancelled
type alias ReadyWitness =
{ epoch : Int
}
Custom unions and aliases name failure and evidence values directly in the DSL.
Evidence Binding with let
let receipt = transfer Session from Coordinator to Worker
handoff acceptInvite to Worker with receipt
Ordinary let syntax is the only binding form used for receipts and other authority artifacts.
Timeout and Cancellation
timeout 5s Coordinator at
Worker -> Coordinator : Ready
on timeout =>
Coordinator -> Worker : Cancel
on cancel =>
Coordinator -> Worker : Cancelled
Timeout and cancellation are explicit protocol branches, not host-only control flow.
Evidence Guard
choice Coordinator at
| Commit when check Runtime.ready(session) yields witness =>
Coordinator -> Worker : Commit(witness)
| Abort =>
Coordinator -> Worker : Abort
Evidence guards bind the witness directly at the branch point that authorizes the action.
Linear Single-Use Binding
let receipt = transfer Session from Coordinator to Worker
handoff acceptInvite to Worker with receipt
Transfers produce first-class transition receipts. The compiler requires each receipt to be consumed exactly once, and the runtime/Lean model treats the receipt as a transition artifact rather than an untyped helper value.
Local Helper Expression
authoritative let readiness = check Runtime.ready(session) in
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(reason) =>
Coordinator -> Worker : Cancel(reason)
let ... in ... keeps small authority decisions local instead of pushing them into host callbacks.
No Implicit Default
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(reason) =>
Coordinator -> Worker : Cancel(reason)
Protocol-critical matches must be exhaustive. Implicit catch-all masking is rejected.
Typed External Query
authoritative let readiness = check Runtime.ready(session)
Typed external queries always enter the protocol through explicit check expressions.
Result and Maybe
The DSL currently treats Result and Maybe as built-in naming conventions rather than a separate kind system.
Resultconstructors:Ok,ErrMaybeconstructors:Just,Nothing
Current compiler rules:
case/ofoverResultmust include bothOkandErrcase/ofoverMaybemust include bothJustandNothing- implicit catch-all masking is not supported
Let and Linearity
let is the normal binding form for transition receipts and non-authoritative
query results. authoritative let and observe let are the explicit binding
forms for protocol-critical authoritative and observational reads.
DSL Capability Mapping
| DSL form | Capability class | Lifecycle emphasis |
|---|---|---|
authoritative let x = check ... | evidence | issued -> consumed/rejected |
observe let x = observe ... | evidence | observed-only input, never canonical directly |
let receipt = transfer ... | transition | issued -> committed/rolled_back/rejected/invalidated |
publish witness as Publication | evidence | authoritative -> published |
materialize proof from Publication | evidence | materialized/rejected |
handoff op to Role with receipt | transition | committed/rolled_back/invalidated |
case/of over evidence | control-flow over evidence | must preserve linear assets and remain exhaustive |
timeout ... on timeout ... on cancel ... | evidence/transition-adjacent control-flow | explicit timeout/cancellation outcomes with replay-visible lifecycle |
Current linear classification:
transfer ...bindings are linear first-class transition receiptsauthoritative let check ...bindings are first-class evidence readsobserve let observe ...bindings are first-class observational reads and may not be used where authoritative evidence is required
Current linear rules:
- linear bindings must be consumed exactly once
- duplicate use is rejected
- implicit discard is rejected
Current cross-construct rule:
- the implementation counts use across subsequent statements and across explicit branch bodies
- the parser and protocol-machine surface support
choice,case,timeout,loop,par,call, and recursion with explicit linear preservation checks - rejected programs fail closed when linear assets diverge across branches, timeout/cancel paths, loop iterations, recursive unfoldings, or parallel arms
- observational bindings remain separated from authoritative evidence under these control-flow forms
Explicit Scope Boundary
The DSL currently gives first-class meaning to:
- authoritative reads
- observed reads
- transfer receipts
- publication events
- materialization proofs
- canonical handles
- semantic handoffs
The DSL does not currently give first-class syntax to runtime upgrades or reconfiguration statements. Those remain runtime/model surfaces, not choreography statements, until they have a real shared Rust and Lean meaning.
Authority Metatheory Boundary
The authority theorem story is intentionally narrower than the executable DSL surface.
Current proof-plane split:
- ordinary communication, recursion, and other coordination-only structure remain part of the session-typed theorem story
- the supported authority-bearing theorem slice lives in the protocol-machine semantic-object layer, not in a generalized session-type extension
Current supported authority theorem slice:
- evidence-bearing reads introduced by
authoritative let,observe let, and evidence guards - canonical publication/materialization paths introduced by
publish ... as ...andmaterialize ... from ... - explicit progress contracts for parity-critical operations protecting those paths
Current runtime-semantic-only authority surfaces:
case/oftimeout ... on timeout ... on cancel ...par- transfer-produced receipts and their consumption
handoffdependent work- explicit commitment lifecycle forms such as
begin,await,resolve, andinvalidate
These forms name protocol-machine semantic obligations. Concrete executable lowering is entry-point-specific, as described in the source-derived support matrix and prepared enclosing-operation slice. Parser acceptance, generated metadata and communication projection do not establish that an authority obligation is executed. Protocol-machine conservation theorems retain their declared premises and do not supply a missing source-to-machine lowering. These forms remain outside the smallest authority-specific theorem slice.
Effect Declarations and Uses
effect declarations are nominal interfaces.
Current validation rules:
- effect interface names must be unique
- operation names must be unique within an effect
- effect operations may be classified as
authoritative,command, orobserve protocol ... uses ...may reference only declared effectscheck Effect.op(...)and evidence guards may reference only:- effects named in
uses - operations declared on those effects
- effects named in
check Effect.op(...)may not targetobserveoperationsobserve Effect.op(...)may reference only observational operations
This is the current clean-break contract.
There is no fallback to implicit host knowledge.
Lowering is expected to preserve this split into the runtime semantic-object
family as AuthoritativeRead versus ObservedRead. Observational reads are not
allowed to become proof-bearing success or canonical publication by repair.
Typed Host Effect Invocation
check Effect.op(...) is the language-level way to invoke typed host effects.
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
protocol Flow uses Runtime =
roles Coordinator, Worker
authoritative let readiness = check Runtime.ready(session)
Coordinator -> Worker : Commit
Current contract:
- the protocol must declare the effect in
uses - the operation must exist on the named nominal interface
checkis for authoritative or command operationsobserveis for observational operations- lowering stays centered on the existing protocol-machine
invokeboundary - effect observations keep the nominal interface and operation identity for replay/audit surfaces
Projection and Theory Boundary
Current projection behavior is intentionally explicit:
letis treated as local-only and projects through to its continuationcase/ofis currently rejected by projectiontimeout ... on timeout ... on cancel ...is currently rejected by projection- accepted authority-aware
choice,call,loop,par, and recursion forms remain protocol-machine executable, and the subset without other blockers remains session-projectable
This preserves MPST clarity while the explicit projection rules are still being designed.
Theory conversion is narrower than projection today:
let,case/of, andtimeoutmust be lowered further before conversion to theory-facing global typeschoice,call, counted loops, and recursion convert today when the surrounding protocol stays in the common subsetparis session-projectable and protocol-machine executable, but it still has no theory-facing global-type conversion and fails closed with an explicitParallelblocker
Source-Derived Support Matrix
The following rows are source-derived from the checked authority fixture corpus
and validated by rust/bridge/tests/docs_contract_tests.rs.
| Fixture surface | Protocol-machine executable | Session-projectable | Theory-convertible | Authority theorem tier | Explicit blocker |
|---|---|---|---|---|---|
call with plain communication | yes | yes | yes | session_typed_coordination | none |
choice with observational binding | yes | yes | no | evidence_publication_semantic_objects | AuthorityBinding |
counted loop with authoritative binding | yes | yes | no | evidence_publication_semantic_objects | AuthorityBinding |
| recursion with authoritative binding | yes | yes | no | evidence_publication_semantic_objects | AuthorityBinding |
par with observational binding | yes | yes | no | runtime_semantic_only | AuthorityBinding |
case/of with authoritative binding | yes | yes | no | runtime_semantic_only | AuthorityBinding |
timeout with observational binding | yes | yes | no | runtime_semantic_only | AuthorityBinding |
Explanatory prose below remains hand-written. The table above is the checked contract surface for the reviewed authority/control-flow subset.
Lowering Boundary
The current lowering split is:
- parser/AST surface in
rust/language - protocol-machine effect execution boundary in
rust/machine - typed handler boundary centered on protocol-machine
invoke
Current intent:
effectdeclarations become the source for generated Rust host interfacescheck Effect.op(...)remains a DSL-level reference to a typed external query- execution must still cross the same typed handler-obligation boundary rather than inventing a second host channel
- the Lean bridge statement for nominal effect declarations stays centered on the existing protocol-machine
invokeobligation rather than a separate effect-language proof surface
Observable Trace Model
Language-level effect usage is expected to remain observable through the same runtime/effect tracing surfaces used by protocol-machine replay and effect-bisimulation work.
Current design rule:
- language-level authority checks and evidence-sensitive decisions must map to explicit effect operations and explicit protocol-machine-visible events
- missing or ambiguous authoritative input must become typed failure, never silent fallback success
- canonical replay artifacts must preserve these decisions as structured semantic audit records rather than opaque host-only log text
Source-Derived Boundary Rows
The rows below are source-derived and checked against
telltale_machine::protocol_critical_capability_boundary() by
rust/bridge/tests/docs_contract_tests.rs.
| Surface | Class | Lifecycle | Rust boundary | Lean boundary | Rationale |
|---|---|---|---|---|---|
runtime_admission | admission | issued, rejected | rust/machine/src/runtime_contracts.rs | lean/Runtime/Proofs/TheoremPack/AdmissionBoundary.lean | Admits or rejects runtime/profile execution before protocol-critical execution begins. |
theorem_pack_capabilities | admission | issued, rejected | rust/machine/src/composition.rs | lean/Runtime/Proofs/TheoremPack/API.lean | Carries proof-backed eligibility that higher-level runtime admission consumes. |
ownership_capability | ownership | issued, invalidated, expired, rejected | rust/machine/src/session/overview.rs | lean/Runtime/Proofs/Conservation/Authority.lean | Proves which actor may currently mutate session-local protocol-critical state. |
readiness_witness | evidence | issued, consumed, rejected, invalidated, expired | rust/machine/src/session/overview.rs | lean/Runtime/Proofs/AuthorityMetatheory.lean | Justifies a protocol-critical readiness decision under one live owner generation. |
authoritative_read | evidence | issued, consumed, rejected | rust/machine/src/semantic_objects.rs | lean/Runtime/Proofs/Conservation/Evidence.lean | Carries evidence-bearing protocol input that may author canonical truth. |
materialization_proof | evidence | issued, consumed, rejected | rust/machine/src/semantic_objects.rs | lean/Runtime/Proofs/Conservation/Evidence.lean | Witnesses proof-bearing success on the sanctioned materialization path. |
canonical_handle | evidence | issued, consumed, rejected, invalidated | rust/machine/src/semantic_objects.rs | lean/Runtime/Proofs/Conservation/Evidence.lean | Provides the strong reference required on parity-critical follow-on paths. |
ownership_receipt | transition | issued, committed, rolled_back, rejected, invalidated, expired | rust/machine/src/session/overview.rs | lean/Runtime/Proofs/Conservation/Authority.lean | Stages and commits explicit ownership transfer rather than ambient authority mutation. |
semantic_handoff | transition | committed, rolled_back, rejected, invalidated | rust/machine/src/semantic_objects.rs | lean/Runtime/Proofs/Conservation/Authority.lean | Represents explicit protocol-visible authority transfer and old-owner revocation. |
reconfiguration_transition | transition | issued, committed, rolled_back, rejected | rust/machine/src/composition.rs | lean/Runtime/Proofs/ReconfigurationObserver.lean | Captures protocol-critical cutover and membership/runtime transition artifacts. |
Runtime Meaning
Language-level authority checks lower into the existing runtime authority surfaces:
check Effect.op(...)lowers to the typed protocol-machine effect boundary- successful protocol-critical checks produce explicit evidence/witness objects
- invalid or missing evidence fails closed
- timeout and cancellation become explicit observable/runtime-audit outcomes
- transfer/delegation emits explicit receipts and audit records
- stale capability, receipt, and witness reuse are rejected and retained in the lifecycle audit
This keeps the language aligned with EffectHandler typed outcomes,
OwnedSession ownership capability checks, the witness family
(ReadinessWitness, CancellationWitness, TimeoutWitness), and
semantic_audit_log replay-visible event ordering.
Fail-Closed Rules
Protocol-critical authority must not degrade into ambient host heuristics.
- no implicit wildcard/default masking for
ResultandMaybe - no ad hoc “missing data means false” authority checks
- no silent reuse of stale capabilities or receipts
- no hidden host-only branch decisions outside typed effect queries
This design does not create a second host/runtime bridge. The authoritative
path is: declare a nominal effect, name it in uses, invoke it with
check Effect.op(...), lower it into the existing typed protocol-machine
invoke boundary, and preserve its outcome in effect/audit/replay surfaces.
Communication Artifact Refusal
Communication projection remains a type-level abstraction. Plain, non-linear identifier and constructor bindings have no invocation node and may project through to communication. Their identifier-only constructor payload grammar cannot contain an effect call. This does not evaluate a local value or grant authority. Calls are not presumed pure: a plain call can contain an effect-call argument and has no checked purity contract.
Theory conversion and integration executable-artifact requests now reject explicit authoritative/observational modes, check/observe/transfer expressions, linear aliases, and unclassified calls instead of silently erasing them. The local artifact request checks the strongest original AST before consuming its already projected communication view. Scope shadowing cannot hide a preceding authority binding from that whole-AST check. The generated-effects entry point also refuses these bindings, including those nested in control flow.
The table’s protocol-machine-executable classification describes the existing AST semantic surface, not proof that these bindings lower through a communication-only CodeImage. Positive effect-preserving authority lowering is still unavailable on that path. The supported Lean semantic-object theorems do not supply the missing compiler correspondence.
Explicit Local Binding Placement Prerequisite
A binding may spell its execution placement as let value at Coordinator = expr,
with the same at Role clause after the binding name for authoritative and
observational forms and for let ... in. The role reference must name a declared
role. Indexed references retain the existing role-reference syntax. Placement is
preserved in the AST, cloning and continuation composition. An omitted clause
remains unplaced. It does not choose the first role or the next sender.
This is a source representation prerequisite, not executable authority lowering.
An explicitly placed binding requires effect-preserving lowering even when its
expression is plain. Communication theory conversion, direct local projection
and generated-effects artifact construction refuse it. Generic communication
programs and runtime.step keep their existing semantics. Placement does not
issue a session capability, resolve a role family to an original endpoint, name a
nominal operation at runtime, or authorize a continuation.
A future admitted nominal construct must bind this source placement to the actual coroutine’s session endpoint and generation, declared operation and argument/result types, then retain one issued continuation through its accepted outcome. The existing communication-only LocalTypeR and total Lean EffectRuntime.exec contract cannot express that transaction. They are unchanged by the placement prerequisite. Positive nominal execution and correspondence proof remain required before authority bindings can be admitted.
First Nominal Command Execution Contract
The selected-operation compiler admits an explicitly placed, declared Command
with Unit input and Result String Bool output, followed immediately by an
exhaustive Ok/Err case whose branches each send a distinct terminal label to the
same peer. This is a command result, not authoritative evidence. Application
Err, host Failure and a pending host outcome are distinct states.
The enclosing operation retains its source-selected NoAgreement profile, owner role and named progress window. That window requires an explicit nonzero tick budget. Missing configuration and unsupported agreement or escalation semantics refuse admission. Model ticks do not replace a host’s physical deadline.
An executable nominal call requires an actually issued owned-session handle,
the original session generation and role endpoint, its exact admitted nominal
declaration and a live canonical effect/operation occurrence. The coroutine
retains the original nonserialized continuation and any returned typed result
through commit gates. A pending response resumes that same occurrence and
progress owner, without resetting the window. Admission and result commit require
current_tick <= original_issued_tick + selected_budget, with checked overflow
refused before issuance. Multi-window progress escalation cannot extend this
original endpoint even while its canonical effect still reports Blocked. A changed semantic handler
identity, stale generation, invalidated occurrence or restored checkpoint without
that original handle cannot invoke the callback or consume the continuation.
Handler identity is a semantic contract identifier. Equality does not prove
physical provider identity or host-future teardown.
The compiler does not admit arbitrary expressions, authority-producing checks, readiness, publication, materialization, transfer or handoff by this command contract. Ordinary communication conversion continues to refuse explicitly placed authority expressions. Both backend producers and an executable nominal Lean transition are prepared source. Their fixtures and correspondence facts must be checked before any execution or parity claim. This is not full lowering of the remaining authority/evidence forms.
Executable Source Operation Continuations
begin, await, resolve, and invalidate describe one original operation
continuation on its actual executing coroutine. begin does not spawn another
runtime, thread or task, and does not invoke an unspecified host callback. It
selects the declared operation, explicit owner role, agreement profile and original
logical progress window. Its live continuation retains the actual issued session
owner. Serialized declaration fields cannot reconstruct it.
A selected operation body retains the validated parent role scope and its original declaration. Its explicit owner must belong to that scope, and every referenced role must remain declared. An empty local operation need not invent communication for every parent role. Ordinary complete choreographies still require all declared roles to participate.
The selected source compiler binds its declared nominal callbacks as explicit
children in the existing operation/effect/progress records. Communication and
control instructions remain on the actual coroutine continuation. Incidental
observational exchanges are not invented as additional declared child slots. A child is identified
by its original canonical occurrence, not by a later effect name, last trace row,
or a newly constructed equal-valued request. await observes this admitted set.
A missing, ambiguous or pruned admitted child refuses. It never counts as terminal
success. Live children keep the same operation and PC pending. An operation with
no admitted children may pass this child-quiescence condition, but this neither
executes omitted work nor proves any required dependent work was performed.
Original unsuccessful child outcomes remain typed failures on the continuation.
resolve retains its proposed typed result before the final original-owner,
window and child-completion gates. A refusal cannot substitute another result,
reset the original window, or discard the original child failure. Duplicate
resolution is rejected. An application Success status does not satisfy a
selected proof requirement or create a publication, materialization proof or
canonical handle. Those require their own genuine issuing/consuming transitions.
invalidate revokes the original semantic continuation and its still-live child
occurrences. Late results cannot commit through that continuation. Invalidation
is not host-future cancellation or physical resource settlement. Those resources
and their actual completion obligations remain under the original host owner.
An executable handoff must transport this actual original continuation to an
eligible target, including its child identities, selected original window,
retained result and failure. Changing a session’s host owner, moving an endpoint,
or emitting a semantic record alone does not execute handoff. Static role names
may identify a destination but cannot replace the original issued owner gate.
Prepared authoritative readiness command slice
compile_command_operation additionally lowers an explicitly placed
authoritative let result at A = check Host.ready() when the admitted nominal
operation is authoritative, class : authoritative, progress : may_block,
region : session, Unit input and Result String ReadinessWitness output.
The existing explicit NoAgreement profile and selected named simulation-tick
budget remain required. This bounded slice branches to terminal unit-message
continuations. It does not implement witness transport, publication,
materialization, or a general authority expression interpreter.
The callback returns a typed NominalCommandValue::Readiness, distinct from
Bool. The predicate is the exact source interface and operation joined by
.. Before executing the success continuation, both backends consume the
original registered witness using the retained issued session owner, after
output and WAL admission and destination validation. The registry checks exact
issuance, session, owner, generation, full-session scope and single use.
A Boolean, copied serialized field set, stale owner, wrong predicate, or foreign
session cannot replace that check. Gate refusal keeps the original callback
result resident and leaves the witness unconsumed. Transfer/rollback remains
the canonical session transaction. This lowering adds no transfer registry.
These implementation and fixture declarations are source-only until their actual Rust harness discovery/execution and Lean elaboration succeed.
Prepared enclosing-operation executable slice
compile_operation_image selects the checked original operation body. The
current enclosing slice supports an explicit Begin and either an empty body or
one placed nominal Bool/readiness command with distinct terminal unit-message
branches. Each child branch retains its explicit Await/Resolve tail. The static
manifest binds each required child to the complete installed typed call and PC.
Role-image assembly and slicing relocate both, while the loader verifies them
against the actual installed opcode. Skipping required work cannot produce
empty-inventory success. A Halt cannot abandon an unresolved original parent.
Begin, child invocation, result commit and explicit resolution retain one original issued owner and logical window. All nominal calls, including Bool commands, require that owner. Ordinary loading, checkpoint observations and a new registry generation cannot refresh a stale coroutine. Actual callback failure and independent output refusal retain the first typed execution fault and original returned outcome while revoking protocol commit. No revocation or application cancellation acknowledges physical host cleanup.
In this same-coroutine slice, a Blocked nominal child retains its original call PC until that child returns. This is not evidence of a separately pending Await. The existing scheduler has original-child wake semantics, but the genuine cross-continuation dependent-work producer, executable handoff, general cancellation/timeout escalation, stronger agreement, publication and materialization remain required implementation work. Unsupported forms refuse. That refusal does not complete those requirements. The prepared source and model examples require actual compilation and execution before acceptance.
Lean Verification
This document summarizes the current Lean implementation surface and the proof APIs consumed by runtime gates.
Source of Truth
Verification scale and proof-hole status are tracked by generated reports.
| Source | Purpose |
|---|---|
| Lean Verification Code Map | generated library map with file counts and module inventory |
just escape | machine check for axiom and sorry budget |
These generated artifacts are the canonical sources for current scale and proof-hole status.
Library Layers
The Lean tree is organized as a layered stack.
| Layer | Main content |
|---|---|
| SessionTypes | global and local syntax, de Bruijn forms, conversions |
| SessionCoTypes | coinductive equality, bisimulation, decidable regular checks |
| Choreography | projection, blindness, erasure, harmony |
| Semantics | small-step semantics, determinism, deadlock surfaces |
| Protocol | coherence, typing, monitoring, deployment composition |
| Runtime | protocol-machine model, semantics, runtime adapters, theorem-pack APIs |
| Distributed | FLP, CAP, quorum, synchrony, Nakamoto, reconfiguration, safety families |
| Classical | transported queueing and stochastic theorem families |
| ClassicalAnalysisInstance | real-analysis-backed concrete models used by classical transport |
| IrisExtractionInstance | runtime proof extraction and ghost logic bridge |
Protocol-Machine Model and Runtime Surfaces
The protocol-machine model is centered under lean/Runtime/ProtocolMachine.
| Surface | Location |
|---|---|
| Core instruction and state model | Runtime/ProtocolMachine/Model/* |
| Executable semantics | Runtime/ProtocolMachine/Semantics/* |
| Runtime adapters and monitor | Runtime/ProtocolMachine/Runtime/* |
| Composition and domain instances | Runtime/ProtocolMachine/Composition.lean |
The effect model uses the current split EffectRuntime and EffectSpec. Monitor typing lives in Runtime/ProtocolMachine/Runtime/Monitor.lean.
Heap Verification and Parity Surface
The runtime heap now has a focused Lean mirror for the contract that must stay stable across implementations.
| Surface | Location |
|---|---|
| Canonical heap bytes, tagged preimages, ordering rules, replay interpreter | Runtime/Resources/HeapModel.lean |
| Determinism lemmas for the executable heap model | Runtime/Proofs/Heap.lean |
| Executable heap parity runner | Runtime/Tests/HeapParityRunner.lean |
This Lean heap lane is intentionally narrower than a full cryptographic model. Lean owns canonical encoding, tagged preimages, active/nullifier ordering, proof-index semantics, and deterministic replay for the published fixture corpus. The BLAKE3 digest function remains an operational assumption checked through the published heap vectors and the Rust↔Lean bridge suite.
Semantic Objects Model
The semantic objects layer lives under Runtime/ProtocolMachine/Model/SemanticObjects/.
| Surface | Location |
|---|---|
| Identity, ownership, observed-read discipline | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean, Discipline.lean |
| Deferred-effect admissibility and stale late-result rejection | Runtime/ProtocolMachine/Model/SemanticObjects/OutstandingEffects.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/OutstandingEffects.lean |
| Semantic handoff activation and delegation bridge | Runtime/ProtocolMachine/Model/SemanticObjects/SemanticHandoffTransition.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/SemanticHandoff.lean |
| Authoritative-read commitment and publication projection | Runtime/ProtocolMachine/Model/SemanticObjects/AuthoritativeReadsPublication.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/AuthoritativeReadsPublication.lean |
| Materialization-proof adequacy and canonical-handle adequacy | Runtime/ProtocolMachine/Model/SemanticObjects/MaterializationSuccess.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/MaterializationSuccess.lean |
| First-class capability/finalization and runtime-upgrade facade | lean/Runtime/Proofs/CapabilityModel.lean, lean/Runtime/Tests/ProtocolMachineRunner.lean |
| Progress-contract semantics and escalation lemmas | Runtime/ProtocolMachine/Model/SemanticObjects/ProgressContracts.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/ProgressContracts.lean |
| Transformation-local obligation bundles | Runtime/ProtocolMachine/Model/SemanticObjects/TransformationLocalObligations.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/TransformationLocalObligations.lean |
| Replay-failure exactness | Runtime/ProtocolMachine/Model/SemanticObjects/ReplayFailureExactness.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/ReplayFailureExactness.lean |
| Cross-target progress and dependent work | Runtime/ProtocolMachine/Model/SemanticObjects/CrossTargetProgressDependentWork.lean, Runtime/Proofs/ProtocolMachine/SemanticObjects/CrossTargetProgressDependentWork.lean |
Semantic-object theorem families attach to theorem-pack proof spaces through Runtime/Proofs/InvariantSpace.lean via SemanticObjectWitnessBundle.
Proof Packs and Admission APIs
Runtime proof inventory is exported through theorem-pack modules.
| Surface | Purpose |
|---|---|
Runtime/Proofs/TheoremPack/API.lean | public theorem-pack facade and runtime gate aliases |
Runtime/Proofs/TheoremPack/Inventory.lean | capability inventory keys and determinism extension |
Runtime/Proofs/TheoremPack/ReleaseConformance.lean | release gate and replay conformance report |
Runtime/Adequacy/EnvelopeCore/AdmissionLogic.lean | admission soundness, completeness, diagnostics vocabulary |
These APIs are consumed by Rust runtime gates and composition admission checks.
The capability/finalization bridge surface is also consumed directly by the
strict correspondence lane through inspectCapabilityModel, which keeps the
Lean model for finalization paths and runtime-upgrade artifacts in executable
alignment with Rust.
Premise-Scoped Interfaces
Some surfaces are intentionally premise-scoped.
| Interface class | Example |
|---|---|
Threaded refinement beyond n = 1 | ThreadedRoundRefinementPremises |
| Envelope admission and profile diagnostics | admission protocol structures in AdmissionLogic.lean |
| Mixed-profile runtime gates | theorem-pack and runtime-contract capability checks |
These are explicit interfaces and not unconditional global theorems.
Distributed and Classical Reach
Distributed and classical families are part of the active theorem-pack space. They are not side notes.
Distributed families include FLP, CAP, quorum geometry, partial synchrony, responsiveness, Nakamoto security, reconfiguration, atomic broadcast, accountable safety, failure detectors, data availability, coordination, CRDT, Byzantine safety, consensus envelope, and failure envelope.
Classical transport families include Foster-Lyapunov, MaxWeight, large deviations, mean-field, heavy-traffic, mixing, fluid limits, concentration bounds, Little’s law, functional CLT, and spectral-gap termination. The runtime proof-pack path is Classical.Profiles / Protocol.Classical constructors, Runtime.Proofs.Adapters.Classical profiles, ProtocolMachineInvariantSpaceWithProfiles setters, and buildProtocolMachineTheoremPack artifacts. These families are honest conditional interfaces: the named classical theorems are assumed inputs carried by witness fields (or the family proves only an elementary identity), not Lean proofs. See classicalEvidenceInventory and Theorem Pack Inputs.
Real-analysis facts used by these families remain explicit behind the ClassicalAnalysisAPI.lean and ClassicalAnalysisInstance.lean trust boundary. The Classical proof audit rejects new theorem-shaped assumptions outside named API/Instance boundaries.
Runtime Alignment Lanes
Lean and Rust alignment is checked by automation lanes.
| Lane | Command |
|---|---|
| Runtime capability gates | just check-capability-gates |
| Release conformance | just check-release-conformance |
| Protocol-machine parity suite | just check-parity --suite |
| Type and schema parity | just check-parity --types |
| Conformance-specific parity lane | just check-parity --conformance |
| Consolidated parity lane | just check-parity --all |
| Heap parity lane | cargo test -p telltale-bridge --test heap_lean_parity |
Related Docs
| rust-communication-hash-model | telltale-machine runtime maintainers | Active, runtime-only | Lean models communication nullifiers and replay roots as injective reprStr preimages; Rust digests the same preimage with the configured HashModel | Hash choice changes digest bytes only, not acceptance: both sides reject exactly the repeated preimages for the selected CommunicationNullifierIdentity; default model digests are unchanged | Modeling a concrete hash in Lean would add a collision assumption without strengthening the replay contract | 2027-04-08 | Rust unit and engine regressions cover default digest stability, custom-model determinism, content-only duplicate rejection and fail-closed deserialization; Lean runtime tests 41a and 41b cover both identity scopes |
| CommunicationNullifierIdentity variants (sequence_bound, content_only) | Runtime/ProtocolMachine/Model/Config.lean, Runtime/ProtocolMachine/Model/State.lean | rust/machine/src/communication_replay/identity.rs | Aligned |
Rust-Lean Bridge and Parity
This document defines the typed Rust<->Lean bridge surface and the parity contract enforced across that boundary for protocol-machine behavior, choreography projection, semantic-object schemas, and deviation governance.
Contract Levels
Parity is enforced at two levels. Level 1 covers policy and data shape parity for shared runtime encodings. Level 2 covers behavior parity for executable traces under the declared concurrency envelope.
Bridge Scope
The telltale-bridge crate is the typed boundary between Rust artifacts and Lean validation entrypoints.
It handles JSON conversion, schema versioning, runner invocation, trace comparison, semantic-object export, and typed invariant bundle export.
The bridge does not define protocol-machine semantics.
Semantics remain in telltale-machine, telltale-theory, and Lean runtime modules.
Host-runtime handlers also remain outside the bridge and re-enter through typed effect surfaces.
The same reduced-fixture parity style also applies to telltale-search for
the concurrency-critical slice. That lane covers:
- canonical min-key batch extraction
- proposal independence over declared authority surfaces
- replay epoch and phase contracts
- epoch-barrier transitions
- fairness-bundle fixture alignment
It is intentionally reduced. It does not attempt to prove full ARA* optimality or every scheduler policy.
This document covers the bridge behavior implemented in rust/bridge/src:
exportimportschemarunnerprotocol_machine_runnerheap_parity_runnersim_referencesemantic_objectsprotocol_machine_traceinvariantsequivalencevalidate
Bridge Crate Surface
The canonical public bridge surface uses:
LeanRunnerProtocolMachineRunnerHeapParityRunnerProtocolMachineRunInputProtocolMachineRunOutputHeapParityOutputProtocolMachineSemanticObjects
No public legacy compatibility modules are part of the supported bridge story.
Features and Binaries
The crate has feature-gated modules and binaries. The default feature set enables Lean runner integration.
[features]
default = ["runner"]
cli = ["clap"]
runner = ["telltale-theory"]
exporter = ["telltale-runtime", "anyhow", "bpaf"]
golden = ["clap", "anyhow", "runner"]
[[bin]]
name = "lean-bridge"
required-features = ["cli"]
[[bin]]
name = "lean-bridge-exporter"
required-features = ["exporter"]
[[bin]]
name = "golden"
required-features = ["golden"]
runner controls LeanRunner, ProtocolMachineRunner, HeapParityRunner, and the equivalence modules.
Without runner, conversion and schema modules remain available.
Conversion and Schema Families
export::global_to_json() and export::local_to_json() map Rust protocol types into Lean-compatible JSON.
import::json_to_global() and import::json_to_local() parse those JSON forms back into GlobalType and LocalTypeR.
The bridge defines three main schema families with explicit version constants:
| Family | Version Constant | Value | Primary Payloads |
|---|---|---|---|
| Lean bridge core | LEAN_BRIDGE_SCHEMA_VERSION | lean_bridge.v1 | ProtocolMachineRunInput, ProtocolMachineRunOutput, SimRunInput, SimRunOutput, replay bundles |
| Protocol bundles | PROTOCOL_BUNDLE_SCHEMA_VERSION | protocol_bundle.v1 | ProtocolBundle |
| Protocol-machine semantic objects | SEMANTIC_OBJECTS_SCHEMA_VERSION | protocol_machine.semantic_objects.v1 | ProtocolMachineSemanticObjects export payloads |
schema::ensure_supported_schema_version() rejects unsupported bridge-core versions.
Unsupported or missing schema versions are rejected rather than normalized.
Lean Runners
LeanRunner invokes the validator binary at lean/.lake/build/bin/telltale_validator.
It supports validation and projection export workflows.
ProtocolMachineRunner invokes lean/.lake/build/bin/protocol_machine_runner.
It serializes ProtocolMachineRunInput to stdin and parses ProtocolMachineRunOutput from stdout.
It also exposes validate_trace(), run_reference_simulation(), validate_simulation_trace(), verify_invariants(), and compare_execution().
HeapParityRunner invokes lean/.lake/build/bin/heap_parity_runner.
It loads the published heap parity corpus and returns typed HeapParityOutput values for strict Rust<->Lean comparison.
Semantic Objects and Trace Normalization
semantic_objects provides the canonical bridge payloads for the protocol-machine semantic-object family.
ProtocolMachineRunOutput.semantic_objects and ProtocolMachineRunOutput.effect_exchanges are the canonical bridge-side exports for semantic runtime state and typed effect exchange data.
protocol_machine_trace::event_session() extracts session ids from common event shapes.
normalize_semantic_audit() rewrites tick values into per-session local counters.
semantic_audits_equivalent() and observationally_equivalent() compare traces after that normalization.
Invariant, Equivalence, and Validator Utilities
invariants defines typed claim schemas for Lean-side protocol bundle verification through ProtocolBundle.
equivalence::EquivalenceChecker supports both golden-file comparison and live Lean comparison.
validate::Validator provides lightweight checks for projection, roundtrip, and subtype-related workflows that do not require full protocol-machine execution.
Bridge Test and Tooling Lanes
Bridge tests in rust/bridge/tests cover conversion, projection parity, schema compatibility, invariant verification, protocol-machine correspondence, and heap parity.
Examples include:
lean_integration_tests.rslean_trace_validation.rsprojection_runner_tests.rsprotocol_machine_correspondence_tests.rsprotocol_machine_differential_steps.rsheap_lean_parity.rscapability_model_correspondence.rsprotocol_bundle_admission_contracts.rs
These lanes align with repository-level gates such as just check-parity --types, just check-parity --suite, and just check-capability-gates.
Protocol-Machine Policy and Data Shapes
The following shapes must remain aligned between Lean and Rust unless a deviation entry is active.
| Area | Lean Surface | Rust Surface | Status |
|---|---|---|---|
FlowPolicy variants | Runtime/ProtocolMachine/Model/Kledge.lean | rust/machine/src/engine/ | Aligned |
FlowPredicate variants | Runtime/ProtocolMachine/Model/Kledge.lean | rust/machine/src/engine/ | Aligned |
OutputConditionPolicy | Runtime/ProtocolMachine/Model/OutputCondition.lean | rust/machine/src/output_condition.rs | Aligned |
Capability/finalization model (FinalizationPath, FinalizationReadClass, FinalizationStage, RuntimeUpgradeArtifact) | lean/Runtime/Proofs/CapabilityModel.lean, Runtime/Tests/ProtocolMachineRunner.lean | rust/machine/src/semantic_objects.rs, rust/machine/src/composition.rs, rust/machine/src/capabilities.rs | Aligned |
Runtime Value variants | Protocol/Values.lean | rust/machine/src/coroutine.rs | Aligned |
ProgressToken fields | Runtime/ProtocolMachine/Model/State.lean | rust/machine/src/coroutine.rs | Aligned |
CommunicationReplayMode variants | Runtime/ProtocolMachine/Model/Config.lean | rust/machine/src/communication_replay/mod.rs | Aligned |
SignedValue transport fields (payload, signature, sequence_no) | Runtime/ProtocolMachine/Model/TypeClasses.lean | rust/machine/src/buffer.rs | Aligned |
Payload hardening controls (payload_validation_mode, max_payload_bytes) | Runtime/ProtocolMachine/Model/Config.lean, Runtime/ProtocolMachine/Semantics/ExecComm.lean | rust/machine/src/engine/ | Aligned |
Register bounds failure semantics (OutOfRegisters) | Runtime/ProtocolMachine/Semantics/ExecSteps.lean | rust/machine/src/engine/, rust/machine/src/threaded/ | Aligned |
Explicit failure/timeout observable event inventory (TimeoutIssued, CancellationRequested, Cancelled, FailureBranchEntered, SessionTerminal) | Runtime/ProtocolMachine/Model/State.lean, Runtime/ProtocolMachine/Runtime/Json.lean, Runtime/Proofs/TheoremPack/ReleaseConformance.lean | rust/machine/src/engine/protocol_machine_config.rs, rust/machine/src/trace.rs | Aligned |
These checks are automated by just check-parity --types.
Protocol-Machine Behavior Contract
| Regime | Required Behavior |
|---|---|
Canonical n = 1 | Exact parity between cooperative and threaded execution |
Threaded n > 1 | Conformance within declared EnvelopeDiff bounds |
| Failure-visible artifacts | Snapshot parity within declared failure envelope class |
| Speculation | No sentinel fallback behavior for join/abort with deterministic gated semantics |
| Register bounds | Out-of-range register operands fail with OutOfRegisters (no unchecked panic paths) |
| Load boundary | Runtime rejects malformed trusted image role/type shape before session open |
| Explicit failure and timeout ordering | per-session trace preserves TimeoutIssued, CancellationRequested, Cancelled, FailureBranchEntered, and SessionTerminal ordering |
| Host targeted retirement | Cooperative and threaded disposal share the required retirement contract; Lean host-lifecycle deviation is recorded as rust-host-targeted-disposal |
These checks are automated by just check-parity --suite.
Explicit failure, timeout, cancellation, and session-terminal events are part of the executable Lean runtime/event inventory. Replay tagging, JSON serialization, and theorem-pack release conformance all use the same observable event family.
Language-level nominal effect declarations do not introduce a second runtime bridge.
Their intended justification remains the existing protocol-machine invoke boundary and handler-typing obligations in Runtime/Proofs/protocol machine/BridgeStrengthening.lean.
Typed effect requests and outcomes are part of the parity surface directly.
Rust and Lean must agree on effect-interface metadata, request bodies, outcome statuses, and replay-visible effect exchanges.
Thatincludes the internal wal_sync effect metadata and the shared durable recovery vocabulary used to classify checkpoint-plus-WAL resume decisions.
Effect Interface Justification
The language-level effect interface design follows the same proof boundary used by the MPST-plus-effects papers and the Lean runtime.
- typed effect obligations remain attached to the executable handler boundary, not to a separate host-only mechanism
- authority-sensitive language constructs must lower to explicit effect observations, witness issuance/consumption, and failure-visible events
- proof-carrying evidence remains fail-closed on missing or invalid authority
- behavioral correspondence is observational, through traces and effect/audit surfaces, rather than through arbitrary host internals
This is why the current language design is nominal-first:
effectdeclarations andusesclauses are stable names for explicit host obligations- lowering stays centered on the existing protocol-machine
invokeandEffectSpecstory - generalized effect polymorphism waits until the nominal surface, lowering, and parity/audit semantics are stable
Durability Alignment
The durability layer is part of the documented Rust<->Lean correspondence boundary for runtime effects.
- Rust
EffectRequestBody::WalSynccorresponds to LeanwalSyncMetadataplus the sharedEffectRequestBody/EffectResponseeffect model inlean/Runtime/ProtocolMachine/Model/Effects.lean - Rust
DurableRecoveryAction/DurableRecoveryDecisioncorrespond to LeanRuntime.ProtocolMachine.Model.DurableRecoveryAction/DurableRecoveryDecision - The current Lean theorem surface for this layer is intentionally narrow:
walSyncMetadata_legal, internal-handler admissibility, recovery-rank monotonicity fromAgreementEscalation, and gate-crossing / terminal-truth consequences fromDurableRecoveryDecision.sound
The simulator-side WAL backend wrappers and fault-injection helpers remain Rust assurance surfaces. They consume the typed durability vocabulary above but are not themselves part of the current mechanized claim.
Choreography Projection Parity
The parity scope covers projection behavior from global choreography forms to local session forms. This includes send, choice, recursion, and merge behavior for non-participant branches. Rust-only runtime conveniences and extension-only AST constructs are excluded.
Shared Projection Semantics
Rust and Lean are expected to align on the following surfaces.
| Area | Lean Surface | Rust Surface | Status |
|---|---|---|---|
| Projection core relation | lean/Choreography/Projection/Project.lean | rust/runtime/src/compiler/projection.rs | Aligned on supported subset |
| Merge semantics | lean/Choreography/Projection/Erasure/Merge.lean | rust/language/src/compiler/projection/merge.rs | Aligned |
| Projection validation pipeline | lean/Choreography/Projection/Validator.lean | rust/bridge/src/runner_projection_export.rs | Aligned |
Rust-Only Extensions
The following surfaces are intentionally outside direct Lean parity. They must be documented as extensions and must not be confused with theorem-backed projection claims.
| Surface | Rust Module | Parity Status |
|---|---|---|
LocalType::LocalChoice | rust/language/src/ast/local_type.rs | Rust extension |
| Timeout wrappers in local AST | rust/language/src/ast/local_type.rs | Rust extension |
Effect runtime Parallel execution contract | rust/runtime/src/effects/interpreter.rs | Rust runtime contract |
Projection Cross-Validation
Projection cross-validation is exercised through rust/bridge/tests/projection_runner_tests.rs. Tests skip per test when the Lean validator binary is unavailable. Skipping one test must not terminate the rest of the suite.
Heap Parity Surface
The runtime heap has a focused Lean mirror for the parts of the contract that must remain cross-implementation stable.
| Surface | Lean Surface | Rust Surface | Status |
|---|---|---|---|
| Canonical resource bytes and tagged preimages | lean/Runtime/Resources/HeapModel.lean | rust/runtime/src/heap/encoding.rs, rust/runtime/src/heap/resource.rs | Aligned |
| Active/nullifier ordering and replay interpreter | lean/Runtime/Resources/HeapModel.lean, lean/Runtime/Proofs/Heap.lean | rust/runtime/src/heap/heap_impl.rs | Aligned |
| Proof-index and sibling-direction semantics | lean/Runtime/Resources/HeapModel.lean | rust/runtime/src/heap/merkle.rs | Aligned |
| Published heap parity corpus | lean/Runtime/Tests/HeapParityRunner.lean | rust/runtime/tests/data/heap_lean_parity_v1.json, rust/bridge/tests/heap_lean_parity.rs | Aligned |
This heap lane is narrower than a full cryptographic equivalence proof. Lean does not currently implement BLAKE3. Instead, Lean reconstructs canonical bytes, tagged preimages, ordering, proof-path structure, and deterministic replay from the published corpus, and Rust must match the published digest values exactly.
The authoritative digest corpus remains the published heap vector set documented in Resource Heap.
rust/runtime/tests/data/heap_vectors_v1.json remains the minimal public digest vector file.
rust/runtime/tests/data/heap_lean_parity_v1.json is the richer Rust<->Lean parity corpus.
Heap Enforcement
The Lean heap surface is split into three files:
| Surface | Location |
|---|---|
| Executable heap model | lean/Runtime/Resources/HeapModel.lean |
| Basic determinism lemmas | lean/Runtime/Proofs/Heap.lean |
| Executable parity runner | lean/Runtime/Tests/HeapParityRunner.lean |
The strict Rust<->Lean heap suite is rust/bridge/tests/heap_lean_parity.rs.
It runs the Lean executable, recomputes the same values in Rust through public heap APIs, and checks that both sides match the published corpus exactly.
This lane justifies a stronger statement than “heap behavior is only tested in Rust,” but it is still not a full theorem-backed digest proof.
Current claim:
- Lean and Rust agree on canonical heap bytes, tagged preimages, ordering, proof-path structure, and deterministic replay for the published heap corpus.
- Rust produces the authoritative digest values, and those values must match the published vectors exactly.
Not yet claimed:
- a pure Lean implementation of BLAKE3
- a theorem that the Rust digest implementation is cryptographically correct
- full mechanized heap refinement against all runtime heap code paths
State Schema
Lean and Rust schemas remain shape-equivalent on safety-visible and replay-visible fields. Runtime-only helper fields are permitted when they do not alter observable semantics.
Semantic Object Family
The canonical cross-language semantic-object family must remain aligned between Lean, Rust, and the Rust/Lean bridge.
| Object | Lean Surface | Rust Surface | Bridge Surface | Status |
|---|---|---|---|---|
OperationInstance | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
OutstandingEffect | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
SemanticHandoff | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
TransformationObligation | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
AuthoritativeRead / ObservedRead | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
MaterializationProof / CanonicalHandle | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
ProgressContract | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
ProgressTransition | Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean | rust/machine/src/semantic_objects.rs | rust/bridge/src/semantic_objects.rs | Aligned |
| typed effect metadata / request / outcome model | Runtime/ProtocolMachine/Model/Effects.lean | rust/machine/src/effect.rs | rust/bridge/src/protocol_machine_runner.rs (effect_exchanges) | Aligned |
OperationInstance and OutstandingEffect are compared as canonical runtime state, not as post-hoc derivations from generic effect-trace order.
Parity on these objects covers owner identity, phase/status, budget/invalidation fields, dependent-operation edges, and terminal publication state.
SemanticHandoff parity also covers explicit owner revocation and activation (revoked_owner_id, activated_owner_id).
TransformationObligation parity covers transformed fragments, affected operations/effects, transport vs invalidation policy, and publication transfer or revocation.
Bridge-side execution comparison reports these handoff and invalidation surfaces separately from raw trace equivalence.
This prevents stale-owner and late-result mismatches from hiding inside otherwise equivalent instruction traces.
The same strict bridge layer also compares the first-class
capability/finalization/transition facade exported by
inspectCapabilityModel. That lane checks canonical vs invalidated
finalization paths and committed-cutover vs rolled-back runtime-upgrade
artifacts directly, instead of treating them as documentation-only structure.
Canonical layout mapping for this family is:
- Lean executable spec:
lean/Runtime/ProtocolMachine/Model/SemanticObjects/*.lean - Rust runtime mirror:
rust/machine/src/semantic_objects.rs - Rust bridge mirror:
rust/bridge/src/semantic_objects.rsThis bridge surface must re-export the machine semantic-object family rather than carrying a duplicate schema copy.
Region is part of the canonical protocol-machine semantic-object family.
It names the session-scoped locality and framing domain over operations,
effects, authoritative reads, canonical handles, and publication events.
ProgressContract parity covers bounded-wait metadata, explicit no-progress and degraded states, and timeout escalation state.
ProgressTransition parity makes those escalations replay-visible instead of leaving them as target-specific scheduling heuristics.
The Lean implementation layer keeps executable semantic-object definitions in Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean.
Basic theorem-facing predicates live in Runtime/ProtocolMachine/Model/SemanticObjects/Discipline.lean.
The re-export facade is Runtime/ProtocolMachine/Model/SemanticObjects.lean.
Deferred-effect admissibility, retry shape, and late-result rejection live in Runtime/ProtocolMachine/Model/SemanticObjects/OutstandingEffects.lean.
Associated theorem-facing lemmas are in Runtime/Proofs/ProtocolMachine/SemanticObjects/OutstandingEffects.lean.
Semantic handoff realization lives in Runtime/ProtocolMachine/Model/SemanticObjects/SemanticHandoffTransition.lean.
Theorem-facing owner/publication/delegation bridge lemmas are in Runtime/Proofs/ProtocolMachine/SemanticObjects/SemanticHandoff.lean.
Authoritative-read commitment contexts and canonical publication-path uniqueness live in Runtime/ProtocolMachine/Model/SemanticObjects/AuthoritativeReadsPublication.lean.
Observer-projection, blindness, and noninterference lemmas are in Runtime/Proofs/ProtocolMachine/SemanticObjects/AuthoritativeReadsPublication.lean.
Proof-backed success contexts and materialization-proof adequacy live in Runtime/ProtocolMachine/Model/SemanticObjects/MaterializationSuccess.lean.
Lemmas ruling out proof-less success and observational materialization promotion are in Runtime/Proofs/ProtocolMachine/SemanticObjects/MaterializationSuccess.lean.
Progress-contract semantics live in Runtime/ProtocolMachine/Model/SemanticObjects/ProgressContracts.lean.
Owner-liveness, escalation, and Lyapunov/weighted-measure/scheduling-bound compatibility lemmas are in Runtime/Proofs/ProtocolMachine/SemanticObjects/ProgressContracts.lean.
Transformation-local obligation bundles live in Runtime/ProtocolMachine/Model/SemanticObjects/TransformationLocalObligations.lean.
Coverage/admissibility lemmas and lightweight linking/reconfiguration bridge structures are in Runtime/Proofs/ProtocolMachine/SemanticObjects/TransformationLocalObligations.lean.
Theorem-pack attachment for these semantic-object proof families lives in Runtime/Proofs/InvariantSpace.lean via SemanticObjectWitnessBundle.
The same attachment points are exposed through Runtime/Proofs/TheoremPack/Inventory.lean, Runtime/Proofs/TheoremPack/API.lean, and Runtime/Proofs/Contracts/RuntimeContracts.lean.
Lean ProtocolMachineState
Source: lean/Runtime/ProtocolMachine/Model/State.lean
CoroutineState contains id, programId, pc, regs, status, effectCtx, ownedEndpoints, progressTokens, kledgeSet, costBudget, and specState.
The Lean protocol-machine state structure (ProtocolMachineState) contains config, programs, coroutines, sessions, monitor, sched, resourceStates, persistent, obsTrace, failure/topology state fields, and output-condition state.
Rust Protocol Machine
Source: rust/machine/src/engine/
The Rust protocol-machine structure (ProtocolMachine, exported as an alias for protocol machine) contains config, programs, code, coroutines, sessions, monitor, sched, resource_states, persistent, obs_trace, symbol/clock counters, failure/topology state fields, and output-condition state.
Coroutine in rust/machine/src/coroutine.rs contains identity/program/pc/status, register file, ownership/progress/kledge sets, cost budget, speculation metadata, and effect context.
Canonical Rust Runtime Object Inventory
The Rust public runtime surface exposes one canonical naming scheme:
protocol-machine objects use ProtocolMachine*, guest-runtime objects use
GuestRuntime*, and bridge execution objects use ProtocolMachineRunner*.
No public telltale_machine::vm::*, telltale_machine::threaded::*,
telltale_bridge::protocol_machine_runner::*, or telltale_bridge::vm_trace::*
entrypoints remain.
| Runtime Object | Lean Surface | Rust Surface | Bridge Surface | Status |
|---|---|---|---|---|
| protocol-machine config | Runtime/ProtocolMachine/Model/Config.lean | telltale_machine::ProtocolMachineConfig | telltale_bridge::ProtocolMachineRunInput | Aligned |
| protocol-machine state | Runtime/ProtocolMachine/Model/State.lean | telltale_machine::ProtocolMachineState | telltale_bridge::ProtocolMachineRunOutput | Aligned |
| protocol-machine executor | Runtime/ProtocolMachine/API.lean, Runtime/ProtocolMachine/Runtime/Runner.lean | telltale_machine::ProtocolMachine | telltale_bridge::ProtocolMachineRunner | Aligned |
| protocol-machine step result | Runtime/ProtocolMachine/Model/State.lean | telltale_machine::StepResult | telltale_bridge::ProtocolMachineStepState | Aligned |
| protocol-machine run status | Runtime/ProtocolMachine/Model/State.lean | telltale_machine::RunStatus | telltale_bridge::ProtocolMachineRunOutput.status | Aligned |
| protocol-machine error surface | Runtime/ProtocolMachine/Model/State.lean, Runtime/ProtocolMachine/Runtime/Json.lean | telltale_machine::ProtocolMachineError | telltale_bridge::LeanStructuredError | Aligned |
| protocol-machine memory accounting | Runtime/ProtocolMachine/Model/State.lean | telltale_machine::ProtocolMachineMemoryUsage, telltale_machine::ProtocolMachineRetainedBytes | n/a | Aligned |
| guest runtime driver | Runtime/ProtocolMachine/API.lean | telltale_machine::GuestRuntime, telltale_machine::ThreadedGuestRuntime | n/a | Aligned |
| threaded protocol-machine adapter | Runtime/ProtocolMachine/API.lean, Runtime/ProtocolMachine/Composition.lean | telltale_machine::ThreadedProtocolMachine | parity tests under rust/bridge/tests/protocol_machine_cross_target_tests.rs | Aligned |
| semantic-object inventory | Runtime/ProtocolMachine/Model/SemanticObjects*.lean | telltale_machine::{ProtocolMachineSemanticObjects, OperationInstance, OutstandingEffect, SemanticHandoff, TransformationObligation, AuthoritativeRead, ObservedRead, MaterializationProof, CanonicalHandle, PublicationEvent, Region, ProgressContract, ProgressTransition} | telltale_bridge::{ProtocolMachineSemanticObjects, OperationInstance, OutstandingEffect, SemanticHandoff, TransformationObligation, AuthoritativeRead, ObservedRead, MaterializationProof, CanonicalHandle, PublicationEvent, Region, ProgressContract, ProgressTransition} | Aligned |
| runtime admission contracts | Runtime/Proofs/Contracts/RuntimeContracts.lean | telltale_machine::{requires_protocol_machine_runtime_contracts, admit_protocol_machine_runtime, enforce_protocol_machine_runtime_gates, request_determinism_profile, runtime_capability_snapshot} | n/a | Aligned |
Runtime Capability Gates
Runtime modes that require theorem/capability evidence are admission gated.
| Gate Surface | Lean API | Rust API | Status |
|---|---|---|---|
| Advanced mode admission | requiresVMRuntimeContracts, admitVMRuntime | requires_protocol_machine_runtime_contracts, admit_protocol_machine_runtime | Aligned |
| Determinism profile validation | requestDeterminismProfile | request_determinism_profile | Aligned |
| Runtime capability snapshot | runtimeCapabilitySnapshot | runtime_capability_snapshot | Aligned |
The Rust surfaces are in rust/machine/src/runtime_contracts.rs and rust/machine/src/composition.rs.
On the Lean side, TheoremPackCapabilityContract.semanticAttachmentPoints provides the canonical runtime-facing list of enabled semantic-object theorem attachment points.
Determinism Profiles
| Profile | Lean | Rust | Status |
|---|---|---|---|
| Full | full | DeterminismMode::Full | Aligned |
| Modulo effect trace | moduloEffectTrace | DeterminismMode::ModuloEffects | Aligned |
| Modulo commutativity | moduloCommutativity | DeterminismMode::ModuloCommutativity | Aligned |
| Replay | replay | DeterminismMode::Replay | Aligned |
Full is exact-trace mode.
ModuloEffects and ModuloCommutativity require mixed-profile capability evidence and artifact support.
Replay requires replay artifact support and mixed-profile capability evidence.
Threaded Concurrency Envelope
| Regime | Lean Surface | Rust Surface | Status |
|---|---|---|---|
n = 1 exact refinement | runScheduledThreaded_one_eq_runScheduled | threaded_equivalence.rs::test_threaded_matches_cooperative | Aligned |
Spawn step parity (n = 1) | Runtime/ProtocolMachine/Semantics/ExecControl.lean, Runtime/ProtocolMachine/Semantics/ExecSteps.lean | differential_step_corpus.rs::threaded_matches_cooperative_step_corpus_control_spawn | Aligned |
| Certified-wave fallback | executeCertifiedRound | threaded.rs wave certificate check with one-step fallback | Aligned |
n > 1 envelope-bounded parity | ThreadedRoundRefinementPremises (premise-scoped) | parity_fixtures_v2.rs::envelope_bounded_parity_holds_for_n_gt_1 | Aligned under envelope contract |
Simulator Field Mirror
Lean includes executable mirror dynamics for simulator field handlers under lean/Runtime/Simulation/. Rust field handlers live under rust/simulator/src/field_handlers/.
The mirror includes a Lean-native field-model boundary in lean/Runtime/Simulation/Field.lean, including built-in catalog dispatch and default initial-state derivation for shipped field families.
It remains an executable parity layer, not a mirror of Rust trait objects or serde-based scenario parsing.
Parity fixtures are enforced by:
rust/simulator/tests/field_handler_parity.rslean/Runtime/Tests/SimulatorParity.lean(built assimulator_parity_tests)
Reduced Semantic-Effect Parity
Lean also publishes a reduced semantic-effect fixture surface for the protocol-machine/runtime boundary. This lane is narrower than full executable protocol-machine equivalence: it checks the semantic classification we rely on for effect-facing debugging and replay exactness without claiming that Lean executes the full Rust effect handler stack.
The reduced surface covers:
- effect kind
- lifecycle classification (
blocked/succeeded) - interface and operation naming
- output-condition predicate material visible to publications
- whether the successful send path materializes an authoritative publication handle
Parity fixtures are enforced by:
lean/Runtime/Tests/SemanticEffectParity.lean(built assemantic_effect_parity_runner)rust/machine/tests/semantic_effect_lean.rsrust/simulator/tests/semantic_effect_lean.rs
The machine and simulator tests compare canonical Rust outputs against the Lean fixture bundle for:
- one blocked-send fixture
- one successful send/publication fixture
- one
output_condition_hintfixture - one
wal_syncfixture
This lane is intended to keep Lean, Rust machine semantics, replay/runtime behavior, and simulator artifact extraction aligned on the effect-visible semantic slice that matters for time-travel debugging and exact replay.
Lean Module Boundaries
The lean/Runtime/ProtocolMachine directory is organized into executable and proof modules.
The Runtime/ProtocolMachine/Model/ directory contains runtime data types, config, state, instruction forms, and event surfaces. The Runtime/ProtocolMachine/Semantics/ directory contains executable step semantics. The Runtime/ProtocolMachine/Runtime/ directory contains runtime adapters for loading, JSON, monitoring, and failure ingress.
The Runtime/ProtocolMachine/API.lean file provides the stable facade for executable protocol-machine API imports. The Runtime/ProtocolMachine/Composition.lean file contains composition/admission and theorem-pack integration surfaces. The Runtime/Proofs/ directory contains proof/theorem-pack modules not required for core executable stepping.
Executable modules must not depend on placeholder proof definitions. Proof-only placeholders stay isolated under proof modules. Any executable-path dependency on a stub or placeholder is a release blocker.
Deviation Governance
Any intentional parity break must be recorded in the deviation table below before merge. Required fields include id, owner, status, reason, impact, alternatives considered, revisit date, and coverage scope.
Deviation Registry
| Id | Owner | Status | Reason | Impact | Alternatives considered | Revisit date | Coverage scope |
|---|---|---|---|---|---|---|---|
| rust-host-targeted-disposal | telltale-machine runtime maintainers | Active, runtime-only | Exclusive host disposal and scoped Rayon worker acknowledgment concern Rust execution custody outside the Lean small-step program | Adds targeted retirement APIs and typed preflight errors; no shared wire schema, instruction or capability shape changes; host retirement does not establish protocol success | Status-only close leaves runnable custody; whole-machine drop removes unrelated sessions | 2027-01-03 | Cooperative and threaded required-reap regressions cover surviving stable ids, actual worker acknowledgment, preflight faults, natural terminal epochs, repeated acknowledgment and cooperative deserialization; cross-backend disposal summary regression covers the common retirement contract |
| rust-host-worker-pool-error-source | telltale-machine runtime maintainers | Active, runtime-only | Native worker construction and its original I/O cause are outside Lean protocol state and small-step execution | ThreadPoolBuild retains the actual Rayon error chain; diagnostic display is unchanged; no instruction, capability, wire format or successful protocol trace changes; constructor refusal supplies no worker-termination ACK | Stringifying native causes or treating constructor failure and pool Drop as acknowledged teardown | 2027-01-20 | Two native multi-thread fixtures declare first-spawn refusal and later-spawn refusal after an actual first-worker acknowledgment; the latter retains and joins original native handles before assertions; these tests do not establish production pool-join capability |
| rust-host-owned-session-parity | telltale-machine runtime maintainers | Active, runtime-only | Host session mutation, staged ownership receipt validation and original Rust error sources are embedding contracts outside endpoint/coroutine small-step transfer | Both backends share resident-state ownership transitions; owned handles validate receipt/session association and current generation; transfer/attenuation cannot widen scope; release/reclaim generations remain monotonic; exhausted counters fail before mutation; the canonical journal preserves actual cross-session emission order and machine ticks across disposal; cooperative snapshots add a defaulted ordered journal and last-generation storage; OwnershipContract carries a typed source | Duplicated threaded ownership logic, bare-store mutation, saturating/reused authority ids, stringified causes, or reconstructing authority from audit records | 2027-01-19 | owned_backend_parity exercises both backends, claim-specific failed commit/rollback, foreign receipt refusal, witness consumption/rejection, stale and scoped handles, terminal ordering, typed causes and retained audit; ownership_preflight_tests covers release/reclaim restoration, scope escalation, counter exhaustion before terminal mutation and foreign edges; ownership_lock_tests exercises the original poisoned custody and audit locks before mutation |
The Lean Runtime/ProtocolMachine/Model/State.lean session state and
Runtime/ProtocolMachine/Semantics/ExecSteps.lean execution surfaces remain the
reference for protocol steps. Targeted host disposal is not a new instruction,
transition witness, or claim that Lean proves Rayon worker termination. Rust’s
exclusive owner and joined worker scope enforce that runtime obligation.
conservative-async-subtyping-contract
Conservative async-subtyping (Lean and Rust) is intentionally phase- and tree-structural:
- SISO decomposition must succeed on both sides under bounded unfolding.
- Subtype and supertype must have equal phase counts.
- Each aligned phase must satisfy input-tree and output-tree structural compatibility.
- Empty behavior (
End) only matches supertypes with empty phase decomposition under this conservative contract.
CI Gates
The minimum parity governance gates are just check-parity --all, just check-release-conformance, and workflow gates in .github/workflows/verify.yml and .github/workflows/check.yml.
If any gate fails, parity drift is treated as a release blocker.
Performance SLA
Runtime performance governance enforces explicit thresholds from artifacts/v2/benchmark_matrix/summary.json through just perf-baseline sla.
TT_SLA_THROUGHPUT_RATIO_MIN(default1.0)TT_SLA_P99_REGRESSION_MAX_PERCENT(default15.0)TT_SLA_LOCK_CONTENTION_REDUCTION_MIN_PERCENT(default50.0)
If any threshold is violated, CI fails before benchmark lanes are considered healthy.
Update Rules
When any parity matrix row changes, update the Deviation Registry table in this file in the same change set. For any protocol-machine PR that changes public runtime behavior, include a parity impact statement in the PR checklist. Add differential tests when observable behavior changes.
Any Rust PR that changes projection or merge semantics must include:
- The affected Rust module list.
- The Lean module list reviewed for parity.
- New or updated cross-validation tests for the changed behavior.
- A parity note update in this document when scope or status changes.
Naming Surface
Rust protocol-machine code uses one canonical snake_case naming surface. Lean-specific casing remains on the Lean side. Rust APIs should not preserve parallel wrapper names such as openDelta, siteName, or signValue.
Semantic Object Name Parity
Shared semantic/runtime objects keep the same PascalCase type name in Lean and Rust.
Field names may differ only by language casing convention: Lean uses camelCase, Rust uses snake_case.
Proof-only Lean packaging objects and Rust-only operational wrappers are listed separately and are not parity targets.
Lean source of truth: lean/Runtime/ProtocolMachine/Model/SemanticObjects/Core.lean
Rust source of truth: rust/machine/src/semantic_objects.rs
| Shared object | Lean | Rust | Status |
|---|---|---|---|
| Operation instance | OperationInstance | OperationInstance | Exact name match |
| Outstanding effect | OutstandingEffect | OutstandingEffect | Exact name match |
| Semantic handoff | SemanticHandoff | SemanticHandoff | Exact name match |
| Transformation obligation | TransformationObligation | TransformationObligation | Exact name match |
| Authoritative read | AuthoritativeRead | AuthoritativeRead | Exact name match |
| Observed read | ObservedRead | ObservedRead | Exact name match |
| Materialization proof | MaterializationProof | MaterializationProof | Exact name match |
| Canonical handle | CanonicalHandle | CanonicalHandle | Exact name match |
| Publication event | PublicationEvent | PublicationEvent | Exact name match |
| Progress contract | ProgressContract | ProgressContract | Exact name match |
| Progress transition | ProgressTransition | ProgressTransition | Exact name match |
| Semantic object bundle | ProtocolMachineSemanticObjects | ProtocolMachineSemanticObjects | Exact name match |
| Shared enum | Lean | Rust | Status |
|---|---|---|---|
| Operation phase | OperationPhase | OperationPhase | Exact name match |
| Outstanding effect status | OutstandingEffectStatus | OutstandingEffectStatus | Exact name match |
| Authoritative read kind | AuthoritativeReadKind | AuthoritativeReadKind | Exact name match |
| Authoritative read lifecycle | AuthoritativeReadLifecycle | AuthoritativeReadLifecycle | Exact name match |
| Canonical handle kind | CanonicalHandleKind | CanonicalHandleKind | Exact name match |
| Publication observer class | PublicationObserverClass | PublicationObserverClass | Exact name match |
| Publication status | PublicationStatus | PublicationStatus | Exact name match |
| Progress state | ProgressState | ProgressState | Exact name match |
| Ownership scope | OwnershipScope | OwnershipScope | Exact name match |
| Delegation status | DelegationStatus | DelegationStatus | Exact name match |
Representative field casing mappings:
| Lean field | Rust field |
|---|---|
operationId | operation_id |
ownerId | owner_id |
effectIds | effect_ids |
terminalPublication | terminal_publication |
publicationId | publication_id |
observerClass | observer_class |
lastProgressTick | last_progress_tick |
Lean-Only Proof Packaging Names
These are canonical Lean proof/runtime packaging objects not expected to have direct Rust runtime peers.
ProtocolMachineInvariantSpace, ProtocolMachineTheoremPack, ProtocolMachineRuntimeContracts, ProtocolMachineLivenessBundle, ProtocolMachineSchedulerBundle, ProtocolMachineEnvelopeAdherenceProtocol, ProtocolMachineEnvelopeAdmissionProtocol, ProtocolMachineBridgePremises, CoroutineViewEquiv, protocolMachinePotential.
Rust-Only Operational Wrapper Names
These are canonical Rust operational or embedding surfaces not expected to have direct Lean theorem peers.
GuestRuntime, ThreadedGuestRuntime, EffectHandler, ProtocolMachine, ProtocolMachineConfig, ProtocolMachineState, ProtocolMachineError, ProtocolMachineRunner, ProtocolMachineRunInput, ProtocolMachineRunOutput, ProtocolMachineReplayBundle, GuestRuntimeDeclaration.
Related Docs
- Protocol Machine Architecture
- Protocol-Machine Bytecode Instructions
- Lean Verification
- Capability Admission
Native worker-pool settlement deviation
The Rust-only private worker-pool disposal API concerns real host thread custody, not a Lean instruction, session status, capability witness or machine-tick transition. Its opaque receipt is issued from actual native JoinHandle results. It cannot reconstruct protocol authority or certify signed-terminal success.
Native join-operation unwind is distinct from a worker panic returned by a completed join. The original native custodian retains untouched handles, completed-join counts and chronological causes across that unwind. A consumed handle with unproven settlement permanently prevents whole-pool acknowledgment, even after same-original retry joins every remaining worker. This repairs host custody rather than adding a Lean instruction or protocol authority witness. Injected join-boundary tests establish this retention transaction, not an actual OS deadlock diagnosis or physical settlement of an uncertain consumed handle.
| Id | Owner | Status | Reason | Impact | Alternatives considered | Revisit date | Coverage scope |
|---|---|---|---|---|---|---|---|
| rust-host-native-pool-settlement | telltale-machine runtime maintainers | Active, runtime-only | Native worker exit and join are outside Lean protocol small-step state | Consuming explicit machine teardown joins original workers; partial construction retains actual worker handles and Rayon source; no instruction, wire or successful trace change | Rayon Drop and targeted session receipt do not acknowledge private native-pool termination | 2027-01-20 | Eleven native source-declared component fixtures cover real worker TLS exit before ACK, original panic payload, actual machine pool, first spawn refusal and partial start followed by original joins, actual self-worker refusal returning the machine for external retry, join-operation unwind retaining untouched original workers and chronological causes, retained consumed-worker refusal after unwind, and observation during actual pending join; execution evidence remains required |
Authority Binding Artifact Boundary
The communication-only theory artifact route cannot certify execution of an AST authority binding. It now rejects authority/observation modes, explicit check/observe/transfer expressions, linear aliases and unclassified calls rather than erasing those operations. Plain non-linear identifiers and constructors remain local communication abstractions. Their projection is not a value interpreter or authority witness.
The existing authority semantic-object conservation and supported-slice Lean theorems remain unchanged. They do not prove that the DSL compiler emitted an Invoke, passed the original nominal effect request, preserved typed outcome branching or bound a continuation to the issued owner. Those compiler and execution correspondence obligations remain open. A positive communication machine run and an explicit authority-artifact refusal are separate evidence.
In the current producer, a named Invoke action is recorded as action metadata, but step_invoke still submits the generic invokeStep request with role and register state. Renaming that action to a declared effect operation would not invoke that operation. Lean’s corresponding invokeStep request has the same role/state boundary. A nominal request/typed-outcome and continuation contract must be established at this shared boundary before claiming positive lowering.
Typed Outcome Acceptance Prerequisite
The Rust outcome adapters now separate raw status from accepted success. The same exact request/response-kind rule controls register updates, lifecycle publication and effect-composition success on cooperative and threaded paths. Recording and replay state adapters consume their actual returned outcome. They do not recover a state payload by rereading the latest shared trace entry.
The pinned release19 Lean model has eight request/response families, while Rust also has WalSync as a ninth family. The new pure acceptedStatusFor and invokeStepState helpers cover the actual eight modeled families. Their examples are not an execution correspondence proof. Existing EffectAlgebra.succeeded still classifies raw status, and ExecGuardEffect.stepInvoke executes the abstract total EffectRuntime.exec without a typed outcome gate. Connecting those premises to accepted typed outcomes, updating composition proofs and modeling WalSync are open correspondence prerequisites. The earlier durability alignment description must not be read as proof that the pinned request enum already contains WalSync.
Full nominal dispatch also remains open: the original declaration and uses admission, argument/result types, actual coroutine role and owner generation, result destination and success/failure continuation must be bound to one issued invocation. Late or duplicate completion must target that same live occurrence. No named audit string, post-hoc owner lookup or new side ledger establishes this contract. Host physical future and worker custody remain the embedding owner’s separate responsibility.
VM Invoke and Receive requests retain their actual configured register width as
optional instruction context. All VM state-replacing responses, including branch
Choose/Receive, must preserve that width before register replacement or effect-
success publication. This enforces the canonical Rust wf_coroutine_state
register-width invariant, not an inferred equal-length convention. Receive also
retains its actual destination and requires that register to exist. Branch receive
omits the destination because it performs no destination write, but retains width.
After existing signature, replay and payload validation, both VM receive backends reject an out-of-range instruction destination with OutOfRegisters before invoking the host receive handler. This preserves the VM fault taxonomy and prevents host receive mutation for that invalid destination. It does not roll back earlier dequeue or replay work. Handler-only wrappers omit both instruction contexts. Missing fields on older
serialized requests decode as absent and do not invent historical VM context. The original raw outcome remains observational evidence. This check
is not rollback of earlier buffer dequeue, replay consumption, clock or type work.
Optional observability retention cannot replace an original host failure: the
canonical recorded exchange supplies lifecycle status without a second synthetic
issue/complete cycle. Blocked remains a Blocked observation while the synchronous
instruction reports its existing blocked-contract fault.
The Lean request schema does not yet carry instruction width/destination context.
Its pure family/status examples do not prove this Rust applicability rule. That
schema and executable-semantics correspondence remain an explicit prerequisite.
Host-response acceptance is distinct from whole-instruction commit. In particular, Invoke may apply its accepted register state and host-success observation before persistence-delta application faults. No rollback of that state, emitted events, or earlier communication work is implied by the response-kind/destination guard. The prior cooperative owner could evict live enforcement inputs under diagnostic retention, while the prior threaded owner used untrimmed vectors. The canonical live/resident retention rule below repairs that storage boundary in both existing owners. Response/error preservation alone was insufficient. Neither correction claims nominal authority, a disposal acknowledgment or a Lean correspondence proof.
Serialized request context is an observation used to validate response shape, not proof of original request ownership or nominal authority. The replay handler selects by expected entry kind. It does not establish full incoming request identity. Actual VM producers attach current configured width before execution, including when replay supplies the response. Lean’s abstract effect-context step and spawn register copying are not a proof of this concrete Rust register ABI.
Canonical host exchanges also supply Acquire/Release/SendDecision outcome observations directly. Duplicate synthetic issue/complete cycles must not replace the original typed failure under optional diagnostic retention. Existing policy is preserved: cooperative Acquire Blocked waits on AcquireDenied, threaded Acquire Blocked reports its synchronous blocked-contract fault, and Release/SendDecision Blocked remain synchronous faults. The separate Send buffer WouldBlock producer retains its actual live capacity obligation: successful host decision is not queue admission, so that issue/complete pair is not a duplicate host outcome.
Canonical Live Effect Retention
Cooperative and threaded owners retain active effect, operation and progress records independently of Disabled/Capped diagnostic history. Both use the same live/resident classification and retired terminal-history pruning rule. Actual blocked Acquire and next-round topology producers must retain the original owner/budget and progress/reentrancy enforcement. Targeted disposal permits terminal history to obey its configured bound. The pre-existing instruction policy for Blocked Acquire still differs (cooperative blocks, threaded faults). Paired retention fixtures do not claim that wider behavior is equivalent.
Lean progress/reentrancy predicates concern retained semantic objects and their actual steps. They do not authorize observational eviction of an uncompleted operation. This Rust retention repair does not add a Rust/Lean correspondence proof, capture arbitrary asynchronous work, or equate invalidation with physical teardown. New fixture declarations require actual discovery and execution.
The original terminal-only retention child was insufficient: actual current_wal_sync_request consumes terminal operation/publication records through semantic_objects. Both backends now additionally protect every canonical record of an actually resident session. Terminal history eligibility starts only after actual session retirement. A terminal lifecycle status is not proof of nonresidency or irrelevance. A shared real Invoke/commit/WalSync fixture compares the exact handler request, agreement state and evidence under all three modes. Full correspondence and physical teardown remain separate obligations.
The composed required native ownership/disposal inventory contains 59 unique fixtures: the original 35 plus 17 strict-outcome/error, six live/resident retention and one cooperative full-queue/Disabled regression. The existing canonical checker requires their nonignored declarations, actual harness discovery and successful execution. Declarations alone are not passing evidence. Module filters can also execute required fixtures appearing in a narrower sub-suite, without changing the unique required count. Authority guard DSL/compiler/bridge fixtures and formal examples remain separate targets and obligations.
Explicit Placement Source Representation
The optional Protocol::Let.execution_role field preserves an explicit at Role
clause through parsing, clone and continuation append. AST Protocol is not a
serde wire type: no CodeImage or effect trace schema changes here. Existing Rust
literal constructors now explicitly supply None. This is a Rust source migration,
not an implicit role reconstruction. Legacy DSL forms remain unplaced.
Lean LocalTypeR contains communication and recursion only. CompileLocalTypeR cannot preserve a local typed authority binding. EffectRuntime.exec remains a total action/context transition. StepInvoke checks handler presence but has no nominal operation/generation/result-continuation transaction. Therefore no new Lean constructor, execution theorem or parity claim follows from source placement. The placed artifact refusal is deliberate until the actual shared model and Rust executable producers implement that transaction. Direct local projection of unplaced local expressions remains communication analysis, not proof that those expressions execute. This child does not change that existing analysis contract or classify its result as executable authority lowering.
Nominal Command Transaction Prerequisite
The Rust selected-operation compiler produces a static nominal declaration and call plus existing communication continuations. The owned loader retains the issued OwnedSession in coroutine custody. Serde deliberately omits that handle and the live result continuation. Static descriptors, effect traces and restored ownership metadata do not reconstruct it. A source-selected agreement profile is explicit on the existing OperationInstance. Legacy instances deserialize to None and retain their previous derivation rules.
Both Rust backends issue the canonical outstanding effect, operation and progress owner before the callback. The threaded path performs issuance before worker dispatch, retains the actual continuation on the original coroutine, and gates the main-thread result commit after the worker returns. The raw exchange remains separate from accepted status. Immediate Blocked is refused. MayBlock retains the selected original budget. Admission and commit use the inclusive original issued tick plus budget. Progress escalation never extends that endpoint. Semantic handler identity is observed before admission and once at final commit, with no subsequent identity callback after derived success publication. It is not proof of a physical provider identity or native-future settlement.
The executable Lean path uses typed action classifiers and invokeCommand,
with the original typed action supplied to sum composition. The default callback
refuses. Parsing/classification alone never falls through to generic exec.
Products retain nominal classification but have no admitted result-combination
contract, so their default nominal callback/relocation refuses. Legacy models
with no nominal classifiers keep the existing generic path.
The model’s canonical coroutine, outstanding-effect, operation and progress fields retain the same nominal occurrence through Blocked and output refusal. The original typed exchange is an observation. A returned value is held before post-callback validation. Output refusal retains that holder instead of rolling back to the pre-issuance state. Exact logical-window, ordering preservation and typed-branch commit theorem declarations accompany real loader/opcode/commit fixtures. These source declarations require actual Lean checking. They are not a completed Rust/compiler refinement theorem.
Canonical Halt keeps the program counter at the actual terminal instruction while marking the coroutine done and removing its active endpoint type. Both Rust backends and the executable Lean transition use this same terminal PC. The pending nominal bridge case runs through declaration, the peer’s blocked choice, and the first invocation at logical tick three. It checks that actual original occurrence and its selected budget before testing retained authority. This does not change the scheduler clock or extend the original window.
Role entry PCs in Lean need not be zero. Static declarations apply only to the prefix of the actual role’s executable chunk, and the loader fixes that role on the original coroutine. A declaration is a static manifest, not an executed capability grant. Typed result branches must stay in that chunk. Converting an executable image to a role map now slices and rebases its actual instructions. It never recompiles local communication types and discards nominal actions. Typed action relocation must preserve the exact rebased descriptor or refuse. Missing/lying adapters and cross-role targets have negative fixture declarations.
Rust’s issued OwnedSession and generation checks remain the additional host
boundary described by rust-host-owned-session-parity. Lean endpoint/occurrence
records do not reconstruct that handle or prove physical host cleanup. The
source compiler, both backend fixtures and executable Lean relation are still
uncompiled/unexecuted in this prepared child. Full source-to-model refinement,
authoritative evidence/readiness, publication, materialization and transfer
lowering remain required and open. No broader authority proof follows from this
Command application-result slice.
Executable nominal bridge scope
The existing protocol-machine runner has a separate runNominalImage operation.
It installs the source compiler’s actual tagged instructions through the checked
role-image loader. Missing bytecode or typed outcomes cannot fall back to local
communication-type recompilation. The legacy communication operation remains
communication-only. The targeted Lean prerequisite builds this runner module and
its nominal model fixtures explicitly before lean --run imports them.
The comparison retains actual application outcomes, accepted statuses, semantic
fault payloads, issuance and callback-observation ticks, operation/profile/proof
requirements, registers, PCs, and readonly observations of the original pending
continuation, staged result and independent refusal. These observations contain
no OwnedSession and cannot restore authority. Unsupported typed responses and
terminal fault families refuse rather than becoming absent payloads.
The source-derived normalization ledger permits only the validated single original occurrence-ID relation and an exact original-or-plus-one reply relation. It does not equate arbitrary foreign IDs, normalize ticks, or erase retained results. Explicit local payload annotations and unsupported instructions remain outside this narrow image bridge and refuse. The fixture declarations include actual Blocked and output-denied executions plus omitted-holder, changed-result, changed-terminal-cause and foreign-ID controls. They require real Rust/Lean execution. No source-only preparation counts as correspondence acceptance or a full compiler-refinement theorem.
Finite simulation-counter admission
Lean Runtime/ProtocolMachine/Runtime/Runner.lean::runScheduled and
ThreadedRunner.lean::runScheduledThreaded advance the model clock by one before
the round. Rust preserves that one-round increment over its representable domain.
Its u64 tick and Duration are finite, unlike the mathematical model clock.
At either boundary, Rust returns a typed admission error before changing either
counter or entering the round. This refuses an unrepresentable execution. It
never saturates, wraps, advances per worker wave, or substitutes a physical clock.
| Id | Owner | Status | Reason | Impact | Alternatives considered | Revisit date | Coverage scope |
|---|---|---|---|---|---|---|---|
| rust-finite-simclock-admission | telltale-machine runtime maintainers | Active, finite representation boundary | Rust tick and simulated duration have finite successors while Lean model arithmetic is unbounded | Typed atomic refusal before round mutation; all representable rounds retain one tick each; no authority or cleanup ACK | Wrapping, saturation, panic or partial tick mutation changes the selected model time | 2027-01-21 | Three direct clock declarations and two actual-round declarations per backend; source-only until discovered and executed |
Finite effect-occurrence admission
Rust’s canonical effect-occurrence counter is finite. Its exhausted sentinel is
u64::MAX. An occurrence is allocated only below that value. Lean’s natural
number counter has no corresponding finite boundary. Correspondence requires
capacity for the complete selected dispatch or dynamic topology batch. This
premise is separate from simulation-clock admission and never renews an original
operation’s tick, deadline, owner or progress window.
Both Rust backends check a conservative fresh-occurrence bound before instruction
or host mutation. Threaded dispatch checks the sum before its first nominal
issuance or worker. The check holds capacity without advancing the counter.
Unused capacity creates no holes and ordinary occurrence order stays unchanged.
A topology poll needs its own slot. Its actual outcome is retained before a
second check covers every returned event, before the first topology mutation.
Conservative bounds may refuse a smaller outcome that would have fit alone.
Refusal is typed and does not acknowledge worker cleanup. Independent timeout
witness capacity must also fit before a topology batch applies. Unlike atomic
SimClock admission, effect-capacity refusal may follow the admitted round’s tick
increment and scheduler selection. A public round at u64::MAX refuses before
its topology poll. Entering at u64::MAX - 1 can admit that poll and leave the
instruction boundary at exhaustion for zero-fresh original settlement.
An already issued nominal callback can return Failure or Blocked with no fresh occurrence. It keeps its original invocation and selected window at exhaustion. Success and application Err retain their actual typed result before publication capacity is checked. If publication cannot fit, existing completion settles the same original canonical effect as Failed, including an unparented invocation, and updates its operation/progress state. The actual callback outcome remains unchanged and its typed returned result and independent refusal remain resident. No hint, WAL effect, readiness consumption or successful continuation is authorized. This is terminal refusal, not positive retry. A fresh nominal invocation must fit its complete bound before issuance. For a threaded fallback only the actual first, already issued nominal pick may run. Other original picks return to the existing scheduler.
| Id | Owner | Status | Reason | Impact | Alternatives considered | Revisit date | Coverage scope |
|---|---|---|---|---|---|---|---|
| rust-finite-effect-occurrence-admission | telltale-machine runtime maintainers | Prepared finite representation boundary | Rust canonical occurrence IDs are finite while Lean model IDs are natural numbers | Complete-dispatch and topology-batch capacity premise, preserving zero-fresh original settlement | Saturation, wrapping, eager unused IDs, per-worker allocation, or replacing the original occurrence | 2027-01-21 | Source preparation, backend/nominal/topology/wave fixtures require actual discovery and execution |
Prepared readiness continuation and session authority state
The typed nominal result now distinguishes Boolean application values from
ReadinessWitness data. Both Rust backends use the existing canonical session
issuer/consumer and the retained original OwnedSession. The witness is consumed
only at gated commit. Lean adds owner/current-generation and issued/consumed
readiness state to the same canonical SessionState, with explicit model owner
admission and issuance independent of callback fields. The original model owner
is retained in the nominal continuation. Model data is not an issued Rust host
handle, and does not establish physical resource custody or asynchronous cleanup.
Value and ValType readiness cases are appended, preserving prior enum order.
Witness data keeps the original registry witness field order and scope shape.
The previously unreleased nominal reply uses an explicitly tagged typed result
(the Bool-only nominal draft shape is rejected). This is not claimed compatible
with old draft nominal JSON/binary artifacts. Released v19 has no nominal reply
surface. No canonical replay/trace v1 field or existing variant is reordered.
Original owner/continuation serde skips are unchanged: decoding a register or
witness never restores live authority. Both JSON and canonical binary roundtrips
and old value/type tags have declared regressions. Broad source-to-model bridge
normalization must explicitly handle the new typed result and cannot erase it.
New Rust and Lean cases are uncompiled/unexecuted source. No static proof of physical custody, complete DSL authority lowering, or restoration follows.
The prepared existing compiled-image bridge uses nominal_image.v2 for explicit
typed Boolean/readiness results and complete original owner/registry observations.
Admission input carries only an owner intent and an after-Blocked scheduling
choice. The actual original runtime/model session issues the witness between
rounds after a genuine Blocked occurrence. Transported witness fields cannot mint
authority. Commit uses the canonical store-owned audit adapter with the current
SimClock tick. Observations include genuine issue, consumption and rejection.
The single-original effect-id relation does not normalize witness ids, owner,
session, generation, predicate, callback ticks, ordering or completion ticks.
The canonical OutstandingEffect.applyEvent contract preserves the original
ordering key for blocked/completed/timeout/cancellation/invalidation transitions.
Only an explicit retry event replaces it. The prepared native producers now
preserve that key, retain no terminal completion for a Blocked transition, and
record the actual current tick for terminal settlement. Bridge output separates
original request issuance, ordering key and completion tick. The existing model
ordering-preservation theorem remains unchanged. This source correction and the
actual Blocked-to-later-success regressions still require execution.
Prepared original source-operation correspondence
The existing compiled-image bridge now preserves typed Begin/Await/Resolve/
Invalidate opcodes and exact required-child manifests. It does not regenerate
these actions from communication projections. Both native backends retain the
same enclosing coroutine holder, distinct from the singleton effect:id child,
and the executable model uses the same canonical coroutine, session ownership,
operation, effect and progress fields. Every selected nominal call captures the
original coroutine-held model owner through the canonical owning-load admission.
Looking up the current registry owner is insufficient. Model owner data does not
prove Rust opaque-handle issuance or native resource custody.
Comparison includes the original enclosing declaration, admitted slots, actual child outcome, proposed resolution, typed first execution failure and canonical parent/progress records. The raw original parent id is retained. Its comparison relation validates the actual role entry and installed original PC before relating the role-local PC and derived occurrence id. It does not normalize ticks, generations, policy, values or failures. Child ids follow the existing validated single-original relation. Missing parent records or executable payload, unknown response families and unsupported fault observations refuse comparison. This is a prepared executable relation, not an elaborated compiler-refinement theorem or full authority-language proof. Required Rust/Lean execution is open.
Finite session-identifier representation
Lean’s session ids are natural numbers. WFVMState requires every resident sid to
be below nextSessionId. Source Open advances that natural-number counter, and
the public loader’s nextFreshSessionId avoids resident ids even for noncanonical
input. Rust’s shared checked session-open admission retains this freshness within
its finite representable range and explicitly refuses exhausted or non-fresh ids.
It never wraps, saturates into reuse, replaces a resident, or reconstructs an owner.
The new typed Rust allocation fault has no corresponding exhausted-Nat Lean case.
Parity therefore requires the finite fresh-allocation premise. It does not assert
that Lean’s unbounded execution takes a finite host refusal step.
The existing finite-effect occurrence admission remains a separate counter family. Session allocation uses the original session-store high-water mark and exclusive store transaction, not an effect occurrence, extra allocator, nonce registry or new authority. Source and backend boundary tests in this preparation still require actual harness discovery and execution, native/default/WASM configuration checks, strict lint and the required full final-source model/CI/release gates.
Host Lean child custody
Lean bridge process deadlines are physical host execution bounds, outside the simulated protocol-machine clock. The original admitted monotonic horizon also covers source-fallback configuration selection and input transmission. Neither cleanup grace nor model ticks restart or widen that operation horizon.
The bridge retains its original Unix child group, stdin writer and separate stdout/stderr readers until physical settlement. Source-backed runner and validator commands delegate through the canonical project launcher, which transfers its actual local writer-lock descriptor into the selected shared or native owner. Shared protocol-v1 custody retains that descriptor through its original Lake lock, budget lease and timing command. It is not reconstructed from a PID, an observation, or a serialized record. On timeout the bridge asks the original owner to cancel and waits. It never hardkills the sole shared cleanup owner first. The shared owner retains its own existing escalation and actual child-wait responsibility. Native/custom group cleanup has its separate existing two-second TERM grace and actual wait, without claiming custody of arbitrarily detached descendants.
Primary I/O failures, independent cleanup causes, original child status and partial separate streams remain retained. SIGKILL cannot be trapped. An external hardkill or a lost native wait is not evidence of successful descendant cleanup. This host contract supplies no theorem-pack inventory, semantic publication, cryptographic authority or model-time transition. Positive execution and same-binary negative controls on the final release source remain required.
Version 22 is a breaking bridge API transition: the public exhaustive
LeanRunnerError, ProtocolMachineRunnerError, and HeapParityRunnerError
variants retain cleanup causes, original child status, and partial streams.
Downstream timeout patterns must bind those fields or use ... Constructing
these variants requires the new fields. Original I/O errors remain available
through the error source. The prelude continues to reexport the same types.
Aura’s current Quint adapter only reads LEAN_BRIDGE_SCHEMA_VERSION and needs
no error-pattern migration. This API change does not manufacture Lean evidence.
The explicitly selected protocol2 host launcher uses the existing shared resource request while retaining custody protocol1. The original one-worker profile reserves8168MiB, including the original1024MiB overhead and7144MiB safe worker reservation. This complete charge is independent of the shared ledger’s5120MiB compatibility weight. The protocol2 adapter refuses a different worker set. Ordinary protocol1 calls retain their original worker arguments and caller overrides. It retains the actual unlinked request descriptor and original writer descriptor through the existing pass-fd chain. Resource names identify canonical scheduling domains and do not reconstruct live authority. Source identity, when provided, is the existing outer admission fingerprint and remains observation metadata.
A shared refusal never retries native execution. A missing protocol2 endpoint permits the announced local path only when the original one-worker profile fits the selected capacity and reserve. This is no machine-wide grant or physical teardown proof. Protocol-v2 forwarded cancellation returns the original wrapper classification143. The canonical timing record separately retains the actual inner command outcome, including a trap’s native41. Consumers retain both outcomes and actual cleanup acknowledgments. Neither invalidation nor an equal-valued identifier proves that the original child, writer descriptor or memory lease has settled.
Theorem Program
This document maps the three-paper theorem program to implemented Lean modules and runtime surfaces.
3 Paper Structure
The theorem program is organized into three stages.
| Stage | Paper focus | Core output |
|---|---|---|
| Paper 1 | coherence and effect bridge | preservation kernel and typed protocol-machine bridge premises |
| Paper 2 | quantitative and decision dynamics | bounds, decidability packages, crash and Byzantine interfaces |
| Paper 3 | reconfiguration and envelope adequacy | harmony under reconfiguration, envelope exactness, admission and adherence |
Paper 1 Mapping
Paper 1 centers on operational coherence and the Consume kernel.
| Claim family | Lean anchor modules |
|---|---|
message-type alignment via Consume | Protocol/Coherence/Consume.lean |
| subtype replacement and coherence lift | Protocol/Coherence/SubtypeReplacement*.lean |
| coherence preservation stack | Protocol/Preservation*.lean, Protocol/Coherence/* |
| typed effect bridge to protocol machine | Runtime/Proofs/protocol machine/BridgeStrengthening.lean, Runtime/Proofs/EffectBisim/* |
Claim scope is assumption-scoped. Delivery and monitor premises remain explicit in the bridge layer.
Paper 2 Mapping
Paper 2 adds quantitative dynamics and algorithmic decision surfaces.
| Claim family | Lean anchor modules |
|---|---|
| weighted quantitative descent and scheduler-lifted bounds | Runtime/Proofs/WeightedMeasure/*, Runtime/Proofs/SchedulingBound*.lean, Runtime/Proofs/Lyapunov.lean |
| regular finite-reachability decidability | SessionCoTypes/AsyncSubtyping/*, SessionCoTypes/Coinductive/Regular*.lean |
| crash-stop characterization | Protocol/CrashTolerance.lean |
| Byzantine exact safety interface package | distributed adapter and theorem-pack modules |
Bound classes differ by theorem. Productive-step bounds are exact under premises. Scheduler-lifted totals are profile-dependent conservative bounds.
Paper 3 Mapping
Paper 3 integrates reconfiguration, exact envelope characterization, and protocol-machine adherence.
| Claim family | Lean anchor modules |
|---|---|
| reconfiguration harmony for link and delegation | Protocol/Deployment/Linking*.lean, harmony and delegation modules |
| relative minimality and composed-system conservation | Protocol/Coherence/*, linking and Lyapunov modules |
| envelope exactness and normalization | Runtime/Adequacy/EnvelopeCore/* |
| adequacy and runtime adherence | Runtime/Adequacy/*, Runtime/Proofs/TheoremPack/* |
| Byzantine envelope extension | distributed adapters and theorem-pack Byzantine surfaces |
This stage ties proof objects to runtime capability and admission surfaces.
Assumption and Boundary Model
Each theorem family is tied to explicit assumption bundles.
| Boundary class | Meaning |
|---|---|
| exact under assumptions | theorem gives iff or maximality class under declared premises |
| conservative under profile | theorem gives safe upper bound or admitted envelope class |
| interface only | package provides typed boundary and witness hooks without stronger global claim |
| out of scope | intentionally deferred claim class |
Scheduler-lifted total-step bounds are an example of the conservative class. Byzantine liveness outside declared timing and fairness bundles falls under out of scope.
Runtime Capability Link
The theorem program is operationalized through theorem-pack inventory and admission gates.
Runtime/Proofs/TheoremPack/API.lean provides gate aliases. Runtime/Proofs/TheoremPack/Inventory.lean provides capability key extraction. Runtime/Adequacy/EnvelopeCore/AdmissionLogic.lean provides admission soundness, completeness, and diagnostics categories.
Rust admission paths in rust/machine/src/runtime_contracts.rs and rust/machine/src/composition.rs consume these proof-facing concepts as executable gates.
Classical theorem packs follow the same path: protocol or transport constructors build semantic inputs, runtime adapter profiles attach them to the invariant space, and buildProtocolMachineTheoremPack emits artifacts and inventory keys. The current classical keys are documentation-background inventory, not Rust runtime-admission gates, and the real-analysis dependency is isolated behind the named ClassicalAnalysis API/Instance boundary. The classical families are honest conditional interfaces: Little’s law, MaxWeight optimality, concentration tails, and spectral-gap termination are assumed inputs, and the heavy-traffic, mean-field, and functional-CLT families prove only elementary identities. classicalEvidenceInventory records the class of each key.
Related Docs
Theorem Pack Inputs
This document catalogs every input that can contribute to a ProtocolMachineTheoremPack. The pack is the capability surface consumed by release gates, runtime admission paths, and conformance reports. All theorem-pack artifacts are derived from one ProtocolMachineInvariantSpaceWithProfiles by buildProtocolMachineTheoremPack in lean/Runtime/Proofs/TheoremPack/Build.lean.
Pack Shape
ProtocolMachineTheoremPack is a record of Option artifact fields, one per theorem family. Inputs arrive in two shapes. Invariant-space inputs attach via API setters on the combined space. Optional profile inputs attach via family-specific with* setters and are consumed by the builder.
The combined invariant space is ProtocolMachineInvariantSpaceWithProfiles in lean/Runtime/Proofs/TheoremPack/Profiles.lean. It extends the distributed-profile space and adds a classical profile record. Two projection views (toDistributedSpace, toClassicalSpace) let builders consume the distributed or classical subset in isolation.
Core Invariant-Space Inputs
Three input families enter the pack directly from the invariant space. They do not require a distributed or classical profile.
| Family | Source field | API setter | Inventory key |
|---|---|---|---|
| termination | liveness? | withLivenessBundle | termination |
| output condition soundness | outputConditionWitness? | withOutputCondition | output_condition_soundness |
| semantic objects | semanticObjectWitnesses? | withSemanticObjectWitnesses | semantic_object_* |
Termination artifacts use Adapters.toLivenessBundle? from lean/Runtime/Proofs/Adapters/Progress.lean and the theorem protocol_machine_termination_from_invariant_space. The termination bound is derived (FairTermination.fair_termination) from one-step ranking obligations in ProtocolMachineTerminationModel (measure non-increasing, strict decrease on enabled steps, enabledness stable under unproductive steps, non-complete ⇒ some role enabled). Those obligations are not yet discharged for the concrete execInstr step. Output-condition artifacts lift soundness from outputConditionWitness? into OutputConditionArtifact. Semantic-object artifacts are built by SemanticObjectArtifacts.ofWitnessBundle and cover core invariants, outstanding effects, semantic handoffs, authoritative-read publication, materialization success, progress contracts, effect contracts, replay failure exactness, cross-target progress-dependent work, and transformation-local obligations.
Distributed Families
Distributed profile wrappers are defined in lean/Runtime/Proofs/Adapters/Distributed/ProfileWrappers.lean. Each family attaches through a with* setter on the combined space. Inventory keys are string labels from lean/Runtime/Proofs/TheoremPack/Inventory.lean, not Lean theorem names. Each key reports whether the profile artifact is present. The theorems are carried by the family profile. For example, quorum_geometry_safety corresponds to Distributed.QuorumGeometry.no_conflicting_commits_of_protocol, and accountable_safety corresponds to Distributed.AccountableSafety.accountable_safety_of_protocol.
| Family | Profile setter | Inventory key |
|---|---|---|
| FLP lower bound | withFLP | flp_lower_bound |
| FLP impossibility | withFLP | flp_impossibility |
| CAP impossibility | withCAP | cap_impossibility |
| quorum geometry | withQuorumGeometry | quorum_geometry_safety |
| partial synchrony | withPartialSynchrony | partial_synchrony_liveness |
| responsiveness | withResponsiveness | responsiveness |
| Nakamoto security | withNakamoto | nakamoto_security |
| reconfiguration | withReconfiguration | reconfiguration_safety |
| atomic broadcast | withAtomicBroadcast | atomic_broadcast_ordering |
| accountable safety | withAccountableSafety | accountable_safety |
| failure detectors | withFailureDetectors | failure_detector_boundaries |
| data availability | withDataAvailability | data_availability_retrievability |
| coordination | withCoordination | coordination_characterization |
| CRDT envelope family | withCRDT | crdt_envelope_and_equivalence |
| CRDT op-core erasure | withCRDTErasure | (stored in pack as crdtErasure?) |
| triangle of forgetting | withTriangleOfForgetting | triangle_of_forgetting_impossibility |
| Byzantine safety | withByzantineSafety | byzantine_safety_characterization |
| consensus envelope | withConsensusEnvelope | consensus_envelope |
| failure envelope | withFailureEnvelope | failure_envelope |
| protocol machine envelope adherence | withProtocolMachineEnvelopeAdherence | protocol_machine_envelope_adherence |
| protocol machine envelope admission | withProtocolMachineEnvelopeAdmission | protocol_machine_envelope_admission |
| protocol envelope bridge | withProtocolEnvelopeBridge | protocol_envelope_bridge |
Three inventory keys are derived from other profiles and have no distinct setter. calm_characterization is derived from the coordination profile. crdt_monotonicity is derived from the CRDT profile. effect_interface_bridge is derived from the conjunction of protocolMachineEnvelopeAdherence? and protocolEnvelopeBridge?.
Classical Families
Classical wrappers are defined in lean/Runtime/Proofs/Adapters/Classical.lean. The inventory keys below are string labels, not Lean theorem names. Each classical family has a per-family setter on ProtocolMachineInvariantSpaceWithProfiles. withClassicalProfiles remains available as a batch replacement API when a caller wants to replace the whole ClassicalProfiles record.
| Family | Setter | Wrapper type | Inventory key |
|---|---|---|---|
| Foster-Lyapunov | withFoster | FosterProfile | classical_foster |
| MaxWeight | withMaxWeight | MaxWeightProfile | classical_maxweight |
| large deviations | withLDP | LDPProfile | classical_ldp |
| mean-field | withMeanField | MeanFieldProfile | classical_mean_field |
| heavy-traffic | withHeavyTraffic | HeavyTrafficProfile | classical_heavy_traffic |
| mixing time | withMixing | MixingProfile | classical_mixing |
| fluid limit | withFluid | FluidProfile | classical_fluid |
| concentration bounds | withConcentration | ConcentrationProfile | classical_concentration |
| Little’s law | withLittlesLaw | LittlesLawProfile | classical_littles_law |
| functional CLT | withFunctionalCLT | FunctionalCLTProfile | classical_functional_clt |
| spectral-gap termination | withSpectralGap | SpectralGapProfile | classical_spectral_gap_termination |
Classical families are honest conditional interfaces (“Option B”). The named classical theorems (Little’s law, MaxWeight optimality, sub-Gaussian tails, large-deviation and mixing envelopes, spectral-gap termination, Cheeger) are not proved in Lean. They enter as caller-supplied witness fields, and an inventory flag of true only records that an artifact is present. classicalEvidenceInventory in Runtime.Proofs.TheoremPack.Inventory classifies each key:
| Inventory key | Evidence class |
|---|---|
classical_foster | provedDeterministic (deterministic drift; no Harris recurrence) |
classical_maxweight | assumedClassicalInput |
classical_ldp | derivedFromAssumedInput |
classical_mean_field | elementaryIdentity (finite-average reindexing) |
classical_heavy_traffic | elementaryIdentity ((σ√n)² = σ²n) |
classical_mixing | derivedFromAssumedInput |
classical_fluid | derivedFromAssumedInput |
classical_concentration | assumedClassicalInput (tail bound) |
classical_littles_law | assumedClassicalInput |
classical_functional_clt | elementaryIdentity (degenerate constant path; not a CLT) |
classical_spectral_gap_termination | assumedClassicalInput |
Execution Profile
ProtocolMachineExecutionProfile is the proof-carrying execution context derived from the invariant space. It fixes fairness assumptions, admissibility classes, escalation-window classes, a theorem-pack eligibility list, and a necessity catalog. The builder function is ProtocolMachineInvariantSpaceWithProfiles.executionProfile.
Fairness assumptions enumerate scheduleConfluence, starvationFreedom, and livenessFairness. These are declared assumptions. The proved scheduler fact is schedule_nonblocking (one-step: a queued ready coroutine means schedule steps), which is not starvation freedom. The scheduler artifact’s scheduleDeterministic field records selection determinism. Confluence of independent steps is LocalStepSystem.independent_steps_commute and is not yet instantiated for execInstr. Admissibility classes cover localEnvelope, shardedEnvelope, and protocolEnvelopeBridge. Escalation-window classes cover progressContractBounded, admissionComplexityBounded, and protocolBridgeBounded.
The execution profile also carries a list of TransportNecessityProfile values. executionProfile_necessity_hardened_of_all_proven_necessary and executionProfile_necessity_minimal_basis_of_hardened_and_oracles lift per-profile necessity into catalog-level hardening and minimal-basis closure. Both theorems are stated in lean/Runtime/Proofs/TheoremPack/Profiles.lean.
Proof-Carrying Metadata
ProofCarryingArtifactMetadata summarizes profile, progress, and envelope metadata alongside each pack.
| Component | Record | Source |
|---|---|---|
| execution profile and eligibility | ProfileArtifactMetadata | execution profile on the combined space |
| progress contracts and failure taxonomy | ProgressArtifactMetadata | semantic-object witnesses |
| envelope adherence, admission, bridge | EnvelopeArtifactMetadata | derived distributed profiles |
ProofCarryingArtifactMetadata.ofInvariantSpace assembles these from the combined space and the three envelope-presence booleans. Metadata inventory keys are enumerated in proofCarryingMetadataInventory in lean/Runtime/Proofs/TheoremPack/Inventory.lean. The metadata_* keys end in _premise_supplied and record presence only. metadata_progress_contracts_premise_supplied tracks semantic-object witnesses, while metadata_replay_failure_exactness_premise_supplied tracks the replay/failure-exactness witness specifically.
Inventory and Capability Keys
theoremInventory in lean/Runtime/Proofs/TheoremPack/Inventory.lean projects the pack into a flat list of (key, Bool) pairs. Keys cover termination, output-condition soundness, every distributed family, every classical family, the effect-interface bridge, and each semantic-object component. Release and admission paths consume this inventory rather than the pack directly. Each Bool is a presence flag meaning “premise bundle supplied”, not “proved”. Key strings are kept stable because Rust consumes them.
theoremInventoryWithDeterminism augments the inventory with determinism-policy booleans. The semantic-object sub-inventory is produced by SemanticObjectArtifacts.inventory and folds each semantic witness into a capability key under the semantic_object_* prefix.
Admission Boundary
transportedTheoremBoundaryInventory in lean/Runtime/Proofs/TheoremPack/AdmissionBoundary.lean tags each inventory key with a TransportedTheoremUsageClass. The class distinguishes black-box premise use, runtime-critical instantiated premises, and documentation-only references.
runtimeCriticalTransportedTheoremBoundaryInventory narrows the ledger to families that gate runtime admission. rustRuntimeCriticalTransportedTheoremKeys and leanRuntimeCriticalTransportedTheoremKeys expose the Rust and Lean subsets consumed by admission paths. runtimeCriticalTransportedTheoremsExplicit_true certifies that the Rust and Lean lists agree.
Lean Evidence for Envelope Keys
Rust admission of the three envelope keys (protocol_machine_envelope_adherence, protocol_machine_envelope_admission, protocol_envelope_bridge) is evidence-backed. ProtocolMachineExecutionProfile and TheoremPackCapabilities carry a LeanEvidenceInventory (key to Bool), parsed by LeanEvidenceInventory::from_json_str from the Lean theoremInventory export, either as a {key: bool} object or as the List (String × Bool) encoding [[key, bool], ...]. execution_profile_supported admits a required envelope key only when that inventory reports true. Self-asserted theorem_pack_eligibility Bools without evidence are rejected. Missing keys are not admitted.
The defaults (ProtocolMachineExecutionProfile::full(), TheoremPackCapabilities::full()) carry an empty inventory, so they do not admit any envelope key. Callers attach evidence with with_lean_evidence. The explicit test and development opt-in is assume_envelope_evidence_for_testing() on either type. It asserts all three keys without Lean evidence and must not be used in production admission paths. An inventory flag records that a premise bundle was supplied, not that the premise was proved.
Transport Contracts
Theorem-backed distributed claims also require selected transport contracts. The Rust machine consumes RuntimeTransportContract values as semantic evidence, not as TCP-, TLS-, or key-management-specific implementation details. Concrete transport crates are responsible for mapping their configured mode into those fields.
The standard protocol-origin profile requires role-addressed routing, authenticated peers, per-peer FIFO delivery, fail-closed unknown-role handling, and no message synthesis. For telltale-transport, TcpTransportConfig::runtime_transport_contract() maps pre-shared-key TCP to authenticated_peers = true and trusted-network TCP to authenticated_peers = false. Trusted-network contracts are intentionally rejected for theorem-pack profiles that depend on authenticated protocol origins.
Release Conformance
buildReleaseConformanceReport in lean/Runtime/Proofs/TheoremPack/ReleaseConformance.lean assembles a ReleaseConformanceReport from the pack, a ReleaseRunEvidence record, and a replay trace. Each gate checks distinct evidence. The cross-target and restart gates require the failure-envelope premise bundle plus the crossTargetRunsAgree and restartReplayPassed run bits respectively. The single-thread gate requires the adherence premise bundle plus singleThreadRunPassed. The multi-thread gate requires multiThreadRunPassed. The sharded gate requires the protocol-bridge premise bundle plus shardedRunPassed. releaseReady is always the conjunction of replay conformance and all five gates. There is no untagged bypass, and ReleaseRunEvidence.none fails every gate. releaseBuildGate_sound proves that a passing gate implies each piece of evidence. Gates report that premises were supplied and runs passed, not that the premises were proved.
Optimization transformations are enumerated by RuntimeTransformationEnvelopeClass. The four classes are schedulerPermutation, waveWidening, effectReordering, and failureReplayNormalization. transformationClassRequiredCapabilities defines the inventory keys each class requires, and transformationClassEligible is the inventory gate.
CertifiedReplayEquivalenceClass pairs a transformation class with a certificate reference. defaultCertifiedReplayClasses is the canonical list admitted by release gates. replayConformsToClasses is the per-class replay gate used by the build gate.
Capability API Gates
lean/Runtime/Proofs/TheoremPack/API.lean exposes the runtime capability gates that consume the pack.
| Gate | Artifact requirement |
|---|---|
canAdmitShardPlacement | protocolEnvelopeBridge? |
canLiveMigrate | protocolEnvelopeBridge? |
canRefinePlacement | protocolEnvelopeBridge? |
canRelaxReordering | protocolEnvelopeBridge? |
canUseMixedDeterminismProfiles | protocolMachineEnvelopeAdherence? and protocolMachineEnvelopeAdmission? |
canOperateUnderByzantineEnvelope | byzantineSafety? and protocolMachineEnvelopeAdherence? |
canAutoscaleOrRepartition | protocolEnvelopeBridge? |
Rust admission paths in rust/machine/src/runtime_contracts.rs and rust/machine/src/composition.rs consume these gates as executable admission checks. claimedCapabilitiesWithinInventory verifies that a list of declared capability tags is supported by the pack capabilities. envelopeCapabilitySnapshot extracts the envelope subset used by conformance reports.
Assumption Discipline
Each family carries a proof-bearing semantic profile. Completed families avoid final-theorem witness fields. Profiles expose smaller laws such as delivery and ordering facts, quorum geometry, timed-run predicates, CRDT laws, chain growth and quality, shard reconstruction, or slashable-evidence construction.
Capability bits indicate that the theorem-pack builder derived the corresponding artifact from a profile. They do not imply stronger claims outside the family assumption bundle or an explicit documented trust boundary.
Runtime Admission Impact
Runtime features that require profile evidence are gate-controlled. Examples include mixed determinism profiles, Byzantine envelope operation, autoscaling and repartition requests, and placement refinement. Gate aliases are provided in lean/Runtime/Proofs/TheoremPack/API.lean. Rust runtime admission paths consume these aliases.
Related Docs
- Lean Verification
- Capability Admission
- Theorem Program
- Verification Inventory
- Glossary and Notation Index
API Reference
This document provides a high level map of the public APIs. For full signatures, use the crate level lib.rs files and generated rustdoc.
Core Crates
telltale
Core session type library with channel primitives and macros.
Key exports:
GlobalType,LocalTypeR,Label,PayloadSortRole,Roles,Messagederive macros- Channel traits and session state types
See rust/src/lib.rs for the full list of re-exports.
Generated tell! APIs use one canonical effect-boundary import style:
- import
Protocol::effects effects::Runtimefor host traitseffects::RuntimeRequest/effects::RuntimeOutcomefor typed dispatcheffects::runtime::operation("ready")oreffects::runtime::READYfor generated per-operation semantic metadataProtocol::proof_statusfor theorem-pack, tier, parity, and agreement/finality metadata
telltale-types
Type definitions shared across the stack.
Key exports:
GlobalType,LocalTypeR,Label,PayloadSortContentId,Blake3Hasher,ContentStore,KeyedContentStoreDefaultContentHasherandDefaultContentIdfor central content-hash policySha256HasherandContentIdSha256when thesha256feature is enabled- Merge helpers (
merge,merge_all,can_merge) and canonical-serialization utilities
See rust/types/src/lib.rs for re-exports.
telltale-theory
Session-type algorithms and executable theory checks.
Key modules:
- Projection:
telltale_theory::projection::{project, project_all, MemoizedProjector} - Merge, duality, well-formedness, and semantics checks
- Subtyping (feature-gated):
telltale_theory::subtyping::{async_subtype, sync_subtype}
Exports are module-scoped, not re-exported at crate root. See rust/theory/src/lib.rs for the complete feature-gated API.
telltale-runtime
Choreographic DSL, projection, and effect execution.
Key exports:
- AST types:
Choreography,Protocol,Role,MessageType - Effect system:
Program,ProgramBuilder,interpret - Handlers:
ChoreoHandler,InMemoryHandler,TelltaleHandler - Topology:
Topology,TopologyHandler,TransportType - Heap:
Heap,DefaultHeapHasher,Hasher,Resource,ResourceId,CanonicalHeapEncoding,CanonicalHeapEncoder,HEAP_ENCODING_MAGIC,HEAP_ENCODING_VERSION,resource_leaf_hash,nullifier_leaf_hash,merkle_node_hash,MerkleTree,HeapCommitment - Heap preimage helpers (module-scoped, not at crate root):
telltale_runtime::heap::{resource_id_preimage, resource_leaf_preimage, nullifier_leaf_preimage, merkle_node_preimage, heap_commitment_preimage} - Extensions:
ExtensionRegistry,GrammarExtension,ProtocolExtension
See rust/runtime/src/lib.rs for the full export surface.
telltale-machine
Protocol-machine and guest-runtime surfaces for executing projected local types.
Canonical public modules:
telltale_machine::modeltelltale_machine::runtimetelltale_machine::semantics
Key exports:
ProtocolMachine,ProtocolMachineConfig,GuestRuntime,SchedPolicy,SimClockInstr,Value,SessionStore,SessionIdOwnedSession,EffectHandler, andNestedProtocolMachineHandler- proof-aligned effect algebra:
EffectSemanticClass,EffectRetryShape,EffectCompositionPolicyEffectResponsibilityDomain - canonical semantic objects:
OperationInstance,OutstandingEffect,SemanticHandoff,TransformationObligation,AuthoritativeRead,ObservedRead,MaterializationProof,CanonicalHandle,PublicationEvent,Region,ProgressContract,ProgressTransition,ProtocolMachineSemanticObjects - first-class capability/finalization taxonomy:
ProtocolCriticalCapabilityClass,ProtocolCriticalCapabilityLifecycleState,ProtocolCriticalCapabilityArtifact,ProtocolCriticalCapabilityLifecycleRecord,ProtocolMachineFinalization,FinalizationPath,FinalizationReadClass,FinalizationStage - proof-carrying runtime profiles:
ProtocolMachineExecutionProfile,ProtocolMachineFairnessAssumption,ProtocolMachineAdmissibilityClass,ProtocolMachineEscalationWindowClass - runtime introspection (methods on
ProtocolMachine/GuestRuntime):operation_instances(),outstanding_effects(),semantic_objects(),progress_contracts(),progress_transitions(),publication_events(),require_authoritative_read(),require_canonical_handle(),protocol_machine_finalization(),capability_lifecycle_audit_log(),semantic_audit_log(),canonical_replay_fragment()
GuestRuntime is the Telltale-owned runtime instantiated around the protocol
machine. EffectHandler is the host-runtime boundary implemented by embedders
and simulators.
telltale-search
Generic weighted-graph search substrate for the Telltale workspace.
Module structure:
telltale_search::costtelltale_search::domaintelltale_search::machinetelltale_search::admissiontelltale_search::runtimetelltale_search::observe
The supported boundary is intentionally generic:
- canonical search-machine semantics
- serial min-key batch extraction and deterministic commit
- full legal batch execution even under chunked parallel schedulers
- search replay and comparison artifacts
- scheduler and fairness capability vocabulary
- explicit graph-epoch and snapshot inputs
Downstream consumers are expected to provide application-specific node, edge, heuristic, and epoch semantics through typed domain traits.
Current serial-core exports include:
SearchCost,EpsilonMilliSearchDomain,SearchQuery,SearchQueryError,SearchSelectedResultSemanticsClassSearchMachineCanonicalBatch,Proposal,ProposalKind,SelectedSolutionSearchBudgetState,SearchTraceStateSearchError,SearchInvariantViolation- profile/admission types:
SearchDeterminismMode,SearchSchedulerProfile,SearchFairnessAssumption,SearchObservableClass,SearchClaimClass,CommutativityRegionClass,SearchDUser,SearchCertifiedCapability,AdmissionRejectionReason,check_capability_containment(...) - observation/comparison types:
SearchObservationArtifact,NormalizedCommitRecord,SelectedSolutionPublicationRecord,ObservationComparison,ObservationRelation,compare_observations(...) - runtime/replay types:
ProposalExecutor,SerialProposalExecutor,NativeParallelExecutor,NativeParallelExecutorError,AuthorityReadSet,AuthorityWriteSet,AuthoritySurface,SchedulerArtifact,SchedulerArtifactClass,ProgressSummary,TotalStepMode,SearchExecutionReport,SearchReplayArtifact,ReplayRoundRecord,ReplayExpectation,ReplayError,SearchRunConfig,SearchRunTermination,run_with_executor(...),replay_observation(...),EpochReconfigurationRequest,commit_epoch_reconfiguration(...),proposals_independent(...)
Replay validation is derived from canonical round commits rather than trusting stored summary fields when selected-result semantics are query-derived. For domain-defined selected-result semantics, replay fails closed instead of silently falling back to a compatibility goal anchor. Epoch reconfiguration is barriered and resets frontier/parent/incumbent state before reseeding the new epoch from the start node. Fairness bundles are explicit observation and admission surfaces. Observation artifacts also carry canonical parent identity and selected-result publication traces, while fairness and capability bundles use set semantics rather than order-sensitive vectors.
Import posture:
- prefer
SearchQuery::try_multi_goal(...)andSearchQuery::try_candidate_set(...) - prefer generic selected-result types over route/incumbent aliases
- use
telltale_search::compatonly for legacy migration - treat
SearchExecutionPolicyas an explicit compatibility matrix:CanonicalSerial=> serial + width1,ThreadedExactSingleLane=> native parallel + width1,BatchedParallelExact/BatchedParallelEnvelopeBounded=> native parallel + width> 1 - treat
SchedulerStepBudget(n)as the supported budgeted-anytime effort mode andSearchCachingProfile::IncrementalReuseas outside the stable import posture until implemented
Optional layers above the core crate:
telltale_simulator::project_search_run(...)telltale_viewer::project_search_artifacts(...)
Module access (not re-exported at crate root):
- Effect boundary:
telltale_machine::model::effects::EffectHandler,EffectRequest,EffectOutcome,EffectInterfaceMetadata,EffectExchangeRecord,EffectCompositionPolicyEffectSemanticClass,EffectRetryShape,EffectResponsibilityDomain,SendDecision,SendDecisionInputThe typed internal durability request isEffectRequestBody::WalSync. - Effect trace:
telltale_machine::model::effects::RecordingEffectHandler,ReplayEffectHandler - Durability:
telltale_machine::model::durability::{AgreementWal, AgreementWalArtifact, AgreementWalEntry, AgreementWalHandler, EvidenceIdResolver, EvidenceOutcomeCache, EvidenceOutcomeCacheArtifact, EvidenceOutcomeCacheEntry, EvidencePersistenceHandler, DurableRecoveryAction, DurableRecoveryDecision, DurableRecoveryMetadata, DurableRecoveryPlan, FileAgreementWal, FileEvidenceOutcomeCache, InMemoryAgreementWal, InMemoryEvidenceOutcomeCache, PersistedDurabilityArtifact, PersistedDurabilityPayload, WalSyncMode, WalSyncRequest}These are the authoritative typed contracts for durable agreement WALs, evidence outcome caches, recovery metadata, typed recovery planning, and the internalwal_syncdurability boundary. Helper/generated/viewer surfaces should consume projections of these artifacts rather than defining peer durable state. Downstream integrations should implementAgreementWaland/orEvidenceOutcomeCachedirectly rather than introducing backend-branded wrapper APIs. The supported contract is append/read/load plus fail-closed error returns. Storage-specific retries, replication, or transport details must remain behind that trait boundary. - Child-effect aggregation:
EffectCompositionPolicyis a secondary sibling-effect algebra used beneath parent agreement contracts, not the top-level agreement model - Loader:
telltale_machine::runtime::loader::CodeImage - Runtime contracts:
telltale_machine::runtime::failure::{RuntimeContracts, ProtocolMachineExecutionProfile} - Runtime runner:
telltale_machine::runtime::runner::{ProtocolMachine, GuestRuntime, StepResult, RunStatus} - Semantics:
telltale_machine::semantics::exec::{ExecResult, ExecStatus, StepPack}
See rust/machine/src/lib.rs for the full API.
See Effect Handlers and Session Types for integration-boundary guidance.
telltale-simulator
Simulation utilities built on the protocol machine.
Key exports:
- Harness surface in
rust/simulator/src/harness.rs:HostAdapter,DirectAdapter,FieldAdapter,HarnessSpec,HarnessConfig,SimulationHarness,BatchConfig,BatchRunResultFieldAdapter::from_scenario(...)requires built-in scenario field params. Generic harness runs may omitScenario.fieldwhen the adapter supplies its own initial states or environment models.SimulationHarness::run_batch(...)andrun_batch_with(...)preserve input-order results while parallelizing independent runs.
Module access (not re-exported at crate root):
telltale_simulator::trace::Trace,StepRecordtelltale_simulator::runner::run,run_concurrent,run_with_scenario,ChoreographySpectelltale_simulator::scenario::{Scenario, ExecutionSpec, ExecutionBackend, ResolvedExecution, ResolvedExecutionBackend, DurabilitySpec, DurabilityMode}telltale_simulator::runner::{resume_with_checkpoint_artifact, resume_with_durable_checkpoint_artifact, DurableResumeArtifacts, DurableResumeSummary}resume_with_checkpoint_artifact(...)is the exact non-durable checkpoint lane. Scenarios that declareDurabilityMode::Walmust resume throughresume_with_durable_checkpoint_artifact(...)so the simulator cannot bypass the typed WAL/evidence contract.telltale_simulator::durability::{FaultInjectingAgreementWal, DurableFaultProgram, ScheduledDurableFault, DurableFaultKind, DurableFaultRecord, DurableRecoveryRun, DurablePropertyReport, run_durable_recovery_case, durable_property_report}This is the reusable simulator-side durability assurance surface for deterministic fault injection, crash/recovery comparison, and durable property monitoring.telltale_simulator::durability::{DurableInspectionReport, DurableWalEntryProjection, EvidenceCacheEntryProjection, inspect_durable_artifacts}These are observed-only tooling projections of authoritative durable artifacts for viewer and CLI inspection.- CLI surface:
cargo run -p telltale-simulator --bin durable -- --wal <wal.cbor> --cache <cache.cbor> [--checkpoint <checkpoint.cbor> --scenario <scenario.toml>] [--json]This inspection-only lane projects typed WAL/cache/recovery artifacts without exposing backend-specific storage details. telltale_simulator::generated::{GeneratedEffectScenario, GeneratedEffectScenarioBuilder, GeneratedEffectSimulationReport, ScenarioEffectDisposition, ScenarioEffectResult, ScenarioEffectStep}Helper-only generated-effect support.GeneratedEffectSimulationReportexposes helper accessors, not authoritative replay or theorem-classification fields.telltale_simulator::{CheckpointArtifact, PersistedReplayArtifact, PersistedReplayPayload}Typed persisted replay/checkpoint wrappers for on-disk simulator artifacts. Durable agreement WALs and recovery metadata intentionally remain machine-level typed contracts instead of simulator helper exports.telltale_viewer::{SemanticComparisonResult, TheoremAwareCounterexample, DeterministicSweepReport, ExperimentSuiteReport, EffectTraceArtifact, MinimizationResult, ViewerExtensionManifest}Shared viewer/webapp tooling surfaces for comparison, counterexamples, sweeps, effects, minimization, and downstream overlays.- Contract checks in
rust/simulator/src/contracts.rs:ContractCheckConfig,ContractCheckReport,evaluate_contracts,assert_contracts - Preset helpers in
rust/simulator/src/presets.rs - Field handlers and factory:
IsingHandler,HamiltonianHandler,ContinuumFieldHandler,handler_from_fieldinrust/simulator/src/field_handlers/
telltale-bridge
Lean bridge for JSON export, import, and validation.
Key exports:
global_to_json,local_to_json,json_to_global,json_to_localLeanRunner,Validator,ValidationResultHeapParityRunner,HeapParityOutputProtocolMachineSemanticObjectsand semantic-object schema helpers These come from the same canonical semantic-object family astelltale_machine::model::semantic_objects, not a bridge-local duplicate.
See Rust-Lean Bridge and Parity for details.
telltale-transport
Reference TCP transport implementation for choreography topologies.
Key exports:
TcpTransport,TcpTransportConfig,TransportState- Resolver and factory surfaces:
EnvResolver,StaticResolver,TcpTransportFactory - Re-exported transport traits/types:
Transport,TransportError,TransportResult,RoleName
For the transport-contract boundary example, run cargo run -p telltale-transport --example theorem_admission. The example does not open sockets. It shows that pre-shared-key TCP exports an authenticated runtime contract and trusted-network TCP fails theorem admission on authenticated_peers.
TLS is intentionally out of scope for this reference transport. Production deployments that require confidentiality or certificate-bound identity should wrap or replace it and export equivalent semantic contract evidence.
See rust/transport/src/lib.rs for the current public surface.
Guidance
When you need an exact signature, open the crate lib.rs and follow re-exports to the module definition. This keeps the reference accurate as the API evolves.
Verification Inventory
Lean metrics check mode compares generated source counts without modifying the tracked code map or renewing its editorial date. The minimal-environment gate also exercises an unchanged historical date and actual source-count drift. Failed checks must leave the original code map byte-for-byte unchanged.
The pinned Nix development shell provides ripgrep for the Lean proof-audit scripts in the canonical fast-structure lane. Release validation must not rely on an undeclared host installation of this required tool.
This page is the authoritative inventory for verification counts. Only counts that are stable enough to check automatically are tracked here.
The goal is to track coverage of key system properties and regression gates, not raw unit-test volume.
When one of these values changes legitimately:
- update the underlying source of truth,
- refresh this page,
- rerun
just check-verification-inventory.
The numeric rows in this section are source-derived and checked by
toolkit/xtask/src/checks/verification_inventory.rs.
| Metric | Value | Source |
|---|---|---|
| Lean core-library files | 767 | lean/CODE_MAP.md total row |
| Lean core-library lines | 152,326 | lean/CODE_MAP.md total row |
| Lean-backed search fairness inventory entries | 56 | lean/Runtime/Proofs/Search/Inventory.lean |
| Ownership contract gate commands | 7 | just check-ownership-contracts |
| Aura-derived boundary checks | 9 | just check-aura-borrowed-lints |
| Explicit failure/timeout observable event kinds | 5 | rust/machine/src/engine/protocol_machine_config.rs (ObsEvent) |
| Macro UI pass fixtures | 11 | rust/macros/tests/macro_ui.rs |
| Macro UI compile-fail fixtures | 13 | rust/macros/tests/macro_ui.rs |
| Property buckets with executable assurance suites | 22 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Property buckets currently lacking executable assurance suites | 0 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Authority and ownership semantic assurance suites | 2 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Lean-backed correspondence strict suites | 8 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Identity and replay semantic assurance suites | 5 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Commitment and progress semantic assurance suites | 4 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Cross-mode semantic parity suites | 4 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Fail-closed lowering and admission gate suites | 5 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Structural locality and reconfiguration executable assurance suites | 5 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Semantic lifecycle invariant suites | 1 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Deterministic adversarial lifecycle scenarios | 10 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| End-to-end DSL runtime semantic conformance suites | 1 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Simulator semantic contract categories enforced automatically | 6 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Theorem-pack and admission executable assurance suites | 4 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Distributed and topology semantic harness suites | 3 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Agreement and composition runtime semantic suites | 4 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Extension and middleware semantic hardening suites | 2 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Generated topology and transport public-path suites | 1 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Runtime substrate boundary assurance suites | 2 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Handler contract boundary assurance suites | 2 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Long-horizon recovery differential harness suites | 1 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Artifact and release assurance suites | 4 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Mutation fail-closed assurance suites | 2 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Concrete protocol-machine refinement suites | 3 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Compiler and serialization pipeline suites | 5 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Deadlock automation fragment assurance suites | 3 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Docs-as-contract assurance suites | 3 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Deterministic scale and budget assurance suites | 1 | Curated property-suite map in toolkit/xtask/src/checks/verification_inventory.rs |
| Explicit unsupported or fail-closed property notes | 0 | Curated unsupported-surface note list in toolkit/xtask/src/checks/verification_inventory.rs |
Current Formal Verification Claim
Telltale is formally verified for the declared shipped surface documented in this inventory, under the assumptions listed in the public TCB ledger below.
The exact public claim is:
Telltale is formally verified for its declared shipped surface: the Lean session-type, projection, protocol-machine, and conservation-property models and theorems, the theorem-defined Rust↔Lean protocol-machine runtime correspondence core for the audited supported corpus, the shipped first-party handler/transport boundary as documented by machine-checkable contract profiles, and the shipped first-party crate artifacts through the audited artifact-correspondence pipeline.
This does not mean every Rust API, compiler helper, macro path, plugin, or third-party integration is mechanically proved. Anything outside the declared shipped surface is listed explicitly below and is not part of the formal claim.
The runtime heap now has a focused Lean parity lane for canonical bytes, tagged preimages, ordering, proof-path structure, and deterministic replay. Its digest algorithm remains an operational conformance surface checked through published vectors and runtime tests rather than a first-class Lean hash model.
The simulator assurance story now includes explicit exact-backend parity, canonical replay reproduction, exact checkpoint resume, durable WAL fault injection and crash/recovery matrices, batch-order reproducibility, fail-closed theorem-classification matrices, approximation admissibility gates, nested distributed invariance, and helper-surface non-authoritativeness checks. These remain executable assurance lanes rather than part of the current mechanized formal claim.
The search crate now has a scoped Lean fairness lane as well. The current proved search fairness surface is:
- canonical serial search is exact one-step fair for the current legal min-key batch
- canonical serial search now has an executable reduced machine semantics plus an explicit reduced Rust-facing state/artifact projection boundary
- canonical serial search now also has an executable full-machine semantics, an exported full-state artifact projection, and premise-scoped full-step refinement contracts back into the reduced executable lane
- canonical serial search now also has a reduced machine-level invariant and projection layer, including executable machine-step invariant preservation, executable trace refinement into the packaged machine theorem surface, and a machine-trace restatement of current min-key eventual service
- canonical serial search also has a dynamic liveness theorem under an explicit stability premise: if an entry remains continuously eligible and no strictly better entry appears, it is eventually serviced
- canonical serial search has a fixed-phase termination theorem under an explicit finite reachable-node premise bundle
- canonical serial search also has a rebuild-aware ARA-style termination theorem under an explicit lexicographic phase/work measure premise bundle
- canonical serial search has both bounded strict-preemption and finite better-entry exhaustion theorems for entries that are not initially in the current min-key batch
- canonical serial search now also has a scheduler-facing non-min service theorem whose public statement no longer exposes bounded-better-arrival or finite-better-universe terminology
- canonical serial search has both frontier-level and machine-level witness-path goal-reachability theorems for the reduced model
- canonical serial search now also has a graph-reachability-driven completeness theorem under explicit reachable-path, finiteness, and heuristic premises
- canonical serial search now also has a raw-successor-semantics completeness theorem whose public statement quantifies over the domain successor contract instead of a user-supplied ready-path witness
- canonical serial search also has an explicit machine-level bridge from goal reachability to incumbent publication under a publication premise bundle
- canonical serial search now also has an eventual optimal-goal publication theorem under explicit admissibility and consistency premises
- threaded exact single-lane search has the same exact one-step fairness through reduced one-step, commit-trace, state-slice, and observation-slice refinement theorems to canonical serial search
- batched exact search has a certified-window fairness theorem with explicit premises plus a bounded dynamic starvation-freedom theorem under an explicit bounded-preemption premise, and its theorem-pack/export surface now also carries explicit support-class metadata alongside the certified multi-step window-trace validity theorem
- envelope-bounded batched search now also has a certified-window theorem surface, theorem-backed normalized-commit observables, and a runtime certificate/export path aligned with the theorem pack
These search fairness claims are exposed operationally through the
SearchFairnessArtifact and SearchFairnessCertificate runtime surfaces and
checked by the dedicated just check-search-fairness gate, the Rust↔Lean
search parity suite, and the verification-inventory gate. The current theorem
pack is a first-class Lean artifact under
Runtime.Proofs.Search.TheoremPack, with a mirrored Rust
SearchTheoremPackArtifact for release-facing inventory export, now including
per-theorem support classes that distinguish executable-semantics theorems,
refinement corollaries, and premise-scoped theorems. Exact runtime runs now
also export SearchStateArtifact plus per-round state/certificate traces.
They also export SearchRouteBoundArtifact, whose current discovery surface
is an observed run-scoped bound to first candidate publication, an observed
recovery bound after the latest epoch transition, and a theorem-backed
one-step goal-window service bound from the fairness certificate surface, now
packaged as a structured SearchRouteDiscoveryCertificate and attached to the
exact observed goal-window and publication steps for that run. The selected-route
summary also carries a stable generic metric list alongside its scalar
convenience fields. Its current quality surface remains profile-scoped rather
than a separate Lean end-to-end discovery theorem. The release/provenance lane records the generated
target/search-theorem-pack/search-theorem-pack.json artifact plus the
generated canonical vector artifact
target/search-artifacts/search-vectors-v1.json and the generated recovery
vector artifact target/search-artifacts/search-recovery-vectors-v1.json.
These claims are also backed by source-controlled search artifact vectors
checked by the search_vectors and search_recovery_vectors conformance
tests. They are still narrower than a blanket proof of fairness and
completeness for all future frontier growth, all rebuild schedules, and all
parallel modes without premises.
Claimed Surface
The current proved or proof-backed claimed surface is:
- the Lean
SessionTypes,SessionCoTypes,Choreography,Protocol, andRuntimemodels and theorem libraries - the strict Rust↔Lean correspondence corpora and comparison contracts tracked in the Lean correspondence rows below
- the first-class protocol-critical capability/finalization/transition model carried by the Lean capability-model surface and its strict Rust↔Lean bridge correspondence suite
- the packaged first-party crates and binaries only at the level of operationally checked artifact/release assurance, not full mechanized proof
Compiler and Macro Claim Boundary
The current public formal-verification claim does not include any Rust parser, lowering, projection, import/export, or macro-expansion entry path. More concretely: the current public formal-verification claim does not include any Rust parser, lowering, projection, import/export, or macro-expansion entry path.
For the current claim:
parse_choreography_str,parse_choreography_file,choreography_to_global,local_to_local_r, Rust-side projection/codegen/import-export helpers, andtell!macro expansion are outside the formal claim- because those entry paths are outside the current formal claim, no compiler-facing theorem-object JSON/import-export path remains on the current claim-critical path
- those Rust compiler and macro paths are still covered by strict operational gates, especially the compiler-pipeline, projection-equivalence, exact-shape JSON, Lean-validator, and macro-UI suites
- the exact DSL fragment that would need mechanized compiler proof before the
public claim can broaden is the theory-convertible subset already tracked in
the property table below: straight-line send/recv, communicated
choice,call, countedloop, and guarded recursion par,case/of,timeout, and any other fail-closed or runtime-only forms are outside both the current formal claim and that future proof-target subset
Artifact Correspondence Claim
The local release preflight checks its largest required disk reserve before expensive build lanes. An insufficient reserve fails the preflight without deleting build artifacts: Cargo targets may be shared with other workspaces or active builders. This resource failure is not a passing verification result. The preflight must be rerun after guarded cleanup by the artifact owner.
The manually dispatched Release Preflight workflow runs the same complete
just ci-dry-run command in the pinned Nix environment on an isolated runner.
Its evidence records the checked commit and full command log. A successful
focused machine suite or a failed resource check does not substitute for this
complete preflight. Publishing must use the exact checked source revision.
For the current public claim, the shipped first-party crate artifacts are covered only by operational artifact correspondence, not by mechanized proof.
More concretely, the current release/artifact claim is:
- every publishable first-party crate tarball for the released version is built from the checked workspace manifests for that version
- the packaged tarballs compiled by the artifact lane are the same tarballs
whose hashes, sizes, git revision, toolchain versions, and critical embedded
resource hashes are recorded in the provenance manifest under
target/package-artifact-tarballs/provenance.json - critical embedded resources on the shipped path, including packaged README files, the embedded runtime grammar, and release metadata like the WASM example lockfile, are part of the audited artifact pipeline rather than an unchecked side channel
This is still narrower than a fully reproduced, mechanically proved binary build pipeline. Cargo, crates.io, git hosting, and the local build toolchain remain explicit external assumptions.
Out of Scope / Assumption Boundaries
First-class protocol-critical capability semantics are in scope. General host application authorization is out of scope.
The current public claim does not include:
- Rust parser/lowering/projection/import-export and
tell!macro expansion - user-supplied handlers, transports, plugins, or deployment adapters
- third-party infrastructure or deployment environments
- Cargo, crates.io, git hosting, OS, compiler, and toolchain correctness beyond the documented operational checks
- cryptographic hardness assumptions beyond standard SHA-256-style assumptions
- a blanket claim that every shipped Rust code path is mechanically proved
- general host application authorization or arbitrary app-policy capability systems unrelated to protocol-critical semantics
Final Surface Audit
This table is the final public audit of which major surfaces are inside the formal claim and which are deliberately outside it.
| Surface | Claim status | Note |
|---|---|---|
Lean SessionTypes, SessionCoTypes, Choreography, Protocol, and Runtime semantics/theorems | inside | Core mechanized proof surface |
Theorem-defined protocol-machine runtime core (ConcreteScheduledStep, claimed state/transition/event slice) | inside | Exact Rust↔Lean refinement/correspondence surface |
First-party documented handlers (InMemoryHandler, TelltaleHandler, middleware profiles) | inside | Included through machine-checkable contract profiles |
First-party documented transports (InMemoryChannelTransport, runtime topology TCP helper, telltale-transport TCP transport) | inside | Included through machine-checkable contract profiles |
| First-party packaged crate tarballs and embedded resources | inside | Included through the operational artifact-correspondence and provenance pipeline |
| Rust parser, lowering, projection, import/export helpers | outside | Publicly supported but outside the formal claim and guarded operationally |
tell! and other proc-macro convenience surfaces | outside | Publicly supported but outside the formal claim and guarded operationally |
| User-supplied third-party handlers, transports, plugins, and deployment adapters | outside | Remain assumption-boundary integrations unless separately justified |
External build/delivery infrastructure (cargo, crates.io, git hosting, toolchains, OS) | assumption boundary | Explicit public TCB items rather than proved surfaces |
Public TCB Ledger
The current trusted computing base for the public claim is:
| Component | Why it remains trusted | Current enforcement |
|---|---|---|
| Lean kernel and imported proof libraries | theorem checker and proof environment | Lean build + code map + proof targets |
| Lean model definitions | theorems are only as correct as the modeled semantics | Lean proof suites and docs inventory |
| Classical real-analysis API | external analytic laws used by transported classical families | ClassicalAnalysisAPI.lean, ClassicalAnalysisInstance.lean, and scripts/lean/check-classical-proof-audit.sh |
| Rust/Lean bridge normalization and interchange | comparison/equality surface between Rust and Lean | just check-bridge-normalization, strict correspondence suites |
| Rust runtime implementation | shipped executable semantics are still comparison-checked, not fully proved | strict correspondence, semantic assurance, refinement slice |
| First-party handlers/transports | external impurity boundary for the runtime | handler-contract, transport-contract, and runtime-boundary suites |
| Release/package scripts and generated resources | artifact identity path from workspace to published crates | package-artifact, package-provenance, release-recovery, and docs-as-contract gates |
| Cargo / crates.io / git / toolchains | external delivery/build platform | operational checks only |
If that TCB shrinks or the claim broadens, this section must be updated before a broader public wording is used.
Final Public Claim Text
Use this exact wording in docs, release material, and summaries:
Telltale is formally verified for the declared shipped surface documented in the verification inventory, under the listed public assumptions and TCB.
Supporting scope statement:
The declared shipped surface includes the Lean semantics/theorems, the theorem-defined Rust↔Lean protocol-machine runtime correspondence core, the shipped first-party handler/transport contract boundary, and the shipped first-party crate artifacts through the audited artifact-correspondence pipeline. It does not include Rust compiler/macro entry paths or third-party integrations unless stated otherwise.
Supporting TCB statement:
The remaining public TCB consists of the Lean kernel and imported proof libraries, the minimized Rust/Lean bridge/interchange layer, the comparison- checked Rust runtime implementation for the claim-critical surface, the documented first-party handler/transport boundary, the audited release/package and generated-resource pipeline, and external build/delivery infrastructure such as Cargo, crates.io, git hosting, and the underlying toolchains.
Refinement Coverage Map
This map names the current protocol-machine state surfaces that matter to the claimed runtime story and shows whether they are already inside the exact Rust↔Lean refinement slice.
| Runtime state component | Rust surface | Lean surface | Current refinement status | Note |
|---|---|---|---|---|
| Coroutine identity, program counter, status, owned-endpoint count, progress-token count | rust/machine/src/refinement.rs (CoroutineRefinementSlice) | lean/Runtime/Proofs/ProtocolMachine/ConcreteRefinement.lean (ConcreteCoroutineSlice) | exact slice | Compared exactly across cooperative, Lean, and threaded executions today |
| Session id, role count, local-type entry count, buffer-edge count, buffered-message count, status tag, epoch | rust/machine/src/refinement.rs (SessionRefinementSlice) | lean/Runtime/Proofs/ProtocolMachine/ConcreteRefinement.lean (ConcreteSessionSlice) | exact slice | The theorem-side session slice now carries the same session-status and buffer-edge surface used by the Rust runtime slice |
| Scheduler ready queue, blocked-set tags, step count | rust/machine/src/refinement.rs (SchedulerRefinementSlice) | lean/Runtime/Proofs/ProtocolMachine/ConcreteRefinement.lean (ConcreteSchedulerSlice) | exact slice | The canonical scheduler slice is compared exactly today |
Per-step event stream, theorem-defined pre_state/post_state, selected coroutine/type state, session_type_counts, ready_queue, and blocked snapshots | rust/bridge/src/protocol_machine_runner.rs (ProtocolMachineStepState), rust/machine/src/refinement.rs (TransitionRefinementSummary, ClaimedRuntimeCoreBundle) | lean/Runtime/Proofs/ProtocolMachine/ConcreteRefinement.lean, lean/Runtime/Tests/ProtocolMachineRunner.lean step-state JSON export | theorem-defined claimed transition surface with exact strict correspondence to Rust | The claim-critical transition object is now defined on the Lean side as a scheduled-step projection carrying pre/post slices, transition summary, and step event projection. Rust is compared against that exact surface, while compiler-layout-specific program counters remain exported for audit/debugging rather than as a separate semantic claim |
| Effect exchanges and output-condition trace | rust/bridge/src/protocol_machine_runner.rs (effect_exchanges, output_condition_trace) | Lean runner JSON export and strict bridge suites | outside the current claim-critical refinement core | Compared in strict lanes, but the current mechanized runtime-refinement claim is intentionally scoped to the theorem-defined protocol-machine state/transition/event surface above |
| Semantic-object families (handoffs, progress contracts, publications, agreement state, transformation obligations) | rust/machine/src/engine/runtime_exec/introspection.rs, rust/machine/src/threaded/runtime_introspection.rs | lean/Runtime/Proofs/ProtocolMachine/SemanticObjects/* | theorem-backed separately, outside the current claim-critical refinement core | Conservation theorems and strict comparison exist, but they are tracked as separate theorem families rather than folded into the concrete protocol-machine refinement core |
Gate Ownership
The verification surface is organized around three canonical just-entry lanes.
just ci-dry-run, check.yml, and verify.yml should call these names rather
than duplicating their inner gate lists by hand.
| Gate | Canonical entry point | Primary owning files | Local run surface | GitHub run surface |
|---|---|---|---|---|
| Fast structural verification | just check-fast-structure | justfile, check-formal-claim-scope, check-ci-assurance-lanes, toolkit/xtask/src/checks/verification_inventory.rs, toolkit/xtask/src/checks/bridge_normalization_ledger.rs, toolkit/xtask/src/checks/fail_closed_mutations.rs, check-source-doc-snippets, toolkit/xtask/src/checks/tooling_convergence.rs, Lean bootstrap scripts | just check-pr-critical, just ci-dry-run, direct local recipe use | check.yml, verify.yml |
| Focused assurance | just check-focused-assurance | justfile, strict Lean bridge suites, compiler pipeline suites, metatheory/refinement/runtime boundary suites | just check-pr-critical, just ci-dry-run, direct local recipe use | check.yml, verify.yml |
| Packaged artifact assurance | just check-package-artifacts | justfile, toolkit/xtask/src/checks/package_artifacts.rs, toolkit/xtask/src/checks/package_provenance.rs, toolkit/xtask/src/checks/durable_boundaries (via _toolkit-check), toolkit/xtask/src/checks/release_recovery.rs | just check-pr-critical, just ci-dry-run, direct local recipe use | check.yml, verify.yml |
| PR-critical assurance | just check-pr-critical | justfile, .github/workflows/check.yml, .github/workflows/verify.yml | just ci-dry-run, direct local recipe use | check.yml, verify.yml |
| Scheduled deep assurance | just check-deep-assurance | justfile, .github/workflows/verify.yml, scale-budget and larger-corpus verification lanes | just ci-dry-run full, direct local recipe use | verify.yml |
For staged diffs restricted to the simulator subsystem, the narrower local gate is now just check-simulator-subsystem-staged.
That path is intentionally local-only and does not replace the canonical repo-wide lanes above.
It exists so simulator-only staged work still gets formatting, compile, test, and simulator-doc link enforcement without being blocked by unrelated pre-existing breakage elsewhere in the workspace.
The dedicated local search-fairness gate is just check-search-fairness.
That gate is intentionally narrower than the canonical repo-wide lanes above
and exists to keep the Lean theorem-pack, parity fixture, and inventory rows
aligned while working inside telltale-search.
Property Coverage Baseline
This baseline records whether each conserved-property bucket has at least one high-signal executable assurance suite today. It is intentionally curated. The aim is to make gaps explicit rather than to produce vanity totals.
| Property bucket | Executable status | High-signal suites | Current note |
|---|---|---|---|
| Evidence | Spot checks | rust/machine/tests/ownership_contracts.rs, rust/machine/tests/conformance_lean.rs | Evidence-bearing paths are exercised directly in runtime and Lean-backed spot checks. The current theorem-focused authority slice starts from authoritative reads plus canonical publication/materialization and the explicit finalization/canonical-handle subsystem rather than the whole authority lifecycle family |
| Authority | Cross-path checks | rust/machine/tests/ownership_contracts.rs, rust/simulator/tests/ownership_faults.rs | The supported authority theorem slice is now explicit: evidence-bearing reads, transfer receipts, semantic handoff, and canonical publication/materialization are justified at the semantic-object and lifecycle layer, while broader host-policy lifecycle surfaces still rely on the wider protocol-machine conservation theorems |
| Lean correspondence | Strict executable validation checks | rust/bridge/tests/lean_trace_validation.rs, rust/bridge/tests/property_tests.rs, rust/bridge/tests/protocol_bundle_admission_contracts.rs, rust/bridge/tests/protocol_machine_correspondence_tests.rs, rust/bridge/tests/protocol_machine_differential_steps.rs, rust/bridge/tests/capability_model_correspondence.rs, rust/bridge/tests/heap_lean_parity.rs, rust/simulator/tests/lean_reference_parity.rs | validateTrace, validateSimulationTrace, runSimulation, verifyProtocolBundle, deterministic reconfiguration-transition validation, epoch-aware multi-step reconfiguration phase validation, explicit capability/finalization/transition-model correspondence, exact normalized simulator-trace parity, heap canonical-byte and replay parity, full protocol-machine event-stream parity, session-status parity, and step-indexed scheduler-state parity now execute in strict deterministic lanes for the supported corpus |
| Identity | Cross-path checks | rust/machine/tests/serialization_replay.rs, rust/machine/tests/replay_persistence_identity.rs, rust/bridge/tests/semantic_object_roundtrip.rs, rust/bridge/tests/protocol_machine_cross_target_tests.rs, rust/bridge/tests/reconfiguration_recovery_harness.rs | Replay, snapshot/restore, canonical fragment roundtrip, semantic-object identity, ownership-transfer replay artifacts, and bridge-exported reconfiguration snapshot identity are now checked as one differential family across multiple surfaces |
| Commitment | Spot + path checks | rust/machine/tests/unit/protocol_machine/tests_semantic_state.rs, rust/machine/tests/conformance_lean.rs, rust/machine/tests/threaded_equivalence.rs, rust/machine/src/runtime_contracts.rs | Progress and terminalization are covered across runtime contracts, lifecycle harnesses, and cross-driver parity |
| Commitment | Deterministic lifecycle harness | rust/machine/src/engine/runtime_exec/semantic_state.rs | Seeded lifecycle and adversarial corpus now exercise terminalization, invalidation, proof-gaps, and progress escalation as one semantic state machine |
| Structure | Deterministic runtime structure suite | rust/machine/src/engine/runtime_exec/semantic_state.rs, rust/machine/tests/ownership_contracts.rs, rust/machine/src/composition.rs, rust/bridge/tests/protocol_bundle_admission_contracts.rs, rust/runtime/tests/generated_topology_public_path.rs | Structural handoff locality, transformation obligations, pre-transfer witness invalidation, generated topology validation, executable region inheritance/conflict checks, bridge-to-runtime reconfiguration admission, atomic multi-step plan execution, canonical placement/transport-boundary phase artifacts, deterministic reconfiguration history, snapshot/restore state, and Lean-validated transition artifacts are now exercised on executable runtime paths |
| Premise | Fail-closed + admission checks | rust/machine/src/runtime_contracts.rs, rust/machine/src/composition.rs, rust/language/src/compiler/parser/mod.rs, rust/runtime/tests/authority_compile_fail.rs, rust/runtime/tests/authority_control_flow_corpus.rs | Assumption-heavy paths are rejected or gated, and the authority/control-flow boundary is now exercised with deterministic accepted/rejected .tell and tell! fixtures |
| Premise | End-to-end supported/fail-closed lowering | rust/tests/dsl_runtime_semantics_tests.rs | The theory-backed supported subset is explicit and executable: choice, call, counted loop, and recursion remain parser -> projection -> theory-conversion -> protocol-machine clean. par, case/of, and timeout stay outside that theory-convertible subset and are covered through the executable/runtime and boundary suites above |
| Admission | Theorem-pack and bundle assurance | rust/bridge/tests/protocol_bundle_admission_contracts.rs, rust/bridge/tests/invariant_verification.rs, rust/machine/src/runtime_contracts.rs, rust/tests/dsl_runtime_semantics_tests.rs | Proof-bundle declarations, capability drops, admission-gated runtime requests, and the explicit transported-theorem boundary ledger are now exercised end to end with stable diagnostics. Runtime-critical instantiated premises are separated from Lean-only assumption boundaries and black-box/background theorem inventory |
| Distributed topology | Deterministic harness | rust/simulator/tests/distributed.rs, rust/machine/tests/topology_effect_ingress.rs, rust/runtime/tests/generated_topology_public_path.rs | Distributed replay, ordered topology ingress, generated helpers, and invalid placement rejection now run through executable runtime paths without ambient network dependency |
| Simulator replay and classification assurance | Deterministic executable regression net | rust/simulator/tests/correctness_regression.rs, rust/simulator/tests/threaded_backend.rs, rust/simulator/tests/classification_assurance.rs, rust/simulator/tests/environment_models.rs, rust/simulator/tests/distributed.rs, rust/simulator/tests/durable_assurance.rs | Canonical exact, threaded exact, canonical replay, exact checkpoint resume, durable WAL fault injection, crash/recovery coverage matrices, write-ahead and evidence-consistency monitors, fail-closed theorem eligibility, approximation admissibility, normalized observability boundaries, observed-only distributed manifests, and non-authoritative helper surfaces are now checked together as one simulator-facing assurance family |
| Agreement | Runtime commitment semantics | rust/machine/src/effect/core_types.rs, rust/machine/src/semantic_objects.rs, rust/machine/tests/threaded_equivalence.rs, rust/tests/dsl_runtime_semantics_tests.rs | Agreement profiles and child-effect rollups are checked as runtime semantics across scenario tables, lowering, and cross-driver parity |
| Extension boundary | Deterministic parse-to-runtime dispatch + middleware stacks | rust/runtime/tests/extension_integration.rs, rust/runtime/tests/middleware_semantic_hardening.rs | Registered statement rules now parse into Protocol::Extension, lower into runtime extension effects, execute through deterministic extensible handlers, fail with stable diagnostics when handlers are missing or payloads are malformed, and remain middleware-safe under retry, metrics, trace, and seeded fault injection |
| Generated deployment path | Public helper end-to-end execution | rust/runtime/tests/generated_topology_public_path.rs | handler(...), with_topology(...), and named inline topology helpers are exercised as public surfaces with real loopback remote transport, negative validation coverage, executable region semantics, preservation of inline role-family constraints, and a transport-agnostic runtime topology API |
| Runtime substrate | Target-aware wrapper contracts | rust/runtime/tests/runtime_substrate_contracts.rs, rust/runtime/tests/wasm_compat.rs | Native and WASM wrapper seams now have direct regression coverage for spawn, spawn_local, and deterministic clock/RNG discipline, and deterministic assurance suites are guarded against accidental SystemClock / SystemRng drift |
| Handler contract boundary | Machine-checkable contract profiles for first-party handlers and transports, plus fail-closed extension dispatch | rust/runtime/tests/handler_contracts.rs, rust/runtime/tests/transport_contracts.rs | ChoreoHandler and the first-party transport families now have explicit machine-checkable contract ledgers that separate protocol-semantic obligations from policy choices, validate shipped production and harness profiles mechanically, and prove deterministic registered-only extension dispatch plus fail-closed unregistered behavior through runtime tests. User-supplied third-party handlers/transports remain outside the formal claim unless they separately satisfy the same contract |
| Recovery | Long-horizon differential harness | rust/bridge/tests/reconfiguration_recovery_harness.rs | Ownership-transfer replay artifacts, bridge export/import, topology-derived placement artifacts, atomic multi-step reconfiguration plans, snapshot/restore recovery, and deterministic suffix replay now execute as one end-to-end recovery family with explicit divergence detection |
| Artifact / release | Packaged-crate, provenance, and resume verification | toolkit/xtask/src/checks/package_artifacts.rs, toolkit/xtask/src/checks/package_provenance.rs, toolkit/xtask/src/checks/durable_boundaries (via _toolkit-check), toolkit/xtask/src/checks/release_recovery.rs | Every publishable crate now goes through the cargo publish --dry-run --locked --no-verify packaging path, package-manifest resource paths are checked before packaging, the full packaged crate set is compiled from extracted tarballs, critical embedded resources are compared byte-for-byte against source, external consumer canaries for telltale, telltale-runtime, and telltale-bridge run outside the workspace layout with exact last-line stdout assertions, a provenance manifest records tarball hashes plus source/resource/toolchain metadata for the packaged set, package-root resource escapes are fail-closed, the packaged WASM and embedded-grammar surfaces are verified explicitly, and release resume behavior is exercised under a deterministic fake cargo/git harness |
| Mutation pressure | Direct fail-closed perturbation suites | rust/machine/src/runtime_contracts.rs, toolkit/xtask/src/checks/fail_closed_mutations.rs | Representative bridge payload, theorem-boundary, source-derived docs-row, package-registry, package-manifest, package-resource, and inventory mutations are injected directly against the narrow owning gates so drift is rejected before broader integration lanes run |
| Concrete refinement | Exact cooperative/Lean/threaded state-slice parity plus Lean proof-connected slice | rust/bridge/tests/protocol_machine_differential_steps.rs, rust/machine/tests/lean_protocol_machine_equivalence.rs, rust/machine/tests/threaded_equivalence.rs | The first concrete protocol-machine refinement slice now compares coroutine/session/scheduler state exactly across Rust, Lean, and canonical threaded execution, exports bounded u64 bridge fields fail-closed, and is connected to dedicated Lean refinement theorems over the same slice |
| Compiler / serialization pipeline | Strict DSL-to-theory lowering, exact-shape JSON bridge, and Lean-backed projection acceptance | rust/bridge/tests/compiler_pipeline_conformance.rs, rust/bridge/tests/projection_equivalence.rs, rust/bridge/tests/proptest_json_roundtrip.rs, rust/bridge/tests/lean_integration_tests.rs, rust/bridge/tests/merge_semantics_tests.rs | This pipeline is operationally checked, not part of the current formal claim: the supported DSL subset runs through parser -> protocol_to_global() / local_to_local_r() -> exact-shape import/export -> Lean projection export and validator acceptance in deterministic strict lanes, and bridge import rejects unknown fields fail-closed so schema drift cannot hide behind permissive parsing |
| Deadlock automation | Lean-sound regular-fragment checker mirrored into Rust diagnostics | rust/types/src/local.rs, rust/bridge/tests/regular_practical_fragment_checks.rs, rust/tests/dsl_runtime_semantics_tests.rs | The automatic deadlock-discharge fragment is now mechanically characterized as closed + contractive projected locals whose full unfold exposes send/recv, checked first in Lean on SessionTypes.LocalTypeR, mirrored in Rust only for macro/proof-status diagnostics, and exercised end to end through bridge parity and generated proof_status surfaces |
| Public docs as contract | Source-derived capability/admission, authority-support, and trust-surface tables | rust/bridge/tests/docs_contract_tests.rs, toolkit/xtask/src/checks/verification_inventory.rs, toolkit/xtask/src/checks/bridge_normalization_ledger.rs | The highest-value public verification/capability docs now separate source-derived tables from explanatory prose, and those rows are checked mechanically against runtime-contract, DSL-tier, and bridge-ledger facts |
| Deterministic scale budgets | Larger supported corpora with structural size envelopes | rust/bridge/tests/scale_budget_contracts.rs | Larger lowering/projection corpora, longer record/replay histories, larger reconfiguration snapshots, and larger Lean bridge payloads now run as deterministic structural-budget gates with exact replay/snapshot equality and explicit serialized-size envelopes rather than ambient wall-clock benchmarks |
Bridge Normalization Trust Surface
The Lean bridge still contains a small amount of trusted normalization logic.
That logic is intentionally narrow and is audited explicitly in CI by
just check-bridge-normalization.
The rows in this table are source-derived and checked by
telltale_bridge::bridge_normalization_ledger() via
toolkit/xtask/src/checks/bridge_normalization_ledger.rs.
| Surface | Normalization rule | Classification | Why permitted |
|---|---|---|---|
| semantic-audit tick normalization | Normalize only tick, and only per extracted session id | irreducible trusted comparison logic | Absolute cross-session scheduling order is not semantic protocol truth. Per-session observable order is. |
| single nominal occurrence comparison | Relate only the validated original nominal occurrence ID and preserve exact original-or-plus-one reply relation/refusal; never normalize ticks, typed application results, lifecycle or continuation | irreducible trusted comparison logic | Rust also issues non-nominal observational effects, so its absolute counter namespace differs from the selected executable model. Each side must retain exactly one actual nominal occurrence with consistent operation/request references before the comparison relates that ID. Raw outputs are unchanged. |
Enforcing artifacts:
rust/bridge/src/protocol_machine_trace.rsrust/bridge/tests/protocol_machine_correspondence_tests.rsrust/bridge/tests/protocol_machine_differential_steps.rsSession-status ordering is no longer part of the trusted comparison surface. Both the Lean runner and Rust-side correspondence harness now emit session rows in canonicalsidorder, so comparison is exact rather than normalized.
The older schema_version backfill helper remains only as a test-only
compatibility shim for legacy fixture coverage. It is outside the claim-critical
bridge surface and must not synthesize semantic content.
Explicit Unsupported / Fail-Closed Notes
No explicit unsupported or fail-closed implementation-gap notes remain in the tracked inventory. New notes should be added here only for intentional, documented non-goals or for temporary regressions that are not yet executable.
The inventory deliberately does not track raw unit-test totals, assertion counts, or line counts for tests.
Required Targeted Disposal Evidence
just check-session-disposal validates ten named native lifecycle regressions
through Rust AST inventory, actual Cargo harness discovery with multi-thread,
and successful execution of every required name. Missing, ignored, nested
conditional ignore, wrong-module, and zero-test outcomes fail the gate.
just check-ownership-contracts includes this gate in the PR-critical lane.
Prepared readiness lowering inventory
The authoritative nominal readiness child declares three portable machine codec/shape
fixtures and six actual source/runtime fixture families for each backend in
rust/tests/nominal_command_runtime.rs. Cooperative fixtures are portable, while
threaded fixtures require multi-thread and non-WASM. They cover original registry single-use after
actual rollback, real Blocked→between-round registry issuance→same-original retry, malformed/Boolean/predicate/reused evidence, genuine foreign
session and committed transfer refusal, and actual callback-before-output-gate
refusal with the witness still unconsumed. Same original native worker shutdown
is explicit in the threaded fixtures. Discovery and execution remain required.
Source declarations are not passing evidence.
NominalCommandExecution.lean adds eight named readiness examples over the
actual installed nominal opcode/canonical commit or direct canonical model
owner/issuer/consumer, including stale installed original owner before issuance and changed generation before retained-result
reuse. They remain unelaborated source. The general source-to-model bridge must
adapt explicitly to the typed result before claiming readiness correspondence.
The transport model deliberately refuses readiness decoding. Witness transport
is outside this terminal-unit continuation slice. Neither model state nor
serialized witness data reconstructs Rust host authority.
Prepared original-operation checkpoint inventory
The existing dsl_runtime_semantics_tests target includes operation lifecycle,
joint readiness, compiler-obligation refusal and central fault/Halt modules.
Native threaded declarations require the root multi-thread feature. Same
original worker disposal precedes final assertions. Existing bridge
protocol_machine_differential_steps additionally declares actual source-parent
Bool/result/Blocked/failure correspondence, parent/readiness output-gate
correspondence and parent/child/window omission mutations. These use the existing
required Lean runner and refuse its absence.
The targeted model prerequisite explicitly builds OperationLifecycleExecution,
OperationReadinessExecution and OperationFailureExecution alongside the
existing nominal modules. Examples execute installed typed opcodes and canonical
owner/issuer/commit producers. Declarations remain unelaborated and Rust
fixtures undiscovered/unexecuted. Genuine dependent-work AwaitPending, handoff,
timeout/cancellation escalation and complete authority-family proofs remain
open. No source-only count is acceptance evidence.
Glossary and Notation Index
This page defines shared terms and symbols used across the documentation and paper-aligned pages. It serves as the stable lookup for terminology and notation.
Core Terms
| Term | Meaning | Primary Docs |
|---|---|---|
| coherence | Session-wide compatibility invariant between local type state, buffers, and global structure. | Theory, Theorem Program |
| harmony | Projection and protocol evolution commute under declared premises. | Theory, Theorem Program |
| projection | Mapping from global choreography to per-role local session types. | Choreographic Projection Patterns |
| local type | Per-role protocol view used for runtime typing and progression. | Theory, Session Lifecycle |
| effect handler | Runtime boundary that interprets protocol-machine or choreography actions. | Choreography Effect Handlers, Effect Handlers and Session Types |
| theorem-pack | Lean-exported capability inventory used by runtime admission gates. | Lean Verification, Capability Admission |
| capability taxonomy | The four first-class protocol-critical capability classes: admission, ownership, evidence, transition. | Capability Model, Authority Language Surface |
| admission | Runtime gate process that checks contracts and capability evidence. | Capability Admission |
| ownership capability | Runtime host authority token carrying owner label, generation, and scope. | Effect Handlers and Session Types, Session Lifecycle |
| evidence | Typed proof object or witness consumed by protocol-critical authority flow. | Authority Language Surface |
| receipt | Single-use transfer or handoff proof emitted by an explicit ownership/delegation path. | Session Lifecycle, Authority Language Surface |
| finalization subsystem | The explicit runtime/Lean model that classifies observed, authoritative, materialized, canonical, invalidated, and rejected semantic paths. | Protocol Machine Architecture, Capability Model |
| canonical handle | Non-forgeable runtime reference proving a materialized/publication path is consumable as canonical truth. | Authority Language Surface, Capability Model |
| transition artifact | Receipt, handoff, or reconfiguration/runtime-upgrade object that carries explicit transfer or cutover authority. | Capability Model, Authority Language Surface |
| linear binding | Binding that the compiler requires to be consumed exactly once. | Choreographic DSL, Authority Language Surface |
Result | Built-in success/failure sum form with Ok and Err. | Choreographic DSL |
Maybe | Built-in optional-value sum form with Just and Nothing. | Choreographic DSL |
| effect declaration | Nominal choreography declaration for one typed external host interface. | Authority Language Surface |
uses clause | Protocol-level declaration of which named effect interfaces may be invoked. | Authority Language Surface |
| timeout branch | Protocol-visible timeout/cancellation construct with explicit alternate branches. | Choreographic DSL, Authority Language Surface |
| ownership epoch | Generation used to invalidate stale owner handles after transfer or scope attenuation. | Session Lifecycle |
| canonical ingress | Sanctioned host event entry path such as topology_events, send_decision, handle_recv, or step. | Effect Handlers and Session Types |
| stale-owner rejection | Fail-closed behavior when a prior ownership capability is reused after transfer or attenuation. | Effect Handlers and Session Types, Session Lifecycle |
| envelope | Declared refinement boundary for higher-concurrency and profile-scoped behavior. | Protocol Machine Architecture, Rust-Lean Bridge and Parity |
| determinism profile | Runtime trace-equivalence contract mode such as Full or Replay. | Protocol Machine Architecture, Rust-Lean Bridge and Parity |
| communication replay mode | Transport replay-consumption policy: off, sequence, or nullifier. | Protocol Machine Architecture, Session Lifecycle |
| communication nullifier | Domain-separated digest of canonical communication identity used for one-time receive consumption checks. | Protocol Machine Architecture, Session Lifecycle |
| consumption root | Deterministic accumulator root over communication replay-consumption state. | Protocol Machine Architecture, Rust-Lean Bridge and Parity |
| protocol machine | Single-thread execution engine (ProtocolMachine) that runs projected local types with session type monitoring. | Protocol Machine Architecture |
| guest runtime | Telltale-owned driver (GuestRuntime) instantiated around the protocol machine for simulation and runtime integration. | Protocol Machine Architecture, API Reference |
| effect handler | Host-runtime boundary trait (EffectHandler) implemented by embedders and simulators. | Effect Handlers and Session Types, API Reference |
| semantic object | Typed introspection record (such as OperationInstance, OutstandingEffect, CanonicalHandle) maintained by the protocol machine for audit and replay. | Protocol Machine Architecture, API Reference |
| typed outcome | Structured success/failure result at the effect boundary using EffectResult and EffectFailure rather than raw strings. | Capability Model, Authority Language Surface |
| content addressing | Cryptographic identity scheme (ContentId) for protocol artifacts enabling deduplication and integrity checks. | API Reference |
| nominal effect interface | Named effect declaration (effect Name) that makes host dependencies explicit and typed at the language level. | Authority Language Surface, Capability Model |
| conservation framework | The organizing design principle: all semantically meaningful behavior is expressed in terms of six conserved quantities. | Conservation Framework |
| evidence (conserved property) | The integrity of what has been established. Witnesses, proofs, and attestations are its concrete forms. | Conservation Framework |
| authority (conserved property) | The exclusivity of who may act. Ownership is its concrete form. | Conservation Framework |
| identity (conserved property) | The definiteness of object references across retries, effects, and handoffs. | Conservation Framework |
| commitment (conserved property) | The account of outstanding obligations. Outstanding effects are its concrete form. | Conservation Framework |
| structure (conserved property) | The essential shape of the protocol. Multiparty session types are its concrete form. | Conservation Framework, Theory |
| premise (conserved property) | The explicitness of environmental assumptions. Failure models and fairness assumptions are its concrete forms. | Conservation Framework |
| erasure principle | Behavior not part of the conserved quantities is not part of the programming model. | Conservation Framework |
| reduction principle | All runtime behavior must reduce to the conservation framework. | Conservation Framework |
| region | Locality and framing domain for structured coordination with lifecycle, authority, and observation boundaries. | Conservation Framework |
| semantic core objects | The closed set of protocol-visible objects: Region, OperationInstance, OutstandingEffect, SemanticHandoff, AuthoritativeRead, ObservedRead, MaterializationProof, CanonicalHandle, ProgressContract. | Conservation Framework, Protocol Machine Architecture |
Symbol and Notation Index
| Symbol or Form | Meaning | Primary Docs |
|---|---|---|
G | Global protocol type. | Theory |
L or LocalTypeR | Local role protocol type. | Theory, Protocol-Machine Bytecode Instructions |
project(G, R) | Projection of global type G for role R. | Theory, Choreographic Projection Patterns |
μX. ... X | Recursive protocol form with bound variable X. | Theory |
⊕{...} | Internal choice at the selecting endpoint. | Theory |
&{...} | External choice at the receiving endpoint. | Theory |
!T.S | Send T, then continue as S. | Theory |
?T.S | Receive T, then continue as S. | Theory |
end | Session termination state. | Theory |
Consume | Recursive receiver-side trace alignment kernel used in coherence proofs. | Theory, Theorem Program |
n = 1 | Canonical single-step concurrency regime for exact parity. | Protocol Machine Architecture, Rust-Lean Bridge and Parity |
n > 1 | Higher-concurrency regime admitted under envelope and premise-scoped constraints. | Protocol Machine Architecture, Rust-Lean Bridge and Parity |
Full, ModuloEffects, ModuloCommutativity, Replay | Runtime determinism profiles. | Protocol Machine Architecture, Rust-Lean Bridge and Parity |
off, sequence, nullifier | Communication replay-consumption modes. | Protocol Machine Architecture, Session Lifecycle |
telltale.comm.identity.v1 | Domain-separation tag for canonical communication identity schema. | Protocol Machine Architecture |
case ... of | Exhaustive sum-pattern branching over forms such as Result and Maybe. | Choreographic DSL, Authority Language Surface |
let x = check Effect.op(args) | Typed external query binding that later lowers to the protocol-machine effect boundary. | Choreographic DSL, Authority Language Surface |
effect Name | Nominal effect-interface declaration. | Authority Language Surface |
protocol P uses Runtime, Audit | Protocol declaration naming its allowed effect interfaces. | Authority Language Surface |
timeout 5s R at ... on timeout => ... on cancel => ... | Protocol-visible timeout and cancellation branch form. | Choreographic DSL, Authority Language Surface |
Notation Consistency Rules
Use one symbol for one concept in a local section. Reintroduce symbols when crossing between theory and runtime notation. Prefer existing symbols from this index unless precision requires a different one.