diff --git a/apps/control-station/test/devicePluginContracts.test.mjs b/apps/control-station/test/devicePluginContracts.test.mjs index 5dde64b..f815cb4 100644 --- a/apps/control-station/test/devicePluginContracts.test.mjs +++ b/apps/control-station/test/devicePluginContracts.test.mjs @@ -1991,7 +1991,7 @@ test("observed SCANNING recovery is a successful STOP-only connection outcome", ); assert.ok( acquisitionSource.indexOf("terminalPhysicalStopObserved ? (") - < acquisitionSource.indexOf("
"), + < acquisitionSource.indexOf("renderToStaticMarkup(createElement(Detail,{de test('manual K1 surface admits one START only after current control proof',()=>{ const markup=render(device); assert.match(markup,/Инициировать запуск/); + assert.match(markup,/]*disabled=""[^>]*>Инициировать запуск<\/button>/); + for(const label of ['Тип установки / носитель','Режим GNSS','Название проекта'])assert.ok(markup.includes(label)); assert.match(markup,/Настройки устройства/); assert.doesNotMatch(markup,/Остановить устройство|Обновить просмотр|sensor-live-layout/); const waiting={...device,online:false,verified:false,control:{...device.control,can_start:false,network_applied:true,reason_code:'application_authority_unavailable'}}; @@ -36,7 +38,7 @@ test('active acquisition exposes STOP and Rerun without another START',()=>{ const markup=render(active); assert.match(markup,/Остановить устройство/); assert.match(markup,/rerun-viewport__canvas/); - assert.match(markup,/>СтатусСтатусПространственная сцена{ + for(const phase of ['connecting','network_applied']){ + assert.equal(api.enrollmentNotice({...initial,connection_attempt:{ + schema_version:'missioncore.xgrids-k1-connection-attempt/v1',attempt_id:'test',status:'running',phase, + }}),''); + } +}); diff --git a/apps/node-agent/packaging/build_deb.py b/apps/node-agent/packaging/build_deb.py index 9182641..dad76f0 100644 --- a/apps/node-agent/packaging/build_deb.py +++ b/apps/node-agent/packaging/build_deb.py @@ -11,7 +11,7 @@ import sys ROOT = Path(__file__).resolve().parents[1] -VERSION = "0.8.10" +VERSION = "0.8.11" sys.path.insert(0, str(ROOT.parents[1] / "scripts/packaging")) from debian import package diff --git a/docs/audits/2026-09-07-k1-enrollment-recheck-and-launch-r14.md b/docs/audits/2026-09-07-k1-enrollment-recheck-and-launch-r14.md new file mode 100644 index 0000000..1f1bb06 --- /dev/null +++ b/docs/audits/2026-09-07-k1-enrollment-recheck-and-launch-r14.md @@ -0,0 +1,74 @@ +# K1 R14: enrollment failure, recheck and launch controls + +## Observed failures + +The owner exercised installed R13 through the UI. Camera, cloud and a 2.47 m +trajectory were visible, and STOP was accepted. This supports that bounded run; +it is not acceptance of arbitrary network outages or long acquisitions. + +The first provisioning failure at 18:03:32 MSK terminated in the installed +Bleak BlueZ manager `_check_device`, line 217, called by `add_device_watcher` +while `BleakClient.connect` was opening its D-Bus connection. The exact selected +device path was absent from the manager. The installed code was inspected to +confirm the branch. This precedes the physical Connect request, GATT baseline +and Wi-Fi write; an incorrect Wi-Fi password did not cause this failure. + +The preflight cache check and client entry had an asynchronous gap. Discovery +was already stopped, so the checked path could disappear in that gap. BlueZ +cache cleanup is the implementation mechanism addressed here; the bounded +daemon journal did not record a reason for removing this particular path. +The second owner search/provisioning succeeded. No password, raw payload or +private device evidence is included in this report. + +At 18:09:40 MSK, post-STOP Verify failed in `ensure_device_for_gatt` while +requiring rediscovery of the exact selected Bluetooth transport. At 18:09:52, +the next Verify had no exact BLE device available. These are discovery failures, +not a rejected Wi-Fi password. The Node detail adapter used legacy server-target +resolution, which prioritizes fresh/retained BLE even when an admitted durable +network target is available. + +## Changes + +- Hold one BlueZ discovery reference from the selected-path preflight until + the one Bleak client connection completes. Release it before characteristic + reads/writes, including failure and cancellation. The existing exact address, + adapter, capture, owner and GATT checks remain. No Connect or Wi-Fi-write retry + is introduced. The CoreBluetooth path is unchanged. +- Node Verify requests the exact persisted Bridge target only when the existing + server policy admits it without mandatory live GATT validation. The facade + continues to verify ledger lineage, route, DeviceInfo, and physical recovery + requirements. All other cases retain the existing verification path. +- Failed verification has a direct action to reopen wireless enrollment. The + separate Status card is removed; actionable failure stays in the device card. +- Enrollment pending feedback stays in its button. The full-width primary + configuration button now also has a full-width footer action container. +- The camera notice is bounded by a relative media-content container. It no + longer covers the shared floating window's draggable header and actions. +- Direct and onboard launch consume shared `K1AcquisitionFields`: existing + installation/GNSS options and project validation. Empty/invalid names disable + START. Node validates all three values before workspace/project commands and + passes the operator name to `acquisition.prepare`; a mismatched prepared + project cannot be started. Admitted options remain handheld and no RTK. + +## Validation and delivery boundary + +Control Station tests: 805/805 passed, including architecture and direct K1 +recovery regressions. Native/Node/Wi-Fi/installer tests: 93/93 passed; the final +START subset passed 29/29. They cover the BlueZ cache gap, exact target/adapter, +cancellation cleanup, at most one GATT connection/write, persisted verification +admission, invalid launch drafts and propagation of the operator project name. +Ruff and whitespace checks passed. The final Control Station production build +(including typecheck) and Node UI boundary test passed. Tests use synthetic +fixtures, without commands +to the physical scanner. + +The canonical endpoint remains 8000. CUA exposes no documented click/cache +controls; no clean-cache hardware acceptance is claimed from source checks or +an in-app page load. Owner acceptance must confirm first-attempt enrollment, +post-STOP Verify, full-width configuration, draggable camera while waiting/live, +and a named acquisition through the existing UI. + +R14 is a public matched update: Node 0.8.11 and K1 0.1.11. It uses the existing +encrypted onboard credential. No Keychain export, new secret package, physical +CLI probe, Git push or Ops retry is part of this change. Production build, +artifact identity and installation outcomes are recorded when completed. diff --git a/packages/sensor-ui/src/SensorWorkspace.tsx b/packages/sensor-ui/src/SensorWorkspace.tsx index 114b556..1aff4d3 100644 --- a/packages/sensor-ui/src/SensorWorkspace.tsx +++ b/packages/sensor-ui/src/SensorWorkspace.tsx @@ -35,7 +35,7 @@ export function SensorWorkspace({transport,enabled=true,onDetailChange,createRer const editingCurrent=inventory?.items.find(v=>v.id===editing?.id); useEffect(()=>{onDetailChange?.(!!device);},[!!device,onDetailChange]); const Detail=device?(sensorContribution(contributions,device)?.Detail??(device.kind?null:SensorDetail)):null; - return
{device?Detail?setSelected(null)} refresh={refresh} failure={failure} createRerunHost={createRerunHost}/>:
:<> + return
{device?Detail?setSelected(null)} refresh={refresh} failure={failure} createRerunHost={createRerunHost} reconnect={transport.enrollment?()=>setAdding(true):undefined}/>:
:<>
{!enabled?'БК недоступен':fresh?'Сведения с БК':'Нет свежих сведений'}
{transport.enrollment&&wirelessContributions(contributions).length>0&&setAdding(true)}>}{void refresh();}}>
{!inventory?:connected.length===0?:{connected.map(item=>{ const operation=inventory.operations?.find(v=>v.device_id===item.id&&v.state==='running');const busy=!!operation||localBusy===item.id; diff --git a/packages/sensor-ui/src/WirelessEnrollmentWindow.tsx b/packages/sensor-ui/src/WirelessEnrollmentWindow.tsx index 61a12d4..78abfd4 100644 --- a/packages/sensor-ui/src/WirelessEnrollmentWindow.tsx +++ b/packages/sensor-ui/src/WirelessEnrollmentWindow.tsx @@ -12,7 +12,7 @@ export function WirelessEnrollmentWindow({contributions,transport,onClose,onChan const Enrollment=supported.find(value=>value.kind===selected)?.wirelessEnrollment?.View; const renderWindow:SensorEnrollmentProps['renderWindow']=({content,actions,busy=false})=>( {actions}:undefined}> + footer={actions?{actions}:undefined}>
+
+
+ Режим GNSS + -
-
- Режим GNSS -