Skip to content

Developer documentation

The rewrite is complete. Every backend line has an owning document, and the three cross-cutting documents that line coverage cannot see โ€” 01, 02, 03 โ€” are written. The previous set of subsystem guides was retired wholesale to docs/retired/dev/, not because it was wrong but because it was built to a standard that rewarded restating the code. What replaced it is written against the anchor system, so a document addresses a region or a rule rather than a file and a line.

Retired docs are still on disk and still readable โ€” in the repo, not on the site. docs/retired/ is excluded from the published build (see mkdocs.yml): it is an archive, it was never in the nav, and building it only surfaced relative links that broke when the files were moved there. They are as-of-their-date records, and several are the only written account of a subsystem's history. Read them for that; do not treat them as current, and do not repair them โ€” that instruction is why the fix was a build exclusion and not eighty-three edits.

โš  If you scope another campaign, read this before you start. The instruction that stood here was "scope the remaining work from the tree, not from the retired file list." It exists because the retired list has holes โ€” nine live modules, 2,780 lines, had no owning document in it, and clean_order/ was the trap: the string appears 23 times across seven retired docs, every one of them the capability flag honors_clean_order, which is a different subject from the package that reads the device's clean sequence.

That instruction is correct and it is not sufficient. Scoping from the tree finds every package โ€” and a document about the system owns no lines, so it cannot be found that way. Three were dropped exactly like that and were only noticed when someone asked. The tell was in the link graph the whole time: the two most-cited missing targets were the two missing documents, because orienting docs are cited most. Scope from the tree AND from what the corpus is cited for.

python scripts/docs_coverage.py            # line coverage, plus the declared cross-cutting list
python -m mkdocs build --strict            # what the corpus is cited for, and what no longer resolves

Start here

doc what it is for
00 โ€” How These Docs Work The standard. Shelves, the acceptance test, citation rules, the release gate. Read before writing anything here.

The registries โ€” rules with a durable identity

These are the addressing layer. Declarations live in source; these files index them.

doc holds
00b โ€” Invariants IN โ€” a rule the program must preserve, each with the consequence of breaking it. Also EN โ€” a rule that binds a person, where no test can ever go red.
00c โ€” Replicas RN โ€” one rule deliberately implemented in more than one place. A green suite cannot see a missing copy.
00b-h โ€” Invariant harvest Working table for rules found but not yet ruled on.
00c-h โ€” Replica harvest The same, for replica sets.
00d โ€” Audit crosswalk Maps audit findings to where they landed.

The notation itself โ€” every class, what each is for, and how to mint one โ€” is specified in design/shipped/notation-anchors.md.

NOW โ€” what the system does today

Code is authoritative. A NOW doc that disagrees with the code is stale, and the fix is the doc.

doc covers
01 โ€” Architecture Overview The map: five layers and which way they point, where a run travels, and the four boundaries that carry the design. Start here.
02 โ€” The Home Assistant Surface What the integration exposes to HA and takes from it โ€” and the eleven outbound events, which have no other owning document.
03 โ€” The Data Model Everything persisted, in one place: two stores with different write shapes, the schema that is mostly undeclared, and the two identifiers that are names.
05 โ€” While a Run Is Live Queue derivation and the refusal ladder, dispatch, brand-conditional room advance, the two stuck detectors, and the mid-run observers.
06 โ€” How a Run Ends Every path by which a run ends, the exactly-once claim, finalization and its commit point, error-second deduction, and which derived stores never self-heal.
11 โ€” A Map's Stored State The 29 services that write a map's stored representation โ€” images and the segment cache, custom segmentation, layout lifecycle, display state, and saved zones.
12 โ€” Where the Map Comes From The provider's own segmentation and pose, normalized into one brand-neutral shape. Backends are declared, never inferred.
13 โ€” How Rooms Are Found The segmenter contract, the shape every engine must return, the shared geometry toolkit, and what survives of boundary derivation.
14 โ€” Live Room Tracking Room identity from the device's own signal; position survives only as a movement delta. Plus the dock-drift log.
15 โ€” The Stall Capture Image The pure renderer behind a stall notification, and why every behaviour in it is an absence behaviour.
16 โ€” The Battery Record Two evidence streams meeting in one record: the sampling guards and their asymmetric reach, charge sessions, the two-regime health proxy, per-job drain, and the twelve sensors.
17 โ€” A Room's Identity What a room is, where identity is minted, how it survives the device renumbering its segments, and the guards on the write path.
18 โ€” The Access Graph Reachability and live-entity rules over the room store: the delta-scoped edit gate, tri-state rule evaluation, and what reaches a user.
19 โ€” The Event Ingress Layer The ten listeners: three subscription models, why nothing is serialized at ingress, and where deduplication actually lives.
20 โ€” Room Profiles The global profile library and the contract that keeps one brand's vocabulary out of another brand's rooms: core owns the keys, the adapter owns every value.
21 โ€” Run Profiles The per-map saved-run library: what a save captures, the four-rung apply ladder, and why applying one writes the queue and not just the rooms.
22 โ€” The Adapter Contract What a brand must declare, what each omission falls back to, and which of those rules actually run for a code adapter.
23 โ€” The Eufy Adapter How the reference brand answers the contract: the five things it computes, the declarations that look like mistakes and are not, and the surfaces that no longer do what they say.
24 โ€” The Roborock Adapter What it cost to be the second brand: the live dock resolution and the three probes it rejects, where the reverse port forced a new name or a change to core, and the two model tables that disagree.
24a โ€” The Dreame Adapter What it cost to be third, and the answer is: very little. The decoded-raster map and the projection replica the go-to affine rides on, a zone that is a service rather than a verb, capabilities that fail closed with one known false, and how to read the method change that ran alongside the port without crediting it to the brand.
25 โ€” The Eufy Segmentor The HSV pipeline that infers rooms from map screenshots: why it exists after the vendor gave us rooms, the two-theme image trick at its centre, and how to find the mis-tuned stage without memorising a threshold.
26 โ€” The Learning Record Store Where learning keeps what it knows: six directories per vacuum, three record kinds, and the tri-state read that stops a torn file becoming a wrong statistic.
27 โ€” What Counts As Learnable The two vocabularies that record the verdict, the three places that can veto a run, and why a cancelled run is not evidence about a room in either direction.
28 โ€” From Records To Statistics The key that decides what counts as the same clean, what a partial clean loses and keeps, and why a renamed room starts from zero.
29 โ€” Prediction and Accuracy The five-pass lookup and why it relaxes cheapest-first, what a relaxed match costs, and the loop that feeds a prediction error back into its next confidence.
30 โ€” External Runs Runs started from the vendor app: capture without identity, the dock grace window, and why one finishes into a pending review rather than a job.
31 โ€” The Setup Layer The declared step machine, the asymmetric drift signal that reopens a finished step, and why a rejected phantom room belongs to one map.
32 โ€” The Store The one persistent document everything writes to: two write paths, a schema that is mostly undeclared, and the guard that stops a failed setup writing an empty dict over everything.
33 โ€” The Orchestrator Fifteen subsystems and the three that do not need the manager, what a restart loses, and the migration loop that must never write a brand word.
34 โ€” Capability Detection The two kinds of adapter hint and why confusing them shipped a defect, and the vocabulary that records how a role was resolved rather than only what won.
35 โ€” The Fault Tracker Three latches with three lifetimes, the two-phase handoff that survives a failed save, and where Home Assistant stops speaking and the brand starts.
36 โ€” The Service Layer Eighty services across sixteen domains: why a write refuses where a read answers, and why service names are never translated but their failures are.
37 โ€” The Entity Surface What works without the card: six platforms, a unique id that may never be parsed, and why cleanup is the complement of what was built.
38 โ€” The Theme Library One subtree owned outright, why deleting a built-in theme needs a tombstone, and which tags are stored versus derived from the palette.
39 โ€” The Integration Entry Point The four functions HA calls, the cold-start race that makes setup run twice, and the ruling that removing the integration keeps your learning tree.
40 โ€” Diagnostics and Evidence Four layered ways to ask what actually happened: the read-only support dump, the silent log ring, the record that makes invisible branches visible, and the receipt protocol where both ends assert the edge.
41 โ€” Maintenance and the Dock Why the framework keeps a bookmark rather than a counter, what each of the two clamps defends against, and the gate that asked the dispatched question about the floor.
42 โ€” The Send Side The last mile: ids re-resolved at dispatch, why a mixed batch takes the safest water, and the safety abort that could never fire.
43 โ€” Observing a Run Without Geometry Counter plateaus instead of coordinates, the pose ring that outlives the job, and a module that deliberately decides nothing because a wrong rule is worse than the bug.
44 โ€” Onboarding and First Run Installed to usable, where nothing blocks: an optional vacuum picker, completeness computed rather than stepped, and a sidebar title the user owns.
45 โ€” The Shared Layer The four modules everything imports: a constants file that re-exports a brand, ensure-versus-require arrived at four times, and how much of the first data model is still here.

The rest of the NOW shelf is unwritten. Until a subsystem is rewritten here, its retired guide in docs/retired/dev/ is the only account there is.

DESIGN โ€” how we want it to work

Either side may be wrong; when a design doc and the code disagree, adjudicate rather than assuming the doc is stale.

HISTORY โ€” what we stopped doing

Outside the shelves

  • frontend/ โ€” the card. Its own hub, its own index.
  • reference/ โ€” generated. Never hand-edit; regenerate with python scripts/check_generated_docs.py --fix. GENERATION_MAP โ€” who generates what, and what to edit instead ยท EVENTS ยท THEME_TOKEN_MAP ยท THEME_TOKEN_USAGE ยท ai-theme-authoring
  • deltas/ โ€” open deltas, tracked against live behaviour.
  • maintenance/ โ€” dated audit records, excluded from the citation and index gates by rule.

Gates

Documentation is a release gate, not a per-push one.

command checks
python scripts/check_doc_citations.py every ::symbol resolves; flags surviving line citations
python scripts/check_generated_docs.py generated docs match their generator
python scripts/check_docs_index.py every doc is reachable from an index
python scripts/doc_anchor.py --check anchor identity: duplicate, broken, moved, malformed
mkdocs build --strict links only โ€” it has passed clean through eleven false sentences and can never be the freshness gate

Naming a file in backticks does not make it reachable. That is how the whole of design/ once went missing from the corpus.