fix(k1): restore canonical local connection lifecycle

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 13:52:46 +03:00
parent 52da9b75b7
commit aff331082f
19 changed files with 2169 additions and 274 deletions
@@ -635,6 +635,72 @@ test("a provisioned address is not presented as a verified device connection", (
}), "connected");
});
test("K1 provisioning mutations require the operator's fresh BLE candidate", () => {
const backendLease = {
selected_device_id: "stale-backend-lease-device",
connection_mode: "bridge",
devices: [
{
device_id: "fresh-scan-candidate",
name: "K1 candidate",
connectable: true,
},
],
};
assert.equal(
lifecycle.provisioningCandidateById(backendLease.devices, ""),
null,
);
assert.equal(
lifecycle.provisioningCandidateById(
backendLease.devices,
backendLease.selected_device_id,
),
null,
);
assert.equal(lifecycle.canSubmitProvisioningMutation({
devices: backendLease.devices,
selectedDeviceId: "expired-scan-candidate",
powerConfirmed: true,
credentialsReady: true,
isBusy: false,
}), false);
assert.equal(lifecycle.canSubmitProvisioningMutation({
devices: backendLease.devices,
selectedDeviceId: "fresh-scan-candidate",
powerConfirmed: true,
credentialsReady: true,
isBusy: false,
}), true);
});
test("K1 connection status is green only for a reachable matching lease", () => {
const unreachableLease = {
k1_ip: "192.168.68.50",
connection_mode: "bridge",
connection_verification: {
lease_state: "disconnected",
network_reachability: "unreachable",
},
};
const reachableLease = {
...unreachableLease,
connection_verification: {
lease_state: "reachable",
network_reachability: "reachable",
},
};
assert.equal(lifecycle.isReachableConnectionLease(unreachableLease, "bridge"), false);
assert.equal(lifecycle.isReachableConnectionLease(reachableLease, "bridge"), true);
assert.equal(lifecycle.isReachableConnectionLease(reachableLease, "quick-connect"), false);
assert.equal(lifecycle.isReachableConnectionLease({
k1_ip: "192.168.68.50",
connection_mode: "bridge",
}, "bridge"), false);
});
test("provisioning intent keeps one idempotency key and exposes unsafe outcomes", () => {
let created = 0;
const createUuid = () => {
@@ -751,3 +817,52 @@ test("rejected network-profile writes use safe operator copy", () => {
);
assert.doesNotMatch(message, /Bleak|GATT|ATT/i);
});
test("host Wi-Fi helper failures do not fabricate a missing-password diagnosis", () => {
const operationTimeout = networkProvisionFailureMessage({
status: "failed",
error: { code: "host-wifi-operation-timeout" },
});
const buildTimeout = networkProvisionFailureMessage({
status: "failed",
error: {
code: "host-wifi-helper-build-timeout",
side_effect_status: "none",
safe_to_retry: true,
},
});
const buildFailed = networkProvisionFailureMessage({
status: "failed",
error: {
code: "host-wifi-helper-build-failed",
side_effect_status: "none",
safe_to_retry: true,
},
});
const postWriteBuildFailed = networkProvisionFailureMessage({
status: "failed",
error: {
code: "host-wifi-helper-build-failed",
side_effect_status: "confirmed",
safe_to_retry: false,
},
});
assert.equal(
operationTimeout,
"Локальная операция подготовки Wi‑Fi не завершилась вовремя. Это могло произойти до изменения состояния K1; наличие сохранённого пароля этим кодом не подтверждается и не опровергается. Проверьте состояние K1 и повторите подключение отдельным действием.",
);
assert.doesNotMatch(operationTimeout, /получите пароль|пароль отсутствует/i);
assert.equal(
buildTimeout,
"Локальный компонент Wi‑Fi не успел собраться за отведённое время. Команда K1 не отправлялась; подготовьте локальный компонент и повторите подключение отдельным действием.",
);
assert.equal(
buildFailed,
"Локальный компонент Wi‑Fi не удалось собрать. Команда K1 не отправлялась; подготовьте локальный компонент и повторите подключение отдельным действием.",
);
assert.equal(
postWriteBuildFailed,
"Локальный компонент Wi‑Fi не удалось собрать уже после начала операции с K1. Состояние устройства нельзя выводить из этой локальной ошибки; автоматического повтора команды не было. Выполните read-only проверку K1 перед новым подключением.",
);
});