feat(perception): integrate calibrated operator pipeline
Add calibrated K1 projection, recorded and near-live perception qualification, unified Rerun operator layers, bounded replay admission, audited viewer controls, worker experiments, and lab evidence.
This commit is contained in:
@@ -484,6 +484,31 @@ test("202 preparation polls through explicit phases and reveals launch only when
|
||||
assert.deepEqual(calls.map((entry) => entry[2]), [null, preparationEtag, preparationEtag]);
|
||||
});
|
||||
|
||||
test("initial replay restore is not mistaken for a stalled preparation poll", async () => {
|
||||
const sessionId = "session-20260716T205632Z";
|
||||
let requestSignal;
|
||||
const launch = await resolveObservationSessionReplay(sessionId, {
|
||||
signal: new AbortController().signal,
|
||||
requestTimeoutMs: 100,
|
||||
fetcher: async (_input, init) => {
|
||||
requestSignal = init.signal;
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(resolve, 150);
|
||||
init.signal.addEventListener("abort", () => {
|
||||
clearTimeout(timer);
|
||||
reject(new DOMException("cancelled", "AbortError"));
|
||||
}, { once: true });
|
||||
});
|
||||
return new Response(JSON.stringify(replay({ session_id: sessionId })), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
},
|
||||
});
|
||||
assert.equal(requestSignal.aborted, false);
|
||||
assert.equal(launch.sessionId, sessionId);
|
||||
});
|
||||
|
||||
test("status-ready response must match the preparation ETag before launch is accepted", async () => {
|
||||
const decoded = decodeObservationSessionPreparation(
|
||||
preparation({ state: "finalizing", progress: 0.95 }),
|
||||
@@ -704,6 +729,29 @@ test("switching saved sessions aborts only local polling and never cancels share
|
||||
assert.doesNotMatch(selectorSource, /cancelReplay|>\s*Отменить\s*</);
|
||||
});
|
||||
|
||||
test("recording preparation never presents phase heartbeats as fake percentages", async () => {
|
||||
const selectorSource = await readFile(
|
||||
new URL("../src/components/ObservationSessionSelect.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.match(selectorSource, /exporting:\s*["']Готовим операторскую сцену["']/);
|
||||
assert.doesNotMatch(selectorSource, /Math\.round\(progress\s*\*\s*100\)/);
|
||||
});
|
||||
|
||||
test("unified recorded AI view keeps the raw camera mounted but does not cover overlays", async () => {
|
||||
const workspaceSource = await readFile(
|
||||
new URL("../src/workspaces/Workspaces.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.match(workspaceSource, /hidden=\{pointCloudFocused \|\| unifiedPerception\}/);
|
||||
assert.match(
|
||||
workspaceSource,
|
||||
/presentedMediaSourceCount = unifiedPerception \? 0 : visibleMediaSources\.length/,
|
||||
);
|
||||
});
|
||||
|
||||
test("replay descriptor rejects path leaks, mismatched sessions and non-seekable data", () => {
|
||||
assert.throws(
|
||||
() => decodeObservationSessionReplay(replay({ source_url: "file:///private/session.rrd" })),
|
||||
@@ -776,7 +824,7 @@ test("replay decodes opaque recorded cameras and their same-origin fMP4 manifest
|
||||
);
|
||||
|
||||
const manifest = decodeObservationRecordedMediaManifest({
|
||||
schema_version: "missioncore.observation-recorded-media/v2",
|
||||
schema_version: "missioncore.observation-recorded-media/v3",
|
||||
source_id: source.id,
|
||||
generation_sha256: "c".repeat(64),
|
||||
byte_length: 3_266,
|
||||
@@ -788,21 +836,15 @@ test("replay decodes opaque recorded cameras and their same-origin fMP4 manifest
|
||||
timeline_start_seconds: 0.25,
|
||||
timeline_end_seconds: 20,
|
||||
media_type: 'video/mp4; codecs="avc1.640028"',
|
||||
init_url: source.manifest_url.replace("/manifest", "/epochs/1/init.mp4"),
|
||||
init_byte_length: 128,
|
||||
init_sha256: "a".repeat(64),
|
||||
segment_count: 12,
|
||||
segment_url_prefix: source.manifest_url.replace("/manifest", "/epochs/1/segments/"),
|
||||
segments: Array.from({ length: 12 }, (_, index) => ({
|
||||
sequence: index + 1,
|
||||
url: source.manifest_url.replace("/manifest", `/epochs/1/segments/${index + 1}.m4s`),
|
||||
byte_length: 256 + index,
|
||||
sha256: "b".repeat(64),
|
||||
})),
|
||||
byte_length: 3_266,
|
||||
stream_url: source.manifest_url.replace(
|
||||
"/manifest",
|
||||
`/epochs/1/recording.mp4?generation=${"c".repeat(64)}`,
|
||||
),
|
||||
}],
|
||||
}, decoded.mediaSources[0]);
|
||||
assert.equal(manifest.epochs[0].segmentCount, 12);
|
||||
assert.equal(manifest.epochs[0].segments.length, 12);
|
||||
assert.equal(manifest.epochs[0].byteLength, 3_266);
|
||||
assert.match(manifest.epochs[0].streamUrl, /recording\.mp4\?generation=/);
|
||||
assert.equal(manifest.epochs[0].timelineStartSeconds, 0.25);
|
||||
assert.throws(
|
||||
() => decodeObservationRecordedMediaManifest({
|
||||
@@ -821,7 +863,7 @@ test("replay decodes opaque recorded cameras and their same-origin fMP4 manifest
|
||||
);
|
||||
assert.throws(
|
||||
() => decodeObservationRecordedMediaManifest({
|
||||
schema_version: "missioncore.observation-recorded-media/v2",
|
||||
schema_version: "missioncore.observation-recorded-media/v3",
|
||||
source_id: source.id,
|
||||
generation_sha256: "c".repeat(64),
|
||||
byte_length: 3_267,
|
||||
@@ -834,6 +876,53 @@ test("replay decodes opaque recorded cameras and their same-origin fMP4 manifest
|
||||
);
|
||||
});
|
||||
|
||||
test("replay decodes a synchronized full-epoch perception video", () => {
|
||||
const sessionId = "20260720T065719Z_viewer_live";
|
||||
const resultId = `result-${"d".repeat(64)}`;
|
||||
const generation = "e".repeat(64);
|
||||
const source = {
|
||||
id: "recorded.perception.right",
|
||||
label: "Сегментация · камера right",
|
||||
modality: "video",
|
||||
manifest_url: `/api/v1/observation-sessions/${sessionId}/perception-media/${resultId}/manifest`,
|
||||
manifest_generation_sha256: generation,
|
||||
byte_length: 12_345_678,
|
||||
media_type: "video/mp4",
|
||||
timeline_start_seconds: 35.421857292,
|
||||
timeline_end_seconds: 484.144857292,
|
||||
seekable: true,
|
||||
synchronization: "host-arrival-best-effort",
|
||||
};
|
||||
const decoded = decodeObservationSessionReplay(replay({
|
||||
session_id: sessionId,
|
||||
timeline_end_seconds: 500,
|
||||
media_sources: [source],
|
||||
}));
|
||||
assert.equal(decoded.mediaSources[0].id, "recorded.perception.right");
|
||||
assert.equal(decoded.mediaSources[0].manifestUrl, source.manifest_url);
|
||||
|
||||
const manifest = decodeObservationRecordedMediaManifest({
|
||||
schema_version: "missioncore.observation-recorded-media/v3",
|
||||
source_id: source.id,
|
||||
generation_sha256: generation,
|
||||
byte_length: source.byte_length,
|
||||
timeline_start_seconds: source.timeline_start_seconds,
|
||||
timeline_end_seconds: source.timeline_end_seconds,
|
||||
synchronization: "host-arrival-best-effort",
|
||||
epochs: [{
|
||||
ordinal: 1,
|
||||
timeline_start_seconds: source.timeline_start_seconds,
|
||||
timeline_end_seconds: source.timeline_end_seconds,
|
||||
media_type: 'video/mp4; codecs="avc1.640028"',
|
||||
byte_length: source.byte_length,
|
||||
stream_url: `/api/v1/observation-sessions/${sessionId}/perception-media/${resultId}/recording.mp4?generation=${generation}`,
|
||||
}],
|
||||
}, decoded.mediaSources[0]);
|
||||
assert.equal(manifest.epochs.length, 1);
|
||||
assert.equal(manifest.epochs[0].timelineStartSeconds, source.timeline_start_seconds);
|
||||
assert.equal(manifest.epochs[0].timelineEndSeconds, source.timeline_end_seconds);
|
||||
});
|
||||
|
||||
test("recorded camera contracts reject foreign origins, path escapes and unknown fields", () => {
|
||||
const sessionId = "session-20260716T205632Z";
|
||||
const base = {
|
||||
@@ -888,7 +977,7 @@ test("recorded camera contracts reject foreign origins, path escapes and unknown
|
||||
}));
|
||||
assert.throws(
|
||||
() => decodeObservationRecordedMediaManifest({
|
||||
schema_version: "missioncore.observation-recorded-media/v2",
|
||||
schema_version: "missioncore.observation-recorded-media/v3",
|
||||
source_id: base.id,
|
||||
generation_sha256: "c".repeat(64),
|
||||
byte_length: 384,
|
||||
@@ -900,17 +989,8 @@ test("recorded camera contracts reject foreign origins, path escapes and unknown
|
||||
timeline_start_seconds: 0,
|
||||
timeline_end_seconds: 20,
|
||||
media_type: 'video/mp4; codecs="avc1.640028"',
|
||||
init_url: "file:///private/init.mp4",
|
||||
init_byte_length: 128,
|
||||
init_sha256: "a".repeat(64),
|
||||
segment_count: 1,
|
||||
segment_url_prefix: "file:///private/segments/",
|
||||
segments: [{
|
||||
sequence: 1,
|
||||
url: "file:///private/segments/1.m4s",
|
||||
byte_length: 256,
|
||||
sha256: "b".repeat(64),
|
||||
}],
|
||||
byte_length: 384,
|
||||
stream_url: "file:///private/recording.mp4",
|
||||
}],
|
||||
}, decoded.mediaSources[0]),
|
||||
/небезопасный API URL/,
|
||||
|
||||
@@ -269,7 +269,14 @@ test("recorded blueprint fetch is bounded, strict and sends only display setting
|
||||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||||
{
|
||||
origin: "http://127.0.0.1:5174",
|
||||
activeView: "perception",
|
||||
activeView: "perception3d",
|
||||
viewResetGeneration: 1,
|
||||
perceptionLayers: {
|
||||
enabled: true,
|
||||
detections2d: true,
|
||||
segmentation: false,
|
||||
cuboids3d: true,
|
||||
},
|
||||
fetcher: async (input, init) => {
|
||||
calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) });
|
||||
return new Response(payload, {
|
||||
@@ -292,7 +299,12 @@ test("recorded blueprint fetch is bounded, strict and sends only display setting
|
||||
point_size: 4.5,
|
||||
palette: "custom",
|
||||
custom_color: "#35d7c1",
|
||||
active_view: "perception",
|
||||
active_view: "perception3d",
|
||||
view_reset_generation: 1,
|
||||
unified_perception: true,
|
||||
show_detections_2d: true,
|
||||
show_segmentation: false,
|
||||
show_cuboids_3d: true,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
@@ -665,7 +677,7 @@ test("camera delivery rejects literal and encoded endpoint or credential leaks",
|
||||
"/api/preview?upstream=rtsp://camera.local/live",
|
||||
"/api/preview?upstream=rtsp%3A%2F%2Fcamera.local%2Flive",
|
||||
"/api/preview?upstream=rtsp%253A%252F%252Fcamera.local%252Flive",
|
||||
"/api/preview?endpoint=192.168.68.52:8554",
|
||||
"/api/preview?endpoint=192.0.2.52:8554",
|
||||
"/api/preview?endpoint=192%2E168%2E68%2E52",
|
||||
"/api/preview?password=not-for-the-browser",
|
||||
"/api/preview?%70%61%73%73%77%6f%72%64=not-for-the-browser",
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { after, before, test } from "node:test";
|
||||
|
||||
import { createServer } from "vite";
|
||||
|
||||
let server;
|
||||
let fetchVerifiedRecordedMediaArchive;
|
||||
let fetchRecordedMediaArchive;
|
||||
let recordedMediaPresentationState;
|
||||
let recordedMediaSeekableCoverage;
|
||||
let appendRecordedMediaBuffer;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
@@ -18,10 +16,9 @@ before(async () => {
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
({
|
||||
fetchVerifiedRecordedMediaArchive,
|
||||
fetchRecordedMediaArchive,
|
||||
recordedMediaPresentationState,
|
||||
recordedMediaSeekableCoverage,
|
||||
appendRecordedMediaBuffer,
|
||||
} = await server.ssrLoadModule("/src/components/RecordedFmp4Player.tsx"));
|
||||
});
|
||||
|
||||
@@ -29,41 +26,28 @@ after(async () => {
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
function digest(payload) {
|
||||
return createHash("sha256").update(payload).digest("hex");
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
function fixture({ byteLength = 36_000_000_000 } = {}) {
|
||||
const manifestUrl = "/api/v1/observation-sessions/session-1/media/camera-1/manifest";
|
||||
const init = Buffer.from("canonical-init");
|
||||
const first = Buffer.from("canonical-first-fragment");
|
||||
const second = Buffer.from("canonical-second-fragment");
|
||||
const generation = "a".repeat(64);
|
||||
const segmentPrefix = manifestUrl.replace("/manifest", "/epochs/1/segments/");
|
||||
const streamUrl = manifestUrl.replace(
|
||||
"/manifest",
|
||||
`/epochs/1/recording.mp4?generation=${generation}`,
|
||||
);
|
||||
const manifest = {
|
||||
schema_version: "missioncore.observation-recorded-media/v2",
|
||||
schema_version: "missioncore.observation-recorded-media/v3",
|
||||
source_id: "recorded.camera.camera-1",
|
||||
generation_sha256: generation,
|
||||
byte_length: init.byteLength + first.byteLength + second.byteLength,
|
||||
byte_length: byteLength,
|
||||
timeline_start_seconds: 0,
|
||||
timeline_end_seconds: 20,
|
||||
timeline_end_seconds: 36_000,
|
||||
synchronization: "host-arrival-best-effort",
|
||||
epochs: [{
|
||||
ordinal: 1,
|
||||
timeline_start_seconds: 0,
|
||||
timeline_end_seconds: 20,
|
||||
timeline_end_seconds: 36_000,
|
||||
media_type: 'video/mp4; codecs="avc1.640028"',
|
||||
init_url: manifestUrl.replace("/manifest", "/epochs/1/init.mp4"),
|
||||
init_byte_length: init.byteLength,
|
||||
init_sha256: digest(init),
|
||||
segment_count: 2,
|
||||
segment_url_prefix: segmentPrefix,
|
||||
segments: [first, second].map((payload, index) => ({
|
||||
sequence: index + 1,
|
||||
url: `${segmentPrefix}${index + 1}.m4s`,
|
||||
byte_length: payload.byteLength,
|
||||
sha256: digest(payload),
|
||||
})),
|
||||
byte_length: byteLength,
|
||||
stream_url: streamUrl,
|
||||
}],
|
||||
};
|
||||
const source = {
|
||||
@@ -72,14 +56,14 @@ function fixture() {
|
||||
modality: "video",
|
||||
manifestUrl,
|
||||
manifestGenerationSha256: generation,
|
||||
byteLength: manifest.byte_length,
|
||||
byteLength,
|
||||
mediaType: "video/mp4",
|
||||
timelineStartSeconds: 0,
|
||||
timelineEndSeconds: 20,
|
||||
timelineEndSeconds: 36_000,
|
||||
seekable: true,
|
||||
synchronization: "host-arrival-best-effort",
|
||||
};
|
||||
return { source, manifest, generation, init, first, second };
|
||||
return { source, manifest, generation, streamUrl };
|
||||
}
|
||||
|
||||
function jsonResponse(payload, generation) {
|
||||
@@ -92,145 +76,54 @@ function jsonResponse(payload, generation) {
|
||||
});
|
||||
}
|
||||
|
||||
function mediaResponse(payload, sha = digest(payload), length = payload.byteLength) {
|
||||
return new Response(payload, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Length": String(length),
|
||||
ETag: `"sha256:${sha}"`,
|
||||
test("multi-hour camera admission fetches only its compact generation-bound manifest", async () => {
|
||||
const { source, manifest, generation, streamUrl } = fixture();
|
||||
const requested = [];
|
||||
const archive = await fetchRecordedMediaArchive(source, {
|
||||
fetcher: async (input, request = {}) => {
|
||||
requested.push(String(input));
|
||||
assert.equal(new Headers(request.headers).get("If-Match"), `"sha256:${generation}"`);
|
||||
return jsonResponse(manifest, generation);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function deferred() {
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise((resolvePromise, rejectPromise) => {
|
||||
resolve = resolvePromise;
|
||||
reject = rejectPromise;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
async function flushAsync() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
test("recorded camera archive stays pending until every canonical byte is verified", async () => {
|
||||
const { source, manifest, generation, init, first, second } = fixture();
|
||||
const finalSegment = deferred();
|
||||
const requested = [];
|
||||
let manifestRequests = 0;
|
||||
const fetcher = async (input, request = {}) => {
|
||||
const url = String(input);
|
||||
requested.push(url);
|
||||
const headers = new Headers(request.headers);
|
||||
if (url === source.manifestUrl) {
|
||||
manifestRequests += 1;
|
||||
assert.equal(headers.get("If-Match"), `"sha256:${generation}"`);
|
||||
return jsonResponse(manifest, generation);
|
||||
}
|
||||
const epoch = manifest.epochs[0];
|
||||
if (url === epoch.init_url) {
|
||||
assert.equal(headers.get("If-Match"), `"sha256:${epoch.init_sha256}"`);
|
||||
return mediaResponse(init);
|
||||
}
|
||||
if (url === epoch.segments[0].url) return mediaResponse(first);
|
||||
if (url === epoch.segments[1].url) return finalSegment.promise;
|
||||
throw new Error(`Unexpected URL ${url}`);
|
||||
};
|
||||
|
||||
let settled = false;
|
||||
const archivePromise = fetchVerifiedRecordedMediaArchive(source, { fetcher })
|
||||
.then((archive) => {
|
||||
settled = true;
|
||||
return archive;
|
||||
});
|
||||
await flushAsync();
|
||||
|
||||
assert.equal(settled, false, "init and a partial segment prefix must not publish the camera");
|
||||
assert.deepEqual(requested.slice(0, 4), [
|
||||
source.manifestUrl,
|
||||
manifest.epochs[0].init_url,
|
||||
manifest.epochs[0].segments[0].url,
|
||||
manifest.epochs[0].segments[1].url,
|
||||
]);
|
||||
|
||||
finalSegment.resolve(mediaResponse(second));
|
||||
const archive = await archivePromise;
|
||||
assert.equal(manifestRequests, 2, "the immutable generation is revalidated after transfer");
|
||||
assert.equal(archive.byteLength, init.byteLength + first.byteLength + second.byteLength);
|
||||
assert.equal(archive.epochs[0].segments.length, 2);
|
||||
assert.deepEqual(requested, [source.manifestUrl]);
|
||||
assert.equal(archive.byteLength, 36_000_000_000);
|
||||
assert.equal(archive.manifest.epochs[0].streamUrl, streamUrl);
|
||||
});
|
||||
|
||||
test("first camera manifest request is launch-generation bound and rejects replacement", async () => {
|
||||
test("first camera manifest request rejects a replaced generation", async () => {
|
||||
const { source, manifest, generation } = fixture();
|
||||
const replacementGeneration = "d".repeat(64);
|
||||
let requests = 0;
|
||||
const fetcher = async (input, request = {}) => {
|
||||
requests += 1;
|
||||
assert.equal(String(input), source.manifestUrl);
|
||||
assert.equal(
|
||||
new Headers(request.headers).get("If-Match"),
|
||||
`"sha256:${generation}"`,
|
||||
);
|
||||
return jsonResponse(
|
||||
{ ...manifest, generation_sha256: replacementGeneration },
|
||||
replacementGeneration,
|
||||
);
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
fetchVerifiedRecordedMediaArchive(source, { fetcher }),
|
||||
fetchRecordedMediaArchive(source, {
|
||||
fetcher: async (_input, request = {}) => {
|
||||
assert.equal(new Headers(request.headers).get("If-Match"), `"sha256:${generation}"`);
|
||||
return jsonResponse(
|
||||
{ ...manifest, generation_sha256: replacementGeneration },
|
||||
replacementGeneration,
|
||||
);
|
||||
},
|
||||
}),
|
||||
/несовместим|заменён/,
|
||||
);
|
||||
assert.equal(requests, 1);
|
||||
});
|
||||
|
||||
test("recorded camera archive fails closed on truncation despite plausible headers", async () => {
|
||||
const { source, manifest, generation, init, first, second } = fixture();
|
||||
const fetcher = async (input) => {
|
||||
const url = String(input);
|
||||
if (url === source.manifestUrl) return jsonResponse(manifest, generation);
|
||||
const epoch = manifest.epochs[0];
|
||||
if (url === epoch.init_url) return mediaResponse(init);
|
||||
if (url === epoch.segments[0].url) return mediaResponse(first);
|
||||
if (url === epoch.segments[1].url) {
|
||||
return mediaResponse(second.subarray(0, second.byteLength - 1), digest(second), second.byteLength);
|
||||
}
|
||||
throw new Error(`Unexpected URL ${url}`);
|
||||
test("camera manifest fails closed when epoch bytes do not match launch bytes", async () => {
|
||||
const { source, manifest, generation } = fixture();
|
||||
const mismatched = {
|
||||
...manifest,
|
||||
epochs: [{ ...manifest.epochs[0], byte_length: manifest.byte_length - 1 }],
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
fetchVerifiedRecordedMediaArchive(source, { fetcher }),
|
||||
/усечён/,
|
||||
fetchRecordedMediaArchive(source, {
|
||||
fetcher: async () => jsonResponse(mismatched, generation),
|
||||
}),
|
||||
/не совпадает/,
|
||||
);
|
||||
});
|
||||
|
||||
test("recorded camera archive fails closed when bytes are replaced under an old ETag", async () => {
|
||||
const { source, manifest, generation, init, first, second } = fixture();
|
||||
const replacement = Buffer.from(second.map((value) => value ^ 0xff));
|
||||
assert.equal(replacement.byteLength, second.byteLength);
|
||||
const fetcher = async (input) => {
|
||||
const url = String(input);
|
||||
if (url === source.manifestUrl) return jsonResponse(manifest, generation);
|
||||
const epoch = manifest.epochs[0];
|
||||
if (url === epoch.init_url) return mediaResponse(init);
|
||||
if (url === epoch.segments[0].url) return mediaResponse(first);
|
||||
if (url === epoch.segments[1].url) {
|
||||
return mediaResponse(replacement, digest(second), second.byteLength);
|
||||
}
|
||||
throw new Error(`Unexpected URL ${url}`);
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
fetchVerifiedRecordedMediaArchive(source, { fetcher }),
|
||||
/SHA-256/,
|
||||
);
|
||||
});
|
||||
|
||||
test("camera presentation gate opens only for the completely appended selected epoch", () => {
|
||||
test("camera presentation gate opens only for the seekable selected epoch", () => {
|
||||
assert.equal(recordedMediaPresentationState("loading", null, "g1", false), "loading");
|
||||
assert.equal(recordedMediaPresentationState("ready", null, "g1", false), "loading");
|
||||
assert.equal(recordedMediaPresentationState("ready", "g2", "g1", false), "loading");
|
||||
@@ -251,40 +144,13 @@ test("decoded duration and seekable range cover the complete declared epoch", ()
|
||||
assert.equal(recordedMediaSeekableCoverage(20, 20, 20, 1, 1.01), false);
|
||||
});
|
||||
|
||||
test("aborting SourceBuffer append removes every temporary listener", async () => {
|
||||
const listeners = new Map();
|
||||
const sourceBuffer = {
|
||||
addEventListener(type, listener) {
|
||||
const bucket = listeners.get(type) ?? new Set();
|
||||
bucket.add(listener);
|
||||
listeners.set(type, bucket);
|
||||
},
|
||||
removeEventListener(type, listener) {
|
||||
listeners.get(type)?.delete(listener);
|
||||
},
|
||||
appendBuffer() {},
|
||||
};
|
||||
const abort = new AbortController();
|
||||
const pending = appendRecordedMediaBuffer(
|
||||
sourceBuffer,
|
||||
new ArrayBuffer(8),
|
||||
abort.signal,
|
||||
);
|
||||
assert.equal(listeners.get("updateend")?.size, 1);
|
||||
assert.equal(listeners.get("error")?.size, 1);
|
||||
abort.abort();
|
||||
await assert.rejects(pending, (error) => error?.name === "AbortError");
|
||||
assert.equal(listeners.get("updateend")?.size, 0);
|
||||
assert.equal(listeners.get("error")?.size, 0);
|
||||
});
|
||||
|
||||
test("verified camera archives remain immutable for safe player remount", async () => {
|
||||
test("recorded player range-streams and never builds a whole-video RAM Blob", async () => {
|
||||
const source = await readFile(
|
||||
new URL("../src/components/RecordedFmp4Player.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
assert.doesNotMatch(source, /\.init\s*=\s*new ArrayBuffer/);
|
||||
assert.doesNotMatch(source, /\.segments\.length\s*=\s*0/);
|
||||
assert.match(source, /video\.src\s*=\s*descriptor\.streamUrl/);
|
||||
assert.doesNotMatch(source, /new Blob\(|response\.arrayBuffer\(|SourceBuffer/);
|
||||
});
|
||||
|
||||
test("loading and error overlays fully conceal recorded camera pixels", async () => {
|
||||
@@ -301,3 +167,20 @@ test("loading and error overlays fully conceal recorded camera pixels", async ()
|
||||
/\.recorded-media-player__notice\s*\{[^}]*inset:\s*0;[^}]*background:\s*#070809/s,
|
||||
);
|
||||
});
|
||||
|
||||
test("point-cloud fullscreen keeps the admitted recorded camera worker mounted", async () => {
|
||||
const source = await readFile(
|
||||
new URL("../src/workspaces/Workspaces.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(source, /\{visibleMediaSources\.map\(\(source, index\) => \(/);
|
||||
assert.match(source, /hidden=\{pointCloudFocused \|\| unifiedPerception\}/);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/\{!pointCloudFocused \? visibleMediaSources\.map/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/\(pointCloudFocused \|\| !observationLayout\.visibleSourceIds\.has\(source\.id\)\)/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -78,34 +78,27 @@ test("any RRD or camera failure closes the complete recorded session", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("camera admission enforces independent 16/256/512 MiB limits before fetching", () => {
|
||||
test("camera admission keeps source scheduling bounded but has no duration or byte ceiling", () => {
|
||||
const {
|
||||
MAX_RECORDED_CAMERA_SOURCES,
|
||||
MAX_RECORDED_MEDIA_SOURCE_BYTES,
|
||||
MAX_RECORDED_SESSION_CAMERA_BYTES,
|
||||
recordedCameraDescriptorPreflight,
|
||||
} = admission;
|
||||
assert.equal(MAX_RECORDED_CAMERA_SOURCES, 16);
|
||||
assert.equal(MAX_RECORDED_MEDIA_SOURCE_BYTES, 256 * 1024 * 1024);
|
||||
assert.equal(MAX_RECORDED_SESSION_CAMERA_BYTES, 512 * 1024 * 1024);
|
||||
assert.equal(recordedCameraDescriptorPreflight(
|
||||
Array.from({ length: 2 }, (_, index) => ({
|
||||
id: `camera.${index}`,
|
||||
byteLength: MAX_RECORDED_MEDIA_SOURCE_BYTES,
|
||||
byteLength: 36_000_000_000,
|
||||
})),
|
||||
), "ready");
|
||||
assert.equal(recordedCameraDescriptorPreflight(
|
||||
Array.from({ length: 17 }, (_, index) => ({ id: `camera.${index}`, byteLength: 1 })),
|
||||
), "error");
|
||||
assert.equal(recordedCameraDescriptorPreflight([
|
||||
{ id: "camera.large", byteLength: MAX_RECORDED_MEDIA_SOURCE_BYTES + 1 },
|
||||
{ id: "camera.ten-hours", byteLength: 36_000_000_000 },
|
||||
]), "ready");
|
||||
assert.equal(recordedCameraDescriptorPreflight([
|
||||
{ id: "camera.invalid", byteLength: Number.MAX_SAFE_INTEGER + 1 },
|
||||
]), "error");
|
||||
assert.equal(recordedCameraDescriptorPreflight(
|
||||
Array.from({ length: 5 }, (_, index) => ({
|
||||
id: `camera.${index}`,
|
||||
byteLength: 110 * 1024 * 1024,
|
||||
})),
|
||||
), "error");
|
||||
assert.equal(recordedCameraDescriptorPreflight([
|
||||
{ id: "camera.duplicate", byteLength: 1 },
|
||||
{ id: "camera.duplicate", byteLength: 1 },
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import makeRerunRuntime from "../vendor/rerun-web-viewer-0.34.1/re_viewer.nodedc.js";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const packageRoot = resolve(root, "node_modules/@rerun-io/web-viewer");
|
||||
const vendorRoot = resolve(root, "vendor/rerun-web-viewer-0.34.1");
|
||||
|
||||
const sha256 = (path) =>
|
||||
createHash("sha256").update(readFileSync(path)).digest("hex");
|
||||
|
||||
test("NODE.DC Rerun runtime is the audited 0.34.1 zoom-to-cursor build", () => {
|
||||
const manifest = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"));
|
||||
assert.equal(manifest.version, "0.34.1");
|
||||
|
||||
const expectedWasm = "38d19bac06b7c3b8e549489469cf7c4a24f319ec953849e4656b5827a6c105bb";
|
||||
const expectedGlue = "0f7b76c9f24cbd8437021b5d37499894aeadc586183e422ebc82ef556d7b8339";
|
||||
|
||||
assert.equal(sha256(resolve(vendorRoot, "re_viewer_bg.nodedc.wasm")), expectedWasm);
|
||||
assert.equal(sha256(resolve(vendorRoot, "re_viewer.nodedc.js")), expectedGlue);
|
||||
assert.equal(sha256(resolve(packageRoot, "re_viewer_bg.wasm")), expectedWasm);
|
||||
assert.equal(sha256(resolve(packageRoot, "re_viewer.js")), expectedGlue);
|
||||
});
|
||||
|
||||
test("custom JavaScript glue references only exports present in its paired WASM", () => {
|
||||
const wasmPath = resolve(vendorRoot, "re_viewer_bg.nodedc.wasm");
|
||||
const gluePath = resolve(vendorRoot, "re_viewer.nodedc.js");
|
||||
const module = new WebAssembly.Module(readFileSync(wasmPath));
|
||||
const exports = new Set(WebAssembly.Module.exports(module).map(({ name }) => name));
|
||||
const imports = WebAssembly.Module.imports(module);
|
||||
const glue = readFileSync(gluePath, "utf8");
|
||||
const referencedExports = new Set(
|
||||
[...glue.matchAll(/\bwasm\.([A-Za-z_$][\w$]*)/g)].map((match) => match[1]),
|
||||
);
|
||||
const missingExports = [...referencedExports].filter((name) => !exports.has(name));
|
||||
|
||||
assert.equal(imports.length, 927);
|
||||
assert.equal(exports.size, 79);
|
||||
assert.deepEqual(missingExports, []);
|
||||
assert.match(glue, /export default function\(\)/);
|
||||
assert.match(glue, /if \(!wasm\) return;/);
|
||||
});
|
||||
|
||||
test("custom Rerun WASM initializes and grows its externref table", () => {
|
||||
const runtime = makeRerunRuntime();
|
||||
runtime.initSync({
|
||||
module: readFileSync(resolve(vendorRoot, "re_viewer_bg.nodedc.wasm")),
|
||||
});
|
||||
|
||||
assert.equal(typeof runtime.WebHandle, "function");
|
||||
runtime.deinit();
|
||||
});
|
||||
|
||||
test("source patch carries cursor pivot, minimum-radius handoff, and geometry tests", () => {
|
||||
const patch = readFileSync(resolve(vendorRoot, "NODEDC_ZOOM_TO_CURSOR.patch"), "utf8");
|
||||
|
||||
assert.match(patch, /fn pointer_ray_direction/);
|
||||
assert.match(patch, /fn zoom_orbit_towards_pointer/);
|
||||
assert.match(patch, /near_limit_hands_excess_zoom_to_cursor_directed_dolly/);
|
||||
assert.match(patch, /crossing_near_limit_preserves_unconsumed_scene_scaled_zoom/);
|
||||
assert.match(patch, /remaining_zoom_factor\.ln\(\) \* self\.speed/);
|
||||
assert.match(patch, /off_center_pointer_stays_on_the_same_view_ray/);
|
||||
});
|
||||
Reference in New Issue
Block a user