Capture the current unreleased K1 connection, recovery, lifecycle, viewer, and test work as a single known-bad baseline for subsequent fixes.
529 lines
19 KiB
TypeScript
529 lines
19 KiB
TypeScript
import type {
|
|
DeviceModelDefinition,
|
|
ObservationSourceAvailability,
|
|
ObservationSourceDelivery,
|
|
ObservationSourceDescriptor,
|
|
ObservationSourcePresentationLease,
|
|
ObservationSourceProvider,
|
|
} from "@mission-core/plugin-sdk";
|
|
import {
|
|
activeStreamRecoveredBrowserAuthority,
|
|
activeStreamRecoveryOwnsPresentationDecision,
|
|
activeStreamRecoveryPresentationAuthority,
|
|
type ActiveStreamRecoveryPresentationAuthority,
|
|
} from "./activeStreamRecovery";
|
|
import {
|
|
confirmedRuntimeSourceMode,
|
|
effectiveAcquisition,
|
|
hasAuthoritativeData,
|
|
hasControlAuthority,
|
|
} from "./lifecycle";
|
|
import { xgridsK1Manifest } from "./manifest";
|
|
import type {
|
|
XgridsCameraPreviewDelivery,
|
|
XgridsK1State,
|
|
XgridsSensorCatalogStream,
|
|
} from "./api";
|
|
import { isXgridsActiveStreamRecovery } from "./api";
|
|
|
|
function providerFor(
|
|
state: XgridsK1State,
|
|
activeModel: DeviceModelDefinition,
|
|
): ObservationSourceProvider {
|
|
return {
|
|
pluginId: xgridsK1Manifest.metadata.id,
|
|
pluginVersion: xgridsK1Manifest.metadata.version,
|
|
modelId: state.device_ref?.model_id || activeModel.id,
|
|
compatibilityProfileId:
|
|
(state.connection_supervisor?.observed.device_identity.state === "verified"
|
|
? state.connection_supervisor.observed.device_identity.compatibility_profile_id
|
|
: null)
|
|
?? state.device_session?.compatibility_profile_id
|
|
?? state.compatibility?.profile_id
|
|
?? null,
|
|
};
|
|
}
|
|
|
|
function bindingFor(
|
|
state: XgridsK1State,
|
|
recoveryAuthority: ActiveStreamRecoveryPresentationAuthority | null,
|
|
) {
|
|
const acquisition = effectiveAcquisition(state);
|
|
const controlAuthoritative = hasControlAuthority(state);
|
|
const recoveryAuthoritative = recoveryAuthority !== null;
|
|
return {
|
|
// A legacy snapshot may retain a selected device and session long after
|
|
// the control topology has disappeared. Do not publish those values as a
|
|
// live host binding until the supervisor has re-attested the topology.
|
|
deviceId: recoveryAuthoritative
|
|
? acquisition?.device_id?.trim() || null
|
|
: controlAuthoritative ? state.device_ref?.device_id ?? null : null,
|
|
deviceSessionId: recoveryAuthoritative
|
|
? acquisition?.device_session_id?.trim() || null
|
|
: controlAuthoritative ? state.device_session?.device_session_id ?? null : null,
|
|
acquisitionId: acquisition?.acquisition_id ?? null,
|
|
};
|
|
}
|
|
|
|
function recoveryPresentationLease(
|
|
authority: ActiveStreamRecoveryPresentationAuthority,
|
|
): ObservationSourcePresentationLease {
|
|
return {
|
|
kind: "active-stream-recovery",
|
|
runtimeId: authority.snapshotRuntimeId,
|
|
acquisitionId: authority.acquisitionId,
|
|
acquisitionStateRevision: authority.acquisitionStateRevision,
|
|
producerGeneration: authority.runtimeProducerGeneration,
|
|
recoveryGeneration: authority.recoveryGeneration,
|
|
};
|
|
}
|
|
|
|
function catalogDeclares(state: XgridsK1State, streamId: string): boolean {
|
|
return Boolean(state.sensor_catalog?.streams?.some((stream) => stream.stream_id === streamId));
|
|
}
|
|
|
|
function spatialAvailability(
|
|
state: XgridsK1State,
|
|
recoveryAuthoritative: boolean,
|
|
recoveryOwnsPresentation: boolean,
|
|
): ObservationSourceAvailability {
|
|
const mode = confirmedRuntimeSourceMode(state);
|
|
if (mode === "replay" && state.rerun_grpc_url?.trim()) return "streaming";
|
|
const declared = catalogDeclares(state, "spatial.point-cloud.live");
|
|
if (recoveryAuthoritative && state.rerun_grpc_url?.trim()) return "connecting";
|
|
if (recoveryOwnsPresentation) return declared ? "degraded" : "unavailable";
|
|
if (!hasControlAuthority(state)) return declared ? "unverified" : "unavailable";
|
|
if (mode === "live" && state.rerun_grpc_url?.trim() && hasAuthoritativeData(state)) {
|
|
return "streaming";
|
|
}
|
|
if (["stalled", "lost"].includes(
|
|
state.connection_supervisor?.observed.data_plane.state ?? "idle",
|
|
)) return "degraded";
|
|
if (state.rerun_grpc_url?.trim() || declared) return "available";
|
|
return "unavailable";
|
|
}
|
|
|
|
function catalogAvailability(value: string | null | undefined): ObservationSourceAvailability {
|
|
switch (value?.trim().toLowerCase()) {
|
|
case "observed":
|
|
case "available":
|
|
return "available";
|
|
case "connecting":
|
|
return "connecting";
|
|
case "streaming":
|
|
return "streaming";
|
|
case "degraded":
|
|
return "degraded";
|
|
case "unavailable":
|
|
return "unavailable";
|
|
case "error":
|
|
return "error";
|
|
case "declared":
|
|
return "declared";
|
|
default:
|
|
return "unverified";
|
|
}
|
|
}
|
|
|
|
function cameraCatalogEntry(stream: XgridsSensorCatalogStream): boolean {
|
|
const sourceId = stream.source_id?.trim();
|
|
const semanticChannelId = stream.semantic_channel_id?.trim();
|
|
if (!sourceId || !semanticChannelId) return false;
|
|
const modality = stream.modality?.trim().toLowerCase();
|
|
const sensorKind = stream.sensor_kind?.trim().toLowerCase();
|
|
return (
|
|
modality === "encoded-video" ||
|
|
modality === "video" ||
|
|
sensorKind === "camera" ||
|
|
semanticChannelId.startsWith("camera.")
|
|
);
|
|
}
|
|
|
|
function safeEndpointLabel(value: string | null | undefined): string | null {
|
|
const label = value?.trim();
|
|
if (!label || label.length > 128) return null;
|
|
if (label.includes("://") || /(?:^|\D)(?:\d{1,3}\.){3}\d{1,3}(?:\D|$)/.test(label)) {
|
|
return null;
|
|
}
|
|
return label;
|
|
}
|
|
|
|
const SENSITIVE_QUERY_KEYS = new Set([
|
|
"accesskey", "accesstoken", "apikey", "auth", "authorization", "clientsecret",
|
|
"credential", "credentials", "password", "passwd", "passphrase", "privatekey",
|
|
"psk", "pwd", "refreshtoken", "secret", "sessionkey", "token", "user",
|
|
"username", "wifipassword",
|
|
]);
|
|
|
|
function decodedForInspection(value: string): string | null {
|
|
let decoded = value;
|
|
try {
|
|
for (let depth = 0; depth < 4; depth += 1) {
|
|
const next = decodeURIComponent(decoded);
|
|
if (next === decoded) break;
|
|
decoded = next;
|
|
}
|
|
} catch {
|
|
return null;
|
|
}
|
|
return decoded;
|
|
}
|
|
|
|
function sensitiveQueryKey(value: string): boolean {
|
|
const canonical = value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
return SENSITIVE_QUERY_KEYS.has(canonical) ||
|
|
/(?:token|secret|password|passwd|passphrase|credential|credentials)$/.test(canonical);
|
|
}
|
|
|
|
function safeBrowserDeliveryUrl(value: string): boolean {
|
|
if (value.length > 4_096 || !value.startsWith("/") || value.startsWith("//")) return false;
|
|
const inspected = decodedForInspection(value);
|
|
if (!inspected) return false;
|
|
if (
|
|
/[\\\r\n\u0000-\u001f\u007f]/.test(inspected) ||
|
|
/\brtsps?\s*:/i.test(inspected) ||
|
|
/(?:^|\D)(?:\d{1,3}\.){3}\d{1,3}(?:\D|$)/.test(inspected) ||
|
|
/\[[0-9a-f:]+\]/i.test(inspected) ||
|
|
/(?:^|[/?#&=])[^/?#&=\s:@]+:[^/?#&\s@]+@/.test(inspected) ||
|
|
/\b(?:basic|bearer)\s+[a-z0-9._~+/=-]+/i.test(inspected)
|
|
) return false;
|
|
|
|
const base = new URL("https://mission-core.invalid/");
|
|
let parsed: URL;
|
|
let inspectedParsed: URL;
|
|
try {
|
|
parsed = new URL(value, base);
|
|
inspectedParsed = new URL(inspected, base);
|
|
} catch {
|
|
return false;
|
|
}
|
|
if (
|
|
parsed.origin !== base.origin || inspectedParsed.origin !== base.origin ||
|
|
parsed.username || parsed.password || parsed.hash ||
|
|
inspectedParsed.username || inspectedParsed.password || inspectedParsed.hash
|
|
) return false;
|
|
for (const [key] of inspectedParsed.searchParams) {
|
|
if (sensitiveQueryKey(key)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function browserDelivery(
|
|
value: XgridsCameraPreviewDelivery | null | undefined,
|
|
): ObservationSourceDelivery | null {
|
|
const id = value?.id?.trim();
|
|
const url = value?.url?.trim();
|
|
const mediaType = value?.media_type?.trim();
|
|
if (!id || value?.kind !== "mse-fmp4-websocket" || !url || !mediaType) return null;
|
|
// Browser delivery is host-owned and same-origin. A device RTSP/IP endpoint
|
|
// must never cross the plugin boundary into the host descriptor catalog.
|
|
if (!safeBrowserDeliveryUrl(url)) return null;
|
|
if (!/^video\/mp4(?:\s*;|$)/i.test(mediaType)) return null;
|
|
return { id, kind: value.kind, url, mediaType };
|
|
}
|
|
|
|
function sameBrowserDelivery(
|
|
left: ObservationSourceDelivery | null,
|
|
right: ObservationSourceDelivery | null,
|
|
): boolean {
|
|
return Boolean(
|
|
left
|
|
&& right
|
|
&& left.kind === "mse-fmp4-websocket"
|
|
&& right.kind === "mse-fmp4-websocket"
|
|
&& left.id === right.id
|
|
&& left.url === right.url
|
|
&& left.mediaType === right.mediaType,
|
|
);
|
|
}
|
|
|
|
function cameraAvailability(
|
|
state: XgridsK1State,
|
|
stream: XgridsSensorCatalogStream,
|
|
selected: boolean,
|
|
delivery: ObservationSourceDelivery | null,
|
|
attested: boolean,
|
|
recoverySelected: boolean,
|
|
exactCurrentEpochReady: boolean,
|
|
): ObservationSourceAvailability {
|
|
if (recoverySelected) {
|
|
return exactCurrentEpochReady
|
|
? "streaming"
|
|
: state.camera_preview?.phase?.trim().toLowerCase() === "degraded"
|
|
? "degraded"
|
|
: "connecting";
|
|
}
|
|
if (!attested) return "unverified";
|
|
if (!hasControlAuthority(state)) return "degraded";
|
|
if (["stalled", "lost"].includes(
|
|
state.connection_supervisor?.observed.data_plane.state ?? "idle",
|
|
)) return "degraded";
|
|
const base = catalogAvailability(stream.availability);
|
|
if (!selected) return base === "streaming" || base === "connecting" ? "available" : base;
|
|
|
|
const phase = state.camera_preview?.phase?.trim().toLowerCase();
|
|
if (phase === "error" || base === "error") return "error";
|
|
if (delivery && (phase === "streaming" || phase === "active" || phase === "ready")) {
|
|
return "streaming";
|
|
}
|
|
if (phase === "degraded") return "degraded";
|
|
return "connecting";
|
|
}
|
|
|
|
function exactCurrentCameraEpochReady(state: XgridsK1State): boolean {
|
|
const recovery = state.connection_recovery;
|
|
const previewGeneration = state.camera_preview?.generation;
|
|
if (
|
|
!isXgridsActiveStreamRecovery(recovery)
|
|
|| recovery.camera_media_state !== "ready"
|
|
|| recovery.camera_media_ready !== true
|
|
|| !Number.isInteger(previewGeneration)
|
|
|| (previewGeneration ?? 0) < 1
|
|
) return false;
|
|
const epoch = recovery.camera_epoch;
|
|
return Boolean(
|
|
epoch
|
|
&& epoch.generation === previewGeneration
|
|
&& epoch.init_committed === true
|
|
&& epoch.first_media_committed === true
|
|
&& epoch.committed_media_segment_count > 0,
|
|
);
|
|
}
|
|
|
|
function recoveryCameraTupleIsExact(
|
|
state: XgridsK1State,
|
|
authority: ActiveStreamRecoveryPresentationAuthority | null,
|
|
provider: ObservationSourceProvider,
|
|
): boolean {
|
|
const acquisition = state.acquisition;
|
|
const deviceId = acquisition?.device_id?.trim();
|
|
const deviceSessionId = acquisition?.device_session_id?.trim();
|
|
const compatibilityProfileId = acquisition?.compatibility_profile_id?.trim();
|
|
return Boolean(
|
|
authority
|
|
&& authority.recovery.camera_recovery === "owned"
|
|
&& acquisition
|
|
&& acquisition.acquisition_id.trim() === authority.acquisitionId
|
|
&& deviceId
|
|
&& deviceSessionId
|
|
&& compatibilityProfileId
|
|
&& state.device_ref?.device_id?.trim() === deviceId
|
|
&& state.device_session?.device_session_id?.trim() === deviceSessionId
|
|
&& state.device_session?.device_id?.trim() === deviceId
|
|
&& state.device_session?.compatibility_profile_id?.trim() === compatibilityProfileId
|
|
&& provider.compatibilityProfileId?.trim() === compatibilityProfileId
|
|
);
|
|
}
|
|
|
|
function cameraRecoveryPhaseRetainable(state: XgridsK1State): boolean {
|
|
return [
|
|
"active",
|
|
"buffering",
|
|
"connecting",
|
|
"degraded",
|
|
"ready",
|
|
"reconnecting",
|
|
"streaming",
|
|
].includes(state.camera_preview?.phase?.trim().toLowerCase() ?? "");
|
|
}
|
|
|
|
export function xgridsK1ObservationSources(
|
|
state: XgridsK1State,
|
|
activeModel: DeviceModelDefinition,
|
|
): ObservationSourceDescriptor[] {
|
|
const provider = providerFor(state, activeModel);
|
|
const recoveryAuthority = activeStreamRecoveryPresentationAuthority(state);
|
|
const recoveredBrowserAuthority = activeStreamRecoveredBrowserAuthority(state);
|
|
const browserLineageAuthority = recoveryAuthority ?? recoveredBrowserAuthority;
|
|
const recoveryOwnsPresentation = activeStreamRecoveryOwnsPresentationDecision(state);
|
|
const recoveryAuthoritative = recoveryAuthority !== null;
|
|
const presentationLease = browserLineageAuthority
|
|
? recoveryPresentationLease(browserLineageAuthority)
|
|
: null;
|
|
const binding = bindingFor(state, browserLineageAuthority);
|
|
const replayAuthoritative = state.source_mode === "replay";
|
|
const dataAuthoritative = hasAuthoritativeData(state) && !recoveryOwnsPresentation;
|
|
const spatialPreviewUrl = replayAuthoritative || dataAuthoritative || recoveryAuthoritative
|
|
? state.rerun_grpc_url?.trim() || null
|
|
: null;
|
|
const clockId = binding.acquisitionId ?? binding.deviceSessionId ?? binding.deviceId ?? null;
|
|
const descriptorId = (sourceId: string) =>
|
|
`${provider.pluginId}:${provider.modelId}:${sourceId}`;
|
|
const pointCloud: ObservationSourceDescriptor = {
|
|
id: descriptorId("sensor.lidar.primary"),
|
|
sourceId: "sensor.lidar.primary",
|
|
semanticChannelId: "spatial.point-cloud.live",
|
|
label: "K1 · облако точек",
|
|
description: "Облако точек, поза и траектория в общей 3D-сцене",
|
|
modality: "point-cloud",
|
|
role: "primary",
|
|
availability: spatialAvailability(
|
|
state,
|
|
recoveryAuthoritative,
|
|
recoveryOwnsPresentation,
|
|
),
|
|
transport: "rerun-grpc",
|
|
endpointLabel: state.rerun_grpc_url?.trim() ? "Rerun gRPC" : "MQTT → Rerun",
|
|
previewUrl: spatialPreviewUrl,
|
|
delivery: null,
|
|
activation: null,
|
|
presentationLease: (
|
|
recoveryAuthoritative
|
|
|| (recoveredBrowserAuthority !== null && dataAuthoritative)
|
|
) && spatialPreviewUrl
|
|
? presentationLease
|
|
: null,
|
|
provider,
|
|
binding,
|
|
capabilities: {
|
|
overlay: false,
|
|
fullscreen: true,
|
|
resizable: false,
|
|
defaultVisible: true,
|
|
timelineMode: "live-only",
|
|
// Rerun can hold replay data, but the Mission Core host timeline is not
|
|
// wired to its time controller yet.
|
|
seekable: false,
|
|
sessionRecording: false,
|
|
clockId,
|
|
spatialRegistration: "native",
|
|
},
|
|
};
|
|
|
|
const cameraRows = (state.sensor_catalog?.streams ?? []).filter(cameraCatalogEntry);
|
|
const sourceIdCounts = new Map<string, number>();
|
|
for (const stream of cameraRows) {
|
|
const sourceId = stream.source_id?.trim();
|
|
if (sourceId) sourceIdCounts.set(sourceId, (sourceIdCounts.get(sourceId) ?? 0) + 1);
|
|
}
|
|
const supervisor = state.connection_supervisor;
|
|
const verifiedControl = state.application_control_session?.verified_control;
|
|
const attested = Boolean(
|
|
hasControlAuthority(state)
|
|
&& provider.compatibilityProfileId
|
|
&& binding.deviceSessionId
|
|
&& verifiedControl
|
|
&& supervisor?.observed.control_plane.session_id === verifiedControl.control_session_id
|
|
&& supervisor.observed.device_identity.logical_device_id
|
|
=== verifiedControl.logical_device_id
|
|
&& supervisor.observed.device_identity.compatibility_profile_id
|
|
=== verifiedControl.compatibility_profile_id,
|
|
);
|
|
const sessionScope = binding.deviceSessionId ?? binding.deviceId ?? "unbound";
|
|
const activeSourceId = state.camera_preview?.active_source_id?.trim() ?? null;
|
|
const exactCameraMediaReady = exactCurrentCameraEpochReady(state);
|
|
const browserLineageCameraTupleExact = recoveryCameraTupleIsExact(
|
|
state,
|
|
browserLineageAuthority,
|
|
provider,
|
|
);
|
|
|
|
const cameras = cameraRows.flatMap<ObservationSourceDescriptor>((stream) => {
|
|
const sourceId = stream.source_id?.trim();
|
|
const semanticChannelId = stream.semantic_channel_id?.trim();
|
|
if (!sourceId || !semanticChannelId || sourceIdCounts.get(sourceId) !== 1) return [];
|
|
|
|
const rawActivation = stream.activation;
|
|
const groupId = rawActivation?.group_id?.trim();
|
|
const maxActive = rawActivation?.max_active;
|
|
const activationValid = Boolean(
|
|
groupId && Number.isInteger(maxActive) && (maxActive ?? 0) > 0,
|
|
);
|
|
const normallySelected = Boolean(
|
|
!recoveryOwnsPresentation
|
|
&& attested
|
|
&& activationValid
|
|
&& rawActivation?.selected === true
|
|
&& activeSourceId === sourceId
|
|
&& exactCameraMediaReady,
|
|
);
|
|
const streamDelivery = browserDelivery(stream.delivery);
|
|
const previewDelivery = browserDelivery(state.camera_preview?.delivery);
|
|
const candidateDelivery = stream.delivery ?? state.camera_preview?.delivery;
|
|
const retainedDelivery = browserDelivery(candidateDelivery);
|
|
const deliveryConsistent = !stream.delivery || !state.camera_preview?.delivery
|
|
|| sameBrowserDelivery(streamDelivery, previewDelivery);
|
|
const streamRecoveryAvailable = [
|
|
"available",
|
|
"connecting",
|
|
"degraded",
|
|
"streaming",
|
|
].includes(catalogAvailability(stream.availability));
|
|
const browserLineageSelected = Boolean(
|
|
browserLineageCameraTupleExact
|
|
&& activationValid
|
|
&& maxActive === 1
|
|
&& rawActivation?.selected === true
|
|
&& activeSourceId === sourceId
|
|
&& cameraRecoveryPhaseRetainable(state)
|
|
&& streamRecoveryAvailable
|
|
&& retainedDelivery
|
|
&& deliveryConsistent
|
|
);
|
|
const recoverySelected = recoveryAuthority !== null && browserLineageSelected;
|
|
const recoveredBrowserSelected = Boolean(
|
|
recoveredBrowserAuthority
|
|
&& browserLineageSelected
|
|
);
|
|
const selected = normallySelected || recoverySelected || recoveredBrowserSelected;
|
|
const activation = activationValid
|
|
? {
|
|
groupId: `${provider.pluginId}:${sessionScope}:${groupId}`,
|
|
maxActive: maxActive as number,
|
|
selected,
|
|
controllable: Boolean(
|
|
!recoveryOwnsPresentation && attested && rawActivation?.controllable,
|
|
),
|
|
}
|
|
: null;
|
|
const delivery = selected && (
|
|
dataAuthoritative || recoverySelected || recoveredBrowserSelected
|
|
)
|
|
? retainedDelivery
|
|
: null;
|
|
const label = stream.label?.trim() || sourceId;
|
|
|
|
return [{
|
|
id: descriptorId(sourceId),
|
|
sourceId,
|
|
semanticChannelId,
|
|
label,
|
|
description: "Видеоканал, опубликованный активным device-плагином",
|
|
modality: "video",
|
|
role: "auxiliary",
|
|
availability: cameraAvailability(
|
|
state,
|
|
stream,
|
|
selected,
|
|
delivery,
|
|
attested,
|
|
recoverySelected || recoveredBrowserSelected,
|
|
exactCameraMediaReady,
|
|
),
|
|
transport: delivery ? "websocket" : "other",
|
|
endpointLabel: safeEndpointLabel(stream.endpoint_label) ?? "Локальный video adapter",
|
|
previewUrl: null,
|
|
delivery,
|
|
activation,
|
|
presentationLease: (recoverySelected || recoveredBrowserSelected) && delivery
|
|
? presentationLease
|
|
: null,
|
|
provider,
|
|
binding,
|
|
capabilities: {
|
|
overlay: true,
|
|
fullscreen: true,
|
|
resizable: true,
|
|
defaultVisible: selected && Boolean(delivery),
|
|
timelineMode: "live-only",
|
|
seekable: false,
|
|
sessionRecording: false,
|
|
clockId,
|
|
spatialRegistration: "unresolved",
|
|
},
|
|
}];
|
|
});
|
|
|
|
return [pointCloud, ...cameras];
|
|
}
|