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:
@@ -0,0 +1,977 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { after, before, test } from "node:test";
|
||||
|
||||
import React, { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { createServer } from "vite";
|
||||
|
||||
let server;
|
||||
let activeStreamForceFinishAuthority;
|
||||
let activeStreamForceFinishAuthorityMatches;
|
||||
let activeStreamRecoveredBrowserAuthority;
|
||||
let activeStreamRecoveryPresentation;
|
||||
let activeStreamRecoveryPresentationAuthority;
|
||||
let activeStreamRecoveryOwnsPresentationDecision;
|
||||
let exactActiveStreamRecoveryLineage;
|
||||
let formatActiveStreamRecoveryElapsed;
|
||||
let suppressGenericErrorDuringActiveStreamRecovery;
|
||||
let isXgridsActiveStreamRecovery;
|
||||
let K1AcquisitionPipeline;
|
||||
let K1SpatialControlsView;
|
||||
let runSpatialActiveStreamForceFinish;
|
||||
let shouldRenderK1GenericRuntimeError;
|
||||
let shouldRenderK1OperationalPanels;
|
||||
let xgridsK1Actions;
|
||||
let xgridsK1Api;
|
||||
|
||||
const hookSourceUrl = new URL(
|
||||
"../../../plugins/xgrids-k1/frontend/src/useXgridsK1Runtime.ts",
|
||||
import.meta.url,
|
||||
);
|
||||
const acquisitionSourceUrl = new URL(
|
||||
"../../../plugins/xgrids-k1/frontend/src/components/K1AcquisitionPipeline.tsx",
|
||||
import.meta.url,
|
||||
);
|
||||
const recoverySurfaceSourceUrl = new URL(
|
||||
"../../../plugins/xgrids-k1/frontend/src/components/ActiveStreamRecoverySurface.tsx",
|
||||
import.meta.url,
|
||||
);
|
||||
const spatialControlsSourceUrl = new URL(
|
||||
"../../../plugins/xgrids-k1/frontend/src/components/K1SpatialControls.tsx",
|
||||
import.meta.url,
|
||||
);
|
||||
const connectionSourceUrl = new URL(
|
||||
"../../../plugins/xgrids-k1/frontend/src/XgridsK1Connection.tsx",
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
appType: "custom",
|
||||
logLevel: "silent",
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
({
|
||||
activeStreamForceFinishAuthority,
|
||||
activeStreamForceFinishAuthorityMatches,
|
||||
activeStreamRecoveredBrowserAuthority,
|
||||
activeStreamRecoveryPresentation,
|
||||
activeStreamRecoveryPresentationAuthority,
|
||||
activeStreamRecoveryOwnsPresentationDecision,
|
||||
exactActiveStreamRecoveryLineage,
|
||||
formatActiveStreamRecoveryElapsed,
|
||||
suppressGenericErrorDuringActiveStreamRecovery,
|
||||
} = await server.ssrLoadModule("@xgrids-k1/frontend/activeStreamRecovery.ts"));
|
||||
({ isXgridsActiveStreamRecovery, xgridsK1Api } = await server.ssrLoadModule(
|
||||
"@xgrids-k1/frontend/api.ts",
|
||||
));
|
||||
({ xgridsK1Actions } = await server.ssrLoadModule(
|
||||
"@xgrids-k1/frontend/manifest.ts",
|
||||
));
|
||||
({ K1AcquisitionPipeline } = await server.ssrLoadModule(
|
||||
"@xgrids-k1/frontend/components/K1AcquisitionPipeline.tsx",
|
||||
));
|
||||
({
|
||||
K1SpatialControlsView,
|
||||
runSpatialActiveStreamForceFinish,
|
||||
} = await server.ssrLoadModule(
|
||||
"@xgrids-k1/frontend/components/K1SpatialControls.tsx",
|
||||
));
|
||||
({
|
||||
shouldRenderK1GenericRuntimeError,
|
||||
shouldRenderK1OperationalPanels,
|
||||
} = await server.ssrLoadModule(
|
||||
"@xgrids-k1/frontend/XgridsK1Connection.tsx",
|
||||
));
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
function recoveryContract(overrides = {}) {
|
||||
return {
|
||||
schema_version: "missioncore.xgrids-k1-active-stream-recovery/v1",
|
||||
state: "reconnecting",
|
||||
generation: 7,
|
||||
acquisition_id: "acquisition-recovery-001",
|
||||
attempt: 3,
|
||||
started_at_utc: "2026-08-11T19:31:00Z",
|
||||
elapsed_ms: 12_400,
|
||||
reason_code: "read-only-rebind-in-progress",
|
||||
force_finish_allowed: true,
|
||||
automatic_read_only_rebind: true,
|
||||
automatic_command_retry: false,
|
||||
start_performed: false,
|
||||
stop_performed: false,
|
||||
ble_operation_performed: false,
|
||||
network_mutation_performed: false,
|
||||
runtime_producer_generation: 11,
|
||||
camera_recovery: "owned",
|
||||
camera_media_state: "pending-first-media",
|
||||
camera_media_ready: false,
|
||||
camera_epoch: {
|
||||
generation: 7,
|
||||
init_committed: true,
|
||||
init_committed_age_ms: 250,
|
||||
first_media_committed: false,
|
||||
first_media_committed_age_ms: null,
|
||||
committed_media_segment_count: 0,
|
||||
last_media_segment_age_ms: null,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function recoveryState(recoveryOverrides = {}, stateOverrides = {}) {
|
||||
return {
|
||||
snapshot_runtime_id: "snapshot-runtime-recovery-001",
|
||||
snapshot_revision: 43,
|
||||
producer_generation: 11,
|
||||
phase: "reconnecting",
|
||||
source_mode: "live",
|
||||
acquisition: {
|
||||
acquisition_id: "acquisition-recovery-001",
|
||||
device_id: "device-k1-001",
|
||||
device_session_id: "device-session-001",
|
||||
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
|
||||
control_mode: "plugin-commanded",
|
||||
requested_streams: ["spatial.point-cloud.live"],
|
||||
target_host: "127.0.0.1",
|
||||
duration_seconds: 0,
|
||||
evidence_policy: "required",
|
||||
state: "acquiring",
|
||||
state_revision: 9,
|
||||
cleanup_pending: false,
|
||||
},
|
||||
connection_recovery: recoveryContract(recoveryOverrides),
|
||||
...stateOverrides,
|
||||
};
|
||||
}
|
||||
|
||||
function coldRestartPrePclState() {
|
||||
const state = recoveryState({
|
||||
generation: 1,
|
||||
acquisition_id: "acquisition-before-backend-restart-001",
|
||||
attempt: 0,
|
||||
started_at_utc: "2026-08-14T00:31:00Z",
|
||||
elapsed_ms: 450,
|
||||
reason_code: "restart-receiver-awaiting-first-pcl",
|
||||
runtime_producer_generation: 1,
|
||||
camera_recovery: "inactive",
|
||||
camera_media_state: "inactive",
|
||||
camera_media_ready: false,
|
||||
camera_epoch: null,
|
||||
}, {
|
||||
snapshot_runtime_id: "snapshot-runtime-after-cold-restart-001",
|
||||
snapshot_revision: 2,
|
||||
producer_generation: 1,
|
||||
acquisition: {
|
||||
...recoveryState().acquisition,
|
||||
acquisition_id: "acquisition-before-backend-restart-001",
|
||||
state: "awaiting_external_start",
|
||||
state_revision: 4,
|
||||
},
|
||||
camera_preview: {
|
||||
activation_admission: {
|
||||
state: "waiting-for-first-authoritative-pcl",
|
||||
basis: "post-rerun-publish-pcl-frame",
|
||||
runtime_producer_generation: 1,
|
||||
device_command_sent: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
function coldRestartRecoveredState() {
|
||||
const state = coldRestartPrePclState();
|
||||
return {
|
||||
...state,
|
||||
snapshot_revision: state.snapshot_revision + 1,
|
||||
phase: "live",
|
||||
acquisition: {
|
||||
...state.acquisition,
|
||||
state: "acquiring",
|
||||
state_revision: state.acquisition.state_revision + 1,
|
||||
},
|
||||
connection_recovery: {
|
||||
...state.connection_recovery,
|
||||
state: "recovered",
|
||||
elapsed_ms: null,
|
||||
reason_code: null,
|
||||
force_finish_allowed: false,
|
||||
camera_recovery: "owned",
|
||||
camera_media_state: "pending-epoch",
|
||||
camera_media_ready: false,
|
||||
camera_epoch: null,
|
||||
},
|
||||
camera_preview: {
|
||||
activation_admission: {
|
||||
state: "activating",
|
||||
basis: "post-rerun-publish-pcl-frame",
|
||||
runtime_producer_generation: 1,
|
||||
device_command_sent: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function controller(state, overrides = {}) {
|
||||
return {
|
||||
state,
|
||||
pendingAction: null,
|
||||
error: null,
|
||||
physicalStopIntentSpent: false,
|
||||
physicalStopInFlight: false,
|
||||
closeApplicationControlSession: async () => false,
|
||||
prepareCanonicalAcquisition: async () => false,
|
||||
startPreparedAcquisition: async () => false,
|
||||
startReplay: async () => false,
|
||||
stop: async () => false,
|
||||
stopLocalReceiver: async () => false,
|
||||
forceFinishActiveStreamLocally: async () => false,
|
||||
abort: async () => false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function renderPipeline(state, overrides = {}) {
|
||||
return renderToStaticMarkup(createElement(K1AcquisitionPipeline, {
|
||||
controller: controller(state, overrides),
|
||||
desiredConnectionMode: "bridge",
|
||||
openSpatialScene() {},
|
||||
activateAutomaticSpatialSource() {},
|
||||
}));
|
||||
}
|
||||
|
||||
function renderSpatialControls(state, overrides = {}) {
|
||||
return renderToStaticMarkup(createElement(K1SpatialControlsView, {
|
||||
controller: controller(state, overrides),
|
||||
}));
|
||||
}
|
||||
|
||||
function buttonsWithText(markup, text) {
|
||||
return (markup.match(/<button\b[\s\S]*?<\/button>/g) ?? [])
|
||||
.filter((button) => button.includes(text));
|
||||
}
|
||||
|
||||
function sourceSlice(source, startMarker, endMarker) {
|
||||
const start = source.indexOf(startMarker);
|
||||
const end = source.indexOf(endMarker, start + startMarker.length);
|
||||
assert.notEqual(start, -1, `missing source marker: ${startMarker}`);
|
||||
assert.notEqual(end, -1, `missing source marker: ${endMarker}`);
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
test("active recovery contract is strict about all no-write invariants", () => {
|
||||
assert.equal(isXgridsActiveStreamRecovery(recoveryContract()), true);
|
||||
for (const field of [
|
||||
"automatic_command_retry",
|
||||
"start_performed",
|
||||
"stop_performed",
|
||||
"ble_operation_performed",
|
||||
"network_mutation_performed",
|
||||
]) {
|
||||
assert.equal(
|
||||
isXgridsActiveStreamRecovery(recoveryContract({ [field]: true })),
|
||||
false,
|
||||
field,
|
||||
);
|
||||
}
|
||||
assert.equal(
|
||||
isXgridsActiveStreamRecovery(recoveryContract({ state: "retrying-command" })),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isXgridsActiveStreamRecovery(recoveryContract({ elapsed_ms: -1 })),
|
||||
false,
|
||||
);
|
||||
const missingMediaState = recoveryContract();
|
||||
delete missingMediaState.camera_media_state;
|
||||
assert.equal(isXgridsActiveStreamRecovery(missingMediaState), false);
|
||||
assert.equal(
|
||||
isXgridsActiveStreamRecovery(recoveryContract({ camera_media_ready: true })),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isXgridsActiveStreamRecovery(recoveryContract({
|
||||
camera_media_state: "ready",
|
||||
camera_media_ready: true,
|
||||
camera_epoch: {
|
||||
...recoveryContract().camera_epoch,
|
||||
generation: 0,
|
||||
first_media_committed: true,
|
||||
first_media_committed_age_ms: 1,
|
||||
committed_media_segment_count: 1,
|
||||
last_media_segment_age_ms: 1,
|
||||
},
|
||||
})),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("cold restart before first PCL owns exact reconnect UI without camera or physical actions", () => {
|
||||
const state = coldRestartPrePclState();
|
||||
assert.equal(isXgridsActiveStreamRecovery(state.connection_recovery), true);
|
||||
|
||||
const lineage = exactActiveStreamRecoveryLineage(state);
|
||||
assert.deepEqual(lineage && {
|
||||
runtime: lineage.snapshotRuntimeId,
|
||||
acquisition: lineage.acquisitionId,
|
||||
revision: lineage.acquisitionStateRevision,
|
||||
recovery: lineage.recoveryGeneration,
|
||||
producer: lineage.runtimeProducerGeneration,
|
||||
}, {
|
||||
runtime: "snapshot-runtime-after-cold-restart-001",
|
||||
acquisition: "acquisition-before-backend-restart-001",
|
||||
revision: 4,
|
||||
recovery: 1,
|
||||
producer: 1,
|
||||
});
|
||||
assert.notEqual(activeStreamRecoveryPresentationAuthority(state), null);
|
||||
assert.notEqual(activeStreamForceFinishAuthority(state), null);
|
||||
assert.equal(activeStreamRecoveredBrowserAuthority(state), null);
|
||||
assert.equal(state.connection_recovery.camera_recovery, "inactive");
|
||||
assert.equal(state.connection_recovery.camera_media_state, "inactive");
|
||||
assert.equal(state.connection_recovery.camera_media_ready, false);
|
||||
assert.equal(state.connection_recovery.camera_epoch, null);
|
||||
assert.deepEqual(state.camera_preview.activation_admission, {
|
||||
state: "waiting-for-first-authoritative-pcl",
|
||||
basis: "post-rerun-publish-pcl-frame",
|
||||
runtime_producer_generation: 1,
|
||||
device_command_sent: false,
|
||||
});
|
||||
|
||||
const pipeline = renderPipeline(state);
|
||||
assert.match(pipeline, /Восстанавливаем соединение/);
|
||||
assert.match(pipeline, /START, STOP, Bluetooth и настройки устройства не отправляются/);
|
||||
assert.equal(buttonsWithText(pipeline, "Прервать соединение").length, 1);
|
||||
assert.equal(buttonsWithText(pipeline, "Запустить приём").length, 0);
|
||||
assert.equal(buttonsWithText(pipeline, "Остановить устройство и запись").length, 0);
|
||||
assert.equal(buttonsWithText(pipeline, "Остановить сканирование").length, 0);
|
||||
assert.doesNotMatch(
|
||||
pipeline,
|
||||
/СВЯЗЬ ВОССТАНОВЛЕНА|Связь восстановлена · приём продолжается|Продолжаем тот же приём/,
|
||||
);
|
||||
|
||||
const spatial = renderSpatialControls(state);
|
||||
assert.match(spatial, /Восстанавливаем соединение/);
|
||||
assert.equal(buttonsWithText(spatial, "Прервать соединение").length, 1);
|
||||
assert.doesNotMatch(
|
||||
spatial,
|
||||
/Остановить устройство|Остановить K1|Завершить локальный приём/,
|
||||
);
|
||||
});
|
||||
|
||||
test("cold restart first PCL preserves lineage and renders recovered continuation copy", () => {
|
||||
const beforePcl = coldRestartPrePclState();
|
||||
const state = coldRestartRecoveredState();
|
||||
assert.equal(isXgridsActiveStreamRecovery(state.connection_recovery), true);
|
||||
assert.equal(
|
||||
state.connection_recovery.acquisition_id,
|
||||
beforePcl.connection_recovery.acquisition_id,
|
||||
);
|
||||
assert.equal(
|
||||
state.connection_recovery.generation,
|
||||
beforePcl.connection_recovery.generation,
|
||||
);
|
||||
assert.equal(
|
||||
state.connection_recovery.runtime_producer_generation,
|
||||
beforePcl.connection_recovery.runtime_producer_generation,
|
||||
);
|
||||
|
||||
const lineage = exactActiveStreamRecoveryLineage(state);
|
||||
const browserAuthority = activeStreamRecoveredBrowserAuthority(state);
|
||||
assert.deepEqual(lineage && {
|
||||
runtime: lineage.snapshotRuntimeId,
|
||||
acquisition: lineage.acquisitionId,
|
||||
revision: lineage.acquisitionStateRevision,
|
||||
recovery: lineage.recoveryGeneration,
|
||||
producer: lineage.runtimeProducerGeneration,
|
||||
}, {
|
||||
runtime: "snapshot-runtime-after-cold-restart-001",
|
||||
acquisition: "acquisition-before-backend-restart-001",
|
||||
revision: 5,
|
||||
recovery: 1,
|
||||
producer: 1,
|
||||
});
|
||||
assert.deepEqual(browserAuthority, lineage);
|
||||
assert.equal(activeStreamRecoveryPresentationAuthority(state), null);
|
||||
assert.equal(activeStreamForceFinishAuthority(state), null);
|
||||
assert.equal(state.connection_recovery.camera_recovery, "owned");
|
||||
assert.equal(state.camera_preview.activation_admission.device_command_sent, false);
|
||||
|
||||
const pipeline = renderPipeline(state);
|
||||
assert.doesNotMatch(pipeline, /Восстанавливаем соединение|Прервать соединение/);
|
||||
assert.match(pipeline, /СВЯЗЬ ВОССТАНОВЛЕНА · АКТИВНЫЙ ПРИЁМ/);
|
||||
assert.match(pipeline, /Связь восстановлена · приём продолжается/);
|
||||
assert.match(pipeline, /Продолжаем тот же приём без нового START/);
|
||||
assert.doesNotMatch(pipeline, /Назовите проект и запустите приём|Запустить приём/);
|
||||
});
|
||||
|
||||
test("force-finish authority requires exact runtime, acquisition, revision and generations", () => {
|
||||
const state = recoveryState();
|
||||
const authority = activeStreamForceFinishAuthority(state);
|
||||
assert.deepEqual(authority && {
|
||||
snapshotRuntimeId: authority.snapshotRuntimeId,
|
||||
acquisitionId: authority.acquisitionId,
|
||||
acquisitionStateRevision: authority.acquisitionStateRevision,
|
||||
recoveryGeneration: authority.recoveryGeneration,
|
||||
runtimeProducerGeneration: authority.runtimeProducerGeneration,
|
||||
}, {
|
||||
snapshotRuntimeId: "snapshot-runtime-recovery-001",
|
||||
acquisitionId: "acquisition-recovery-001",
|
||||
acquisitionStateRevision: 9,
|
||||
recoveryGeneration: 7,
|
||||
runtimeProducerGeneration: 11,
|
||||
});
|
||||
assert.equal(activeStreamForceFinishAuthorityMatches(authority, state), true);
|
||||
|
||||
const staleCases = [
|
||||
(() => {
|
||||
const value = structuredClone(state);
|
||||
value.snapshot_runtime_id = "snapshot-runtime-recovery-002";
|
||||
return value;
|
||||
})(),
|
||||
(() => {
|
||||
const value = structuredClone(state);
|
||||
value.acquisition.acquisition_id = "acquisition-recovery-002";
|
||||
return value;
|
||||
})(),
|
||||
(() => {
|
||||
const value = structuredClone(state);
|
||||
value.acquisition.state_revision += 1;
|
||||
return value;
|
||||
})(),
|
||||
(() => {
|
||||
const value = structuredClone(state);
|
||||
value.connection_recovery.generation += 1;
|
||||
return value;
|
||||
})(),
|
||||
(() => {
|
||||
const value = structuredClone(state);
|
||||
value.producer_generation += 1;
|
||||
return value;
|
||||
})(),
|
||||
];
|
||||
for (const stale of staleCases) {
|
||||
assert.equal(activeStreamForceFinishAuthorityMatches(authority, stale), false);
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
activeStreamForceFinishAuthority(recoveryState({ state: "recovered", force_finish_allowed: false })),
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
activeStreamForceFinishAuthority(recoveryState({ force_finish_allowed: false })),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test("reconnecting presentation is exact and owns stale supervisor projection", () => {
|
||||
const state = recoveryState({ force_finish_allowed: false });
|
||||
const authority = activeStreamRecoveryPresentationAuthority(state);
|
||||
assert.deepEqual(authority && {
|
||||
runtime: authority.snapshotRuntimeId,
|
||||
acquisition: authority.acquisitionId,
|
||||
producer: authority.runtimeProducerGeneration,
|
||||
recovery: authority.recoveryGeneration,
|
||||
}, {
|
||||
runtime: "snapshot-runtime-recovery-001",
|
||||
acquisition: "acquisition-recovery-001",
|
||||
producer: 11,
|
||||
recovery: 7,
|
||||
});
|
||||
assert.equal(activeStreamRecoveryOwnsPresentationDecision(state), true);
|
||||
|
||||
const wrongProducer = structuredClone(state);
|
||||
wrongProducer.producer_generation += 1;
|
||||
assert.equal(activeStreamRecoveryPresentationAuthority(wrongProducer), null);
|
||||
assert.equal(
|
||||
activeStreamRecoveryOwnsPresentationDecision(wrongProducer),
|
||||
true,
|
||||
"a valid reconnect contract must block stale ordinary data projection",
|
||||
);
|
||||
|
||||
for (const recoveryStateName of [
|
||||
"blocked",
|
||||
"standby",
|
||||
"fault",
|
||||
"force-finishing",
|
||||
"force-finished",
|
||||
]) {
|
||||
const terminal = recoveryState({ state: recoveryStateName });
|
||||
assert.equal(activeStreamRecoveryPresentationAuthority(terminal), null);
|
||||
assert.equal(activeStreamRecoveryOwnsPresentationDecision(terminal), true);
|
||||
}
|
||||
assert.equal(
|
||||
activeStreamRecoveryOwnsPresentationDecision(recoveryState({ state: "recovered" })),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("recovered browser carryover keeps exact lineage without restoring recovery controls", () => {
|
||||
const state = recoveryState({
|
||||
state: "recovered",
|
||||
force_finish_allowed: false,
|
||||
elapsed_ms: null,
|
||||
reason_code: null,
|
||||
}, {
|
||||
phase: "live",
|
||||
});
|
||||
const authority = activeStreamRecoveredBrowserAuthority(state);
|
||||
assert.deepEqual(authority && {
|
||||
runtime: authority.snapshotRuntimeId,
|
||||
acquisition: authority.acquisitionId,
|
||||
revision: authority.acquisitionStateRevision,
|
||||
producer: authority.runtimeProducerGeneration,
|
||||
recovery: authority.recoveryGeneration,
|
||||
}, {
|
||||
runtime: "snapshot-runtime-recovery-001",
|
||||
acquisition: "acquisition-recovery-001",
|
||||
revision: 9,
|
||||
producer: 11,
|
||||
recovery: 7,
|
||||
});
|
||||
assert.equal(activeStreamRecoveryPresentation(state), null);
|
||||
assert.equal(activeStreamForceFinishAuthority(state), null);
|
||||
|
||||
const staleProducer = structuredClone(state);
|
||||
staleProducer.producer_generation += 1;
|
||||
assert.equal(activeStreamRecoveredBrowserAuthority(staleProducer), null);
|
||||
assert.equal(
|
||||
activeStreamRecoveredBrowserAuthority(recoveryState({
|
||||
state: "recovered",
|
||||
camera_recovery: "blocked",
|
||||
force_finish_allowed: false,
|
||||
}, { phase: "live" })),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test("only an exact reconnecting lineage suppresses the generic red error", () => {
|
||||
const state = recoveryState();
|
||||
assert.equal(suppressGenericErrorDuringActiveStreamRecovery(state, null), true);
|
||||
assert.equal(
|
||||
shouldRenderK1GenericRuntimeError("Ошибка локального приёмника", false, state, null),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
suppressGenericErrorDuringActiveStreamRecovery(state, "force-finish"),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
shouldRenderK1GenericRuntimeError(
|
||||
"Локальное завершение не выполнено",
|
||||
false,
|
||||
state,
|
||||
"force-finish",
|
||||
),
|
||||
true,
|
||||
"a failed explicit local finish must keep the generic error banner visible",
|
||||
);
|
||||
|
||||
const wrongProducer = structuredClone(state);
|
||||
wrongProducer.producer_generation += 1;
|
||||
assert.equal(suppressGenericErrorDuringActiveStreamRecovery(wrongProducer), false);
|
||||
|
||||
const wrongAcquisition = structuredClone(state);
|
||||
wrongAcquisition.connection_recovery.acquisition_id = "acquisition-stale";
|
||||
assert.equal(suppressGenericErrorDuringActiveStreamRecovery(wrongAcquisition), false);
|
||||
|
||||
const missingRuntime = structuredClone(state);
|
||||
delete missingRuntime.snapshot_runtime_id;
|
||||
assert.equal(suppressGenericErrorDuringActiveStreamRecovery(missingRuntime), false);
|
||||
|
||||
const blocked = recoveryState({ state: "blocked" });
|
||||
assert.equal(suppressGenericErrorDuringActiveStreamRecovery(blocked), false);
|
||||
|
||||
const nonOwned = recoveryState({ automatic_read_only_rebind: false });
|
||||
assert.equal(suppressGenericErrorDuringActiveStreamRecovery(nonOwned), false);
|
||||
|
||||
const inactiveCases = [
|
||||
recoveryState({}, { phase: "error" }),
|
||||
recoveryState({}, { source_mode: "idle" }),
|
||||
recoveryState({}, {
|
||||
acquisition: {
|
||||
...state.acquisition,
|
||||
state: "failed",
|
||||
},
|
||||
}),
|
||||
];
|
||||
for (const inactive of inactiveCases) {
|
||||
assert.equal(
|
||||
suppressGenericErrorDuringActiveStreamRecovery(inactive, null),
|
||||
false,
|
||||
"stale recovery projection must fail open to the error banner",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("reconnecting presentation is neutral, timed and exposes explicit local finish", () => {
|
||||
const state = recoveryState();
|
||||
const presentation = activeStreamRecoveryPresentation(state);
|
||||
assert.equal(presentation?.state, "reconnecting");
|
||||
assert.equal(presentation?.tone, "neutral");
|
||||
assert.equal(presentation?.progressLabel, "Попытка 3 · 12 с");
|
||||
assert.equal(presentation?.forceFinishAvailable, true);
|
||||
|
||||
const markup = renderPipeline(state);
|
||||
assert.match(markup, /Восстанавливаем соединение/);
|
||||
assert.match(markup, /Попытка 3 · 12 с/);
|
||||
assert.match(markup, /class="nodedc-activity-indicator/);
|
||||
assert.equal(buttonsWithText(markup, "Прервать соединение").length, 1);
|
||||
assert.match(markup, /START, STOP, Bluetooth и настройки устройства не отправляются/);
|
||||
assert.doesNotMatch(markup, /Ошибка локального приёмника/);
|
||||
assert.equal(shouldRenderK1OperationalPanels(state), true);
|
||||
});
|
||||
|
||||
test("spatial scene owns the same recovery spinner and explicit local finish", () => {
|
||||
const markup = renderSpatialControls(recoveryState());
|
||||
assert.match(markup, /Восстанавливаем соединение/);
|
||||
assert.match(markup, /Попытка 3 · 12 с/);
|
||||
assert.match(markup, /class="nodedc-activity-indicator/);
|
||||
assert.match(markup, /data-recovery-state="reconnecting"/);
|
||||
assert.equal(buttonsWithText(markup, "Прервать соединение").length, 1);
|
||||
assert.match(markup, /локальный front\/back-приём/);
|
||||
assert.doesNotMatch(
|
||||
markup,
|
||||
/Остановить устройство|Остановить K1|Завершить локальный приём/,
|
||||
"recovery must not expose canonical STOP or the generic receiver stop",
|
||||
);
|
||||
});
|
||||
|
||||
test("spatial blocked and fault recovery copy is terminal and truthful", () => {
|
||||
const blocked = renderSpatialControls(recoveryState({
|
||||
state: "blocked",
|
||||
reason_code: "exact-binding-changed",
|
||||
}));
|
||||
assert.match(blocked, /Связь не восстановлена/);
|
||||
assert.match(blocked, /Восстановление остановлено/);
|
||||
assert.doesNotMatch(blocked, /class="nodedc-activity-indicator/);
|
||||
assert.equal(buttonsWithText(blocked, "Прервать соединение").length, 1);
|
||||
|
||||
const fault = renderSpatialControls(recoveryState({
|
||||
state: "fault",
|
||||
force_finish_allowed: false,
|
||||
reason_code: "active-stream-recovery-system-error",
|
||||
}));
|
||||
assert.match(fault, /K1 сообщил об ошибке/);
|
||||
assert.match(fault, /Автоматических команд и повторов нет/);
|
||||
assert.equal(buttonsWithText(fault, "Прервать соединение").length, 0);
|
||||
});
|
||||
|
||||
test("spatial recovery interaction routes only to exact local force-finish", async () => {
|
||||
let forceFinishCalls = 0;
|
||||
const current = recoveryState();
|
||||
const invoked = await runSpatialActiveStreamForceFinish({
|
||||
state: current,
|
||||
forceFinishActiveStreamLocally: async () => {
|
||||
forceFinishCalls += 1;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
assert.equal(invoked, true);
|
||||
assert.equal(forceFinishCalls, 1);
|
||||
|
||||
const stale = structuredClone(current);
|
||||
stale.connection_recovery.runtime_producer_generation += 1;
|
||||
const rejected = await runSpatialActiveStreamForceFinish({
|
||||
state: stale,
|
||||
forceFinishActiveStreamLocally: async () => {
|
||||
forceFinishCalls += 1;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
assert.equal(rejected, false);
|
||||
assert.equal(forceFinishCalls, 1, "stale lineage must not dispatch any action");
|
||||
});
|
||||
|
||||
test("spatial force-finish pending owns the surface without a second action", () => {
|
||||
const markup = renderSpatialControls(
|
||||
recoveryState({
|
||||
state: "force-finishing",
|
||||
acquisition_id: null,
|
||||
force_finish_allowed: false,
|
||||
automatic_read_only_rebind: false,
|
||||
camera_recovery: "inactive",
|
||||
}),
|
||||
{ pendingAction: "force-finish" },
|
||||
);
|
||||
assert.match(markup, /Завершаем локальный приём/);
|
||||
assert.match(markup, /data-recovery-state="force-finishing"/);
|
||||
assert.match(markup, /Команда STOP устройству не отправляется/);
|
||||
assert.equal(buttonsWithText(markup, "Прервать соединение").length, 0);
|
||||
});
|
||||
|
||||
test("blocked, camera-blocked, standby and fault copy stay truthful", () => {
|
||||
const blocked = recoveryState({ state: "blocked", reason_code: "exact-binding-changed" });
|
||||
const blockedMarkup = renderPipeline(blocked);
|
||||
assert.match(blockedMarkup, /Связь не восстановлена/);
|
||||
assert.match(blockedMarkup, /Восстановление остановлено/);
|
||||
assert.doesNotMatch(blockedMarkup, /class="nodedc-activity-indicator/);
|
||||
assert.equal(buttonsWithText(blockedMarkup, "Прервать соединение").length, 1);
|
||||
|
||||
const cameraBlocked = recoveryState({
|
||||
state: "blocked",
|
||||
camera_recovery: "blocked",
|
||||
reason_code: "camera-recovery-failed",
|
||||
});
|
||||
assert.match(renderPipeline(cameraBlocked), /Видеопоток не восстановлен/);
|
||||
|
||||
const standby = recoveryState({
|
||||
state: "standby",
|
||||
force_finish_allowed: false,
|
||||
reason_code: "device-reported-standby",
|
||||
});
|
||||
const standbyMarkup = renderPipeline(standby);
|
||||
assert.match(standbyMarkup, /Устройство перешло в ожидание/);
|
||||
assert.match(standbyMarkup, /без команды STOP/);
|
||||
assert.equal(buttonsWithText(standbyMarkup, "Прервать соединение").length, 0);
|
||||
assert.equal(shouldRenderK1OperationalPanels(standby), true);
|
||||
|
||||
const fault = recoveryState({
|
||||
state: "fault",
|
||||
force_finish_allowed: false,
|
||||
reason_code: "active-stream-recovery-system-error",
|
||||
});
|
||||
const faultMarkup = renderPipeline(fault);
|
||||
assert.match(faultMarkup, /K1 сообщил об ошибке/);
|
||||
assert.match(faultMarkup, /Автоматических команд и повторов нет/);
|
||||
assert.equal(buttonsWithText(faultMarkup, "Прервать соединение").length, 0);
|
||||
assert.equal(shouldRenderK1OperationalPanels(fault), true);
|
||||
});
|
||||
|
||||
test("recovered active lineage renders the continued session and one exact STOP", () => {
|
||||
const state = recoveryState({
|
||||
state: "recovered",
|
||||
force_finish_allowed: false,
|
||||
elapsed_ms: null,
|
||||
reason_code: null,
|
||||
}, {
|
||||
phase: "live",
|
||||
compatibility: {
|
||||
vendor_writes_enabled: true,
|
||||
permitted_mode: "active-control",
|
||||
},
|
||||
application_control_session: {
|
||||
session_generation: 5,
|
||||
state_revision: 8,
|
||||
state: "scanning",
|
||||
can_stop: true,
|
||||
control_socket_open: true,
|
||||
},
|
||||
connection_policy: {
|
||||
schema_version: "missioncore.xgrids-k1-connection-policy/v1",
|
||||
facts: { retained_context_is_presence: false },
|
||||
allowed_actions: ["stop-acquisition"],
|
||||
actions: {
|
||||
"stop-acquisition": {
|
||||
allowed: true,
|
||||
reason_codes: [],
|
||||
target_source: "connection-supervisor",
|
||||
required_transport_ref: null,
|
||||
required_connection_mode: null,
|
||||
requires_live_gatt_validation: false,
|
||||
automatic_retry: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(activeStreamRecoveryPresentation(state), null);
|
||||
const markup = renderPipeline(state);
|
||||
assert.doesNotMatch(markup, /Восстанавливаем соединение|Прервать соединение/);
|
||||
assert.match(markup, /СВЯЗЬ ВОССТАНОВЛЕНА · АКТИВНЫЙ ПРИЁМ/);
|
||||
assert.match(markup, /Связь восстановлена · приём продолжается/);
|
||||
assert.match(markup, /Продолжаем тот же приём без нового START/);
|
||||
assert.doesNotMatch(markup, /Назовите проект и запустите приём|Запустить приём/);
|
||||
|
||||
const stopButtons = buttonsWithText(markup, "Остановить устройство и запись");
|
||||
assert.equal(stopButtons.length, 1);
|
||||
assert.doesNotMatch(stopButtons[0], /\bdisabled(?:=|\s|>)/);
|
||||
});
|
||||
|
||||
test("stale recovered marker on an idle released runtime fails closed to idle UI", () => {
|
||||
const state = recoveryState({
|
||||
state: "recovered",
|
||||
force_finish_allowed: false,
|
||||
elapsed_ms: null,
|
||||
reason_code: null,
|
||||
}, {
|
||||
phase: "idle",
|
||||
source_mode: "idle",
|
||||
acquisition: {
|
||||
...recoveryState().acquisition,
|
||||
state: "completed",
|
||||
cleanup_pending: false,
|
||||
},
|
||||
application_control_session: {
|
||||
session_generation: 5,
|
||||
state_revision: 9,
|
||||
state: "completed",
|
||||
can_stop: false,
|
||||
control_socket_open: false,
|
||||
},
|
||||
});
|
||||
assert.equal(activeStreamRecoveryPresentation(state), null);
|
||||
const markup = renderPipeline(state);
|
||||
assert.doesNotMatch(
|
||||
markup,
|
||||
/СВЯЗЬ ВОССТАНОВЛЕНА|Связь восстановлена · приём продолжается|Продолжаем тот же приём/,
|
||||
);
|
||||
assert.match(markup, /Назовите проект и запустите приём/);
|
||||
assert.equal(buttonsWithText(markup, "Остановить устройство и запись").length, 0);
|
||||
assert.equal(buttonsWithText(markup, "Остановить сканирование").length, 0);
|
||||
});
|
||||
|
||||
test("force-finishing shows one local-only pending owner and no second action", () => {
|
||||
const state = recoveryState({
|
||||
state: "force-finishing",
|
||||
acquisition_id: null,
|
||||
force_finish_allowed: false,
|
||||
automatic_read_only_rebind: false,
|
||||
runtime_producer_generation: 11,
|
||||
camera_recovery: "inactive",
|
||||
});
|
||||
const markup = renderPipeline(state, { pendingAction: "force-finish" });
|
||||
assert.match(markup, /Завершаем локальный приём/);
|
||||
assert.match(markup, /Команда STOP устройству не отправляется/);
|
||||
assert.match(markup, /class="nodedc-activity-indicator/);
|
||||
assert.equal(buttonsWithText(markup, "Прервать соединение").length, 0);
|
||||
});
|
||||
|
||||
test("elapsed presentation is deterministic", () => {
|
||||
assert.equal(formatActiveStreamRecoveryElapsed(null), null);
|
||||
assert.equal(formatActiveStreamRecoveryElapsed(-1), null);
|
||||
assert.equal(formatActiveStreamRecoveryElapsed(999), "0 с");
|
||||
assert.equal(formatActiveStreamRecoveryElapsed(59_999), "59 с");
|
||||
assert.equal(formatActiveStreamRecoveryElapsed(60_000), "1 мин");
|
||||
assert.equal(formatActiveStreamRecoveryElapsed(125_900), "2 мин 5 с");
|
||||
});
|
||||
|
||||
test("force-finish manifest/API sends the exact fenced local-only request", async () => {
|
||||
assert.equal(
|
||||
xgridsK1Actions.acquisitionForceFinishLocal,
|
||||
"acquisition.force-finish-local",
|
||||
);
|
||||
const request = {
|
||||
expected_snapshot_runtime_id: "snapshot-runtime-recovery-001",
|
||||
acquisition_id: "acquisition-recovery-001",
|
||||
expected_state_revision: 9,
|
||||
expected_recovery_generation: 7,
|
||||
operator_confirmed: true,
|
||||
operation_id: "op-00000000-0000-4000-8000-000000000321",
|
||||
idempotency_key:
|
||||
"acquisition.force-finish-local:op-00000000-0000-4000-8000-000000000321",
|
||||
deadline_seconds: 30,
|
||||
};
|
||||
let capturedUrl = null;
|
||||
let capturedInit = null;
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input, init) => {
|
||||
capturedUrl = String(input);
|
||||
capturedInit = init;
|
||||
return new Response(JSON.stringify({ state: recoveryState() }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
};
|
||||
try {
|
||||
await xgridsK1Api.forceFinishAcquisitionLocally(request);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
assert.match(
|
||||
capturedUrl,
|
||||
/\/actions\/acquisition\.force-finish-local$/,
|
||||
);
|
||||
assert.equal(capturedInit.method, "POST");
|
||||
assert.deepEqual(JSON.parse(capturedInit.body), { input: request });
|
||||
});
|
||||
|
||||
test("state API rejects a drifted active recovery contract", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => new Response(JSON.stringify({
|
||||
state: recoveryState({ stop_performed: true }),
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => xgridsK1Api.getState(),
|
||||
/некорректное состояние/,
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("frontend boundary keeps recovery automatic work read-only and local finish explicit", () => {
|
||||
const hookSource = readFileSync(hookSourceUrl, "utf8");
|
||||
const forceFinish = sourceSlice(
|
||||
hookSource,
|
||||
"const forceFinishActiveStreamLocally",
|
||||
"const abort",
|
||||
);
|
||||
assert.match(forceFinish, /run\("force-finish"/);
|
||||
assert.match(forceFinish, /activeStreamForceFinishAuthority\(latestState\.current\)/);
|
||||
assert.match(forceFinish, /expected_snapshot_runtime_id:\s*authority\.snapshotRuntimeId/);
|
||||
assert.match(forceFinish, /acquisition_id:\s*authority\.acquisitionId/);
|
||||
assert.match(forceFinish, /expected_state_revision:\s*authority\.acquisitionStateRevision/);
|
||||
assert.match(forceFinish, /expected_recovery_generation:\s*authority\.recoveryGeneration/);
|
||||
assert.match(forceFinish, /operator_confirmed:\s*true/);
|
||||
assert.match(
|
||||
forceFinish,
|
||||
/newMutationContext\("acquisition\.force-finish-local"\)/,
|
||||
);
|
||||
assert.equal(
|
||||
(forceFinish.match(/forceFinishAcquisitionLocally\(/g) ?? []).length,
|
||||
1,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
forceFinish,
|
||||
/startAcquisition|stopAcquisition|scanBle|selectCameraPreview|connect\(/,
|
||||
);
|
||||
|
||||
const acquisitionSource = readFileSync(acquisitionSourceUrl, "utf8");
|
||||
assert.equal(
|
||||
(acquisitionSource.match(/forceFinishActiveStreamLocally\(\)/g) ?? []).length,
|
||||
1,
|
||||
"the explicit recovery button is the only frontend caller",
|
||||
);
|
||||
const recoverySurface = readFileSync(recoverySurfaceSourceUrl, "utf8");
|
||||
assert.match(recoverySurface, /<ActivityIndicator/);
|
||||
assert.match(recoverySurface, /<Button[\s\S]*?variant="secondary"/);
|
||||
assert.doesNotMatch(recoverySurface, /<button\b|style=\{/);
|
||||
|
||||
const spatialSource = readFileSync(spatialControlsSourceUrl, "utf8");
|
||||
assert.match(spatialSource, /variant="compact"/);
|
||||
assert.match(
|
||||
spatialSource,
|
||||
/runSpatialActiveStreamForceFinish\(\{[\s\S]*?forceFinishActiveStreamLocally/,
|
||||
);
|
||||
const spatialForceFinish = sourceSlice(
|
||||
spatialSource,
|
||||
"export function runSpatialActiveStreamForceFinish",
|
||||
"export function K1SpatialControlsView",
|
||||
);
|
||||
assert.match(spatialForceFinish, /activeStreamForceFinishAuthority\(controller\.state\)/);
|
||||
assert.equal(
|
||||
(spatialForceFinish.match(/controller\.forceFinishActiveStreamLocally\(\)/g) ?? []).length,
|
||||
1,
|
||||
);
|
||||
assert.doesNotMatch(spatialForceFinish, /\bstop\(|stopLocalReceiver|start|connect|scanBle/);
|
||||
|
||||
const connectionSource = readFileSync(connectionSourceUrl, "utf8");
|
||||
assert.match(
|
||||
connectionSource,
|
||||
/shouldRenderK1GenericRuntimeError\([\s\S]*?errorCorrelation\?\.action/,
|
||||
);
|
||||
assert.match(
|
||||
connectionSource,
|
||||
/\{showGenericRuntimeError && error \? \([\s\S]*?<K1OperatorError/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user