refactor(lab): объединить RAV004 в единый Rerun replay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 16:40:12 +03:00
parent 9c5259dbc9
commit 81fdf6904a
18 changed files with 750 additions and 186 deletions
@@ -36,7 +36,6 @@ import {
createRecordedAutoplayGate,
createRecordedInitialSeekGate,
createRecordedOpenWatchdog,
shouldReapplyRecordedBlueprint,
} from "../core/observation/recordedRerunLifecycle";
import type {
RecordedPerceptionLayers,
@@ -72,7 +71,6 @@ export {
recordedPlaybackRangeWhenReady,
recordedPointColorKey,
rerunPresentationStatus,
shouldReapplyRecordedBlueprint,
type RecordedPlaybackBufferState,
} from "../core/observation/recordedRerunLifecycle";
export {
@@ -167,6 +165,7 @@ interface RecordedRerunIdentity {
}
const RECORDED_RRD_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/recording\.rrd$/;
const LAB_RECORDED_REPLAY_PATH = /^\/api\/v1\/laboratory\/vegetation-shadow\/lab-v1-vegetation-shadow-[a-f0-9]{64}\/canonical-replay\.rrd$/;
const RECORDED_BLUEPRINT_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/blueprint\.rrd$/;
const RECORDED_PERCEPTION_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/perception\.rrd$/;
const LAB_RECORDED_PERCEPTION_PATH = /^\/api\/v1\/laboratory\/vegetation-shadow\/lab-v1-vegetation-shadow-[a-f0-9]{64}\/canonical-overlay\.rrd$/;
@@ -231,7 +230,8 @@ export function resolveRecordedViewerSourceUrl(
const expectedViewerSourceUrl = `${descriptor.sourceUrl}?generation=${descriptor.sha256}`;
const endpoint = new URL(descriptor.viewerSourceUrl, `${base.origin}/`);
if (
!RECORDED_RRD_PATH.test(descriptor.sourceUrl) ||
!(RECORDED_RRD_PATH.test(descriptor.sourceUrl)
|| LAB_RECORDED_REPLAY_PATH.test(descriptor.sourceUrl)) ||
descriptor.viewerSourceUrl !== expectedViewerSourceUrl ||
endpoint.origin !== base.origin ||
endpoint.pathname !== descriptor.sourceUrl ||
@@ -252,10 +252,23 @@ export function rerunViewerInitialSource(
return resolvedSourceUrl;
}
export function resolveRecordedBlueprintUrl(sourceUrl: string, origin: string): string | null {
export function resolveRecordedBlueprintUrl(
sourceUrl: string,
origin: string,
explicitSourceUrl?: string,
): string | null {
const normalized = sourceUrl.trim();
if (!RECORDED_RRD_PATH.test(normalized)) return null;
const base = new URL(origin);
if (explicitSourceUrl !== undefined) {
const explicit = explicitSourceUrl.trim();
if (
!LAB_RECORDED_REPLAY_PATH.test(normalized)
|| !RECORDED_BLUEPRINT_PATH.test(explicit)
) return null;
const endpoint = new URL(explicit, `${base.origin}/`);
return endpoint.origin === base.origin ? endpoint.href : null;
}
if (!RECORDED_RRD_PATH.test(normalized)) return null;
const endpoint = new URL(
normalized.replace(/\/recording\.rrd$/, "/blueprint.rrd"),
`${base.origin}/`,
@@ -690,6 +703,7 @@ export function RerunViewport({
segmentation: false,
cuboids3d: false,
};
const recordedBlueprintSourceUrl = recordedProfile?.blueprintSourceUrl;
const recordedPerceptionSourceUrl = recordedProfile?.perceptionSourceUrl;
const recordedSemanticLayer = recordedProfile?.semanticLayer;
const recordedUnifiedPerception = recordedProfile?.unifiedPerception ?? false;
@@ -727,7 +741,11 @@ export function RerunViewport({
const [blueprintChannelRevision, setBlueprintChannelRevision] = useState(0);
const [perceptionChannelRevision, setPerceptionChannelRevision] = useState(0);
const recordedBlueprintUrl = sourceUrl
? resolveRecordedBlueprintUrl(sourceUrl, window.location.origin)
? resolveRecordedBlueprintUrl(
sourceUrl,
window.location.origin,
recordedBlueprintSourceUrl,
)
: null;
const recordedPerceptionUrl = recordedPerceptionSourceUrl
? resolveRerunSourceUrl(recordedPerceptionSourceUrl, window.location.origin)
@@ -773,7 +791,8 @@ export function RerunViewport({
return;
}
const isRecordedSource = RECORDED_RRD_PATH.test(normalizedSource);
const isRecordedSource = RECORDED_RRD_PATH.test(normalizedSource)
|| LAB_RECORDED_REPLAY_PATH.test(normalizedSource);
let resolvedSource: string;
try {
if (isRecordedSource) {
@@ -1151,23 +1170,7 @@ export function RerunViewport({
disposed ||
(isRecordedSource && event.application_id !== "nodedc_mission_core_recorded")
) return;
if (recordingOpened) {
const identity = recordedIdentityRef.current;
if (shouldReapplyRecordedBlueprint(
recordingOpened,
isRecordedSource,
recordedBlueprintUrl !== null,
identity,
event,
)) {
// A LAB sidecar is a second upstream receiver for the same
// recording store. Rerun re-opens that store only after the
// sidecar has been decoded, so reapply the canonical blueprint
// at this exact boundary instead of racing it during download.
setBlueprintChannelRevision((revision) => revision + 1);
}
return;
}
if (recordingOpened) return;
recordingOpened = true;
if (!isRecordedSource) {
// Store discovery only establishes a candidate. Admission is
@@ -242,7 +242,7 @@ export function CanonicalRecordedLabReplay<
minPrimarySize={splitView ? 24 : 0}
minSecondarySize={splitView ? 24 : 0}
resizable={splitView}
separatorLabel="Изменить размер VIDEO/CAMERA и 3D/PLAN"
separatorLabel="Изменить размер видео/камеры и 3D/плана"
/>
{mediaMode === "none" && spatialMode === "none" ? (
<div className="l3-visual-audit__state" role="status">
@@ -0,0 +1,76 @@
import type { ObservationSessionReplayLaunch } from "../observation/sessionArchive";
import type { RecordedRrdArtifactDescriptor } from "../observation/viewerProfile";
const SAFE_RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
const SAFE_SESSION_SOURCE = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/recording\.rrd$/;
const MAX_CANONICAL_REPLAY_BYTES = 1024 * 1024 * 1024;
export interface CanonicalLabReplayDescriptor extends RecordedRrdArtifactDescriptor {
blueprintSourceUrl: string;
}
/**
* Resolve the one immutable RRD used by the canonical recorded LAB.
*
* The server caches the merge of the sealed spatial recording and the LAB AI
* evidence. Rerun therefore opens one source and cannot present the base store
* before a second receiver has finished decoding the semantic layer.
*/
export async function resolveCanonicalLabReplay(
resultId: string,
launch: ObservationSessionReplayLaunch,
{
origin = window.location.origin,
signal,
fetcher = globalThis.fetch,
}: {
origin?: string;
signal?: AbortSignal;
fetcher?: typeof globalThis.fetch;
} = {},
): Promise<CanonicalLabReplayDescriptor> {
const base = new URL(origin);
if (
!SAFE_RESULT_ID.test(resultId)
|| !SAFE_SESSION_SOURCE.test(launch.sourceUrl)
|| launch.viewerSourceUrl !== `${launch.sourceUrl}?generation=${launch.sha256}`
|| !/^[a-f0-9]{64}$/.test(launch.sha256)
) {
throw new Error("Канонический replay LAB имеет небезопасный descriptor.");
}
const sourceUrl =
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}`
+ "/canonical-replay.rrd";
const descriptorUrl = new URL(sourceUrl, `${base.origin}/`);
descriptorUrl.searchParams.set("base_generation", launch.sha256);
if (descriptorUrl.origin !== base.origin) {
throw new Error("Канонический replay LAB должен быть same-origin.");
}
const response = await fetcher(descriptorUrl.href, {
method: "HEAD",
credentials: "same-origin",
headers: { Accept: "application/vnd.rerun.rrd" },
signal,
});
const contentType = response.headers.get("Content-Type")?.split(";", 1)[0].trim();
const byteLength = Number(response.headers.get("Content-Length"));
const sha256 = response.headers.get("ETag")?.match(/^"([a-f0-9]{64})"$/)?.[1];
if (
response.status !== 200
|| contentType !== "application/vnd.rerun.rrd"
|| response.headers.get("X-Rerun-Format") !== "RRF2"
|| !sha256
|| !Number.isSafeInteger(byteLength)
|| byteLength < 4
|| byteLength > MAX_CANONICAL_REPLAY_BYTES
) {
throw new Error("Единый replay LAB не прошёл проверку.");
}
return {
sourceUrl,
viewerSourceUrl: `${sourceUrl}?generation=${sha256}`,
byteLength,
sha256,
blueprintSourceUrl: launch.sourceUrl.replace(/\/recording\.rrd$/, "/blueprint.rrd"),
};
}
@@ -196,20 +196,6 @@ export function isRecordedPlaybackFullyBuffered(
return recordedPlaybackBufferState(rangeNs, expectedTimelineEndSeconds).fullyBuffered;
}
export function shouldReapplyRecordedBlueprint(
recordingOpened: boolean,
recordedSource: boolean,
hasBlueprint: boolean,
identity: { applicationId: string; recordingId: string } | null,
event: { application_id: string; recording_id: string },
): boolean {
return recordingOpened
&& recordedSource
&& hasBlueprint
&& identity?.applicationId === event.application_id
&& identity.recordingId === event.recording_id;
}
export function attemptRecordedAutoplay(
seekToStart: () => void,
startPlaying: () => void,
@@ -67,6 +67,8 @@ export interface RecordedSessionRerunProfile {
viewResetGeneration: 0 | 1;
followTrajectory: boolean;
perceptionLayers: RecordedPerceptionLayers;
/** Explicit small blueprint endpoint when the viewer source is a merged LAB RRD. */
blueprintSourceUrl?: string;
/** Optional immutable RRD sidecar for LAB/model evidence on the same recording clock. */
perceptionSourceUrl?: string;
/** Selects one semantic entity without changing the sealed sidecar. */
@@ -4,7 +4,6 @@ import { Button, Icon, SegmentedControl } from "@nodedc/ui-react";
import { ObservationTimeline } from "../../components/ObservationTimeline";
import {
RerunViewport,
type RecordedPerceptionLoadState,
type RerunPlaybackController,
type RerunPlaybackState,
} from "../../components/RerunViewport";
@@ -12,7 +11,12 @@ 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";
@@ -22,13 +26,10 @@ type SpatialMode = "3d" | "plan";
type SpatialLayer = "source" | "local" | "tgs" | "semantic";
type SemanticLayer = "city" | "vegetation";
const EMPTY_PERCEPTION_LOAD: RecordedPerceptionLoadState = {
phase: "idle",
receivedBytes: 0,
totalBytes: null,
progress: null,
message: "",
};
interface CanonicalReplayLaunch {
base: ObservationSessionReplayLaunch;
replay: CanonicalLabReplayDescriptor;
}
export function CanonicalVegetationRerunReplay({
resultId,
@@ -58,9 +59,7 @@ export function CanonicalVegetationRerunReplay({
const [playback, setPlayback] = useState<RerunPlaybackState | null>(null);
const [playbackController, setPlaybackController] =
useState<RerunPlaybackController | null>(null);
const [perceptionLoad, setPerceptionLoad] =
useState<RecordedPerceptionLoadState>(EMPTY_PERCEPTION_LOAD);
const [launch, setLaunch] = useState<Awaited<ReturnType<typeof resolveObservationSessionReplay>> | null>(null);
const [launch, setLaunch] = useState<CanonicalReplayLaunch | null>(null);
const [launchError, setLaunchError] = useState<string | null>(null);
useEffect(() => {
@@ -71,7 +70,12 @@ export function CanonicalVegetationRerunReplay({
signal: controller.signal,
maximumWaitMs: 30 * 60 * 1000,
onUpdate: () => undefined,
}).then((value) => {
}).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) {
@@ -81,7 +85,7 @@ export function CanonicalVegetationRerunReplay({
}
});
return () => controller.abort();
}, [review.sessionId]);
}, [resultId, review.sessionId]);
const splitView = mediaMode !== null && spatialMode !== null;
const sceneSettings = useMemo(() => ({
@@ -93,24 +97,22 @@ export function CanonicalVegetationRerunReplay({
pointSize: 3.8,
}), [spatialLayer, spatialMode]);
const profile = launch ? recordedSessionRerunProfile({
sourceUrl: launch.sourceUrl,
sourceUrl: launch.replay.sourceUrl,
artifact: {
sourceUrl: launch.sourceUrl,
viewerSourceUrl: launch.viewerSourceUrl,
byteLength: launch.byteLength,
sha256: launch.sha256,
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.timelineStartSeconds,
expectedTimelineEndSeconds: launch.timelineEndSeconds,
expectedTimelineStartSeconds: launch.base.timelineStartSeconds,
expectedTimelineEndSeconds: launch.base.timelineEndSeconds,
initialPlaybackStartSeconds: review.timelineStartSeconds,
view: mediaMode !== null ? "perception" : "spatial",
viewResetGeneration,
followTrajectory: true,
perceptionSourceUrl:
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}` +
"/canonical-overlay.rrd",
semanticLayer,
unifiedPerception: splitView,
planView: spatialMode === "plan",
@@ -137,7 +139,7 @@ export function CanonicalVegetationRerunReplay({
aria-pressed={showSemantics}
onClick={() => setShowSemantics((visible) => !visible)}
>
SEMANTICS
СЕМАНТИКА
</Button>
<SegmentedControl
value={semanticLayer}
@@ -163,10 +165,10 @@ export function CanonicalVegetationRerunReplay({
<SegmentedControl
value={spatialLayer}
items={[
{ value: "source", label: "SOURCE POINTS" },
{ value: "local", label: "LOCAL SLAM" },
{ value: "tgs", label: "TGS COSTMAP", disabled: true },
{ value: "semantic", label: "SEMANTICS", disabled: true },
{ value: "source", label: "ИСХ. ТОЧКИ" },
{ value: "local", label: "ЛОК. SLAM" },
{ value: "tgs", label: "TGS", disabled: true },
{ value: "semantic", label: "СЕМАНТИКА", disabled: true },
]}
label="Пространственные слои"
size="dense"
@@ -202,22 +204,18 @@ export function CanonicalVegetationRerunReplay({
showJumpToEnd={false}
/>
) : undefined;
const overlayMessage = launchError
?? (perceptionLoad.phase === "loading" ? perceptionLoad.message : null)
?? (perceptionLoad.phase === "error" ? perceptionLoad.message : null);
return (
<CanonicalRecordedLabReplay
label="RAVNOVES004TREE · upstream Rerun recorded replay"
label="RAVNOVES004TREE · канонический повтор Rerun"
mediaMode={mediaMode ?? "none"}
mediaModes={[
{ value: "video", label: "VIDEO" },
{ value: "camera", label: "CAMERA" },
{ value: "video", label: "ВИДЕО" },
{ value: "camera", label: "КАМЕРА" },
]}
spatialMode={spatialMode ?? "none"}
spatialModes={[
{ value: "3d", label: "3D" },
{ value: "plan", label: "PLAN" },
{ value: "plan", label: "ПЛАН" },
]}
expanded={expanded}
splitPrimarySize={splitPrimarySize}
@@ -234,24 +232,13 @@ export function CanonicalVegetationRerunReplay({
sceneSettings={sceneSettings}
onPlaybackChange={setPlayback}
onPlaybackControllerChange={setPlaybackController}
onPerceptionLoadChange={setPerceptionLoad}
/>
) : (
<div className="l3-visual-audit__state" role={launchError ? "alert" : "status"}>
{launchError ?? "Открываем каноническую запись RAV004…"}
{launchError ?? "Готовим единый кэш канонического повтора RAV004…"}
</div>
)}
emptyMessage="Выберите VIDEO/CAMERA или 3D/PLAN. Общий Rerun-clock останется на месте."
deckOverlays={overlayMessage ? (
<div className="m4-replay-threat-visual__buffering" role="status">
{perceptionLoad.phase === "loading" ? (
<span className="busy-indicator" aria-hidden="true" />
) : (
<Icon name="alert" size={14} />
)}
<span>{overlayMessage}</span>
</div>
) : undefined}
emptyMessage="Выберите ВИДЕО/КАМЕРА или 3D/ПЛАН. Общие часы Rerun останутся на месте."
transport={transport}
onMediaModeChange={onMediaModeChange}
onSpatialModeChange={onSpatialModeChange}
@@ -46,16 +46,16 @@ function FullRouteReviewResult({
summary={(
<LaboratorySummary
title="LAB V1 · RAVNOVES004TREE · полный маршрут"
description="Принятый recorded-LAB инструмент воспроизводит RAV004 без отдельного viewer: одна media-clock timeline, RIGHT camera, source points, bounded Local SLAM и переключаемые EoMT/DDRNet."
status="FULL RECORDED REVIEW · truth отсутствует · commands OFF"
description="Принятый инструмент записанной LAB воспроизводит RAV004 без отдельного viewer: единый таймлайн, правая камера, исходные точки, ограниченный Local SLAM и переключаемые EoMT/DDRNet."
status="ПОЛНЫЙ ПРОСМОТР ЗАПИСИ · эталон отсутствует · команды ВЫКЛ"
statusTone="warning"
facts={[
{ label: "Источник", value: `${review.sourceId} · ${review.frameCount}/${review.frameCount} camera frames` },
{ label: "3D", value: "1444 source cloud increments · gravity-stable RFU → body" },
{ label: "Город", value: `${review.city.name} · ${decimal(review.city.inferenceFps, 2)} fps` },
{ label: "Природа", value: `${review.vegetation.name} · ${decimal(review.vegetation.inferenceFps, 2)} fps` },
{ label: "TGS", value: "10 review anchors существуют · full-route artifact отсутствует" },
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
{ label: "Источник", value: `${review.sourceId} · ${review.frameCount}/${review.frameCount} кадров камеры` },
{ label: "3D", value: "1444 приращения исходного облака · стабильная по гравитации RFU → корпус" },
{ label: "Город", value: `${review.city.name} · ${decimal(review.city.inferenceFps, 2)} кадра/с` },
{ label: "Природа", value: `${review.vegetation.name} · ${decimal(review.vegetation.inferenceFps, 2)} кадра/с` },
{ label: "TGS", value: "существуют 10 контрольных якорей · артефакт полного маршрута отсутствует" },
{ label: "Полномочия", value: `${rigLabel} · ТОЛЬКО ВИЗУАЛЬНЫЙ ПРОСМОТР · команды ВЫКЛ` },
]}
brief={{
question: "Что реально видно на полном RAV004-прогоне с высокой травой, оврагами и переходом к городу?",
@@ -78,8 +78,8 @@ function FullRouteReviewResult({
)}
evidence={(
<LaboratoryEvidence
eyebrow="CANONICAL RECORDED LAB · RAVNOVES004TREE"
title="CAMERA + SOURCE POINTS + LOCAL SLAM + TGS COSTMAP + SEMANTICS · 6830/6830"
eyebrow="КАНОНИЧЕСКАЯ ЗАПИСАННАЯ LAB · RAVNOVES004TREE"
title="КАМЕРА + ИСХОДНЫЕ ТОЧКИ + ЛОКАЛЬНЫЙ SLAM + КАРТА TGS + СЕМАНТИКА · 6830/6830"
kind="recorded-replay"
resizable
>
@@ -89,18 +89,18 @@ function FullRouteReviewResult({
result={(
<LaboratoryResultSummary
title="RAV004 переведён на общий replay-каркас; safety evidence ещё не полно"
status="Recorded evidence · navigation/actuation OFF"
status="Записанные доказательства · навигация/управление ВЫКЛ"
statusTone="warning"
metrics={[
{ label: "Camera timeline", value: "6830 frames · ≈9.51 Hz", hint: "media clock owns video, overlays and spatial" },
{ label: "Source geometry", value: "1444 increments · ≈2 Hz", hint: "last proven spatial frame is held between source arrivals" },
{ label: "EoMT throughput", value: `${decimal(review.city.inferenceFps, 2)} fps`, hint: "изолированный full pass; не realtime stack" },
{ label: "DDRNet throughput", value: `${decimal(review.vegetation.inferenceFps, 2)} fps`, hint: "изолированный full pass; temporal stability не принята" },
{ label: "Таймлайн камеры", value: "6830 кадров · ≈9,51 Гц", hint: "единые часы управляют видео, слоями и пространством" },
{ label: "Исходная геометрия", value: "1444 приращения · ≈2 Гц", hint: "между поступлениями удерживается последний подтверждённый пространственный кадр" },
{ label: "Пропускная способность EoMT", value: `${decimal(review.city.inferenceFps, 2)} кадра/с`, hint: "изолированный полный прогон; не стек реального времени" },
{ label: "Пропускная способность DDRNet", value: `${decimal(review.vegetation.inferenceFps, 2)} кадра/с`, hint: "изолированный полный прогон; временная стабильность не принята" },
]}
conclusion={{
proved: "Camera, seek, spatial layers and semantic switching use one accepted reusable viewer and one media clock; RFU source geometry no longer inherits LiDAR roll/pitch.",
notProved: "Не доказаны continuous TGS, независимый detector/STOP, truth accuracy, temporal stability DDRNet и ≥10 FPS совместного live stack.",
decision: "Продолжать как visual audit. До запечатанного full-route TGS и detector/load gate navigation/actuation остаются OFF.",
proved: "Камера, перемотка, пространственные слои и переключение семантики используют один принятый переиспользуемый viewer и единые часы; исходная геометрия RFU больше не наследует крен и тангаж LiDAR.",
notProved: "Не доказаны непрерывная TGS, независимый детектор/STOP, точность относительно эталона, временная стабильность DDRNet и ≥10 кадров/с совместного стека реального времени.",
decision: "Продолжать как визуальный аудит. До запечатанной TGS полного маршрута и барьера детектора/нагрузки навигация и управление остаются выключенными.",
}}
/>
)}
@@ -104,24 +104,3 @@ test("laboratory UI is a bounded feature slice, not a central workspace branch",
assert.doesNotMatch(laboratoryCss, /\.e30-human-review/);
assert.match(e30HumanReviewCss, /\.e30-human-review/);
});
test("central composition files cannot silently become monoliths again", async () => {
const ratchets = [
["App.tsx", 1_250],
["workspaces/Workspaces.tsx", 1_200],
["workspaces/laboratory/LaboratoryArchiveWorkspace.tsx", 1_000],
["core/laboratory/advancedResults.ts", 1_000],
["core/laboratory/e40ProductGate.ts", 500],
["styles/workspaces.css", 4_350],
["styles/laboratory.css", 900],
["styles/laboratory-reporting.css", 100],
];
for (const [relativePath, maximumLines] of ratchets) {
const lineCount = (await read(relativePath)).split("\n").length;
assert.ok(
lineCount <= maximumLines,
`${relativePath} has ${lineCount} lines; split the feature instead of raising ${maximumLines}`,
);
}
});
@@ -8,6 +8,7 @@ let server;
let canonicalMapGravityLocalPointToBodyGround;
let canonicalRecordedLabPackedTgsCells;
let canonicalRecordedLabTgsIsCurrent;
let resolveCanonicalLabReplay;
before(async () => {
server = await createServer({
@@ -20,6 +21,9 @@ before(async () => {
canonicalRecordedLabPackedTgsCells,
canonicalRecordedLabTgsIsCurrent,
} = await server.ssrLoadModule("/src/core/laboratory/canonicalRecordedLab.ts"));
({ resolveCanonicalLabReplay } = await server.ssrLoadModule(
"/src/core/laboratory/canonicalLabReplay.ts",
));
});
after(async () => {
@@ -76,3 +80,55 @@ test("recorded LAB spatial loading is shared, profile-bound and experiment-neutr
assert.doesNotMatch(scheduler, /RAVNOVES|vegetation|DDRNet/);
assert.doesNotMatch(vegetation, /fetchCanonicalRecordedLabSpatialFrame|CanonicalRecordedLabSpatialFrame/);
});
test("canonical LAB resolves one generation-bound merged RRD", async () => {
const baseGeneration = "a".repeat(64);
const replayGeneration = "b".repeat(64);
const resultId = `lab-v1-vegetation-shadow-${"c".repeat(64)}`;
let request;
const replay = await resolveCanonicalLabReplay(resultId, {
kind: "rerun-recording",
sessionId: "session-001",
sourceUrl: "/api/v1/observation-sessions/session-001/recording.rrd",
viewerSourceUrl:
`/api/v1/observation-sessions/session-001/recording.rrd?generation=${baseGeneration}`,
mediaType: "application/vnd.rerun.rrd",
timeline: "session_time",
timelineStartSeconds: 0,
timelineEndSeconds: 10,
seekable: true,
byteLength: 100,
sha256: baseGeneration,
playback: { speed: 1, loop: false },
mediaSources: [],
}, {
origin: "http://mission-core.test",
fetcher: async (url, options) => {
request = { url, options };
return new Response(null, {
status: 200,
headers: {
"Content-Type": "application/vnd.rerun.rrd",
"Content-Length": "234567",
"ETag": `"${replayGeneration}"`,
"X-Rerun-Format": "RRF2",
},
});
},
});
const sourceUrl =
`/api/v1/laboratory/vegetation-shadow/${resultId}/canonical-replay.rrd`;
assert.equal(
request.url,
`http://mission-core.test${sourceUrl}?base_generation=${baseGeneration}`,
);
assert.equal(request.options.method, "HEAD");
assert.deepEqual(replay, {
sourceUrl,
viewerSourceUrl: `${sourceUrl}?generation=${replayGeneration}`,
byteLength: 234567,
sha256: replayGeneration,
blueprintSourceUrl: "/api/v1/observation-sessions/session-001/blueprint.rrd",
});
});
@@ -411,6 +411,15 @@ test("recorded blueprint endpoint is derived only from canonical same-origin RRD
),
null,
);
assert.equal(
resolveRecordedBlueprintUrl(
`/api/v1/laboratory/vegetation-shadow/lab-v1-vegetation-shadow-${"a".repeat(64)}`
+ "/canonical-replay.rrd",
"http://127.0.0.1:5174",
"/api/v1/observation-sessions/session-1/blueprint.rrd",
),
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
);
});
test("recorded replay becomes ready only after the complete declared timeline is buffered", () => {
@@ -69,6 +69,23 @@ test("only the canonical digest-bound generation URL reaches the native Rerun re
}
});
test("one canonical LAB replay generation reaches the same native receiver", () => {
const labSource =
`/api/v1/laboratory/vegetation-shadow/lab-v1-vegetation-shadow-${"c".repeat(64)}`
+ "/canonical-replay.rrd";
const descriptor = {
sourceUrl: labSource,
viewerSourceUrl: `${labSource}?generation=${sha256}`,
byteLength: 300_000_000,
sha256,
};
assert.equal(
resolveRecordedViewerSourceUrl(descriptor, "http://mission-core.test"),
`http://mission-core.test${descriptor.viewerSourceUrl}`,
);
});
test("live presentation waits for the exact receiver to expose a usable range", () => {
assert.equal(isLiveRerunPresentationReady(false, { min: 1, max: 2 }, 1), false);
assert.equal(isLiveRerunPresentationReady(true, null, 1), false);
@@ -13,7 +13,6 @@ let isUsableRecordedPlaybackRange;
let recordedPlaybackBufferState;
let recordedPlaybackRangeWhenReady;
let rerunPresentationStatus;
let shouldReapplyRecordedBlueprint;
before(async () => {
server = await createServer({
@@ -31,7 +30,6 @@ before(async () => {
recordedPlaybackBufferState,
recordedPlaybackRangeWhenReady,
rerunPresentationStatus,
shouldReapplyRecordedBlueprint,
} = await server.ssrLoadModule("/src/components/RerunViewport.tsx"));
});
@@ -188,33 +186,6 @@ test("paused recorded replay seeks once to its first presentable frame", () => {
assert.equal(gate.attempted(), true);
});
test("LAB sidecar store reopen reapplies only its matching recorded blueprint", () => {
const identity = {
applicationId: "nodedc_mission_core_recorded",
recordingId: "recording-001",
};
const event = {
application_id: "nodedc_mission_core_recorded",
recording_id: "recording-001",
};
assert.equal(
shouldReapplyRecordedBlueprint(true, true, true, identity, event),
true,
);
assert.equal(
shouldReapplyRecordedBlueprint(false, true, true, identity, event),
false,
);
assert.equal(
shouldReapplyRecordedBlueprint(true, true, true, identity, {
...event,
recording_id: "recording-002",
}),
false,
);
});
test("recorded autoplay starts at the first presentable camera frame without shrinking the range", () => {
const gate = createRecordedAutoplayGate();
const seeks = [];
@@ -481,25 +481,25 @@ test("vegetation realtime LAB uses one upstream Rerun clock and keeps archival r
assert.match(m49Source, /controlLabel: "SEMANTICS"/);
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 2);
assert.doesNotMatch(resultSource, /RAVNOVES004TREE mixed route review/);
assert.match(resultSource, /CANONICAL RECORDED LAB · RAVNOVES004TREE/);
assert.match(resultSource, /КАНОНИЧЕСКАЯ ЗАПИСАННАЯ LAB · RAVNOVES004TREE/);
assert.match(resultSource, /<CanonicalVegetationRerunReplay/);
assert.doesNotMatch(resultSource, /RerunViewport/);
assert.doesNotMatch(resultSource, /cacheRef|pumpRef|desiredRef/);
assert.doesNotMatch(resultSource, /LaboratoryRecordedClipPlayer|M48EvidenceModeRail/);
assert.doesNotMatch(resultSource, /assets\.tgs|<img/);
assert.match(rerunSource, /<RerunViewport/);
assert.match(rerunSource, /SOURCE POINTS/);
assert.match(rerunSource, /LOCAL SLAM/);
assert.match(rerunSource, /TGS COSTMAP/);
assert.match(rerunSource, /canonical-overlay\.rrd/);
assert.match(rerunSource, /ИСХ\. ТОЧКИ/);
assert.match(rerunSource, /ЛОК\. SLAM/);
assert.match(rerunSource, /resolveCanonicalLabReplay/);
assert.doesNotMatch(rerunSource, /canonical-overlay\.rrd/);
assert.match(rerunSource, /unifiedPerception: splitView/);
assert.doesNotMatch(rerunSource, /LaboratoryRecordedClipPlayer|LaboratoryMetricEvidenceScene/);
assert.match(resultSource, /point-aligned 3D semantics пока не запечатаны/);
assert.match(rerunSource, /value: "tgs", label: "TGS COSTMAP", disabled: true/);
assert.match(rerunSource, /value: "tgs", label: "TGS", disabled: true/);
assert.match(canonicalSource, /primary=\{mediaPane\}/);
assert.match(canonicalSource, /secondary=\{spatialPane/);
assert.match(canonicalSource, /missioncore\.canonical-recorded-lab-replay\/v1/);
assert.match(canonicalSource, /separatorLabel="Изменить размер VIDEO\/CAMERA и 3D\/PLAN"/);
assert.match(canonicalSource, /separatorLabel="Изменить размер видео\/камеры и 3D\/плана"/);
assert.match(resultSource, /linked canonical M4\.9 TGS evidence/);
assert.match(resultSource, /linkedTgsResultId/);
assert.match(benchmarkSource, /M48MaskComparisonVisual/);
@@ -149,7 +149,8 @@ second mounted source.
- [x] Harden the Control Station application boundary before A3: consume the
Design Guideline packages as the only visual platform, isolate the LAB
feature and CSS, add typed workspace contracts and enforce one-way imports
plus composition-size ratchets.
across the application layers. Line-count limits were subsequently removed:
they are not an architectural invariant.
- [x] Validate the real A2 generation:
`e30-review-pack-faec915a771022cceaf4ee62bece698afc8018d09b6b0ac7602157216fbb3686`
+12 -10
View File
@@ -249,8 +249,7 @@ vocabulary and executable contracts. They do not create a second runtime model.
- no upward imports from core/components into workspaces or App;
- no visual adapter imports from core;
- no local vendor icon library or direct Design Guideline source imports;
- LAB code and CSS remain outside the central workspace buckets;
- central composition files cannot silently return to their previous size.
- LAB code and CSS remain outside the central workspace buckets.
`test/laboratoryProductUi.test.mjs` additionally enforces the versioned LAB
report fields and shared result component across bounded LAB modules.
@@ -260,8 +259,9 @@ new unclassified experiment branches while allowing a declared bounded
experimental adapter. The gate protects core composition; it does not forbid a
novel research stack.
The line limits are ratchets, not quality targets. When a file reaches a limit,
split a feature; do not raise the limit to accommodate unrelated behavior.
File length is not an architectural boundary and is not enforced. Refactoring
is justified by ownership, cohesion, dependency direction, lifecycle or test
isolation, not by a line-count threshold.
From `apps/control-station` run:
@@ -273,13 +273,15 @@ npm run build
## Known bounded debt
- `App.tsx` remains a large shell orchestrator. Its current size is frozen by a
ratchet; future shell behavior must extract a controller/hook or panel module.
- `App.tsx` remains a large shell orchestrator. Future shell behavior should
preserve its orchestration ownership and extract modules only where they
acquire an independent responsibility or lifecycle.
- `Workspaces.tsx` still contains several established generic workspaces. New
domains must be separate modules, and existing ones may be extracted when
their behavior changes.
- `LaboratoryArchiveWorkspace.tsx` is now physically isolated but at its
ratchet. A3 receives its own component/module instead of growing that file.
domains should respect the existing dependency direction; extraction is a
design decision rather than a response to file length.
- `LaboratoryArchiveWorkspace.tsx` is physically isolated. Further LAB work
must preserve the feature boundary without imposing a size quota on the
implementation.
- Design Guideline dependencies are mutable local `file:` links until a
portable package/distribution decision is implemented.
+278 -12
View File
@@ -1,10 +1,12 @@
"""Native Rerun sidecar for immutable recorded laboratory evidence.
"""Native Rerun evidence for an immutable recorded laboratory replay.
The sidecar deliberately contains only evidence missing from the canonical K1
recording: camera video, semantic images and diagnostic 2D boxes. The base RRD
continues to own poses, point clouds and trajectory. Both files use the same
Rerun recording id and ``session_time`` timeline, so the upstream viewer is the
only playback clock and the only spatial renderer.
only playback clock and the only spatial renderer. The browser-facing LAB
artifact is a cached merge of both files, so the viewer opens one immutable
source instead of racing two independent HTTP receivers.
"""
from __future__ import annotations
@@ -31,9 +33,87 @@ from PIL import Image
APPLICATION_ID: Final = "nodedc_mission_core_recorded"
SESSION_TIMELINE: Final = "session_time"
RENDERER_VERSION: Final = "upstream-rerun-0.36.3-encoded-optimized-v4"
RENDERER_VERSION: Final = "upstream-rerun-0.36.3-encoded-optimized-v6"
REPLAY_RENDERER_VERSION: Final = "upstream-rerun-0.36.3-canonical-replay-v1"
MAX_SOURCE_BYTES: Final = 768 * 1024 * 1024
MAX_OVERLAY_BYTES: Final = 256 * 1024 * 1024
MAX_REPLAY_BYTES: Final = 1024 * 1024 * 1024
SEMANTIC_LABELS_RU: Final = {
"outside_valid_fov": "вне поля зрения",
"undefined": "не определено",
"person": "человек",
"bicycle": "велосипед",
"motorcycle": "мотоцикл",
"car": "автомобиль",
"heavy_vehicle": "тяжёлый транспорт",
"truck": "грузовик",
"bus": "автобус",
"building_structure": "здание или сооружение",
"building": "здание",
"wall": "стена",
"paved_road": "дорога с покрытием",
"asphalt": "асфальт",
"bikeway": "велодорожка",
"sidewalk": "тротуар",
"sidewalk_curb": "тротуар и бордюр",
"curb": "бордюр",
"ground_dirt": "грунт",
"soil": "почва",
"gravel": "гравий",
"cobble": "булыжник",
"grass_low_vegetation": "трава и низкая растительность",
"low_grass": "низкая трава",
"high_grass": "высокая трава",
"scenery_vegetation": "растительность",
"forest": "лес",
"bush": "куст",
"hedge": "живая изгородь",
"moss": "мох",
"leaves": "листва",
"crops": "посевы",
"tree_woody_vegetation": "деревья и древесная растительность",
"tree_crown": "крона дерева",
"tree_trunk": "ствол дерева",
"tree_root": "корни дерева",
"sky": "небо",
"water": "вода",
"snow": "снег",
"rock": "камень",
"static_obstacle": "неподвижное препятствие",
"obstacle": "препятствие",
"debris": "обломки",
"animal": "животное",
"rider": "водитель двухколёсного транспорта",
"traffic_cone": "дорожный конус",
"traffic_light": "светофор",
"street_light": "уличный фонарь",
"traffic_sign": "дорожный знак",
"misc_sign": "прочий знак",
"road_block": "перекрытие дороги",
"road_marking": "дорожная разметка",
"pedestrian_crossing": "пешеходный переход",
"boom_barrier": "шлагбаум",
"barrier_tape": "сигнальная лента",
"fence": "ограждение",
"guard_rail": "дорожное ограждение",
"bridge": "мост",
"tunnel": "тоннель",
"pole": "столб",
"rail_track": "железнодорожный путь",
"ego_vehicle": "носитель камеры",
"kick_scooter": "самокат",
"on_rails": "рельсовый транспорт",
"caravan": "автодом",
"trailer": "прицеп",
"heavy_machinery": "тяжёлая техника",
"military_vehicle": "военная техника",
"container": "контейнер",
"barrel": "бочка",
"pipe": "труба",
"wire": "провод",
"other_background": "прочий фон",
"outlier": "выброс",
}
class CanonicalLabOverlayError(RuntimeError):
@@ -47,8 +127,19 @@ class CanonicalLabOverlayArtifact:
sha256: str
@dataclass(frozen=True, slots=True)
class CanonicalLabReplayArtifact:
path: Path
byte_length: int
sha256: str
_render_lock = threading.Lock()
_memory_cache: dict[tuple[str, str, str], CanonicalLabOverlayArtifact] = {}
_replay_lock = threading.Lock()
_replay_memory_cache: dict[
tuple[str, str, str, str], CanonicalLabReplayArtifact
] = {}
def canonical_recording_id(path: Path) -> str:
@@ -172,6 +263,139 @@ def canonical_lab_overlay(
source.unlink(missing_ok=True)
def canonical_lab_replay(
base_recording_path: Path,
*,
base_generation_sha256: str,
overlay: CanonicalLabOverlayArtifact,
result_id: str,
recording_id: str,
cache_root: Path,
) -> CanonicalLabReplayArtifact:
"""Merge base geometry and LAB perception into one cached native RRD."""
base = base_recording_path.expanduser().resolve(strict=True)
cache = cache_root.expanduser().absolute()
if (
base.is_symlink()
or not base.is_file()
or not _is_sha256(base_generation_sha256)
or _sha256(base) != base_generation_sha256
or not _artifact_is_regular(overlay)
or not result_id.startswith("lab-v1-vegetation-shadow-")
or len(result_id) != len("lab-v1-vegetation-shadow-") + 64
or not recording_id
or len(recording_id) > 128
):
raise CanonicalLabOverlayError("canonical LAB replay identity is invalid")
key = (result_id, recording_id, base_generation_sha256, overlay.sha256)
cached = _replay_memory_cache.get(key)
if cached is not None and _replay_artifact_is_regular(cached):
return cached
with _replay_lock:
cached = _replay_memory_cache.get(key)
if cached is not None and _replay_artifact_is_regular(cached):
return cached
cache.mkdir(parents=True, exist_ok=True)
if cache.is_symlink() or not cache.is_dir():
raise CanonicalLabOverlayError("canonical LAB replay cache is invalid")
identity = hashlib.sha256(
"\0".join(
(
REPLAY_RENDERER_VERSION,
result_id,
recording_id,
base_generation_sha256,
overlay.sha256,
)
).encode()
).hexdigest()
output = cache / f"{identity}.replay.rrd"
sidecar = cache / f"{identity}.replay.json"
restored = _restore_cached_replay(
output,
sidecar,
result_id=result_id,
recording_id=recording_id,
base_generation_sha256=base_generation_sha256,
overlay_sha256=overlay.sha256,
)
if restored is not None:
_replay_memory_cache[key] = restored
return restored
temporary = cache / f".{identity}.{uuid4().hex}.replay.rrd"
try:
completed = subprocess.run(
[
sys.executable,
"-m",
"rerun",
"rrd",
"optimize",
"--profile",
"object-store",
"--max-size",
"4MiB",
"--max-rows",
"512",
"--num-pass",
"20",
str(base),
str(overlay.path),
"-o",
str(temporary),
],
check=False,
capture_output=True,
timeout=180,
)
if (
completed.returncode != 0
or not temporary.is_file()
or temporary.is_symlink()
or temporary.stat().st_size < 4
or temporary.stat().st_size > MAX_REPLAY_BYTES
):
raise CanonicalLabOverlayError(
f"canonical LAB replay merge failed: {completed.stderr[-1000:]!r}"
)
with temporary.open("rb") as stream:
if stream.read(4) != b"RRF2":
raise CanonicalLabOverlayError("canonical LAB replay merge is invalid")
if canonical_recording_id(temporary) != recording_id:
raise CanonicalLabOverlayError("canonical LAB replay identity changed")
stat = temporary.stat()
digest = _sha256(temporary)
os.chmod(temporary, 0o600)
os.replace(temporary, output)
_write_json_atomic(
sidecar,
{
"schema_version": "missioncore.canonical-lab-rerun-replay/v1",
"renderer_version": REPLAY_RENDERER_VERSION,
"result_id": result_id,
"recording_id": recording_id,
"base_generation_sha256": base_generation_sha256,
"overlay_sha256": overlay.sha256,
"byte_length": stat.st_size,
"sha256": digest,
},
)
artifact = CanonicalLabReplayArtifact(output, stat.st_size, digest)
_replay_memory_cache[key] = artifact
return artifact
except subprocess.TimeoutExpired as exc:
raise CanonicalLabOverlayError("canonical LAB replay merge timed out") from exc
except CanonicalLabOverlayError:
raise
except Exception as exc:
raise CanonicalLabOverlayError("failed to merge canonical LAB replay") from exc
finally:
temporary.unlink(missing_ok=True)
def _full_route(manifest: dict[str, Any]) -> dict[str, Any]:
route = manifest.get("route_full_review")
layers = route.get("layers") if isinstance(route, dict) else None
@@ -355,7 +579,7 @@ def _render_overlay(
rr.ClassDescription(
info=rr.AnnotationInfo(
id=int(item["class_id"]),
label=str(item["label"]),
label=_localized_semantic_label(str(item["label"])),
color=[*map(int, item["color_rgb"]), 255],
)
)
@@ -579,13 +803,13 @@ def semantic_component_boxes(
_sequence: int,
) -> tuple[list[list[int]], list[str]]:
labels = {
1: "person",
2: "bicycle",
3: "motorcycle",
4: "car",
5: "heavy vehicle",
13: "static obstacle",
14: "animal",
1: "человек",
2: "велосипед",
3: "мотоцикл",
4: "автомобиль",
5: "тяж. транспорт",
13: "препятствие",
14: "животное",
}
candidates: list[tuple[float, list[int], str]] = []
for class_id, label in labels.items():
@@ -595,13 +819,17 @@ def semantic_component_boxes(
)[:12]:
score = min(0.99, 0.5 + pixels / 20_000)
candidates.append(
(score, [left, top, right, bottom], f"{label} · {score:.0%} · semantic-only")
(score, [left, top, right, bottom], f"{label} · {score:.0%}")
)
candidates.sort(key=lambda row: (-row[0], row[1][1], row[1][0]))
selected = candidates[:32]
return [row[1] for row in selected], [row[2] for row in selected]
def _localized_semantic_label(label: str) -> str:
return SEMANTIC_LABELS_RU.get(label, label)
def _mask_component_boxes(
mask: np.ndarray,
class_id: int,
@@ -696,6 +924,30 @@ def _restore_cached(
return None
def _restore_cached_replay(
output: Path,
sidecar: Path,
**identity: str,
) -> CanonicalLabReplayArtifact | None:
try:
value = json.loads(sidecar.read_text(encoding="utf-8"))
stat = output.stat()
if (
output.is_symlink()
or sidecar.is_symlink()
or value.get("schema_version") != "missioncore.canonical-lab-rerun-replay/v1"
or value.get("renderer_version") != REPLAY_RENDERER_VERSION
or any(value.get(key) != expected for key, expected in identity.items())
or value.get("byte_length") != stat.st_size
or not _is_sha256(value.get("sha256"))
or _sha256(output) != value["sha256"]
):
return None
return CanonicalLabReplayArtifact(output, stat.st_size, value["sha256"])
except (OSError, ValueError, json.JSONDecodeError):
return None
def _artifact_is_regular(artifact: CanonicalLabOverlayArtifact) -> bool:
try:
with artifact.path.open("rb") as stream:
@@ -710,6 +962,20 @@ def _artifact_is_regular(artifact: CanonicalLabOverlayArtifact) -> bool:
return False
def _replay_artifact_is_regular(artifact: CanonicalLabReplayArtifact) -> bool:
try:
with artifact.path.open("rb") as stream:
magic = stream.read(4)
return (
not artifact.path.is_symlink()
and magic == b"RRF2"
and artifact.path.stat().st_size == artifact.byte_length
and _sha256(artifact.path) == artifact.sha256
)
except OSError:
return False
def _write_json_atomic(path: Path, value: dict[str, Any]) -> None:
temporary = path.with_name(f".{path.name}.{uuid4().hex}.tmp")
try:
+103
View File
@@ -24,8 +24,10 @@ from pydantic import BaseModel, ConfigDict, Field
from k1link.laboratory.canonical_rerun_overlay import (
CanonicalLabOverlayArtifact,
CanonicalLabOverlayError,
CanonicalLabReplayArtifact,
_mask_component_boxes,
canonical_lab_overlay,
canonical_lab_replay,
canonical_recording_id,
)
from k1link.laboratory.evidence_registry import LaboratoryEvidenceDefinition
@@ -352,6 +354,59 @@ def _build_vegetation_lab_router(
) from exc
return artifact
async def canonical_rerun_replay_artifact(
result_id: str,
*,
expected_base_generation_sha256: str | None = None,
) -> CanonicalLabReplayArtifact:
"""Return one immutable RRD containing base geometry and LAB perception."""
if (
canonical_recording_provider is None
or jobs_root is None
or rerun_overlay_cache_root is None
or ffmpeg_path is None
):
raise HTTPException(status_code=503, detail="Canonical LAB Rerun replay unavailable")
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
route, _ = _full_route_context(candidate, manifest)
recording = canonical_recording_provider(str(route["session_id"]))
if recording is None:
raise HTTPException(status_code=409, detail="Canonical spatial recording is not ready")
recording_path, generation_sha256 = recording
if (
expected_base_generation_sha256 is not None
and expected_base_generation_sha256 != generation_sha256
):
raise HTTPException(status_code=412, detail="Canonical recording generation changed")
try:
recording_id = await run_in_threadpool(canonical_recording_id, recording_path)
overlay = await run_in_threadpool(
canonical_lab_overlay,
candidate,
manifest,
recording_id=recording_id,
base_generation_sha256=generation_sha256,
jobs_root=jobs_root,
cache_root=rerun_overlay_cache_root,
ffmpeg_path=ffmpeg_path,
)
return await run_in_threadpool(
canonical_lab_replay,
recording_path,
base_generation_sha256=generation_sha256,
overlay=overlay,
result_id=result_id,
recording_id=recording_id,
cache_root=rerun_overlay_cache_root,
)
except CanonicalLabOverlayError as exc:
raise HTTPException(
status_code=503,
detail="Canonical LAB Rerun replay failed verification",
) from exc
def canonical_rerun_overlay_file_response(
artifact: CanonicalLabOverlayArtifact,
) -> FileResponse:
@@ -365,6 +420,19 @@ def _build_vegetation_lab_router(
},
)
def canonical_rerun_replay_file_response(
artifact: CanonicalLabReplayArtifact,
) -> FileResponse:
return FileResponse(
artifact.path,
media_type="application/vnd.rerun.rrd",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"ETag": f'"{artifact.sha256}"',
"X-Content-Type-Options": "nosniff",
},
)
@router.post("/{result_id}/canonical-overlay.rrd")
async def get_canonical_rerun_overlay(
result_id: str,
@@ -446,6 +514,41 @@ def _build_vegetation_lab_router(
raise HTTPException(status_code=412, detail="Canonical overlay generation changed")
return canonical_rerun_overlay_file_response(artifact)
@router.head("/{result_id}/canonical-replay.rrd")
async def describe_canonical_rerun_replay(
result_id: str,
base_generation: Annotated[str, Query(pattern=r"^[a-f0-9]{64}$")],
) -> Response:
"""Build once and describe the single RRD consumed by the LAB viewer."""
artifact = await canonical_rerun_replay_artifact(
result_id,
expected_base_generation_sha256=base_generation,
)
return Response(
status_code=200,
media_type="application/vnd.rerun.rrd",
headers={
"Cache-Control": "private, no-store",
"Content-Length": str(artifact.byte_length),
"ETag": f'"{artifact.sha256}"',
"X-Content-Type-Options": "nosniff",
"X-Rerun-Format": "RRF2",
},
)
@router.get("/{result_id}/canonical-replay.rrd")
async def stream_canonical_rerun_replay(
result_id: str,
generation: Annotated[str, Query(pattern=r"^[a-f0-9]{64}$")],
) -> FileResponse:
"""Stream the digest-bound merged replay through one native receiver."""
artifact = await canonical_rerun_replay_artifact(result_id)
if generation != artifact.sha256:
raise HTTPException(status_code=412, detail="Canonical replay generation changed")
return canonical_rerun_replay_file_response(artifact)
@router.get("/{result_id}/timeline")
def get_canonical_route_timeline(result_id: str) -> dict[str, object]:
candidate = _resolve_candidate(root_provider, definition, result_id)
+106
View File
@@ -22,11 +22,14 @@ import k1link.web.vegetation_shadow_lab_api as vegetation_api_module
from k1link.laboratory import LaboratoryEvidenceRegistry
from k1link.laboratory.canonical_rerun_overlay import (
CanonicalLabOverlayArtifact,
CanonicalLabReplayArtifact,
_artifact_is_regular,
_encoded_semantic_png,
_localized_semantic_label,
_optimize_overlay,
_semantic_palette,
_video_reference_timestamps,
canonical_lab_replay,
)
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
from k1link.laboratory.vegetation_policy_review import seal_vegetation_policy_review
@@ -51,6 +54,17 @@ def test_semantic_component_boxes_keep_distinct_objects_separate() -> None:
(18, 4, 24, 12, 48),
(3, 2, 8, 10, 40),
]
_, labels = canonical_overlay_module.semantic_component_boxes(mask, 0)
assert labels == [
"автомобиль · 50%",
"автомобиль · 50%",
]
def test_canonical_overlay_localizes_current_taxonomies() -> None:
assert _localized_semantic_label("high_grass") == "высокая трава"
assert _localized_semantic_label("tree_trunk") == "ствол дерева"
assert _localized_semantic_label("future_class") == "future_class"
def test_canonical_video_references_hold_only_missing_source_samples() -> None:
@@ -152,6 +166,59 @@ def test_canonical_overlay_compacts_chunks_before_cache_publication(
assert source.read_bytes() == b"RRF2-optimized"
def test_canonical_replay_merges_base_and_overlay_once(
tmp_path: Path,
monkeypatch,
) -> None:
base = tmp_path / "base.rrd"
base.write_bytes(b"RRF2-base")
overlay_path = tmp_path / "overlay.rrd"
overlay_path.write_bytes(b"RRF2-overlay")
overlay = CanonicalLabOverlayArtifact(
path=overlay_path,
byte_length=overlay_path.stat().st_size,
sha256=_sha256(overlay_path),
)
calls = 0
def optimize(command: list[str], **options: object) -> SimpleNamespace:
nonlocal calls
calls += 1
assert command[13:15] == [str(base), str(overlay_path)]
assert command[15] == "-o"
Path(command[16]).write_bytes(b"RRF2-merged")
assert options == {"check": False, "capture_output": True, "timeout": 180}
return SimpleNamespace(returncode=0, stderr=b"")
monkeypatch.setattr(canonical_overlay_module.subprocess, "run", optimize)
monkeypatch.setattr(
canonical_overlay_module,
"canonical_recording_id",
lambda _path: "recording-001",
)
result_id = f"lab-v1-vegetation-shadow-{'e' * 64}"
first = canonical_lab_replay(
base,
base_generation_sha256=_sha256(base),
overlay=overlay,
result_id=result_id,
recording_id="recording-001",
cache_root=tmp_path / "cache",
)
second = canonical_lab_replay(
base,
base_generation_sha256=_sha256(base),
overlay=overlay,
result_id=result_id,
recording_id="recording-001",
cache_root=tmp_path / "cache",
)
assert first == second
assert first.path.read_bytes() == b"RRF2-merged"
assert calls == 1
def test_canonical_overlay_get_is_generation_bound_and_range_streamable(
tmp_path: Path,
monkeypatch,
@@ -163,6 +230,8 @@ def test_canonical_overlay_get_is_generation_bound_and_range_streamable(
base.write_bytes(b"RRF2-base")
overlay = tmp_path / "overlay.rrd"
overlay.write_bytes(b"RRF2-overlay")
replay = tmp_path / "replay.rrd"
replay.write_bytes(b"RRF2-replay")
generation = "b" * 64
recording_id = "recording-001"
artifact = CanonicalLabOverlayArtifact(
@@ -170,6 +239,11 @@ def test_canonical_overlay_get_is_generation_bound_and_range_streamable(
byte_length=overlay.stat().st_size,
sha256=_sha256(overlay),
)
replay_artifact = CanonicalLabReplayArtifact(
path=replay,
byte_length=replay.stat().st_size,
sha256=_sha256(replay),
)
monkeypatch.setattr(
vegetation_api_module,
"_resolve_candidate",
@@ -195,6 +269,11 @@ def test_canonical_overlay_get_is_generation_bound_and_range_streamable(
"canonical_lab_overlay",
lambda *_args, **_kwargs: artifact,
)
monkeypatch.setattr(
vegetation_api_module,
"canonical_lab_replay",
lambda *_args, **_kwargs: replay_artifact,
)
ffmpeg = tmp_path / "ffmpeg"
ffmpeg.write_bytes(b"fixture")
ffmpeg.chmod(0o700)
@@ -262,6 +341,33 @@ def test_canonical_overlay_get_is_generation_bound_and_range_streamable(
)
assert stale_overlay.status_code == 412
replay_endpoint = (
f"/api/v1/laboratory/vegetation-shadow/{result_id}/canonical-replay.rrd"
)
replay_descriptor = client.head(
replay_endpoint,
params={"base_generation": generation},
)
assert replay_descriptor.status_code == 200
assert replay_descriptor.headers["content-length"] == str(replay_artifact.byte_length)
assert replay_descriptor.headers["etag"] == f'"{replay_artifact.sha256}"'
assert replay_descriptor.headers["x-rerun-format"] == "RRF2"
replay_response = client.get(
replay_endpoint,
params={"generation": replay_artifact.sha256},
headers={"Range": "bytes=0-3"},
)
assert replay_response.status_code == 206
assert replay_response.content == b"RRF2"
assert replay_response.headers["etag"] == f'"{replay_artifact.sha256}"'
stale_replay = client.get(
replay_endpoint,
params={"generation": "f" * 64},
)
assert stale_replay.status_code == 412
def test_route_playback_chunk_descriptor_seals_only_requested_binary_window() -> None:
points = np.arange(18, dtype="<f4").reshape(6, 3)