Skip to content

Make the report 0057 decides, and put every one of them through 0047's queue (#57) - #290

Merged
iderex merged 1 commit into
mainfrom
the-report-through-the-queue-57
Sep 3, 2026
Merged

Make the report 0057 decides, and put every one of them through 0047's queue (#57)#290
iderex merged 1 commit into
mainfrom
the-report-through-the-queue-57

Conversation

@iderex

@iderex iderex commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The issue this belongs to

Closes #57

The last section says which of its four conditions this reaches, which is all of them, and what it leaves outside.

What changed

src/playback/report.rs is the report docs/decisions/0057-the-progress-reporting-cadence.md decides: the one act that puts a playback position on the queue in 0047, on each of the five events the moment it happens and on the interval while something is playing. src/playback/cadence.rs already held the interval and the events as values; nothing made a report out of them, so no position was ever enqueued and #57's three open conditions had no subject in the tree.

git rev-parse origin/the-report-through-the-queue-57
6a6284c5d63f4e945dca2f9fa4c467d135326401

git show origin/the-report-through-the-queue-57:src/playback/report.rs | grep -n '^pub enum \|^pub struct \|    pub fn \|    pub const fn '
86:pub enum ReportedOn {
106:pub struct PositionReport {
114:    pub const fn position(self) -> Ticks {
120:    pub const fn reported_on(self) -> ReportedOn {
133:pub enum WhatObservingDid {
159:pub struct Reporting {
173:    pub const fn for_item(target: Target, at: ElapsedInstant) -> Self {
182:    pub const fn target(&self) -> &Target {
188:    pub const fn interval(&self) -> TheInterval {
203:    pub fn report(
234:    pub fn observe(

Three things the module holds, each because the convenient shape is wrong in a way nothing would report.

Every call that reports takes the queue and hands back what the queue did. 0057 says every report is put on the queue rather than sent, and 0047 says why: a path that sends directly and queues only when the server is away is two paths that agree until a device's connectivity changes mid-playback. So the type has no second way out. A report is WhatTheEnqueueDid, and nothing in the module can produce one without asking the queue.

A report always states the position. 0056 reads the server at one commit: a report carrying no position is read as the whole duration, which is the item finished. PositionReport has no absent value, and a report at the beginning carries the beginning.

Observing a position makes a report only when the interval says one is due. A client hands the core every position its player produces, and most of those calls enqueue nothing. That is the whole of the cadence, and it is the shape the method loses the moment one question is deleted, which is the first guard below.

The other four files register the module where every landing registers one: the crate's thread assertions in src/lib.rs, the module list and two paragraphs in src/playback/mod.rs, the same assertions asked from outside the crate in tests/thread_statements.rs, and the paragraph in src/playback/cadence.rs that said no report existed and #57's conditions were met by nothing.

The means is the one the tree already carries, which is the check ## Choosing the means asks for. This is a value beside two values that already exist, the interval in cadence and the queue in write_queue, in the language 0011 measured and chose, and every case asserts against the queue's own answers rather than against a second apparatus.

What failure it prevents

Reporting on every position change. The player hands a client a position several times a second, and a reporter written straight from that produces a request per second per stream, multiplied by every stream in a household, at the one place on this board where the machine belongs to the person paying for it. 0057 prices that as its first refused alternative, and the module's observe is where that shape arrives.

A report that skips the queue. Sending directly when the server is there and queueing when it is not reads as the smaller change, and 0047 refuses it because the two paths disagree only on a phone on a train. The module cannot express it.

A seek that waits for the interval. It reads as an optimisation, since a scrub already coalesces to one entry, and it costs the report that carries where a person actually is when the process is killed while they scrub. 0057 fixes that a seek reports the moment it happens.

Evidence

The module's own cases, at the commit above:

cargo test --locked --lib playback::report
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 536 filtered out; finished in 0.00s

The whole suite, which is one of the two commands the README names:

cargo test --locked
test result: ok. 547 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s
test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 123 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s

The other one, and the three legs that run without a network:

cargo build --locked --all-targets
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.44s

bash .github/lint/lint.sh check
Every lint the groups above carry was refused, outside the register printed with it.

bash .github/format/format.sh check
Every tracked source file above is written the way the formatter would write it.

bash .github/invariants/invariants.sh check
Every rule above was applied to its subject and refused nothing.

The size of the change:

git diff --shortstat origin/main...origin/the-report-through-the-queue-57
 5 files changed, 731 insertions(+), 12 deletions(-)

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 re-run green. Every run below is cargo test --locked --lib playback::report, and the deliberate violation is the edit named above each run.

Nothing is reported between interval ticks. The violation is the due question deleted from observe, which is the reporter that reports on every position change:

-        if !self.interval.a_report_is_due(now) {
-            return WhatObservingDid::NotDueYet;
-        }

test playback::report::tests::every_report_passes_through_the_queue ... FAILED
test playback::report::tests::an_interval_report_is_made_ten_seconds_after_the_last_and_not_before ... FAILED
test playback::report::tests::a_position_observed_before_the_interval_is_due_is_not_reported ... FAILED
test playback::report::tests::resuming_reports_and_runs_the_interval_from_the_resume ... FAILED
thread 'playback::report::tests::a_position_observed_before_the_interval_is_due_is_not_reported' panicked at src\playback\report.rs:396:13:
  left: Reported(ReplacedInPlace)
 right: NotDueYet
test result: FAILED. 7 passed; 4 failed; 0 ignored; 0 measured; 536 filtered out; finished in 0.00s

A report on the interval moves the interval on. The violation is the interval left where it was after an interval report, so the next one is due at once:

-        self.interval = self.interval.after_a_report_at(now);

test playback::report::tests::an_interval_report_is_made_ten_seconds_after_the_last_and_not_before ... FAILED
thread 'playback::report::tests::an_interval_report_is_made_ten_seconds_after_the_last_and_not_before' panicked at src\playback\report.rs:553:9:
  left: Reported(ReplacedInPlace)
 right: NotDueYet
test result: FAILED. 10 passed; 1 failed; 0 ignored; 0 measured; 536 filtered out; finished in 0.00s

An event moves the interval the way 0057 says it does. The violation is the interval left where it was after an event, so a started stream never runs and a paused one is never told it stopped:

-        self.interval = self.interval.after(event, at);

test playback::report::tests::a_position_observed_before_the_interval_is_due_is_not_reported ... FAILED
test playback::report::tests::a_seek_reports_and_does_not_move_the_interval ... FAILED
test playback::report::tests::an_interval_report_is_made_ten_seconds_after_the_last_and_not_before ... FAILED
test playback::report::tests::every_report_passes_through_the_queue ... FAILED
test playback::report::tests::resuming_reports_and_runs_the_interval_from_the_resume ... FAILED
thread 'playback::report::tests::every_report_passes_through_the_queue' panicked at src\playback\report.rs:467:9:
  left: 5
 right: 8
test result: FAILED. 6 passed; 5 failed; 0 ignored; 0 measured; 536 filtered out; finished in 0.00s

A seek does not wait for the interval. The violation is the optimisation somebody writes because a scrub coalesces anyway: a seek that reports only where the interval is already due, answering the queue's replaced-in-place without asking it:

+        if event == ReportsWithoutWaiting::Seeked && !self.interval.a_report_is_due(at) {
+            return WhatTheEnqueueDid::ReplacedInPlace;
+        }

test playback::report::tests::each_immediate_event_reports_without_waiting_for_the_interval ... FAILED
test playback::report::tests::a_scrub_produces_one_report ... FAILED
test playback::report::tests::reports_for_two_items_do_not_collapse_into_one ... FAILED
thread 'playback::report::tests::a_scrub_produces_one_report' panicked at src\playback\report.rs:350:9:
  left: Ticks { ticks: 0 }
 right: Ticks { ticks: 24000000000 }
test result: FAILED. 8 passed; 3 failed; 0 ignored; 0 measured; 536 filtered out; finished in 0.00s

The fourth is the one worth reading: the queue answered nothing and the scrub case still saw one entry, holding the position playback started at rather than the one the person scrubbed to. A case that counted entries alone would have passed it.

What this does not cover

Which of #57's conditions this reaches: all four, and this section is what it leaves outside them. The cadence and its reason were recorded by 0057. A scrub produces one report, each immediate event reports without waiting for the interval, and every report is observed passing through the queue, each by a case named for it above.

Nothing is delivered. No report leaves the device. Nothing in this tree opens a connection, the drain 0047 describes runs on 0045's recovery report and does not exist, and the queue every report passes through is not durable, which src/server/write_queue.rs says of itself and #47 holds. What is proven here is the reporting and never the delivery, so 0057's statement about the report a person most expects to have landed, durable rather than delivered, is today neither.

Which request carries a report is not decided here, and two landed records read differently on it. 0057 says every one of the five events' reports goes on the queue. 0010's table classifies POST /Sessions/Playing and POST /Sessions/Playing/Stopped as accumulations, and 0047 says an action that cannot be expressed as an assertion is not queued but attempted when it is asked for. What this module builds is every report as an assertion of position, whatever occasioned it, which is the shape 0047 admits and the progress path carries. Whether a report occasioned by started or stopped is also an accumulating call made outside the queue, or the progress assertion alone, is a question between those records that #27's transport meets and nothing here answers. The occasion is carried on the report so that whoever answers it has what it needs.

No client-facing call exists. Reporting is a value a caller drives with the queue in hand; the calls a client makes are #115's creation and its lanes, and no session in this tree holds a queue for one to be driven against.

The position is admitted elsewhere. Reporting takes an AdmittedPosition rather than a number, so 0056's two bounds are applied at whichever boundary hands it one, and nothing here applies them. 0056's clamp report through 0100, once per item, is still not made, for the reason src/playback/mod.rs already gives.

The interval is chosen and not measured. Ten seconds is 0057's number and that record says so of itself. #65 is the harness a measured replacement would come from.

The coverage leg has no new subject. src/playback/ is not an area on the pinned surface, which that register's own paragraph states and this change does not alter.

The document-paths, thread-detector and target legs were not run here. Nothing in this change adds or edits a tracked Markdown file, which is the first leg's whole subject; the second needs a nightly compiler this machine does not have installed; the third compiles for triples this machine has no standard libraries for. That is a claim about what each leg reads rather than a run of it, and the run on the runner is the verdict.

Who has read it

Nobody other than me. There was no second reader available for it, and the evidence above carries the change in place of one.

…s queue

0057 fixes that every position report goes onto the queue in 0047 rather than
to a server, that five events report the moment they happen, and that the
interval produces one while something is playing. The interval and the events
were values in src/playback/cadence.rs and nothing made a report: no position
was ever enqueued, and #57's three open conditions had no subject.

src/playback/report.rs is the report. It takes the queue on every call that
reports and hands back what the queue did, so a report that was not asked of
the queue is not a value it can produce; it carries the position always, which
is 0056's rule about a report with no position reading as the item finished;
and it makes no report between interval ticks, which is the alternative 0057
refuses first.

Four guards were watched failing. Deleting the question of whether a report is
due reddens four cases, which is reporting on every position change. Leaving
the interval where it was after an interval report reddens one. Leaving it
where it was after an event reddens five, the paused case among them. Making a
seek wait for the interval reddens three, and the scrub case is one of them.

No coalescing rule is written here: forty seeks leave one entry because 0047
coalesces at enqueue, and src/server/write_queue.rs is untouched.

Which request carries a report is not decided here. 0010 classifies the paths
for playback started and ended as accumulations, and 0047 does not queue an
accumulation, so how a report occasioned by started or stopped is delivered is
a question between those two records and 0057 that #27's transport meets.

Closes #57.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex closed this Sep 3, 2026
@iderex iderex reopened this Sep 3, 2026
@iderex
iderex merged commit 5e9a9ec into main Sep 3, 2026
51 of 53 checks passed
@iderex
iderex deleted the the-report-through-the-queue-57 branch September 3, 2026 05:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Report progress to the server on a decided cadence

1 participant