Hold 0005's mid-playback sequence as what the queue and the session do (#35) - #293
Merged
Conversation
#35) 0005 fixes what happens when a token dies while something is playing: the stream is left alone, the report that was due is not lost and not retried at once, the queue is not touched by an authentication failure, one renewal is attempted, and each of its outcomes does one thing to the queue and tells the client one thing or nothing. 0034's counter, 0047's queue and 0057's report were all in the tree as values and nothing joined them, so a rejection during playback had no answer and #35's sequence existed only as a sentence. src/session/mid_playback.rs is the join. A rejected position report is answered off 0034's counter and holds the report where it is in every case, which the shared reference to the queue is what holds: the path cannot remove an entry. A renewal's outcome is read for playback: a fresh token reports the current position through the queue, so 0047's coalescing replaces the entry the rejection left at the head and the drain resumes from the same place with one report rather than two; a refusal keeps every report, answers the forced sign-out 0114 fixes and tells the client once, as an event carrying how many positions are held; a silence keeps every report and moves nothing. Neither answer carries a value of 0004's vocabulary, so nothing here can fail a call the client did not make. src/playback/report.rs gains the third occasion a report is made on, which is 0005's rather than 0057's, and the one call that makes it. The field name the event carries is registered in 0071's statement, and the test that holds that statement against the tree names it. Four guards were watched failing. Answering the current position without asking the queue reddens the success case. Emptying the queue on a refusal reddens the refusal case. Telling the client on a silence reddens two cases. Ending a session at the first rejection without telling the client reddens the no-route case. Nothing here sends a byte, runs a drain or holds a session, and the queue is not durable. #35's condition, a run through a token death against the fake server, has no subject in this tree and the issue stays open on it. Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The issue this belongs to
#35. The last section says which of its conditions this reaches, which is none of the three as written, and what it does reach instead.
What changed
src/session/mid_playback.rsis the sequencedocs/decisions/0005-the-session-model.mdfixes for a token that dies mid-playback, as what the queue and the session do at each step. 0034's counter insrc/session/renewal.rs, 0047's queue insrc/server/write_queue.rsand 0057's report insrc/playback/report.rswere all in the tree as values, and nothing joined them: a position report the server rejected had no answer, and the sequence 0005 names #35 for existed only as a sentence.Four things the module holds, each because the convenient shape is wrong in a way nothing would report.
A rejected report is held where it is, in every case, and the type is what holds it.
a_report_was_rejectedtakes the queue by shared reference, so the path that answers a rejection cannot remove an entry from it. The report that was due when the rejection arrived was the head, it stays the head, and the drain stops there, which is 0047's own rule for an entry that could not be delivered. 0005: nothing in the queue is discarded because of an authentication failure, because the failure says nothing about whether the positions are correct.Nothing here can fail a call the client did not make. Neither answer type carries a value of 0004's vocabulary, so there is no cancellation to deliver and no outcome to arrive on a call nobody made. 0005 says the client hears nothing except through diagnostics at that moment, and that is the one route out.
A fresh token reports the current position through the queue, so it is one report and not two. The report goes through 0047's coalescing, replaces the entry the rejection left at the head, and keeps that entry's place in the order, so the drain resumes exactly where it stopped and the position reached during the renewal arrives once.
src/playback/report.rsgains the occasion that report is made on and the one call that makes it; the interval is left where it was, as for a seek.A refusal keeps every report, signs the session out by the forced act, and tells the client once. The event is
session.reporting-suspendedatfailure, carrying how many positions are held under a field name treated as carried whole, which 0071 lists a count among. 0005: the queue belongs to the session, and a person who signs in again gets those positions reported in order before anything else. A silence keeps every report and moves nothing, which is 0034's half the natural code gets wrong.The other files register the module where every landing registers one:
src/session/mod.rs, the crate's thread assertions insrc/lib.rs, the same assertions asked from outside intests/thread_statements.rs, the coverage surface in.github/coverage/pinned-surface, the new field name in 0071's statement insrc/lifecycle/mod.rs, and the listtests/the_rule_as_data_names_every_field.rsholds that statement against.The means is the one the tree already carries, which is the check
## Choosing the meansasks for: a value joining three values that already exist, in the language 0011 measured and chose, and every case asserts against the queue's, the counter's and the sink's own answers rather than against a second apparatus.What failure it prevents
The current position reported around the queue after a renewal. It reads as the smaller change, and it delivers the held report and the current one as two writes for one item, the first of them stale, on the one path 0047 was built for.
A queue emptied because a renewal was refused. The session is over, so clearing what it held reads as tidy, and it discards the positions from a train journey at the moment they are least recoverable. 0005 refuses it by name.
A session signed out, or a client alarmed, because a renewal got no answer. 0034 says this is the half decided wrongly by reflex: a silence is not a refusal, and the damage is not the failed call but a token that is gone.
Evidence
The module's own cases, at the commit above:
The whole suite, which is one of the two commands the README names:
The other one, and the legs that run without a network:
The coverage leg, read off the runner rather than this machine. Run by hand here,
bash .github/coverage/coverage.sh checkmeasured nothing at all on this Windows machine, reportingactual -% (0 line(s), 0 missed)and refusing on it, so the local run is not evidence in either direction and the runner's is what stands:The size of the change:
What a guard here refuses, and the proof it bites
Four guards, each watched failing on the version of the mistake it is against, each restored afterwards, and the restored file compared byte for byte against the kept copy. Every run below is
cargo test --locked --lib session::mid_playback, and the deliberate violation is the edit named above each run.The current position after a fresh token goes through the queue. The violation answers the queue's own value without asking it:
A refusal keeps every report. The violation empties the queue before saying so, which is the tidy version of the failure 0005 refuses:
A silence tells the client nothing. The violation reports a silence as if the session had ended:
A server with no renewal route ends the session and the client is told. The violation ends it silently:
The guard that holds the rejected report in place is not one of the four, and that is said rather than left to be assumed: it is the signature of
a_report_was_rejected, and the one-change neighbour that removes an entry there does not compile. A case asserts the queue is unchanged across every rejection anyway, so a later widening of that signature meets a red run rather than a green one.What this does not cover
None of #35's three conditions as written. A test that plays through a token death against the fake server, proves the position reached during the outage arrives after renewal, and proves nothing is reported twice is a run with a stream, a drain and a renewal request in flight, and nothing in this tree sends a byte: the transport is #27, and #27 now names #291 and #292 as what its socket waits on. What this reaches is the shape those conditions will be asserted through, and the comment of 2026-08-11 on #35 already says the first condition cannot be a proof about a stream the core never holds.
Nothing is durable. The queue lives as long as the value does, which
src/server/write_queue.rssays of itself, so a restart between the rejection and the renewal is not something this can be asked about.Nothing runs a drain. The module answers that the drain stops, waits or resumes, and nothing consults that answer, because there is no drain.
Nothing here observes the position reached after the token died. 0005 guarantees the last one observed before the rejection and bounds the gap by 0057's cadence, and this claims nothing about the gap.
The sign-out is answered and not performed.
LocalHalfis what 0114's local half looks like once done; no token is dropped from memory because there is none, and no store is called.Nobody other than me has read the change. There was no second reader available for it, and the evidence above stands in place of one.