Files
NODEDC_MISSION_CORE/docs/audits/2026-09-06-k1-bridge-connection-incident.md
T
DCCONSTRUCTIONS 1c7dd29d8a chore(node): preserve pre-canonicalization experiment snapshot
Historical working copy retained for audit before consolidation into main. The canonicalized plugin architecture and later fixes already live in main; this snapshot is not a release or a request to restore obsolete source layout.
2026-09-21 08:45:34 +03:00

141 lines
8.2 KiB
Markdown

# 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](2026-09-06-k1-station-reply-semantics.md)
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.