526 lines
15 KiB
JavaScript
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,
|
|
}));
|
|
});
|