feat(k1): wire dormant control lease
This commit is contained in:
@@ -31,6 +31,8 @@ The plugin owns:
|
||||
- K1-specific operator instructions and compatibility tests.
|
||||
- the scoped React `device.connection` contribution and its BLE/Wi-Fi and
|
||||
acquisition pipeline UI.
|
||||
- the dormant application-control facade boundary: explicit shadow arm/disarm,
|
||||
a 15–300 second Keychain-backed in-memory authority lease, and redacted state.
|
||||
|
||||
The plugin does not own:
|
||||
|
||||
@@ -79,6 +81,12 @@ device I/O with:
|
||||
uv run python plugins/xgrids-k1/profile_loader.py
|
||||
```
|
||||
|
||||
Plugin v0.4.0 does not widen device authority. Its application-control
|
||||
coordinator has no request-emission method, no live MQTT sink and no UI control;
|
||||
`vendor_writes_enabled` remains false. Keychain item provisioning and a separate
|
||||
review of any future write-capable transport are still required before a
|
||||
physical command can be considered.
|
||||
|
||||
The optional owner-controlled iPhone/LixelGO observation tool lives under
|
||||
[`lab/iphone-capture/`](lab/iphone-capture/). It pins `pymobiledevice3` in a
|
||||
separate `uv` environment, writes only ignored evidence sessions, and remains a
|
||||
|
||||
@@ -12,6 +12,8 @@ The contribution contains:
|
||||
start/stop and compatibility file replay;
|
||||
- plugin-local diagnostics, metrics, API state, lifecycle mapping,
|
||||
observation-source mapping and scoped styles;
|
||||
- typed v0.4.0 shadow application-control state/arm/disarm contracts. No button
|
||||
is rendered because the plugin has no live command transport;
|
||||
- `plugin.ts`, which binds the manifest `device.connection` component key to
|
||||
the runtime provider and connection view.
|
||||
|
||||
|
||||
@@ -82,6 +82,28 @@ export interface XgridsModelingControlSafety {
|
||||
publisher_installed: false;
|
||||
}
|
||||
|
||||
export interface XgridsApplicationControlExecution {
|
||||
mode: "dormant-write-disabled";
|
||||
state: "disarmed" | "armed-shadow-only" | "expired" | "closed";
|
||||
lease?: {
|
||||
state: "armed" | "expired" | "closed";
|
||||
remaining_seconds: number;
|
||||
authority_cached: boolean;
|
||||
exportable: false;
|
||||
} | null;
|
||||
orchestrator?: Record<string, unknown> | null;
|
||||
publisher: {
|
||||
mode: "write-disabled";
|
||||
denied_attempts: number;
|
||||
transport_calls: 0;
|
||||
vendor_writes_enabled: false;
|
||||
publisher_armed: false;
|
||||
automatic_retry: false;
|
||||
};
|
||||
live_transport_installed: false;
|
||||
can_emit_requests: false;
|
||||
}
|
||||
|
||||
export interface XgridsAcquisition {
|
||||
schema_version?: string;
|
||||
acquisition_id: string;
|
||||
@@ -205,6 +227,7 @@ export interface XgridsK1State {
|
||||
metrics?: XgridsK1Metrics;
|
||||
compatibility?: XgridsCompatibilityState | null;
|
||||
modeling_control_safety?: XgridsModelingControlSafety | null;
|
||||
application_control_execution?: XgridsApplicationControlExecution | null;
|
||||
device_ref?: XgridsDeviceRef | null;
|
||||
device_session?: XgridsDeviceSession | null;
|
||||
acquisition?: XgridsAcquisition | null;
|
||||
@@ -306,6 +329,12 @@ export interface StopCameraPreviewRequest {
|
||||
generation: number;
|
||||
}
|
||||
|
||||
export interface ShadowApplicationControlArmRequest {
|
||||
operator_confirmed: true;
|
||||
lease_seconds?: number;
|
||||
timezone_name: string;
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
readonly status: number;
|
||||
|
||||
@@ -453,6 +482,20 @@ export const xgridsK1Api = {
|
||||
updateViewerSettings(body: ViewerSettings): Promise<XgridsK1State> {
|
||||
return invokeState(xgridsK1Actions.viewerSettingsUpdate, body);
|
||||
},
|
||||
|
||||
getShadowApplicationControlState(): Promise<XgridsK1State> {
|
||||
return invokeState(xgridsK1Actions.applicationControlShadowState);
|
||||
},
|
||||
|
||||
armShadowApplicationControl(
|
||||
body: ShadowApplicationControlArmRequest,
|
||||
): Promise<XgridsK1State> {
|
||||
return invokeState(xgridsK1Actions.applicationControlShadowArm, body);
|
||||
},
|
||||
|
||||
disarmShadowApplicationControl(): Promise<XgridsK1State> {
|
||||
return invokeState(xgridsK1Actions.applicationControlShadowDisarm);
|
||||
},
|
||||
};
|
||||
|
||||
export type EventSocketStatus = "connecting" | "open" | "closed" | "error";
|
||||
|
||||
@@ -30,4 +30,16 @@ export const xgridsK1Actions = Object.freeze({
|
||||
cameraPreviewSelect: requirePluginAction(xgridsK1Manifest, "camera.preview.select"),
|
||||
cameraPreviewStop: requirePluginAction(xgridsK1Manifest, "camera.preview.stop"),
|
||||
viewerSettingsUpdate: requirePluginAction(xgridsK1Manifest, "viewer.settings.update"),
|
||||
applicationControlShadowState: requirePluginAction(
|
||||
xgridsK1Manifest,
|
||||
"application-control.shadow-state",
|
||||
),
|
||||
applicationControlShadowArm: requirePluginAction(
|
||||
xgridsK1Manifest,
|
||||
"application-control.shadow-arm",
|
||||
),
|
||||
applicationControlShadowDisarm: requirePluginAction(
|
||||
xgridsK1Manifest,
|
||||
"application-control.shadow-disarm",
|
||||
),
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"kind": "DevicePlugin",
|
||||
"metadata": {
|
||||
"id": "nodedc.device.xgrids-lixelkity-k1",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"displayName": "XGRIDS K1 Integration"
|
||||
},
|
||||
"spec": {
|
||||
@@ -25,7 +25,8 @@
|
||||
"network.mqtt.subscribe-private-lan",
|
||||
"network.rtsp.read-private-lan",
|
||||
"media.publish-local-browser",
|
||||
"evidence.write-session-artifacts"
|
||||
"evidence.write-session-artifacts",
|
||||
"credential.keychain.read-local"
|
||||
],
|
||||
"actions": [
|
||||
{ "id": "state.read", "mutating": false, "secretFields": [] },
|
||||
@@ -45,7 +46,10 @@
|
||||
{ "id": "stream.stop", "mutating": true, "secretFields": [] },
|
||||
{ "id": "camera.preview.select", "mutating": true, "secretFields": [] },
|
||||
{ "id": "camera.preview.stop", "mutating": true, "secretFields": [] },
|
||||
{ "id": "viewer.settings.update", "mutating": true, "secretFields": [] }
|
||||
{ "id": "viewer.settings.update", "mutating": true, "secretFields": [] },
|
||||
{ "id": "application-control.shadow-state", "mutating": false, "secretFields": [] },
|
||||
{ "id": "application-control.shadow-arm", "mutating": true, "secretFields": [] },
|
||||
{ "id": "application-control.shadow-disarm", "mutating": true, "secretFields": [] }
|
||||
],
|
||||
"models": [
|
||||
{
|
||||
|
||||
@@ -39,10 +39,11 @@ capture verifies the `ModelingRequest` topic, action values, field layout,
|
||||
literal `{device_id}:ModelingRequest` session relation, retained start settings
|
||||
and numeric success code. Retained client/wire evidence identifies OpenAPI as
|
||||
one private application-level value rather than a per-scanner profile. A fixed
|
||||
read-only macOS Keychain loader and offline response orchestrator now exist;
|
||||
operator-owned provisioning/execution-lease wiring, durable save completion,
|
||||
timeout and rollback behavior remain unresolved. Acquisition therefore
|
||||
stays `operator-manual` through the verified physical double-click.
|
||||
read-only macOS Keychain loader, bounded in-memory lease and dormant facade
|
||||
orchestrator now exist. No request-emission method or live MQTT sink exists;
|
||||
operator-owned Keychain item provisioning, reviewed emission, durable save
|
||||
completion, timeout and rollback behavior remain unresolved. Acquisition
|
||||
therefore stays `operator-manual` through the verified physical double-click.
|
||||
The standalone encoder models the recovered wire schema, including enum values
|
||||
outside the retained request. It is not an authorization policy: any future
|
||||
publisher must enforce the exact profile mapping (`2/1/0`, omitted
|
||||
|
||||
@@ -359,7 +359,7 @@
|
||||
},
|
||||
"success_result_code": 302252033,
|
||||
"required_unresolved_context": [
|
||||
"operator-owned Keychain authority provisioning and execution-lease wiring",
|
||||
"operator-owned Keychain item provisioning and reviewed request emission",
|
||||
"authorization policy for any setting outside the retained request",
|
||||
"timeout, rejection and rollback contract"
|
||||
],
|
||||
@@ -406,7 +406,7 @@
|
||||
"request_fields": {},
|
||||
"success_result_code": 302252033,
|
||||
"required_unresolved_context": [
|
||||
"operator-owned Keychain authority provisioning and execution-lease wiring",
|
||||
"operator-owned Keychain item provisioning and reviewed request emission",
|
||||
"save-completion and final-standby state mapping",
|
||||
"timeout and rollback contract"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user