Eagle light lab · read before the next session

Where Bluetooth actually stands

Written 2026-09-15 at Ray's instruction, after a fourteen-hour lab that ended by overturning its own conclusion. Its job is to stop the next session re-deriving all of this from scratch — and to stop it inheriting the parts that turned out to be wrong.

Ray, 2026-09-15, and it is the reason this page exists

“I don't want to leave here believing we have this locked down and then run in circles again at the next eagle light lab. Some days your ability to adapt on the fly and improvise is a trap.”

He is right, and this lab is the proof. Every section below is tagged by how well it is known. Improvisation is not the failure mode — improvisation that gets written down as fact is. Do not promote a tag without new measurement.

Start here — the first three commands, before any theory

# 1. IS EAGLE RUNNING THE COMMITTED BASELINE? Everything else depends on this answer.
sha256sum /root/eagle-gui-mk3/mk3_ui.py
git --git-dir=gty6-eagle-live.git cat-file -p db676c6:eagle-gui-mk3/mk3_ui.py | sha256sum

# 2. Did the power cycle revert the debugfs value we set on a dead theory?
cat /sys/kernel/debug/bluetooth/hci0/supervision_timeout      # expect stock 42

# 3. Is the wired-LED service up? The MK III LED pane proxies to it and dies without it.
systemctl is-active led-control && curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/

Command 1 is the whole fork in the road. If the hashes match, the scanning story is dead and the cause is somewhere nobody has looked. If they differ, the diff is the bug report. Either way, do not rewrite MK III before running it.

Status board

Proven
The locked transport. btmgmt add-device -a 2 into the controller's accept-list, then busctl for reads/writes. Never Device1.Connect(). Locked by Ray 2026-08-18. Short Whip Driver: 135 s and 12+ consecutive failures → 0.2 s. Unchanged and not in question.
Proven
Six family modules + allied work. Ray's own word, and the retrieval is in gty6-ble-lab.git. allied imports the six unchanged and routes. Per-device parallel dispatch, ThreadPoolExecutor(12).
Proven
The poller is an ingredient, not an extra. Ray: “that poller was the last ingredient that made allied work fast and consistently.” It exists so a click never pays for “is it connected?”. A down device is skipped instantly rather than waited on.
Proven
The MK III baseline contains no scanning. Measured against db676c6, one grep each: bleak, BleakScanner, BleakClient, DETECTORS, .detect(), Device1.Connect, scan — all zero. It calls A.register_all() at :689 and A.start_state_poller() at :691, and imports allied read-only.
Retracted
“MK III bolted scanning on top.” Repeated all evening by two seats as the root cause. False. DO-NOT-REGRESS.md never mentions MK III. It bans a pattern — software scan-then-connect. We attached that ban to a component and nobody checked the component.
Retracted
“MK III is not in git.” It is: gty6-eagle-live.git → eagle-gui-mk3/mk3_ui.py, 36,959 B. Claimed after searching one repo out of 223. A wrong absence-claim travels further than a wrong presence-claim, because nobody re-runs a search that found nothing.
Unmeasured
What Eagle is actually running. Last commit to mk3_ui.py is 2026-09-12; the thrashing is 09-15. Nobody has diffed them. Same trap that hit eagle-uptime-probe.sh the same evening: the file said CONTROL_HOST=10.0.5.12, the running process carried 10.0.5.1.
Unmeasured
The cause of the MK III thrashing. No one has observed MK III's behaviour with an instrument. Every explanation offered on 09-15 was inference. Including the led-control proxy path on :8000, which has never been examined at all.
Unmeasured
supervision_timeout. Set to 400 on a theory that went nowhere; stock is 42. debugfs is runtime state, so a power cycle should have reverted it. “Should” is not measured. Eagle went dark before it could be read back.
Hypothesis
The 45-second blocking acquire is the real cost. send_many() calls ensure() per command, which can block per device. Strongly supported by reading the code and by the vendor-app contrast. Not yet demonstrated against hardware.

The rule this lab produced

State may only be used to fail fast. Never to wait.

allied, device down    "DOWN — skipped, not waiting 45s (kernel still pursuing it)"   FAIL FAST
ensure(), device down   blocks up to 45 s trying to MAKE the state true                 WAIT

Same mechanism, opposite purpose. This is why “remove all state” was the wrong instruction — it would have deleted allied's poller, the one thing Ray identified as making it work. It is also the vendor apps in one sentence: the app never blocks, it fails and you tap again.

Specced fixes

Ordered. Each states its own precondition. None of them are started, and none should be started before command 1 above.

#ChangeWherePrecondition
1Delete the Connected guardeagle_auto.py :107-110none — safe cold
2Take ensure() off the send patheagle_auto.py :103test on 3 whips
3Propagate 1+2 to the other fivecopy-forward only2 proven first
4MK III: nothing—the hash diff first

1 · Delete eagle_auto.py:107-110

The guard exists because “a write to a dropped link reports OK” — true of the old _write. :120-129 already fixed exactly that by parsing output instead of the exit code. The guard now protects nothing and only refuses to send where the app would have sent. A guard that outlived its bug.

2 · Take ensure() off the send path

This is the actual subtraction. Acquisition belongs at establish or in the background, not inside a keypress. A cold device should return “down, try again” in milliseconds — the kernel keeps pursuing it via the accept-list either way.

4 · MK III: do nothing yet

The baseline already meets the spec Ray issued. register_all() at startup, the poller, allied imported read-only, no scanning. There is nothing in it to strip. Rewriting it would destroy a locked baseline and fix nothing.

Three things its own doc marks as must not be tidied out, worth knowing before anyone touches it: register_all() at startup; full state on every SSE reconnect, never a delta (browsers reconnect silently, so a delta-based screen returns looking healthy and showing a stale value); and LED calls as POST, 1-based channel, rgb in the query string — a JSON body returns 200 and silently applies red.

How this lab went wrong — so it isn't repeated

Four failures, one shape. All four were caught by a second person or a negative control. None was caught by the author re-reading their own work.

  1. A ban on a pattern became an accusation against a component. DO-NOT-REGRESS.md bans software scan-then-connect. It never names MK III. We supplied the component ourselves and then cited the document as the source.
  2. An absence claimed from a 0.45% sample. “Not in git” after searching 1 of 223 repos. The honest sentence — “not in gty6-ble-lab.git” — carries its own scope and would have been harmless.
  3. A claim that fit the story got waved through. Twice, both times by the seat that had already read the file. Agreement is when scrutiny is cheapest to skip and costliest to have skipped.
  4. A file was trusted as evidence about a running process. The uptime probe's source said one control host; the live process carried another. The same gap is currently open on MK III itself.