flybrain/services/flysim/crates/fly-session
acamilo 52ad46ecb0 refactor(session): build the session slice on the shared fly-session-types crate
CONTRACT-01 landed its crate, so the local stand-in module goes away and the domain
scalars, method payloads, their validation, the canonical digests and the trace format
all come from the contract. `src/types.rs` is a facade over that crate plus the few
things a coordinator needs that are not part of the type contract: a session-side
DomainError, the synthetic composition's schema and event-id derivations, and the
coordinator-local ControllerIntent, PortBinding and AgentOutcome that never cross the
bus.

Consequences worth naming:

- Payloads are read and written through DomainType::from_json / to_json instead of
  serde derives, so a misspelled required field fails where the contract says it
  should. serde, sha2 and ryu-js leave this crate's dependencies with them.
- The step-v1 section 8 trace is the contract's TransitionTrace, with behaviour and
  operational metadata already separated; the dispatch-order comparison now runs over
  the contract's own behaviour encoding.
- Two things the migration found. IN_PROGRESS is raised strictly before any mutation,
  so its certainty is "none", not "unknown"; the local module had it wrong.
  WorldObservation::validate_against checks the views a result carries but does not
  require every declared view to be present, so requiring them is made explicit in the
  coordinator's phase C check, where step-v1 puts it.
- The lost-Advance-result injection now waits for the worker to report the operation
  before abandoning the call, so the case it injects really is a loss after dispatch
  rather than a cancellation before it.

Gates: cargo test -p fly-session (61 tests, both transports), cargo clippy
--all-targets clean, and the runnable example produces the same behaviour trace over
both transports.
2026-09-22 12:30:41 +00:00
..
examples refactor(session): build the session slice on the shared fly-session-types crate 2026-09-22 12:30:41 +00:00
src refactor(session): build the session slice on the shared fly-session-types crate 2026-09-22 12:30:41 +00:00
tests refactor(session): build the session slice on the shared fly-session-types crate 2026-09-22 12:30:41 +00:00
Cargo.toml refactor(session): build the session slice on the shared fly-session-types crate 2026-09-22 12:30:41 +00:00
README.md refactor(session): build the session slice on the shared fly-session-types crate 2026-09-22 12:30:41 +00:00

fly-session

The lockstep session coordinator, its phase machine and a synthetic composition over flybus.

This crate is the SESSION-01 slice of the session-framework implementation guide: the sequential transaction of step-v1, driven over the Flybus router, with small fake workers standing in for a brain and an emulator. It contains no public controller API, no implicit best-effort retry, no real emulator and no real brain.

The domain scalars, method payloads, their validation, the canonical digests and the trace format all come from fly-session-types, the CONTRACT-01 crate. This crate adds only what is not part of the type contract: a session-side error value, the synthetic composition's schema and event-id derivations, and the coordinator-local ControllerIntent, PortBinding and AgentOutcome that never cross the bus.

Ready(k) ─ Prepare all agents concurrently ────────────> every agent Prepared(k)
         ─ one executor per agent, sorted agent-id order
         ─ one complete port batch, descriptor port order
         ─ exactly one Environment.Advance(k, batch) ──> boundary k+1
         ─ task.evaluate_transition, once
         ─ Commit all agents concurrently ─────────────> every agent Ready(k+1)
         ─ committed boundary k+1, publish, next Prepare allowed

Layout

Module Contents
types A facade over the fly-session-types crate, plus the session-side additions a coordinator needs
clock The step-v1 section 5 rational tick accumulator and the coordinator's pacing
phase The step-v1 section 2 state machine as an explicit edge table
dedup The ipc-v1 section 5 operation keys, result caches and retention
worker The worker dispatch shell: one service, the common Worker.* methods, admission
agent A fake agent worker: seeded model, mutation counter, fixed readout stub
environment The counter arena: one complete batch per advance, one native frame
task The task and executor traits, the deterministic counter task, the identity executor
rpc Domain calls: req-<U64> serials, incarnation pinning, the retry rule
coordinator The transaction, the trace, the failure rules and the publication boundary
harness The runnable composition: router, two agents, one arena, one coordinator

What it implements

  • The transaction, in order. Prepare all agents concurrently; run each task-local executor once in sorted agent-id order; assemble all configured port controls in descriptor port order; send exactly one Environment.Advance; evaluate the task once; commit all agents concurrently. The committed boundary moves only when every commit has succeeded.
  • The state machine, including Paused and Failed, with every transition recorded. A transition the step-v1 section 2 table does not list returns INVALID_PHASE.
  • The committed boundary rule. Only Ready(k) or Paused(k) is a committed boundary; a snapshot publishes one of those and never an in-progress mix of new agent state and an old world.
  • Time and pacing with checked rational accumulation. A 60 Hz world with a 1 ms model tick produces 16, 17, 17 ticks over three steps, totalling 50, with a remainder of exactly zero. Wall time is only pacing: when behind, the coordinator omits the sleep and reports the lag.
  • Initialization, pause and episodes. The environment initializes first, while stopped; the task bootstraps; then the agents warm up with learning disabled. Nothing in bootstrap advances the world or produces a gameplay reward. A pause arriving mid-step completes the transition and pauses at its committed boundary. A terminal task event commits its final rewards, then the session pauses; no worker resets itself.
  • The failure rules. A partial commit fails the epoch; an uncertain Advance is resolved against its original domain request id and never becomes a second batch; a worker incarnation change invalidates the epoch.
  • Domain deduplication over bus calls. Same key, request and body replays its cached reply with fresh delivery ownership over retained artifacts; a changed body is CONFLICT; a duplicate of a running operation is IN_PROGRESS for that bus call while the original completes; an evicted record is RESULT_EXPIRED; a newly issued request naming an old step is STALE_STEP. Worker.Acknowledge releases a domain result cache, which is not a bus delivery.consumed.

API

let harness = SessionHarness::start(Via::Unix, dir.path(), HarnessConfig::default()).await?;
harness.coordinator.bootstrap().await?;            // Ready(0), world stopped at boundary 0
let reports = harness.coordinator.run(3).await?;   // three transitions
harness.coordinator.pause_handle().request();      // finish this transition, then pause
harness.coordinator.trace.behavior();              // the step-v1 section 8 behaviour trace
harness.shutdown().await;
  • Coordinator::dispatch selects Sequential, Concurrent or Reversed per-agent dispatch. All three must produce the same behaviour trace; that is a test.
  • Coordinator::injections asks for one deliberate message fault at one step: a duplicate Prepare or Commit, an abandoned Advance result, an altered control batch, or a consumed result artifact followed by a replay. injection_log reports what came back.
  • Coordinator::probe_raw sends one domain request as it stands and returns the worker's own terminal outcome, without letting the answer change session state.
  • AgentFaults and EnvironmentFaults ask a worker for a deliberate delay or failure.

The synthetic composition

  • Agents. A fake model is an LCG with an explicit seed and one counter of everything that mutated it: ticks, stimulations, reinforcements and input installs. The worker reports that counter as its progressCounter, which is how a test proves a duplicate repeated nothing. The readout is a fixed stub: it reads bits of the current state, masked by the declared available actions, and never changes its own weights or invents a default winner.
  • Environment. A signed counter. inc adds one, dec subtracts one, and one bipolar bias axis is carried and validated but does not move the world. Each observation seals one immutable 4x4 RGBA frame whose bytes carry the counter, so an agent reading its sensory view reads the world rather than a constant.
  • Task. Rewards are the counter delta of each agent's own port control, with deterministic event ids derived from epoch, source step, rule and ordinal.
  • Executors. The stateless identity executor only, as v1 specifies.

Where this crate narrows or adds to the contract crate

  • Required views. WorldObservation::validate_against checks the views a result carries against their descriptors. Requiring every declared view to be there at all is the coordinator's Phase C check, so verify_step_result makes it: a missing required sensory view fails the transition with BUFFER_INVALID rather than being replaced by an older frame.
  • ControllerIntent. workers-v1 section 4 calls the task and executor interfaces local libraries, so their types live here rather than in the payload contract. An intent is a PortControl without its port, and only the coordinator adds the port.
  • The phase machine. step-v1 section 2 is this crate's, not the contract crate's; the trace's phase path is recorded beside the contract's TransitionTrace.

Limitations

  • Fake workers. There is no neural model and no emulator. What is modelled exactly is the ordering, the identity rules and the retry rules, not any numerical behaviour.
  • No state methods. State.Capture, State.StageRestore and State.ActivateRestore are STATE-01. The phase machine has their edges (Capturing, Restoring) and the workers do not advertise them as implemented methods.
  • One process. SESSION-01 runs every participant in one process over the same router. SESSION-02 is the per-fly process split.
  • No audience input. The admitted pre-step stimulation list exists and is always empty.
  • Pacing is coarse. The pacing deadline rounds one step to whole nanoseconds for sleeping only; simulation time stays rational and that rounding never re-enters the accumulator.

Tests

cargo test -p fly-session                                    # unit + both integration suites
cargo run -p fly-session --example session                   # the runnable synthetic session

Every integration test runs twice, once over the in-memory transport and once over a Unix socket, through the same router code:

  • tests/session.rs: one world advance per complete batch; every agent Prepared before the advance; one task evaluation per transition; every agent committed before the next Prepare or any committed publication; the 16/17/17 tick profile with a zero remainder; a mid-step pause completing its transition; bootstrap advancing nothing; the committed snapshot naming the transition that just ended; a terminal episode pausing at its own boundary; Worker.Status during a session; and sequential, concurrent and reversed dispatch producing one behaviour trace.
  • tests/failures.rs: a duplicate Prepare after a lost reply; a duplicate Commit; the same batch with altered controls; a lost Advance result; a cached artifact consumed by its first caller; one Commit failing after another succeeded; a replaced registration; a reply from another incarnation; a world that advanced without sensory data; an exact duplicate of a running operation; and an old-epoch operation.