Files
NODEDC_MISSION_CORE/apps/control-station/test/devicePluginFrontendBoundary.test.mjs
T
DCCONSTRUCTIONS 1c7dd29d8a chore(node): preserve pre-canonicalization experiment snapshot
Historical working copy retained for audit before consolidation into main. The canonicalized plugin architecture and later fixes already live in main; this snapshot is not a release or a request to restore obsolete source layout.
2026-09-21 08:45:34 +03:00

1162 lines
50 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join, resolve } from "node:path";
import { after, before, test } from "node:test";
import { createServer } from "vite";
const testRoot = dirname(fileURLToPath(import.meta.url));
const controlStationRoot = resolve(testRoot, "..");
const repositoryRoot = resolve(controlStationRoot, "../..");
const coreSourceRoot = join(controlStationRoot, "src");
const pluginFrontendRoot = join(repositoryRoot, "plugins/xgrids-k1/frontend/src");
const legacyPluginRoot = join(coreSourceRoot, "device-plugins/xgrids-k1");
function sourceFiles(root) {
return readdirSync(root).flatMap((entry) => {
const path = join(root, entry);
if (statSync(path).isDirectory()) return sourceFiles(path);
return /\.(?:css|ts|tsx)$/.test(entry) ? [path] : [];
});
}
let server;
let createDevicePluginRegistry;
before(async () => {
server = await createServer({
appType: "custom",
logLevel: "silent",
server: { middlewareMode: true },
});
({ createDevicePluginRegistry } = await server.ssrLoadModule(
"/src/core/device-plugins/registry.ts",
));
});
after(async () => {
await server?.close();
});
function syntheticPlugin({
pluginId,
modelId,
componentKey,
connectionView,
spatialControlsView,
}) {
return {
manifest: {
apiVersion: "missioncore.nodedc/v1alpha2",
kind: "DevicePlugin",
metadata: { id: pluginId, version: "1.0.0", displayName: pluginId },
spec: {
hostApiRange: "v1alpha2",
runtime: { backendEntrypoint: `${pluginId}:build`, isolation: "transitional-in-process" },
permissions: ["device.read"],
actions: [{ id: "state.read", mutating: false, secretFields: [] }],
compatibilityProfiles: [{
profileId: `${modelId}.profile.v1`,
path: `profiles/${modelId}.json`,
modelId,
}],
models: [{
id: modelId,
vendor: pluginId,
displayName: modelId,
category: "Sensor",
description: "Synthetic frontend contribution",
verified: true,
capabilities: [{ id: "device.read", label: "Read" }],
ui: { slot: "device.connection", componentKey },
}],
},
},
RuntimeProvider: ({ children }) => children,
connectionViews: { [componentKey]: connectionView },
...(spatialControlsView ? { SpatialControlsView: spatialControlsView } : {}),
};
}
test("each device plugin contributes its own connection pipeline component", () => {
const alphaConnection = () => null;
const betaConnection = () => null;
const registry = createDevicePluginRegistry([
syntheticPlugin({
pluginId: "synthetic.alpha",
modelId: "synthetic.alpha.sensor",
componentKey: "alpha.connection",
connectionView: alphaConnection,
}),
syntheticPlugin({
pluginId: "synthetic.beta",
modelId: "synthetic.beta.sensor",
componentKey: "beta.connection",
connectionView: betaConnection,
}),
]);
assert.equal(registry.resolveModel("synthetic.alpha.sensor").ConnectionView, alphaConnection);
assert.equal(registry.resolveModel("synthetic.beta.sensor").ConnectionView, betaConnection);
assert.notEqual(
registry.resolveModel("synthetic.alpha.sensor").ConnectionView,
registry.resolveModel("synthetic.beta.sensor").ConnectionView,
);
});
test("selected-model shell leaves the model name to the connection heading", () => {
const workspace = readFileSync(
join(coreSourceRoot, "workspaces/DeviceWorkspace.tsx"),
"utf8",
);
const app = readFileSync(join(coreSourceRoot, "App.tsx"), "utf8");
const selectedSlot = workspace.slice(workspace.indexOf("const ConnectionView ="));
assert.match(workspace, /<h3>\{model\.displayName\}<\/h3>/);
assert.match(selectedSlot, /СЦЕНАРИЙ ПОДКЛЮЧЕНИЯ/);
assert.match(selectedSlot, /<strong>Модель выбрана<\/strong>/);
assert.doesNotMatch(selectedSlot, /selection\.model\.displayName/);
assert.doesNotMatch(
selectedSlot,
/selection\.plugin\.manifest\.metadata\.displayName/,
);
const localContour = app.slice(
app.indexOf('id: "local-contour"'),
app.indexOf("items={rootWorkspaces", app.indexOf('id: "local-contour"')),
);
assert.match(
localContour,
/description: selection \? "Подключение" : "Модель не выбрана"/,
);
assert.doesNotMatch(localContour, /activeDevice\?\.endpointLabel/);
assert.doesNotMatch(localContour, /selection\?\.model\.displayName/);
});
test("registry exposes an optional model-scoped spatial controls contribution", () => {
const connectionView = () => null;
const spatialControlsView = () => null;
const registry = createDevicePluginRegistry([
syntheticPlugin({
pluginId: "synthetic.spatial",
modelId: "synthetic.spatial.sensor",
componentKey: "spatial.connection",
connectionView,
spatialControlsView,
}),
syntheticPlugin({
pluginId: "synthetic.connection-only",
modelId: "synthetic.connection-only.sensor",
componentKey: "connection-only.connection",
connectionView,
}),
]);
assert.equal(
registry.resolveModel("synthetic.spatial.sensor").SpatialControlsView,
spatialControlsView,
);
assert.equal(
registry.resolveModel("synthetic.connection-only.sensor").SpatialControlsView,
undefined,
);
});
test("XGRIDS frontend is physically plugin-owned and split by operator pipeline", () => {
if (existsSync(legacyPluginRoot)) {
assert.deepEqual(readdirSync(legacyPluginRoot), []);
}
for (const relativePath of [
"plugin.ts",
"runtimeContext.tsx",
"styles.css",
"components/K1ProvisioningPipeline.tsx",
"components/K1AcquisitionPipeline.tsx",
"components/K1SpatialControls.tsx",
"components/K1Diagnostics.tsx",
"physicalCommandConfirmation.ts",
"projectName.ts",
]) {
assert.equal(existsSync(join(pluginFrontendRoot, relativePath)), true, relativePath);
}
const spatialControls = readFileSync(
join(pluginFrontendRoot, "components/K1SpatialControls.tsx"),
"utf8",
);
assert.match(spatialControls, /shouldRenderSpatialControls\(state\)/);
assert.match(spatialControls, /cleanup_pending/);
assert.match(spatialControls, /spatialActionFailure/);
assert.match(spatialControls, /role="alert"/);
assert.doesNotMatch(spatialControls, /Повторить остановку/);
assert.match(spatialControls, /stopLocalReceiver/);
assert.match(spatialControls, /<ActivityIndicator size="compact"/);
assert.match(spatialControls, /aria-busy=\{phase\.busy\}/);
assert.match(spatialControls, /K1 калибруется и готовит облако точек/);
assert.match(spatialControls, /Первые данные могут появиться через десятки секунд/);
assert.match(spatialControls, /Не перемещайте устройство/);
const styles = readFileSync(join(pluginFrontendRoot, "styles.css"), "utf8");
assert.doesNotMatch(styles, /@keyframes xgrids-k1-spin/);
});
test("K1 connection surface enforces one scan, local selection, and one Apply", () => {
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
const lifecycle = readFileSync(join(pluginFrontendRoot, "lifecycle.ts"), "utf8");
const submitPrerequisites = lifecycle.slice(
lifecycle.indexOf("export function canSubmitProvisioningMutation"),
lifecycle.indexOf("export function canAdmitProvisioningConnection"),
);
assert.match(provisioning, /bridge:\s*\{/);
assert.match(
provisioning,
/compatibility_attestation: profileSelectionForConnectionMode\([\s\S]*attemptedConnectionMode/,
);
assert.match(provisioning, /scanSecondsRemaining/);
assert.match(provisioning, /setInterval\(updateCountdown, 250\)/);
assert.match(provisioning, /Поиск Bluetooth · до \{scanSecondsRemaining \?\? BLE_DISCOVERY_TIMEOUT_SECONDS\} с/);
assert.match(provisioning, /scanWithResult\(\{[^}]*durationSeconds:\s*BLE_DISCOVERY_TIMEOUT_SECONDS/);
assert.doesNotMatch(provisioning, /K1 уже доступен|Сетевой адрес K1 доступен/);
assert.doesNotMatch(
provisioning,
/automaticRecovery|automaticDiscovery|reconnectFallbackAction/,
);
assert.match(provisioning, /Переподключиться/);
assert.match(provisioning, /Подключить новый K1/);
assert.doesNotMatch(provisioning, /Вернуть прежний K1 и проверить/);
assert.match(provisioning, /explicitProvisioningDraftMatches/);
assert.doesNotMatch(
provisioning,
/powerConfirmed|powerConfirmationEpoch|resetPowerConfirmation|Питание включено|title="Питание"/,
);
assert.doesNotMatch(submitPrerequisites, /power|питани/i);
assert.match(provisioning, /ПОДКЛЮЧЕНИЕ · ШАГИ 01–02/);
assert.match(
provisioning,
/number="01"[\s\S]*?title="Подключение"/,
);
assert.match(provisioning, /number="02"[\s\S]*?title="Сеть"/);
assert.doesNotMatch(provisioning, /number="03"|showDeviceStep/);
assert.match(
provisioning,
/const backendScanAllowed = scanAllowedByPolicy\s*&& !isBusy\s*&& !networkRecoveryRequired/,
);
assert.match(provisioning, /const showNetworkStep = Boolean\([\s\S]*explicitProvisioningDraftRetained/);
assert.match(
provisioning,
/if \(result\.networkIntentCompleted\)[\s\S]*setExplicitProvisioningDraft\(null\)/,
);
assert.equal(
(provisioning.match(/buttonLabel:\s*"Применить"/g) ?? []).length,
3,
);
assert.doesNotMatch(provisioning, /<(?:button|input|select|textarea)\b/);
assert.doesNotMatch(provisioning, /allow_host_wifi_switch/);
assert.doesNotMatch(provisioning, /(?:color|background(?:-color)?):\s*(?:#[0-9a-f]{3,8}|rgba?\()/i);
for (const sharedControl of ["Button", "IconButton", "TextField", "ActivityIndicator", "StatusBadge"]) {
assert.match(provisioning, new RegExp(`<${sharedControl}\\b`), sharedControl);
}
assert.doesNotMatch(
provisioning,
/Подключиться к сохранённому|Исходный K1|Проверить связь с K1|Проверить прежнее подключение/,
);
});
test("K1 click-owned actions are fenced without hidden frontend continuations", () => {
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
const search = provisioning.slice(
provisioning.indexOf("const repeatDeviceScan"),
provisioning.indexOf("const submitConnect"),
);
const apply = provisioning.slice(
provisioning.indexOf("const submitConnect"),
provisioning.indexOf("const verifyAppliedNetwork"),
);
assert.equal((search.match(/scanWithResult\(/g) ?? []).length, 1);
assert.match(search, /durationSeconds:\s*BLE_DISCOVERY_TIMEOUT_SECONDS/);
assert.equal((apply.match(/await connect\(/g) ?? []).length, 1);
assert.doesNotMatch(
apply,
/scanWithResult\(|verifyConnection\(|candidateRefresh|void submitConnect/,
);
assert.match(runtime, /class SnapshotRuntimeActionArbiter/);
assert.match(runtime, /runtimeActionArbiter\.current\.isCurrent\(actionToken\)/);
assert.match(runtime, /runtimeActionArbiter\.current\.settle\(actionToken\)/);
});
test("connected presentation uses canonical process copy", () => {
const connection = readFileSync(
join(pluginFrontendRoot, "XgridsK1Connection.tsx"),
"utf8",
);
assert.match(
connection,
/connectionTopology\?\.status === "active"\s*\? "Подключение установлено"/,
);
assert.match(connection, /"Готово к новой сессии\."/);
assert.match(connection, /<h2>Подключение \{model\.displayName\}<\/h2>/);
assert.match(
connection,
/const operationalPanelsVisible = shouldRenderK1OperationalPanels\(\s*state,\s*controller\.pendingAction,\s*\)/,
);
assert.match(
connection,
/operationalPanelsVisible \? <K1Metrics controller=\{controller\} \/> : null/,
);
assert.match(
connection,
/<K1ProvisioningPipeline[\s\S]*?operationalPanelsVisible \? \([\s\S]*?<K1AcquisitionPipeline[\s\S]*?<K1Diagnostics/,
);
assert.match(connection, /hasControlAuthority\(state\)/);
assert.match(connection, /requiresCanonicalStopAfterTerminalLocalFailure\(state\)/);
assert.match(connection, /state\?\.acquisition\?\.cleanup_pending === true/);
assert.doesNotMatch(
connection,
/Связь с K1 подтверждена|Требуется переподключение к K1|Устройство готово/,
);
});
test("local connection page owns model naming and process-only shell copy", () => {
const app = readFileSync(join(coreSourceRoot, "App.tsx"), "utf8");
const shellPresentation = readFileSync(
join(coreSourceRoot, "presentation.ts"),
"utf8",
);
const connection = readFileSync(
join(pluginFrontendRoot, "XgridsK1Connection.tsx"),
"utf8",
);
const operatorError = readFileSync(
join(pluginFrontendRoot, "components/K1OperatorError.tsx"),
"utf8",
);
const acquisition = readFileSync(
join(pluginFrontendRoot, "components/K1AcquisitionPipeline.tsx"),
"utf8",
);
const metrics = readFileSync(
join(pluginFrontendRoot, "components/K1Metrics.tsx"),
"utf8",
);
assert.match(connection, /<h2>Подключение \{model\.displayName\}<\/h2>/);
assert.doesNotMatch(connection, /:\s*message\}/);
assert.match(
operatorError,
/Подключение не завершено\. Автоматического повтора не было/,
);
const deviceHeader = app.slice(
app.indexOf('activeDefinition.kind === "device" ? ('),
app.indexOf('activeDefinition.kind === "spatial"', app.indexOf('activeDefinition.kind === "device" ? (')),
);
assert.match(deviceHeader, /localConnectionPhaseLabel\(runtime\.state\?\.phase\)/);
assert.doesNotMatch(deviceHeader, /phaseLabel\(runtime\.state\?\.phase\)/);
assert.match(
shellPresentation,
/phase === "configuring"\) return "Подключение"/,
);
assert.match(
shellPresentation,
/phase === "connected"\) return "Подключение установлено"/,
);
assert.match(shellPresentation, /configuring: "Настройка устройства"/);
assert.match(shellPresentation, /connected: "Устройство подключено"/);
assert.match(acquisition, /hint="Локальный файл записи"/);
assert.match(acquisition, /Состояние сканирования остаётся неизвестным/);
assert.doesNotMatch(
acquisition,
/Локальный файл исходных данных|Физическое состояние сканера/,
);
assert.match(metrics, /Данные потока при этом сохраняются/);
assert.doesNotMatch(metrics, /Исходные данные при этом сохраняются/);
});
test("K1 START renders an in-button spinner for the complete live orchestration", () => {
const acquisition = readFileSync(
join(pluginFrontendRoot, "components/K1AcquisitionPipeline.tsx"),
"utf8",
);
assert.match(acquisition, /pendingAction === "live"[\s\S]*<ActivityIndicator size="compact"/);
assert.match(acquisition, /aria-busy=\{pendingAction === "live"\}/);
assert.doesNotMatch(acquisition, /readOnlyConnectionObservationTarget\(state\)/);
assert.match(acquisition, /appliedTopology\?\.status === "active"\s*&& desiredModeMatchesActive/);
assert.match(acquisition, /if \(!draftPreparationTarget\) return;/);
assert.doesNotMatch(acquisition, /START не используется для установки связи/);
assert.match(acquisition, /Синхронизация…/);
});
test("Connect accepts exact network-applied REST state while START still requires exact ready", () => {
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
const exactReady = runtime.slice(
runtime.indexOf("function hasExactConnectionReady"),
runtime.indexOf("async function waitForPhysicalReconciliationProof"),
);
const connectFlow = runtime.slice(
runtime.indexOf("const connect = useCallback"),
runtime.indexOf("const verifyConnection = useCallback"),
);
const appliedProof = runtime.slice(
runtime.indexOf("export function exactAppliedNetworkIntentCompleted"),
runtime.indexOf("function requireExactReadOnlyVerificationOutcome"),
);
assert.match(exactReady, /state\.desired_connection_mode === connectionMode/);
assert.match(exactReady, /state\.active_connection_mode === connectionMode/);
assert.match(exactReady, /application_control_session\?\.state === "connection-ready"/);
assert.match(exactReady, /currentAppliedConnectionTopology\(state, connectionMode\)\?\.status === "active"/);
assert.doesNotMatch(connectFlow, /openApplicationControlSession|waitForControlPhase/);
assert.doesNotMatch(connectFlow, /verifyConnection\(|scanWithResult\(/);
assert.doesNotMatch(connectFlow, /startAcquisition|startPreparedAcquisition|START acquisition/);
assert.match(connectFlow, /networkIntentCompleted/);
assert.match(appliedProof, /attempt\.phase === "network_applied"/);
assert.match(appliedProof, /operation\.status === "succeeded"/);
assert.match(appliedProof, /operationPhase === "network_applied"/);
assert.match(appliedProof, /ledger\.resolution === "target-observed"/);
assert.match(appliedProof, /deviceNetwork\?\.state === "applied"/);
assert.doesNotMatch(appliedProof, /control_state/);
assert.match(
connectFlow,
/const exactNetworkIntentCompleted = exactAppliedNetworkIntentCompleted\([\s\S]*?if \(\s*!exactNetworkIntentCompleted\s*&& !hasExactConnectionReady\([\s\S]*?return requireExactConnectionReady\([\s\S]*?nextState = acceptSuccessfulConnectState\(nextState\)/,
);
assert.match(provisioning, /Подключение установлено/);
assert.doesNotMatch(
provisioning,
/Подключиться к сохранённому|Проверить связь с K1|Проверить прежнее подключение/,
);
assert.match(provisioning, /Переподключиться/);
assert.match(provisioning, /Подключить новый K1/);
assert.match(provisioning, /expected_mode_revision: modeAuthority\.desiredModeRevision/);
assert.match(
provisioning,
/expected_discovery_generation: modeAuthority\.discoveryGeneration/,
);
assert.doesNotMatch(provisioning, /Настройки сети применены/);
});
test("K1 mode reset is explicit while Scan and Apply keep exact backend CAS", () => {
const api = readFileSync(join(pluginFrontendRoot, "api.ts"), "utf8");
const manifest = readFileSync(join(pluginFrontendRoot, "manifest.ts"), "utf8");
const connection = readFileSync(join(pluginFrontendRoot, "XgridsK1Connection.tsx"), "utf8");
const runtime = readFileSync(join(pluginFrontendRoot, "useXgridsK1Runtime.ts"), "utf8");
const acquisition = readFileSync(
join(pluginFrontendRoot, "components/K1AcquisitionPipeline.tsx"),
"utf8",
);
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
assert.match(manifest, /connectionModeSelect:[\s\S]*"connection\.mode\.select"/);
assert.match(api, /interface SelectConnectionModeRequest/);
assert.match(api, /expected_revision: number/);
assert.match(api, /reset_scenario\?: true/);
assert.match(api, /reset_id\?: string/);
assert.match(api, /expected_mode_revision: number/);
assert.match(api, /expected_discovery_generation: number/);
const localModeHandler = connection.slice(
connection.indexOf("const updateDesiredConnectionMode"),
connection.indexOf("const sourceTone"),
);
assert.match(localModeHandler, /setDesiredConnectionMode\(mode\)/);
assert.doesNotMatch(localModeHandler, /await|selectConnectionMode\(|refresh\(|connect\(/);
assert.match(provisioning, /const commitDesiredModeForExplicitAction = useCallback\(async/);
assert.match(provisioning, /await selectConnectionMode\(\{/);
assert.match(provisioning, /expected_revision: expectedRevision as number/);
assert.match(provisioning, /reset_scenario: true/);
assert.match(provisioning, /const resetId = newOperationId\(\)/);
assert.match(provisioning, /reset_id: resetId/);
assert.match(provisioning, /Подключить новый K1/);
const configurationAnchor = provisioning.slice(
provisioning.indexOf('<div className="configuration-anchor">'),
provisioning.indexOf('<div className="wizard-list">'),
);
assert.doesNotMatch(configurationAnchor, /Подключить новый K1/);
assert.match(provisioning, /value=\{connectionMode\}/);
assert.doesNotMatch(
provisioning,
/disabled=\{\s*isBusy\s*\|\|\s*networkRecoveryRequired\s*\|\|\s*physicalRecoveryRequired\s*\|\|\s*connectionRecoveryRequired/,
);
assert.match(runtime, /catch \(selectionError\)[\s\S]*xgridsK1Api\.getState\(\)/);
assert.match(acquisition, /state\?\.active_connection_mode/);
assert.match(acquisition, /desiredSelectionCommitted/);
assert.match(acquisition, /configuredConnectionMode !== desiredConnectionMode/);
assert.doesNotMatch(acquisition, /Выбран другой способ связи/);
});
test("top-right device utility is an explicit pending-aware K1 scenario reset", async () => {
const { deviceRuntimeUtilityAction } = await server.ssrLoadModule(
"/src/components/useApplicationPanelActions.ts",
);
let resetCalls = 0;
let refreshCalls = 0;
const reset = deviceRuntimeUtilityAction({
refreshRuntime: () => {
refreshCalls += 1;
},
resetConnectionScenario: async () => {
resetCalls += 1;
return true;
},
connectionScenarioResetting: false,
});
assert.equal(reset.label, "Сбросить подключение");
assert.equal(reset.icon, "refresh");
assert.equal(reset.disabled, undefined);
reset.onClick();
await Promise.resolve();
assert.equal(resetCalls, 1);
assert.equal(refreshCalls, 0);
const pending = deviceRuntimeUtilityAction({
refreshRuntime: () => {
refreshCalls += 1;
},
resetConnectionScenario: async () => true,
connectionScenarioResetting: true,
});
assert.equal(pending.label, "Сбрасываем подключение");
assert.equal(pending.icon, "activity");
assert.equal(pending.disabled, true);
pending.onClick();
await Promise.resolve();
assert.equal(resetCalls, 1);
assert.equal(refreshCalls, 0);
const app = readFileSync(join(coreSourceRoot, "App.tsx"), "utf8");
const contracts = readFileSync(
join(coreSourceRoot, "core/runtime/contracts.ts"),
"utf8",
);
const runtimeContext = readFileSync(
join(pluginFrontendRoot, "runtimeContext.tsx"),
"utf8",
);
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
const connection = readFileSync(
join(pluginFrontendRoot, "XgridsK1Connection.tsx"),
"utf8",
);
assert.match(contracts, /resetConnectionScenario\?: \(\) => Promise<boolean>/);
assert.match(app, /resetConnectionScenario: runtime\.resetConnectionScenario/);
assert.match(app, /connectionScenarioResetting: runtime\.pendingAction === "mode"/);
assert.match(
runtimeContext,
/resetConnectionScenario: controller\.resetConnectionScenario/,
);
const resetStart = runtime.indexOf("const resetConnectionScenario");
const resetEnd = runtime.indexOf(
"const prepareConnectionReconfigurationWithResult",
resetStart,
);
assert.notEqual(resetStart, -1);
assert.notEqual(resetEnd, -1);
const resetFlow = runtime.slice(resetStart, resetEnd);
assert.equal((resetFlow.match(/selectConnectionMode\(/g) ?? []).length, 1);
assert.match(resetFlow, /connection_mode: DEFAULT_CONNECTION_MODE/);
assert.match(resetFlow, /expected_revision: expectedRevision as number/);
assert.match(resetFlow, /reset_scenario: true/);
assert.match(resetFlow, /reset_id: newOperationId\(\)/);
assert.doesNotMatch(
resetFlow,
/refresh\(|getState\(|scan|verify|connect\(|prepare|start|stop|camera/i,
);
const selectStart = runtime.indexOf("const selectConnectionMode");
const selectEnd = runtime.indexOf("const resetConnectionScenario", selectStart);
const selectFlow = runtime.slice(selectStart, selectEnd);
assert.match(selectFlow, /expected_snapshot_runtime_id: expectedSnapshotRuntimeId\(\)/);
assert.match(selectFlow, /supersedePending: request\.reset_scenario === true/);
const runStart = runtime.indexOf("const run = useCallback");
const runEnd = runtime.indexOf("const scanWithResult", runStart);
const runFlow = runtime.slice(runStart, runEnd);
assert.match(runFlow, /setPendingAction\(action\)/);
assert.match(runFlow, /setPresentedErrorCorrelation\(null\)/);
assert.match(runFlow, /setError\(null\)/);
assert.match(runFlow, /setErrorDiagnostic\(null\)/);
assert.match(runFlow, /setPendingAction\(null\)/);
const draftFenceStart = provisioning.indexOf("const nextFence = localProvisioningDraftFenceKey");
assert.notEqual(draftFenceStart, -1);
const draftFence = provisioning.slice(draftFenceStart, draftFenceStart + 2_700);
assert.match(draftFence, /reconfigurationRevision/);
assert.match(draftFence, /setSelectedDeviceId\(""\)/);
assert.match(draftFence, /setSelectedDeviceSnapshot\(null\)/);
assert.match(draftFence, /setExplicitProvisioningDraft\(null\)/);
assert.match(draftFence, /setSsid\(""\)/);
assert.match(draftFence, /setPassword\(""\)/);
assert.match(draftFence, /setCandidateUnavailableMessage\(null\)/);
assert.match(draftFence, /resetSearchPresentation\(\)/);
assert.match(
provisioning,
/const hydratedScenarioResetPresentationKey = useRef<string \| null>\(null\)/,
);
assert.match(
provisioning,
/hydratedScenarioResetPresentationKey\.current = scenarioResetPresentationKey/,
);
assert.match(provisioning, /setConnectionAttemptPresentation\(null\)/);
assert.match(
provisioning,
/const modeResetInFlight = pendingAction === "mode" \|\| modeResetPending !== null/,
);
assert.match(provisioning, /if \(modeResetInFlight\) return/);
assert.equal((provisioning.match(/disabled=\{modeResetInFlight\}/g) ?? []).length, 1);
assert.match(provisioning, /disabled=\{isBusy \|\| modeResetInFlight\}/);
assert.match(connection, /const hydratedScenarioResetKey = useRef<string \| null>\(null\)/);
assert.match(
connection,
/scenarioReset\.revision === state\?\.desired_connection_mode_revision/,
);
assert.match(connection, /scenarioReset\.desired_mode === backendDesiredMode/);
assert.match(connection, /desiredModeLocallyDirty\.current = false/);
assert.match(connection, /setDesiredConnectionMode\(backendDesiredMode\)/);
});
test("background polling stays read-only while backend state reconciliation retires terminal control", () => {
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
const refreshFlow = runtime.slice(
runtime.indexOf("const refresh = useCallback"),
runtime.indexOf("const run = useCallback"),
);
assert.match(runtime, /refresh\(false\)/);
assert.match(runtime, /refresh\(true\)/);
assert.doesNotMatch(runtime, /terminalControlCleanupInFlight/);
assert.doesNotMatch(
refreshFlow,
/closeApplicationControlSession|startAcquisition|stopAcquisition|networkProvision/,
);
assert.match(refreshFlow, /xgridsK1Api\.getState\(\)/);
});
test("automatic K1 live start opens the selected delivered camera despite an older saved layout", () => {
const app = readFileSync(join(coreSourceRoot, "App.tsx"), "utf8");
const layout = readFileSync(
join(coreSourceRoot, "core/observation/useObservationLayout.ts"),
"utf8",
);
assert.match(app, /observationLayout\.activateAutomaticDefaults\(\)/);
assert.match(layout, /const activateAutomaticDefaults = useCallback/);
assert.match(layout, /restoredLayoutAuthorityRef\.current = false/);
assert.match(layout, /sources\.filter\(canOpenByDefault\)/);
assert.match(layout, /source\.capabilities\.overlay/);
});
test("K1 connect errors preserve the result and expose explicit recovery", () => {
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
const connectStart = runtime.indexOf("const connect = useCallback(");
const connectEnd = runtime.indexOf("const verifyConnection = useCallback(", connectStart);
assert.notEqual(connectStart, -1);
assert.notEqual(connectEnd, -1);
const connectFlow = runtime.slice(connectStart, connectEnd);
assert.match(connectFlow, /observeProvisioningRequest\(/);
assert.match(connectFlow, /operation\?\.status !== "succeeded"/);
assert.match(connectFlow, /networkProvisionFailureMessage\(/);
assert.doesNotMatch(
connectFlow,
/требует ручной проверки|измените параметры только после проверки устройства|Сохранён тот же ключ/,
);
const networkFlow = readFileSync(join(pluginFrontendRoot, "networkProvisioning.ts"), "utf8");
assert.match(networkFlow, /Проверьте состояние K1 через «Переподключиться»/);
assert.match(networkFlow, /новое подключение через поиск Bluetooth/);
assert.equal((networkFlow.match(/await context\.send\(/g) ?? []).length, 1);
const verifyStart = runtime.indexOf("const verifyConnection = useCallback(");
const verifyEnd = runtime.indexOf("const probeConfiguredEndpoint = useCallback(", verifyStart);
const verifyFlow = runtime.slice(verifyStart, verifyEnd);
assert.match(verifyFlow, /failedOperation\?\.status === "succeeded"/);
assert.doesNotMatch(verifyFlow, /xgridsK1Api\.verifyConnection\([^)]*\)[\s\S]*xgridsK1Api\.verifyConnection/);
assert.doesNotMatch(runtime, /automatic.?retry\s*:\s*true/);
});
test("K1 provisioning keeps the operator draft separate from the backend lease", () => {
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
const selection = provisioning.slice(
provisioning.indexOf("const selectFreshDevice"),
provisioning.indexOf("const chooseAnother"),
);
const apply = provisioning.slice(
provisioning.indexOf("const submitConnect"),
provisioning.indexOf("const verifyAppliedNetwork"),
);
assert.match(provisioning, /explicitProvisioningDraftRetained/);
assert.match(provisioning, /localProvisioningDraftFenceKey/);
assert.match(provisioning, /provisioningIntentKey\(null\)/);
assert.match(provisioning, /const \[selectedDeviceSnapshot, setSelectedDeviceSnapshot\]/);
assert.doesNotMatch(selection, /await|scanWithResult\(|verifyConnection\(|connect\(/);
assert.match(selection, /requestExplicitProvisioning\(device\.device_id/);
assert.equal((apply.match(/await connect\(/g) ?? []).length, 1);
assert.doesNotMatch(apply, /scanWithResult\(|verifyConnection\(|candidateRefresh/);
assert.match(provisioning, /scanWithResult\(\{ durationSeconds: BLE_DISCOVERY_TIMEOUT_SECONDS \}\)/);
assert.match(
provisioning,
/onChange=\{\(event\) => setPassword\(event\.target\.value\)\}/,
);
});
test("K1 plugin layout follows its contribution width and contains long topology text", () => {
const styles = readFileSync(join(pluginFrontendRoot, "styles.css"), "utf8");
const baseGrid = styles.slice(
styles.indexOf(".device-workspace__grid"),
styles.indexOf(".device-workspace__side"),
);
const splitThreshold = styles.match(
/@container xgrids-k1 \(min-width:\s*([0-9.]+)rem\)/,
);
const splitColumns = styles.match(
/grid-template-columns:\s*minmax\(([0-9.]+)rem,\s*0\.8fr\)\s*minmax\(([0-9.]+)rem,\s*1\.2fr\)/,
);
assert.match(styles, /container:\s*xgrids-k1\s*\/\s*inline-size/);
assert.match(styles, /container:\s*k1-connection-panel\s*\/\s*inline-size/);
assert.match(styles, /container:\s*k1-session-panel\s*\/\s*inline-size/);
assert.match(styles, /@container xgrids-k1 \(min-width:\s*78rem\)/);
assert.match(
styles,
/grid-template-columns:\s*minmax\(32rem,\s*0\.8fr\)\s*minmax\(38rem,\s*1\.2fr\)/,
);
assert.match(styles, /@container k1-connection-panel \(max-width:\s*48rem\)/);
assert.match(styles, /@container k1-session-panel \(max-width:\s*48rem\)/);
assert.match(styles, /@container xgrids-k1 \(max-width:\s*48rem\)/);
assert.match(styles, /overflow-wrap:\s*anywhere/);
assert.match(baseGrid, /grid-template-columns:\s*minmax\(0,\s*1fr\)/);
assert.ok(splitThreshold);
assert.ok(splitColumns);
const splitThresholdPixels = Number(splitThreshold[1]) * 16;
const minimumSplitPixels = (Number(splitColumns[1]) + Number(splitColumns[2]) + 0.85) * 16;
assert.equal(splitThresholdPixels, 1248);
assert.ok(minimumSplitPixels < splitThresholdPixels);
assert.ok(390 < splitThresholdPixels);
assert.ok(760 < splitThresholdPixels);
assert.ok(1280 > splitThresholdPixels);
assert.match(
styles,
/\.wizard-list,[\s\S]*?\.wizard-step,[\s\S]*?\.session-form,[\s\S]*?\.device-row,[\s\S]*?\.detail-list\s*\{[^}]*min-width:\s*0[^}]*max-width:\s*100%/,
);
assert.match(
styles,
/> \*\s*\{[^}]*min-width:\s*0[^}]*max-width:\s*100%/,
);
assert.match(
styles,
/\.metrics-grid > \*,[\s\S]*?\.device-workspace__grid > \*,[\s\S]*?\.diagnostics-grid > \*\s*\{[^}]*min-width:\s*0[^}]*max-width:\s*100%/,
);
assert.match(
styles,
/\.device-row code,[\s\S]*?\.detail-row code\s*\{[^}]*overflow-wrap:\s*anywhere[^}]*white-space:\s*normal/,
);
assert.match(
styles,
/\.detail-row dd\s*\{[^}]*overflow-wrap:\s*anywhere[^}]*text-overflow:\s*clip[^}]*white-space:\s*normal/,
);
assert.match(
styles,
/\.error-banner__actions > \.nodedc-button\s*\{[^}]*min-width:\s*0[^}]*max-width:\s*100%[^}]*overflow-wrap:\s*anywhere/,
);
assert.doesNotMatch(
styles,
/\.connection-panel,[\s\S]*?\.session-panel\s*\{[^}]*overflow:\s*(?:clip|hidden)/,
);
assert.match(
styles,
/@container k1-connection-panel \(max-width:\s*48rem\)[\s\S]*?\.wizard-step__content > header[^}]*flex-wrap:\s*wrap/,
);
assert.match(
styles,
/@container k1-connection-panel \(max-width:\s*48rem\)[\s\S]*?\.retained-recovery-target > div[^}]*flex-direction:\s*column/,
);
assert.match(
styles,
/@container k1-session-panel \(max-width:\s*48rem\)[\s\S]*?\.panel-heading[^}]*flex-wrap:\s*wrap/,
);
assert.match(
styles,
/\.connection-summary__value > \.nodedc-status,[\s\S]*?white-space:\s*normal/,
);
assert.doesNotMatch(styles, /\.nodedc-checker(?:__copy|__label)?\s*\{/);
assert.match(
styles,
/\.workspace-lead__status > span,[\s\S]*?\.nodedc-field__description,[\s\S]*?\.retained-recovery-target small,[\s\S]*?\.session-footer p\s*\{[^}]*overflow-wrap:\s*anywhere/,
);
assert.match(
styles,
/@container xgrids-k1 \(max-width:\s*32rem\)[\s\S]*?\.error-banner__actions\s*\{[^}]*align-items:\s*stretch[^}]*flex-direction:\s*column/,
);
assert.doesNotMatch(styles, /@media \(max-width:\s*(?:1280|1480)px\)/);
assert.doesNotMatch(styles, /device-recovery-choice/);
});
test("Mission Core shell protects the device workspace before the shared mobile breakpoint", () => {
const responsive = readFileSync(join(coreSourceRoot, "styles/responsive.css"), "utf8");
assert.match(
responsive,
/@media \(min-width:\s*761px\) and \(max-width:\s*929px\)/,
);
assert.match(
responsive,
/\.nodedc-app-shell__navigation,\s*\.nodedc-app-shell__content\s*\{[^}]*left:\s*var\(--nodedc-app-page-pad\)[^}]*width:\s*auto/s,
);
assert.match(
responsive,
/\[data-content-open="true"\] \.nodedc-app-shell__navigation\s*\{[^}]*opacity:\s*0[^}]*pointer-events:\s*none/s,
);
assert.match(
responsive,
/@media \(max-width:\s*760px\)[\s\S]*?\.nodedc-application-panel__head,\s*\.nodedc-application-panel__body\s*\{[^}]*width:\s*auto[^}]*min-width:\s*0[^}]*max-width:\s*100%/,
);
assert.match(
responsive,
/\.nodedc-application-panel__head\s*\{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)\s*auto/,
);
});
test("K1 frontend state models durable mutation and connection supervision facts", () => {
const api = readFileSync(join(pluginFrontendRoot, "api.ts"), "utf8");
const lifecycle = readFileSync(join(pluginFrontendRoot, "lifecycle.ts"), "utf8");
assert.match(api, /interface XgridsNetworkMutationLedger/);
assert.match(api, /scope:\s*"durable-ledger"/);
assert.match(api, /network_mutation_ledger\?: XgridsNetworkMutationLedger \| null/);
assert.match(api, /handle_retained\?: boolean/);
assert.match(api, /gatt_validated_recently\?: boolean/);
assert.match(api, /interface XgridsConnectionSupervisor/);
assert.match(api, /interface XgridsConnectionSupervisorDeviceNetwork/);
assert.match(api, /device_network: XgridsConnectionSupervisorDeviceNetwork/);
assert.match(api, /missioncore\.k1-connection-supervisor\/v1/);
assert.match(api, /connection_supervisor\?: XgridsConnectionSupervisor \| null/);
assert.match(api, /interface XgridsConnectionPolicy/);
assert.match(api, /missioncore\.xgrids-k1-connection-policy\/v1/);
assert.match(api, /connection_policy\?: XgridsConnectionPolicy \| null/);
assert.match(api, /interface XgridsSemanticTopologyStore/);
assert.match(api, /configured_offline_evidence: boolean/);
assert.match(api, /live_connection_authority: false/);
assert.match(api, /semantic_topology_store\?: XgridsSemanticTopologyStore \| null/);
const reconciliation = lifecycle.slice(
lifecycle.indexOf("export function readOnlyVerificationClearedReconciliation"),
lifecycle.indexOf("export function provisioningCandidateById"),
);
assert.match(reconciliation, /nextState\?\.network_mutation_ledger/);
assert.match(reconciliation, /nextLedger\.status === "resolved"/);
assert.match(reconciliation, /nextLedger\.operation_id === previousOperationId/);
assert.doesNotMatch(reconciliation, /snapshot_runtime_id/);
});
test("Bridge device and network reconfiguration stays backend-owned and CAS-fenced", () => {
const api = readFileSync(join(pluginFrontendRoot, "api.ts"), "utf8");
const manifest = readFileSync(join(pluginFrontendRoot, "manifest.ts"), "utf8");
const lifecycle = readFileSync(join(pluginFrontendRoot, "lifecycle.ts"), "utf8");
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
const prepare = provisioning.slice(
provisioning.indexOf("const prepareReconfiguration"),
provisioning.indexOf("const changeDesiredConnectionMode"),
);
const selection = provisioning.slice(
provisioning.indexOf("const selectFreshDevice"),
provisioning.indexOf("const chooseAnother"),
);
assert.match(manifest, /connectionReconfigurePrepare:[\s\S]*"connection\.reconfigure\.prepare"/);
assert.match(api, /expected_reconfiguration_revision: number/);
assert.match(api, /expected_reconfiguration_intent_id/);
assert.match(prepare, /prepareConnectionReconfigurationWithResult\(\{/);
assert.doesNotMatch(prepare, /scanWithResult\(|verifyConnection\(|connect\(/);
assert.doesNotMatch(selection, /await|scanWithResult\(|verifyConnection\(|connect\(/);
assert.match(provisioning, /showNetworkStep = Boolean\([\s\S]*changeNetworkDialogue/);
assert.match(provisioning, /localProvisioningDraftFenceKey/);
assert.match(lifecycle, /reconfigurationAllowsFreshDevice/);
assert.match(runtime, /prepareConnectionReconfiguration/);
});
test("shared runtime exposes only a reachable K1 endpoint as active", () => {
const runtimeContext = readFileSync(
join(pluginFrontendRoot, "runtimeContext.tsx"),
"utf8",
);
assert.match(
runtimeContext,
/endpointLabel: activeConnectionEndpointLabel\(state\)/,
);
assert.doesNotMatch(runtimeContext, /endpointLabel: state\.k1_ip/);
});
test("every K1 connection and acquisition action is fenced to the accepted backend runtime", () => {
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
assert.match(runtime, /latestState\.current\?\.snapshot_runtime_id/);
assert.equal(
[...runtime.matchAll(
/expected_snapshot_runtime_id: expectedSnapshotRuntimeId\(\)/g,
)].length,
14,
);
assert.match(
runtime,
/mode:\s*"graceful",[\s\S]*?expected_snapshot_runtime_id: checkpoint\.snapshotRuntimeId/,
);
assert.match(
runtime,
/xgridsK1Api\.scanBle\([\s\S]*?expected_snapshot_runtime_id/,
);
assert.match(
runtime,
/xgridsK1Api\.verifyConnection\([\s\S]*?expected_snapshot_runtime_id/,
);
assert.match(
runtime,
/xgridsK1Api\.connect\([\s\S]*?expected_snapshot_runtime_id/,
);
assert.match(
runtime,
/xgridsK1Api\.retireUnavailablePhysicalCommand\([\s\S]*?expected_snapshot_runtime_id:\s*exactSnapshotRuntimeId/,
);
assert.match(
runtime,
/run\("retire",[\s\S]*?surfaceErrors: false[\s\S]*?await refresh\(false\)/,
);
assert.match(
runtime,
/const exactSnapshotRuntimeId = actionSnapshotRuntimeId\.trim\(\)[\s\S]*?isSnapshotRuntimeCurrent\(exactSnapshotRuntimeId\)[\s\S]*?xgridsK1Api\.reopenRetiredPhysicalReconciliation\([\s\S]*?expected_snapshot_runtime_id: exactSnapshotRuntimeId/,
);
for (const action of [
"openApplicationControlSession",
"enterApplicationWorkspace",
"closeApplicationControlSession",
"prepareAcquisition",
"startAcquisition",
"abortAcquisition",
"reconcilePhysicalCommand",
]) {
assert.match(
runtime,
new RegExp(`xgridsK1Api\\.${action}\\([\\s\\S]*?expected_snapshot_runtime_id`),
action,
);
}
assert.equal(
[...runtime.matchAll(
/expected_snapshot_runtime_id: actionSnapshotRuntimeId/g,
)].length,
4,
);
assert.match(
runtime,
/stopSessionCompatibility\(\{[\s\S]*?expected_snapshot_runtime_id/,
);
assert.match(
runtime,
/run\([\s\S]*?"reopen"[\s\S]*?surfaceErrors: false, supersedePending: true[\s\S]*?await refresh\(false\)/,
);
assert.match(
runtime,
/const actionSnapshotRuntimeId =\s*options\.expectedSnapshotRuntimeId\?\.trim\(\) \|\| null[\s\S]*?expected_snapshot_runtime_id:\s*actionSnapshotRuntimeId \?\? expectedSnapshotRuntimeId\(\)/,
);
});
test("fresh Scan treats an exact prior K1 as one local Select action", () => {
const provisioning = readFileSync(
join(pluginFrontendRoot, "components/K1ProvisioningPipeline.tsx"),
"utf8",
);
const lifecycle = readFileSync(join(pluginFrontendRoot, "lifecycle.ts"), "utf8");
const runtime = readFileSync(
join(pluginFrontendRoot, "useXgridsK1Runtime.ts"),
"utf8",
);
const selection = provisioning.slice(
provisioning.indexOf("const selectFreshDevice"),
provisioning.indexOf("const chooseAnother"),
);
const apply = provisioning.slice(
provisioning.indexOf("const submitConnect"),
provisioning.indexOf("const verifyAppliedNetwork"),
);
assert.match(selection, /requestExplicitProvisioning\(device\.device_id/);
assert.match(selection, /const actionableDevices = devices\.filter\(candidateSelectionAllowed\)/);
assert.doesNotMatch(
selection,
/physicallyRetired|retiredPhysical|await|verifyConnection\(|retireUnavailable|reopenRetired/,
);
const resultRows = provisioning.slice(
provisioning.indexOf('<div className="device-list" aria-label="Результаты Bluetooth">'),
provisioning.indexOf('<div className="empty-state" role="status">'),
);
assert.match(resultRows, /actionLabel="Выбрать"/);
assert.match(resultRows, /onSelect=\{\(\) => selectCandidate\(device\)\}/);
assert.doesNotMatch(resultRows, /Переподключиться|reopen|verifyConnection/);
assert.doesNotMatch(provisioning, /recoverRetiredPhysicalCandidate/);
assert.doesNotMatch(apply, /verifyConnection\(|retireUnavailable|reopenRetired/);
assert.match(provisioning, /Переподключиться/);
assert.match(provisioning, /Подключить новый K1/);
assert.doesNotMatch(provisioning, /Вернуть прежний K1 и проверить/);
assert.match(runtime, /reopenRetiredPhysicalReconciliation/);
assert.match(runtime, /retireUnavailablePhysicalCommand/);
assert.match(lifecycle, /retiredPhysicalReopenAuthority/);
});
test("K1 orchestration accepts backend recovery but still requires exact topology before physical START", () => {
const acquisition = readFileSync(
join(pluginFrontendRoot, "components/K1AcquisitionPipeline.tsx"),
"utf8",
);
const diagnostics = readFileSync(
join(pluginFrontendRoot, "components/K1Diagnostics.tsx"),
"utf8",
);
assert.match(acquisition, /currentAppliedConnectionTopology\(state\)/);
assert.ok(
[...acquisition.matchAll(/!connectionConfigured/g)].length >= 2,
"handler and button must both reject absent or configured-offline topology",
);
assert.match(acquisition, /desiredModeMatchesActive/);
assert.match(acquisition, /modeSwitchRequired/);
assert.doesNotMatch(acquisition, /Выбран другой способ связи/);
assert.match(acquisition, /prepareCanonicalAcquisition/);
assert.match(acquisition, /startPreparedAcquisition/);
assert.match(acquisition, /operatorActionPhysicalAcceptance\(\)/);
assert.doesNotMatch(acquisition, /K1PhysicalCommandConfirmation/);
assert.match(acquisition, /connectionPolicyAllows\(state, "start-acquisition"\)/);
assert.match(acquisition, /canIssueCanonicalStop\(state, physicalStopIntentSpent\)/);
assert.match(acquisition, /physicalStopInFlight \|\| physicalStopExecutable/);
assert.match(acquisition, /connectionPolicyAllows\(state, "stop-local-receiver"\)/);
assert.match(acquisition, /if \(finalStartTarget\)/);
assert.match(acquisition, /if \(physicalStopExecutable\)/);
assert.match(acquisition, /if \(!physicalStartAllowed\) return;/);
assert.doesNotMatch(acquisition, /physicalStopAllowed/);
const replaySubmit = acquisition.slice(
acquisition.indexOf("const submitReplay ="),
acquisition.indexOf("return (", acquisition.indexOf("const submitReplay =")),
);
assert.doesNotMatch(
replaySubmit,
/connectionPolicyAllows|physicalStartAllowed|physicalStopExecutable/,
);
assert.match(acquisition, /void stopLocalReceiver\(\);/);
assert.match(acquisition, /onClick=\{\(\) => void abort\(\)\}/);
assert.doesNotMatch(acquisition, /acknowledge-data-loss/);
assert.doesNotMatch(acquisition, /PHYSICAL_ACCEPTANCE/);
assert.doesNotMatch(acquisition, /!state\?\.k1_ip/);
assert.match(diagnostics, /activeConnectionEndpointLabel\(state\)/);
assert.match(diagnostics, /Адрес конфигурации/);
assert.match(diagnostics, /связь не подтверждена/);
assert.doesNotMatch(diagnostics, /state\?\.k1_ip/);
});
test("one explicit K1 action performs START or STOP without a redundant checklist modal", () => {
const acquisition = readFileSync(
join(pluginFrontendRoot, "components/K1AcquisitionPipeline.tsx"),
"utf8",
);
const spatial = readFileSync(
join(pluginFrontendRoot, "components/K1SpatialControls.tsx"),
"utf8",
);
const confirmation = readFileSync(
join(pluginFrontendRoot, "physicalCommandConfirmation.ts"),
"utf8",
);
const styles = readFileSync(join(pluginFrontendRoot, "styles.css"), "utf8");
assert.equal(
existsSync(join(pluginFrontendRoot, "components/K1PhysicalCommandConfirmation.tsx")),
false,
);
assert.doesNotMatch(acquisition, /K1PhysicalCommandConfirmation|ConfirmationModal/);
assert.doesNotMatch(spatial, /K1PhysicalCommandConfirmation|ConfirmationModal/);
assert.match(acquisition, /operatorActionPhysicalAcceptance\(\)/);
assert.match(acquisition, /await submitFinalStart\(\)/);
assert.match(spatial, /stop\(operatorActionPhysicalAcceptance\(\)\)/);
assert.match(spatial, /physicalStopExecutable/);
assert.match(spatial, /canIssueCanonicalStop\(state, physicalStopIntentSpent\)/);
assert.match(spatial, /physicalStopInFlight \|\| physicalStopExecutable/);
assert.match(spatial, /connectionPolicyAllows\(state, "stop-local-receiver"\)/);
assert.doesNotMatch(spatial, /Физическая остановка K1 недоступна/);
assert.match(spatial, /onClick=\{\(\) => void stopLocalReceiver\(\)\}/);
assert.match(spatial, /Завершить локальный приём/);
assert.doesNotMatch(spatial, /Повторить остановку/);
assert.match(acquisition, /canIssueCanonicalStop\(state, physicalStopIntentSpent\)/);
assert.match(acquisition, /stopLocalReceiver/);
assert.match(acquisition, /Повторная команда устройству не отправляется/);
assert.doesNotMatch(spatial, /acknowledge-data-loss/);
assert.match(confirmation, /operatorActionPhysicalAcceptance/);
assert.match(confirmation, /operator_present:\s*true/);
assert.match(confirmation, /acquisition\.state !== "prepared"/);
assert.match(confirmation, /control\.state !== "project-ready"/);
assert.match(confirmation, /latest_device_session_state/);
assert.match(confirmation, /acquisition_start_allowed !== true/);
assert.doesNotMatch(styles, /xgrids-k1-physical-confirmation/);
});
test("generic Control Station has one composition import and no K1 implementation knowledge", () => {
const compositionPath = join(coreSourceRoot, "composition/devicePlugins.ts");
const composition = readFileSync(compositionPath, "utf8");
assert.match(composition, /from "@xgrids-k1\/frontend\/plugin"/);
for (const path of sourceFiles(coreSourceRoot)) {
if (path === compositionPath) continue;
const source = readFileSync(path, "utf8");
assert.doesNotMatch(source, /xgrids|lixel|\bk1\b/i, path);
}
for (const path of sourceFiles(pluginFrontendRoot)) {
const source = readFileSync(path, "utf8");
assert.doesNotMatch(source, /apps\/control-station|\.\.\/\.\.\/core|\.\.\/\.\.\/components/, path);
}
});