feat(observatory): add portable LAB V1 foundation

This commit is contained in:
DCCONSTRUCTIONS
2026-08-31 11:02:34 +03:00
parent d67e86176e
commit c9302c78c2
27 changed files with 7629 additions and 266 deletions
@@ -5,6 +5,7 @@ import { createServer } from "vite";
let server;
let fetchObservatoryLaboratorySetups;
let fetchObservatoryPortableLaboratorySetups;
let preflightObservatoryLaboratorySetup;
let ObservatoryLaboratorySetupContractError;
@@ -70,6 +71,76 @@ function setup() {
};
}
function portableSetup() {
return {
setup_id: "lab-v1-eomt-ddrnet-portable-v1",
display_name: "LAB V1 · EoMT Cityscapes Large 1024 + DDRNet-39",
description: "Проверка записанной K1-сессии моделями EoMT и DDRNet; только наблюдение.",
origin: "portable-definition",
source_requirements: {
plugin_id: "nodedc.device.xgrids-lixelkity-k1",
archive_id: "xgrids-k1.viewer-live.evidence",
required_modalities: ["point-cloud", "trajectory", "video"],
camera_source_id: "sensor.camera.right",
camera_semantic_channel_id: "camera.video.recorded",
recorded_media_type: 'video/mp4; codecs="avc1.641028"',
recorded_media_init_sha256: "1".repeat(64),
camera_width: 800,
camera_height: 600,
calibration_slot: "camera_1",
calibration_identity_sha256: "2".repeat(64),
exactly_one_media_epoch: true,
seekable: true,
},
run_definition: {
definition_id: "lab-v1-eomt-ddrnet-portable",
version: 1,
definition_sha256: "3".repeat(64),
result_schema: "missioncore.recorded-eomt-ddrnet-review/v2",
result_kind: "recorded-perception-qualification",
models: [
{
name: "EoMT Cityscapes Large 1024",
release_id: "eomt-cityscapes-large-1024-v1",
model_id: "tue-mps/cityscapes_semantic_eomt_large_1024",
architecture: "EomtForUniversalSegmentation",
},
{
name: "DDRNet-39",
release_id: "lab-v1-ddrnet-39-goose-fine-64-v1",
model_id: "goose-ddrnet-class-512",
architecture: "ddrnet_39",
},
],
},
source_compatibility: {
outcome: "pass",
compatible: true,
reason: "Запись соответствует требованиям EoMT + DDRNet.",
evidence: {
frame_count: 6830,
timeline_start_seconds: 0,
timeline_end_seconds: 819,
},
},
executor: {
contour_id: "worker-006",
state: "not-installed",
ready: false,
reason: "Immutable executor release не установлен.",
},
existing_results: [],
preflight: {
outcome: "blocked",
action: "blocked",
reason: "Вычислительный контур LAB V1 пока недоступен.",
submission_allowed: false,
existing_result_ids: [],
},
authority,
};
}
before(async () => {
server = await createServer({
appType: "custom",
@@ -78,6 +149,7 @@ before(async () => {
});
({
fetchObservatoryLaboratorySetups,
fetchObservatoryPortableLaboratorySetups,
preflightObservatoryLaboratorySetup,
ObservatoryLaboratorySetupContractError,
} = await server.ssrLoadModule("/src/core/observatory/laboratorySetups.ts"));
@@ -111,6 +183,104 @@ test("Observatory setup catalog keeps definition identity separate from executor
assert.equal(calls[0].init.method, "GET");
});
test("portable LAB V1 reports compatible source separately from unavailable Worker", async () => {
const calls = [];
const selected = (await fetchObservatoryPortableLaboratorySetups("source-a", {
fetcher: async (input, init) => {
calls.push({ input: String(input), init });
return new Response(JSON.stringify({
schema_version: "missioncore.observatory-portable-setup-catalog/v2",
source_session_id: "source-a",
setups: [portableSetup()],
authority,
}), { status: 200 });
},
})).setups[0];
assert.equal(selected.origin, "portable-definition");
assert.equal(selected.compatibility.compatible, true);
assert.equal(selected.executor.state, "not-installed");
assert.equal(selected.preflight.submissionAllowed, false);
assert.deepEqual(selected.runDefinition.models.map((model) => model.name), [
"EoMT Cityscapes Large 1024",
"DDRNet-39",
]);
assert.equal(
calls[0].input,
"/api/v1/observatory/portable-laboratory-setups?source_session_id=source-a",
);
let unexpectedNetworkCall = false;
const preflight = await preflightObservatoryLaboratorySetup("source-a", selected, {
fetcher: async () => {
unexpectedNetworkCall = true;
throw new Error("portable preflight must use its server projection");
},
});
assert.equal(unexpectedNetworkCall, false);
assert.equal(preflight.outcome, "blocked");
assert.equal(preflight.submissionAllowed, false);
assert.equal(preflight.checks[0].outcome, "pass");
assert.equal(preflight.checks[1].outcome, "fail");
});
test("portable LAB V1 rejects a premature enqueue projection", async () => {
const premature = portableSetup();
premature.executor = {
contour_id: "worker-006",
state: "ready",
ready: true,
reason: null,
};
premature.preflight = {
outcome: "ready",
action: "enqueue",
reason: "Запись готова к постановке в очередь.",
submission_allowed: true,
existing_result_ids: [],
};
await assert.rejects(
fetchObservatoryPortableLaboratorySetups("source-a", {
fetcher: async () => new Response(JSON.stringify({
schema_version: "missioncore.observatory-portable-setup-catalog/v2",
source_session_id: "source-a",
setups: [premature],
authority,
}), { status: 200 }),
}),
/значение изменилось|значение не поддерживается|постановка в очередь ещё не поддерживается/,
);
});
test("portable LAB V1 rejects an unbound existing result projection", async () => {
const spoofed = portableSetup();
spoofed.existing_results = [{
result_id: `legacy-vegetation-${"4".repeat(64)}`,
result_kind: "recorded-perception-qualification",
created_at_utc: "2026-08-30T18:16:00Z",
}];
spoofed.preflight = {
outcome: "existing",
action: "open-existing",
reason: "Результат якобы готов.",
submission_allowed: false,
existing_result_ids: [spoofed.existing_results[0].result_id],
};
await assert.rejects(
fetchObservatoryPortableLaboratorySetups("source-a", {
fetcher: async () => new Response(JSON.stringify({
schema_version: "missioncore.observatory-portable-setup-catalog/v2",
source_session_id: "source-a",
setups: [spoofed],
authority,
}), { status: 200 }),
}),
/значение изменилось|проверяемая привязка результата/,
);
});
test("Observatory preflight sends the exact selected definition and never submits a run", async () => {
const selected = (await fetchObservatoryLaboratorySetups("source-a", {
fetcher: async () => new Response(JSON.stringify({
@@ -239,7 +239,10 @@ test("Observatory keeps one compact selector axis without the obsolete setup det
workspace,
/"preemption-pending": \{[\s\S]*label: "Ждём подтверждения остановки"/,
);
assert.match(workspace, /succeeded: \{ label: "Готово"/);
assert.doesNotMatch(setupHook, /Promise\.allSettled/);
assert.match(setupHook, /publishSetupCatalog\(legacyCatalog/);
assert.match(workspace, /Worker установлен, запуск закрыт/);
assert.match(workspace, /succeeded: \{ label: "Вычислено · ждёт публикации"/);
assert.match(workspace, /failed: \{ label: "Ошибка расчёта"/);
assert.match(workspace, /"reconciliation-required": \{ label: "Нужна сверка"/);
assert.match(