feat(k1): add device-bound shadow control gate
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
This plan supersedes the app-dependent experiment order in the reference Bible.
|
||||
Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
||||
|
||||
## Current checkpoint — 2026-07-17
|
||||
## Current checkpoint — 2026-07-18
|
||||
|
||||
| Stage | Result |
|
||||
| --- | --- |
|
||||
@@ -21,6 +21,7 @@ Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
||||
| Stage 7 observation archive | GO (point/pose/telemetry contract) — durable catalog, recovery, capture-clock-bounded RRD preparation, archived metric time series, saved-session timeline and atomic playback are implemented |
|
||||
| Stage 7 recorded cameras | GO (contract), acceptance pending — acquisition-owned fMP4 archive and player are implemented/tested; one real archived K1 camera plus point-cloud session has not passed playback yet |
|
||||
| Plugin isolation | GO (laboratory control plane) — vendor backend/frontend and optional scene controls are plugin-owned; manifest/runtime descriptor parity, versioned handshake, lifecycle health and transport correlation fail closed while execution remains in-process |
|
||||
| K1 application control | PAUSE (shadow gate implemented) — the exact captured codec is byte-matched, live status binds vendor identity plus serial, and START/STOP can be planned without publish authority; device-bound OpenAPI enrollment, one-shot publisher, physical save acceptance and recovery policy remain closed gates |
|
||||
| Stage 8 product storage | PAUSE — retention, replication, encryption, capacity monitoring and long-run browser/WASM stress remain deployment gates |
|
||||
|
||||
USB project copying remains optional ground truth rather than a blocker for the
|
||||
@@ -30,9 +31,13 @@ encoder, response correlator and device-status state machine now exist without a
|
||||
publisher. Header construction is known: an explicit ASCII device ID and ASCII
|
||||
OpenAPI key are required, while the session ID is derived exactly as
|
||||
`${device_id}:ModelingRequest`. MQTT control publishing remains deliberately
|
||||
disabled because legitimate OpenAPI credential provenance/provisioning and a
|
||||
durable post-stop save gate are not proven. The physical button remains the
|
||||
known-safe fallback.
|
||||
disabled. A live-only shadow safety layer now observes the device-reported
|
||||
vendor identity and serial, rejects identity drift or malformed status, and can
|
||||
form a non-executable START only from unbound `READY` or STOP only from a bound
|
||||
`SCANNING` state. Its output contains a digest and wire metadata, never the
|
||||
device identity, serial, OpenAPI value or payload. Legitimate per-device OpenAPI
|
||||
provisioning, an authorized one-shot publisher and a durable post-stop save gate
|
||||
are not proven. The physical button remains the known-safe fallback.
|
||||
|
||||
The Stage 6 live path uses a bounded raw-first bridge: loss in the visualization
|
||||
queue cannot discard MQTT evidence. The queue holds four preview messages;
|
||||
|
||||
@@ -157,13 +157,37 @@ correlated them with device success responses and physical start/stop of the
|
||||
high-rate streams. This promotes the mapping from a static hint to descriptive
|
||||
wire evidence; it does not authorize replay or publishing.
|
||||
|
||||
An offline bounded audit of the retained owner-controlled captures additionally
|
||||
proved that the repository encoder reproduces the observed START and STOP
|
||||
payloads byte-for-byte for this one K1 and exact firmware profile. It also
|
||||
separated four values which must never be substituted for one another:
|
||||
|
||||
- Mission Core's provisional device UUID is local inventory identity and never
|
||||
belongs in a vendor request;
|
||||
- the vendor device ID comes from live K1 status and occupies the request header;
|
||||
- the K1 serial is a separate live device-binding value;
|
||||
- the OpenAPI value is a private, device-bound command credential and is not
|
||||
present in the status report.
|
||||
|
||||
Consequently, support for another K1 cannot replay this scanner's captured
|
||||
payload or OpenAPI value. It requires a separate owner-authorized enrollment
|
||||
whose vendor device ID and serial are attested against live status under the
|
||||
same exact firmware/topology profile. Any mismatch, identity drift, malformed
|
||||
status, replay-only evidence or unexpected lifecycle state fails closed.
|
||||
|
||||
The repository now contains an inert bounded encoder/response parser and a
|
||||
fail-closed device-status state machine for this exact profile. They have no
|
||||
MQTT publish dependency. Publishing remains deliberately disabled: the OpenAPI
|
||||
credential's provenance and secure provisioning are unresolved, and the
|
||||
complete `ScanOver`/ready/stable-artifact sequence has not physically proved a
|
||||
durable save result. The verified physical double-click remains the acquisition
|
||||
control until a separate reviewed write gate closes.
|
||||
fail-closed device-status state machine for this exact profile. A live-only
|
||||
shadow planner accepts START only from `READY` with no project and STOP only
|
||||
from `SCANNING` with a bound project. It emits only a non-executable digest and
|
||||
wire metadata, uses QoS 2 with retain false, and declares automatic retry
|
||||
forbidden. It has no MQTT publish dependency and cannot send a command.
|
||||
|
||||
Publishing remains deliberately disabled: secure per-device OpenAPI enrollment
|
||||
is unresolved, and the complete stop/save sequence has not physically proved a
|
||||
durable vendor project. A correlated STOP success, stream quiescence, return to
|
||||
`READY`/steady-green hardware state and appearance of a reusable native project
|
||||
are separate gates. The verified physical double-click remains the acquisition
|
||||
control until a separately reviewed one-shot write gate closes.
|
||||
|
||||
## Decoder and raw-capture bounds
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# ADR 0012: device-bound K1 command authority
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-07-18
|
||||
- Extends: ADR 0004, ADR 0005, ADR 0009 and ADR 0010
|
||||
|
||||
## Context
|
||||
|
||||
Owner-controlled LixelGO traffic proves the K1 modeling START/STOP protobuf
|
||||
shape, MQTT topic, QoS and correlated success response for one K1 running the
|
||||
exact `xgrids.lixelkity-k1.fw-3.0.2.direct-lan.v1` profile. A bounded offline
|
||||
audit also proves that Mission Core's encoder reproduces both retained requests
|
||||
byte-for-byte. This is sufficient to describe the current device dialogue, but
|
||||
not to replay a captured request or authorize writes to any K1.
|
||||
|
||||
The protocol exposes several identities with different authority. Mission
|
||||
Core's provisional device UUID is local inventory identity. The vendor device
|
||||
ID is reported by the live K1 and occupies the request header. The K1 serial is
|
||||
a separate device-binding value. The OpenAPI value is private command material
|
||||
which is absent from live status. LixelGO derives the command session relation
|
||||
from the vendor device ID and request type; it is not a reusable caller session.
|
||||
|
||||
Native project durability is also not equivalent to command acceptance. STOP
|
||||
success, stream quiescence, return to READY/steady green and a reusable project
|
||||
on the scanner are separate observations.
|
||||
|
||||
## Decision
|
||||
|
||||
K1 application control is exact-profile, device-bound and fail-closed.
|
||||
|
||||
1. Mission Core never sends its local inventory UUID, BLE identifier or a K1
|
||||
serial in place of the vendor request identity.
|
||||
2. Each K1 requires an owner-authorized enrollment containing its vendor device
|
||||
ID, serial and private OpenAPI value. Enrollment secrets are not committed,
|
||||
placed in Ops, returned by APIs or written to logs.
|
||||
3. Before a command can be considered, live MQTT status must attest the same
|
||||
vendor device ID and serial under the exact firmware/topology profile.
|
||||
Replay evidence cannot establish current command authority.
|
||||
4. Identity drift, missing identity, malformed status, profile mismatch or an
|
||||
unexpected lifecycle state blocks command planning.
|
||||
5. START retains the observed LixelGO parameters: record-and-calculate mode 2,
|
||||
LCC scan mode 1, handheld mount mode 0 and the operator's validated project
|
||||
name. STOP contains only the same bound header and action.
|
||||
6. MQTT command semantics remain QoS 2, retain false. Automatic retry is
|
||||
forbidden: an unknown outcome must be reconciled from live status and
|
||||
physical evidence before another command.
|
||||
7. No firmware, activation, account, update or vendor-cloud endpoint is part of
|
||||
the control path. Local direct-LAN MQTT is the only reviewed transport.
|
||||
8. STOP completion has four gates: correlated command result, local stream
|
||||
quiescence and evidence sealing, READY/steady-green device state, then native
|
||||
project verification. Earlier gates must not claim the later ones.
|
||||
|
||||
## Current implementation boundary
|
||||
|
||||
`LiveModelingControlSafety` observes only live device-status messages. It binds
|
||||
vendor identity and serial, tracks lifecycle state and creates non-executable
|
||||
shadow START/STOP plans. Public state and plan output contain only booleans,
|
||||
counts, action, topic, QoS, retain flag, payload length and digest. The encoded
|
||||
payload and enrolled values remain private in memory.
|
||||
|
||||
The shadow plan is blocked by both `vendor-writes-disabled` and
|
||||
`publisher-not-installed`. There is no MQTT publisher, enrollment endpoint or
|
||||
automatic retry path. The physical K1 control remains the production fallback.
|
||||
|
||||
## Promotion gate
|
||||
|
||||
A future publisher requires a separate review and an operator-present physical
|
||||
acceptance on the enrolled K1:
|
||||
|
||||
1. confirm exact firmware/profile, steady-green state, battery and storage;
|
||||
2. close LixelGO and arm raw evidence plus the intended camera receiver;
|
||||
3. attest live READY identity against the device enrollment;
|
||||
4. review the shadow command metadata;
|
||||
5. send one START, without retry, and correlate its exact response;
|
||||
6. observe calibration and first point/pose/camera data;
|
||||
7. send one STOP, without retry, and correlate its exact response;
|
||||
8. seal local evidence while accepting the bounded stream tail;
|
||||
9. wait for READY and steady green before power-off or another scan;
|
||||
10. verify the native project independently through the vendor-supported
|
||||
workflow.
|
||||
|
||||
Any unknown response, identity change, activation state, fault, low battery or
|
||||
unexpected lifecycle transition pauses the test. It does not trigger a guessed
|
||||
recovery command.
|
||||
|
||||
## Consequences
|
||||
|
||||
Adding another K1 means creating another private enrollment and proving its
|
||||
exact compatibility profile; it never means reusing captured bytes from the
|
||||
first scanner. The design preserves native record-and-calculate behavior while
|
||||
keeping command authority inside the XGRIDS plugin and out of generic Mission
|
||||
Core. It intentionally postpones convenience automation until identity,
|
||||
credential provenance, one-shot transport behavior and durable save semantics
|
||||
have all passed physical acceptance.
|
||||
Reference in New Issue
Block a user