session_over_one_router asserted that a latest subscriber must drop
snapshots. bus-v1 section 7 says a latest subscription replaces an
undelivered value; it lets a consumer miss values, it does not oblige it
to. Under contention the publisher was slow enough that the renderer kept
up, saw all twenty snapshots and the test failed on conforming behaviour:
16 of 40 runs beside four busy loops, and 9 of 20 whole-crate runs.
The renderer is now held until the publisher's completion is observed
rather than until a timer expires, so the coalescing is forced instead of
raced for: the subscription keeps the one delivery in flight and one
replaceable queued value, and the renderer receives snapshots 1 and 20 of
20. The assertions are the guarantees that hold -- what arrives is in
publication order, the last value is the latest published, the stalled
spectator never refuses a publication or drops out of the fan-out, and
every snapshot the renderer missed is counted as a replacement to the
publisher at admission and to the renderer on delivery, so nothing is
lost silently.
Three more tests in the crate asserted the same kind of race:
- latest_replay_is_ordered_ahead_of_a_racing_publish demanded the
non-coalesced outcome of a race section 7 allows either way ("bounded
mode preserves that order, while latest mode may coalesce it"). It now
puts one racing publication to a bounded and a latest subscription at
once: bounded must deliver the replay and then the publication, and the
latest branch is chosen by that publication's own replaced count.
- collection_waits_for_every_retained_owner, and the two disconnect
cleanup tests beside it, read the store directory for the unlink that
follows the registry update outside the router lock. They use
settle_files, like every other unlink check in the suite.
- The demo example printed a root count taken before the producer's own
release had reached the router, so the line the guide quotes was a
race. It waits for the release, the same way it already waits for
collection; the printed output is unchanged.
Nothing under flybus/src is touched: no routing defect was found. The
conformance rows for the credit/queue split, the replaced count, the
replay ordering and the latest spectator that cannot refuse a publication
now cite what each rewritten test actually varies.
The command line between a launcher and a participant in its own process
was coupled by string convention on both sides and checked by neither: the
parser accepted any --flag it did not know, so a renamed or dropped option
would have become a fault that never fires or a delay that is never
applied, with nothing failing.
Every flag now has one name, in launcher::flags, written by the launcher's
argv and read by the parser. Options::parse takes the sets its command
allows and refuses anything outside them by name, so a mismatch fails the
launch. measure and measure-row use the same constants for their own
flags.
Three tests hold the two sides together: every flag a launch writes is one
its command accepts, every media option is written for a separate process,
and an unknown or wrong-command flag is refused by name. The four media
faults now also run end to end in all three execution modes, each failing
its own transition, so the wiring across a process boundary is proved
rather than assumed.
Keep-both everywhere the two slices met. lib.rs takes both module sets.
AgentConfig keeps worker_threads and the sensor log; EnvironmentConfig
keeps worker_threads, the render delay and the render counter.
coordinator.rs keeps the two-stage resolution and its blame() beside the
media split of the Advance reply's attachments, and its imports take both.
harness.rs is main's launcher-based file with this slice's media
instrumentation re-applied on top.
The media instrumentation is shared memory, so it now follows the
launcher's own rule for the progress counter: sensor_log and renders
return None for a participant with a process of its own rather than a
misleading zero. The launcher carries the sensor log and the render
counter to a participant in this process and the render delay and the four
media faults on the command line to one in another process, where the
child builds its own log and counter.
The media path itself is mode-agnostic and is now tested as such: one
image per boundary, forwarded to every agent and published once, asserted
over the bus in all three execution modes, with the shared-memory
assertions made only where those participants live.
Two review notes.
The launcher allocation became a required field of HelloResult.limits
with no negative fixture behind it. Three rows now cover it: missing,
above maxWorkerThreads, and zero. Both readers reject all three, and no
derived fixture moved, because invalid.json is not one of them.
The resolution's two bounds disagreed. 512 attempts at a 2 ms pause
give up near 1.5 s, so the attempt count silently pre-empted the 8 s
budget the doc comment advertised. The budget is now the working limit
and says so: the guard is 8192 attempts, over sixteen seconds of pauses
against an eight-second budget, so at the default values the budget is
always what fires. Which one did is no longer arithmetic either --
ResolutionEnd records it, the failure message names the bound and its
size, and the code, the doc comment and the README all state the same
numbers. a_resolution_says_which_of_its_two_bounds_ended_it drives each
bound to the end in every execution mode.
Review fixes for SESSION-02.
An expired caller deadline was becoming a failed epoch without the
ipc-v1 section 6 resolution. That procedure existed and was correct and
had exactly one caller, a test injection, so the deadline this slice
introduced bypassed it and a merely slow participant lost its epoch.
Deadlines is now the two-stage shape section 6 describes -- a probe,
then a bounded resolve budget and attempt count -- call_owned returns a
typed CallOutcome so an expiry is distinguishable from a refusal, and
Prepare, Commit, Advance and the lifecycle calls all query the same
request id against the same incarnation before the epoch can fail. This
is also step-v1 section 7's Advance row, which was imperative about it.
The coordinator peak-RSS column was measuring the measuring process.
VmHWM never falls and every row shared one process, so the column was
cumulative and the mode ranking reversed when the rows were reordered.
Each row now runs in a measure-row child of its own. The corrected
numbers say the opposite of what the first report claimed: the
coordinator's own peak is roughly flat across the modes and lowest in
process mode, and the cost of the split is the children.
workers-v1 section 2 bounded Agent.Initialize's workerThreads by
"within launcher allocation" and named no wire for it. Dated amendment:
HelloResult.limits gains workerThreads, the worker reports what its
launcher gave it, and the launcher refuses one that disagrees. The
schema set, the shared fixtures and the TypeScript package move
together; contractDigest changes, which ipc-v1 section 4 provides for.
Also: the stale-epoch row now reaches the stale-epoch path against a
live agent process and asserts exact codes on both halves; the
router-restart row asserts the handle drop it claimed; frames are
counted from the behaviour trace instead of calculated; the README says
which suites run over which transports; bootstrap is fence-guarded; the
shutdown reason is an Id rather than a silent fallback; and
agent_mutations returns None rather than zero where the counter lives
in another process.
Review round two.
A restored timeline refuses a declared stream with no recorded sample
position instead of resuming it at zero, which would have restarted the
episode's audio clock silently.
A declared stream that produces no chunk for a transition now fails the
step. The contract does not say a chunk must exist; this slice requires
one, because a silently missing chunk cannot be told from a lost one.
Boundary 0 is exempt: no interval precedes it.
The fresh-epoch discontinuity refusal is dropped rather than written into
the amendment. Only the restore direction is stated, and sections 6 and 7
both have a fresh timeline publishing a discontinuity after a recovery or
a reset, so refusing the flag at an origin contradicted them. The dated
amendment now says the requirement is one-directional.
A snapshot with an unreadable chunk, or one whose handle is not attached,
is now unreadable as a whole rather than quietly carrying less audio than
was published. ViewPipeline::is_bootstrap_repeat is removed.
New tests: a missing chunk fails the step, restored timelines need every
declared stream's position, and a 7 Hz world's seven chunks sum to exactly
one second of 48 kHz samples. The bus-conformance reconnect row also cites
the test that actually replaces an incarnation.
Two readings of the section that are now checked rather than assumed: the
bootstrap window is exactly the boundaries where max(0, boundary - delay)
is zero and its repeated O[0] is the same artifact, and a chunk that
continues the previous one exactly cannot claim a discontinuity, which is
what keeps the restore rule checkable instead of advisory.
SESSION-02: one agent process per fly and one environment process under
the coordinator over the Unix-socket transport, compared against the
in-process composition and a dedicated-thread variant. The mode is the
only thing that changes; the composition, the coordinator, the workers
and the router are the same code in all three.
The launcher is the configured supervisor. It owns a total thread budget
with one allocation per participant, refused as BUSY before anything
starts when the total cannot cover it; the configured client, service,
worker and port identities, proved in Worker.Hello before the
coordinator pins a registration; Worker.Status health on the
supervisor's own monotonic clock at the ipc-v1 section 6 budgets; and
reaping, where Worker.Shutdown is the request and the operating system
is the guarantee.
The worker executable is a subcommand of this crate's one binary, which
is what implementation.md section 2 allows in place of a separate
worker crate.
The coordinator's fault behaviour: every failure names the participant
it is attributed to, every domain call has a caller-side deadline so a
dead participant is a diagnosed outcome rather than a hang, and failing
fences the epoch -- the boundary stops, the handles drop, and no further
transition or publication is allowed. Agent.Initialize now carries the
launcher's allocation, and an agent refuses one asking for more.
tests/processes.rs proves every acceptance bullet once per execution
mode, and the two section 4 rows SESSION-01 could not reach in one
process: a router restart during a world advance, and an old worker's
reply after a restart. measure compares the three modes at one, two and
four agents; its table is in the crate README, and it is not a capacity
claim.
caller_disconnect_cleanup_works_before_and_after_consumption failed 37 times
in 240 runs under load: the reply raced the router's own teardown. op_reply
reports routed:false only once disconnect has marked the call detached, and
the router runs that when its connection task reads EOF, while Client::close
waits for this client's reader only. The reply could therefore reach a
connection that was still closing and be routed. Nothing escaped -- teardown
releases those roots -- but the flag was read one step early.
The test now settles on the caller's connection being gone before asserting
the section 6 sentence, which is the poll the rest of the file already uses.
360 runs after the fix, 0 failures.
bus-conformance.md records the mechanism, the counts and the two other
intermittent failures seen in the same sweep, which are left to the bus slice.
Puts MEDIA-01 on top of the bus ArtifactRef rather than beside it.
fly-session-types gains the two validators a descriptor and a reference
cannot carry on their own: AudioTimeline, which holds one stream chunk
sequence for one epoch (no overlap, no backwards, the first chunk after a
restore marks the discontinuity), and check_imported_asset, which checks
that a transient artifact carries an installed asset content without ever
converting one identity into the other.
fly-session gains a media module: a ViewPipeline that renders one native
frame per boundary and serves the frame the declared observationDelaySteps
requires (so bootstrap repeats O[0] exactly while the pipeline fills), an
AudioSource with an exact rational sample budget and a deterministic
integer-phase waveform, the Phase C acceptance checks, a latest-subscription
Spectator with finite credits, a SensorLog recording what each agent read,
and an AssetRegistry for installed persistent content.
The counter arena now emits a real 32x24 RGBA8 pattern per boundary and one
audio chunk per transition; the coordinator forwards the one owned view
handle to every agent Commit and publishes the same handle, with the audio
handles, for presentation.
36 new tests: 12 shape rules against the contract crate and 24 session-level
acceptance tests over both transports.
The CONTRACT-01 amendment narrows Worker.Acknowledge ids to the req-<U64> form, which this
crate already read as DomainRequestId. The 1..=16 bound now comes from the contract's
MAX_ACKNOWLEDGE too, so there is one definition of it rather than two.
The reproduction, the three surveys and the before/after for the rung-10 Pewter stall, with the
ROM-gated run's table. Rows 51 (UNKNOWN's two states), 52 (a building with no row on the map
graph) and 53 (a frontier excluded by a window that lapses) each with their tests.
Two fixes from the independent review of f043cd7.
RationalNs::checked_add cross-multiplied into u128 and then added without a check. Each
product fits, but their sum does not: two reduced fractions near the U64 maximum add to about
2^129, which panics in debug and wraps in release, after which the reduction returns a
confidently wrong rational. Every multiplication and the addition now go through a checked
path, as ipc-v1 section 2 requires, with a fixture row and a test that reaches the overflow
rather than the "does not fit U64" reduction limit.
The schema-set test checked fourteen hardcoded type names, so a new payload type could have
shipped outside contractDigest. It now iterates the readers own list, all fifty of them.
Also amends ipc-v1 section 5, dated: Worker.Acknowledge carries req-<U64> request ids, not
arbitrary Ids, because the serial watermark rule in that same section cannot reject reuse
after acknowledgment unless the acknowledged id carries its serial.
From the live rung-10 checkpoint with the stub rotation: map 0x34 to map 0x36 on frame 2423, on
15 macros, against never in five and a half live hours. TALK is on the pad inside the gym -- the
fly standing in front of one of the people rung 11's place names -- and it presses it 15 times
there. BACK on a dialog or unknown frame is 0 against the 189 the hunt measured on Pewter City
alone, no frame deals a pad on an unknown with nothing drawn on it, and GO FRONTIER starts 0
times on the museum's two floors.
The unit test beside it is the other half of "the objective reaches the leader": on the gym's
own map the rung's place resolves to the person standing in it, the walk aims at the four tiles
around them, the door is not a candidate while they are there, and talking to them empties the
list. It also pins what the ROM run showed: the town's errands come first for every map in the
area, the gym included, so both have to be paid before the objective is the leader.
Review found two Memory-only tests behind a README claim that every integration test runs
twice. Both are now generated by both_transports!: the one-agent composition, and the
old-epoch refusal, which is one of the three tests the old-worker-replies row cites. The
README says which single test walks both transports inside itself instead, because it
compares their traces against each other.
step-v1 section 2 gains the mid-step pause line and a dated amendment saying why it is not
a new edge: a pause requested during a transition is the ordinary
Committing(k) -> Ready(k+1) edge followed by Ready(k+1) -> Paused(k+1), because section 6
requires the transition to finish first, so the only boundary such a pause can land on is
the one the transition just committed.
Two Stuck rollbacks fired on rung 10 inside half an hour, both on a fly that was walking, and
both were the ratchet working to contract: the stall window is reset by exploration -- one tile
the run has never stood on -- and a fly crossing a town it has already covered to reach the
rung's own door earns none of it. Entering a map for the first time already counts, because a
new map is ground nobody has stood on; re-entering one does not, which is what the museum was.
So the window gains a second signal, passed in by the caller and meaningless to the ratchet
itself, exactly as coverage is: the Pokemon loop answers with "nearer the objective, in map
hops, than this run has ever been", over the same map graph GO OBJECTIVE walks. It can fire at
most once per step of the road, it spends no budget, it captures nothing and it skips no
trigger. Nothing in the macro layer reads it back and no button is bound on it.
The checkpointed ratchet state is untouched: the signal is a level on one sample, not a counter.
GO FRONTIER was 1,235 of the rung-10 run's macro starts in 47 minutes, over two museum floors
and a town whose ground the fly had already covered. The tiles it aimed at were real and
unreachable: 98 walkable tiles on the museum's ground floor, 62 of them reachable from the door,
39 never stood on and almost all of those behind the admission desk. A walk that can reach none
of its goals refuses no route and writes every goal to the blocked ledger -- which is a ten
brain minute window, so all of it came back and the refusal happened again, once per hold.
A window is right for a target somebody is standing in front of and wrong for ground the map has
fenced off. So the refusal is remembered per map instead, with no window, and it is cleared by
the one event that can change the answer: the fly standing somewhere on that map it had not
stood on before -- a door opened, a script carried it through, somebody moved out of a doorway.
Re-entering the map clears nothing, which is the loop the window made.
BACK was 678 macro starts in 47 minutes on rung 10, 189 of them on Pewter City with no text box
on screen at all. BACK is on no overworld and no dialog pad, so every one of them was dealt by
Scene::Unknown -- which holds two states under one name. One is a screen this crate cannot name,
the Pokedex or the trainer card or OPTION, where A and B are what leave it. The other is a frame
of the overworld where the cartridge is driving -- a warp in flight, a scripted push-back, the
museum guide walking the fly through the door -- which the detector calls Unknown because the
buttons are not reaching the player.
On the second, NEXT and BACK are an A and a B pressed into somebody else's script: they change
nothing and they complete where the fly stands, which is section 12.2's trap with no box to
advance. So Unknown's pad is dealt on whether a box is drawn, and a scripted overworld frame is
an empty pad the fly waits out -- the cartridge gives the buttons back by itself.
Rung 10 spent five and a half hours in Pewter City with the objective two doors away, and the
fly kept ending up inside the museum, where GO OBJECTIVE, GO SHOP and GO HEAL were all off the
pad: a map with no row in the graph has no neighbours, so next_hop answers nothing and area_of
answers nothing. Named as a residual by the previous review and worked here.
Both ids come from the cartridge's own warp table, surveyed from the rung-10 checkpoint rather
than counted: Pewter City names 0x34 at (14, 7) and at (19, 5), which are the museum's two
doors, and 0x34 names 0x35 at (7, 7), which is the staircase. The upper floor has no area, for
the same reason a bedroom has none -- no front door of its own -- and the road out of it is the
staircase, which is what the fly needs there.
The rung-10 review needed to tell "the whole-map grid is wrong on this map" from "this frame
was mid-step", and the refusal label cannot: both read "screen disagrees". So the decode is
split from its own cross-check, the disagreement is readable tile by tile, and the map survey
stands still for a while and counts how many of those frames decode.
On Pewter City, measured from the rung-10 checkpoint: standing still it decodes on 118 of 120
frames, and the frame the survey caught disagrees on three tiles by exactly one tile row in the
direction the fly was walking. The map is fine; the reading is refused while the fly is moving.
An independent review of 4f44894 accepted the audit with two required evidence
changes, neither of which needed a code change.
"Classification is not a topic-name heuristic" cited a lane-order test that
never varies a topic name. The row is now conforms-by-construction, naming the
four scheduler functions that read only the queue an item sits in, and
a_topic_named_like_a_notice_is_still_classified_as_topic_data publishes on
topics called call.failed, route.removed and subscription.closed and asserts
each arrives as a topic.message delivery with its payload intact.
"A latest subscriber never causes BACKPRESSURE" cited a bounded-mode test and
the ignored measurement. a_latest_subscriber_never_refuses_a_publication floods
one unconsumed latest slot with 100 publications of 60 KB, six times the
per-client bounded byte pool, and asserts none is refused, 98 coalesce, the
queue stays at one and the credit never comes back. That row also underwrites
the byte-budget deviation and the first contradiction.
Both tests run over both transports.
The coordinator resolved both contradictions in the spec rather than the code,
so bus-v1 carries them as dated amendments with one line of reason each: the
section 9 table row becomes "Per-client ordinary bounded queued envelope bytes"
with a new "Latest subscription slots | subscriptions x 64 KiB" row, and the
section 2 sketch drops the budget argument and shows the deadline at the caller.
With the amended wording the byte-budget row conforms, so the report's counts
are 178 / 9 / 1 fixed / 7, and the crate README's differences 4 and 8 say they
no longer differ.
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.
bus-conformance.md audits the flybus crate against bus-v1 sections 2 to 11, one
row per normative sentence: requirement, status, code location, the test that
proves it. 195 rows, 177 conforming, 10 deviating with the sentence that allows
each, 1 must-fix (the teardown starvation, fixed in this branch) and 7 not
implemented with an owner named. It also maps every BUS-01/02/03 acceptance
bullet to its test, reconciles all ten of the crate's own differences from the
draft, records the frame measurement with the reasons it is not a capacity
claim, and lists the two contradictions inside the draft it will not guess at:
the per-client queued byte budget against the latest-mode guarantee, and
section 2's call budget against section 6's client-owned deadline. Neither
changed any code.
bus-v1 gains section 12: dated amendments for the three error codes the
implementation needs and the draft left unnamed (CONFLICT, NO_TOPIC,
ARTIFACT_MISMATCH), one line of reason each, and for the one operation it adds
(rpc.responder.release), which is how bounded call correlation ends when a
handler keeps reply authority after releasing the request delivery. Section 9's
error list is inclusive, and section 4 already provides for a digest change.
The crate README points at the report, carries the new measurement summary in
place of the old numbers, and describes the two new test files; the design index
and the flybus index point at the report too.
The guide's first deliverable is one small program with a counter RPC, a pub/sub
observer and a frame artifact held past its message object. examples/demo.rs was
already that; its body moves into run(), which returns the lines it prints, so
tests/example_demo.rs can assert all seven of them. The counter now uses the
spec's own example.counter / Counter.Increment / {"amount": 1} and pins the
registration it discovered, and the example prints the router's root count while
the frame is held and after the last handle goes, so the lifetime it
demonstrates is visible rather than implied. cargo run -p flybus --example demo
prints the same lines.
tests/perf.rs reports what BUS-03 and bus-v1 section 11.7 actually ask for.
Allocate, the producer's copy into staging, the router's seal copy, publish
admission, the RPC round trip and a consumer's readback are six separate
percentile lines instead of one. The router gets its own two-thread runtime
whose threads carry a distinct name, and per-thread CPU is sampled from /proc by
that name, so router CPU is separable from the clients' in the same process.
Store bytes, outstanding roots and queue lengths are reported live as well as
peak.
One named test per implementation-guide bullet that the existing suites did not
already cover: a lost result that leaks no roots while the endpoint's own cache
still replays it, a retransmission of one domain body under a fresh call id, no
failover onto a replacement registration, a status RPC answered while another
handler is delayed, and a disconnect that reclaims logical ownership without
touching a file the consumer still has open. Each runs over both transports.
BUS-01 also asks for equivalent behaviour traces from the two transports, so
tests/common/mod.rs gains a Trace recorder whose record() panics on any
router-issued id: a trace can hold methods, payload fields, counts, sequences,
credits, cancel states and error codes, and nothing operational. One RPC
scenario and one pub/sub-with-artifacts scenario produce 29 events, identical in
memory and over a Unix socket. FLYBUS_TRACE=1 prints them.
The sixteen bullets the older suites already prove are cited in the conformance
report rather than duplicated here.
The write gate was one mutex the writer held across every synchronous transport
poll, and close_conn took that same mutex before reclaiming a connection's
owners. Under a transport that accepts a byte per poll the writer re-acquired it
hundreds of times while teardown queued behind it, so teardown could be starved
for a whole frame and the delivery completed just before its owner was
reclaimed. bus-v1 section 9 puts release and route-health control out of reach of
telemetry starvation; tests/sol_rereview_regressions.rs's poll-gate regression
failed 6 release runs out of 6 alone, and about one debug run in five.
The gate now separates "teardown has begun" from "a poll is in progress": a
mutex plus a condvar. begin_close sets closing once without waiting, then waits
for the poll already in progress and records whether it left a frame half
written. The writer brackets each poll with enter_poll (refused once closing)
and leave_poll(bytes) instead of holding a lock across it, so teardown's window
is one poll rather than a frame, and no byte follows it. close_conn no longer
holds the gate across reclamation. Every item is pub(crate): no public
signature changed.
The regression test no longer synchronises with a sleep. The shutdown thread
announces itself on a channel, and what follows is structural: teardown cannot
pass the gate until the held poll returns, which only release() allows. Its
delivery is 50 KB now, which a writer resuming a byte per poll cannot finish
inside the window, so the test observes the ordering instead of a coin toss and
asserts the two legal shapes of the stream: cut short with nothing appended, or
never begun with exactly the closing notices following.
packages/session-types mirrors the Rust crate: canonical JSON (RFC 8785), strict parsing,
canonical digests, readers with a validate step for every payload of ipc-v1, workers-v1,
state-media-v1 and publishing-v1, the trace comparator, seed derivation and the FLYSESS1
envelope. It loads the crate fixtures/ directory rather than keeping a copy.
43 tests with node test runner, as in packages/feed. Both languages assert the same recorded
canonical bytes and digests, so agreement is checked rather than assumed; the envelope test
goes further and requires the bytes this package writes to equal the fixture the Rust crate
wrote.
Also narrows the canonical number rule to a value-based one (finite, and within the exact
integer range when integral), because JSON.parse cannot tell 1e21 from the same digits
written out, and two implementations that disagree about one number agree about no digest.
The lockstep coordinator, its phase machine and a synthetic composition, as a new
workspace member. Every worker method is a Flybus RPC to an incarnation-pinned
service; the domain request ids and result caches of ipc-v1 section 5 sit in front of
every mutation.
- coordinator: the step-v1 section 3 transaction in order -- prepare every agent
concurrently, run one executor per agent in sorted agent-id order, assemble all port
controls in descriptor order, send exactly one Environment.Advance, evaluate the task
once, commit every agent concurrently, and move the committed boundary only when all
of them succeeded. Sequential, concurrent and reversed dispatch are selectable and
must agree.
- phase: the section 2 machine as an explicit edge table, Paused and Failed included,
with a committed-boundary predicate that gates pauses, captures and publication.
- clock: checked rational accumulation. A 60 Hz world with a 1 ms model tick runs
16, 17, 17 ticks and comes back to a remainder of exactly zero.
- dedup: operation keys, the cached reply with its own artifact holds, CONFLICT,
IN_PROGRESS, RESULT_EXPIRED, STALE_STEP, the lifecycle bound and Worker.Acknowledge.
- worker: the dispatch shell. Admission order and the result cache live here; the
endpoint mutex is the worker's simulation lock, so one mutation runs at a time while
Worker.Status answers from a separate cell.
- agent, environment, task: a fake model with an explicit seed and a mutation counter
the tests read, a fixed readout stub, a counter arena that seals one immutable frame
per boundary, a deterministic task and the identity executor.
- fly_session_types: the CONTRACT-01 domain types as a local stand-in, reconciled with
the shared crate in a following commit.
Tests run twice, over the in-memory transport and over a Unix socket, through the same
router: the SESSION-01 acceptance bullets and every section 4 failure-injection row
that applies to this slice.
fixtures/ is the shared contract corpus: accepted payloads with their canonical JSON and
digest, refused payloads with the rule each breaks, raw byte sequences that must not even
parse, recipes for the payloads too large to store, the U64 and double boundaries, rational
arithmetic vectors, the identity matrix, descriptor cross-checks, operation keys and bodies,
trace comparison variants, the schema set and its digest, seed vectors and one FLYSESS1
envelope. packages/session-types loads the same files.
Tests: 45 across nine files. Round trips preserve every field and reproduce the recorded
canonical bytes; duplicate keys, invalid UTF-8, NaN, trailing data, oversize typed values and
oversize envelopes fail; U64 and double boundaries reject on the right side; the four
identities never accept one another spellings; the accumulator produces 16, 17, 17 and a zero
remainder; the trace comparator ignores wall time, request ids, callIds and delivery ids and
notices every behaviour change; the contract digest survives reformatting and moves when a
field, bound, enum member or type changes.
Also: a crate README, and the update_fixtures example that regenerates the derived files.
The CONTRACT-01 type definitions, committed early so the session slice can build on
them while the fixtures and cross-language tests are still being written.
- scalar: Scope, RationalNs (reduced, positive denominator, 0/1 zero, checked
arithmetic and the step-v1 accumulator), SchemaRef, TypedValue with its 32-KiB
canonical-JSON cap, and the four identities that must not be confused (bus callId,
domain requestId, artifact identity, delivery/hold owner token) as distinct types.
Id, U64 and Digest call into flybus::wire instead of forking the encodings.
- canonical: RFC 8785 canonical JSON, SHA-256 digests, the operation key and the
canonical body rules of ipc-v1 section 5, with bus identities refused in a body.
- rpc, workers, media, publishing: the payloads of ipc-v1, workers-v1, state-media-v1
and publishing-v1, each with a validate step for the documented ranges, uniqueness,
ordering and bounds.
- schema: the canonical schema set, whose digest is contractDigest, declared as data
so source formatting cannot change it.
- trace: the step-v1 section 8 record, behaviour separated from operational metadata.
- seed, checkpoint: the seed derivation and FLYSESS1 envelope layout.
The first run of the new ROM test failed on YES 134, and every one past
the sixth was in Pewter City or the gym the fly had walked to -- which is
the fly playing the game, not the ring. Row 41 is about the presses spent
in the room, so the harness records starts by name while the fly is still
on the map it resumed on: 6 macros there, YES 2.
Unit: HEAL is off a full party's pad, including the rung-10 party's own
numbers, which is the assertion that the loop was never the heal's; TALK
is off a rested nurse's pad and on a hurt one's, and an ordinary
villager is unaffected; her prompt deals one answer and a plain box still
deals three; a readable prompt that is not hers deals both answers and no
NEXT; an answer whose prompt reopens is excluded and one that settled the
box is not; a completed heal and a declined prompt both retire her.
ROM-gated from the live checkpoint (FLY_CENTER_CHECKPOINT): the fly
leaves map 0x3a on a bounded number of macros, YES starts stay under
five, NEXT is on no pad while a prompt is readable, and TALK is on no pad
at a rested nurse. The run also counts dialog and prompt frames, which is
what makes the forty-six-to-one ratio visible.
TALK's precondition reaches over a counter because the cartridge does;
its talked-ledger entry was read one tile ahead. So TALK was bound at the
counter and recorded nothing, 107 times, and the nurse was never retired.
The entry now comes from the same reading the precondition makes.
A completed HEAL writes her into the talked ledger: the macro has had the
conversation with its own presses, and the reached window would otherwise
expire in ten brain minutes and offer the ring again. A declined prompt
writes it too -- 12.4 inverted for one person, because the pad only ever
offers NO at her prompt when the party is already full.
And the general rule: a YES or NO whose prompt is up again on the same
map and tile within one hold did nothing, so that answer joins the
blocked ledger for its window.
On a box that is a readable YES/NO prompt, NEXT is YES under another
name -- an A press at a two-option menu confirms the option the cursor is
on -- so it is off that pad and the pad is the box's two answers. That is
12.10's pair rule in a dialog.
At the nurse's own prompt only the answer that changes something is
bound: YES with a hurt or statused party, NO with a full one, read from
the same byte HEAL's precondition has read since section 13.
And TALK is off the pad at a nurse the party has no use for. Her
conversation is a service whose need the cartridge publishes, and a ring
of text that ends where it began is section 12.2's trap. Nobody else is
narrowed.
An answer the reopened-prompt ledger excludes leaves the pad, and the
exclusion never empties one: a box nothing can answer cannot be left.
Row 41 is the Pokemon Center nurse: 2,142 YES starts on one tile of map
0x3a with a party at 70/70. The survey that names it is a new probe mode
(FLY_PROBE_CATCH=nurse) that leaves the box with B and then pulses A,
printing every state the conversation passes through with the two-option
menu's own geometry beside it. It is a ring of forty-six presses and the
box is a choice on exactly one of them.
So the reading: wFontLoaded plus the border DisplayTwoOptionMenu draws,
plus the cursor it parks inside it. Both halves are needed -- the cursor
bytes survive the box closing, so all forty-six frames carry the
geometry. It claims that box and not "a choice is open" in general.
TargetKey gains Answer { at, yes } for the reopened-prompt exclusion.
The doc comment on Cursor::want credited THROW BALL's 63 blocked starts to the
bag drawing slowly, which is true of the frames and not the cause. What the rule
did was make the step report which list it had been left looking at, and the
answer was never the bag - it was the party list, because battle_entry's ITEM and
PKMN were the other way round. The rule stands on its own: a step that reads the
list it has already answered takes its length and its direction from entries it
is not walking.
The rung-10 loop whole: which building the fly was in, why every candidate list
on that map emptied, the byte dump that named the battle menu's swapped entries,
the ROM-gated before and after, and the two arms of the trap hunt being identical
because a restore clears the ledgers the loop was built out of.
Section 13.1's tables follow: MENU off both overworld rows and off the narrow
counter row, the move list's conditional BACK, the start menu reachable only by
the raw START button, and two new rows in the pad-empty audit - the room whose
one door the ledger rests, and the map with no way out at all, which is the one
empty pad left and is asserted rather than covered.
A new test from the rung-10 Pewter checkpoint: the fly is on map 0x35, the
museum's upper floor, the objective is the gym where the badge is, and over
thirty-three brain minutes MENU is on no overworld pad, no overworld pad is
empty, MENU and BACK never alternate, and the fly leaves the building on frame
182. On v0.4.3 the same test fails at the first of those: MENU is on the pad of
maps 2, 52, 53, 54, 55, 57 and 58 with 147 starts.
The harness records where a macro reported blocked, which is what identified the
battle menu's swapped entries, and the forest test now asserts THROW BALL never
blocks: 15 starts and 0 blocked from the rung-9 checkpoint, against 6 of 6.
Its bound on a battle's macros goes from 400 to 700 against a re-measured worst
of 450, because a fly whose SWITCH and ITEM reach their own lists fights longer
than one that can only attack. Every battle still ends: 3 of 3.