feat(perception): integrate calibrated operator pipeline

Add calibrated K1 projection, recorded and near-live perception qualification, unified Rerun operator layers, bounded replay admission, audited viewer controls, worker experiments, and lab evidence.
This commit is contained in:
DCCONSTRUCTIONS
2026-07-23 00:23:28 +03:00
parent ada2a55ee6
commit b53d6d5a45
221 changed files with 55923 additions and 1357 deletions
+47 -11
View File
@@ -20,11 +20,21 @@ The plugin owns:
[`profiles/`](profiles/), with strict evidence flags and a fail-closed loader;
- BLE discovery hints and K1 GATT metadata;
- Bridge and Direct Connect through the reviewed firmware-3 BLE Wi-Fi
provisioning frame, plus a one-shot macOS CoreWLAN Quick Connect adapter;
- K1 LAN status and private-address validation;
provisioning frame, plus the reviewed Quick Connect AP-enable frame and
one-shot host-profile association adapter;
- read-only refresh of the selected K1's session-scoped DHCP address before a
new LAN operation; address changes rotate the device session and IP is never
treated as device identity;
- subscribe-only data MQTT transport plus a separately bounded canonical
application-control transport;
- native `.k1mqtt` capture;
- the read-only factory-calibration transport: one live `DeviceInfo` binding,
command-5 reads of exactly `camera.yaml` and
`extrinsic_camera_lidar.yaml`, and append-only private source snapshots; file
write and arbitrary paths are unavailable;
- safe exact-schema normalization of KB4 intrinsics, the main-camera 800x600
resize and explicit `T_camera_N_from_lidar` matrices, with firmware-verified
`sensor.camera.left -> camera_0` and `sensor.camera.right -> camera_1` binding;
- firmware-scoped protobuf/LZ4 and legacy codecs;
- normalization of K1 point cloud and pose, plus raw-only preservation of the
still-undecoded status and heartbeat channels;
@@ -89,11 +99,39 @@ Plugin v0.6.0 retains the physically accepted v0.5.0 control transport and adds
the connection matrix behind the existing explicit `network.provision` action.
Bridge remains the default. Direct Connect sends the same single reviewed
99-byte frame with credentials for an already-running controller hotspot. Quick
Connect sends no BLE write: a short-lived Swift/CoreWLAN helper receives the
operator-entered K1 AP credentials only over stdin, performs at most one scan
and one association, and never receives an automatic retry. Mission Core then
uses the observed K1 AP data-plane address. Automatic extraction of AP
credentials from undocumented BLE data is deliberately not implemented.
Connect accepts no browser/API credential: it sends one reviewed fixed 100-byte
AP-enable frame to the selected K1, waits up to 15 seconds for the canonical
byte-51 AP-ready flag, and keeps that BLE session alive while the macOS adapter
performs bounded exact-SSID CoreWLAN discovery and one association. Credentials
are resolved by a preinstalled exact `3.0.2` firmware provider. Its optional laboratory importer
validates the reviewed official archive, extracts the single AP declaration and
installs firmware-scoped material in the OS secure store. The macOS helper then
materializes the selected device profile entirely inside Keychain before any
BLE write. The secret never enters the browser, API, argv, logs or evidence;
the importer's short-lived mutable buffer is zeroized after the stdin handoff.
There is no automatic BLE-write or association retry. A clean host cannot
obtain the provider from BLE and the product does not download firmware during
connection. Windows/Linux Quick Connect adapters are not planned while that
bootstrap remains unresolved. Mission Core then uses the
reviewed K1 AP data-plane address. The
99-byte station-provisioning payload and 100-byte
AP-enable payload share `7f01` but remain distinct exact reviewed frames; neither
is a credential-read command.
For retained laboratory reproduction only, an operator can install the reviewed
source on a macOS host without displaying the credential:
```bash
uv run k1link authority import-k1-fw302-ap \
--firmware /private/path/to/the-official-3.0.2-archive.tar \
--confirm-reviewed-firmware
```
The 2026-07-20 UI run physically accepted the prepared-host path: one BLE discovery,
one AP-enable write, AP-ready, one CoreWLAN association, control bootstrap and
the normal acquisition lifecycle. It does not prove that the current build is
portable to a clean Mac. Bridge/direct-LAN remains the default and continuing
product route; Quick Connect stays available as laboratory functionality.
Plugin v0.5.0 installed the reviewed application-control transport behind
explicit plugin actions.
@@ -105,10 +143,8 @@ next reviewed action may be requested; it never schedules a device command by
elapsed time. START waits for bound `SCANNING + project + init_ready` before
operations 1314. STOP is separately permitted, never retried, and keeps the same socket until K1
reports unbound READY. That protocol state automatically completes local sealing;
stable green is physical corroboration rather than a second UI gate. The
admin CLI still provisions the fixed Keychain item through Apple's hidden
prompt without accepting the private value as an argument. No physical command
is emitted merely by loading the plugin, opening the page, navigating, polling
stable green is physical corroboration rather than a second UI gate. No
physical command is emitted merely by loading the plugin, opening the page, navigating, polling
state or running repository tests. The full v0.5.0 START/live/STOP/save path is
physically accepted on the reviewed K1/A4/FW 3.0.2 unit.
+8 -3
View File
@@ -186,7 +186,7 @@ export interface XgridsAcquisition {
cleanup_pending?: boolean;
requested_streams: string[];
target_host: string;
duration_seconds: number;
duration_seconds: number | null;
evidence_policy: "required" | "best-effort" | "disabled";
state: AcquisitionState;
state_revision: number;
@@ -229,6 +229,11 @@ export interface XgridsK1Metrics {
frame_rate_hz?: number | null;
point_count?: number | null;
dropped_preview_frames?: number | null;
ai_end_to_end_ms?: number | null;
ai_end_to_end_p95_ms?: number | null;
ai_frame_rate_hz?: number | null;
ai_dropped_frames?: number | null;
ai_stale_ms?: number | null;
device_elapsed_seconds?: number | null;
device_route_distance_meters?: number | null;
device_speed_meters_per_second?: number | null;
@@ -329,8 +334,8 @@ export interface CompatibilityAttestation {
export interface ConnectRequest {
device_id: string;
ssid: string;
password: string;
ssid?: string;
password?: string;
connection_mode: "bridge" | "quick-connect" | "direct-connect";
compatibility_attestation: CompatibilityAttestation;
operation_id?: string;
@@ -23,8 +23,8 @@ import type { XgridsK1Controller } from "../runtimeContext";
const connectionCopy: Record<ConnectionMode, {
stepTitle: string;
ssidLabel: string;
ssidPlaceholder: string;
ssidLabel?: string;
ssidPlaceholder?: string;
buttonLabel: string;
safetyNote: string;
}> = {
@@ -36,18 +36,16 @@ const connectionCopy: Record<ConnectionMode, {
safetyNote: "K1 получит реквизиты существующей сети одним рассмотренным BLE-запросом без автоматического повтора.",
},
"quick-connect": {
stepTitle: "Подключитесь к точке доступа K1",
ssidLabel: "Название точки доступа K1",
ssidPlaceholder: "SSID сканера, например XGR-…",
buttonLabel: "Подключить этот Mac к K1",
safetyNote: "Введите SSID и пароль точки доступа вашего K1. Mac сменит текущую Wi‑Fi сеть одним CoreWLAN-запросом; недокументированный BLE-секрет не читается и K1 не получает BLE-запись.",
stepTitle: "Включите точку доступа K1 и подключитесь к ней",
buttonLabel: "Включить точку K1 и подключиться",
safetyNote: "Mission Core сначала проверит локальный device-scoped профиль выбранного K1. Если профиль отсутствует, операция остановится до BLE-записи. После preflight Mission Core отправит один рассмотренный AP-enable кадр и найдёт точный SSID выбранного устройства.",
},
"direct-connect": {
stepTitle: "Подключите K1 к хотспоту контроллера",
ssidLabel: "Название хотспота контроллера",
ssidPlaceholder: "SSID управляющего устройства",
buttonLabel: "Подключить K1 к хотспоту",
safetyNote: "Хотспот должен быть уже включён, а этот Mac — иметь к нему маршрут. K1 получит его реквизиты одним рассмотренным BLE-запросом.",
safetyNote: "Хотспот должен быть уже включён, а управляющее устройство — иметь к нему маршрут. K1 получит его реквизиты одним рассмотренным BLE-запросом.",
},
};
@@ -131,7 +129,8 @@ export function K1ProvisioningPipeline({
const provisioningIntentRef = useRef<string | null>(null);
const devices = state?.devices ?? [];
const isBusy = pendingAction !== null;
const credentialsReady = ssid.trim().length > 0 && password.length > 0;
const credentialsReady = connectionMode === "quick-connect"
|| (ssid.trim().length > 0 && password.length > 0);
const canConnect = powerConfirmed && selectedDeviceId.length > 0 && credentialsReady && !isBusy;
const modeCopy = connectionCopy[connectionMode];
const selectedModeConnected = Boolean(
@@ -170,10 +169,12 @@ export function K1ProvisioningPipeline({
if (!canConnect) return;
const idempotencyKey = provisioningIntentKey(provisioningIntentRef.current);
provisioningIntentRef.current = idempotencyKey;
const networkCredentials = connectionMode === "quick-connect"
? {}
: { ssid: ssid.trim(), password };
const succeeded = await connect({
device_id: selectedDeviceId,
ssid: ssid.trim(),
password,
...networkCredentials,
connection_mode: connectionMode,
compatibility_attestation: profileSelectionForConnectionMode(connectionMode),
idempotency_key: idempotencyKey,
@@ -181,6 +182,11 @@ export function K1ProvisioningPipeline({
if (succeeded) {
provisioningIntentRef.current = null;
setPassword("");
} else if (connectionMode === "quick-connect") {
// The backend has already persisted and reconciled the failed bounded
// attempt. A later click is a new explicit Quick Connect intent, not an
// automatic replay of the consumed operation key.
provisioningIntentRef.current = null;
}
};
@@ -252,15 +258,27 @@ export function K1ProvisioningPipeline({
status={selectedModeConnected ? "Адрес получен" : "Ожидает подключения"}
tone={selectedModeConnected ? "success" : "neutral"}
>
<div className="field-stack">
<TextField label={modeCopy.ssidLabel} hint="SSID" value={ssid} onChange={(event) => { setSsid(event.target.value); provisioningIntentRef.current = null; }} autoComplete="off" spellCheck={false} placeholder={modeCopy.ssidPlaceholder} />
<TextField label="Пароль WiFi" hint="Только в оперативной памяти" type="password" value={password} onChange={(event) => { setPassword(event.target.value); provisioningIntentRef.current = null; }} autoComplete="off" placeholder="Введите пароль" />
</div>
{connectionMode === "quick-connect" ? (
<div className="connection-summary">
<span>Канонический путь</span>
<strong>BLE включает AP macOS подключает Mac к AP выбранного K1</strong>
</div>
) : (
<div className="field-stack">
<TextField label={modeCopy.ssidLabel ?? "Название сети Wi‑Fi"} hint="SSID" value={ssid} onChange={(event) => { setSsid(event.target.value); provisioningIntentRef.current = null; }} autoComplete="off" spellCheck={false} placeholder={modeCopy.ssidPlaceholder} />
<TextField label="Пароль WiFi" hint="Только в оперативной памяти" type="password" value={password} onChange={(event) => { setPassword(event.target.value); provisioningIntentRef.current = null; }} autoComplete="off" placeholder="Введите пароль" />
</div>
)}
<div className="connection-summary"><span>Устройство</span><strong>{deviceSummary?.name || selectedDeviceId || "Сначала выберите устройство"}</strong></div>
<Button width="full" variant="primary" icon={<Icon name="network" />} disabled={!canConnect} onClick={() => void submitConnect()}>
{pendingAction === "connect" ? "Подключаем…" : modeCopy.buttonLabel}
{pendingAction === "connect"
? connectionMode === "quick-connect" ? "Включаем точку и подключаем…" : "Подключаем…"
: modeCopy.buttonLabel}
</Button>
<p className="safety-note">{modeCopy.safetyNote} Пароль передаётся только локальному сервису, не сохраняется в браузере и удаляется из формы после успеха.</p>
<p className="safety-note">
{modeCopy.safetyNote}
{connectionMode === "quick-connect" ? " Это лабораторный путь для уже подготовленного хоста: credential provider должен существовать в системном хранилище заранее. На чистом Mac операция завершится до BLE-записи; браузер, API, журналы и evidence секрета не получают." : " Пароль передаётся только локальному сервису, не сохраняется в браузере и удаляется из формы после успеха."}
</p>
</WizardStep>
</div>
</GlassSurface>
@@ -17,7 +17,7 @@ export const connectionModeOptions: Array<SelectOption<ConnectionMode>> = [
{
value: "quick-connect",
label: "Точка доступа K1 · Quick Connect",
description: "Этот Mac один раз подключается к Wi‑Fi сканера; K1 остаётся точкой доступа.",
description: "Лабораторный режим: Mission Core включает AP K1 и подключает только заранее подготовленный хост. Для обычной работы используйте Bridge.",
},
{
value: "direct-connect",
@@ -108,6 +108,10 @@ function normalizeState(
frameRateHz: finiteMetric(metrics?.frame_rate ?? metrics?.frame_rate_hz),
pointCount: finiteMetric(metrics?.point_count),
droppedPreviewFrames: finiteMetric(metrics?.dropped_preview_frames),
aiLatencyMs: finiteMetric(metrics?.ai_end_to_end_ms),
aiFrameRateHz: finiteMetric(metrics?.ai_frame_rate_hz),
aiDroppedFrames: finiteMetric(metrics?.ai_dropped_frames),
aiStaleMs: finiteMetric(metrics?.ai_stale_ms),
elapsedSeconds: telemetry.elapsedSeconds,
routeDistanceMeters: telemetry.routeDistanceMeters,
speedMetersPerSecond: telemetry.speedMetersPerSecond,
@@ -12,6 +12,7 @@ import {
type OperatorPresenceConfirmation,
type PrepareAcquisitionRequest,
type ReplayRequest,
type XgridsOperation,
type XgridsApplicationControlPhase,
type XgridsK1State,
} from "./api";
@@ -186,6 +187,34 @@ function messageFor(error: unknown): string {
return "Запрос к локальному сервису устройства завершился ошибкой.";
}
function networkProvisionFailureMessage(
operation: XgridsOperation | null | undefined,
): string | null {
if (!operation || operation.status !== "failed") return null;
const code = operation.error?.code;
if (typeof code !== "string") return null;
const messages: Record<string, string> = {
"network-not-found":
"Точка доступа выбранного K1 не найдена. Команда включения точки не повторялась; проверьте питание и состояние K1.",
"credential-entry-cancelled":
"Первичная регистрация пароля K1 отменена. Получите пароль сохранённой сети этого K1 на авторизованном устройстве и повторите подключение отдельным действием.",
"credential-invalid":
"Пароль точки доступа K1 имеет недопустимую длину. Получите сохранённый пароль этого K1 в LixelGO/iPhone и повторите подключение.",
"host-wifi-operation-timeout":
"Первичное системное подключение к K1 не было завершено вовремя. BLE-команда автоматически не повторялась; получите пароль сохранённой сети этого K1 и запустите подключение заново.",
"profile-ssid-mismatch":
"Сохранённый профиль относится к другому K1. Подключение остановлено без повторной команды сканеру.",
"corewlan-error":
"macOS не смогла подключиться к точке доступа K1. Проверьте пароль сохранённой сети этого K1; автоматического повтора не было.",
"wifi-interface-unavailable":
"Системный Wi-Fi-интерфейс macOS недоступен. Команда сканеру автоматически не повторялась.",
"unsupported-platform":
"Для этой операционной системы адаптер подключения к точке K1 ещё не реализован.",
};
return messages[code] ?? null;
}
function measuredLatency(state: XgridsK1State | null): number | null {
if (state?.source_mode !== "live") return null;
const metrics = state?.metrics;
@@ -318,7 +347,29 @@ export function useXgridsK1Runtime(enabled: boolean) {
);
}
const nextState = await xgridsK1Api.connect(request);
let nextState: XgridsK1State;
try {
nextState = await xgridsK1Api.connect(request);
} catch (connectError) {
// A failed network action is terminal and persisted by the backend.
// Re-read state only; never replay the device command. This replaces
// an opaque HTTP 502 banner with the exact host-association outcome.
let failedState: XgridsK1State;
try {
failedState = await xgridsK1Api.getState();
} catch {
throw connectError;
}
acceptState(failedState);
const failedOperation = operationByIdempotencyKey(
failedState,
"network.provision",
request.idempotency_key,
);
const failureMessage = networkProvisionFailureMessage(failedOperation);
if (failureMessage) throw new ApiError(failureMessage);
throw connectError;
}
const operation = operationByIdempotencyKey(
nextState,
"network.provision",
@@ -334,7 +385,7 @@ export function useXgridsK1Runtime(enabled: boolean) {
}
return nextState;
}),
[run, state],
[acceptState, run, state],
);
const openApplicationControlSession = useCallback(
@@ -0,0 +1,240 @@
"""Export only LixelGO preferences from an owner-authorized USB iPhone.
MobileBackup2 asks the host how much free space is available before it starts.
The device estimates that requirement as if the transfer were an ordinary full
backup, even when this client discards every payload outside one exact app
preferences domain. This lab tool reports a synthetic capacity only inside
this filtered process. Non-matching payload bytes are consumed without being
written by pymobiledevice3's existing ``preserve_file`` boundary.
The completed raw backup exists only inside a private temporary directory. The
tool copies the one reviewed preferences plist to an ignored mode-0600 session
and removes the temporary backup before returning. It never prints plist
values, the device identifier, or any credential.
"""
from __future__ import annotations
import argparse
import asyncio
import hashlib
import json
import os
import plistlib
import secrets
import shutil
import sqlite3
import tempfile
from contextlib import closing
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from unittest.mock import patch
REPO_ROOT = Path(__file__).resolve().parents[4]
SESSION_ROOT = REPO_ROOT / "sessions" / "iphone-k1-observation"
BUNDLE_ID = "com.XGrids.LixelGo"
APP_DOMAIN = f"AppDomain-{BUNDLE_ID}"
PREFERENCES_PATH = f"Library/Preferences/{BUNDLE_ID}.plist"
PREFERENCES_FILE_ID = hashlib.sha1(
f"{APP_DOMAIN}-{PREFERENCES_PATH}".encode(),
usedforsecurity=False,
).hexdigest()
SYNTHETIC_AVAILABLE_BYTES = 2 * 1024**4
MAX_EXPORTED_PREFERENCES_BYTES = 16 * 1024 * 1024
class TargetedProfileBackupError(RuntimeError):
"""The bounded LixelGO preference export could not be completed safely."""
def _is_preferences_backup_file(backup_file: Any) -> bool:
"""Select only the exact LixelGO preferences payload or manifest row."""
# During transfer the hashed destination is exposed as ``file_name`` while
# some protocol versions also expose it as ``device_name``. Manifest
# pruning supplies the logical domain/path instead.
for attribute in ("file_name", "device_name"):
candidate = getattr(backup_file, attribute, None)
if isinstance(candidate, str) and Path(candidate).name == PREFERENCES_FILE_ID:
return True
return (
getattr(backup_file, "domain", None) == APP_DOMAIN
and getattr(backup_file, "relative_path", None) == PREFERENCES_PATH
)
def _utc_now() -> str:
return datetime.now(UTC).isoformat()
def _single_device_directory(backup_root: Path) -> Path:
candidates = [
path
for path in backup_root.iterdir()
if path.is_dir() and not path.is_symlink()
]
if len(candidates) != 1:
raise TargetedProfileBackupError("filtered backup did not create one device directory")
return candidates[0]
def _manifest_file_id(device_directory: Path) -> str:
manifest_path = device_directory / "Manifest.db"
if not manifest_path.is_file() or manifest_path.is_symlink():
raise TargetedProfileBackupError("filtered backup has no readable Manifest.db")
with closing(sqlite3.connect(f"file:{manifest_path}?mode=ro", uri=True)) as connection:
rows = connection.execute(
"SELECT fileID FROM Files WHERE domain = ? AND relativePath = ?",
(APP_DOMAIN, PREFERENCES_PATH),
).fetchall()
if len(rows) != 1 or not isinstance(rows[0][0], str):
raise TargetedProfileBackupError("LixelGO preferences were not present in the backup")
file_id = rows[0][0]
if len(file_id) != 40 or any(character not in "0123456789abcdef" for character in file_id):
raise TargetedProfileBackupError("LixelGO preferences have an invalid backup file id")
if file_id != PREFERENCES_FILE_ID:
raise TargetedProfileBackupError(
"LixelGO preferences backup file id does not match the exact requested path"
)
return file_id
def _read_preferences(device_directory: Path) -> bytes:
manifest = plistlib.loads((device_directory / "Manifest.plist").read_bytes())
if manifest.get("IsEncrypted") is True:
raise TargetedProfileBackupError(
"encrypted filtered backups are not supported by this probe"
)
file_id = _manifest_file_id(device_directory)
source = device_directory / file_id[:2] / file_id
if source.is_symlink() or not source.is_file():
raise TargetedProfileBackupError("LixelGO preferences payload is missing")
size = source.stat().st_size
if not 1 <= size <= MAX_EXPORTED_PREFERENCES_BYTES:
raise TargetedProfileBackupError(
"LixelGO preferences payload size is outside the safe bound"
)
payload = source.read_bytes()
parsed = plistlib.loads(payload)
if not isinstance(parsed, dict):
raise TargetedProfileBackupError("LixelGO preferences root is not a dictionary")
return payload
async def _filtered_backup(backup_root: Path) -> None:
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.device_link import DeviceLink
from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
original_free_disk_handler = DeviceLink.get_free_disk_space
async def report_filtered_capacity(self: Any, message: Any) -> None:
del message
actual = shutil.disk_usage(self.root_path).free
await self.status_response(
0,
status_dict=max(actual, SYNTHETIC_AVAILABLE_BYTES),
)
lockdown = await create_using_usbmux(connection_type="USB")
try:
with patch.object(
DeviceLink,
"get_free_disk_space",
report_filtered_capacity,
):
async with Mobilebackup2Service(lockdown) as service:
if await service.get_will_encrypt():
raise TargetedProfileBackupError(
"device backup encryption is enabled; no filtered export was attempted"
)
await service.backup(
full=True,
backup_directory=backup_root,
filter_callback=_is_preferences_backup_file,
)
finally:
DeviceLink.get_free_disk_space = original_free_disk_handler
await lockdown.close()
def _write_session(preferences: bytes) -> Path:
session_id = (
datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
+ f"_lixelgo-profile-export_{secrets.token_hex(2)}"
)
session_dir = SESSION_ROOT / session_id
private_dir = session_dir / "private"
private_dir.mkdir(parents=True, mode=0o700)
preferences_path = private_dir / "lixelgo-preferences.plist"
preferences_path.write_bytes(preferences)
preferences_path.chmod(0o600)
manifest = {
"schema": "missioncore.xgrids-k1/lixelgo-profile-export/v1alpha1",
"session_id": session_id,
"status": "completed",
"completed_at_utc": _utc_now(),
"bundle_id": BUNDLE_ID,
"source": "owner-authorized-usb-mobilebackup2-filtered",
"preferences_present": True,
"preferences_size_bytes": len(preferences),
"device_identity_recorded": False,
"preference_values_recorded_in_manifest": False,
"credential_values_recorded_in_manifest": False,
"temporary_backup_retained": False,
}
manifest_path = session_dir / "manifest.redacted.json"
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
manifest_path.chmod(0o600)
return session_dir
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Export only LixelGO preferences from one trusted USB iPhone"
)
parser.add_argument(
"--acknowledge-filtered-capacity",
action="store_true",
help=(
"confirm that the operator approved synthetic free-space reporting while "
"all non-LixelGO preference payloads are discarded"
),
)
return parser.parse_args()
def main() -> int:
from preflight import pymobiledevice3_binary, usb_device_count
args = parse_args()
if not args.acknowledge_filtered_capacity:
raise SystemExit("explicit --acknowledge-filtered-capacity is required")
os.umask(0o077)
if usb_device_count(pymobiledevice3_binary()) != 1:
raise SystemExit("exactly one trusted USB iPhone must be connected")
try:
with tempfile.TemporaryDirectory(prefix="mission-core-lixelgo-") as temporary:
backup_root = Path(temporary) / "backup"
backup_root.mkdir(mode=0o700)
asyncio.run(_filtered_backup(backup_root))
preferences = _read_preferences(_single_device_directory(backup_root))
session_dir = _write_session(preferences)
except (
OSError,
sqlite3.Error,
plistlib.InvalidFileException,
TargetedProfileBackupError,
) as exc:
raise SystemExit(f"filtered LixelGO profile export failed: {exc}") from exc
print(f"session: {session_dir}")
print("status: completed")
print("credential values printed: no")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+601 -31
View File
@@ -1,79 +1,649 @@
import AppKit
import CoreWLAN
import Foundation
import Security
private struct AssociationRequest: Decodable {
let ssid: String
let password: String
private let keychainService = "NODEDC Mission Core Host Wi-Fi Profiles"
private let credentialMaterialKeychainService =
"NODEDC Mission Core Device Credential Materials"
private struct HostWifiRequest: Decodable {
let action: String
let profileID: String
let ssid: String?
let password: String?
let scanTimeoutSeconds: Double?
let credentialSourceID: String?
enum CodingKeys: String, CodingKey {
case action
case profileID = "profile_id"
case ssid
case password
case scanTimeoutSeconds = "scan_timeout_seconds"
case credentialSourceID = "credential_source_id"
}
}
private struct AssociationResponse: Encodable {
private struct StoredProfile: Codable {
let schemaVersion: Int
let ssid: String
let password: String
let credentialSource: String?
enum CodingKeys: String, CodingKey {
case schemaVersion = "schema_version"
case ssid
case password
case credentialSource = "credential_source"
}
}
private struct StoredCredentialMaterial: Codable {
let schemaVersion: Int
let password: String
enum CodingKeys: String, CodingKey {
case schemaVersion = "schema_version"
case password
}
}
private struct HostWifiResponse: Encodable {
let ok: Bool
let adapter: String?
let alreadyAssociated: Bool?
let stored: Bool?
let found: Bool?
let profileAvailable: Bool?
let profileEnrolled: Bool?
let scanAttemptCount: Int?
let scanElapsedMilliseconds: Int?
let credentialSource: String?
let reasonCode: String?
enum CodingKeys: String, CodingKey {
case ok
case adapter
case alreadyAssociated = "already_associated"
case stored
case found
case profileAvailable = "profile_available"
case profileEnrolled = "profile_enrolled"
case scanAttemptCount = "scan_attempt_count"
case scanElapsedMilliseconds = "scan_elapsed_ms"
case credentialSource = "credential_source"
case reasonCode = "reason_code"
}
}
private func emit(_ response: AssociationResponse, exitCode: Int32) -> Never {
let encoder = JSONEncoder()
if let data = try? encoder.encode(response) {
private func emit(
ok: Bool,
adapter: String? = nil,
alreadyAssociated: Bool? = nil,
stored: Bool? = nil,
found: Bool? = nil,
profileAvailable: Bool? = nil,
profileEnrolled: Bool? = nil,
scanAttemptCount: Int? = nil,
scanElapsedMilliseconds: Int? = nil,
credentialSource: String? = nil,
reasonCode: String? = nil,
exitCode: Int32
) -> Never {
let response = HostWifiResponse(
ok: ok,
adapter: adapter,
alreadyAssociated: alreadyAssociated,
stored: stored,
found: found,
profileAvailable: profileAvailable,
profileEnrolled: profileEnrolled,
scanAttemptCount: scanAttemptCount,
scanElapsedMilliseconds: scanElapsedMilliseconds,
credentialSource: credentialSource,
reasonCode: reasonCode
)
if let data = try? JSONEncoder().encode(response) {
FileHandle.standardOutput.write(data)
}
exit(exitCode)
}
private let input = FileHandle.standardInput.readDataToEndOfFile()
guard input.count <= 1024 else {
emit(
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "request-too-large"),
exitCode: 1
private func profileIsValid(_ profile: StoredProfile) -> Bool {
guard profile.schemaVersion == 1,
let ssidData = profile.ssid.data(using: .utf8),
(1 ... 32).contains(ssidData.count),
let passwordData = profile.password.data(using: .utf8),
(1 ... 64).contains(passwordData.count)
else {
return false
}
return true
}
private func credentialMaterialIsValid(_ material: StoredCredentialMaterial) -> Bool {
guard material.schemaVersion == 1,
let passwordData = material.password.data(using: .utf8),
(8 ... 63).contains(passwordData.count)
else {
return false
}
return true
}
private func keychainQuery(service: String, account: String) -> [String: Any] {
return [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
]
}
private func profileKeychainQuery(profileID: String) -> [String: Any] {
return keychainQuery(service: keychainService, account: profileID)
}
private func materialKeychainQuery(sourceID: String) -> [String: Any] {
return keychainQuery(service: credentialMaterialKeychainService, account: sourceID)
}
private func loadProfile(profileID: String) throws -> StoredProfile {
var query = profileKeychainQuery(profileID: profileID)
query[kSecReturnData as String] = true
query[kSecMatchLimit as String] = kSecMatchLimitOne
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess, let data = item as? Data else {
throw NSError(domain: "HostWifiProfile", code: Int(status))
}
let profile = try JSONDecoder().decode(StoredProfile.self, from: data)
guard profileIsValid(profile) else {
throw NSError(domain: "HostWifiProfile", code: Int(errSecDecode))
}
return profile
}
private func storeProfile(profileID: String, profile: StoredProfile) throws {
guard profileIsValid(profile) else {
throw NSError(domain: "HostWifiProfile", code: Int(errSecParam))
}
let data = try JSONEncoder().encode(profile)
let query = profileKeychainQuery(profileID: profileID)
let attributes = [
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
] as [String: Any]
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
if updateStatus == errSecSuccess {
return
}
guard updateStatus == errSecItemNotFound else {
throw NSError(domain: "HostWifiProfile", code: Int(updateStatus))
}
var newItem = query
attributes.forEach { key, value in newItem[key] = value }
let addStatus = SecItemAdd(newItem as CFDictionary, nil)
guard addStatus == errSecSuccess else {
throw NSError(domain: "HostWifiProfile", code: Int(addStatus))
}
}
private func loadCredentialMaterial(sourceID: String) throws -> StoredCredentialMaterial {
var query = materialKeychainQuery(sourceID: sourceID)
query[kSecReturnData as String] = true
query[kSecMatchLimit as String] = kSecMatchLimitOne
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess, let data = item as? Data else {
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(status))
}
let material = try JSONDecoder().decode(StoredCredentialMaterial.self, from: data)
guard credentialMaterialIsValid(material) else {
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(errSecDecode))
}
return material
}
private func storeCredentialMaterial(
sourceID: String,
material: StoredCredentialMaterial
) throws {
guard credentialMaterialIsValid(material) else {
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(errSecParam))
}
let data = try JSONEncoder().encode(material)
let query = materialKeychainQuery(sourceID: sourceID)
let attributes = [
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
] as [String: Any]
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
if updateStatus == errSecSuccess {
return
}
guard updateStatus == errSecItemNotFound else {
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(updateStatus))
}
var newItem = query
attributes.forEach { key, value in newItem[key] = value }
let addStatus = SecItemAdd(newItem as CFDictionary, nil)
guard addStatus == errSecSuccess else {
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(addStatus))
}
}
private func loadSystemWiFiProfile(ssid: String, ssidData: Data) -> StoredProfile? {
var password: NSString?
let status = CWKeychainFindWiFiPassword(
CWKeychainDomain.user,
ssidData,
&password
)
guard status == errSecSuccess, let password else {
return nil
}
let profile = StoredProfile(
schemaVersion: 1,
ssid: ssid,
password: password as String,
credentialSource: "system-wifi-keychain"
)
return profileIsValid(profile) ? profile : nil
}
private struct TargetedScanResult {
let network: CWNetwork?
let attemptCount: Int
let elapsedMilliseconds: Int
}
private func scanForExpectedNetwork(
interface: CWInterface,
ssid: String,
ssidData: Data,
timeoutSeconds: Double
) throws -> TargetedScanResult {
let started = ProcessInfo.processInfo.systemUptime
var attemptCount = 0
while true {
attemptCount += 1
let networks = try interface.scanForNetworks(withSSID: ssidData)
if let network = networks.first(where: { $0.ssid == ssid }) {
return TargetedScanResult(
network: network,
attemptCount: attemptCount,
elapsedMilliseconds: max(
0,
Int((ProcessInfo.processInfo.systemUptime - started) * 1_000)
)
)
}
let elapsed = ProcessInfo.processInfo.systemUptime - started
let remaining = timeoutSeconds - elapsed
if remaining <= 0 {
return TargetedScanResult(
network: nil,
attemptCount: attemptCount,
elapsedMilliseconds: max(0, Int(elapsed * 1_000))
)
}
Thread.sleep(forTimeInterval: min(0.75, remaining))
}
}
private func promptForDevicePassword(ssid: String) -> String? {
let application = NSApplication.shared
application.setActivationPolicy(.accessory)
let passwordField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 360, height: 24))
passwordField.placeholderString = "Пароль точки доступа K1"
let alert = NSAlert()
alert.alertStyle = .informational
alert.messageText = "Первое подключение к \(ssid)"
alert.informativeText = "macOS не нашла локальный профиль этой точки доступа. Если credential вам неизвестен, нажмите «Отмена» и выполните авторизованный импорт device-профиля LixelGO. Введённое значение будет сохранено только в Keychain этого Mac и не попадёт в браузер, API, журнал или evidence Mission Core."
alert.accessoryView = passwordField
alert.addButton(withTitle: "Подключиться")
alert.addButton(withTitle: "Отмена")
application.activate(ignoringOtherApps: true)
guard alert.runModal() == .alertFirstButtonReturn else {
return nil
}
return passwordField.stringValue
}
private let input = FileHandle.standardInput.readDataToEndOfFile()
guard input.count <= 2048 else {
emit(ok: false, reasonCode: "request-too-large", exitCode: 1)
}
do {
let request = try JSONDecoder().decode(AssociationRequest.self, from: input)
guard let ssidData = request.ssid.data(using: .utf8),
(1 ... 32).contains(ssidData.count),
let passwordData = request.password.data(using: .utf8),
(1 ... 64).contains(passwordData.count)
let request = try JSONDecoder().decode(HostWifiRequest.self, from: input)
guard (1 ... 128).contains(request.profileID.count),
request.profileID.allSatisfy({
$0.isASCII && ($0.isLetter || $0.isNumber || ".-_".contains($0))
})
else {
emit(ok: false, reasonCode: "profile-id-invalid", exitCode: 1)
}
if request.action == "store-profile" {
guard let ssid = request.ssid, let password = request.password else {
emit(ok: false, reasonCode: "credential-missing", exitCode: 1)
}
try storeProfile(
profileID: request.profileID,
profile: StoredProfile(
schemaVersion: 1,
ssid: ssid,
password: password,
credentialSource: nil
)
)
emit(ok: true, adapter: "macOS Keychain", stored: true, exitCode: 0)
}
if request.action == "store-credential-material" {
guard let password = request.password else {
emit(ok: false, reasonCode: "credential-missing", exitCode: 1)
}
try storeCredentialMaterial(
sourceID: request.profileID,
material: StoredCredentialMaterial(schemaVersion: 1, password: password)
)
emit(ok: true, adapter: "macOS Keychain", stored: true, exitCode: 0)
}
if request.action == "check-credential-material" {
do {
_ = try loadCredentialMaterial(sourceID: request.profileID)
emit(
ok: true,
adapter: "macOS Keychain",
profileAvailable: true,
credentialSource: "exact-firmware-profile",
exitCode: 0
)
} catch {
emit(
ok: true,
adapter: "macOS Keychain",
profileAvailable: false,
exitCode: 0
)
}
}
if request.action == "ensure-profile" {
guard let ssid = request.ssid,
let ssidData = ssid.data(using: .utf8),
(1 ... 32).contains(ssidData.count),
let sourceID = request.credentialSourceID,
(1 ... 128).contains(sourceID.count),
sourceID.allSatisfy({
$0.isASCII && ($0.isLetter || $0.isNumber || ".-_".contains($0))
})
else {
emit(ok: false, reasonCode: "credential-source-invalid", exitCode: 1)
}
let material: StoredCredentialMaterial
do {
material = try loadCredentialMaterial(sourceID: sourceID)
} catch {
emit(
ok: true,
adapter: "macOS Keychain",
profileAvailable: false,
profileEnrolled: false,
reasonCode: "credential-source-unavailable",
exitCode: 0
)
}
do {
let existing = try loadProfile(profileID: request.profileID)
guard existing.ssid == ssid else {
emit(
ok: false,
adapter: "macOS Keychain",
profileAvailable: false,
profileEnrolled: false,
reasonCode: "profile-ssid-mismatch",
exitCode: 1
)
}
if existing.password == material.password,
existing.credentialSource != "exact-firmware-profile" {
try storeProfile(
profileID: request.profileID,
profile: StoredProfile(
schemaVersion: 1,
ssid: ssid,
password: existing.password,
credentialSource: "exact-firmware-profile"
)
)
}
emit(
ok: true,
adapter: "macOS Keychain",
profileAvailable: true,
profileEnrolled: false,
credentialSource: existing.password == material.password
? "exact-firmware-profile"
: (existing.credentialSource ?? "mission-core-keychain"),
exitCode: 0
)
} catch {
try storeProfile(
profileID: request.profileID,
profile: StoredProfile(
schemaVersion: 1,
ssid: ssid,
password: material.password,
credentialSource: "exact-firmware-profile"
)
)
emit(
ok: true,
adapter: "macOS Keychain",
profileAvailable: true,
profileEnrolled: true,
credentialSource: "exact-firmware-profile",
exitCode: 0
)
}
}
if request.action == "check-profile" {
let profile: StoredProfile
do {
profile = try loadProfile(profileID: request.profileID)
} catch {
emit(
ok: true,
adapter: "macOS Keychain",
profileAvailable: false,
exitCode: 0
)
}
if let expectedSSID = request.ssid, profile.ssid != expectedSSID {
emit(
ok: false,
adapter: "macOS Keychain",
profileAvailable: false,
reasonCode: "profile-ssid-mismatch",
exitCode: 1
)
}
emit(
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "credential-bounds"),
exitCode: 1
ok: true,
adapter: "macOS Keychain",
profileAvailable: true,
exitCode: 0
)
}
guard request.action == "associate" || request.action == "scan-profile" else {
emit(ok: false, reasonCode: "action-unsupported", exitCode: 1)
}
guard let interface = CWWiFiClient.shared().interface() else {
emit(ok: false, reasonCode: "wifi-interface-unavailable", exitCode: 1)
}
let scanTimeoutSeconds = request.scanTimeoutSeconds ?? 0
guard scanTimeoutSeconds >= 0, scanTimeoutSeconds <= 60 else {
emit(ok: false, reasonCode: "scan-timeout-invalid", exitCode: 1)
}
if request.action == "scan-profile" {
let profile: StoredProfile
do {
profile = try loadProfile(profileID: request.profileID)
} catch {
emit(ok: false, reasonCode: "profile-unavailable", exitCode: 1)
}
let ssidData = profile.ssid.data(using: .utf8)!
let scan = try scanForExpectedNetwork(
interface: interface,
ssid: profile.ssid,
ssidData: ssidData,
timeoutSeconds: scanTimeoutSeconds
)
emit(
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "wifi-interface-unavailable"),
ok: true,
adapter: "CoreWLAN",
found: scan.network != nil,
scanAttemptCount: scan.attemptCount,
scanElapsedMilliseconds: scan.elapsedMilliseconds,
exitCode: 0
)
}
guard request.action == "associate" else {
emit(ok: false, reasonCode: "action-unsupported", exitCode: 1)
}
guard let expectedSSID = request.ssid,
let expectedSSIDData = expectedSSID.data(using: .utf8),
(1 ... 32).contains(expectedSSIDData.count)
else {
emit(ok: false, reasonCode: "ssid-invalid", exitCode: 1)
}
let scan = try scanForExpectedNetwork(
interface: interface,
ssid: expectedSSID,
ssidData: expectedSSIDData,
timeoutSeconds: scanTimeoutSeconds
)
guard let network = scan.network else {
emit(
ok: false,
scanAttemptCount: scan.attemptCount,
scanElapsedMilliseconds: scan.elapsedMilliseconds,
reasonCode: "network-not-found",
exitCode: 1
)
}
if interface.ssid() == request.ssid {
emit(AssociationResponse(ok: true, alreadyAssociated: true, reasonCode: nil), exitCode: 0)
var profileEnrolled = false
var profileNeedsStore = false
let credentialSource: String
let profile: StoredProfile
do {
profile = try loadProfile(profileID: request.profileID)
credentialSource = profile.credentialSource ?? "mission-core-keychain"
} catch {
if let systemProfile = loadSystemWiFiProfile(
ssid: expectedSSID,
ssidData: expectedSSIDData
) {
profile = systemProfile
credentialSource = "system-wifi-keychain"
profileNeedsStore = true
} else {
guard let password = promptForDevicePassword(ssid: expectedSSID) else {
emit(
ok: false,
scanAttemptCount: scan.attemptCount,
scanElapsedMilliseconds: scan.elapsedMilliseconds,
reasonCode: "credential-entry-cancelled",
exitCode: 1
)
}
guard let passwordData = password.data(using: .utf8),
(1 ... 64).contains(passwordData.count)
else {
emit(
ok: false,
scanAttemptCount: scan.attemptCount,
scanElapsedMilliseconds: scan.elapsedMilliseconds,
reasonCode: "credential-invalid",
exitCode: 1
)
}
profile = StoredProfile(
schemaVersion: 1,
ssid: expectedSSID,
password: password,
credentialSource: "native-secure-prompt"
)
credentialSource = "native-secure-prompt"
profileNeedsStore = true
}
}
let networks = try interface.scanForNetworks(withSSID: ssidData)
guard let network = networks.first(where: { $0.ssid == request.ssid }) else {
guard profile.ssid == expectedSSID else {
emit(
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "network-not-found"),
ok: false,
scanAttemptCount: scan.attemptCount,
scanElapsedMilliseconds: scan.elapsedMilliseconds,
reasonCode: "profile-ssid-mismatch",
exitCode: 1
)
}
try interface.associate(to: network, password: request.password)
if interface.ssid() == profile.ssid {
if profileNeedsStore {
try storeProfile(profileID: request.profileID, profile: profile)
profileEnrolled = true
}
emit(
ok: true,
adapter: "CoreWLAN",
alreadyAssociated: true,
profileEnrolled: profileEnrolled,
scanAttemptCount: scan.attemptCount,
scanElapsedMilliseconds: scan.elapsedMilliseconds,
credentialSource: credentialSource,
exitCode: 0
)
}
try interface.associate(to: network, password: profile.password)
if profileNeedsStore {
try storeProfile(profileID: request.profileID, profile: profile)
profileEnrolled = true
}
// CoreWLAN's synchronous association call throws on failure. Reading the
// current SSID again would require Location authorization on recent macOS
// versions and could turn a successful association into a false negative.
emit(AssociationResponse(ok: true, alreadyAssociated: false, reasonCode: nil), exitCode: 0)
} catch {
emit(
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "corewlan-error"),
exitCode: 1
ok: true,
adapter: "CoreWLAN",
alreadyAssociated: false,
profileEnrolled: profileEnrolled,
scanAttemptCount: scan.attemptCount,
scanElapsedMilliseconds: scan.elapsedMilliseconds,
credentialSource: credentialSource,
exitCode: 0
)
} catch {
emit(ok: false, reasonCode: "corewlan-error", exitCode: 1)
}
+5 -5
View File
@@ -156,12 +156,12 @@ def _validate_connection_modes(scope: dict[str, Any]) -> None:
"quick-connect": {
"topology": "device-ap",
"direction": "controller-joins-k1-network",
"device_network_action": "none",
"host_network_action": "single-corewlan-association",
"target_address_policy": "reviewed-fixed-k1-ap-private-ipv4",
"acceptance": (
"lixelgo-data-plane-observed-mission-core-physical-acceptance-pending"
"device_network_action": "single-reviewed-ble-ap-activation-write",
"host_network_action": (
"exact-firmware-provider-and-single-corewlan-association"
),
"target_address_policy": "reviewed-fixed-k1-ap-private-ipv4",
"acceptance": "prepared-macos-host-physical-accepted",
},
}
if set(modes) != set(expected):
@@ -11,7 +11,7 @@
"version": "3.0.2"
},
"topology": "local-network-matrix",
"claim_limit": "One owner-controlled K1 on exact firmware 3.0.2 across controlled Bridge/Mission Core and owner-operated Quick Connect LixelGO/iPhone runs. Direct Connect reuses the physically accepted 99-byte BLE Wi-Fi frame but still requires its own Mission Core physical acceptance. This is not a vendor API or a cross-firmware compatibility claim.",
"claim_limit": "One owner-controlled K1 on exact firmware 3.0.2. Bridge/Mission Core is physically accepted. Quick Connect AP activation and association are accepted only on one prepared macOS host with a preinstalled firmware provider; clean-host bootstrap is not implemented. Direct Connect reuses the physically accepted 99-byte BLE Wi-Fi frame but still requires its own Mission Core physical acceptance. This is not a vendor API or a cross-firmware compatibility claim.",
"connection_modes": [
{
"id": "bridge",
@@ -35,10 +35,10 @@
"id": "quick-connect",
"topology": "device-ap",
"direction": "controller-joins-k1-network",
"device_network_action": "none",
"host_network_action": "single-corewlan-association",
"device_network_action": "single-reviewed-ble-ap-activation-write",
"host_network_action": "exact-firmware-provider-and-single-corewlan-association",
"target_address_policy": "reviewed-fixed-k1-ap-private-ipv4",
"acceptance": "lixelgo-data-plane-observed-mission-core-physical-acceptance-pending"
"acceptance": "prepared-macos-host-physical-accepted"
}
]
},
@@ -91,6 +91,12 @@
"kind": "redacted-physical-protocol-report",
"path": "docs/lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md",
"scope": "Owner-operated LixelGO start/stop mapping, local-only bounded traffic result and left/right RTSP/H.264 camera-preview discovery."
},
{
"id": "lab-004",
"kind": "redacted-physical-and-firmware-analysis-report",
"path": "docs/lab/004_K1_FW302_AP_CREDENTIAL_PROVIDER_20260720.redacted.md",
"scope": "Official K1 3.0.2 firmware credential source, SSID construction, secure provider import, prepared-host physical association and the decision to keep Quick Connect laboratory-only."
}
],
"transports": [
@@ -118,7 +124,8 @@
},
"source_ids": [
"wifi-provisioning-profile",
"lab-001"
"lab-001",
"lab-004"
]
},
{