NODEDC_MISSION_CORE/apps/control-station/test/polygonRunArchive.test.mjs

526 lines
15 KiB
JavaScript

import assert from "node:assert/strict";
import { after, before, test } from "node:test";
import { createServer } from "vite";
let server;
let decodePolygonRunCatalog;
let decodePolygonRunDetail;
let fetchPolygonRunCatalog;
let fetchPolygonRunDetail;
let resolvePolygonRunRoute;
let PolygonRunContractError;
let decodePolygonWorkerStatus;
let decodePolygonVehicleState;
let decodePolygonCommandAcceptance;
let fetchPolygonWorkerStatus;
let fetchPolygonVehicleState;
let sendPolygonRoverCommand;
let startPolygonWorker;
let stopPolygonWorker;
let PolygonWorkerContractError;
let workspaceById;
let workspacesForRoot;
let rootsForCapabilities;
before(async () => {
server = await createServer({
appType: "custom",
logLevel: "silent",
server: { middlewareMode: true },
});
({
decodePolygonRunCatalog,
decodePolygonRunDetail,
fetchPolygonRunCatalog,
fetchPolygonRunDetail,
resolvePolygonRunRoute,
PolygonRunContractError,
} = await server.ssrLoadModule("/src/core/polygon/runArchive.ts"));
({
decodePolygonWorkerStatus,
decodePolygonVehicleState,
decodePolygonCommandAcceptance,
fetchPolygonWorkerStatus,
fetchPolygonVehicleState,
sendPolygonRoverCommand,
startPolygonWorker,
stopPolygonWorker,
PolygonWorkerContractError,
} = await server.ssrLoadModule("/src/core/polygon/liveWorker.ts"));
({
workspaceById,
workspacesForRoot,
rootsForCapabilities,
} = await server.ssrLoadModule("/src/productModel.ts"));
});
after(async () => {
await server?.close();
});
function summary(overrides = {}) {
return {
run_id: "s1b-6cb1495-20260724t1535z",
episode_id: "episode-s1b-6cb1495-20260724t1535z",
kind: "simulation_closed_loop",
state: "completed",
created_at_utc: "2026-07-24T15:35:00Z",
started_at_utc: "2026-07-24T15:35:02Z",
ended_at_utc: "2026-07-24T15:35:05Z",
terminal_reason: "operator-stop-clean",
scenario_generation: "px4-v1.17.0-stock-rover-ackermann",
profile_generation: "stock-rover-lifecycle-v1",
mission_core_commit: "6cb1495a1234567890abcdef1234567890abcdef",
host_profile_id: "mission-gpu-s0",
reproducibility_tier: "R1",
clock_domain: "gazebo:/clock",
revision: 5,
provider_ids: ["px4-autopilot", "gazebo"],
artifact_count: 1,
...overrides,
};
}
function catalog(overrides = {}) {
return {
schema_version: "missioncore.polygon-run-catalog/v1",
access: "read-only",
items: [summary()],
total: 1,
limitations: ["Qualification evidence only."],
...overrides,
};
}
function detail(overrides = {}) {
return {
schema_version: "missioncore.polygon-run-detail/v1",
access: "read-only",
run: {
...summary(),
scenario_sha256: "a".repeat(64),
profile_sha256: "b".repeat(64),
host_profile_sha256: "c".repeat(64),
seed: 42,
parent_run_id: null,
providers: [
{
identifier: "px4-autopilot",
version: "v1.17.0",
revision: "v1.17.0",
digest: null,
},
{
identifier: "gazebo",
version: "harmonic",
revision: "8.9.0",
digest: null,
},
],
authority: {
generation: 1,
command_ttl_max_ns: 250_000_000,
heartbeat_timeout_monotonic_ns: 500_000_000,
simulation_or_shadow_only: true,
actuator_authority: false,
navigation_or_safety_accepted: false,
direct_actuator_setpoints_allowed: false,
},
},
events: [
{
schema_version: "missioncore.qualification-event/v1",
run_id: "s1b-6cb1495-20260724t1535z",
sequence: 5,
event_type: "lifecycle.state-changed",
observed_at_utc: "2026-07-24T15:35:05Z",
host_monotonic_ns: 5,
sim_time_ns: 2_000_000,
payload: {
from: "stopping",
to: "completed",
reason: "operator-stop-clean",
},
},
],
events_total: 1,
events_truncated: false,
commands: {
count: 0,
content_exposed: false,
},
artifacts: [
{
artifact_id: "provider-log-index",
kind: "log-index",
relative_path: "provider-runtime/processes.json",
sha256: "a".repeat(64),
byte_length: 512,
source_of_record: true,
sequence: 1,
},
],
limitations: ["Qualification evidence only."],
...overrides,
};
}
function jsonResponse(payload, status = 200) {
return new Response(JSON.stringify(payload), {
status,
headers: { "content-type": "application/json" },
});
}
function workerStatus(overrides = {}) {
return {
schema_version: "missioncore.simulation-worker-status/v2",
worker_id: "mission-gpu-s1",
transport: "unix",
mode: "simulation",
available: true,
control_available: true,
active_run_id: "s1c-6cb1495-20260724t180000z-aabbcc",
run_state: "running",
active_provider_ids: ["micro-xrce-dds-agent", "px4-gazebo-stock-rover"],
provider_profile: {
schema_version: "missioncore.simulation-provider-profile/v1",
profile_id: "stock-rover-gazebo-px4-s1d",
providers: [
{
provider_id: "gazebo",
roles: ["world", "physics", "state", "sensor"],
capabilities: [
"clock.simulation",
"state.vehicle-pose",
"truth.ground-truth",
"sensor.virtual",
],
},
{
provider_id: "px4-ros2-offboard",
roles: ["controller"],
capabilities: ["command.rover-speed-steering/v1"],
},
{
provider_id: "micro-xrce-dds-agent",
roles: ["transport"],
capabilities: ["transport.ros2"],
},
],
clock: {
provider_id: "gazebo",
domain: "gazebo:/clock",
unit: "nanoseconds",
mode: "simulation",
},
control_profiles: ["rover-speed-steering/v1"],
canonical_frames: {
world: "map_enu",
body: "base_link_flu",
},
},
isolation: {
network: "loopback-only-netns",
process_identity: "missioncore",
artifact_policy: "d-only",
},
authority: {
scope: "virtual-only",
actuator_authority: false,
direct_actuator_setpoints_allowed: false,
},
...overrides,
};
}
function vehicleState(overrides = {}) {
return {
schema_version: "missioncore.vehicle-state/v1",
run_id: "s1c-6cb1495-20260724t180000z-aabbcc",
sequence: 7,
observed_at_utc: "2026-07-24T18:00:07Z",
host_monotonic_ns: 123_456_789,
sim_time_ns: 7_000_000_000,
frame_id: "map_enu",
child_frame_id: "base_link_flu",
pose: {
position_m: { x: 1.25, y: -0.5, z: 0.18 },
orientation_xyzw: { x: 0, y: 0, z: 0.1, w: 0.995 },
},
source: {
provider: "gazebo",
topic: "/world/rover/dynamic_pose/info",
signal: "ground-truth",
quality: "diagnostic",
},
safety: {
scope: "virtual-only",
actuator_authority: false,
navigation_or_safety_accepted: false,
},
...overrides,
};
}
function commandAcceptance(overrides = {}) {
return {
schema_version: "missioncore.command-acceptance/v2",
run_id: "s1c-6cb1495-20260724t180000z-aabbcc",
command_id: "cmd-aabbcc",
sequence: 1,
issued_at_sim_ns: 7_000_000_000,
valid_until_sim_ns: 7_250_000_000,
speed_mps: 1,
steering_normalized: -0.55,
authority_scope: "virtual-only",
delivery: {
provider_id: "px4-ros2-offboard",
control_profile: "rover-speed-steering/v1",
accepted: true,
controller_ready: true,
ttl_expired_count: 0,
diagnostics: {
armed: true,
offboard: true,
},
},
...overrides,
};
}
test("Polygon is a worker-gated product root and keeps a compatible direct route", () => {
assert.deepEqual(resolvePolygonRunRoute("?workspace=polygon-run"), {
active: true,
runId: null,
error: null,
});
assert.deepEqual(
resolvePolygonRunRoute("?workspace=polygon-run&run=s1b-6cb1495-20260724t1535z"),
{
active: true,
runId: "s1b-6cb1495-20260724t1535z",
error: null,
},
);
assert.equal(resolvePolygonRunRoute("?workspace=missions").active, false);
assert.match(
resolvePolygonRunRoute("?workspace=polygon-run&run=../../etc").error,
/некорректный/,
);
assert.equal(workspaceById("polygon-run").root, "polygon");
assert.equal(
workspacesForRoot("polygon").some(({ id }) => id === "polygon-run"),
true,
);
assert.equal(
workspacesForRoot("system").some(({ id }) => id === "polygon-run"),
false,
);
assert.equal(
rootsForCapabilities({ polygonWorkerAvailable: false })
.some(({ id }) => id === "polygon"),
false,
);
assert.equal(
rootsForCapabilities({ polygonWorkerAvailable: true })
.some(({ id }) => id === "polygon"),
true,
);
});
test("Polygon contracts decode path-free evidence and preserve the safety boundary", () => {
const decodedCatalog = decodePolygonRunCatalog(catalog());
const decodedDetail = decodePolygonRunDetail(detail());
assert.equal(decodedCatalog.items[0].state, "completed");
assert.equal(decodedDetail.run.authority.actuatorAuthority, false);
assert.equal(decodedDetail.run.authority.navigationOrSafetyAccepted, false);
assert.equal(decodedDetail.commandCount, 0);
assert.equal(decodedDetail.artifacts[0].relativePath, "provider-runtime/processes.json");
assert.equal("content" in decodedDetail, false);
});
test("Polygon contracts fail closed on command content, paths and unknown fields", () => {
assert.throws(
() => decodePolygonRunDetail(detail({
commands: { count: 1, content_exposed: true },
})),
PolygonRunContractError,
);
assert.throws(
() => decodePolygonRunDetail(detail({
artifacts: [{
...detail().artifacts[0],
relative_path: "/mnt/d/private/processes.json",
}],
})),
PolygonRunContractError,
);
assert.throws(
() => decodePolygonRunCatalog(catalog({ unexpected: true })),
PolygonRunContractError,
);
});
test("Polygon fetchers use bounded same-origin GET endpoints", async () => {
const calls = [];
const fetcher = async (url, init) => {
calls.push({ url, init });
return String(url).includes("event_limit") ? jsonResponse(detail()) : jsonResponse(catalog());
};
await fetchPolygonRunCatalog({ limit: 20, fetcher });
await fetchPolygonRunDetail("s1b-6cb1495-20260724t1535z", {
eventLimit: 200,
fetcher,
});
assert.deepEqual(calls.map(({ url }) => url), [
"/api/v1/polygon/runs?limit=20",
"/api/v1/polygon/runs/s1b-6cb1495-20260724t1535z?event_limit=200",
]);
assert.ok(calls.every(({ init }) => init.method === "GET"));
assert.ok(calls.every(({ init }) => init.headers.Accept === "application/json"));
});
test("Polygon Live decodes only D-only virtual diagnostic state", () => {
const status = decodePolygonWorkerStatus(workerStatus());
const live = decodePolygonVehicleState(vehicleState());
const command = decodePolygonCommandAcceptance(commandAcceptance());
assert.equal(status.available, true);
assert.equal(status.activeRunId, live.runId);
assert.equal(status.isolation.artifactPolicy, "d-only");
assert.deepEqual(live.position, { x: 1.25, y: -0.5, z: 0.18 });
assert.equal(command.controllerReady, true);
assert.equal(command.deliveryProvider, "px4-ros2-offboard");
assert.equal(command.steeringNormalized, -0.55);
assert.throws(
() => decodePolygonWorkerStatus(workerStatus({
authority: {
...workerStatus().authority,
actuator_authority: true,
},
})),
PolygonWorkerContractError,
);
assert.throws(
() => decodePolygonVehicleState(vehicleState({
source: {
...vehicleState().source,
quality: "accepted",
},
})),
PolygonWorkerContractError,
);
assert.throws(
() => decodePolygonCommandAcceptance(commandAcceptance({
authority_scope: "physical",
})),
PolygonWorkerContractError,
);
});
test("Polygon Live accepts an Unreal provider behind the canonical contract", () => {
const unrealStatus = workerStatus({
provider_profile: {
...workerStatus().provider_profile,
profile_id: "unreal-native-rover-v1",
providers: [
{
provider_id: "unreal-native",
roles: ["world", "physics", "state", "sensor"],
capabilities: [
"clock.simulation",
"state.vehicle-pose",
"truth.ground-truth",
"sensor.virtual",
],
},
{
provider_id: "unreal-direct-control",
roles: ["controller"],
capabilities: ["command.rover-speed-steering/v1"],
},
],
clock: {
provider_id: "unreal-native",
domain: "unreal:fixed-step",
unit: "nanoseconds",
mode: "simulation",
},
},
});
const status = decodePolygonWorkerStatus(unrealStatus);
const live = decodePolygonVehicleState(vehicleState({
source: {
provider: "unreal-native",
topic: "missioncore/vehicle-state",
signal: "ground-truth",
quality: "diagnostic",
},
}));
const command = decodePolygonCommandAcceptance(commandAcceptance({
delivery: {
provider_id: "unreal-direct-control",
control_profile: "rover-speed-steering/v1",
accepted: true,
controller_ready: true,
ttl_expired_count: 0,
diagnostics: { fixed_step: true },
},
}));
assert.equal(status.providerProfile.clock.domain, "unreal:fixed-step");
assert.equal(live.sourceProvider, "unreal-native");
assert.equal(command.deliveryProvider, "unreal-direct-control");
});
test("Polygon Live uses same-origin gateway and idempotent lifecycle requests", async () => {
const calls = [];
const stopped = workerStatus({
active_run_id: null,
run_state: null,
active_provider_ids: [],
});
const fetcher = async (url, init) => {
calls.push({ url: String(url), init });
if (String(url).endsWith("/live")) return jsonResponse(vehicleState());
if (String(url).endsWith("/commands")) return jsonResponse(commandAcceptance());
if (String(url).endsWith("/stop")) return jsonResponse(stopped);
return jsonResponse(workerStatus());
};
await fetchPolygonWorkerStatus({ fetcher });
await fetchPolygonVehicleState({ fetcher });
await startPolygonWorker({ idempotencyKey: "start-001", fetcher });
await sendPolygonRoverCommand("s1c-6cb1495-20260724t180000z-aabbcc", {
speedMps: 1,
steeringNormalized: -0.55,
idempotencyKey: "command-001",
fetcher,
});
await stopPolygonWorker("s1c-6cb1495-20260724t180000z-aabbcc", {
idempotencyKey: "stop-001",
fetcher,
});
assert.deepEqual(calls.map(({ url }) => url), [
"/api/v1/polygon/worker",
"/api/v1/polygon/worker/live",
"/api/v1/polygon/worker/runs",
"/api/v1/polygon/worker/runs/s1c-6cb1495-20260724t180000z-aabbcc/commands",
"/api/v1/polygon/worker/runs/s1c-6cb1495-20260724t180000z-aabbcc/stop",
]);
assert.equal(calls[2].init.headers["Idempotency-Key"], "start-001");
assert.equal(calls[3].init.headers["Idempotency-Key"], "command-001");
assert.equal(calls[4].init.headers["Idempotency-Key"], "stop-001");
assert.equal(calls[2].init.body, JSON.stringify({ scenario_id: "stock-rover-ackermann" }));
assert.equal(calls[3].init.body, JSON.stringify({
speed_mps: 1,
steering_normalized: -0.55,
}));
});