wip(k1): checkpoint connection recovery rewrite

Capture the current unreleased K1 connection, recovery, lifecycle, viewer, and test work as a single known-bad baseline for subsequent fixes.
This commit is contained in:
DCCONSTRUCTIONS
2026-08-14 14:57:50 +03:00
parent aff331082f
commit 0ca7316a24
157 changed files with 152962 additions and 4036 deletions
+239 -1
View File
@@ -1,7 +1,245 @@
import type { StatusTone } from "@nodedc/ui-react";
import type { BackendStatus } from "@mission-core/plugin-sdk";
import type { XgridsK1Metrics } from "./api";
import type {
XgridsConnectionPolicyAction,
XgridsK1Metrics,
XgridsK1State,
} from "./api";
import {
connectionPolicyAllows,
connectionPolicyDecision,
} from "./lifecycle";
const connectionPolicyReasonCopy: Record<string, string> = {
"connection-supervisor-closed": "Контур связи K1 закрыт.",
"supervisor-action-not-allowed": "Текущая связь с K1 не подтверждает право на эту физическую команду.",
"network-provision-operation-active": "Предыдущая сетевая операция K1 ещё не завершена.",
"acquisition-active": "Сетевой режим K1 нельзя менять во время активного приёма.",
"acquisition-cleanup-pending": "Локальный приём K1 ещё завершает очистку ресурсов.",
"local-runtime-active": "Локальный исполнительный контур K1 ещё активен.",
"control-session-not-admissible-for-network-change": "Текущая управляющая сессия K1 ещё не допускает смену сети.",
"network-mutation-reconciliation-required": "Результат предыдущей сетевой записи K1 не подтверждён.",
"network-mutation-ledger-corrupt": "Журнал сетевых изменений K1 повреждён.",
"fresh-ble-candidate-required": "Для этого действия нужен K1 из нового Bluetooth-поиска.",
"retained-recovery-context-unavailable": "Сохранённый Bluetooth-контекст текущего K1 больше недоступен.",
"fresh-candidate-supersedes-retained-recovery": "K1 снова виден в свежем поиске; используйте новый найденный экземпляр.",
"reconciliation-target-not-observed": "В свежем Bluetooth-поиске не найден K1, связанный с незавершённой записью.",
"reconciliation-target-not-retained": "Текущий серверный Bluetooth-контекст относится не к тому K1, для которого не завершена сетевая операция.",
"current-device-context-unavailable": "Текущий K1 не подтверждён в оперативном контексте этого процесса.",
"durable-recovery-target-unavailable": "В сохранённом серверном состоянии нет одной точной пары K1 и режима для проверки после перезапуска.",
"fresh-candidate-supersedes-durable-recovery": "Нужный K1 снова найден свежим Bluetooth-поиском; сервер требует проверить именно свежий экземпляр.",
"retained-recovery-supersedes-durable-recovery": "Сервер хранит более свежий контекст текущего K1.",
"configured-endpoint-unavailable": "Нет подтверждённого сохранённого адреса K1 для безопасной проверки.",
"configured-endpoint-probe-lifecycle-busy": "Контур подключения K1 занят другой операцией.",
"configured-endpoint-changed-during-probe": "Адрес K1 изменился во время проверки; результат отброшен.",
"configured-endpoint-probe-failed": "Маршрут и управляющий endpoint K1 не удалось проверить.",
"host-path-unavailable": "На этом компьютере не подтверждён сетевой путь до K1.",
"host-route-not-direct": "Маршрут до K1 проходит не через ожидаемую локальную сеть.",
"endpoint-not-reachable": "Управляющий endpoint K1 сейчас недоступен.",
"device-identity-unverified": "Идентичность подключённого K1 ещё не подтверждена.",
"device-identity-stale": "Подтверждение идентичности K1 устарело.",
"device-identity-mismatch": "Подключённое устройство не совпало с выбранным K1.",
"device-identity-pin-store-corrupt": "Хранилище привязки устройства повреждено.",
"network-provisioning-idempotency-unavailable": "Журнал сетевых намерений K1 недоступен.",
"network-provisioning-idempotency-corrupt": "Журнал сетевых намерений K1 повреждён.",
"network-provisioning-idempotency-invalid": "Журнал сетевых намерений K1 не прошёл проверку.",
"network-provisioning-idempotency-operation-mismatch": "Текущая сетевая операция не совпала с сохранённым намерением.",
"semantic-topology-store-corrupt": "Сохранённая топология K1 повреждена.",
"control-plane-not-healthy": "Управляющий канал K1 не подтверждён.",
"connection-lease-not-reachable": "Текущая сессия связи K1 больше не подтверждена.",
"data-plane-stalled": "Поток данных K1 перестал обновляться.",
"data-plane-lost": "Поток данных K1 потерян.",
"physical-command-reconciliation-required": "Результат предыдущей физической команды K1 не подтверждён.",
"physical-device-already-active": "Последнее подтверждённое состояние K1 — активное сканирование.",
"physical-command-recovery-target-unavailable": "Журнал не содержит точную Bluetooth-цель для восстановления K1.",
"physical-command-recovery-target-not-observed": "Исходный K1 пока не найден в свежем Bluetooth-поиске.",
"physical-command-recovery-target-not-retained": "Сохранённый Bluetooth-контекст относится не к исходному K1.",
"physical-command-recovery-target-mismatch": "Выбрано другое устройство или другой способ связи, чем в незавершённой физической сессии.",
"physical-command-ledger-corrupt": "Журнал физических команд K1 повреждён.",
"physical-command-ledger-unavailable": "Журнал физических команд K1 недоступен.",
"physical-control-authority-unavailable": "Управляющая связь не позволяет безопасно отправить физический STOP.",
"ble-runtime-restart-required": "BLE-контур требует контролируемого перезапуска.",
"ble-runtime-cleanup-pending": "BLE-контур завершает предыдущую операцию.",
"ble-runtime-busy": "BLE-контур занят другой операцией.",
"k1-lifecycle-process-lease-network-owned": "Сетевой переход K1 ещё владеет исполнительным контуром.",
"k1-lifecycle-process-lease-control-owned": "Управляющая сессия K1 ещё владеет исполнительным контуром.",
"local-acquisition-receiver-not-active": "Активного локального приёмника сейчас нет.",
"physical-stop-is-authoritative": "Доступна подтверждённая физическая остановка K1; локальная очистка не должна её подменять.",
"action-not-implemented": "Это действие не реализовано и не может быть выполнено.",
"connection-reconfiguration-lifecycle-busy": "Другое действие подключения ещё не завершено.",
"connection-reconfiguration-bridge-only": "Это действие доступно только для подключения Bridge.",
"connection-reconfiguration-current-device-unavailable": "Нет точной привязки текущего устройства для изменения сети.",
"connection-reconfiguration-active": "Сначала завершите или отмените текущий выбор устройства или сети.",
"connection-reconfiguration-required-device-not-observed": "Исходное устройство не найдено в текущем Bluetooth-поиске.",
"connection-reconfiguration-not-active": "Активного изменения устройства или сети уже нет.",
"connection-reconfiguration-process-lease-busy": "Другой локальный процесс ещё управляет подключением устройства.",
"connection-reconfiguration-acquisition-changed": "Состояние приёма изменилось во время подготовки подключения.",
"connection-reconfiguration-revision-conflict": "Выбор устройства или сети уже изменился в другой вкладке.",
"connection-reconfiguration-binding-conflict": "Активное подключение изменилось до выполнения действия.",
"connection-reconfiguration-fresh-scan-required": "Для этого действия нужен новый Bluetooth-поиск.",
"connection-reconfiguration-discovery-conflict": "Результаты Bluetooth-поиска относятся к предыдущему действию.",
"connection-reconfiguration-target-mismatch": "Изменение сети разрешено только для исходного устройства.",
"acquisition-start-operation-active": "Запуск приёма ещё не завершён.",
"control-session-state-unsafe": "Управляющий диалог ещё не достиг безопасного состояния ожидания.",
};
const connectionPolicyNextActionCopy: Record<string, string> = {
"wait-for-operation": "Дождитесь завершения текущей операции и обновите состояние.",
"diagnose-network-ledger": "Не отправляйте новые команды и проверьте журнал сетевой операции.",
"diagnose-physical-command-ledger": "Не повторяйте команду; сначала проверьте журнал физических команд.",
"restart-ble-runtime": "Контролируемо перезапустите локальный BLE-контур и обновите состояние.",
"scan-ble": "Выполните свежий поиск Bluetooth-устройств.",
"observe-fresh-device-network": "Дождитесь автоматического восстановления связи с выбранным K1.",
"observe-current-device-network": "Дождитесь автоматического восстановления связи с тем же K1.",
"observe-configured-device-network": "Дождитесь автоматического восстановления сохранённого подключения K1.",
"recover-current-device-network": "Нажмите «Подключиться заново».",
"inspect-host-network": "Проверьте активную локальную сеть и маршрут этого компьютера.",
"probe-endpoint": "Дождитесь обновления подключения K1.",
"verify-control-device-info": "Подключитесь заново к выбранному K1.",
"select-connection-intent": "Выберите способ подключения и заново подтвердите текущий K1.",
"start-acquisition": "Повторно откройте финальное подтверждение физического START.",
"stop-acquisition": "Остановите K1 через подтверждённую физическую остановку.",
"stop-local-receiver": "Завершите локальный приём; физическое состояние K1 проверьте вручную.",
"retire-unavailable-physical-target": "Явно исключите недоступный прежний K1 перед новым выбором.",
"manual-recovery-required": "Автоматически безопасного продолжения нет; проверьте состояние K1 вручную.",
"cancel-reconfiguration": "Отмените текущий выбор и вернитесь к обычному восстановлению подключения.",
};
export interface ConnectionPolicyOperatorGuidance {
reason: string;
nextAction: string;
}
const connectionModeSelectionReasonCopy: Record<string, string> = {
"connection-mode-selection-physical-state-unsafe":
"Предыдущая физическая команда K1 осталась без подтверждённого результата. Поэтому способ подключения пока нельзя изменить.",
"connection-mode-selection-control-state-unsafe":
"Текущий управляющий процесс K1 ещё не завершён. После его завершения способ подключения снова станет доступен.",
"connection-mode-selection-lifecycle-busy":
"Текущее действие подключения ещё завершается. После него способ подключения снова станет доступен.",
"connection-reconfiguration-active":
"Сначала завершите или отмените текущий выбор устройства или сети.",
};
const physicalRetirementReasonCopy: Record<string, string> = {
"physical-command-retirement-operation-conflict":
"Сейчас завершается другая операция с физическим состоянием устройства. Дождитесь её завершения и обновите состояние.",
"physical-command-retirement-state-unsafe":
"Состояние предыдущей физической команды изменилось. Обновите состояние перед новым выбором устройства.",
"physical-command-retirement-not-required":
"Предыдущая физическая команда уже разрешена или больше не удерживает выбор устройства. Обновите состояние и продолжите обычное подключение.",
"physical-command-target-retired":
"Предыдущее устройство уже выведено из текущего контура. Можно сразу выполнить новый явный Bluetooth-поиск.",
"network-provision-operation-active":
"Сейчас завершается подключение устройства к сети. Дождитесь результата перед выбором другого устройства.",
"connection-reconfiguration-active":
"Сначала завершите или отмените текущее изменение устройства или сети.",
"control-local-retirement-pending":
"Управляющая сессия ещё освобождает локальные ресурсы. Дождитесь завершения и обновите состояние.",
"acquisition-active":
"Сканирование ещё активно. Сначала остановите его и дождитесь подтверждённого завершения.",
"acquisition-cleanup-pending":
"Локальный приём ещё освобождает ресурсы после остановки. Дождитесь завершения.",
"acquisition-start-operation-active":
"Запуск сканирования ещё не завершён. Дождитесь его результата перед сменой устройства.",
"acquisition-stop-operation-active":
"Остановка сканирования ещё не завершена. Дождитесь её результата перед сменой устройства.",
"local-runtime-active":
"Локальный поток устройства ещё активен или завершается. Дождитесь перехода в состояние ожидания.",
"control-session-state-unsafe":
"Управляющая сессия устройства ещё не завершена. Дождитесь её закрытия и обновите состояние.",
"ble-runtime-busy":
"Bluetooth занят другой операцией устройства. Дождитесь её завершения; новый поиск автоматически не запустится.",
"ble-runtime-cleanup-pending":
"Bluetooth ещё завершает предыдущую операцию. Дождитесь освобождения соединения и обновите состояние.",
"ble-runtime-restart-required":
"Локальный Bluetooth-контур требует контролируемого перезапуска. Команды устройству не отправлялись.",
"k1-lifecycle-process-lease-active":
"Другой локальный процесс ещё завершает действие с устройством. Дождитесь его завершения и обновите состояние.",
"physical-command-ledger-corrupt":
"Журнал физической команды повреждён. Не выбирайте другое устройство до проверки журнала.",
};
const physicalReopenReasonCopy: Record<string, string> = {
...physicalRetirementReasonCopy,
"physical-command-reconciliation-reopen-not-required":
"Это устройство больше не требует возврата из предыдущего выбора. Обновите состояние и продолжите обычное подключение.",
"physical-command-reconciliation-reopen-target-not-observed":
"Предыдущее устройство не найдено в последнем Bluetooth-поиске. Обновите поиск после проверки питания K1.",
"physical-command-reconciliation-reopen-target-not-connectable":
"Предыдущее устройство найдено, но сейчас не принимает Bluetooth-подключение. Проверьте питание K1 и повторите явный поиск.",
"physical-command-reconciliation-reopen-candidate-ambiguous":
"Последний Bluetooth-поиск не подтвердил один точный экземпляр предыдущего K1. Повторите поиск рядом только с нужным устройством.",
"physical-command-reconciliation-reopen-operation-conflict":
"Другая операция уже меняет локальное состояние предыдущего устройства. Дождитесь её завершения и обновите поиск.",
"device-calibration-read-active":
"Сейчас читается калибровка устройства. Дождитесь завершения проверки перед повторным использованием K1.",
};
/** Human explanation for a backend-disabled topology selector. */
export function connectionModeSelectionGuidance(
state: XgridsK1State | null | undefined,
): string | null {
const selection = state?.connection_lifecycle?.mode_selection;
if (!selection) return null;
if (selection?.allowed === true) return null;
const reasonCode = selection?.reason_codes.find((code) => code.trim().length > 0);
return reasonCode
? connectionModeSelectionReasonCopy[reasonCode]
?? "Способ подключения пока недоступен, потому что состояние K1 не позволяет безопасно изменить его."
: "Способ подключения пока недоступен, потому что состояние K1 не позволяет безопасно изменить его."
}
/** Human explanation for a currently unavailable local-only device escape. */
export function physicalRetirementGuidance(
state: XgridsK1State | null | undefined,
): string | null {
const retirement = (
state?.physical_command
?? state?.application_control_session?.physical_command
?? null
)?.operator_retirement;
if (!retirement || retirement.allowed === true) return null;
const reasonCode = retirement.reason_codes.find((code) => code.trim().length > 0);
return reasonCode
? physicalRetirementReasonCopy[reasonCode]
?? "Выбор другого устройства пока небезопасен. Обновите состояние после завершения текущей операции."
: "Выбор другого устройства пока небезопасен. Обновите состояние после завершения текущей операции.";
}
/** Human explanation for a backend-disabled exact retired-device reopen. */
export function physicalReopenGuidance(
state: XgridsK1State | null | undefined,
): string | null {
const reopen = state?.physical_command?.operator_reconciliation_reopen;
if (!reopen || reopen.allowed === true) return null;
const reasonCode = reopen.reason_codes.find((code) => code.trim().length > 0);
return reasonCode
? physicalReopenReasonCopy[reasonCode]
?? "Повторная проверка предыдущего K1 сейчас небезопасна. Дождитесь завершения текущей операции и обновите поиск."
: "Повторная проверка предыдущего K1 сейчас небезопасна. Обновите поиск после завершения текущей операции.";
}
export function connectionPolicyOperatorGuidance(
state: XgridsK1State | null | undefined,
action: XgridsConnectionPolicyAction,
): ConnectionPolicyOperatorGuidance | null {
if (connectionPolicyAllows(state, action)) return null;
const decision = connectionPolicyDecision(state, action);
const reasonCode = decision?.reason_codes.find((code) => code.trim().length > 0);
const recommendedAction = state?.connection_policy?.recommended_action?.trim();
return {
reason: reasonCode
? connectionPolicyReasonCopy[reasonCode]
?? "Система временно запретила действие до восстановления подтверждённого состояния."
: "Подтверждённая политика действия ещё не получена.",
nextAction: recommendedAction
? connectionPolicyNextActionCopy[recommendedAction]
?? "Обновите состояние подключения и следуйте рекомендованному безопасному действию."
: "Обновите состояние подключения перед новым действием.",
};
}
const phaseLabels: Record<string, string> = {
idle: "Ожидание",