# K1 connection-path audit and R15 ## Observed failure sequence The owner reported four failed enrollment results between 19:08 and 19:11 MSK on 2026-09-07. The board is running the matched R14 pair: Node 0.8.11 and K1 0.1.11. The K1 service entered active state at 19:06:33 MSK with zero restarts. This is not an old-package or service-restart explanation. | Journal time (MSK) | Recorded failure boundary | Device Wi-Fi write | | --- | --- | --- | | 19:08:36 | `BleakDBusError`, `ensure_device_for_gatt → find_device_by_address → active_scan:477 → assert_reply` | Not reached | | 19:08:51 | `NetworkProvisioningConflict`, exact capture admission at `facade.connect:11127` | Not reached | | 19:09:51 | Same nested discovery boundary as 19:08:36 | Not reached | | 19:10:41 | Same revoked-capture admission as 19:08:51 | Not reached | The first and third failures occur before `BleakClient.__aenter__`, service validation, baseline read and the journalled Wi-Fi write. The second and fourth occur even earlier. A wrong Wi-Fi password cannot cause these four failures; this does not prove the supplied credentials correct, because K1 never received them in these calls. No credential or raw exception payload is copied here. R14 held a `BleakScanner` open while its nested resolver started a second scanner. Bleak 3.0.2 uses one BlueZ manager/D-Bus client per loop. BlueZ permits one discovery session per client and adapter, not one per Python scanner. See the [BlueZ Adapter API](https://github.com/bluez/bluez/blob/master/doc/org.bluez.Adapter.rst). The retained path was absent, so these calls took that nested branch. The Wi-Fi function then revoked the exact selected capture in `finally` simply because no GATT baseline had completed. Discovery failure was thus incorrectly treated as a failed GATT exchange. The UI classified only `connect-failed` as a Bluetooth failure, leaving the form usable after `resolution-failed`. The next explicit Apply met the already-revoked capture and produced the misleading expired-search message. This is explicit invalidation, not a form timeout or a Wi-Fi refusal. The old journal formatter omitted structured `extra` fields and native D-Bus codes. The exact native code of the historical calls was not retained. A test using the installed Bleak manager and a synthetic D-Bus wire enforcing BlueZ's documented single-session rule reproduces R14's exact call chain with `org.bluez.Error.InProgress`. This is reproduction evidence, not a claim that the missing historical code was recovered. The bounded Bluetooth service journal contains no additional entries for this interval. Protected stores were not bypassed to obtain more diagnostics. ## Path ownership review | Path | Owner and final authority | Result of review | | --- | --- | --- | | Search / select in enrollment | UI selects one row from runtime + discovery + mode revision; selecting a row performs no physical connection | Retained; no automatic Apply or scan added | | UI → Core Fleet → Node Go broker | One operation ID, durable delivery journal, deadline, exact node/runtime binding; lost response observes the same ID | Retained; delivery completion does not mean device connected | | Node bridge → plugin facade | One bridge lock; plugin journal, lifecycle/dispatch gates and OS BLE arbiter | Retained; no second writer or recovery loop introduced | | Fresh / retained / durable BLE identity | Exact address/native path, owner epoch and scan/session lineage; these identities have distinct admission purposes | Retained; age alone does not revoke a form selection | | Native path resolution → Connect | Previously separate holder plus nested resolver | Replaced by one `selected_device_discovery` context; exact observations come from its own scanner; held through Connect and released before GATT I/O | | Station provisioning / status read / AP activation | Different reviewed payloads and policies, same native connection lifecycle | All three use that single discovery context; one Connect, no automatic write repeat | | Failure / cancellation cleanup | Transport lease, native callbacks and command journal have separate ownership | Removed duplicate revocation in exception and finally paths; only an attempted Connect/GATT exchange without baseline revokes a capture | | Verify in enrollment / Verify in device detail | Facade policy, ledger lineage, exact persisted target, route, DeviceInfo and physical reconciliation | Removed the adapter-level divergence: both use the same `verification_parameters`; another selected device cannot inherit the saved target | | Host Wi-Fi network list | Linux host network manager, read-only listing | Separate from BLE discovery; not a second K1 scanner | | Wi-Fi response → control readiness | Same parent intent and owned DeviceInfo bootstrap; network-applied and control-ready are distinct | Retained; missing control confirmation never authorizes resending Wi-Fi | | START / STOP / preview | Named acquisition, physical command journal, active session and persistent preview recording | Existing R13/R14 behavior retained; this change issues no physical test commands | The simplification removes overlapping native discovery, duplicate failure cleanup, divergent Node verification selection and duplicated password markup. It does not combine different physical modes into a guessed fallback or erase their side-effect/identity fences. ## Operator presentation and diagnostics - Both direct and onboard enrollment consume `K1WifiPasswordField`, extracted from the existing direct composition using Design Guideline `TextField`, `IconButton` and eye icons. Visibility resets when the value is cleared. Passwords remain absent from command journals and are cleared after submission. - Search busy, unavailable Bluetooth, missing selected K1, and other discovery failures have distinct shared messages. They state that Wi-Fi was not sent only with `side_effect_status=none`. Unknown/after-write outcomes keep the existing read-only reconciliation path. - Legacy resolution errors are recognized as Bluetooth failures so an old result cannot leave the same invalid form open for another Apply. - Node failure logs now print a finite allowlist of stage, native BlueZ error and write-attempt/confirmation booleans alongside the existing causal stack. Exception messages, SSID, password, frame data and locals are excluded. ## Verification and delivery The regression first failed on unmodified R14 with the same nested stack, then passed after replacing the resolver. The test uses the real pinned Bleak BlueZ manager/scanner and dbus-fast with a synthetic wire; no adapter is accessed. Tests cover cached/missing paths, timeout, busy reply, cancellation, connection failure and callback/session release for all three BLE operation kinds. Other tests cover exact address/adapter, owner and capture changes, write boundary, single submission, shared Verify admission, and password visibility. The dev dependency explicitly includes the same pinned dbus-fast version so this contract is also tested on macOS. Linux runtime dependencies are unchanged. Detailed final check counts and artifact provenance are recorded after the release checks. R15 is Node 0.8.12 plus K1 0.1.12 and uses the existing encrypted onboard application credential. Installation and physical UI acceptance are separate from synthetic checks; neither is claimed from a successful build. ### Source acceptance - Backend connection/arbiter/journal suite: 189 passed. - Installer and package lifecycle tests: 14 passed. - Complete Control Station unit suite: 808 passed, including direct K1 recovery, frontend boundaries and shared password visibility. - Node UI boundary test: 1 passed. - Node Go enrollment tests passed. - Control Station production build and TypeScript check passed (8.00 s). - Ruff and Git whitespace checks passed. The board was online and published idle acquisition before staging. The local Docker daemon was not running; no Docker startup or parallel heavy builds were used. The original canonical Core process remained on port 8000. ### Built and staged R15 Source commit: `762d77ef95ccd763b7e3a0d62e04464a4f9289c1`. Node production build/typecheck passed. Both packages were inspected: no archive links, unsafe paths or private application-key member; matching source provenance. K1 public provenance has `application_material_included=false`, and every payload hash listed in its provenance matched. Node retains the admitted Design Guideline commit `5b882bc3d9b13a86e6c26111ef5ded687ea3a2fc`. Payload regular-file counts are 71 for Node and 150 for K1. - `mission-core-node_0.8.12_amd64.deb`: 109838058 bytes; SHA-256 `b5ee0471e3dc61f5533e5e4e80b3b98a6afc73aa964f7a74fbb20d1df58ef44f`. - `mission-core-xgrids-k1_0.1.12_amd64.deb`: 318115004 bytes; SHA-256 `9b382258d358f8cabf5b75e4f46d3475b83856ec604a389b10d0a575ea85cd27`. The unique R15 directory is staged on the authorized board. Both remote SHA-256 checks passed. Apt simulation upgrades exactly Node 0.8.11 → 0.8.12 and K1 0.1.11 → 0.1.12, with zero additions/removals. Existing pre-install safe-state guards are unchanged. No credential extraction or replacement is included. The native “Mission Core · K1 R15” installer is open for owner-entered sudo. Actual installation status and physical acceptance remain pending. The owner should test through the regular UI with Chrome cache cleared: fresh scan/select, password visibility, one Apply, named start, stop, and Verify after returning to the device. No physical CLI probe or scanner command was issued by the agent. CUA exposes no documented cache/click controls in this session, so clean-cache visual/hardware acceptance is not inferred from unit or source checks. Final Core health returned HTTP 200 and `ok=true`. Port 8000 is still served by the original PID 88424; its HTML references the newly built `app-ClyCkvea.js` and `app-DD9yVBC_.css`. No service on port 8765 was started.