Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

### Added

- `dispatch_optic_intent(...)` and the default `KernelPort` optic dispatch path
now reject EINT payloads that use Echo's reserved scheduler/control op id,
closing the remaining application-facing control-intent ingress path.
- `warp-core` optic invocation admission now has a narrow ApertureResolution v0
boundary. Identity-covered invocations with exact fixture basis bytes
`basis-request:resolved-fixture:v0` and exact fixture aperture bytes
Expand Down
201 changes: 148 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,115 @@

# Echo

Echo is the hot runtime optic in the WARP stack.
Echo is a real-time, deterministic
[WARP optic](https://github.com/flyingrobots/aion) that participates in the
[Continuum protocol](https://github.com/flyingrobots/continuum): a protocol for
exchanging shared, witnessed causal history between distributed peers.

Unlike traditional runtimes that model state as a mutable object graph, Echo
treats witnessed causal history as the immutable source of truth. State is not
the substrate; it is materialized on demand as a lawful, witnessed, replayable
reading over that history. Graph-shaped state is a reading. Files are readings.
Build outputs are readings. Debugger views are readings. Echo exists to govern
the integrity of those views without making any one view the territory.

Echo does this through WARP optics: structured, law-named processes for
observing, transforming, importing, and retaining causal history.

- **Ticking** is an optic that advances a worldline by applying a deterministic
batch of canonical intents.
- **Braiding** is an optic that merges two or more concurrent strands of
history under explicit settlement law.
- **Querying** is an optic that materializes a bounded reading for an observer.
- **Admission** is an optic that lawfully incorporates transported history from
a remote peer.

Because transformations go through lawful optics, Echo can emit computational
holograms: compact, evidence-bearing boundary artifacts that name the causal
basis, law, aperture, identity, posture, witnesses, and retained support needed
to replay or audit a computation. For ticked execution, that support includes
the initial basis and ordered tick receipts or provenance payloads, so Echo does
not need to retain every intermediate materialized state as authoritative truth.

Echo operates in a deterministic cycle: it admits canonical intents, schedules
work, settles speculative paths, emits evidence-bearing receipts for every
transition, serves bounded observations to clients, and retains the minimal
artifacts needed for replay or verification.

You can use Echo to write deterministic real-time simulations, verifiable build
systems, and other applications that demand strong auditability. To get
started, read [Writing An Echo Application](#writing-an-echo-application) and
[Application Contract Hosting](docs/architecture/application-contract-hosting.md).

## Developer Experience

Echo is a participant in a larger architecture for provable determinism. Most
applications do not call Echo with application objects directly. They:

```mermaid
flowchart LR
contract["Author GraphQL contract"]
wesley["Compile with Wesley"]
helpers["Use generated helpers"]
intents["Submit canonical EINT intents"]
runtime["Echo-owned tick, scheduling, and settlement"]
readings["Observe ReadingEnvelope-backed results"]

contract --> wesley --> helpers --> intents
intents --> runtime --> readings
```

It does not treat a graph, database, file tree, editor buffer, or in-memory
object heap as the ultimate truth. Echo's substrate is witnessed causal history:
admitted transitions, frontiers, receipts, witnesses, patches, checkpoints,
retained readings, and boundary artifacts.
Echo handles causal admission, scheduling, receipts, witnesses, retention,
replay, and bounded observations. The application owns domain semantics.
Wesley bridges the two by turning authored contracts into generated Echo-facing
surfaces.

The hard doctrine is:
### 1. Declare A Contract

```text
There is no privileged graph.
There are causal histories and lawful readings of those histories.
```
The developer journey starts with GraphQL SDL that names the application's
data, operations, readings, and law metadata. GraphQL is the authoring surface,
not Echo's runtime language. Data types define the nouns; operation metadata,
footprints, policies, capabilities, and constraints define the verbs and laws
that govern those nouns.

Echo turns that doctrine into runtime machinery.
### 2. Compile With Wesley

It admits canonical intents, schedules deterministic work, settles speculative
paths, emits evidence-bearing receipts, serves bounded observations, and retains
the artifacts needed to replay or verify what happened. Graph-shaped state is a
reading. Files are readings. Build outputs are readings. Debugger views are
readings. Echo exists to make those readings lawful, witnessed, and
replayable.
The [Wesley](https://github.com/flyingrobots/wesley) compiler lowers the
authored contract into generated artifacts that Echo can verify and host:

## Thirty Second Version
- type-safe codecs and operation helpers;
- registry metadata, operation ids, and artifact identity;
- footprint certificates and runtime-facing contract metadata.

Echo is a deterministic runtime for admitting canonical intents and producing
witnessed readings.
The checked-in Echo path is Rust-first through
[`echo-wesley-gen`](crates/echo-wesley-gen/README.md). TypeScript and browser
generation should follow the same contract identity, registry,
artifact-verification, and footprint-honesty rules instead of inventing a
parallel Echo API.

Most applications do not call Echo with application objects directly. They:
### 3. Submit Intents

```text
author GraphQL contract
-> compile with Wesley
-> use generated helpers
-> dispatch canonical EINT intents
-> observe ReadingEnvelope-backed results
```
Applications do not mutate Echo state directly, and they do not decide when
Echo ticks. They submit canonical EINT bytes through `dispatch_intent(...)`.
That call is ingress: it gives Echo causal input and returns dispatch evidence.
It is not an application-owned tick command or a domain mutation RPC.

Echo owns the tick boundary, pending-set order, scheduler, and settlement law.
When Echo reaches a runtime-owned tick boundary, it evaluates pending intents
against installed contract law, footprints, and scheduler constraints, then
emits receipts for what was admitted, staged, pluralized, conflicted, or
obstructed.

### 4. Observe Readings

Clients do not ask Echo for "the state." They ask for a bounded reading from an
explicit basis under an observer plan.

Echo handles causal admission, receipts, witnesses, retention, replay, and
bounded observations. The application owns domain semantics. Wesley bridges the
two by turning authored contracts into typed generated surfaces.
Generated query helpers build `ObservationRequest` values. Echo returns an
`ObservationArtifact` containing payload bytes and a `ReadingEnvelope` naming
the basis, observer, witness references, budget posture, rights posture, and
whether the reading is complete, residual, plural, or obstructed. Application
code decodes the payload only after inspecting that evidence.

## Reader Paths

Expand Down Expand Up @@ -215,18 +283,23 @@ Echo receives canonical intents and returns witnessed readings.
The current shape is:

```text
Application UI / adapter
-> Wesley-generated contract client
Application UI
-> application adapter / Wesley-generated contract client
-> canonical operation variables
-> EINT intent bytes
-> Echo dispatch_intent(...)
-> Echo causal admission and receipts
-> host-owned Echo ingress: dispatch_intent(...)
-> Echo-owned scheduler tick / settlement
-> Echo receipts and witness refs
-> Echo observe(...)
-> ReadingEnvelope + payload bytes
-> generated/application decoding
-> UI
```

The application layer submits canonical bytes and reads observations. It does
not own the scheduler lifecycle or decide when Echo ticks; host/runtime policy
owns that authority.

This is why a serious text editor such as `jedit` can own its rope model,
buffer law, edit-group law, checkpoint policy, and UI behavior while Echo stays
generic. Echo hosts the generated contract, verifies artifact metadata, admits
Expand All @@ -244,24 +317,44 @@ The normal authoring loop is contract-first:
operation mutates or observes application state.
4. Run `echo-wesley-gen` to generate Rust contract helpers.
5. Have the host verify the generated registry/artifact metadata.
6. Use generated helpers to pack EINT intent bytes and build observation
requests.
7. Let Echo admit the intent, emit receipts, retain witnesses, and return a
6. Use generated helpers to pack EINT intent bytes and submit them to Echo
ingress.
7. Let Echo's runtime-owned tick cycle admit work, emit receipts, retain
witnesses, and make observations available.
8. Use generated query helpers to build observation requests, then decode and
present the returned reading in the application after inspecting its
`ReadingEnvelope`.
8. Decode and present the result in the application.

The end-to-end shape is:

```text
counter.graphql
-> echo-wesley-gen
-> generated.rs
-> verify_contract_artifact(...)
-> pack_increment_intent(...)
-> dispatch_intent(...)
-> counter_value_observation_request(...)
-> observe(...)
-> inspect ReadingEnvelope
```mermaid
flowchart TD
contract["counter.graphql"]
gen["echo-wesley-gen"]
generated["generated.rs"]

verify["verify_contract_artifact(...)"]
pack["pack_increment_intent(...)"]
dispatch["dispatch_intent(...)"]
tick["Echo-owned tick / settlement"]

request["counter_value_observation_request(...)"]
observe["observe(...)"]
envelope["inspect ReadingEnvelope"]

subgraph compile["Compile Contract"]
contract --> gen --> generated
end

subgraph write["Admit Intent"]
generated --> verify --> pack --> dispatch --> tick
end

subgraph read["Observe Reading"]
generated --> request --> observe --> envelope
end

tick -. "receipts and witness refs" .-> observe
```

A tiny contract looks like this:
Expand Down Expand Up @@ -299,7 +392,8 @@ cargo run -p echo-wesley-gen -- --schema counter.graphql --out generated.rs
```

Application code should use generated helpers rather than hand-rolling Echo
wire bytes. Conceptually:
wire bytes. Dispatch submits canonical input to Echo; it does not command Echo
to tick. Conceptually:

```rust
let intent = generated::pack_increment_intent(
Expand All @@ -308,14 +402,15 @@ let intent = generated::pack_increment_intent(
},
)?;

let response = echo_wasm_abi::kernel_port::KernelPort::dispatch_intent(
let dispatch_response = echo_wasm_abi::kernel_port::KernelPort::dispatch_intent(
&mut kernel,
&intent,
)?;
```

For reads, generated query helpers build `ObservationRequest` values. Echo
returns an `ObservationArtifact` containing payload bytes plus a
Echo's runtime owns the tick cadence, scheduler, settlement, and receipt
emission. For reads, generated query helpers build `ObservationRequest` values.
Echo returns an `ObservationArtifact` containing payload bytes plus a
`ReadingEnvelope`; the application should inspect that envelope before treating
the reading as complete.

Expand Down
48 changes: 38 additions & 10 deletions crates/echo-wasm-abi/src/kernel_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ pub mod error_codes {
pub const UNSUPPORTED_OBSERVATION_RIGHTS: u32 = 17;
/// The requested observation exceeded its explicit read budget.
pub const OBSERVATION_BUDGET_EXCEEDED: u32 = 18;
/// Application-facing intent ingress rejected a privileged control intent.
pub const FORBIDDEN_CONTROL_INTENT: u32 = 19;
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -499,7 +501,7 @@ pub struct WriterHeadKey {
pub head_id: HeadId,
}

/// Privileged control intents routed through the same intent intake surface.
/// Privileged control intents routed through trusted runtime control.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ControlIntentV1 {
Expand Down Expand Up @@ -2503,10 +2505,11 @@ fn coordinate_at_tick(at: &CoordinateAt) -> Option<u64> {
/// WASM is single-threaded, so `KernelPort` does not require `Send` or `Sync`.
/// Native test harnesses should use appropriate synchronization if needed.
pub trait KernelPort {
/// Ingest a canonical intent envelope into the kernel inbox.
/// Ingest a canonical application intent envelope into the kernel inbox.
///
/// The kernel content-addresses the intent and returns whether it was
/// newly accepted or a duplicate.
/// newly accepted or a duplicate. This is application-facing ingress and
/// must not accept [`ControlIntentV1`] envelopes.
fn dispatch_intent(&mut self, intent_bytes: &[u8]) -> Result<DispatchResponse, AbiError>;

/// Returns the current coordinate for an optic focus when the implementation
Expand Down Expand Up @@ -2541,12 +2544,24 @@ pub trait KernelPort {

match &request.payload {
OpticIntentPayload::EintV1 { bytes } => {
if let Err(error) = crate::unpack_intent_v1(bytes) {
return Ok(optic_dispatch_obstruction(
&request,
OpticObstructionKind::UnsupportedIntentFamily,
format!("optic dispatch EINT v1 payload is malformed: {error}"),
));
let (op_id, _) = match crate::unpack_intent_v1(bytes) {
Ok(intent) => intent,
Err(error) => {
return Ok(optic_dispatch_obstruction(
&request,
OpticObstructionKind::UnsupportedIntentFamily,
format!("optic dispatch EINT v1 payload is malformed: {error}"),
));
}
};

if op_id == crate::CONTROL_INTENT_V1_OP_ID {
return Err(AbiError {
code: error_codes::FORBIDDEN_CONTROL_INTENT,
message:
"application optic dispatch cannot carry scheduler control intents"
.into(),
});
}

let dispatch = self.dispatch_intent(bytes)?;
Expand Down Expand Up @@ -2655,7 +2670,20 @@ pub trait KernelPort {
///
/// This call is side-effect free. Implementations may report a run that has
/// already completed by the time the host polls here; for example, a
/// synchronous `Start` can return from `dispatch_intent(...)` with
/// synchronous `Start` can return from trusted runtime control with
/// `state = Inactive` and `last_run_completion` populated immediately.
fn scheduler_status(&self) -> Result<SchedulerStatus, AbiError>;
}

/// Privileged scheduler/runtime control boundary for trusted runtime owners.
///
/// Application-facing adapters should accept only [`KernelPort`] when they need
/// intent ingress, observation, status, or registry metadata. Host/runtime
/// owners that control scheduler lifecycle may require this separate trait.
pub trait TrustedKernelControlPort: KernelPort {
/// Execute a privileged scheduler/runtime control intent.
fn dispatch_control_intent_trusted(
&mut self,
intent: ControlIntentV1,
) -> Result<DispatchResponse, AbiError>;
}
Loading
Loading