Files
NODEDC_MISSION_CORE/docs/audits/2026-09-06-k1-bridge-connection-incident.md
T

8.2 KiB

K1 Bridge: provisioning recovery and first-scan discovery

Scope: the operator-local K1 connection in LAB on canonical Core 8000. The onboard installation is a separate pending acceptance task.

The later firmware callback audit explains the two fresh 19:22/19:23 UTC failures and supersedes the earlier generic ATT interpretation below with a bounded FW 3.0.2 station diagnosis.

Evidence and diagnosis

Three explicit Bridge attempts returned HTTP 502 on 2026-09-06 at 18:04:50, 18:05:13 and 18:05:43 UTC. All failed at gatt-write with BleakGATTProtocolError, ATT 4 INVALID_PDU. Each attempted one 99-byte write-with-response; acknowledgement and a joined Wi-Fi address were absent. The characteristic advertised read, write; the observed write-without-response capacity was 253 bytes. This capacity alone does not prove a successful ATT write.

A separately admitted, exact-target Bridge connection.verify at 18:15 UTC read K1 state without a provisioning write or host Wi-Fi switch. K1 answered, but supplied no shared-LAN address (connection-verify-address-unavailable). A preceding request was rejected at input validation because of the diagnostic operation identifier format; it performed no device I/O.

The same ATT error exists in August's private logs. The accepted August Bridge record uses the same Bleak 3.0.2, write mode, frame length and capacity. Successful historical writes also include an empty network baseline, so an empty baseline does not justify adding an AP-enable command or rejecting Bridge in advance. The exact reason for the peripheral's rejection remains unproved; a wrong Wi-Fi password, frame-format regression or MTU failure must not be asserted from this error alone. No alternate transport mode, frame, retry, START or STOP was sent.

Private evidence is retained under the ignored incident directory, with UTC and monotonic timestamps, redacted events and SHA-256 artifact index. Credentials and raw packet contents are absent from this report and source changes.

Software causes and refactor

  • The Connect hook had three overlapping result/error branches. Its failed HTTP path accepted the server snapshot but returned observedState: null to the form; the form therefore lost the exact operation and its ATT diagnosis.
  • A completed Bluetooth search suppressed the recovery surface even after the subsequent, click-owned Connect failed. The disabled credential form instead promised a future safe continuation despite there being no running operation.
  • Recovery guidance claimed an automatic recovery or a completed UI reset that had not occurred. The lead status fell back to idle despite a terminal failure.
  • Durable JSON logging omitted the write-mode, frame-size and GATT-property fields already supplied by the BLE implementation.

networkProvisioning.ts now owns the single submission, bounded observation of that exact idempotency key, monotonic snapshot selection and reviewed failure copy. An existing operation is only observed, never resubmitted. The runtime hook retains exact applied-network/control-authority checks and React action ownership. The form renders the returned operation and allows explicit recovery after its failed attempt even when its preceding search is complete. It still clears the credential immediately upon submission.

The frozen BLE frames and canonical MQTT dialogue are unchanged. Ops reference: MISSIONCOR-3, “Mission Core. Lixel K1 / XGRIDS Integration”, and docs/lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md. That iPhone capture begins at the IP stack and does not contain Bluetooth HCI. The reviewed BLE profile remains docs/04_K1_WIFI_PROVISIONING_PROFILE.md.

Validation and remaining acceptance

Focused frontend: 227 passed, including new behavioral cases for HTTP 502, existing-key non-replay, lost-response success, pending settlement, stale REST versus newer WebSocket, superseded intent and unrelated journal rows. The rendered regression covers completed search followed by failed Connect and requires enabled recovery choices instead of the trapped form.

Full frontend suite: 772 passed sequentially; TypeScript and final production build passed. Application architecture: 4 passed.

BLE and persistent-diagnostic tests: 34 passed. Canonical guardrail: four immutable LixelGO captures verified in their original checkout, frozen protocol/Bridge contour unchanged, 32 synthetic sentinels passed. The convenience script initially failed because raw captures are deliberately absent from the active worktree; its original-location integrity check passed without copying or changing the captures. Ruff and diff checks passed.

A later runtime journal records an explicit successful Bridge write at 18:42 UTC, followed by application-control acceptance, canonical START and the first point frame, then a confirmed STOP at 18:44 UTC. These actions occurred while the assistant was editing/testing discovery; they were not dispatched by this investigation. The success followed the recovery-UI refactor and preceded the new discovery implementation. It proves a subsequent successful physical connection, not the cause or permanent resolution of the intermittent ATT error. Do not automatically replay the earlier failed attempts.

First-scan miss

The operator reported that an already active K1 is absent from the first search and appears on the second. This was reproduced on the original six-second implementation: 18:32 UTC returned four devices and zero K1 candidates; a second explicit scan at 18:35 UTC returned five devices including the expected K1. These observations do not distinguish radio advertisement latency from macOS state and do not establish that the camera was powered off.

Discovery now opens one native Bleak scanner context. It listens for an initial six-second window and, if no K1 name has appeared, continues the same context up to a twenty-second bound, stopping on a later K1 candidate. There is no hidden second scan, GATT connection, provisioning retry or cache-based candidate promotion. Name matching only ends discovery; compatibility and connection still require their separate evidence. The owner arbiter, generation revocation, native-handle capture and cancellation cleanup remain in place. A caller's explicit shorter duration is respected.

The shared frontend request/countdown, backend default and Node Bridge source use the twenty-second bound. Completed operations and private structured logs now include scanner startup, total elapsed time, first-candidate time and whether the initial window was extended. They contain no Wi-Fi credentials.

After the new canonical process started, the first explicit UI search at 18:51 UTC found the expected K1. Native startup took 766 ms, first K1 detection 2023 ms from scanner construction, and total discovery 6768 ms. Extension was not needed. The browser showed the fresh candidate and the arbiter returned idle. This is one successful process-restart test; the browser cache was not cleared. The operator requested a separate test after browser-cache clearing. That acceptance is pending: the available in-app browser automation exposes no cache clearing capability, and the clear-browsing-data keyboard shortcut had no effect. The operator was asked to clear it; no cache clearing is claimed.

Additional discovery validation: 73 backend tests passed (scanner, owner arbiter, persistent diagnostics and Node Bridge), seven facade discovery tests passed, 138 focused frontend tests passed, and architecture checks passed. TypeScript and production build passed. Final focused discovery checks, Ruff, whitespace and the frozen protocol/Bridge comparison passed. The previously completed full 772-test frontend pass applies to the recovery refactor; only affected suites were rerun for the subsequent discovery change.

Canonical Core remains on port 8000; no listener exists on 8765. Onboard source is synchronized separately; this investigation does not install or rebuild the pending Node package.

Ops publication

The existing Ops canon was read. Automatic approval review rejected the attempted card update because consultation was authorized but publication of internal technical details was not. No Ops card was changed; this local report is the reviewable result pending explicit publication authorization.