Skip to content

Hold 0116's rule per kind, and the window no listener may lengthen - #261

Merged
iderex merged 1 commit into
mainfrom
the-rule-per-kind-and-the-window-no-listener-lengthens-116
Aug 31, 2026
Merged

Hold 0116's rule per kind, and the window no listener may lengthen#261
iderex merged 1 commit into
mainfrom
the-rule-per-kind-and-the-window-no-listener-lengthens-116

Conversation

@iderex

@iderex iderex commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

The issue this belongs to

#116. This does not close it, and the reason is at the end.

What changed

src/cache/notification.rs holds the part of 0116 a comparison of identifiers
and a match on a kind settle. It is the second of the three ways 0006 says an
entry stops being trusted; the third, the passage of time, is already in
src/cache/freshness.rs.

what_a_notification_does answers for one entry and one message. An item the
message names as removed or updated is invalidated, because what is held is
wrong rather than old and 0043 answers a later read with Absent. An item named
only as added moves nothing. Every library query result is shortened to zero on a
library change whether or not the message named an item, because the entry is
keyed by a digest over the request and a digest cannot be asked whether the
answer under it contained a given item. Artwork bytes, decoded dimensions and
capability answers have no rule under either message. A per-account message
invalidates the item entries it names and reaches nothing keyed under another
account.

WhatAListenerDoesToAThreshold is 0116's degradation rule written as a type. It
has one variant, and what_a_listener_does_to_a_threshold is total over
ListenerState::all() and answers the same way for all five states, including
the one anybody would be tempted to reward.

One answer is a reading rather than a sentence in 0116, and it is written as one
with its argument on the function that makes it: a per-account message leaves a
library query result where the table put it.

What failure it prevents

Three, and each is named in #116 or in 0116 as the thing that goes wrong.

A tile wall that empties and refills while a library scan runs. Invalidating
every query result is the more obviously correct reading of "the answer may have
changed", it is what gets written when nobody has decided, and during a scan the
server batches a notification once per its own configured interval, so what a
person watches is the screen emptying repeatedly.

A cache that is confidently wrong for as long as a lengthened window lasts. A
core being told about every change can hold a library list for a day instead of
five minutes and the hit rate improves visibly; a listener that is connected and
silent for a reason nobody noticed then serves that day out of a stale cache, and
silent is exactly what a listener is when something has gone wrong with it.

One person on a shared device moving another person's entries. 0041 keys per
account and the per-account message carries the account it is about, so a rule
that read the identifiers and not the account would cross that key.

None of the three has happened here: there is no listener in this tree to have
got any of them wrong yet, which is the point of deciding before the caller
exists.

Evidence

Every command run at bdd08fa52efea9a5289fedd1a2050d714ec73161, the head being
pushed.

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

cargo test --locked 2>&1 | grep -c '^test .* ok$'
589

cargo test --locked 2>&1 | grep -c '^test result: FAILED'
0

cargo test --locked --lib cache::notification 2>&1 | grep -E '^test result'
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 429 filtered out; finished in 0.00s

The legs of the gate this machine can run, each by its own script and each by its
own exit code:

for s in format invariants doc-paths lint test excluded-targets targets; do
  bash .github/$s/$s.sh check >/dev/null 2>&1; echo "$s exit=$?"
done
format exit=0
invariants exit=0
doc-paths exit=0
lint exit=0
test exit=0
excluded-targets exit=0
targets exit=0

cargo clippy --all-targets -- -D warnings -D clippy::all -D clippy::pedantic -D clippy::cargo 2>&1 | grep -cE '^(error|warning)'
0

What a guard here refuses, and the proof it bites

A query result is shortened and never invalidated. The violation is the one
line somebody writes when they read "the answer may have changed":

-            EntryKind::LibraryQueryResults => WhatTheNotificationDoes::ShortenedToZero,
+            EntryKind::LibraryQueryResults => WhatTheNotificationDoes::Invalidated,

cargo test --locked --lib cache::notification 2>&1 | grep -E '^    cache|^test result'
    cache::notification::tests::a_library_change_shortens_every_query_result_named_or_not
    cache::notification::tests::a_query_result_carrying_an_item_is_still_shortened_and_never_invalidated
test result: FAILED. 10 passed; 2 failed; 0 ignored; 0 measured; 429 filtered out; finished in 0.00s

A per-account message reaches only its own account. The violation is the
account test dropped, which is exactly what a rule written from the identifier
list alone looks like:

(in what_a_notification_does, the UserDataChanged arm)
-            if account != the_sessions_account {
-                return WhatTheNotificationDoes::Untouched;
-            }

cargo test --locked --lib cache::notification 2>&1 | grep -E '^    cache|^test result'
    cache::notification::tests::a_per_account_change_about_another_account_reaches_nothing
test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 429 filtered out; finished in 0.00s

No state of a listener lengthens a window, and this guard has two layers, so
the violation has to defeat both.
The type is the first: with one variant there
is nothing to return that means "kept longer", so the change has to widen the
enum before it can be written. Widened and written, the run refuses it:

(a second variant on WhatAListenerDoesToAThreshold, and)
-    _state: ListenerState,
-) -> WhatAListenerDoesToAThreshold {
-    WhatAListenerDoesToAThreshold::Nothing
+    state: ListenerState,
+) -> WhatAListenerDoesToAThreshold {
+    match state {
+        ListenerState::Connected => WhatAListenerDoesToAThreshold::LengthenedWhileConnected,
+        _ => WhatAListenerDoesToAThreshold::Nothing,
+    }

cargo test --locked --lib cache::notification 2>&1 | grep -E 'moved a threshold|^    cache|^test result'
assertion `left == right` failed: connected moved a threshold, and 0116 says no state does
    cache::notification::tests::every_kind_keeps_the_table_threshold_in_every_listener_state
    cache::notification::tests::no_state_of_a_listener_changes_a_threshold
test result: FAILED. 10 passed; 2 failed; 0 ignored; 0 measured; 429 filtered out; finished in 0.00s

A second variant added and never returned reddens nothing, which was run and is
stated here rather than left for a reader to assume: the type refuses the value,
the cases refuse the use, and neither covers the other.

Each violation above was applied to the working tree, run, and reverted. With all
three restored:

cargo test --locked --lib cache::notification 2>&1 | grep -E '^test result'
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 429 filtered out; finished in 0.00s

What this does not cover

#116's own conditions are untouched and this does not close it. They ask for
a test that makes a change on the fake server and proves a cached read reflects
it, and a test that breaks the mechanism without closing it cleanly. Both drive a
connection, nothing in this tree opens one, and that is #27. The reading already
on the issue about whether the fake server reaches an established upgrade is not
restated in this body and nothing here answers it.

No listener, no reconnection and no delay. Nothing here opens, reads, closes
or counts a connection, and there is no schedule of its own, which is 0116's
decision rather than a gap: 0038 bounds a request's attempts and 0045 is the
recovery schedule for a server that is gone.

No bound as a number. #116 asks that a change be reflected within a stated
bound and 0116 answers that the core cannot state one, because the server's own
batching sits ahead of it on an operator's setting. Nothing here claims a number.

Nothing applies any of this to a cache. The answers are values a read path
will act on, and no read path exists: nothing in this tree fetches bytes out of a
store and hands them anywhere.

The coverage leg was not judged on this machine. Its instrumentation reads
zero lines for every area here, on this branch and on an unmodified checkout of
the default branch alike, so the number is absent rather than low.
.github/workflows/coverage.yml runs on this pull request and is what judges it.
The pinned-surface register carries the new module with its reason.

Three other legs were not run here. headless needs a program this machine
does not carry (ip: command not found), thread-detector needs a nightly
compiler this tree deliberately does not pin, and mutation is weekly rather
than a pull-request leg. None was skipped by editing anything; they were not run.

Who has read it

Nobody but me has read this change. There is no second reader on this board
tonight, and the evidence above stands in place of one rather than the question
being left open.

0116 decides what a change the server reported does to what is cached: an item
the message names as removed or updated is invalidated, every library query
result is shortened to zero rather than invalidated, artwork, dimensions and
capability answers have no rule, a per-account message reaches only the account
it names, and no state of a listener ever lengthens a freshness window. None of
that was in the tree. `src/cache/notification.rs` holds the part a comparison of
identifiers and a match on a kind settle.

The shortening is the half that is otherwise got wrong in the expensive
direction. A query result is keyed by a digest over the request, a digest cannot
be asked whether the answer under it contained a given item, so invalidating
every one of them on a notification empties the tile wall once per the server's
batching interval for the whole of a library scan. Shortening serves the entry
at once, marks it stale with its age, and lets the screen correct itself.

The degradation rule is a type rather than a sentence.
`WhatAListenerDoesToAThreshold` has one variant, so a read path that wanted to
hold a library list for a day because a connection is up has to add a second
one, which is a change to 0116 rather than a line somebody writes while a cache
hit rate is being looked at. What that prevents is the failure #116 names: a
listener that is connected and silent for a reason nobody noticed, producing a
cache that is confidently wrong for as long as the lengthened window lasts.

The account test prevents the other one. 0041 keys the cache per account, and a
rule reading the identifiers without the account lets one person on a shared
device move another person's entries.

One answer here is a reading rather than a sentence in 0116, and it says so
where it is made: a per-account message leaves a library query result where the
table put it, because a position report is a per-account change on 0057's
cadence and a rule shortening every query result on each of them would leave the
tile wall permanently stale while anything is playing.

#116 stays open. Both its conditions drive the fake server - one makes a change
on it, the other breaks a connection without closing it cleanly - and nothing in
this tree opens a connection, which is #27.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added architecture Shape of the core: boundaries, interfaces, contracts performance The speed budget and what measures it labels Aug 31, 2026
@iderex iderex self-assigned this Aug 31, 2026
@iderex
iderex merged commit 886dbcd into main Aug 31, 2026
24 checks passed
@iderex
iderex deleted the the-rule-per-kind-and-the-window-no-listener-lengthens-116 branch August 31, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

architecture Shape of the core: boundaries, interfaces, contracts performance The speed budget and what measures it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant