feat(observatory): admit canonical recorded replay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 19:21:27 +03:00
parent e6a9846167
commit 023151c186
32 changed files with 4069 additions and 342 deletions
@@ -0,0 +1,249 @@
import { useEffect, useMemo, useState } from "react";
import { Button, Icon, SegmentedControl } from "@nodedc/ui-react";
import { ObservationTimeline } from "../ObservationTimeline";
import {
RerunViewport,
type RerunPlaybackController,
type RerunPlaybackState,
} from "../RerunViewport";
import {
CanonicalRecordedLabReplay,
useCanonicalRecordedLabReplayState,
} from "../laboratory/CanonicalRecordedLabReplay";
import {
resolveCanonicalLabReplay,
type CanonicalLabReplayDescriptor,
} from "../../core/laboratory/canonicalLabReplay";
import type { VegetationFullRouteReview } from "../../core/laboratory/vegetationShadow";
import type { ObservationSessionReplayLaunch } from "../../core/observation/sessionArchive";
import { recordedSessionRerunProfile } from "../../core/observation/viewerProfile";
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
import { defaultSceneSettings } from "../../sceneSettings";
type MediaMode = "video" | "camera";
type SpatialMode = "3d" | "plan";
type SpatialLayer = "source" | "local" | "tgs" | "semantic";
type SemanticLayer = "city" | "vegetation";
interface CanonicalReplayLaunch {
base: ObservationSessionReplayLaunch;
replay: CanonicalLabReplayDescriptor;
}
export function CanonicalVegetationRerunReplay({
resultId,
review,
}: {
resultId: string;
review: VegetationFullRouteReview;
}) {
const {
mediaMode,
spatialMode,
splitPrimarySize,
splitOrientation,
expanded,
onMediaModeChange,
onSpatialModeChange,
onSplitPrimarySizeChange,
onExpandedChange,
} = useCanonicalRecordedLabReplayState<MediaMode, SpatialMode>({
initialMediaMode: "video",
initialSpatialMode: "3d",
});
const [semanticLayer, setSemanticLayer] = useState<SemanticLayer>("vegetation");
const [showSemantics, setShowSemantics] = useState(true);
const [spatialLayer, setSpatialLayer] = useState<SpatialLayer>("source");
const [viewResetGeneration, setViewResetGeneration] = useState<0 | 1>(0);
const [playback, setPlayback] = useState<RerunPlaybackState | null>(null);
const [playbackController, setPlaybackController] =
useState<RerunPlaybackController | null>(null);
const [launch, setLaunch] = useState<CanonicalReplayLaunch | null>(null);
const [launchError, setLaunchError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLaunch(null);
setLaunchError(null);
void resolveObservationSessionReplay(review.sessionId, {
signal: controller.signal,
maximumWaitMs: 30 * 60 * 1000,
onUpdate: () => undefined,
}).then(async (value) => ({
base: value,
replay: await resolveCanonicalLabReplay(resultId, value, {
signal: controller.signal,
}),
})).then((value) => {
if (!controller.signal.aborted) setLaunch(value);
}).catch((caught: unknown) => {
if (!controller.signal.aborted) {
setLaunchError(
caught instanceof Error ? caught.message : "Каноническая запись RAV004 недоступна.",
);
}
});
return () => controller.abort();
}, [resultId, review.sessionId]);
const splitView = mediaMode !== null && spatialMode !== null;
const sceneSettings = useMemo(() => ({
...defaultSceneSettings,
accumulationSeconds: spatialLayer === "local" ? 5 : 0,
showPoints: spatialMode !== null,
showTrajectory: spatialMode !== null,
showGrid: spatialMode !== null,
pointSize: 3.8,
}), [spatialLayer, spatialMode]);
const profile = launch ? recordedSessionRerunProfile({
sourceUrl: launch.replay.sourceUrl,
artifact: {
sourceUrl: launch.replay.sourceUrl,
viewerSourceUrl: launch.replay.viewerSourceUrl,
byteLength: launch.replay.byteLength,
sha256: launch.replay.sha256,
},
blueprintSourceUrl: launch.replay.blueprintSourceUrl,
autoplayWhenReady: false,
presentationGate: "ready",
expectedTimelineStartSeconds: launch.base.timelineStartSeconds,
expectedTimelineEndSeconds: launch.base.timelineEndSeconds,
initialPlaybackStartSeconds: review.timelineStartSeconds,
view: mediaMode !== null ? "perception" : "spatial",
viewResetGeneration,
followTrajectory: true,
semanticLayer,
unifiedPerception: splitView,
planView: spatialMode === "plan",
perceptionLayers: {
enabled: mediaMode !== null,
detections2d: mediaMode === "video",
segmentation: mediaMode === "video" && showSemantics,
cuboids3d: false,
},
perceptionRetryGeneration: 0,
lockPerceptionCameraInteraction: false,
}) : null;
const mediaLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Слои камеры и видео"
>
<Button
size="dense"
shape="pill"
variant={showSemantics ? "primary" : "secondary"}
aria-pressed={showSemantics}
onClick={() => setShowSemantics((visible) => !visible)}
>
СЕМАНТИКА
</Button>
<SegmentedControl
value={semanticLayer}
items={[
{ value: "city", label: "ГОРОД · EoMT" },
{ value: "vegetation", label: "ПРИРОДА · DDRNet" },
]}
label="Источник семантики"
size="dense"
onChange={(value) => {
setSemanticLayer(value);
setShowSemantics(true);
}}
/>
</div>
);
const spatialLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Пространственные слои RAV004"
>
<SegmentedControl
value={spatialLayer}
items={[
{ value: "source", label: "ИСХ. ТОЧКИ" },
{ value: "local", label: "ЛОК. SLAM" },
{ value: "tgs", label: "TGS", disabled: true },
{ value: "semantic", label: "СЕМАНТИКА", disabled: true },
]}
label="Пространственные слои"
size="dense"
onChange={setSpatialLayer}
/>
</div>
);
const resetSpatialView = (
<Button
size="dense"
variant="ghost"
icon={<Icon name="refresh" size={14} />}
aria-label="Сбросить положение 3D камеры"
title="Сбросить положение 3D камеры"
onClick={() => setViewResetGeneration((value) => value === 0 ? 1 : 0)}
>
</Button>
);
const transport = playback && playbackController ? (
<ObservationTimeline
className="m4-replay-threat-visual__timeline"
active
sourceCount={3}
mode="recorded"
seekable
synchronization="shared-clock"
rangeNs={playback.rangeNs}
currentNs={playback.currentNs}
playing={playback.playing}
onSeek={playbackController.seek}
onPlayingChange={playbackController.setPlaying}
showJumpToEnd={false}
/>
) : undefined;
return (
<CanonicalRecordedLabReplay
label="RAVNOVES004TREE · канонический повтор Rerun"
mediaMode={mediaMode ?? "none"}
mediaModes={[
{ value: "video", label: "ВИДЕО" },
{ value: "camera", label: "КАМЕРА" },
]}
spatialMode={spatialMode ?? "none"}
spatialModes={[
{ value: "3d", label: "3D" },
{ value: "plan", label: "ПЛАН" },
]}
expanded={expanded}
splitPrimarySize={splitPrimarySize}
splitOrientation={splitOrientation}
mediaAriaLabel={mediaMode === "camera" ? "Камера" : "Видео и семантика"}
spatialAriaLabel={spatialMode === "plan" ? "Вид сверху" : "Трёхмерная сцена"}
mediaLayerControls={mediaLayerControls}
spatialLayerControls={spatialLayerControls}
spatialLeadingControl={resetSpatialView}
mediaMultiLayer
unifiedContent={profile ? (
<RerunViewport
profile={profile}
sceneSettings={sceneSettings}
onPlaybackChange={setPlayback}
onPlaybackControllerChange={setPlaybackController}
/>
) : (
<div className="l3-visual-audit__state" role={launchError ? "alert" : "status"}>
{launchError ?? "Готовим единый кэш канонического повтора RAV004…"}
</div>
)}
emptyMessage="Выберите ВИДЕО/КАМЕРА или 3D/ПЛАН. Общие часы Rerun останутся на месте."
transport={transport}
onMediaModeChange={onMediaModeChange}
onSpatialModeChange={onSpatialModeChange}
onExpandedChange={onExpandedChange}
onSplitPrimarySizeChange={onSplitPrimarySizeChange}
/>
);
}
@@ -1026,7 +1026,7 @@ export async function fetchVegetationRouteTgsAnchor(
};
}
export async function fetchVegetationShadowResult(
export async function fetchVegetationShadowResultMetadata(
resultId: string,
{
fetcher = fetch,
@@ -1048,6 +1048,17 @@ export async function fetchVegetationShadowResult(
resultId,
"/api/v1/laboratory/vegetation-shadow",
);
return result;
}
export async function fetchVegetationShadowResult(
resultId: string,
{
fetcher = fetch,
signal,
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<VegetationShadowResult> {
const result = await fetchVegetationShadowResultMetadata(resultId, { fetcher, signal });
if (!result.routeFullReview) return result;
const timelineResponse = await fetcher(
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/route-timeline`,
@@ -0,0 +1,195 @@
export interface ObservationLabReplayCapability {
schemaVersion: "missioncore.observation-lab-replay-capability/v1";
kind: "canonical-recorded-rerun";
viewerProfile: "recorded-session";
timeline: "session_time";
activation: "explicit";
commandsEnabled: false;
}
export class ObservationLabReplayCapabilityContractError extends Error {
constructor(message: string) {
super(message);
this.name = "ObservationLabReplayCapabilityContractError";
}
}
const SHA256 = /^[a-f0-9]{64}$/;
const CAPABILITY_KEYS = new Set([
"schema_version",
"kind",
"viewer_profile",
"timeline",
"activation",
"commands_enabled",
]);
const CANONICAL_PROVENANCE_KEYS = new Set([
"schema_version",
"evidence_identity_sha256",
"result_document_sha256",
"replay_capability",
"authority",
"method",
]);
const AUTHORITY_KEYS = new Set([
"commands_enabled",
"navigation_or_safety_accepted",
"actuation_accepted",
]);
const METHOD_KEYS = new Set([
"schema_version",
"completeness",
"execution_class",
"pipeline_id",
"components",
]);
const METHOD_COMPONENT_KEYS = new Set([
"kind",
"name",
"version",
"role",
"identity_sha256",
]);
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function assertExactKeys(
value: Record<string, unknown>,
expected: ReadonlySet<string>,
label: string,
): void {
const keys = Object.keys(value);
if (keys.length !== expected.size || keys.some((key) => !expected.has(key))) {
throw new ObservationLabReplayCapabilityContractError(
`${label} содержит неизвестные или пропущенные поля.`,
);
}
}
export function decodeLabReplayCapability(
value: unknown,
sessionId: string,
): ObservationLabReplayCapability | null {
if (value === null || value === undefined) return null;
if (!isRecord(value)) {
throw new ObservationLabReplayCapabilityContractError(
`Replay-возможность LAB-сессии ${sessionId} должна быть объектом или null.`,
);
}
assertExactKeys(value, CAPABILITY_KEYS, `Replay-возможность LAB-сессии ${sessionId}`);
if (
value.schema_version !== "missioncore.observation-lab-replay-capability/v1"
|| value.kind !== "canonical-recorded-rerun"
|| value.viewer_profile !== "recorded-session"
|| value.timeline !== "session_time"
|| value.activation !== "explicit"
|| value.commands_enabled !== false
) {
throw new ObservationLabReplayCapabilityContractError(
`Replay-возможность LAB-сессии ${sessionId} нарушает observation-only контракт.`,
);
}
return {
schemaVersion: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commandsEnabled: false,
};
}
export function decodeCanonicalLabReplayCapabilityProvenance(
provenance: Readonly<Record<string, unknown>>,
sessionId: string,
resultId: string,
): ObservationLabReplayCapability {
assertExactKeys(provenance, CANONICAL_PROVENANCE_KEYS, `Canonical provenance LAB-сессии ${sessionId}`);
if (
provenance.schema_version !== "missioncore.canonical-recorded-lab-projection/v1"
|| typeof provenance.evidence_identity_sha256 !== "string"
|| !SHA256.test(provenance.evidence_identity_sha256)
|| resultId !== `lab-v1-vegetation-shadow-${provenance.evidence_identity_sha256}`
|| typeof provenance.result_document_sha256 !== "string"
|| !SHA256.test(provenance.result_document_sha256)
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} потеряла immutable identity.`,
);
}
if (!isRecord(provenance.authority)) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} не содержит authority.`,
);
}
assertExactKeys(provenance.authority, AUTHORITY_KEYS, `Canonical authority LAB-сессии ${sessionId}`);
if (
provenance.authority.commands_enabled !== false
|| provenance.authority.navigation_or_safety_accepted !== false
|| provenance.authority.actuation_accepted !== false
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} нарушает observation-only authority.`,
);
}
if (!isRecord(provenance.method)) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} не содержит method manifest.`,
);
}
assertExactKeys(provenance.method, METHOD_KEYS, `Canonical method LAB-сессии ${sessionId}`);
if (
provenance.method.schema_version !== "missioncore.laboratory-method/v1"
|| provenance.method.completeness !== "legacy-partial"
|| provenance.method.execution_class !== "ai-inference"
|| provenance.method.pipeline_id
!== "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1"
|| !Array.isArray(provenance.method.components)
|| provenance.method.components.length !== 1
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical method LAB-сессии ${sessionId} не соответствует записанному прогону.`,
);
}
const component = provenance.method.components[0];
if (!isRecord(component)) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical method LAB-сессии ${sessionId} не содержит source component.`,
);
}
assertExactKeys(
component,
METHOD_COMPONENT_KEYS,
`Canonical source component LAB-сессии ${sessionId}`,
);
if (
component.kind !== "source"
|| component.name !== "sealed full-route LAB result"
|| component.version !== "missioncore.lab-v1-vegetation-shadow/v1"
|| component.role !== "immutable Session catalog projection"
|| component.identity_sha256 !== provenance.evidence_identity_sha256
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical source component LAB-сессии ${sessionId} потерял immutable identity.`,
);
}
const capability = decodeLabReplayCapability(provenance.replay_capability, sessionId);
if (capability === null) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} не содержит replay capability.`,
);
}
return capability;
}
/** Rolling bridge for a new frontend against the pre-v2 catalog endpoint. */
export function decodeRollingCanonicalLabReplayCapability(
provenance: Readonly<Record<string, unknown>>,
sessionId: string,
resultId: string,
): ObservationLabReplayCapability | null {
if (!Object.prototype.hasOwnProperty.call(provenance, "replay_capability")) return null;
return decodeCanonicalLabReplayCapabilityProvenance(provenance, sessionId, resultId);
}
@@ -0,0 +1,43 @@
export interface ObservationReplayAttempt {
readonly signal: AbortSignal;
isCurrent: () => boolean;
finish: () => boolean;
}
export interface ObservationReplayCoordinator {
begin: () => ObservationReplayAttempt;
cancel: () => void;
}
/** Latest explicit admission owns the viewer, even if an older promise settles later. */
export function createObservationReplayCoordinator(): ObservationReplayCoordinator {
let sequence = 0;
let active: AbortController | null = null;
return {
begin() {
active?.abort();
const controller = new AbortController();
const attemptSequence = ++sequence;
active = controller;
return {
signal: controller.signal,
isCurrent: () => (
!controller.signal.aborted
&& active === controller
&& sequence === attemptSequence
),
finish: () => {
if (active !== controller || sequence !== attemptSequence) return false;
active = null;
return true;
},
};
},
cancel() {
active?.abort();
// Keep settlement ownership until finish(). isCurrent() already fails,
// while begin() can replace this attempt immediately.
},
};
}
@@ -1,6 +1,14 @@
import {
MAX_RECORDED_CAMERA_SOURCES,
} from "./recordedSessionAdmission";
import {
decodeLabReplayCapability,
decodeRollingCanonicalLabReplayCapability,
ObservationLabReplayCapabilityContractError,
type ObservationLabReplayCapability,
} from "./labReplayCapability";
export type { ObservationLabReplayCapability } from "./labReplayCapability";
export type ObservationSessionStatus =
| "recording"
@@ -20,6 +28,7 @@ export interface ObservationLabInstance {
configSha256: string | null;
runCreatedAtUtc: string;
publishedAtUtc: string;
replayCapability: ObservationLabReplayCapability | null;
provenance: Readonly<Record<string, unknown>>;
}
@@ -155,7 +164,7 @@ const ITEM_KEYS = new Set([
"preparation",
"lab",
]);
const LAB_KEYS = new Set([
const LEGACY_LAB_KEYS = new Set([
"lab_id",
"source_session_id",
"result_kind",
@@ -166,6 +175,7 @@ const LAB_KEYS = new Set([
"published_at_utc",
"provenance",
]);
const LAB_KEYS = new Set([...LEGACY_LAB_KEYS, "replay_capability"]);
const CATALOG_PREPARATION_KEYS = new Set([
"preparation_id",
"state",
@@ -394,7 +404,15 @@ function decodeLabInstance(
`LAB-привязка сессии ${sessionId} должна быть объектом или null.`,
);
}
assertExactKeys(value, LAB_KEYS, `LAB-привязка сессии ${sessionId}`);
const hasTypedCapability = Object.prototype.hasOwnProperty.call(
value,
"replay_capability",
);
assertExactKeys(
value,
hasTypedCapability ? LAB_KEYS : LEGACY_LAB_KEYS,
`LAB-привязка сессии ${sessionId}`,
);
const labId = requireString(value.lab_id, `lab(${sessionId}).lab_id`, 36);
if (!/^LAB [A-Z][A-Z0-9._-]{0,31}$/.test(labId)) {
throw new ObservationSessionContractError("Каталог содержит некорректный LAB-маркер.");
@@ -430,6 +448,17 @@ function decodeLabInstance(
if (!isRecord(value.provenance)) {
throw new ObservationSessionContractError("LAB provenance должен быть JSON-объектом.");
}
let replayCapability: ObservationLabReplayCapability | null;
try {
replayCapability = hasTypedCapability
? decodeLabReplayCapability(value.replay_capability, sessionId)
: decodeRollingCanonicalLabReplayCapability(value.provenance, sessionId, resultId);
} catch (error) {
if (error instanceof ObservationLabReplayCapabilityContractError) {
throw new ObservationSessionContractError(error.message);
}
throw error;
}
return {
labId,
sourceSessionId,
@@ -445,6 +474,7 @@ function decodeLabInstance(
value.published_at_utc,
`lab(${sessionId}).published_at_utc`,
),
replayCapability,
provenance: value.provenance,
};
}
@@ -1181,6 +1211,7 @@ export async function fetchObservationSessionCatalog({
queryParameters.set("limit", String(Number(limit)));
}
if (scope !== "all") queryParameters.set("scope", scope);
if (scope === "laboratory") queryParameters.set("lab_contract", "v2");
const serializedQuery = queryParameters.toString();
const query = serializedQuery ? `?${serializedQuery}` : "";
let response: Response;
@@ -12,6 +12,16 @@ import {
type ObservationSessionScope,
type ObservationSessionSummary,
} from "./sessionArchive";
import {
createObservationReplayCoordinator,
type ObservationReplayCoordinator,
} from "./replayCoordinator";
export {
createObservationReplayCoordinator,
type ObservationReplayAttempt,
type ObservationReplayCoordinator,
} from "./replayCoordinator";
export type ObservationSessionsLoadState = "idle" | "loading" | "ready" | "error";
export type ObservationReplayOutcome = "accepted" | "error" | "cancelled";
@@ -41,17 +51,6 @@ export interface ObservationSessionsController {
remove: (sessionId: string) => Promise<boolean>;
}
export interface ObservationReplayAttempt {
readonly signal: AbortSignal;
isCurrent: () => boolean;
finish: () => boolean;
}
export interface ObservationReplayCoordinator {
begin: () => ObservationReplayAttempt;
cancel: () => void;
}
export async function deleteObservationSessionAfterTeardown(
sessionId: string,
{
@@ -93,41 +92,6 @@ export class ObservationPreparationStalledError extends Error {
}
}
/** Latest selection wins, even if an obsolete server job finishes later. */
export function createObservationReplayCoordinator(): ObservationReplayCoordinator {
let sequence = 0;
let active: AbortController | null = null;
return {
begin() {
active?.abort();
const controller = new AbortController();
const attemptSequence = ++sequence;
active = controller;
return {
signal: controller.signal,
isCurrent: () => (
!controller.signal.aborted &&
active === controller &&
sequence === attemptSequence
),
finish: () => {
if (active !== controller || sequence !== attemptSequence) return false;
active = null;
return true;
},
};
},
cancel() {
active?.abort();
// Keep ownership until the cancelled attempt reaches `finish()`. This
// lets its finally block settle a replacement that already passed
// onReplayBegin, while `isCurrent()` still fails immediately because the
// signal is aborted. A later `begin()` replaces and invalidates it.
},
};
}
function errorMessage(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
@@ -4,6 +4,10 @@ import {
type ObservationSessionFetch,
type ObservationSessionSummary,
} from "../observation/sessionArchive";
import {
observatoryRecordedRunBinding,
type ObservatoryRecordedRunBinding,
} from "./recordedRun";
export interface ObservatoryEvidence {
readonly sessionId: string;
@@ -11,6 +15,7 @@ export interface ObservatoryEvidence {
readonly status: ObservationSessionSummary["status"];
readonly publishedAtUtc: string;
readonly lab: ObservationLabInstance;
readonly recordedRun: ObservatoryRecordedRunBinding | null;
}
export interface ObservatorySession {
@@ -61,6 +66,7 @@ function evidenceFromSession(
status: session.status,
publishedAtUtc: session.lab.publishedAtUtc,
lab: session.lab,
recordedRun: observatoryRecordedRunBinding(session.id, session.lab),
};
}
@@ -0,0 +1,129 @@
import type { LaboratoryFetch } from "../laboratory/advancedResults";
import {
fetchVegetationShadowResultMetadata,
type VegetationFullRouteReview,
type VegetationShadowResult,
} from "../laboratory/vegetationShadow";
import type { ObservationLabInstance } from "../observation/sessionArchive";
import {
decodeCanonicalLabReplayCapabilityProvenance,
ObservationLabReplayCapabilityContractError,
} from "../observation/labReplayCapability";
const CANONICAL_RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
const CANONICAL_SOURCE_SESSION_ID = "20260828T130511Z_viewer_live";
export interface ObservatoryRecordedRunBinding {
readonly kind: "canonical-recorded-rerun";
readonly evidenceSessionId: string;
readonly sourceSessionId: string;
readonly resultId: string;
readonly viewerProfile: "recorded-session";
readonly timeline: "session_time";
readonly activation: "explicit";
}
export class ObservatoryRecordedRunContractError extends Error {
constructor(message: string) {
super(message);
this.name = "ObservatoryRecordedRunContractError";
}
}
export function observatoryRecordedRunBinding(
evidenceSessionId: string,
lab: ObservationLabInstance,
): ObservatoryRecordedRunBinding | null {
const capability = lab.replayCapability;
if (!capability) return null;
try {
decodeCanonicalLabReplayCapabilityProvenance(
lab.provenance,
evidenceSessionId,
lab.resultId,
);
} catch (error) {
if (!(error instanceof ObservationLabReplayCapabilityContractError)) throw error;
throw new ObservatoryRecordedRunContractError(error.message);
}
if (
evidenceSessionId !== lab.resultId
|| lab.sourceSessionId !== CANONICAL_SOURCE_SESSION_ID
|| lab.labId !== "LAB V1"
|| lab.resultKind !== "recorded-perception-qualification"
|| !CANONICAL_RESULT_ID.test(lab.resultId)
|| lab.configSha256 !== null
|| lab.sourceResultId === null
|| !CANONICAL_RESULT_ID.test(lab.sourceResultId)
|| capability.kind !== "canonical-recorded-rerun"
|| capability.viewerProfile !== "recorded-session"
|| capability.timeline !== "session_time"
|| capability.activation !== "explicit"
|| capability.commandsEnabled !== false
) {
throw new ObservatoryRecordedRunContractError(
`LAB-результат ${lab.resultId} не допущен к каноническому recorded replay.`,
);
}
return {
kind: "canonical-recorded-rerun",
evidenceSessionId,
sourceSessionId: lab.sourceSessionId,
resultId: lab.resultId,
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
};
}
export async function fetchObservatoryRecordedRunReview(
binding: ObservatoryRecordedRunBinding,
{
selectedSourceSessionId,
fetcher = fetch,
signal,
}: {
selectedSourceSessionId: string;
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
},
): Promise<VegetationFullRouteReview> {
if (binding.sourceSessionId !== selectedSourceSessionId) {
throw new ObservatoryRecordedRunContractError(
"Запуск не связан с выбранной исходной сессией.",
);
}
const result = await fetchVegetationShadowResultMetadata(binding.resultId, {
fetcher,
signal,
});
return admitObservatoryRecordedRunReview(
binding,
selectedSourceSessionId,
result,
);
}
export function admitObservatoryRecordedRunReview(
binding: ObservatoryRecordedRunBinding,
selectedSourceSessionId: string,
result: VegetationShadowResult,
): VegetationFullRouteReview {
if (binding.sourceSessionId !== selectedSourceSessionId) {
throw new ObservatoryRecordedRunContractError(
"Запуск не связан с выбранной исходной сессией.",
);
}
if (result.resultId !== binding.resultId) {
throw new ObservatoryRecordedRunContractError(
"Запечатанный результат не совпадает с выбранным запуском.",
);
}
const review = result.routeFullReview;
if (!review || review.sessionId !== binding.sourceSessionId) {
throw new ObservatoryRecordedRunContractError(
"Запечатанный результат потерял точную связь с исходной сессией.",
);
}
return review;
}
@@ -145,6 +145,66 @@
gap: 0.85rem;
}
.observatory-evidence-card__heading,
.observatory-evidence-card__action,
.observatory-replay__header,
.observatory-replay-state,
.observatory-replay-state__actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.85rem;
}
.observatory-evidence-card__heading,
.observatory-evidence-card__action,
.observatory-replay__header,
.observatory-replay-state {
flex-wrap: wrap;
}
.observatory-evidence-card__heading > div,
.observatory-evidence-card__action > span,
.observatory-replay__header > div,
.observatory-replay-state > div {
min-width: 0;
}
.observatory-evidence-card__action {
padding-top: 0.85rem;
border-top: 1px solid rgba(var(--nodedc-accent-rgb), 0.18);
}
.observatory-evidence-card__action > span {
color: var(--nodedc-text-muted);
font-size: var(--nodedc-font-size-xs);
}
.observatory-replay,
.observatory-replay-state {
min-width: 0;
}
.observatory-replay {
display: grid;
gap: 0.85rem;
}
.observatory-replay__header {
padding: 0.25rem 0.25rem 0;
}
.observatory-replay__header h3,
.observatory-replay-state h3 {
margin: 0.3rem 0 0;
}
.observatory-replay__header p,
.observatory-replay-state p {
margin: 0.35rem 0 0;
color: var(--nodedc-text-muted);
}
.observatory-evidence-list strong,
.observatory-evidence-list span {
display: block;
@@ -191,6 +251,13 @@
flex-direction: column;
}
.observatory-evidence-card__action,
.observatory-replay__header,
.observatory-replay-state {
align-items: stretch;
flex-direction: column;
}
.observatory-catalog-bar__controls {
flex-basis: auto;
}
@@ -1,249 +1 @@
import { useEffect, useMemo, useState } from "react";
import { Button, Icon, SegmentedControl } from "@nodedc/ui-react";
import { ObservationTimeline } from "../../components/ObservationTimeline";
import {
RerunViewport,
type RerunPlaybackController,
type RerunPlaybackState,
} from "../../components/RerunViewport";
import {
CanonicalRecordedLabReplay,
useCanonicalRecordedLabReplayState,
} from "../../components/laboratory/CanonicalRecordedLabReplay";
import {
resolveCanonicalLabReplay,
type CanonicalLabReplayDescriptor,
} from "../../core/laboratory/canonicalLabReplay";
import type { VegetationFullRouteReview } from "../../core/laboratory/vegetationShadow";
import type { ObservationSessionReplayLaunch } from "../../core/observation/sessionArchive";
import { recordedSessionRerunProfile } from "../../core/observation/viewerProfile";
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
import { defaultSceneSettings } from "../../sceneSettings";
type MediaMode = "video" | "camera";
type SpatialMode = "3d" | "plan";
type SpatialLayer = "source" | "local" | "tgs" | "semantic";
type SemanticLayer = "city" | "vegetation";
interface CanonicalReplayLaunch {
base: ObservationSessionReplayLaunch;
replay: CanonicalLabReplayDescriptor;
}
export function CanonicalVegetationRerunReplay({
resultId,
review,
}: {
resultId: string;
review: VegetationFullRouteReview;
}) {
const {
mediaMode,
spatialMode,
splitPrimarySize,
splitOrientation,
expanded,
onMediaModeChange,
onSpatialModeChange,
onSplitPrimarySizeChange,
onExpandedChange,
} = useCanonicalRecordedLabReplayState<MediaMode, SpatialMode>({
initialMediaMode: "video",
initialSpatialMode: "3d",
});
const [semanticLayer, setSemanticLayer] = useState<SemanticLayer>("vegetation");
const [showSemantics, setShowSemantics] = useState(true);
const [spatialLayer, setSpatialLayer] = useState<SpatialLayer>("source");
const [viewResetGeneration, setViewResetGeneration] = useState<0 | 1>(0);
const [playback, setPlayback] = useState<RerunPlaybackState | null>(null);
const [playbackController, setPlaybackController] =
useState<RerunPlaybackController | null>(null);
const [launch, setLaunch] = useState<CanonicalReplayLaunch | null>(null);
const [launchError, setLaunchError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLaunch(null);
setLaunchError(null);
void resolveObservationSessionReplay(review.sessionId, {
signal: controller.signal,
maximumWaitMs: 30 * 60 * 1000,
onUpdate: () => undefined,
}).then(async (value) => ({
base: value,
replay: await resolveCanonicalLabReplay(resultId, value, {
signal: controller.signal,
}),
})).then((value) => {
if (!controller.signal.aborted) setLaunch(value);
}).catch((caught: unknown) => {
if (!controller.signal.aborted) {
setLaunchError(
caught instanceof Error ? caught.message : "Каноническая запись RAV004 недоступна.",
);
}
});
return () => controller.abort();
}, [resultId, review.sessionId]);
const splitView = mediaMode !== null && spatialMode !== null;
const sceneSettings = useMemo(() => ({
...defaultSceneSettings,
accumulationSeconds: spatialLayer === "local" ? 5 : 0,
showPoints: spatialMode !== null,
showTrajectory: spatialMode !== null,
showGrid: spatialMode !== null,
pointSize: 3.8,
}), [spatialLayer, spatialMode]);
const profile = launch ? recordedSessionRerunProfile({
sourceUrl: launch.replay.sourceUrl,
artifact: {
sourceUrl: launch.replay.sourceUrl,
viewerSourceUrl: launch.replay.viewerSourceUrl,
byteLength: launch.replay.byteLength,
sha256: launch.replay.sha256,
},
blueprintSourceUrl: launch.replay.blueprintSourceUrl,
autoplayWhenReady: false,
presentationGate: "ready",
expectedTimelineStartSeconds: launch.base.timelineStartSeconds,
expectedTimelineEndSeconds: launch.base.timelineEndSeconds,
initialPlaybackStartSeconds: review.timelineStartSeconds,
view: mediaMode !== null ? "perception" : "spatial",
viewResetGeneration,
followTrajectory: true,
semanticLayer,
unifiedPerception: splitView,
planView: spatialMode === "plan",
perceptionLayers: {
enabled: mediaMode !== null,
detections2d: mediaMode === "video",
segmentation: mediaMode === "video" && showSemantics,
cuboids3d: false,
},
perceptionRetryGeneration: 0,
lockPerceptionCameraInteraction: false,
}) : null;
const mediaLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Слои камеры и видео"
>
<Button
size="dense"
shape="pill"
variant={showSemantics ? "primary" : "secondary"}
aria-pressed={showSemantics}
onClick={() => setShowSemantics((visible) => !visible)}
>
СЕМАНТИКА
</Button>
<SegmentedControl
value={semanticLayer}
items={[
{ value: "city", label: "ГОРОД · EoMT" },
{ value: "vegetation", label: "ПРИРОДА · DDRNet" },
]}
label="Источник семантики"
size="dense"
onChange={(value) => {
setSemanticLayer(value);
setShowSemantics(true);
}}
/>
</div>
);
const spatialLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Пространственные слои RAV004"
>
<SegmentedControl
value={spatialLayer}
items={[
{ value: "source", label: "ИСХ. ТОЧКИ" },
{ value: "local", label: "ЛОК. SLAM" },
{ value: "tgs", label: "TGS", disabled: true },
{ value: "semantic", label: "СЕМАНТИКА", disabled: true },
]}
label="Пространственные слои"
size="dense"
onChange={setSpatialLayer}
/>
</div>
);
const resetSpatialView = (
<Button
size="dense"
variant="ghost"
icon={<Icon name="refresh" size={14} />}
aria-label="Сбросить положение 3D камеры"
title="Сбросить положение 3D камеры"
onClick={() => setViewResetGeneration((value) => value === 0 ? 1 : 0)}
>
</Button>
);
const transport = playback && playbackController ? (
<ObservationTimeline
className="m4-replay-threat-visual__timeline"
active
sourceCount={3}
mode="recorded"
seekable
synchronization="shared-clock"
rangeNs={playback.rangeNs}
currentNs={playback.currentNs}
playing={playback.playing}
onSeek={playbackController.seek}
onPlayingChange={playbackController.setPlaying}
showJumpToEnd={false}
/>
) : undefined;
return (
<CanonicalRecordedLabReplay
label="RAVNOVES004TREE · канонический повтор Rerun"
mediaMode={mediaMode ?? "none"}
mediaModes={[
{ value: "video", label: "ВИДЕО" },
{ value: "camera", label: "КАМЕРА" },
]}
spatialMode={spatialMode ?? "none"}
spatialModes={[
{ value: "3d", label: "3D" },
{ value: "plan", label: "ПЛАН" },
]}
expanded={expanded}
splitPrimarySize={splitPrimarySize}
splitOrientation={splitOrientation}
mediaAriaLabel={mediaMode === "camera" ? "Камера" : "Видео и семантика"}
spatialAriaLabel={spatialMode === "plan" ? "Вид сверху" : "Трёхмерная сцена"}
mediaLayerControls={mediaLayerControls}
spatialLayerControls={spatialLayerControls}
spatialLeadingControl={resetSpatialView}
mediaMultiLayer
unifiedContent={profile ? (
<RerunViewport
profile={profile}
sceneSettings={sceneSettings}
onPlaybackChange={setPlayback}
onPlaybackControllerChange={setPlaybackController}
/>
) : (
<div className="l3-visual-audit__state" role={launchError ? "alert" : "status"}>
{launchError ?? "Готовим единый кэш канонического повтора RAV004…"}
</div>
)}
emptyMessage="Выберите ВИДЕО/КАМЕРА или 3D/ПЛАН. Общие часы Rerun останутся на месте."
transport={transport}
onMediaModeChange={onMediaModeChange}
onSpatialModeChange={onSpatialModeChange}
onExpandedChange={onExpandedChange}
onSplitPrimarySizeChange={onSplitPrimarySizeChange}
/>
);
}
export { CanonicalVegetationRerunReplay } from "../../components/laboratory/CanonicalVegetationRerunReplay";
@@ -54,6 +54,7 @@ import {
buildLaboratoryProfiles,
experimentOptionsForProfile,
freshestLaboratorySelection,
isLegacyPublishedLaboratoryWork,
workOptionsForExperiment,
} from "./laboratoryArchiveProfiles";
import {
@@ -499,12 +500,7 @@ export function LaboratoryArchiveWorkspace(props: LaboratoryWorkspaceProps) {
onDeleteBegin: props.sessionArchive.onDeleteBegin,
});
const publishedWorks = useMemo(
() => sessions.items.filter((session) => (
session.lab !== null
&& session.status === "ready"
&& session.replayable
&& session.modalities.includes("point-cloud")
)).sort((left, right) => (
() => sessions.items.filter(isLegacyPublishedLaboratoryWork).sort((left, right) => (
Date.parse(right.lab?.runCreatedAtUtc ?? right.startedAtUtc)
- Date.parse(left.lab?.runCreatedAtUtc ?? left.startedAtUtc)
)),
@@ -459,6 +459,17 @@ function pipelineIdForSession(session: ObservationSessionSummary): string {
return session.lab?.resultKind ?? "legacy-perception";
}
/** Keep capability projections owned by Observatory out of the legacy LAB surface. */
export function isLegacyPublishedLaboratoryWork(
session: ObservationSessionSummary,
): boolean {
return session.lab !== null
&& session.lab.replayCapability === null
&& session.status === "ready"
&& session.replayable
&& session.modalities.includes("point-cloud");
}
export function buildLaboratoryCatalog({
rigLabel,
knownWorks,
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
ActivityIndicator,
Button,
@@ -8,11 +8,38 @@ import {
StatusBadge,
} from "@nodedc/ui-react";
import { CanonicalVegetationRerunReplay } from "../../components/laboratory/CanonicalVegetationRerunReplay";
import type { ObservationSessionStatus } from "../../core/observation/sessionArchive";
import { createObservationReplayCoordinator } from "../../core/observation/replayCoordinator";
import {
fetchObservatoryRecordedRunReview,
type ObservatoryRecordedRunBinding,
} from "../../core/observatory/recordedRun";
import { useObservatoryCatalog } from "../../core/observatory/useObservatoryCatalog";
import type { WorkspaceDefinition } from "../../productModel";
const MAX_PRESENTED_EVIDENCE = 6;
const EMPTY_OBSERVATORY_ITEMS = [] as const;
type ObservatoryRecordedRunReview = Awaited<
ReturnType<typeof fetchObservatoryRecordedRunReview>
>;
type ObservatoryReplayState =
| { readonly kind: "closed" }
| {
readonly kind: "loading";
readonly binding: ObservatoryRecordedRunBinding;
}
| {
readonly kind: "ready";
readonly binding: ObservatoryRecordedRunBinding;
readonly review: ObservatoryRecordedRunReview;
}
| {
readonly kind: "error";
readonly binding: ObservatoryRecordedRunBinding;
readonly message: string;
};
const statusLabel: Record<ObservationSessionStatus, string> = {
recording: "Запись идёт",
@@ -74,12 +101,22 @@ export function ObservatoryWorkspace({
}) {
const controller = useObservatoryCatalog();
const [selectedSessionId, setSelectedSessionId] = useState("");
const items = controller.catalog?.items ?? [];
const [replay, setReplay] = useState<ObservatoryReplayState>({ kind: "closed" });
const replayCoordinatorRef = useRef(createObservationReplayCoordinator());
const items = controller.catalog?.items ?? EMPTY_OBSERVATORY_ITEMS;
const closeReplay = useCallback(() => {
replayCoordinatorRef.current.cancel();
setReplay((current) => current.kind === "closed" ? current : { kind: "closed" });
}, []);
useEffect(() => {
if (items.some((item) => item.source.id === selectedSessionId)) return;
closeReplay();
setSelectedSessionId(items[0]?.source.id ?? "");
}, [items, selectedSessionId]);
}, [closeReplay, items, selectedSessionId]);
useEffect(() => () => replayCoordinatorRef.current.cancel(), []);
const selectedSession = items.find(
(item) => item.source.id === selectedSessionId,
@@ -96,12 +133,51 @@ export function ObservatoryWorkspace({
const initialLoading = !controller.catalog
&& ["idle", "loading"].includes(controller.state);
const unavailable = !controller.catalog && controller.state === "error";
const replayEvidenceId = replay.kind === "closed"
? null
: replay.binding.evidenceSessionId;
useEffect(() => {
if (
replayEvidenceId === null
|| selectedSession?.evidence.some(
(evidence) => evidence.sessionId === replayEvidenceId,
)
) return;
closeReplay();
}, [closeReplay, replayEvidenceId, selectedSession]);
const openReplay = useCallback((binding: ObservatoryRecordedRunBinding) => {
const attempt = replayCoordinatorRef.current.begin();
setReplay({ kind: "loading", binding });
void fetchObservatoryRecordedRunReview(binding, {
selectedSourceSessionId: selectedSessionId,
signal: attempt.signal,
}).then((review) => {
if (!attempt.isCurrent() || !attempt.finish()) return;
setReplay({ kind: "ready", binding, review });
}).catch((caught: unknown) => {
if (!attempt.isCurrent() || !attempt.finish()) return;
setReplay({
kind: "error",
binding,
message: caught instanceof Error && caught.message.trim()
? caught.message
: "Канонический визуальный разбор недоступен.",
});
});
}, [selectedSessionId]);
const selectSession = useCallback((sessionId: string) => {
closeReplay();
setSelectedSessionId(sessionId);
}, [closeReplay]);
return (
<div
className="observatory-workspace"
data-observatory-authority="observation-only"
data-observatory-viewer="detached"
data-observatory-viewer={replay.kind === "ready" ? "attached" : "detached"}
>
<section className="observatory-lead">
<div>
@@ -123,7 +199,7 @@ export function ObservatoryWorkspace({
<div className="observatory-catalog-bar__copy">
<span className="section-eyebrow">ИСТОЧНИК ДОКАЗАТЕЛЬСТВ</span>
<h3>Сохранённая сессия</h3>
<p>Выбор меняет только читаемую карточку и не готовит Rerun-запись в фоне.</p>
<p>Выбор меняет только читаемую карточку и не готовит визуальный разбор в фоне.</p>
</div>
<div className="observatory-catalog-bar__controls">
<Select
@@ -136,7 +212,7 @@ export function ObservatoryWorkspace({
emptyLabel="Сессия не найдена"
minMenuWidth={360}
menuWidth={460}
onChange={setSelectedSessionId}
onChange={selectSession}
/>
<Button
size="compact"
@@ -241,9 +317,14 @@ export function ObservatoryWorkspace({
{presentedEvidence.map((evidence) => (
<li key={evidence.sessionId}>
<GlassSurface className="observatory-evidence-card" padding="md" tone="soft">
<div>
<strong>{evidence.lab.labId}</strong>
<span>{evidence.label}</span>
<div className="observatory-evidence-card__heading">
<div>
<strong>{evidence.lab.labId}</strong>
<span>{evidence.label}</span>
</div>
{evidence.recordedRun ? (
<StatusBadge tone="accent">Записанный разбор</StatusBadge>
) : null}
</div>
<dl>
<div><dt>Тип результата</dt><dd>{evidence.lab.resultKind}</dd></div>
@@ -253,6 +334,34 @@ export function ObservatoryWorkspace({
</div>
<div><dt>Опубликован</dt><dd>{formatTimestamp(evidence.publishedAtUtc)}</dd></div>
</dl>
{evidence.recordedRun ? (
<div className="observatory-evidence-card__action">
<span>
Записанный маршрут · единая временная шкала · только наблюдение
</span>
<Button
size="compact"
variant="primary"
icon={<Icon name="play" size={14} />}
disabled={
replay.kind === "loading"
&& replay.binding.evidenceSessionId === evidence.sessionId
}
onClick={() => openReplay(evidence.recordedRun!)}
>
{replay.kind === "loading"
&& replay.binding.evidenceSessionId === evidence.sessionId
? "Проверяем результат"
: replay.kind === "ready"
&& replay.binding.evidenceSessionId === evidence.sessionId
? "Открыть заново"
: replay.kind === "error"
&& replay.binding.evidenceSessionId === evidence.sessionId
? "Повторить открытие"
: "Открыть визуальный разбор"}
</Button>
</div>
) : null}
</GlassSurface>
</li>
))}
@@ -277,6 +386,57 @@ export function ObservatoryWorkspace({
</section>
) : null}
{replay.kind === "loading" ? (
<GlassSurface className="observatory-replay-state" padding="lg" role="status">
<ActivityIndicator label="Проверяем запечатанный результат" />
<div>
<h3>Проверяем точную связь результата с исходной сессией</h3>
<p>Визуализатор и данные маршрута ещё не запущены.</p>
</div>
<Button size="compact" variant="ghost" onClick={closeReplay}>Отменить</Button>
</GlassSurface>
) : replay.kind === "error" ? (
<GlassSurface className="observatory-replay-state" padding="lg" role="alert">
<Icon name="alert" size={20} />
<div>
<StatusBadge tone="danger">Просмотр недоступен</StatusBadge>
<h3>{replay.message}</h3>
</div>
<div className="observatory-replay-state__actions">
<Button
size="compact"
variant="secondary"
onClick={() => openReplay(replay.binding)}
>
Повторить
</Button>
<Button size="compact" variant="ghost" onClick={closeReplay}>Закрыть</Button>
</div>
</GlassSurface>
) : replay.kind === "ready" ? (
<section className="observatory-replay" aria-label="Канонический визуальный разбор">
<header className="observatory-replay__header">
<div>
<span className="section-eyebrow">ВИЗУАЛЬНЫЙ РАЗБОР / ЗАПИСАННАЯ СЕССИЯ</span>
<h3>RAVNOVES004TREE · полный маршрут восприятия</h3>
<p>Записанный маршрут синхронизирован по общей временной шкале.</p>
</div>
<Button
size="compact"
variant="secondary"
icon={<Icon name="close" size={14} />}
onClick={closeReplay}
>
Закрыть разбор
</Button>
</header>
<CanonicalVegetationRerunReplay
resultId={replay.binding.resultId}
review={replay.review}
/>
</section>
) : null}
{(controller.catalog?.unresolvedEvidence.length ?? 0) > 0 ? (
<GlassSurface className="observatory-notice" padding="md" tone="soft" role="status">
<StatusBadge tone="warning">Вне среза</StatusBadge>
@@ -11,6 +11,7 @@ let fetchM49TgsAnchorSpatial;
let AdvancedLaboratoryContractError;
let buildLaboratoryCatalog;
let buildLaboratoryProfiles;
let isLegacyPublishedLaboratoryWork;
let experimentOptionsForProfile;
let freshestLaboratorySelection;
let workOptionsForExperiment;
@@ -928,6 +929,7 @@ before(async () => {
({
buildLaboratoryCatalog,
buildLaboratoryProfiles,
isLegacyPublishedLaboratoryWork,
experimentOptionsForProfile,
freshestLaboratorySelection,
workOptionsForExperiment,
@@ -962,6 +964,31 @@ after(async () => {
await server?.close();
});
test("capability-owned projections never enter the legacy LAB catalog", () => {
const legacy = {
status: "ready",
replayable: true,
modalities: ["point-cloud", "trajectory"],
lab: { replayCapability: null },
};
const capabilityProjection = {
...legacy,
lab: {
replayCapability: {
schemaVersion: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commandsEnabled: false,
},
},
};
assert.equal(isLegacyPublishedLaboratoryWork(legacy), true);
assert.equal(isLegacyPublishedLaboratoryWork(capabilityProjection), false);
});
test("LAB catalog is pipeline-scoped and ordered by real run time", () => {
const catalog = buildLaboratoryCatalog({
rigLabel: "K1",
@@ -105,10 +105,14 @@ test("laboratory UI is a bounded feature slice, not a central workspace branch",
assert.match(e30HumanReviewCss, /\.e30-human-review/);
});
test("Observatory is a bounded read-only slice outside legacy LAB and viewer lifecycles", async () => {
test("Observatory owns a bounded explicit-open lifecycle around the shared recorded viewer", async () => {
const workspaceHub = await read("workspaces/Workspaces.tsx");
const observatory = await read("workspaces/observatory/ObservatoryWorkspace.tsx");
const observatoryCore = await read("core/observatory/catalog.ts");
const recordedRun = await read("core/observatory/recordedRun.ts");
const sharedReplay = await read(
"components/laboratory/CanonicalVegetationRerunReplay.tsx",
);
const workspaceCss = await read("styles/workspaces.css");
const observatoryCss = await read("styles/observatory.css");
@@ -119,12 +123,27 @@ test("Observatory is a bounded read-only slice outside legacy LAB and viewer lif
assert.match(observatory, /export function ObservatoryWorkspace/);
assert.match(observatory, /useObservatoryCatalog/);
assert.match(observatory, /data-observatory-authority="observation-only"/);
assert.match(observatory, /data-observatory-viewer="detached"/);
assert.match(
observatory,
/data-observatory-viewer={replay\.kind === "ready" \? "attached" : "detached"}/,
);
assert.doesNotMatch(
`${observatory}\n${observatoryCore}`,
/(?:core|components|workspaces)\/laboratory|\/api\/v1\/laboratory|RerunViewport|ObservationSessionSelect/,
/(?:core|workspaces)\/laboratory|\/api\/v1\/laboratory|RerunViewport|ObservationSessionSelect/,
);
assert.doesNotMatch(observatory, /replayObservationSession|deleteObservationSession/);
assert.match(observatory, /replay\.kind === "ready"[\s\S]*<CanonicalVegetationRerunReplay/);
assert.match(observatory, /closeReplay\(\);[\s\S]*setSelectedSessionId/);
assert.doesNotMatch(
observatory,
/replayObservationSession|deleteObservationSession|useObservationSessions|useAdvancedLaboratoryCatalog/,
);
assert.match(recordedRun, /fetchVegetationShadowResultMetadata/);
assert.doesNotMatch(
recordedRun,
/advanced-index|resolveObservationSessionReplay|resolveCanonicalLabReplay|RerunViewport/,
);
assert.equal(sharedReplay.match(/<RerunViewport\b/g)?.length, 1);
assert.match(sharedReplay, /recordedSessionRerunProfile/);
assert.doesNotMatch(workspaceCss, /\.observatory-/);
assert.match(observatoryCss, /\.observatory-workspace/);
});
@@ -77,6 +77,57 @@ function session(overrides = {}) {
};
}
function canonicalLab(overrides = {}) {
const resultId = `lab-v1-vegetation-shadow-${"8".repeat(64)}`;
return {
lab_id: "LAB V1",
source_session_id: "20260828T130511Z_viewer_live",
result_kind: "recorded-perception-qualification",
result_id: resultId,
source_result_id: `lab-v1-vegetation-shadow-${"9".repeat(64)}`,
config_sha256: null,
run_created_at_utc: "2026-08-29T18:05:11.329061+00:00",
published_at_utc: "2026-08-30T12:00:00.000Z",
replay_capability: {
schema_version: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewer_profile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commands_enabled: false,
},
provenance: {},
...overrides,
};
}
function canonicalProjectionProvenance(resultId, replayCapability) {
return {
schema_version: "missioncore.canonical-recorded-lab-projection/v1",
evidence_identity_sha256: resultId.slice("lab-v1-vegetation-shadow-".length),
result_document_sha256: "a".repeat(64),
replay_capability: replayCapability,
authority: {
commands_enabled: false,
navigation_or_safety_accepted: false,
actuation_accepted: false,
},
method: {
schema_version: "missioncore.laboratory-method/v1",
completeness: "legacy-partial",
execution_class: "ai-inference",
pipeline_id: "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
components: [{
kind: "source",
name: "sealed full-route LAB result",
version: "missioncore.lab-v1-vegetation-shadow/v1",
role: "immutable Session catalog projection",
identity_sha256: resultId.slice("lab-v1-vegetation-shadow-".length),
}],
},
};
}
function replay(overrides = {}) {
const sessionId = overrides.session_id ?? "session-20260716T205632Z";
const sourceUrl = overrides.source_url ??
@@ -141,6 +192,74 @@ test("session catalog decodes canonical snake_case into a path-free camelCase mo
assert.equal("path" in catalog.items[0], false);
});
test("session catalog strictly decodes the explicit recorded LAB replay capability", () => {
const resultId = `lab-v1-vegetation-shadow-${"8".repeat(64)}`;
const decoded = decodeObservationSessionCatalog({
items: [session({ id: resultId, lab: canonicalLab() })],
}).items[0].lab;
assert.deepEqual(decoded.replayCapability, {
schemaVersion: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commandsEnabled: false,
});
const rollingUpgradeLab = canonicalLab();
rollingUpgradeLab.provenance = canonicalProjectionProvenance(
resultId,
rollingUpgradeLab.replay_capability,
);
delete rollingUpgradeLab.replay_capability;
assert.deepEqual(
decodeObservationSessionCatalog({
items: [session({ id: resultId, lab: rollingUpgradeLab })],
}).items[0].lab.replayCapability,
decoded.replayCapability,
);
const invalidComponents = [
{ kind: "tool" },
{ name: "unsealed result" },
{ version: "missioncore.lab-v1-vegetation-shadow/v0" },
{ role: "mutable projection" },
{ identity_sha256: "0".repeat(64) },
{ unexpected: true },
];
for (const componentOverride of invalidComponents) {
const invalidRollingUpgradeLab = canonicalLab();
invalidRollingUpgradeLab.provenance = canonicalProjectionProvenance(
resultId,
invalidRollingUpgradeLab.replay_capability,
);
invalidRollingUpgradeLab.provenance.method.components = [{
...invalidRollingUpgradeLab.provenance.method.components[0],
...componentOverride,
}];
delete invalidRollingUpgradeLab.replay_capability;
assert.throws(
() => decodeObservationSessionCatalog({
items: [session({ id: resultId, lab: invalidRollingUpgradeLab })],
}),
ObservationSessionContractError,
);
}
assert.throws(
() => decodeObservationSessionCatalog({
items: [session({
id: resultId,
lab: canonicalLab({
replay_capability: {
...canonicalLab().replay_capability,
commands_enabled: true,
},
}),
})],
}),
ObservationSessionContractError,
);
});
test("opened archive is named in the scene header and trash hover has no pill", async () => {
const appSource = await readFile(new URL("../src/App.tsx", import.meta.url), "utf8");
const styles = await readFile(
@@ -246,7 +365,7 @@ test("source and laboratory catalogs are requested as disjoint backend projectio
assert.deepEqual(calls, [
"/api/v1/observation-sessions?limit=100&scope=source",
"/api/v1/observation-sessions?limit=100&scope=laboratory",
"/api/v1/observation-sessions?limit=100&scope=laboratory&lab_contract=v2",
]);
});
@@ -60,11 +60,47 @@ function evidence(id, sourceSessionId, publishedAtUtc) {
configSha256: "a".repeat(64),
runCreatedAtUtc: publishedAtUtc,
publishedAtUtc,
replayCapability: null,
provenance: { verdict: "must-not-be-inferred" },
},
};
}
function canonicalProvenance(resultId) {
const identitySha256 = resultId.slice("lab-v1-vegetation-shadow-".length);
return {
schema_version: "missioncore.canonical-recorded-lab-projection/v1",
evidence_identity_sha256: identitySha256,
result_document_sha256: "a".repeat(64),
replay_capability: {
schema_version: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewer_profile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commands_enabled: false,
},
authority: {
commands_enabled: false,
navigation_or_safety_accepted: false,
actuation_accepted: false,
},
method: {
schema_version: "missioncore.laboratory-method/v1",
completeness: "legacy-partial",
execution_class: "ai-inference",
pipeline_id: "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
components: [{
kind: "source",
name: "sealed full-route LAB result",
version: "missioncore.lab-v1-vegetation-shadow/v1",
role: "immutable Session catalog projection",
identity_sha256: identitySha256,
}],
},
};
}
test("Observatory joins evidence only by sourceSessionId and keeps deterministic order", () => {
const catalog = buildObservatoryCatalog(
[
@@ -130,7 +166,7 @@ test("Observatory fetches disjoint read-only source and laboratory projections",
assert.deepEqual(
calls.map(({ input }) => input).sort(),
[
"/api/v1/observation-sessions?limit=50&scope=laboratory",
"/api/v1/observation-sessions?limit=50&scope=laboratory&lab_contract=v2",
"/api/v1/observation-sessions?limit=50&scope=source",
],
);
@@ -157,3 +193,43 @@ test("Observatory exposes bounded-window uncertainty without inventing a broken
["outside-window"],
);
});
test("Observatory projects a typed canonical run only through its exact sourceSessionId", () => {
const canonicalResultId = `lab-v1-vegetation-shadow-${"8".repeat(64)}`;
const canonical = evidence(
canonicalResultId,
"20260828T130511Z_viewer_live",
"2026-08-29T18:05:11Z",
);
canonical.lab = {
...canonical.lab,
labId: "LAB V1",
resultKind: "recorded-perception-qualification",
resultId: canonicalResultId,
sourceResultId: `lab-v1-vegetation-shadow-${"9".repeat(64)}`,
configSha256: null,
provenance: canonicalProvenance(canonicalResultId),
replayCapability: {
schemaVersion: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commandsEnabled: false,
},
};
const catalog = buildObservatoryCatalog(
[source("20260828T130511Z_viewer_live", "2026-08-28T13:05:11Z")],
[canonical],
);
assert.deepEqual(catalog.items[0].evidence[0].recordedRun, {
kind: "canonical-recorded-rerun",
evidenceSessionId: canonicalResultId,
sourceSessionId: "20260828T130511Z_viewer_live",
resultId: canonicalResultId,
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
});
});
@@ -0,0 +1,180 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { after, before, test } from "node:test";
import { createServer } from "vite";
let server;
let admitObservatoryRecordedRunReview;
let observatoryRecordedRunBinding;
let ObservatoryRecordedRunContractError;
before(async () => {
server = await createServer({
appType: "custom",
logLevel: "silent",
server: { middlewareMode: true },
});
({
admitObservatoryRecordedRunReview,
observatoryRecordedRunBinding,
ObservatoryRecordedRunContractError,
} = await server.ssrLoadModule("/src/core/observatory/recordedRun.ts"));
});
after(async () => {
await server?.close();
});
const sourceSessionId = "20260828T130511Z_viewer_live";
const resultId = `lab-v1-vegetation-shadow-${"8".repeat(64)}`;
function canonicalProvenance() {
const identitySha256 = resultId.slice("lab-v1-vegetation-shadow-".length);
return {
schema_version: "missioncore.canonical-recorded-lab-projection/v1",
evidence_identity_sha256: identitySha256,
result_document_sha256: "a".repeat(64),
replay_capability: {
schema_version: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewer_profile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commands_enabled: false,
},
authority: {
commands_enabled: false,
navigation_or_safety_accepted: false,
actuation_accepted: false,
},
method: {
schema_version: "missioncore.laboratory-method/v1",
completeness: "legacy-partial",
execution_class: "ai-inference",
pipeline_id: "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
components: [{
kind: "source",
name: "sealed full-route LAB result",
version: "missioncore.lab-v1-vegetation-shadow/v1",
role: "immutable Session catalog projection",
identity_sha256: identitySha256,
}],
},
};
}
function lab(overrides = {}) {
return {
labId: "LAB V1",
sourceSessionId,
resultKind: "recorded-perception-qualification",
resultId,
sourceResultId: `lab-v1-vegetation-shadow-${"9".repeat(64)}`,
configSha256: null,
runCreatedAtUtc: "2026-08-29T18:05:11.329061+00:00",
publishedAtUtc: "2026-08-30T12:00:00.000Z",
replayCapability: {
schemaVersion: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commandsEnabled: false,
},
provenance: canonicalProvenance(),
...overrides,
};
}
test("Observatory admits only the exact typed canonical recorded run", () => {
const binding = observatoryRecordedRunBinding(resultId, lab());
assert.deepEqual(binding, {
kind: "canonical-recorded-rerun",
evidenceSessionId: resultId,
sourceSessionId,
resultId,
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
});
assert.equal(
observatoryRecordedRunBinding(resultId, lab({ replayCapability: null })),
null,
);
assert.throws(
() => observatoryRecordedRunBinding("different-session", lab()),
ObservatoryRecordedRunContractError,
);
assert.throws(
() => observatoryRecordedRunBinding(
resultId,
lab({ sourceSessionId: "RAVNOVES004TREE" }),
),
ObservatoryRecordedRunContractError,
);
assert.throws(
() => observatoryRecordedRunBinding(
resultId,
lab({ configSha256: "a".repeat(64) }),
),
ObservatoryRecordedRunContractError,
);
assert.throws(
() => observatoryRecordedRunBinding(
resultId,
lab({ sourceResultId: "legacy-result" }),
),
ObservatoryRecordedRunContractError,
);
const invalidProvenance = canonicalProvenance();
invalidProvenance.method.components[0].identity_sha256 = "0".repeat(64);
assert.throws(
() => observatoryRecordedRunBinding(
resultId,
lab({ provenance: invalidProvenance }),
),
ObservatoryRecordedRunContractError,
);
});
test("Observatory rechecks the selected source and sealed review before mounting replay", () => {
const binding = observatoryRecordedRunBinding(resultId, lab());
const review = { sessionId: sourceSessionId, frameCount: 6830 };
assert.equal(
admitObservatoryRecordedRunReview(
binding,
sourceSessionId,
{ resultId, routeFullReview: review },
),
review,
);
assert.throws(
() => admitObservatoryRecordedRunReview(
binding,
"another-session",
{ resultId, routeFullReview: review },
),
ObservatoryRecordedRunContractError,
);
assert.throws(
() => admitObservatoryRecordedRunReview(
binding,
sourceSessionId,
{ resultId, routeFullReview: { ...review, sessionId: "another-session" } },
),
ObservatoryRecordedRunContractError,
);
});
test("Observatory run admission remains metadata-only until explicit UI activation", async () => {
const source = await readFile(
new URL("../src/core/observatory/recordedRun.ts", import.meta.url),
"utf8",
);
assert.match(source, /fetchVegetationShadowResultMetadata/);
assert.doesNotMatch(
source,
/fetchVegetationShadowResult\(|advanced-index|resolveObservationSessionReplay|resolveCanonicalLabReplay|RerunViewport|recording\.rrd/,
);
});
@@ -46,12 +46,25 @@ test("Observatory is the third independent Polygon workspace", () => {
assert.equal(productModel.workspaceById("lab-archive").kind, "lab-archive");
});
test("Observatory reads catalog evidence without inheriting replay or LAB composition", async () => {
const [app, workspaceHub, workspace, hook, viewerProfiles, styles] = await Promise.all([
test("Observatory mounts the one shared canonical replay only after explicit admission", async () => {
const [
app,
workspaceHub,
workspace,
hook,
recordedRun,
sharedReplay,
legacyReplayWrapper,
viewerProfiles,
styles,
] = await Promise.all([
read("App.tsx"),
read("workspaces/Workspaces.tsx"),
read("workspaces/observatory/ObservatoryWorkspace.tsx"),
read("core/observatory/useObservatoryCatalog.ts"),
read("core/observatory/recordedRun.ts"),
read("components/laboratory/CanonicalVegetationRerunReplay.tsx"),
read("workspaces/laboratory/CanonicalVegetationRerunReplay.tsx"),
read("core/observation/viewerProfile.ts"),
read("styles/observatory.css"),
]);
@@ -68,11 +81,30 @@ test("Observatory reads catalog evidence without inheriting replay or LAB compos
assert.match(workspace, /вне текущего загруженного среза/);
assert.match(workspace, /observatory-notice__copy/);
assert.match(workspace, /observatory-evidence-card/);
assert.match(workspace, /Открыть визуальный разбор/);
assert.match(workspace, /replay\.kind === "ready"[\s\S]*<CanonicalVegetationRerunReplay/);
assert.match(workspace, /Проверяем точную связь результата/);
assert.match(workspace, /role="alert"/);
assert.match(workspace, /Повторить/);
assert.match(workspace, /Закрыть разбор/);
assert.match(workspace, /const selectSession[\s\S]*closeReplay\(\);[\s\S]*setSelectedSessionId/);
assert.match(workspace, /data-observatory-authority="observation-only"/);
assert.doesNotMatch(workspace, /Нарушена связь|compactIdentity/);
assert.doesNotMatch(
`${workspace}\n${hook}`,
/RerunViewport|ObservationSessionSelect|resolveObservationSessionReplay|deleteObservationSession|setInterval|setTimeout/,
/RerunViewport|ObservationSessionSelect|resolveObservationSessionReplay|resolveCanonicalLabReplay|deleteObservationSession|setInterval|setTimeout|useObservationSessions|useAdvancedLaboratoryCatalog|advanced-index|prefetch|preload/,
);
assert.equal(sharedReplay.match(/<RerunViewport\b/g)?.length, 1);
assert.match(sharedReplay, /recordedSessionRerunProfile/);
assert.match(sharedReplay, /timelineStartSeconds/);
assert.doesNotMatch(sharedReplay, /live-acquisition|lab-recorded-evidence/);
assert.match(
legacyReplayWrapper,
/export \{ CanonicalVegetationRerunReplay \} from "\.\.\/\.\.\/components\/laboratory\/CanonicalVegetationRerunReplay"/,
);
assert.match(recordedRun, /fetchVegetationShadowResultMetadata/);
assert.doesNotMatch(recordedRun, /advanced-index|recording\.rrd|canonical-replay\.rrd/);
assert.doesNotMatch(workspace, /workspaces\/laboratory|LaboratoryRecordedClipPlayer|SimulationViewport/);
assert.match(viewerProfiles, /kind: "live-acquisition"/);
assert.match(viewerProfiles, /kind: "recorded-session"/);
assert.match(viewerProfiles, /kind: "lab-recorded-evidence"/);
@@ -470,7 +470,7 @@ test("vegetation realtime LAB uses one upstream Rerun clock and keeps archival r
"utf8",
),
readFile(
new URL("../src/workspaces/laboratory/CanonicalVegetationRerunReplay.tsx", import.meta.url),
new URL("../src/components/laboratory/CanonicalVegetationRerunReplay.tsx", import.meta.url),
"utf8",
),
]);
+57 -4
View File
@@ -51,9 +51,48 @@ The existing source-specific viewer contracts remain separate and unchanged:
Canonical LAB compositions may configure the recorded Rerun engine inside the bounded LAB slice,
but that does not make Observatory a LAB or live consumer. The first Observatory slice mounts no
viewer at all. A later viewer slice may activate only the historical `recorded-session` profile
after an explicit Session selection and a separate acceptance proof. It must not add a fourth
profile or silently collapse the three existing lifecycles.
viewer while its catalog is opened, refreshed or navigated. Its bounded canary may activate only
the historical `recorded-session` profile after a separate explicit action and acceptance proof.
It does not add a fourth profile or silently collapse the three existing lifecycles.
## M5.1 canary: canonical recorded replay
The accepted viewer canary reuses the stabilized full-route RAV004 composition; it does not create
a second viewer or a second recorded-data pipeline. The sealed vegetation result is first projected
into the generic Session/LAB catalog through `SessionStore.publish_lab_instance`. Its exact
`route_full_review.session_id` is the only source relationship. Labels, `latest`, result prefixes and
the advanced LAB index are not valid joins.
The immutable LAB projection carries a versioned replay capability:
```json
{
"schema_version": "missioncore.observation-lab-replay-capability/v1",
"kind": "canonical-recorded-rerun",
"viewer_profile": "recorded-session",
"timeline": "session_time",
"activation": "explicit",
"commands_enabled": false
}
```
Catalog entry, Session selection and result selection remain lightweight. Only the explicit
`Открыть визуальный разбор` action reads and revalidates the sealed result metadata. Only after that
admission does the application mount the shared canonical replay component, which follows the
existing `resolveObservationSessionReplay → resolveCanonicalLabReplay → recordedSessionRerunProfile
→ RerunViewport` path. Closing the review or changing Session unmounts that component and aborts
pending admission. At most one viewer and one `session_time` clock exist.
The publication command verifies the registered result root, schema, content-addressed identity,
artifact hashes, exact RAV004 source Session and all false authority flags before adding an
idempotent SQLite projection. The validated source snapshot is compared again inside the same
SQLite write transaction, so reconciliation cannot swap the Session between admission and copy.
The default v1 catalog continues to hide capability-owned projections from legacy consumers; the
explicit v2 LAB catalog returns the typed capability. A narrowly matched rolling migration types
the already-published canonical projection and leaves ordinary legacy LAB rows untouched. The
command does not run inference, ffmpeg, RRD merge or replay
materialization; source/result evidence is not copied or rewritten. Legacy LAB continues to import
the same shared replay through a compatibility wrapper.
## Information hierarchy and states
@@ -89,5 +128,19 @@ workspace.
the historical tail remains in legacy LAB and is never mounted into a long Observatory DOM.
- Loading, empty, refreshing, error/retry and ready states contain no synthetic data.
- Linked evidence is never presented as a CV or safety pass.
- Existing LAB, Simulation, K1 and Data/Sessions code paths remain unchanged.
- Existing LAB, Simulation, K1 and Data/Sessions operator behavior remains unchanged.
- Live, historical Session and legacy LAB viewer-profile contracts remain distinct.
## Canary acceptance
- RAV004 appears as one exact LAB result linked to `20260828T130511Z_viewer_live` in the Session
catalog; Observatory never reads `/api/v1/laboratory/advanced-index`.
- Entering Observatory and changing Session perform no replay POST, RRD HEAD/GET, viewer mount or
canvas creation.
- Explicit open validates the result/source binding, then uses the existing canonical RRD,
blueprint, cache, `recorded-session` profile and `session_time` clock.
- One `RerunViewport` is mounted; legacy clip/Three renderers, a second clock and a second RRD
builder are absent.
- Close, Session change and run replacement abort pending work and fully unmount the viewer.
- K1, Worker 006/add-worker, Simulation/Gaussian, raw Sessions and legacy LAB behavior remain
unchanged.
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Publish one exact sealed RAV004 result into the Session/LAB catalog."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from k1link.laboratory.canonical_recorded_catalog import (
publish_canonical_recorded_vegetation_result,
)
from k1link.sessions import SessionStore
from k1link.sessions.store import resolve_missioncore_data_dir
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Verify and publish an immutable canonical recorded LAB result. "
"This command does not build RRD, run inference or alter source evidence."
)
)
parser.add_argument("--repository-root", required=True, type=Path)
parser.add_argument("--result-id", required=True)
return parser.parse_args()
def main() -> None:
arguments = _arguments()
repository_root = arguments.repository_root.expanduser().absolute()
if repository_root.is_symlink():
raise SystemExit("repository root must not be a symlink")
try:
repository_root = repository_root.resolve(strict=True)
runtime_root = (
repository_root / ".runtime" / "compute-experiments"
).resolve(strict=True)
except OSError as exc:
raise SystemExit("canonical repository runtime is unavailable") from exc
if not repository_root.is_dir() or not runtime_root.is_dir():
raise SystemExit("canonical repository runtime is unavailable")
if not runtime_root.is_relative_to(repository_root):
raise SystemExit("canonical repository runtime escaped its repository root")
data_dir = resolve_missioncore_data_dir(repository_root)
database_path = data_dir / "mission-core.sqlite3"
if database_path.is_symlink() or not database_path.is_file():
raise SystemExit("existing Mission Core catalog is unavailable")
result_root = (
runtime_root
/ "lab-v1-vegetation"
/ "results"
/ arguments.result_id
)
binding = publish_canonical_recorded_vegetation_result(
store=SessionStore(repository_root, data_dir=data_dir),
runtime_root=runtime_root,
result_root=result_root,
)
print(json.dumps(binding.as_dict(), ensure_ascii=False, sort_keys=True))
if __name__ == "__main__":
main()
@@ -0,0 +1,670 @@
"""Publish an immutable canonical recorded LAB result into the Session catalog."""
from __future__ import annotations
import hashlib
import json
import math
import re
import struct
from pathlib import Path, PurePosixPath
from typing import Any, Final
from k1link.laboratory.evidence_registry import LaboratoryEvidenceDefinition
from k1link.laboratory.evidence_report import (
LaboratoryEvidenceReportError,
verify_laboratory_evidence_result,
)
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA
from k1link.sessions import LabReplayCapability, LabSessionBinding, SessionStore
from k1link.sessions.models import SessionDetail, SessionStoreError
CANONICAL_RECORDED_PROJECTION_SCHEMA: Final = (
"missioncore.canonical-recorded-lab-projection/v1"
)
CANONICAL_REPLAY_CAPABILITY_SCHEMA: Final = (
"missioncore.observation-lab-replay-capability/v1"
)
CANONICAL_SOURCE_SESSION_ID: Final = "20260828T130511Z_viewer_live"
CANONICAL_SOURCE_LABEL: Final = "RAVNOVES004TREE"
CANONICAL_WORK_ID: Final = "lab-v1-vegetation-shadow"
CANONICAL_RESULT_PREFIX: Final = "lab-v1-vegetation-shadow"
_MAX_DOCUMENT_BYTES: Final = 1024 * 1024
_RESULT_ID = re.compile(r"^lab-v1-vegetation-shadow-[a-f0-9]{64}$")
_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id=CANONICAL_WORK_ID,
runtime_relative_root=PurePosixPath("lab-v1-vegetation/results"),
result_id_prefix=CANONICAL_RESULT_PREFIX,
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
class CanonicalRecordedCatalogError(ValueError):
"""The sealed result cannot be admitted to the recorded Session catalog."""
def publish_canonical_recorded_vegetation_result(
*,
store: SessionStore,
runtime_root: Path,
result_root: Path,
) -> LabSessionBinding:
"""Project one exact, verified full-route RAV004 result without computing replay data."""
runtime = _real_directory(runtime_root, "LAB runtime root")
candidate = _real_directory(result_root, "canonical LAB result")
if _RESULT_ID.fullmatch(candidate.name) is None:
raise CanonicalRecordedCatalogError("canonical LAB result identity is invalid")
expected_parent = _real_directory(
_DEFINITION.result_root(runtime),
"canonical LAB result collection",
)
if not expected_parent.is_relative_to(runtime):
raise CanonicalRecordedCatalogError(
"canonical LAB result collection escaped its runtime root"
)
if candidate.parent != expected_parent:
raise CanonicalRecordedCatalogError("canonical LAB result escaped its registered root")
try:
proof = verify_laboratory_evidence_result(_DEFINITION, candidate)
except LaboratoryEvidenceReportError as exc:
raise CanonicalRecordedCatalogError(str(exc)) from exc
document = _read_document(
candidate / _DEFINITION.document_name,
expected_sha256=str(proof["document_sha256"]),
)
result_id = _exact_text(document.get("result_id"), candidate.name, "result id")
_exact_text(document.get("schema_version"), LAB_SCHEMA, "result schema")
_exact_text(
document.get("status"),
"visual-shadow-ready-policy-not-authorized",
"result status",
)
identity_sha256 = _exact_text(
document.get("identity_sha256"),
str(proof["identity_sha256"]),
"identity digest",
)
authority = _object(document.get("authority"), "result authority")
_require_observation_only(authority, "result authority")
identity = _object(document.get("identity"), "result identity")
identity_authority = _object(identity.get("authority"), "identity authority")
_require_observation_only(identity_authority, "identity authority")
if identity_authority != authority:
raise CanonicalRecordedCatalogError("top-level authority is not identity-bound")
if document.get("route_video") is not None or document.get("route_review") is not None:
raise CanonicalRecordedCatalogError("canonical full-route result shape is invalid")
review = _object(document.get("route_full_review"), "full-route review")
identity_review = _object(
identity.get("route_full_review"),
"identity full-route review",
)
if identity_review != review:
raise CanonicalRecordedCatalogError("full-route review is not identity-bound")
_exact_text(review.get("source_id"), CANONICAL_SOURCE_LABEL, "source label")
source_session_id = _exact_text(
review.get("session_id"),
CANONICAL_SOURCE_SESSION_ID,
"source session id",
)
if review.get("frame_count") != 6830:
raise CanonicalRecordedCatalogError("canonical frame count is invalid")
source_result_id = _text(
review.get("linked_route_review_result_id"),
"linked route review result id",
)
if _RESULT_ID.fullmatch(source_result_id) is None:
raise CanonicalRecordedCatalogError("linked route review identity is invalid")
if identity.get("base_result_id") != source_result_id:
raise CanonicalRecordedCatalogError("base result is not identity-bound")
result_source = _object(document.get("source"), "result source")
if result_source != _object(
identity.get("source"),
"identity source",
):
raise CanonicalRecordedCatalogError("result source is not identity-bound")
if result_source != {
"shadow_session": CANONICAL_SOURCE_LABEL,
"shadow_camera": "sensor.camera.right",
"shadow_frame_count": 6830,
"video_shadow_frame_count": 6830,
}:
raise CanonicalRecordedCatalogError("canonical source identity changed")
timeline_start = _finite_number(
review.get("timeline_start_seconds"),
"timeline start",
)
timeline_end = _finite_number(
review.get("timeline_end_seconds"),
"timeline end",
)
if timeline_end <= timeline_start:
raise CanonicalRecordedCatalogError("canonical timeline range is invalid")
_validate_viewer_contract(candidate, document, review)
try:
source_detail, source_catalog_sha256 = (
store.get_session_with_catalog_snapshot(source_session_id)
)
except SessionStoreError as exc:
raise CanonicalRecordedCatalogError(
"canonical source session is unavailable"
) from exc
_validate_source_catalog_binding(source_detail)
sealed_method = _object(document.get("method"), "sealed method")
pipeline_id = _exact_text(
sealed_method.get("pipeline_id"),
"ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"sealed pipeline id",
)
execution_class = _exact_text(
sealed_method.get("execution_class"),
"ai-inference",
"sealed execution class",
)
capability = LabReplayCapability(
schema_version=CANONICAL_REPLAY_CAPABILITY_SCHEMA,
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
provenance = {
"schema_version": CANONICAL_RECORDED_PROJECTION_SCHEMA,
"evidence_identity_sha256": identity_sha256,
"result_document_sha256": proof["document_sha256"],
"replay_capability": capability.as_dict(),
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
},
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": execution_class,
"pipeline_id": pipeline_id,
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": LAB_SCHEMA,
"role": "immutable Session catalog projection",
"identity_sha256": identity_sha256,
}
],
},
}
return store.publish_lab_instance(
session_id=result_id,
source_session_id=source_session_id,
display_name="RAVNOVES004TREE · полный маршрут восприятия",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id=result_id,
source_result_id=source_result_id,
config_sha256=None,
run_created_at_utc=_text(document.get("created_at_utc"), "creation time"),
replay_capability=capability,
provenance=provenance,
duration_seconds=timeline_end - timeline_start,
include_recorded_media=False,
expected_source_catalog_sha256=source_catalog_sha256,
)
def _real_directory(path: Path, label: str) -> Path:
candidate = path.expanduser().absolute()
if candidate.is_symlink():
raise CanonicalRecordedCatalogError(f"{label} must not be a symlink")
try:
resolved = candidate.resolve(strict=True)
except OSError as exc:
raise CanonicalRecordedCatalogError(f"{label} is unavailable") from exc
if not resolved.is_dir():
raise CanonicalRecordedCatalogError(f"{label} must be a directory")
return resolved
def _read_document(path: Path, *, expected_sha256: str) -> dict[str, Any]:
if path.is_symlink() or not path.is_file() or path.stat().st_size > _MAX_DOCUMENT_BYTES:
raise CanonicalRecordedCatalogError("canonical LAB document is unavailable")
try:
payload = path.read_bytes()
if hashlib.sha256(payload).hexdigest() != expected_sha256:
raise CanonicalRecordedCatalogError(
"canonical LAB document changed after verification"
)
value = json.loads(payload)
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise CanonicalRecordedCatalogError("canonical LAB document is invalid") from exc
return _object(value, "canonical LAB document")
def _object(value: object, label: str) -> dict[str, Any]:
if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
raise CanonicalRecordedCatalogError(f"{label} must be an object")
return value
def _text(value: object, label: str) -> str:
if not isinstance(value, str) or not value.strip() or value != value.strip():
raise CanonicalRecordedCatalogError(f"{label} must be a non-empty string")
return value
def _exact_text(value: object, expected: str, label: str) -> str:
text = _text(value, label)
if text != expected:
raise CanonicalRecordedCatalogError(f"{label} changed")
return text
def _finite_number(value: object, label: str) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise CanonicalRecordedCatalogError(f"{label} must be numeric")
number = float(value)
if not math.isfinite(number):
raise CanonicalRecordedCatalogError(f"{label} must be finite")
return number
def _require_observation_only(value: dict[str, Any], label: str) -> None:
expected = {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
"camera_semantics_can_clear_rigid_geometry": False,
}
if set(value) != set(expected) or any(value[key] is not False for key in expected):
raise CanonicalRecordedCatalogError(f"{label} permits control authority")
def _validate_viewer_contract(
result_root: Path,
document: dict[str, Any],
review: dict[str, Any],
) -> None:
"""Require the metadata and artifacts consumed before the shared viewer mounts."""
if document.get("ground_truth") is not False:
raise CanonicalRecordedCatalogError("canonical result ground-truth marker changed")
identity = _object(document.get("identity"), "result identity")
selected_candidate = _text(
identity.get("selected_candidate"),
"selected candidate",
)
if selected_candidate not in {"ddrnet", "ppliteseg"}:
raise CanonicalRecordedCatalogError("canonical selected candidate changed")
metrics = _object(document.get("metrics"), "result metrics")
candidates = _object(metrics.get("candidates"), "candidate metrics")
identity_candidates = _object(
identity.get("candidate_metrics"),
"identity candidate metrics",
)
if candidates != identity_candidates or set(candidates) != {"ddrnet", "ppliteseg"}:
raise CanonicalRecordedCatalogError("candidate metrics are not identity-bound")
for candidate in ("ddrnet", "ppliteseg"):
_validate_candidate_metrics(
_object(candidates.get(candidate), f"{candidate} metrics"),
candidate,
)
decision = _object(document.get("decision"), "result decision")
if (
decision.get("selected_candidate") != selected_candidate
or decision.get("visual_shadow_ready") is not True
or decision.get("mission_policy_ready_for_configuration") is not True
or decision.get("navigation_accepted") is not False
or decision.get("production_accepted") is not False
):
raise CanonicalRecordedCatalogError("canonical result decision changed")
catalogs = _object(document.get("catalogs"), "result catalogs")
if catalogs.get("goose") != [] or catalogs.get("ravnoves") != []:
raise CanonicalRecordedCatalogError("canonical result catalogs changed")
limitations = document.get("limitations")
if not isinstance(limitations, list) or not limitations or not all(
isinstance(item, str) and item.strip() for item in limitations
):
raise CanonicalRecordedCatalogError("canonical result limitations are invalid")
expected_route_values: dict[str, object] = {
"source_job_id": "recorded-camera-eb2783c5480d56bda07c8af0",
"source_job_input_sha256": (
"eb2783c5480d56bda07c8af008dff5344d19dc550ef70fe2075d6f098f7cc715"
),
"source_stream_sha256": (
"e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac"
),
"recorded_media_source_id": "recorded.camera.6a3945242828a038",
"recorded_media_generation_sha256": (
"b073ea1e7babf1c77a664e1a5b95e3702d0e05b0e34c1e85a7c67a6f8b392ded"
),
"width": 800,
"height": 600,
}
if any(review.get(key) != expected for key, expected in expected_route_values.items()):
raise CanonicalRecordedCatalogError("canonical full-route source binding changed")
if review.get("ground_truth") is not False:
raise CanonicalRecordedCatalogError("canonical full-route ground-truth marker changed")
artifacts = _artifact_catalog(document.get("artifacts"))
timeline = _object(review.get("timeline"), "full-route timeline")
if (
timeline.get("path") != "video/frame-source-times-ns.bin"
or timeline.get("encoding") != "uint64-le-nanoseconds"
or timeline.get("frame_count") != 6830
or timeline.get("byte_length") != 6830 * 8
):
raise CanonicalRecordedCatalogError("canonical full-route timeline changed")
timeline_sha256 = _sha256_text(timeline.get("sha256"), "timeline digest")
_require_artifact(
artifacts,
path="video/frame-source-times-ns.bin",
sha256=timeline_sha256,
byte_length=6830 * 8,
media_type="application/octet-stream",
)
_validate_timeline_payload(
result_root / "video" / "frame-source-times-ns.bin",
expected_sha256=timeline_sha256,
start_seconds=_finite_number(review.get("timeline_start_seconds"), "timeline start"),
)
decode_repair = _object(review.get("decode_repair"), "decode repair")
if (
decode_repair.get("repaired_frame_count") != 1
or decode_repair.get("sequence") != 6092
or decode_repair.get("method") != "duplicate-previous-decoded-frame"
):
raise CanonicalRecordedCatalogError("canonical decode repair changed")
repair_proofs = _object(decode_repair.get("proofs"), "decode repair proofs")
for key, path in {
"eomt": "proofs/decode_repair.json",
"ddrnet": "proofs/ddrnet_decode_repair.json",
}.items():
proof = _object(repair_proofs.get(key), f"{key} decode proof")
digest = _sha256_text(proof.get("sha256"), f"{key} decode proof digest")
if proof.get("path") != path:
raise CanonicalRecordedCatalogError("canonical decode proof changed")
_require_artifact(artifacts, path=path, sha256=digest)
route_proofs = _object(review.get("proofs"), "full-route proofs")
job_proof = _object(route_proofs.get("job"), "full-route job proof")
job_digest = _sha256_text(job_proof.get("sha256"), "full-route job digest")
if job_proof.get("path") != "proofs/job.json":
raise CanonicalRecordedCatalogError("canonical job proof changed")
_require_artifact(artifacts, path="proofs/job.json", sha256=job_digest)
layers = _object(review.get("layers"), "full-route layers")
if set(layers) != {"city", "vegetation"}:
raise CanonicalRecordedCatalogError("canonical full-route layers changed")
_validate_full_route_layer(
_object(layers.get("city"), "city layer"),
layer="city",
artifacts=artifacts,
)
_validate_full_route_layer(
_object(layers.get("vegetation"), "vegetation layer"),
layer="vegetation",
artifacts=artifacts,
)
def _validate_source_catalog_binding(detail: SessionDetail) -> None:
summary = detail.summary
if (
detail.plugin_id != "nodedc.device.xgrids-lixelkity-k1"
or detail.archive_id != "xgrids-k1.viewer-live.evidence"
or summary.session_id != CANONICAL_SOURCE_SESSION_ID
or summary.display_name != CANONICAL_SOURCE_LABEL
or summary.status != "ready"
or summary.started_at_utc != "2026-08-28T13:05:16.249Z"
or summary.completed_at_utc != "2026-08-28T13:18:45.030Z"
or summary.duration_seconds is None
or not math.isclose(summary.duration_seconds, 808.779495667, abs_tol=1e-9)
or summary.modalities != ("point-cloud", "trajectory", "video")
or summary.source_count != 3
or summary.total_bytes != 799_020_963
or summary.replayable is not True
or summary.lab is not None
):
raise CanonicalRecordedCatalogError("canonical source catalog identity changed")
sources = {
source.source_id: (
source.semantic_channel_id,
source.modality,
source.status,
source.seekable,
source.artifact_id,
)
for source in detail.sources
}
if sources != {
"sensor.camera.right": (
"camera.video.recorded",
"video",
"recorded",
True,
"recorded-video-6a3945242828a038",
),
"sensor.lidar.primary": (
"spatial.point-cloud.recorded",
"point-cloud",
"recorded",
True,
"raw-transport-primary",
),
"spatial.trajectory": (
"spatial.pose.recorded",
"trajectory",
"recorded",
True,
"raw-transport-primary",
),
}:
raise CanonicalRecordedCatalogError("canonical source channels changed")
artifacts = {artifact.artifact_id: artifact for artifact in detail.artifacts}
if set(artifacts) != {
"raw-transport-clock",
"raw-transport-clock-origin",
"raw-transport-index",
"raw-transport-primary",
"recorded-video-6a3945242828a038",
}:
raise CanonicalRecordedCatalogError("canonical source artifacts changed")
raw = artifacts["raw-transport-primary"]
video = artifacts["recorded-video-6a3945242828a038"]
if (
raw.kind != "raw-transport"
or raw.media_type != "application/x-nodedc-k1mqtt"
or raw.byte_length != 245_183_013
or raw.sha256 != "20c789eff922a6bbb53592f86614abc0729a30544df29e740e7a378d12af85c2"
or raw.integrity_status != "verified"
or video.kind != "recorded-video"
or video.media_type != "video/mp4"
or video.byte_length != 553_837_950
or video.sha256 is not None
or video.integrity_status != "validated-structure"
):
raise CanonicalRecordedCatalogError("canonical source artifact proof changed")
def _validate_candidate_metrics(value: dict[str, Any], candidate: str) -> None:
_text(value.get("loaded_model_name"), f"{candidate} model name")
_sha256_text(value.get("checkpoint_sha256"), f"{candidate} checkpoint")
validation = _object(value.get("validation_metrics"), f"{candidate} validation")
validation_timing = _object(
value.get("validation_timing"),
f"{candidate} validation timing",
)
shadow_timing = _object(value.get("shadow_timing"), f"{candidate} shadow timing")
resource = _object(value.get("resource"), f"{candidate} resource")
for key in ("mean_iou_percent", "published_mean_iou_percent", "vegetation_mean_iou"):
_finite_number(validation.get(key), f"{candidate} {key}")
for key in ("latency_ms_p95", "throughput_fps_from_mean_inference"):
_finite_number(validation_timing.get(key), f"{candidate} validation {key}")
_finite_number(shadow_timing.get(key), f"{candidate} shadow {key}")
_finite_number(shadow_timing.get("prewarm_latency_ms"), f"{candidate} prewarm")
_nonnegative_integer(resource.get("peak_reserved_vram_bytes"), f"{candidate} VRAM")
_text(resource.get("gpu_name"), f"{candidate} GPU")
def _validate_full_route_layer(
value: dict[str, Any],
*,
layer: str,
artifacts: dict[str, dict[str, Any]],
) -> None:
expected = {
"city": (
re.compile(r"^result-[a-f0-9]{64}$"),
"missioncore.recorded-eomt-taxonomy/v1",
16,
"video/eomt-semantic-masks.zip",
),
"vegetation": (
re.compile(r"^lab-v1-ravnoves-video-ddrnet-[a-f0-9]{64}$"),
"missioncore.lab-v1-vegetation-taxonomy/v1",
64,
"video/ddrnet-semantic-masks.zip",
),
}[layer]
result_id = _text(value.get("result_id"), f"{layer} result id")
if expected[0].fullmatch(result_id) is None or value.get("frame_count") != 6830:
raise CanonicalRecordedCatalogError(f"canonical {layer} layer identity changed")
_text(value.get("name"), f"{layer} layer name")
_finite_number(value.get("inference_fps"), f"{layer} inference FPS")
_finite_number(value.get("latency_p95_ms"), f"{layer} latency")
_nonnegative_integer(value.get("peak_reserved_vram_bytes"), f"{layer} VRAM")
_validate_taxonomy(
_object(value.get("taxonomy"), f"{layer} taxonomy"),
schema=expected[1],
class_count=expected[2],
label=layer,
)
archive = _object(value.get("mask_archive"), f"{layer} mask archive")
digest = _sha256_text(archive.get("sha256"), f"{layer} archive digest")
byte_length = _positive_integer(archive.get("byte_length"), f"{layer} archive bytes")
if archive.get("path") != expected[3]:
raise CanonicalRecordedCatalogError(f"canonical {layer} archive changed")
_require_artifact(
artifacts,
path=expected[3],
sha256=digest,
byte_length=byte_length,
media_type="application/zip",
)
def _validate_taxonomy(
value: dict[str, Any],
*,
schema: str,
class_count: int,
label: str,
) -> None:
classes = value.get("classes")
if value.get("schema_version") != schema or not isinstance(classes, list):
raise CanonicalRecordedCatalogError(f"canonical {label} taxonomy changed")
if len(classes) != class_count:
raise CanonicalRecordedCatalogError(f"canonical {label} taxonomy size changed")
for expected_id, raw in enumerate(classes):
item = _object(raw, f"{label} taxonomy class")
color = item.get("color_rgb")
if (
item.get("class_id") != expected_id
or isinstance(item.get("class_id"), bool)
or not isinstance(color, list)
or len(color) != 3
or any(
isinstance(channel, bool)
or not isinstance(channel, int)
or not 0 <= channel <= 255
for channel in color
)
or item.get("disposition")
not in {"labeled", "ambiguous", "prediction", "undefined"}
):
raise CanonicalRecordedCatalogError(f"canonical {label} taxonomy class changed")
_text(item.get("label"), f"{label} taxonomy label")
for optional in ("material_class", "evidence_state"):
if item.get(optional) is not None:
_text(item.get(optional), f"{label} taxonomy {optional}")
def _artifact_catalog(value: object) -> dict[str, dict[str, Any]]:
if not isinstance(value, list) or not value:
raise CanonicalRecordedCatalogError("canonical result artifacts are missing")
catalog: dict[str, dict[str, Any]] = {}
for raw in value:
descriptor = _object(raw, "canonical result artifact")
path = _text(descriptor.get("path"), "canonical artifact path")
if path in catalog:
raise CanonicalRecordedCatalogError("canonical result artifact is duplicated")
catalog[path] = descriptor
return catalog
def _require_artifact(
artifacts: dict[str, dict[str, Any]],
*,
path: str,
sha256: str,
byte_length: int | None = None,
media_type: str | None = None,
) -> None:
descriptor = artifacts.get(path)
if (
descriptor is None
or descriptor.get("sha256") != sha256
or (byte_length is not None and descriptor.get("byte_length") != byte_length)
or (media_type is not None and descriptor.get("media_type") != media_type)
):
raise CanonicalRecordedCatalogError("canonical replay artifact binding changed")
def _validate_timeline_payload(
path: Path,
*,
expected_sha256: str,
start_seconds: float,
) -> None:
try:
payload = path.read_bytes()
values = struct.unpack("<6830Q", payload)
except (OSError, struct.error) as exc:
raise CanonicalRecordedCatalogError("canonical timeline payload is invalid") from exc
if (
hashlib.sha256(payload).hexdigest() != expected_sha256
or values[0] != round(start_seconds * 1_000_000_000)
or values[-1] > 9_007_199_254_740_991
or any(
current <= previous
for previous, current in zip(values, values[1:], strict=False)
)
):
raise CanonicalRecordedCatalogError("canonical timeline payload changed")
def _sha256_text(value: object, label: str) -> str:
digest = _text(value, label)
if re.fullmatch(r"[a-f0-9]{64}", digest) is None:
raise CanonicalRecordedCatalogError(f"{label} is invalid")
return digest
def _nonnegative_integer(value: object, label: str) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise CanonicalRecordedCatalogError(f"{label} must be a non-negative integer")
return value
def _positive_integer(value: object, label: str) -> int:
integer = _nonnegative_integer(value, label)
if integer == 0:
raise CanonicalRecordedCatalogError(f"{label} must be positive")
return integer
+2
View File
@@ -20,6 +20,7 @@ from .media import (
validate_recorded_media_timeline,
)
from .models import (
LabReplayCapability,
LabSessionBinding,
LayoutConflictError,
ObservationArtifactCandidate,
@@ -57,6 +58,7 @@ from .store import (
__all__ = [
"LayoutConflictError",
"LabReplayCapability",
"LabSessionBinding",
"ActiveSessionLease",
"ActiveSessionLeaseError",
+54 -2
View File
@@ -6,6 +6,7 @@ from typing import Any, Literal
SessionStatus = Literal["ready", "interrupted", "failed"]
SessionModality = Literal["point-cloud", "trajectory", "video"]
LAB_REPLAY_CAPABILITY_SCHEMA = "missioncore.observation-lab-replay-capability/v1"
class SessionStoreError(RuntimeError):
@@ -28,6 +29,45 @@ class LayoutConflictError(SessionStoreError):
"""A workspace layout revision changed since the caller loaded it."""
@dataclass(frozen=True, slots=True)
class LabReplayCapability:
"""Explicit, observation-only admission for a derived recorded replay."""
schema_version: Literal["missioncore.observation-lab-replay-capability/v1"]
kind: Literal["canonical-recorded-rerun"]
viewer_profile: Literal["recorded-session"]
timeline: Literal["session_time"]
activation: Literal["explicit"]
commands_enabled: Literal[False]
def __post_init__(self) -> None:
expected_text = (
LAB_REPLAY_CAPABILITY_SCHEMA,
"canonical-recorded-rerun",
"recorded-session",
"session_time",
"explicit",
)
if (
self.schema_version,
self.kind,
self.viewer_profile,
self.timeline,
self.activation,
) != expected_text or self.commands_enabled is not False:
raise ValueError("LAB replay capability is invalid")
def as_dict(self) -> dict[str, object]:
return {
"schema_version": self.schema_version,
"kind": self.kind,
"viewer_profile": self.viewer_profile,
"timeline": self.timeline,
"activation": self.activation,
"commands_enabled": self.commands_enabled,
}
@dataclass(frozen=True, slots=True)
class LabSessionBinding:
"""Immutable provenance for one derived laboratory replay."""
@@ -41,10 +81,15 @@ class LabSessionBinding:
config_sha256: str | None
run_created_at_utc: str
published_at_utc: str
replay_capability: LabReplayCapability | None
provenance: dict[str, Any]
def as_dict(self) -> dict[str, Any]:
return {
def as_dict(
self,
*,
include_replay_capability: bool = False,
) -> dict[str, Any]:
document: dict[str, Any] = {
"lab_id": self.lab_id,
"source_session_id": self.source_session_id,
"result_kind": self.result_kind,
@@ -55,6 +100,11 @@ class LabSessionBinding:
"published_at_utc": self.published_at_utc,
"provenance": self.provenance,
}
if include_replay_capability:
document["replay_capability"] = (
None if self.replay_capability is None else self.replay_capability.as_dict()
)
return document
@dataclass(frozen=True, slots=True)
@@ -137,6 +187,8 @@ class SessionDetail:
summary: SessionSummary
sources: tuple[SessionSource, ...]
artifacts: tuple[SessionArtifact, ...]
plugin_id: str
archive_id: str
def as_dict(self) -> dict[str, Any]:
duration = self.summary.duration_seconds
+609 -9
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import hashlib
import json
import math
import os
@@ -17,6 +18,7 @@ from uuid import uuid4
from k1link.artifacts import utc_now_iso
from .models import (
LabReplayCapability,
LabSessionBinding,
LayoutConflictError,
ObservationArtifactCandidate,
@@ -114,6 +116,8 @@ CREATE TABLE IF NOT EXISTS observation_lab_instances (
config_sha256 TEXT,
run_created_at_utc TEXT NOT NULL,
published_at_utc TEXT NOT NULL,
include_recorded_media INTEGER CHECK (include_recorded_media IN (0, 1)),
replay_capability_json TEXT,
provenance_json TEXT NOT NULL
);
@@ -213,10 +217,13 @@ class SessionStore:
limit: int = 20,
cursor: str | None = None,
scope: SessionScope = "all",
include_capability_projections: bool = True,
) -> SessionPage:
if not 1 <= limit <= 100:
raise ValueError("limit must be within 1..100")
scope_clause = {
if not isinstance(include_capability_projections, bool):
raise ValueError("capability projection policy must be boolean")
scope_clause = ({
"all": "1 = 1",
"source": (
"NOT EXISTS (SELECT 1 FROM observation_lab_instances AS lab "
@@ -226,7 +233,22 @@ class SessionStore:
"EXISTS (SELECT 1 FROM observation_lab_instances AS lab "
"WHERE lab.session_id = sessions.session_id)"
),
}.get(scope)
} if include_capability_projections else {
"all": (
"NOT EXISTS (SELECT 1 FROM observation_lab_instances AS lab "
"WHERE lab.session_id = sessions.session_id "
"AND lab.replay_capability_json IS NOT NULL)"
),
"source": (
"NOT EXISTS (SELECT 1 FROM observation_lab_instances AS lab "
"WHERE lab.session_id = sessions.session_id)"
),
"laboratory": (
"EXISTS (SELECT 1 FROM observation_lab_instances AS lab "
"WHERE lab.session_id = sessions.session_id "
"AND lab.replay_capability_json IS NULL)"
),
}).get(scope)
if scope_clause is None:
raise ValueError("scope must be all, source, or laboratory")
parameters: list[object] = []
@@ -283,8 +305,18 @@ class SessionStore:
return SessionPage(items=items, next_cursor=next_cursor)
def get_session(self, session_id: str) -> SessionDetail:
detail, _snapshot_sha256 = self.get_session_with_catalog_snapshot(session_id)
return detail
def get_session_with_catalog_snapshot(
self,
session_id: str,
) -> tuple[SessionDetail, str]:
"""Read one detail and its private catalog snapshot from one SQLite view."""
_validate_identifier(session_id, "session id")
with self._connect() as connection:
connection.execute("BEGIN")
row = connection.execute(
"SELECT * FROM observation_sessions WHERE session_id = ?",
(session_id,),
@@ -292,19 +324,20 @@ class SessionStore:
if row is None:
raise SessionNotFoundError("observation session was not found")
source_rows = connection.execute(
"SELECT source_id, semantic_channel_id, modality, status, seekable, artifact_id "
"FROM observation_session_sources WHERE session_id = ? ORDER BY source_id",
"SELECT * FROM observation_session_sources "
"WHERE session_id = ? ORDER BY source_id",
(session_id,),
).fetchall()
artifact_rows = connection.execute(
"SELECT artifact_id, kind, media_type, byte_length, sha256, integrity_status "
"FROM observation_session_artifacts WHERE session_id = ? ORDER BY artifact_id",
"SELECT * FROM observation_session_artifacts "
"WHERE session_id = ? ORDER BY artifact_id",
(session_id,),
).fetchall()
lab_row = connection.execute(
"SELECT * FROM observation_lab_instances WHERE session_id = ?",
(session_id,),
).fetchone()
snapshot_sha256 = _catalog_snapshot_sha256(row, source_rows, artifact_rows)
sources = tuple(
SessionSource(
source_id=source["source_id"],
@@ -327,14 +360,17 @@ class SessionStore:
)
for artifact in artifact_rows
)
return SessionDetail(
detail = SessionDetail(
summary=_summary_from_row(
row,
lab=None if lab_row is None else _lab_binding_from_row(lab_row),
),
sources=sources,
artifacts=artifacts,
plugin_id=row["plugin_id"],
archive_id=row["archive_id"],
)
return detail, snapshot_sha256
def get_lab_instance(self, session_id: str) -> LabSessionBinding | None:
"""Return immutable LAB provenance without exposing filesystem locators."""
@@ -359,9 +395,11 @@ class SessionStore:
run_created_at_utc: str,
source_result_id: str | None = None,
config_sha256: str | None = None,
replay_capability: LabReplayCapability | None = None,
provenance: dict[str, Any] | None = None,
duration_seconds: float | None = None,
include_recorded_media: bool = True,
expected_source_catalog_sha256: str | None = None,
) -> LabSessionBinding:
"""Append one immutable catalog projection over an existing source session.
@@ -392,9 +430,22 @@ class SessionStore:
or duration_seconds <= 0
):
raise ValueError("LAB duration must be a positive finite value")
if not isinstance(include_recorded_media, bool):
raise ValueError("LAB recorded-media policy must be boolean")
if (
expected_source_catalog_sha256 is not None
and SHA256_PATTERN.fullmatch(expected_source_catalog_sha256) is None
):
raise ValueError("LAB source catalog snapshot SHA-256 is invalid")
normalized_provenance = provenance or {}
_validate_lab_method(normalized_provenance)
_validate_replay_capability_provenance(
normalized_provenance,
replay_capability,
)
serialized_provenance = _serialize_provenance(normalized_provenance)
serialized_replay_capability = _serialize_replay_capability(replay_capability)
serialized_recorded_media_policy = int(include_recorded_media)
published_at = utc_now_iso()
with self._lock, self._connect() as connection:
@@ -406,6 +457,19 @@ class SessionStore:
if source is None:
connection.rollback()
raise SessionNotFoundError("LAB source observation session was not found")
if (
expected_source_catalog_sha256 is not None
and _catalog_snapshot_sha256_for_session(
connection,
source_session_id,
session_row=source,
)
!= expected_source_catalog_sha256
):
connection.rollback()
raise SessionIntegrityError(
"LAB source catalog changed after admission"
)
if (
connection.execute(
"SELECT 1 FROM observation_lab_instances WHERE session_id = ?",
@@ -428,6 +492,7 @@ class SessionStore:
"source_result_id": source_result_id,
"config_sha256": config_sha256,
"run_created_at_utc": run_created_at_utc,
"replay_capability_json": serialized_replay_capability,
"provenance_json": serialized_provenance,
}
if existing is not None:
@@ -436,6 +501,36 @@ class SessionStore:
raise SessionIntegrityError(
"LAB session id is already bound to different provenance"
)
if replay_capability is not None:
stored_recorded_media_policy = existing["include_recorded_media"]
if (
stored_recorded_media_policy is not None
and stored_recorded_media_policy != serialized_recorded_media_policy
):
connection.rollback()
raise SessionIntegrityError(
"LAB session id is already bound to a different recorded-media policy"
)
_validate_existing_lab_projection(
connection,
session_id=session_id,
source_session_id=source_session_id,
display_name=normalized_name,
run_created_at_utc=run_created_at_utc,
duration_seconds=(
source["duration_seconds"]
if duration_seconds is None
else duration_seconds
),
include_recorded_media=include_recorded_media,
)
if stored_recorded_media_policy is None:
connection.execute(
"UPDATE observation_lab_instances SET include_recorded_media = ? "
"WHERE session_id = ?",
(serialized_recorded_media_policy, session_id),
)
_synchronize_lab_projection_summary(connection, session_id)
connection.commit()
return _lab_binding_from_row(existing)
if (
@@ -507,8 +602,8 @@ class SessionStore:
"INSERT INTO observation_lab_instances "
"(session_id, source_session_id, lab_id, result_kind, result_id, "
"source_result_id, config_sha256, run_created_at_utc, "
"published_at_utc, provenance_json) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
"published_at_utc, include_recorded_media, replay_capability_json, "
"provenance_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
session_id,
source_session_id,
@@ -519,9 +614,13 @@ class SessionStore:
config_sha256,
run_created_at_utc,
published_at,
serialized_recorded_media_policy,
serialized_replay_capability,
serialized_provenance,
),
)
if replay_capability is not None:
_synchronize_lab_projection_summary(connection, session_id)
connection.commit()
binding = self.get_lab_instance(session_id)
if binding is None:
@@ -817,6 +916,22 @@ class SessionStore:
"ALTER TABLE observation_session_artifacts "
"ADD COLUMN replay_byte_length INTEGER NOT NULL DEFAULT 0"
)
lab_columns = {
row["name"]
for row in connection.execute("PRAGMA table_info(observation_lab_instances)")
}
if "replay_capability_json" not in lab_columns:
connection.execute(
"ALTER TABLE observation_lab_instances "
"ADD COLUMN replay_capability_json TEXT"
)
if "include_recorded_media" not in lab_columns:
connection.execute(
"ALTER TABLE observation_lab_instances "
"ADD COLUMN include_recorded_media INTEGER "
"CHECK (include_recorded_media IN (0, 1))"
)
_migrate_canonical_replay_capabilities(connection)
connection.commit()
with _ignore_os_error():
self.database_path.chmod(0o600)
@@ -1046,6 +1161,428 @@ def _validate_candidate_replay(
raise SessionIntegrityError("non-replayable observation declares replay artifacts")
def _validate_existing_lab_projection(
connection: sqlite3.Connection,
*,
session_id: str,
source_session_id: str,
display_name: str,
run_created_at_utc: str,
duration_seconds: float | None,
include_recorded_media: bool,
) -> None:
"""Validate the immutable companion snapshot without rereading a mutable source row."""
summary = connection.execute(
"SELECT * FROM observation_sessions "
"WHERE session_id = ?",
(session_id,),
).fetchone()
source_summary = connection.execute(
"SELECT * FROM observation_sessions WHERE session_id = ?",
(source_session_id,),
).fetchone()
if (
summary is None
or source_summary is None
or summary["display_name"] != display_name
or summary["duration_seconds"] != duration_seconds
or summary["plugin_id"] != source_summary["plugin_id"]
or summary["archive_id"] != LAB_ARCHIVE_ID
or summary["status"] != source_summary["status"]
or summary["started_at_utc"] != run_created_at_utc
or summary["completed_at_utc"] != run_created_at_utc
or summary["replayable"] != source_summary["replayable"]
or summary["origin"] != LAB_ORIGIN
or summary["primary_replay_artifact_id"]
!= source_summary["primary_replay_artifact_id"]
or summary["timeline_origin_epoch_ns"]
!= source_summary["timeline_origin_epoch_ns"]
or summary["timeline_origin_monotonic_ns"]
!= source_summary["timeline_origin_monotonic_ns"]
or summary["allowed_root"] != source_summary["allowed_root"]
or summary["session_root"] != source_summary["session_root"]
):
raise SessionIntegrityError(
"LAB session id is already bound to different catalog metadata"
)
if (
summary["status"] != "ready"
or summary["replayable"] != 1
or summary["primary_replay_artifact_id"] is None
or summary["timeline_origin_epoch_ns"] is None
or summary["timeline_origin_monotonic_ns"] is None
or summary["timeline_origin_epoch_ns"] < 0
or summary["timeline_origin_monotonic_ns"] < 0
):
raise SessionIntegrityError(
"LAB replay projection is not a complete ready recording"
)
if not include_recorded_media and (
connection.execute(
"SELECT 1 FROM observation_session_sources "
"WHERE session_id = ? AND modality = 'video' LIMIT 1",
(session_id,),
).fetchone()
is not None
or connection.execute(
"SELECT 1 FROM observation_session_artifacts "
"WHERE session_id = ? AND kind = 'recorded-video' LIMIT 1",
(session_id,),
).fetchone()
is not None
):
raise SessionIntegrityError(
"LAB session id is already bound to a different recorded-media policy"
)
_validate_lab_projection_rows_match_source(
connection,
session_id=session_id,
source_session_id=source_session_id,
include_recorded_media=include_recorded_media,
)
def _validate_lab_projection_rows_match_source(
connection: sqlite3.Connection,
*,
session_id: str,
source_session_id: str,
include_recorded_media: bool,
) -> None:
source_filter = "" if include_recorded_media else " AND modality <> 'video'"
artifact_filter = "" if include_recorded_media else " AND kind <> 'recorded-video'"
source_columns = (
"source_id",
"semantic_channel_id",
"modality",
"status",
"seekable",
"artifact_id",
)
artifact_columns = (
"artifact_id",
"kind",
"media_type",
"byte_length",
"sha256",
"integrity_status",
"locator",
"replay_byte_length",
)
expected_sources = connection.execute(
"SELECT * FROM observation_session_sources WHERE session_id = ?"
f"{source_filter} ORDER BY source_id", # noqa: S608 - closed static fragment
(source_session_id,),
).fetchall()
actual_sources = connection.execute(
"SELECT * FROM observation_session_sources "
"WHERE session_id = ? ORDER BY source_id",
(session_id,),
).fetchall()
expected_artifacts = connection.execute(
"SELECT * FROM observation_session_artifacts WHERE session_id = ?"
f"{artifact_filter} ORDER BY artifact_id", # noqa: S608 - closed static fragment
(source_session_id,),
).fetchall()
actual_artifacts = connection.execute(
"SELECT * FROM observation_session_artifacts "
"WHERE session_id = ? ORDER BY artifact_id",
(session_id,),
).fetchall()
def values(
rows: list[sqlite3.Row],
columns: tuple[str, ...],
) -> tuple[tuple[object, ...], ...]:
return tuple(tuple(row[column] for column in columns) for row in rows)
if (
values(actual_sources, source_columns)
!= values(expected_sources, source_columns)
or values(actual_artifacts, artifact_columns)
!= values(expected_artifacts, artifact_columns)
):
raise SessionIntegrityError(
"LAB replay projection no longer matches its admitted source snapshot"
)
def _synchronize_lab_projection_summary(
connection: sqlite3.Connection,
session_id: str,
) -> None:
"""Make the synthetic summary describe exactly its copied rows."""
summary = connection.execute(
"SELECT modalities_json, source_count, total_bytes, replayable, "
"primary_replay_artifact_id "
"FROM observation_sessions WHERE session_id = ?",
(session_id,),
).fetchone()
if summary is None:
raise SessionIntegrityError("LAB projection catalog row is missing")
try:
declared_modalities = json.loads(summary["modalities_json"])
except (TypeError, json.JSONDecodeError) as exc:
raise SessionIntegrityError("LAB projection modalities are invalid") from exc
if not isinstance(declared_modalities, list) or not all(
isinstance(value, str) for value in declared_modalities
):
raise SessionIntegrityError("LAB projection modalities are invalid")
actual_modalities = {
row["modality"]
for row in connection.execute(
"SELECT DISTINCT modality FROM observation_session_sources "
"WHERE session_id = ?",
(session_id,),
).fetchall()
}
modalities: list[str] = []
for value in declared_modalities:
if value in actual_modalities and value not in modalities:
modalities.append(value)
modalities.extend(sorted(actual_modalities.difference(modalities)))
source_count = connection.execute(
"SELECT COUNT(*) AS value FROM observation_session_sources WHERE session_id = ?",
(session_id,),
).fetchone()["value"]
total_bytes = connection.execute(
"SELECT COALESCE(SUM(byte_length), 0) AS value "
"FROM observation_session_artifacts WHERE session_id = ? "
"AND artifact_id IN ("
"SELECT DISTINCT artifact_id FROM observation_session_sources "
"WHERE session_id = ?)",
(session_id, session_id),
).fetchone()["value"]
primary_artifact_id = summary["primary_replay_artifact_id"]
if summary["replayable"] and (
primary_artifact_id is None
or connection.execute(
"SELECT 1 FROM observation_session_artifacts "
"WHERE session_id = ? AND artifact_id = ?",
(session_id, primary_artifact_id),
).fetchone()
is None
):
raise SessionIntegrityError(
"LAB projection omitted its primary replay artifact"
)
modalities_json = json.dumps(modalities, separators=(",", ":"))
if (
summary["modalities_json"] != modalities_json
or summary["source_count"] != source_count
or summary["total_bytes"] != total_bytes
):
connection.execute(
"UPDATE observation_sessions SET modalities_json = ?, source_count = ?, "
"total_bytes = ?, updated_at_utc = ? WHERE session_id = ?",
(
modalities_json,
source_count,
total_bytes,
utc_now_iso(),
session_id,
),
)
def _catalog_snapshot_sha256_for_session(
connection: sqlite3.Connection,
session_id: str,
*,
session_row: sqlite3.Row | None = None,
) -> str:
row = (
session_row
if session_row is not None
else connection.execute(
"SELECT * FROM observation_sessions WHERE session_id = ?",
(session_id,),
).fetchone()
)
if row is None:
raise SessionNotFoundError("observation session was not found")
source_rows = connection.execute(
"SELECT * FROM observation_session_sources "
"WHERE session_id = ? ORDER BY source_id",
(session_id,),
).fetchall()
artifact_rows = connection.execute(
"SELECT * FROM observation_session_artifacts "
"WHERE session_id = ? ORDER BY artifact_id",
(session_id,),
).fetchall()
return _catalog_snapshot_sha256(row, source_rows, artifact_rows)
def _catalog_snapshot_sha256(
session_row: sqlite3.Row,
source_rows: list[sqlite3.Row],
artifact_rows: list[sqlite3.Row],
) -> str:
"""Bind every stored field copied or referenced by a LAB projection."""
payload = {
"session": dict(session_row),
"sources": [dict(row) for row in source_rows],
"artifacts": [dict(row) for row in artifact_rows],
}
serialized = json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(serialized).hexdigest()
def _migrate_canonical_replay_capabilities(connection: sqlite3.Connection) -> None:
"""Type the one rolling-upgrade projection that predates the v2 column."""
rows = connection.execute(
"SELECT * FROM observation_lab_instances "
"WHERE replay_capability_json IS NULL OR include_recorded_media IS NULL"
).fetchall()
for row in rows:
try:
provenance = json.loads(row["provenance_json"])
except (TypeError, json.JSONDecodeError):
continue
capability = _canonical_rolling_capability(row, provenance)
if capability is None:
continue
serialized = _serialize_replay_capability(capability)
stored = row["replay_capability_json"]
if stored is not None and stored != serialized:
raise SessionIntegrityError(
"stored canonical LAB replay capability conflicts with provenance"
)
summary = connection.execute(
"SELECT * FROM observation_sessions WHERE session_id = ?",
(row["session_id"],),
).fetchone()
if summary is None:
raise SessionIntegrityError("canonical LAB catalog row is missing")
source_summary = connection.execute(
"SELECT * FROM observation_sessions WHERE session_id = ?",
(row["source_session_id"],),
).fetchone()
if (
summary["display_name"]
!= "RAVNOVES004TREE · полный маршрут восприятия"
or summary["duration_seconds"] != 718.0
or row["run_created_at_utc"]
!= "2026-08-29T18:05:11.329061+00:00"
or source_summary is None
or source_summary["plugin_id"]
!= "nodedc.device.xgrids-lixelkity-k1"
or source_summary["archive_id"] != "xgrids-k1.viewer-live.evidence"
):
raise SessionIntegrityError(
"canonical rolling LAB projection metadata is invalid"
)
_validate_existing_lab_projection(
connection,
session_id=row["session_id"],
source_session_id=row["source_session_id"],
display_name=summary["display_name"],
run_created_at_utc=row["run_created_at_utc"],
duration_seconds=summary["duration_seconds"],
include_recorded_media=False,
)
connection.execute(
"UPDATE observation_lab_instances SET replay_capability_json = ?, "
"include_recorded_media = 0 WHERE session_id = ?",
(serialized, row["session_id"]),
)
_synchronize_lab_projection_summary(connection, row["session_id"])
def _canonical_rolling_capability(
row: sqlite3.Row,
provenance: object,
) -> LabReplayCapability | None:
if not isinstance(provenance, dict) or set(provenance) != {
"schema_version",
"evidence_identity_sha256",
"result_document_sha256",
"replay_capability",
"authority",
"method",
}:
return None
evidence_identity = provenance.get("evidence_identity_sha256")
result_document = provenance.get("result_document_sha256")
expected_result_id = (
f"lab-v1-vegetation-shadow-{evidence_identity}"
if isinstance(evidence_identity, str)
else None
)
if (
provenance.get("schema_version")
!= "missioncore.canonical-recorded-lab-projection/v1"
or not isinstance(evidence_identity, str)
or SHA256_PATTERN.fullmatch(evidence_identity) is None
or not isinstance(result_document, str)
or SHA256_PATTERN.fullmatch(result_document) is None
or row["session_id"] != expected_result_id
or row["result_id"] != expected_result_id
or row["source_session_id"] != "20260828T130511Z_viewer_live"
or row["lab_id"] != "LAB V1"
or row["result_kind"] != "recorded-perception-qualification"
or row["config_sha256"] is not None
or not isinstance(row["source_result_id"], str)
or re.fullmatch(
r"lab-v1-vegetation-shadow-[a-f0-9]{64}",
row["source_result_id"],
)
is None
):
return None
authority = provenance.get("authority")
authority_keys = {
"commands_enabled",
"navigation_or_safety_accepted",
"actuation_accepted",
}
if (
not isinstance(authority, dict)
or set(authority) != authority_keys
or any(authority[key] is not False for key in authority_keys)
):
return None
method = provenance.get("method")
expected_component = {
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
if method != {
"schema_version": LAB_METHOD_SCHEMA,
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"components": [expected_component],
}:
return None
capability_document = provenance.get("replay_capability")
try:
serialized = json.dumps(
capability_document,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
return _replay_capability_from_row(serialized)
except (TypeError, ValueError, SessionIntegrityError):
return None
def _summary_from_row(
row: sqlite3.Row,
*,
@@ -1076,6 +1613,13 @@ def _lab_binding_from_row(row: sqlite3.Row) -> LabSessionBinding:
raise SessionIntegrityError("stored LAB provenance is invalid") from exc
if not isinstance(provenance, dict):
raise SessionIntegrityError("stored LAB provenance is not an object")
replay_capability = _replay_capability_from_row(row["replay_capability_json"])
try:
_validate_replay_capability_provenance(provenance, replay_capability)
except ValueError as exc:
raise SessionIntegrityError(
"stored LAB replay capability does not match provenance"
) from exc
return LabSessionBinding(
session_id=row["session_id"],
source_session_id=row["source_session_id"],
@@ -1086,10 +1630,66 @@ def _lab_binding_from_row(row: sqlite3.Row) -> LabSessionBinding:
config_sha256=row["config_sha256"],
run_created_at_utc=row["run_created_at_utc"],
published_at_utc=row["published_at_utc"],
replay_capability=replay_capability,
provenance=provenance,
)
def _serialize_replay_capability(value: LabReplayCapability | None) -> str | None:
if value is None:
return None
if not isinstance(value, LabReplayCapability):
raise ValueError("LAB replay capability must use the typed contract")
return json.dumps(value.as_dict(), sort_keys=True, separators=(",", ":"))
def _replay_capability_from_row(value: object) -> LabReplayCapability | None:
if value is None:
return None
try:
document = json.loads(value)
except (TypeError, json.JSONDecodeError) as exc:
raise SessionIntegrityError("stored LAB replay capability is invalid") from exc
expected_keys = {
"schema_version",
"kind",
"viewer_profile",
"timeline",
"activation",
"commands_enabled",
}
if not isinstance(document, dict) or set(document) != expected_keys:
raise SessionIntegrityError("stored LAB replay capability is invalid")
try:
return LabReplayCapability(
schema_version=document["schema_version"],
kind=document["kind"],
viewer_profile=document["viewer_profile"],
timeline=document["timeline"],
activation=document["activation"],
commands_enabled=document["commands_enabled"],
)
except ValueError as exc:
raise SessionIntegrityError("stored LAB replay capability is invalid") from exc
def _validate_replay_capability_provenance(
provenance: dict[str, Any],
capability: LabReplayCapability | None,
) -> None:
declared = provenance.get("replay_capability")
if capability is None:
if declared is not None:
raise ValueError("LAB provenance cannot grant an untyped replay capability")
return
if (
not isinstance(declared, dict)
or _serialize_provenance(declared)
!= _serialize_provenance(capability.as_dict())
):
raise ValueError("LAB replay capability must exactly match provenance")
def _serialize_provenance(value: dict[str, Any]) -> str:
try:
serialized = json.dumps(
+5 -1
View File
@@ -386,7 +386,11 @@ def finalized_replayable_recording_ids() -> tuple[str, ...]:
finalized: list[str] = []
cursor: str | None = None
while True:
page = session_store.list_recent(limit=100, cursor=cursor)
page = session_store.list_recent(
limit=100,
cursor=cursor,
include_capability_projections=False,
)
finalized.extend(
summary.session_id
for summary in page.items
+16 -2
View File
@@ -341,10 +341,16 @@ def build_session_router(
limit: int = Query(default=20, ge=1, le=100),
cursor: str | None = Query(default=None, max_length=128),
scope: Literal["all", "source", "laboratory"] = "all",
lab_contract: Literal["v1", "v2"] = "v1",
) -> dict[str, Any]:
try:
_refresh_catalog(catalog_refresher)
page = store.list_recent(limit=limit, cursor=cursor, scope=scope)
page = store.list_recent(
limit=limit,
cursor=cursor,
scope=scope,
include_capability_projections=lab_contract == "v2",
)
return {
"items": [
{
@@ -356,7 +362,15 @@ def build_session_router(
"modalities": list(item.modalities),
"duration_seconds": item.duration_seconds or 0.0,
"replayable": item.replayable,
**({"lab": item.lab.as_dict()} if item.lab is not None else {}),
**(
{
"lab": item.lab.as_dict(
include_replay_capability=lab_contract == "v2"
)
}
if item.lab is not None
else {}
),
**(
{
"preparation": _catalog_preparation_document(
+546
View File
@@ -0,0 +1,546 @@
from __future__ import annotations
import hashlib
import json
import struct
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
import k1link.laboratory.canonical_recorded_catalog as canonical_catalog
from k1link.laboratory.canonical_recorded_catalog import (
CANONICAL_REPLAY_CAPABILITY_SCHEMA,
CanonicalRecordedCatalogError,
publish_canonical_recorded_vegetation_result,
)
class CapturingStore:
def __init__(self, *, source_label: str = "RAVNOVES004TREE") -> None:
self.parameters: dict[str, Any] | None = None
self.source_label = source_label
def publish_lab_instance(self, **parameters: Any) -> dict[str, Any]:
self.parameters = parameters
return parameters
def get_session_with_catalog_snapshot(
self,
session_id: str,
) -> tuple[SimpleNamespace, str]:
assert session_id == "20260828T130511Z_viewer_live"
summary = SimpleNamespace(
session_id=session_id,
display_name=self.source_label,
status="ready",
started_at_utc="2026-08-28T13:05:16.249Z",
completed_at_utc="2026-08-28T13:18:45.030Z",
duration_seconds=808.779495667,
modalities=("point-cloud", "trajectory", "video"),
source_count=3,
total_bytes=799_020_963,
replayable=True,
lab=None,
)
sources = tuple(
SimpleNamespace(
source_id=source_id,
semantic_channel_id=semantic_channel_id,
modality=modality,
status="recorded",
seekable=True,
artifact_id=artifact_id,
)
for source_id, semantic_channel_id, modality, artifact_id in (
(
"sensor.camera.right",
"camera.video.recorded",
"video",
"recorded-video-6a3945242828a038",
),
(
"sensor.lidar.primary",
"spatial.point-cloud.recorded",
"point-cloud",
"raw-transport-primary",
),
(
"spatial.trajectory",
"spatial.pose.recorded",
"trajectory",
"raw-transport-primary",
),
)
)
artifacts = (
SimpleNamespace(
artifact_id="raw-transport-clock",
kind="raw-transport-clock",
media_type="application/json",
byte_length=189,
sha256="1" * 64,
integrity_status="verified",
),
SimpleNamespace(
artifact_id="raw-transport-clock-origin",
kind="raw-transport-clock-origin",
media_type="application/json",
byte_length=103,
sha256="2" * 64,
integrity_status="verified",
),
SimpleNamespace(
artifact_id="raw-transport-index",
kind="raw-transport-index",
media_type="application/x-ndjson",
byte_length=7_679_275,
sha256=None,
integrity_status="verified",
),
SimpleNamespace(
artifact_id="raw-transport-primary",
kind="raw-transport",
media_type="application/x-nodedc-k1mqtt",
byte_length=245_183_013,
sha256="20c789eff922a6bbb53592f86614abc0729a30544df29e740e7a378d12af85c2",
integrity_status="verified",
),
SimpleNamespace(
artifact_id="recorded-video-6a3945242828a038",
kind="recorded-video",
media_type="video/mp4",
byte_length=553_837_950,
sha256=None,
integrity_status="validated-structure",
),
)
return (
SimpleNamespace(
summary=summary,
sources=sources,
artifacts=artifacts,
plugin_id="nodedc.device.xgrids-lixelkity-k1",
archive_id="xgrids-k1.viewer-live.evidence",
),
"a" * 64,
)
def _canonical_sha256(value: object) -> str:
encoded = json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
def _sealed_result(runtime_root: Path) -> Path:
authority = {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
"camera_semantics_can_clear_rigid_geometry": False,
}
timeline_payload = struct.pack(
"<6830Q",
*(39_250_000_000 + index * 100_000_000 for index in range(6830)),
)
artifact_payloads = {
"video/eomt-semantic-masks.zip": b"fixture-city-mask-archive",
"video/ddrnet-semantic-masks.zip": b"fixture-vegetation-mask-archive",
"video/frame-source-times-ns.bin": timeline_payload,
"proofs/job.json": b'{"fixture":"job"}',
"proofs/decode_repair.json": b'{"fixture":"decode"}',
"proofs/ddrnet_decode_repair.json": b'{"fixture":"decode"}',
}
def proof(path: str) -> dict[str, object]:
payload = artifact_payloads[path]
return {
"path": path,
"sha256": hashlib.sha256(payload).hexdigest(),
"byte_length": len(payload),
}
def taxonomy(schema: str, count: int) -> dict[str, object]:
return {
"schema_version": schema,
"classes": [
{
"class_id": index,
"label": f"class-{index}",
"color_rgb": [index % 256, (index * 2) % 256, (index * 3) % 256],
"disposition": "undefined" if index == 0 else "prediction",
}
for index in range(count)
],
}
review = {
"source_id": "RAVNOVES004TREE",
"session_id": "20260828T130511Z_viewer_live",
"source_job_id": "recorded-camera-eb2783c5480d56bda07c8af0",
"source_job_input_sha256": (
"eb2783c5480d56bda07c8af008dff5344d19dc550ef70fe2075d6f098f7cc715"
),
"source_stream_sha256": (
"e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac"
),
"recorded_media_source_id": "recorded.camera.6a3945242828a038",
"recorded_media_generation_sha256": (
"b073ea1e7babf1c77a664e1a5b95e3702d0e05b0e34c1e85a7c67a6f8b392ded"
),
"linked_route_review_result_id": (
"lab-v1-vegetation-shadow-" + "9" * 64
),
"frame_count": 6830,
"width": 800,
"height": 600,
"ground_truth": False,
"timeline_start_seconds": 39.25,
"timeline_end_seconds": 757.25,
"timeline": {
**proof("video/frame-source-times-ns.bin"),
"encoding": "uint64-le-nanoseconds",
"frame_count": 6830,
},
"decode_repair": {
"repaired_frame_count": 1,
"sequence": 6092,
"method": "duplicate-previous-decoded-frame",
"proofs": {
"eomt": proof("proofs/decode_repair.json"),
"ddrnet": proof("proofs/ddrnet_decode_repair.json"),
},
},
"proofs": {"job": proof("proofs/job.json")},
"layers": {
"city": {
"name": "EoMT Cityscapes",
"result_id": "result-" + "1" * 64,
"frame_count": 6830,
"taxonomy": taxonomy(
"missioncore.recorded-eomt-taxonomy/v1",
16,
),
"mask_archive": proof("video/eomt-semantic-masks.zip"),
"inference_fps": 3.0,
"latency_p95_ms": 361.0,
"peak_reserved_vram_bytes": 2_977_955_840,
},
"vegetation": {
"name": "ddrnet_39",
"result_id": "lab-v1-ravnoves-video-ddrnet-" + "2" * 64,
"frame_count": 6830,
"taxonomy": taxonomy(
"missioncore.lab-v1-vegetation-taxonomy/v1",
64,
),
"mask_archive": proof("video/ddrnet-semantic-masks.zip"),
"inference_fps": 52.0,
"latency_p95_ms": 27.0,
"peak_reserved_vram_bytes": 331_350_016,
},
},
}
candidates = {
candidate: {
"loaded_model_name": candidate,
"checkpoint_sha256": str(index) * 64,
"validation_metrics": {
"mean_iou_percent": 70.0,
"published_mean_iou_percent": 69.0,
"vegetation_mean_iou": 0.7,
},
"validation_timing": {
"latency_ms_p95": 20.0,
"throughput_fps_from_mean_inference": 50.0,
},
"shadow_timing": {
"latency_ms_p95": 21.0,
"throughput_fps_from_mean_inference": 49.0,
"prewarm_latency_ms": 100.0,
},
"resource": {
"peak_reserved_vram_bytes": 1024,
"gpu_name": "fixture-gpu",
},
}
for index, candidate in enumerate(("ddrnet", "ppliteseg"), start=3)
}
source = {
"shadow_session": "RAVNOVES004TREE",
"shadow_camera": "sensor.camera.right",
"shadow_frame_count": 6830,
"video_shadow_frame_count": 6830,
}
identity = {
"authority": authority,
"base_result_id": "lab-v1-vegetation-shadow-" + "9" * 64,
"route_full_review": review,
"selected_candidate": "ddrnet",
"candidate_metrics": candidates,
"source": source,
}
identity_sha256 = _canonical_sha256(identity)
result_id = f"lab-v1-vegetation-shadow-{identity_sha256}"
result_root = runtime_root / "lab-v1-vegetation" / "results" / result_id
result_root.mkdir(parents=True)
document = {
"schema_version": "missioncore.lab-v1-vegetation-shadow/v1",
"result_id": result_id,
"identity": identity,
"identity_sha256": identity_sha256,
"created_at_utc": "2026-08-29T18:05:11.329061+00:00",
"ground_truth": False,
"status": "visual-shadow-ready-policy-not-authorized",
"authority": dict(authority),
"source": source,
"route_video": None,
"route_review": None,
"route_full_review": dict(review),
"method": {
"completeness": "complete",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
},
"metrics": {"candidates": candidates},
"decision": {
"selected_candidate": "ddrnet",
"visual_shadow_ready": True,
"mission_policy_ready_for_configuration": True,
"navigation_accepted": False,
"production_accepted": False,
},
"limitations": ["fixture is observation-only"],
"catalogs": {"goose": [], "ravnoves": []},
"artifacts": [
{
**proof(path),
"role": "fixture",
"media_type": (
"application/zip"
if path.endswith(".zip")
else "application/octet-stream"
if path.endswith(".bin")
else "application/json"
),
}
for path in artifact_payloads
],
}
for relative, payload in artifact_payloads.items():
path = result_root / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(payload)
(result_root / "result.json").write_text(
json.dumps(document, ensure_ascii=False, sort_keys=True),
encoding="utf-8",
)
return result_root
def test_canonical_recorded_projection_uses_exact_session_binding_without_compute(
tmp_path: Path,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
result_root = _sealed_result(runtime_root)
store = CapturingStore()
published = publish_canonical_recorded_vegetation_result(
store=store, # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=result_root,
)
assert published["session_id"] == result_root.name
assert published["source_session_id"] == "20260828T130511Z_viewer_live"
assert published["source_result_id"] == "lab-v1-vegetation-shadow-" + "9" * 64
assert published["config_sha256"] is None
assert published["duration_seconds"] == pytest.approx(718.0)
assert published["include_recorded_media"] is False
assert published["expected_source_catalog_sha256"] == "a" * 64
capability = published["replay_capability"]
assert capability.as_dict() == {
"schema_version": CANONICAL_REPLAY_CAPABILITY_SCHEMA,
"kind": "canonical-recorded-rerun",
"viewer_profile": "recorded-session",
"timeline": "session_time",
"activation": "explicit",
"commands_enabled": False,
}
assert published["provenance"]["replay_capability"] == capability.as_dict()
assert published["provenance"]["method"]["completeness"] == "legacy-partial"
def test_canonical_recorded_projection_rejects_authority_and_unregistered_root(
tmp_path: Path,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
result_root = _sealed_result(runtime_root)
document_path = result_root / "result.json"
document = json.loads(document_path.read_text(encoding="utf-8"))
document["authority"]["commands_enabled"] = True
document_path.write_text(json.dumps(document, sort_keys=True), encoding="utf-8")
with pytest.raises(CanonicalRecordedCatalogError, match="control authority"):
publish_canonical_recorded_vegetation_result(
store=CapturingStore(), # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=result_root,
)
document["authority"]["commands_enabled"] = False
document_path.write_text(json.dumps(document, sort_keys=True), encoding="utf-8")
outside = tmp_path / result_root.name
outside.mkdir()
(outside / "result.json").write_text(
document_path.read_text(encoding="utf-8"),
encoding="utf-8",
)
with pytest.raises(CanonicalRecordedCatalogError, match="registered root"):
publish_canonical_recorded_vegetation_result(
store=CapturingStore(), # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=outside,
)
def test_canonical_recorded_projection_rejects_non_boolean_authority(
tmp_path: Path,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
result_root = _sealed_result(runtime_root)
document_path = result_root / "result.json"
document = json.loads(document_path.read_text(encoding="utf-8"))
document["authority"]["commands_enabled"] = 0
document_path.write_text(json.dumps(document, sort_keys=True), encoding="utf-8")
with pytest.raises(CanonicalRecordedCatalogError, match="control authority"):
publish_canonical_recorded_vegetation_result(
store=CapturingStore(), # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=result_root,
)
def test_canonical_recorded_projection_rejects_viewer_incomplete_result(
tmp_path: Path,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
result_root = _sealed_result(runtime_root)
document_path = result_root / "result.json"
document = json.loads(document_path.read_text(encoding="utf-8"))
document.pop("metrics")
document_path.write_text(json.dumps(document, sort_keys=True), encoding="utf-8")
store = CapturingStore()
with pytest.raises(CanonicalRecordedCatalogError, match="result metrics"):
publish_canonical_recorded_vegetation_result(
store=store, # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=result_root,
)
assert store.parameters is None
def test_canonical_recorded_projection_rejects_unbound_display_metrics(
tmp_path: Path,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
result_root = _sealed_result(runtime_root)
document_path = result_root / "result.json"
document = json.loads(document_path.read_text(encoding="utf-8"))
document["metrics"]["candidates"]["ddrnet"]["loaded_model_name"] = "tampered"
document_path.write_text(json.dumps(document, sort_keys=True), encoding="utf-8")
with pytest.raises(CanonicalRecordedCatalogError, match="identity-bound"):
publish_canonical_recorded_vegetation_result(
store=CapturingStore(), # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=result_root,
)
def test_canonical_recorded_projection_rejects_wrong_source_catalog_identity(
tmp_path: Path,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
result_root = _sealed_result(runtime_root)
with pytest.raises(CanonicalRecordedCatalogError, match="source catalog identity"):
publish_canonical_recorded_vegetation_result(
store=CapturingStore(source_label="different source"), # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=result_root,
)
def test_canonical_recorded_projection_rejects_intermediate_symlink_escape(
tmp_path: Path,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
outside_runtime = tmp_path / "outside-runtime"
outside_runtime.mkdir()
actual_result = _sealed_result(outside_runtime)
(runtime_root / "lab-v1-vegetation").symlink_to(
outside_runtime / "lab-v1-vegetation",
target_is_directory=True,
)
escaped_result = (
runtime_root
/ "lab-v1-vegetation"
/ "results"
/ actual_result.name
)
with pytest.raises(CanonicalRecordedCatalogError, match="runtime root"):
publish_canonical_recorded_vegetation_result(
store=CapturingStore(), # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=escaped_result,
)
def test_canonical_recorded_projection_rejects_document_changed_after_proof(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
runtime_root = tmp_path / "runtime"
runtime_root.mkdir()
result_root = _sealed_result(runtime_root)
original = canonical_catalog.verify_laboratory_evidence_result
def mutate_after_verification(*args: object, **kwargs: object) -> dict[str, object]:
proof = original(*args, **kwargs) # type: ignore[arg-type]
path = result_root / "result.json"
document = json.loads(path.read_text(encoding="utf-8"))
document["method"]["pipeline_id"] = "tampered-after-proof/v1"
path.write_text(json.dumps(document, sort_keys=True), encoding="utf-8")
return proof
monkeypatch.setattr(
canonical_catalog,
"verify_laboratory_evidence_result",
mutate_after_verification,
)
store = CapturingStore()
with pytest.raises(CanonicalRecordedCatalogError, match="changed after verification"):
publish_canonical_recorded_vegetation_result(
store=store, # type: ignore[arg-type]
runtime_root=runtime_root,
result_root=result_root,
)
assert store.parameters is None
+76 -1
View File
@@ -12,9 +12,10 @@ from pathlib import Path
from typing import Any
import pytest
from fastapi import APIRouter, HTTPException, Request, Response
from fastapi import APIRouter, FastAPI, HTTPException, Request, Response
from fastapi.responses import FileResponse
from fastapi.routing import APIRoute
from fastapi.testclient import TestClient
import k1link.sessions.media as recorded_media_module
import k1link.web.session_api as session_api_module
@@ -22,6 +23,7 @@ from k1link.compute import RecordedPerceptionOverlayArtifact, RecordedPerception
from k1link.device_plugins.xgrids_k1 import xgrids_k1_archive_source
from k1link.device_plugins.xgrids_k1.mqtt.capture import FRAME_HEADER, RAW_MAGIC
from k1link.sessions import (
LabReplayCapability,
MaterializedRecording,
RecordedMediaInspector,
RecordedMediaManifest,
@@ -347,6 +349,79 @@ def test_session_router_exposes_immutable_lab_provenance(tmp_path: Path) -> None
assert_no_local_paths((item, detail), repository)
def test_session_router_rolls_capability_projections_out_only_in_v2(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
legacy = store.publish_lab_instance(
session_id="lab-e21-legacy",
source_session_id=source.name,
display_name="LAB E21 · legacy",
lab_id="LAB E21",
result_kind="e21-realtime-envelope",
result_id="e21-realtime-envelope-" + "1" * 64,
run_created_at_utc="2026-07-23T15:55:15.548Z",
provenance={"method": lab_method()},
)
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
canonical = store.publish_lab_instance(
session_id="lab-v1-vegetation-shadow-" + "8" * 64,
source_session_id=source.name,
display_name="RAV004 · recorded",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "8" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
replay_capability=capability,
provenance={
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
)
router = build_session_router(store)
list_route = endpoint(router, "/api/v1/observation-sessions", "GET")
default_items = list_route(limit=20, cursor=None, scope="all")["items"]
assert {item["id"] for item in default_items} == {source.name, legacy.session_id}
default_legacy = next(item for item in default_items if item["id"] == legacy.session_id)
assert "replay_capability" not in default_legacy["lab"]
v1_labs = list_route(
limit=20,
cursor=None,
scope="laboratory",
lab_contract="v1",
)["items"]
assert [item["id"] for item in v1_labs] == [legacy.session_id]
v2_labs = list_route(
limit=20,
cursor=None,
scope="laboratory",
lab_contract="v2",
)["items"]
by_id = {item["id"]: item for item in v2_labs}
assert set(by_id) == {legacy.session_id, canonical.session_id}
assert by_id[legacy.session_id]["lab"]["replay_capability"] is None
assert by_id[canonical.session_id]["lab"]["replay_capability"] == capability.as_dict()
application = FastAPI()
application.include_router(router)
assert TestClient(application).get(
"/api/v1/observation-sessions?lab_contract=v3"
).status_code == 422
def test_delete_session_removes_evidence_and_cache_but_refuses_an_open_recording(
tmp_path: Path,
) -> None:
+55
View File
@@ -16,6 +16,7 @@ from fastapi.routing import APIRoute
from k1link.device_plugins.xgrids_k1 import xgrids_k1_archive_source
from k1link.device_plugins.xgrids_k1.mqtt.capture import FRAME_HEADER, RAW_MAGIC
from k1link.sessions import (
LabReplayCapability,
SessionRecordingMaterializer,
SessionRecordingPreparationManager,
SessionStore,
@@ -200,6 +201,60 @@ def test_startup_scan_baselines_historical_sessions_without_enqueuing(
manager.close()
def test_background_scan_never_prepares_explicit_capability_projection(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = _make_completed_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
monkeypatch.setattr(app_module, "session_store", store)
baseline = set(app_module.finalized_replayable_recording_ids())
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
projection = store.publish_lab_instance(
session_id="lab-v1-vegetation-shadow-" + "8" * 64,
source_session_id=source.name,
display_name="RAV004 · explicit recorded review",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "8" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
replay_capability=capability,
provenance={
"replay_capability": capability.as_dict(),
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "fixture/v1",
"components": [
{
"kind": "source",
"name": "fixture",
"version": "v1",
"role": "test",
"identity_sha256": "9" * 64,
}
],
},
},
)
current = set(app_module.finalized_replayable_recording_ids())
assert baseline == {source.name}
assert projection.session_id not in current
assert app_module.newly_finalized_recording_ids(baseline, current) == ()
def test_archive_revision_tracks_session_lifecycle_without_capture_churn(
tmp_path: Path,
) -> None:
+525
View File
@@ -15,6 +15,7 @@ from k1link.device_plugins.xgrids_k1.mqtt.capture import (
iter_capture_frames,
)
from k1link.sessions import (
LabReplayCapability,
LayoutConflictError,
SessionIntegrityError,
SessionNotFoundError,
@@ -823,6 +824,7 @@ def test_lab_instance_is_independent_and_never_deletes_source_evidence(
"method": lab_method(),
},
)
assert binding.replay_capability is None
detail = store.get_session(binding.session_id)
lab_command = store.prepare_replay(binding.session_id)
@@ -879,6 +881,421 @@ def test_lab_instance_publication_is_idempotent_but_provenance_is_immutable(
store.publish_lab_instance(**{**parameters, "config_sha256": "0" * 64})
def test_lab_replay_capability_column_migrates_existing_catalog(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
data_dir = tmp_path / "data"
initial = SessionStore(repository, data_dir=data_dir)
initial.reconcile_archive(xgrids_k1_archive_source(sessions))
legacy = initial.publish_lab_instance(
session_id="lab-e19-pre-capability",
source_session_id=source.name,
display_name="LAB E19 · pre-capability",
lab_id="LAB E19",
result_kind="e19-legacy",
result_id="e19-legacy-result",
run_created_at_utc="2026-07-23T05:19:43.138Z",
provenance={"method": lab_method()},
)
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN replay_capability_json"
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN include_recorded_media"
)
connection.commit()
migrated = SessionStore(repository, data_dir=data_dir)
with sqlite3.connect(migrated.database_path) as connection:
columns = {
row[1]
for row in connection.execute(
"PRAGMA table_info(observation_lab_instances)"
)
}
assert "replay_capability_json" in columns
assert "include_recorded_media" in columns
assert migrated.get_lab_instance(legacy.session_id).replay_capability is None
def test_migration_types_only_the_exact_rolling_canonical_projection(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260828T130511Z_viewer_live")
make_recorded_camera_source(source)
data_dir = tmp_path / "data"
initial = SessionStore(repository, data_dir=data_dir)
initial.reconcile_archive(xgrids_k1_archive_source(sessions))
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET archive_id = ? WHERE session_id = ?",
("xgrids-k1.viewer-live.evidence", source.name),
)
connection.commit()
evidence_identity = "a" * 64
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
provenance = {
"schema_version": "missioncore.canonical-recorded-lab-projection/v1",
"evidence_identity_sha256": evidence_identity,
"result_document_sha256": "b" * 64,
"replay_capability": capability.as_dict(),
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
},
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
],
},
}
parameters = {
"session_id": f"lab-v1-vegetation-shadow-{evidence_identity}",
"source_session_id": source.name,
"display_name": "RAVNOVES004TREE · полный маршрут восприятия",
"lab_id": "LAB V1",
"result_kind": "recorded-perception-qualification",
"result_id": f"lab-v1-vegetation-shadow-{evidence_identity}",
"source_result_id": "lab-v1-vegetation-shadow-" + "c" * 64,
"config_sha256": None,
"run_created_at_utc": "2026-08-29T18:05:11.329061+00:00",
"duration_seconds": 718.0,
"replay_capability": capability,
"provenance": provenance,
"include_recorded_media": False,
}
binding = initial.publish_lab_instance(**parameters)
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET modalities_json = ?, source_count = ?, "
"total_bytes = ? WHERE session_id = ?",
('["point-cloud","trajectory","video"]', 3, 999_999_999, binding.session_id),
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN replay_capability_json"
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN include_recorded_media"
)
connection.commit()
migrated = SessionStore(repository, data_dir=data_dir)
assert migrated.get_lab_instance(binding.session_id).replay_capability == capability
migrated_detail = migrated.get_session(binding.session_id)
assert migrated_detail.summary.modalities == ("point-cloud", "trajectory")
assert migrated_detail.summary.source_count == 2
assert all(source.modality != "video" for source in migrated_detail.sources)
assert migrated.list_recent(
scope="laboratory",
include_capability_projections=False,
).items == ()
assert [
item.session_id
for item in migrated.list_recent(
scope="laboratory",
include_capability_projections=True,
).items
] == [binding.session_id]
assert migrated.publish_lab_instance(**parameters).session_id == binding.session_id
def test_migration_rejects_a_non_replayable_canonical_projection(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260828T130511Z_viewer_live")
make_recorded_camera_source(source)
data_dir = tmp_path / "data"
initial = SessionStore(repository, data_dir=data_dir)
initial.reconcile_archive(xgrids_k1_archive_source(sessions))
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET archive_id = ? WHERE session_id = ?",
("xgrids-k1.viewer-live.evidence", source.name),
)
connection.commit()
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
evidence_identity = "a" * 64
result_id = f"lab-v1-vegetation-shadow-{evidence_identity}"
initial.publish_lab_instance(
session_id=result_id,
source_session_id=source.name,
display_name="RAVNOVES004TREE · полный маршрут восприятия",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id=result_id,
source_result_id="lab-v1-vegetation-shadow-" + "c" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
duration_seconds=718.0,
include_recorded_media=False,
replay_capability=capability,
provenance={
"schema_version": "missioncore.canonical-recorded-lab-projection/v1",
"evidence_identity_sha256": evidence_identity,
"result_document_sha256": "b" * 64,
"replay_capability": capability.as_dict(),
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
},
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
],
},
},
)
with sqlite3.connect(initial.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET replayable = 0, "
"primary_replay_artifact_id = NULL, timeline_origin_epoch_ns = NULL, "
"timeline_origin_monotonic_ns = NULL WHERE session_id = ?",
(result_id,),
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN replay_capability_json"
)
connection.execute(
"ALTER TABLE observation_lab_instances DROP COLUMN include_recorded_media"
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="LAB"):
SessionStore(repository, data_dir=data_dir)
def test_migration_rejects_boolean_aliases_in_rolling_authority(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
data_dir = tmp_path / "data"
store = SessionStore(repository, data_dir=data_dir)
with sqlite3.connect(store.database_path) as connection:
capability = {
"schema_version": "missioncore.observation-lab-replay-capability/v1",
"kind": "canonical-recorded-rerun",
"viewer_profile": "recorded-session",
"timeline": "session_time",
"activation": "explicit",
"commands_enabled": False,
}
evidence_identity = "a" * 64
provenance = {
"schema_version": "missioncore.canonical-recorded-lab-projection/v1",
"evidence_identity_sha256": evidence_identity,
"result_document_sha256": "b" * 64,
"replay_capability": capability,
"authority": {
"commands_enabled": 0,
"navigation_or_safety_accepted": 0,
"actuation_accepted": 0,
},
"method": {
"schema_version": "missioncore.laboratory-method/v1",
"completeness": "legacy-partial",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
"components": [
{
"kind": "source",
"name": "sealed full-route LAB result",
"version": "missioncore.lab-v1-vegetation-shadow/v1",
"role": "immutable Session catalog projection",
"identity_sha256": evidence_identity,
}
],
},
}
result_id = f"lab-v1-vegetation-shadow-{evidence_identity}"
connection.execute("PRAGMA foreign_keys = OFF")
connection.execute(
"INSERT INTO observation_sessions "
"(session_id, plugin_id, archive_id, display_name, status, modalities_json, "
"replayable, origin, source_count, total_bytes, allowed_root, session_root, "
"created_at_utc, updated_at_utc) "
"VALUES (?, ?, ?, ?, 'ready', '[]', 0, ?, 0, 0, ?, ?, ?, ?)",
(
result_id,
"fixture.plugin",
"missioncore.lab-instances",
"invalid rolling authority",
"missioncore.lab-instance/v1",
str(repository),
str(repository),
"2026-08-30T00:00:00Z",
"2026-08-30T00:00:00Z",
),
)
connection.execute(
"INSERT INTO observation_lab_instances "
"(session_id, source_session_id, lab_id, result_kind, result_id, "
"source_result_id, config_sha256, run_created_at_utc, published_at_utc, "
"include_recorded_media, replay_capability_json, provenance_json) "
"VALUES (?, ?, 'LAB V1', 'recorded-perception-qualification', ?, ?, "
"NULL, ?, ?, NULL, NULL, ?)",
(
result_id,
"20260828T130511Z_viewer_live",
result_id,
"lab-v1-vegetation-shadow-" + "c" * 64,
"2026-08-29T18:05:11.329061+00:00",
"2026-08-30T00:00:00Z",
json.dumps(provenance, sort_keys=True),
),
)
connection.commit()
migrated = SessionStore(repository, data_dir=data_dir)
with sqlite3.connect(migrated.database_path) as connection:
row = connection.execute(
"SELECT replay_capability_json, include_recorded_media "
"FROM observation_lab_instances WHERE session_id = ?",
("lab-v1-vegetation-shadow-" + "a" * 64,),
).fetchone()
assert row == (None, None)
def test_lab_instance_persists_typed_explicit_recorded_replay_capability(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
binding = store.publish_lab_instance(
session_id="lab-recorded-replay",
source_session_id=source.name,
display_name="LAB V1 · recorded replay",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "a" * 64,
source_result_id="lab-v1-vegetation-shadow-" + "b" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
replay_capability=capability,
provenance={
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
)
assert binding.replay_capability == capability
assert "replay_capability" not in binding.as_dict()
assert binding.as_dict()["provenance"]["replay_capability"] == capability.as_dict()
assert store.get_lab_instance(binding.session_id) == binding
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_lab_instances SET replay_capability_json = ? "
"WHERE session_id = ?",
('{"kind":"unknown"}', binding.session_id),
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="replay capability"):
store.get_lab_instance(binding.session_id)
@pytest.mark.parametrize("value", [0, 1, None, True])
def test_lab_replay_capability_rejects_non_literal_false(value: object) -> None:
with pytest.raises(ValueError, match="replay capability"):
LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=value, # type: ignore[arg-type]
)
def test_lab_instance_rejects_boolean_alias_in_capability_provenance(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
aliased = {**capability.as_dict(), "commands_enabled": 0}
with pytest.raises(ValueError, match="exactly match provenance"):
store.publish_lab_instance(
session_id="lab-recorded-replay-alias",
source_session_id=source.name,
display_name="LAB V1 · recorded replay alias",
lab_id="LAB V1",
result_kind="recorded-perception-qualification",
result_id="lab-v1-vegetation-shadow-" + "a" * 64,
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
replay_capability=capability,
provenance={
"replay_capability": aliased,
"method": lab_method(),
},
)
def test_lab_instance_rejects_publication_without_a_method_manifest(
tmp_path: Path,
) -> None:
@@ -933,6 +1350,114 @@ def test_bounded_lab_instance_excludes_unbounded_recorded_media(
assert store.prepare_replay(binding.session_id).primary_artifact.path.is_file()
def test_capability_projection_summary_is_exact_and_idempotently_repairable(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
make_recorded_camera_source(source)
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
_source_detail, source_snapshot_sha256 = (
store.get_session_with_catalog_snapshot(source.name)
)
capability = LabReplayCapability(
schema_version="missioncore.observation-lab-replay-capability/v1",
kind="canonical-recorded-rerun",
viewer_profile="recorded-session",
timeline="session_time",
activation="explicit",
commands_enabled=False,
)
parameters = {
"session_id": "lab-recorded-bounded",
"source_session_id": source.name,
"display_name": "LAB V1 · bounded recorded review",
"lab_id": "LAB V1",
"result_kind": "recorded-perception-qualification",
"result_id": "lab-v1-vegetation-shadow-" + "1" * 64,
"source_result_id": "lab-v1-vegetation-shadow-" + "2" * 64,
"run_created_at_utc": "2026-08-29T18:05:11.329061+00:00",
"duration_seconds": 59.962,
"include_recorded_media": False,
"expected_source_catalog_sha256": source_snapshot_sha256,
"replay_capability": capability,
"provenance": {
"replay_capability": capability.as_dict(),
"method": lab_method(),
},
}
binding = store.publish_lab_instance(**parameters)
detail = store.get_session(binding.session_id)
assert detail.summary.modalities == ("point-cloud", "trajectory")
assert detail.summary.source_count == len(detail.sources) == 2
referenced_artifacts = {item.artifact_id for item in detail.sources}
assert detail.summary.total_bytes == sum(
artifact.byte_length
for artifact in detail.artifacts
if artifact.artifact_id in referenced_artifacts
)
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_sessions SET modalities_json = ?, source_count = ?, "
"total_bytes = ? WHERE session_id = ?",
('["point-cloud","trajectory","video"]', 3, 999_999_999, binding.session_id),
)
connection.commit()
assert store.publish_lab_instance(**parameters) == binding
repaired = store.get_session(binding.session_id)
assert repaired.summary.modalities == ("point-cloud", "trajectory")
assert repaired.summary.source_count == 2
assert repaired.summary.total_bytes == detail.summary.total_bytes
with pytest.raises(SessionIntegrityError, match="recorded-media policy"):
store.publish_lab_instance(**{**parameters, "include_recorded_media": True})
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_session_artifacts SET sha256 = ? "
"WHERE session_id = ? AND artifact_id = 'raw-transport-primary'",
("0" * 64, binding.session_id),
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="source snapshot"):
store.publish_lab_instance(**parameters)
def test_lab_publication_rejects_a_source_changed_after_snapshot(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
source = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
_detail, snapshot_sha256 = store.get_session_with_catalog_snapshot(source.name)
with sqlite3.connect(store.database_path) as connection:
connection.execute(
"UPDATE observation_session_artifacts SET byte_length = byte_length + 1 "
"WHERE session_id = ? AND artifact_id = 'raw-transport-primary'",
(source.name,),
)
connection.commit()
with pytest.raises(SessionIntegrityError, match="changed after admission"):
store.publish_lab_instance(
session_id="lab-source-snapshot-race",
source_session_id=source.name,
display_name="LAB E21 · source snapshot race",
lab_id="LAB E21",
result_kind="source-snapshot-race",
result_id="source-snapshot-race-result",
run_created_at_utc="2026-08-29T18:05:11.329061+00:00",
provenance={"method": lab_method()},
expected_source_catalog_sha256=snapshot_sha256,
)
assert store.get_lab_instance("lab-source-snapshot-race") is None
def test_delete_session_rejects_a_catalog_target_outside_its_allowed_root(
tmp_path: Path,
) -> None: