laundry: apply cleanup review to the cloud-cycle branch

Four parallel reviews (reuse, simplification, efficiency, altitude). The two
that change behavior:

- observe() could report "changed" on every poll forever, rewriting the
  config entry each time. If both tokens name the same slot with different
  payloads -- a downloaded program with its settings tweaked for one run is
  exactly that shape -- each pass wrote the default's blob then the one-shot's
  over it, so neither was ever already stored. On the SD-card installs this
  integration runs on, sustained entry rewrites are the one cost here that
  bites. The end state is stable, so "changed" is now start-vs-end, not
  per-assignment.
- The write path copied every tracked href to read one rep, walking past the
  accessor added to avoid exactly that. New entity_rep() does the merge for a
  single href; cycle_write drops the resources parameter it never used.

Structure:

- device_resources() is a second accessor giving the pure device view, used
  by diagnostics and the debug read service. That deletes strip_synthetic,
  the _SYNTHETIC_KEY_PREFIX convention and the redact filter added last
  commit: "a dump is what the device said" is now which method you call
  rather than something every future exporter has to remember.
- apply_cloud_courses() is the single mutation path. The flow was reaching
  past the coordinator into the store and relying on a later call to persist
  and invalidate for it; nine names are also now one entry write, not nine.
- option_value/hex_pairs move to capabilities/common.py. The duplicate's
  stated reason -- that the coordinator shouldn't import from
  registry.capabilities -- was simply false; it already does, and so does
  learned.py. The real constraint is narrower: laundry.py imports
  cloudcourse, so the reverse would be a cycle.

Dropped rather than kept:

- The cloud-vs-translated-local-course name check, and catalog.
  translated_state_labels with it. The catalog this process can read is
  English while the dropdown is localized in the frontend, so it rejected
  "Cotton" for a German user seeing "Baumwolle" and missed the real collision
  when they typed "Baumwolle" -- wrong in both directions outside one locale,
  against an outcome option ordering already makes deterministic. The checks
  that survive compare strings that are the same in every locale: the user's
  own names, and the device's personal-course labels.
- stored(), clear()/forget_cloud_courses(), blob(), download_course() -- no
  production callers. stored() was a template artifact whose docstring
  described a caller that cannot exist here.

Diagnostics gains a cloud_courses block, which the store was missing next to
learned_modes -- payloads and which slots are named, but not the names
themselves, since those are the user's words and dumps get pasted publicly.

Kept against one reviewer's advice: option_tokens (two others called
generalizing option_write the right direction) and select._display's
uncatalogued branch, which names a condition the old fallback-is-None proxy
only got right by accident. Deferred: making the store per-subdevice. It is
MAIN-only today and no device seen advertises cloud programs elsewhere; the
limitation is now documented where it is made.
This commit is contained in:
Marc Billow committed 2026-08-10 03:30:23 +00:00
1 parent b921bdbb28
commit 2265c52c77
12 files changed
+220 -247

No files matched your search

+8 -8
View File
@@ -90,8 +90,8 @@ class TestRealDumps:
store = cloudcourse.CloudCourses()
assert store.observe(rep) is True
assert store.blob("55") == SPORTS
assert store.blob("6B") == JEANS
assert store.snapshot()["slots"]["55"]["blob"] == SPORTS
assert store.snapshot()["slots"]["6B"]["blob"] == JEANS
# Learned but unnamed -- nothing is offerable yet.
assert store.named() == {}
assert store.view() == {}
@@ -102,7 +102,7 @@ class TestRealDumps:
store.observe(rep)
assert store.download_candidates() == ["87"]
# A candidate is never used until confirmed.
assert store.download_course() is None
assert store.snapshot()["download_course"] is None
def test_wa55_learns_its_saved_program_but_not_the_sentinel(self):
rep = _load_device("washer_wa55a7700av")["/course/vs/0"]
@@ -110,8 +110,8 @@ class TestRealDumps:
store = cloudcourse.CloudCourses()
store.observe(rep)
assert store.blob("59") == "001C590549164D114A224C2037F0AC22"
assert store.blob("01") is None # the FFFF sentinel's byte 2
assert store.snapshot()["slots"]["59"]["blob"] == "001C590549164D114A224C2037F0AC22"
assert "01" not in store.snapshot()["slots"] # the FFFF sentinel's byte 2
def test_wa55_proposes_no_download_course(self):
"""It is sitting on an ordinary local course with no override
@@ -160,7 +160,7 @@ class TestRealDumps:
for blob in (b06c, b048):
store = cloudcourse.CloudCourses()
store.observe(_rep(["CloudExtraCourse_0A", f"CloudCourse_{blob}"]))
assert store.blob("0A") == blob
assert store.snapshot()["slots"]["0A"]["blob"] == blob
def test_a_sentinel_slot_byte_is_not_the_current_course(self):
"""The two sentinels in the corpus disagree about this -- WA55's byte
@@ -192,14 +192,14 @@ class TestStoreRules:
offers."""
store = cloudcourse.CloudCourses()
store.observe(_rep(["CloudExtraCourse_55", f"OneTimeCloudCourse_{JEANS}"]))
assert store.blob("6B") is None
assert "6B" not in store.snapshot()["slots"]
def test_a_relearned_blob_replaces_the_old_payload(self):
store = cloudcourse.CloudCourses()
store.observe(_rep(["CloudExtraCourse_55", f"CloudCourse_{SPORTS}"]))
rewritten = SPORTS.replace("F005F0AC00", "F005F0AC11")
assert store.observe(_rep(["CloudExtraCourse_55", f"CloudCourse_{rewritten}"])) is True
assert store.blob("55") == rewritten
assert store.snapshot()["slots"]["55"]["blob"] == rewritten
def test_observing_the_same_rep_twice_changes_nothing(self):
store = cloudcourse.CloudCourses()