feat(simulation): establish polygon S0 qualification
This commit is contained in:
parent
7fba39a629
commit
81761067e9
26
README.md
26
README.md
|
|
@ -121,6 +121,28 @@ performance, direct worker transport and bounded live fan-out remain open. See
|
||||||
[ADR 0014](docs/adr/0014-bounded-external-perception-worker.md)
|
[ADR 0014](docs/adr/0014-bounded-external-perception-worker.md)
|
||||||
and the [external worker contract](docs/10_EXTERNAL_PERCEPTION_WORKER.md).
|
and the [external worker contract](docs/10_EXTERNAL_PERCEPTION_WORKER.md).
|
||||||
|
|
||||||
|
## Simulation Polygon
|
||||||
|
|
||||||
|
Mission Core now has a parallel Polygon product branch for reproducible
|
||||||
|
autonomy qualification. Mission Core owns scenarios, qualification-run
|
||||||
|
lifecycle, authority, canonical contracts, provenance and reports; Gazebo, PX4
|
||||||
|
SITL, ROS 2, Nav2 and viewers remain replaceable providers. Simulation, replay,
|
||||||
|
digital twin, HIL and physical shadow keep distinct causal run kinds.
|
||||||
|
|
||||||
|
The current implementation is SIM S0 preflight only. Its strict candidate
|
||||||
|
profile fixes D-only worker placement, loopback ports, process ownership,
|
||||||
|
Gazebo simulation time and disabled real/direct actuator authority. The doctor
|
||||||
|
does not install or launch anything and cannot report `GO` without
|
||||||
|
digest-bound target-worker evidence:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run missioncore-sim s0 doctor --json
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [Polygon product/SRS](docs/12_SIMULATION_POLYGON_PRODUCT_AND_SRS.md),
|
||||||
|
[ADR 0015](docs/adr/0015-simulation-polygon-qualification-boundary.md) and the
|
||||||
|
[SIM S0 worker runbook](docs/runbooks/SIM_S0_AI_WORKER.md).
|
||||||
|
|
||||||
## Mission Core Control Station and visualization adapters
|
## Mission Core Control Station and visualization adapters
|
||||||
|
|
||||||
The browser application is the universal Mission Core Control Station rather than
|
The browser application is the universal Mission Core Control Station rather than
|
||||||
|
|
@ -219,6 +241,10 @@ standalone release install. The frontend consumes sibling `file:` packages from
|
||||||
revision or content hash. Publishing/vendoring those packages or enforcing an
|
revision or content hash. Publishing/vendoring those packages or enforcing an
|
||||||
immutable donor revision remains a packaging and CI prerequisite.
|
immutable donor revision remains a packaging and CI prerequisite.
|
||||||
|
|
||||||
|
Polygon is planned as a seventh section, but UI delivery is gated on the
|
||||||
|
accepted clock/frame/authority contracts and a proven S1 backend lifecycle with
|
||||||
|
persisted run history. It is not present in the current shell.
|
||||||
|
|
||||||
The Observation spatial workspace embeds the open-source Rerun Web Viewer
|
The Observation spatial workspace embeds the open-source Rerun Web Viewer
|
||||||
inside the Mission Core shell. It can open a compatible RRD over same-origin
|
inside the Mission Core shell. It can open a compatible RRD over same-origin
|
||||||
HTTP or a Rerun gRPC/proxy source such as
|
HTTP or a Rerun gRPC/proxy source such as
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
||||||
| Plugin isolation | GO (laboratory control plane) — vendor backend/frontend and optional scene controls are plugin-owned; manifest/runtime descriptor parity, versioned handshake, lifecycle health and transport correlation fail closed while execution remains in-process |
|
| Plugin isolation | GO (laboratory control plane) — vendor backend/frontend and optional scene controls are plugin-owned; manifest/runtime descriptor parity, versioned handshake, lifecycle health and transport correlation fail closed while execution remains in-process |
|
||||||
| K1 application control | GO (physical staged cycle) — after fixing the PCAP-proven `sint64` time field, one explicit UI launch completed all 14 canonical operations on one control session, reached live `SCANNING + project + init_ready`, displayed real points, then one explicit STOP returned K1 to unbound `READY`. No retry or fallback command was sent. Native-project reuse through LixelGO/USB remains an independent verification |
|
| K1 application control | GO (physical staged cycle) — after fixing the PCAP-proven `sint64` time field, one explicit UI launch completed all 14 canonical operations on one control session, reached live `SCANNING + project + init_ready`, displayed real points, then one explicit STOP returned K1 to unbound `READY`. No retry or fallback command was sent. Native-project reuse through LixelGO/USB remains an independent verification |
|
||||||
| Stage 8 product storage | PAUSE — retention, replication, encryption, capacity monitoring and long-run browser/WASM stress remain deployment gates |
|
| Stage 8 product storage | PAUSE — retention, replication, encryption, capacity monitoring and long-run browser/WASM stress remain deployment gates |
|
||||||
|
| Simulation Polygon | STARTED, NOT ACCEPTED — product/SRS and ADR 0015 define a separate qualification boundary; SIM S0 has a strict candidate D-only/version/clock/port/process profile and read-only doctor. No target-worker evidence, rover run, navigation/safety acceptance or real actuator authority exists |
|
||||||
|
|
||||||
USB project copying remains optional ground truth rather than a blocker for the
|
USB project copying remains optional ground truth rather than a blocker for the
|
||||||
now-verified network path. Owner-operated LixelGO traffic verifies the MQTT
|
now-verified network path. Owner-operated LixelGO traffic verifies the MQTT
|
||||||
|
|
@ -121,6 +122,32 @@ control is plugin-owned. A plugin-commanded acquisition sends one canonical STOP
|
||||||
and seals locally after protocol-reported standby; an operator-manual acquisition
|
and seals locally after protocol-reported standby; an operator-manual acquisition
|
||||||
still stops and seals only local reception and reports scanner state as unknown.
|
still stops and seals only local reception and reports scanner state as unknown.
|
||||||
|
|
||||||
|
## Parallel branch — Simulation Polygon
|
||||||
|
|
||||||
|
The Polygon branch follows
|
||||||
|
[`docs/12_SIMULATION_POLYGON_PRODUCT_AND_SRS.md`](12_SIMULATION_POLYGON_PRODUCT_AND_SRS.md)
|
||||||
|
and
|
||||||
|
[`ADR 0015`](adr/0015-simulation-polygon-qualification-boundary.md).
|
||||||
|
It does not reorder or weaken the K1 physical-evidence gates in this document.
|
||||||
|
Its first gate is SIM S0:
|
||||||
|
|
||||||
|
- candidate Windows 11/WSL2 Ubuntu 24.04 worker profile;
|
||||||
|
- all mutable runtime bytes physically on D;
|
||||||
|
- ROS 2 Jazzy, Gazebo Harmonic, PX4 v1.17.0, matching `px4_msgs`,
|
||||||
|
Micro XRCE-DDS and Nav2 compatibility;
|
||||||
|
- authoritative Gazebo `/clock`;
|
||||||
|
- loopback port and deterministic process ownership;
|
||||||
|
- target-worker rover/telemetry/pause/step/stop/resource evidence;
|
||||||
|
- explicit `GO`, `INCOMPLETE` or `BLOCKED` result.
|
||||||
|
|
||||||
|
The repository doctor is side-effect free:
|
||||||
|
|
||||||
|
```text
|
||||||
|
uv run missioncore-sim s0 doctor --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Without admitted target-worker evidence its correct result is `INCOMPLETE`.
|
||||||
|
|
||||||
## Stage 0 — repository and host baseline
|
## Stage 0 — repository and host baseline
|
||||||
|
|
||||||
Deliverables:
|
Deliverables:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,535 @@
|
||||||
|
# Mission Core Polygon: product definition and system requirements
|
||||||
|
|
||||||
|
Status: canonical working SRS, 2026-07-24.
|
||||||
|
|
||||||
|
Ops source of truth:
|
||||||
|
|
||||||
|
- [MISSIONCOR-38](https://ops.nodedc.ru/nodedc/browse/MISSIONCOR-38) is the
|
||||||
|
product epic and original simulation-branch scope.
|
||||||
|
- [MISSIONCOR-39](https://ops.nodedc.ru/nodedc/browse/MISSIONCOR-39) is the
|
||||||
|
canonical, changeable architecture plan.
|
||||||
|
- [MISSIONCOR-40](https://ops.nodedc.ru/nodedc/browse/MISSIONCOR-40) is the
|
||||||
|
active SIM S0 implementation and qualification gate.
|
||||||
|
|
||||||
|
This document and ADR 0015 are the repository truth. A material change to run
|
||||||
|
kinds, authority, clocks, frames, source-of-record, provider boundaries or phase
|
||||||
|
gates must update the ADR/SRS and MISSIONCOR-39 together.
|
||||||
|
|
||||||
|
## 1. Product thesis
|
||||||
|
|
||||||
|
Polygon is Mission Core's autonomy qualification and assurance layer. It is not
|
||||||
|
a simulator embedded for visual effect and it is not a second observation
|
||||||
|
viewer.
|
||||||
|
|
||||||
|
Polygon makes one product promise:
|
||||||
|
|
||||||
|
> Given a versioned scenario and qualification profile, Mission Core can run or
|
||||||
|
> replay the autonomy stack, preserve exact provenance and evidence, explain
|
||||||
|
> each control/safety decision and compare outcomes without granting undeclared
|
||||||
|
> authority.
|
||||||
|
|
||||||
|
The product value appears in increments:
|
||||||
|
|
||||||
|
| Gate | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| P0/S0 | Makes the stack installable, inspectable and reproducible rather than an ad hoc lab |
|
||||||
|
| S1 | Proves Mission Core can own a complete virtual rover run lifecycle |
|
||||||
|
| S2 | Produces the first useful navigation baseline and comparable safety/latency evidence |
|
||||||
|
| S3 | Connects Mission Core perception to a closed-loop consumer and exposes product differentiation |
|
||||||
|
| S4 | Makes recorded real data useful for regression and shadow-policy analysis |
|
||||||
|
| S5/S6 | Reduces risk before a real vehicle, without claiming real-world safety certification |
|
||||||
|
|
||||||
|
The first product-useful baseline is P0 through S2. The current estimate is
|
||||||
|
27–46 engineer-days, excluding waits for hardware, external accounts or a
|
||||||
|
resolved worker-access blocker.
|
||||||
|
|
||||||
|
## 2. Current truth
|
||||||
|
|
||||||
|
The repository's proven vertical remains K1 observation, durable archive,
|
||||||
|
recorded perception and diagnostic motion evidence through LAB E26. It has not
|
||||||
|
accepted navigation behavior, safety behavior or real actuator control.
|
||||||
|
|
||||||
|
Polygon is now a parallel product branch. As of this document:
|
||||||
|
|
||||||
|
- P0 architecture and SRS are being committed;
|
||||||
|
- SIM S0 has a strict candidate qualification profile and read-only doctor;
|
||||||
|
- no target-worker S0 evidence has been admitted;
|
||||||
|
- PX4/Gazebo/ROS 2/Nav2 have not been installed or accepted by this repository;
|
||||||
|
- no stock rover run has been accepted;
|
||||||
|
- `actuator_authority=false`;
|
||||||
|
- `navigation_or_safety_accepted=false`.
|
||||||
|
|
||||||
|
The doctor must report `INCOMPLETE` until exact worker evidence is supplied. A
|
||||||
|
repository manifest is never enough for a `GO`.
|
||||||
|
|
||||||
|
## 3. Goals
|
||||||
|
|
||||||
|
### 3.1 Product goals
|
||||||
|
|
||||||
|
- Turn perception and planning work into measurable sense-plan-act experiments.
|
||||||
|
- Compare standard and Mission Core planners against the same versioned inputs.
|
||||||
|
- Preserve failures, limitations and uncertainty as first-class evidence.
|
||||||
|
- Reuse the same scenario/run/report concepts across simulation, replay, digital
|
||||||
|
twin, controller-in-loop, HIL and physical shadow.
|
||||||
|
- Keep physics, autopilot, middleware, planner and viewer replaceable.
|
||||||
|
- Create a controlled path toward a future trike without silently widening
|
||||||
|
authority.
|
||||||
|
|
||||||
|
### 3.2 Engineering goals
|
||||||
|
|
||||||
|
- Deterministic lifecycle ownership with no orphan simulator processes.
|
||||||
|
- Exact version, environment, input and artifact provenance.
|
||||||
|
- Explicit clock-domain and coordinate-frame conversions.
|
||||||
|
- Fail-closed setpoint validity, TTL, heartbeat, watchdog and loss behavior.
|
||||||
|
- Append-only run history and digest-bound source/derived artifacts.
|
||||||
|
- Side-effect-free infrastructure diagnosis before installation.
|
||||||
|
|
||||||
|
## 4. Non-goals
|
||||||
|
|
||||||
|
The first delivery does not:
|
||||||
|
|
||||||
|
- grant real actuator authority;
|
||||||
|
- expose PX4 directly to the browser;
|
||||||
|
- use direct actuator setpoints;
|
||||||
|
- certify Nav2, PX4 or Mission Core as a real-time safety system;
|
||||||
|
- treat replay as closed-loop evidence;
|
||||||
|
- import all 300 BARN worlds before a stock-world baseline;
|
||||||
|
- download complete CODa, JRDB or NCLT collections;
|
||||||
|
- build a custom physics engine;
|
||||||
|
- make `px4-ros2-interface-lib` mandatory for S1;
|
||||||
|
- overload the observation-session model for qualification runs;
|
||||||
|
- implement a top-level UI before the backend S1 gate.
|
||||||
|
|
||||||
|
## 5. Product and system boundaries
|
||||||
|
|
||||||
|
### 5.1 Mission Core ownership
|
||||||
|
|
||||||
|
Mission Core owns:
|
||||||
|
|
||||||
|
- scenario definitions and profile selection;
|
||||||
|
- qualification-run identity and lifecycle;
|
||||||
|
- command authority and safety policy;
|
||||||
|
- canonical state, setpoint, decision and event contracts;
|
||||||
|
- orchestration of replaceable providers;
|
||||||
|
- evidence admission, provenance and immutable history;
|
||||||
|
- evaluation, comparison and qualification reports.
|
||||||
|
|
||||||
|
### 5.2 Provider ownership
|
||||||
|
|
||||||
|
Providers remain replaceable:
|
||||||
|
|
||||||
|
- Gazebo owns simulated world state, sensors, contacts and physics.
|
||||||
|
- PX4 SITL owns rover control loops, constraints, offboard lifecycle and
|
||||||
|
autopilot failsafe.
|
||||||
|
- ROS 2 transports typed telemetry and commands.
|
||||||
|
- Nav2 supplies the first mature planning/costmap/collision-checking baseline.
|
||||||
|
- Rerun or another viewer presents derived evidence and never becomes a queue,
|
||||||
|
orchestrator or source-of-record.
|
||||||
|
- Dataset adapters expose recorded streams but never pretend alternative
|
||||||
|
commands changed future recorded observations.
|
||||||
|
|
||||||
|
### 5.3 Neighboring Mission Core contexts
|
||||||
|
|
||||||
|
- Observation owns real and recorded sensor streams plus perception layers.
|
||||||
|
- Polygon owns scenarios, qualification runs, decisions, simulated state and
|
||||||
|
comparisons.
|
||||||
|
- Missions owns goals and routes that may later target a virtual or real
|
||||||
|
vehicle.
|
||||||
|
- Data owns source-of-record, derived artifacts, retention and report access.
|
||||||
|
|
||||||
|
Observation `SessionStatus` and its modality model are not extended to describe
|
||||||
|
simulation. Shared UI components require an explicit data-source contract.
|
||||||
|
|
||||||
|
## 6. Target architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
Mission Core UI
|
||||||
|
|
|
||||||
|
Simulation API
|
||||||
|
|
|
||||||
|
Simulation Orchestrator
|
||||||
|
+---- scenario/profile registry
|
||||||
|
+---- run state machine and authority gate
|
||||||
|
+---- clock, namespace, port and process ownership
|
||||||
|
+---- artifact/provenance store and evaluator
|
||||||
|
|
|
||||||
|
+---- Gazebo adapter ---- Gazebo world/physics/sensors
|
||||||
|
+---- PX4 adapter ------- PX4 SITL rover controller/failsafe
|
||||||
|
+---- ROS 2 adapter ----- Micro XRCE-DDS and canonical topics
|
||||||
|
+---- planner adapter --- Nav2 or Mission Core planner
|
||||||
|
+---- viewer adapter ---- canonical state/derived report
|
||||||
|
```
|
||||||
|
|
||||||
|
Gazebo and PX4 never run inside the web process. The browser never owns process
|
||||||
|
lifecycle or command authority. Losing the browser cannot terminate the only
|
||||||
|
copy of run state and cannot bypass the server-side authority gate.
|
||||||
|
|
||||||
|
## 7. Canonical domain model
|
||||||
|
|
||||||
|
### 7.1 Versioned inputs
|
||||||
|
|
||||||
|
`ScenarioDefinition` contains identifiers and references for:
|
||||||
|
|
||||||
|
- world and deterministic actor placement;
|
||||||
|
- vehicle profile;
|
||||||
|
- sensor profile;
|
||||||
|
- route/goal;
|
||||||
|
- planner profile;
|
||||||
|
- fault profile;
|
||||||
|
- metric profile;
|
||||||
|
- seed and reproducibility tier.
|
||||||
|
|
||||||
|
`VehicleProfile`, `SensorProfile`, `PlannerProfile`, `FaultProfile` and
|
||||||
|
`MetricProfile` are independently versioned. A run stores the exact resolved
|
||||||
|
generation and hashes, not only mutable names.
|
||||||
|
|
||||||
|
### 7.2 QualificationRun
|
||||||
|
|
||||||
|
`QualificationRun` is the umbrella aggregate. `SimulationRun` is not used as
|
||||||
|
the common type because replay, HIL and physical shadow are not simulations.
|
||||||
|
|
||||||
|
Required identity:
|
||||||
|
|
||||||
|
- immutable run ID and episode ID;
|
||||||
|
- run kind;
|
||||||
|
- scenario/profile generations and hashes;
|
||||||
|
- Mission Core commit;
|
||||||
|
- provider versions, commits or image digests;
|
||||||
|
- host and resource profile;
|
||||||
|
- seed and reproducibility tier;
|
||||||
|
- authority profile;
|
||||||
|
- start/end UTC provenance;
|
||||||
|
- authoritative clock domain.
|
||||||
|
|
||||||
|
Reset creates a new episode/run identity. It never reuses a completed identity
|
||||||
|
or overwrites artifacts.
|
||||||
|
|
||||||
|
### 7.3 Run kinds
|
||||||
|
|
||||||
|
| Kind | Commands affect future inputs | Actuator authority |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `simulation_closed_loop` | Yes, through virtual physics | Virtual only |
|
||||||
|
| `replay_shadow` | No | None |
|
||||||
|
| `digital_twin_closed_loop` | Yes, through an attested twin | Virtual only |
|
||||||
|
| `controller_in_loop` | Yes, through a bounded controller interface | Lab-specific |
|
||||||
|
| `hil` | Yes, with hardware in the loop | Separate gate |
|
||||||
|
| `physical_shadow` | No | None |
|
||||||
|
|
||||||
|
No result may be promoted from one kind to another by changing report wording.
|
||||||
|
|
||||||
|
### 7.4 Canonical streams
|
||||||
|
|
||||||
|
- `VehicleState`: pose, twist, acceleration, steering/wheel state, estimator
|
||||||
|
state and source frames.
|
||||||
|
- `PerceptionWorldState`: occupancy, objects, motion, confidence, unknown and
|
||||||
|
uncertainty.
|
||||||
|
- `ControlSetpoint`: profile, sequence, issued time, validity deadline,
|
||||||
|
speed/steering or speed/yaw-rate and authority scope.
|
||||||
|
- `SafetyDecision`: allow/slow/stop, reason, active constraint, TTC/clearance
|
||||||
|
evidence and input generation.
|
||||||
|
- `QualificationEvent`: lifecycle, collision/contact, reset, deadline miss,
|
||||||
|
data loss, watchdog, failsafe and evaluator events.
|
||||||
|
- `QualificationReport`: verdict, metrics, violations, limitations and evidence
|
||||||
|
links.
|
||||||
|
|
||||||
|
## 8. Lifecycle requirements
|
||||||
|
|
||||||
|
The minimum lifecycle is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
admitted -> starting -> running <-> paused -> stopping -> completed
|
||||||
|
| |
|
||||||
|
+--------------------+-> failed
|
||||||
|
```
|
||||||
|
|
||||||
|
Cancellation is a requested transition, not evidence that processes stopped.
|
||||||
|
A run reaches a terminal state only after:
|
||||||
|
|
||||||
|
- child-process ownership is reconciled;
|
||||||
|
- stop/failsafe outcome is captured;
|
||||||
|
- source artifacts are sealed;
|
||||||
|
- derived indexes are generated or explicitly marked absent;
|
||||||
|
- the report records the final limitations.
|
||||||
|
|
||||||
|
Process shutdown order is explicit and deterministic. Orchestrator shutdown is
|
||||||
|
last. An orphan process is an S0/S1 failure.
|
||||||
|
|
||||||
|
## 9. Clock contract
|
||||||
|
|
||||||
|
For `simulation_closed_loop`:
|
||||||
|
|
||||||
|
- Gazebo `/clock` is authoritative.
|
||||||
|
- ROS 2 consumers use `use_sim_time=true`.
|
||||||
|
- PX4 uXRCE-DDS time synchronization is disabled when Gazebo time is used.
|
||||||
|
- Canonical timestamps are integer nanoseconds and include their clock domain.
|
||||||
|
- UTC is provenance only.
|
||||||
|
- TTL, deadline and watchdog calculations declare whether they use simulation
|
||||||
|
or monotonic host time.
|
||||||
|
- Pause, resume, single-step and real-time-factor changes are qualification
|
||||||
|
cases, not UI-only behavior.
|
||||||
|
|
||||||
|
Wall clock must not appear implicitly in planner or safety calculations.
|
||||||
|
|
||||||
|
## 10. Coordinate-frame contract
|
||||||
|
|
||||||
|
Mission Core navigation uses:
|
||||||
|
|
||||||
|
- `map` and `odom`: ENU;
|
||||||
|
- `base_link`: FLU.
|
||||||
|
|
||||||
|
PX4 uses:
|
||||||
|
|
||||||
|
- world/local: NED;
|
||||||
|
- body: FRD.
|
||||||
|
|
||||||
|
All PX4 conversion happens at one adapter boundary. Pose, vector, yaw, yaw-rate
|
||||||
|
and covariance transformations require golden tests. Every canonical message
|
||||||
|
identifies source and target frame. A frame mismatch fails closed and cannot be
|
||||||
|
reclassified as planner noise.
|
||||||
|
|
||||||
|
## 11. Authority and safety requirements
|
||||||
|
|
||||||
|
Until a new physical-control card and formal safety gate:
|
||||||
|
|
||||||
|
- `simulation_or_shadow_only=true`;
|
||||||
|
- `actuator_authority=false`;
|
||||||
|
- `navigation_or_safety_accepted=false`;
|
||||||
|
- direct actuator setpoints are rejected;
|
||||||
|
- supported S1 command profiles are `rover-speed-steering/v1` and
|
||||||
|
`rover-speed-yaw-rate/v1`;
|
||||||
|
- each command carries a monotonic sequence and explicit expiry;
|
||||||
|
- heartbeat loss, TTL expiry, invalid state, planner timeout or link loss causes
|
||||||
|
a stop/failsafe transition;
|
||||||
|
- unknown occupancy never becomes free space by omission;
|
||||||
|
- ground truth is unavailable to the planner and perception runtime;
|
||||||
|
- ground truth is separately available to the evaluator.
|
||||||
|
|
||||||
|
Nav2 Collision Monitor is an additional software safety layer. It is not a
|
||||||
|
hard-real-time certified safety controller.
|
||||||
|
|
||||||
|
## 12. Evidence and data requirements
|
||||||
|
|
||||||
|
### 12.1 Source-of-record
|
||||||
|
|
||||||
|
The minimum source set is:
|
||||||
|
|
||||||
|
- exact scenario and qualification profiles;
|
||||||
|
- stack/version lock;
|
||||||
|
- run manifest and authority profile;
|
||||||
|
- PX4 ULog;
|
||||||
|
- Gazebo log or equivalent world-state/contact evidence;
|
||||||
|
- rosbag2/MCAP for admitted canonical streams;
|
||||||
|
- factual host/resource and process-lifecycle evidence.
|
||||||
|
|
||||||
|
### 12.2 Derived artifacts
|
||||||
|
|
||||||
|
Derived data includes:
|
||||||
|
|
||||||
|
- normalized metrics;
|
||||||
|
- evaluator outputs;
|
||||||
|
- comparison tables;
|
||||||
|
- Rerun recording;
|
||||||
|
- qualification report.
|
||||||
|
|
||||||
|
Presentation state is not source evidence. Derived artifacts are rebuildable and
|
||||||
|
must retain input hashes. Source and derived generations are append-only.
|
||||||
|
|
||||||
|
### 12.3 Required metrics
|
||||||
|
|
||||||
|
Each applicable run reports:
|
||||||
|
|
||||||
|
- completion and terminal reason;
|
||||||
|
- collision/contact count and minimum clearance;
|
||||||
|
- path length, elapsed simulation time and efficiency;
|
||||||
|
- route/pose error;
|
||||||
|
- speed/steering tracking;
|
||||||
|
- stop time and distance;
|
||||||
|
- TTC and constraint activation;
|
||||||
|
- replans and oscillation;
|
||||||
|
- dropped/late messages and deadline misses;
|
||||||
|
- stage latency p50/p95/max and queue depth;
|
||||||
|
- real-time factor;
|
||||||
|
- CPU, GPU, RAM, VRAM and disk usage.
|
||||||
|
|
||||||
|
Inapplicable metrics are explicit, not silently zero.
|
||||||
|
|
||||||
|
## 13. Reproducibility
|
||||||
|
|
||||||
|
- R0: exact inputs and versions are retained; outcome may vary.
|
||||||
|
- R1: the same inputs/seed/version lock reproduce the same verdict within
|
||||||
|
declared tolerances.
|
||||||
|
- R2: defined metrics remain within accepted tolerance bands.
|
||||||
|
|
||||||
|
Bitwise-identical physics is not promised. Comparison is rejected when scenario
|
||||||
|
schema, metric profile, provider generation or reproducibility tier are
|
||||||
|
incompatible.
|
||||||
|
|
||||||
|
## 14. Upstream baseline
|
||||||
|
|
||||||
|
The S0 candidate line is:
|
||||||
|
|
||||||
|
- Windows 11 AI worker;
|
||||||
|
- WSL2 Ubuntu 24.04;
|
||||||
|
- ROS 2 Jazzy;
|
||||||
|
- Gazebo Harmonic;
|
||||||
|
- PX4 Autopilot v1.17.0;
|
||||||
|
- matching `px4_msgs` v1.17.0 generation;
|
||||||
|
- Micro XRCE-DDS Agent v2.4.3 candidate;
|
||||||
|
- Nav2 Jazzy;
|
||||||
|
- PX4 Gazebo stock rover models.
|
||||||
|
|
||||||
|
The exact source commits already resolved in the candidate profile do not become
|
||||||
|
accepted merely because they are present. ROS/Gazebo package versions and every
|
||||||
|
candidate build remain pending target-worker qualification.
|
||||||
|
|
||||||
|
S1 uses direct exact `px4_msgs` integration. The experimental
|
||||||
|
`px4-ros2-interface-lib` is evaluated later and is not on the S1 critical path.
|
||||||
|
|
||||||
|
## 15. Storage and runtime placement
|
||||||
|
|
||||||
|
All mutable worker bytes must be physically placed under
|
||||||
|
`D:\NDC_MISSIONCORE`, including:
|
||||||
|
|
||||||
|
- WSL distribution/VHD;
|
||||||
|
- Docker data and images;
|
||||||
|
- sources and build products;
|
||||||
|
- package/model caches;
|
||||||
|
- runtime and logs;
|
||||||
|
- bags, ULog and Gazebo evidence;
|
||||||
|
- datasets and reports.
|
||||||
|
|
||||||
|
The WSL view is `/mnt/d/NDC_MISSIONCORE`. C-drive placement is a blocking
|
||||||
|
failure. S0 requires at least 300 GiB free before installation and a stop-below
|
||||||
|
threshold of 200 GiB. Full external datasets are out of S0 scope.
|
||||||
|
|
||||||
|
## 16. Delivery plan
|
||||||
|
|
||||||
|
| Phase | Estimate | Exit condition |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| P0 | 2–4 days | Product thesis, SRS, ADR and canonical contracts accepted |
|
||||||
|
| S0 | 3–5 days | Target compatibility, D-only runtime, exact pins, clock/process/resource evidence |
|
||||||
|
| S1 | 7–12 days | Stock Ackermann and Differential lifecycle/control/failsafe evidence |
|
||||||
|
| S2 | 15–25 days | LiDAR/odometry/Nav2 baseline, evaluator and obstacle scenarios |
|
||||||
|
| S2B | 5–8 days | Representative 30-world BARN pilot |
|
||||||
|
| S3 | 20–35 days | K1-like virtual sensors and Mission Core perception closed loop |
|
||||||
|
| S4 | 10–20 days | Canonical replay/shadow adapters and reports |
|
||||||
|
| S5 | 20–40 days | Attested RAVNOVES00 digital twin |
|
||||||
|
| S6 | 20–40+ days | Trike model, controller-in-loop/HIL and physical-shadow gate |
|
||||||
|
|
||||||
|
Implementation cards follow this order:
|
||||||
|
|
||||||
|
1. Product SRS and ADR.
|
||||||
|
2. D-only inventory and stack lock.
|
||||||
|
3. Clock, frame and authority contracts.
|
||||||
|
4. Orchestrator lifecycle and artifact store.
|
||||||
|
5. PX4 stock rover control and failsafe.
|
||||||
|
6. Nav2 adapter and truth baseline.
|
||||||
|
7. Evaluator, report and BARN pilot.
|
||||||
|
8. Virtual K1 and perception.
|
||||||
|
9. Replay/shadow datasets.
|
||||||
|
10. RAVNOVES00 twin.
|
||||||
|
11. Trike, HIL and safety gate.
|
||||||
|
|
||||||
|
## 17. SIM S0 acceptance
|
||||||
|
|
||||||
|
SIM S0 requires all of the following factual evidence:
|
||||||
|
|
||||||
|
- worker inventory;
|
||||||
|
- physical D-only placement, including WSL and Docker VHD;
|
||||||
|
- exact component/package pins and licenses;
|
||||||
|
- one stock PX4 rover launch;
|
||||||
|
- ROS 2 telemetry;
|
||||||
|
- authoritative simulation clock;
|
||||||
|
- pause, resume, single-step and speed-factor behavior;
|
||||||
|
- clean stop with no orphan processes;
|
||||||
|
- 1× resource baseline;
|
||||||
|
- 2× resource baseline;
|
||||||
|
- explicit `GO`, `PAUSE` or `BLOCKED` report.
|
||||||
|
|
||||||
|
The repository profile and doctor implement preflight and evidence admission.
|
||||||
|
They do not install packages, start processes, open ports or write a target
|
||||||
|
report.
|
||||||
|
|
||||||
|
Run the local read-only preflight:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run missioncore-sim s0 doctor --json
|
||||||
|
```
|
||||||
|
|
||||||
|
An optional evidence manifest uses
|
||||||
|
`missioncore.simulation-s0-evidence/v1`. Each required claim points to a
|
||||||
|
confined regular artifact and supplies its SHA-256. The profile SHA-256 must
|
||||||
|
match the exact candidate generation.
|
||||||
|
|
||||||
|
## 18. S1 and S2 acceptance outline
|
||||||
|
|
||||||
|
### S1
|
||||||
|
|
||||||
|
- Ackermann primary and Differential reference launch from the orchestrator.
|
||||||
|
- Start, pause, resume, step, reset and stop are server-owned.
|
||||||
|
- Straight, turn, reverse, deceleration and emergency-stop cases pass.
|
||||||
|
- TTL, heartbeat, offboard loss and link loss produce a captured safe outcome.
|
||||||
|
- Pose, speed, steering/controller state and failsafe telemetry are retained.
|
||||||
|
- Reset creates a new run/episode.
|
||||||
|
- A repeated seed produces the declared R1 verdict tolerance.
|
||||||
|
|
||||||
|
K1, Nav2 and top-level UI do not block S1 backend acceptance.
|
||||||
|
|
||||||
|
### S2
|
||||||
|
|
||||||
|
- Gazebo LiDAR/odometry feed the ROS 2/Nav2 baseline.
|
||||||
|
- Ackermann uses MPPI plus Smac Hybrid/Lattice or an explicitly justified
|
||||||
|
equivalent.
|
||||||
|
- Static wall, corridor, bypass, narrow passage, no-data, planner timeout and
|
||||||
|
control-loss scenarios produce comparable reports.
|
||||||
|
- PX4 waypoint following without obstacle avoidance and Nav2 are measured on
|
||||||
|
the same profiles.
|
||||||
|
- A 30-world BARN pilot follows stock-world acceptance; only geometry, maps,
|
||||||
|
paths and difficulty are imported from the legacy runtime.
|
||||||
|
|
||||||
|
## 19. Replay and dataset policy
|
||||||
|
|
||||||
|
Recorded datasets support detector, segmentation, tracking, occupancy and
|
||||||
|
shadow-policy regression. They do not react to alternative commands and cannot
|
||||||
|
prove a closed-loop avoidance maneuver.
|
||||||
|
|
||||||
|
- CODa and JRDB non-commercial/share-alike restrictions prohibit treating them
|
||||||
|
as unrestricted commercial runtime dependencies.
|
||||||
|
- NCLT is large and must be admitted by a size/license manifest.
|
||||||
|
- SCAND supplies social-navigation demonstrations but remains replay evidence.
|
||||||
|
- RAVNOVES00 first enters as canonical replay/shadow; only S5 may turn an
|
||||||
|
attested reconstruction into a digital-twin world.
|
||||||
|
|
||||||
|
Unknown or unobserved space remains unknown/occupied according to the selected
|
||||||
|
safety policy.
|
||||||
|
|
||||||
|
## 20. UI gate
|
||||||
|
|
||||||
|
Polygon is planned as the seventh top-level bounded context:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Center | Fleet | Observation | Polygon | Missions | Data | System
|
||||||
|
```
|
||||||
|
|
||||||
|
Top-level UI implementation begins only after:
|
||||||
|
|
||||||
|
1. this SRS and the clock/frame/authority contracts are accepted;
|
||||||
|
2. S1 backend lifecycle is proven;
|
||||||
|
3. run history is persisted independently of a browser session.
|
||||||
|
|
||||||
|
The future UI may present Scenarios, Run, Control, History, Compare and Report.
|
||||||
|
It cannot own provider processes, bypass authority checks or write directly to
|
||||||
|
PX4.
|
||||||
|
|
||||||
|
## 21. Change control
|
||||||
|
|
||||||
|
A material change requires:
|
||||||
|
|
||||||
|
1. an ADR/SRS diff;
|
||||||
|
2. an update to MISSIONCOR-39;
|
||||||
|
3. migration and compatibility notes;
|
||||||
|
4. new or updated tests;
|
||||||
|
5. fresh evidence before a previously accepted gate remains accepted.
|
||||||
|
|
||||||
|
No checkbox is closed from intent alone. A commit plus factual validation or
|
||||||
|
admitted evidence is required.
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
# ADR 0015: Simulation Polygon as a qualification boundary
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted for architecture and SIM S0 implementation on 2026-07-24.
|
||||||
|
Navigation behavior, safety behavior and real actuator authority are not
|
||||||
|
accepted.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Mission Core has proven a K1 observation/archive/perception vertical, but the
|
||||||
|
perception outputs do not yet have a closed-loop consumer. Adding Gazebo, PX4,
|
||||||
|
ROS 2 and Nav2 directly to the web process or modeling every experiment as an
|
||||||
|
observation session would blur product ownership, lifecycle, evidence and
|
||||||
|
authority.
|
||||||
|
|
||||||
|
Replay, physics simulation, a digital twin, HIL and real shadow also have
|
||||||
|
different causal meaning. A recorded stream cannot react to a hypothetical
|
||||||
|
command, while a closed-loop world can. Calling both `SimulationRun` would make
|
||||||
|
reports easy to misuse.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
1. Polygon is a first-class Mission Core bounded context for autonomy
|
||||||
|
qualification and assurance.
|
||||||
|
2. Mission Core owns scenarios, qualification-run lifecycle, authority policy,
|
||||||
|
canonical contracts, provenance, evidence admission, evaluation and reports.
|
||||||
|
3. Gazebo, PX4 SITL, ROS 2, Nav2, Rerun and datasets are replaceable providers.
|
||||||
|
4. `QualificationRun` is the umbrella aggregate. Its kinds explicitly separate
|
||||||
|
`simulation_closed_loop`, `replay_shadow`,
|
||||||
|
`digital_twin_closed_loop`, `controller_in_loop`, `hil` and
|
||||||
|
`physical_shadow`.
|
||||||
|
5. Simulation Orchestrator is the sole process-lifecycle owner. Gazebo and PX4
|
||||||
|
do not run inside the Mission Core web process, and the browser has no direct
|
||||||
|
PX4 channel.
|
||||||
|
6. Gazebo `/clock` is authoritative in closed-loop simulation. ROS 2 uses
|
||||||
|
simulated time and PX4 uXRCE-DDS time synchronization is disabled for this
|
||||||
|
profile.
|
||||||
|
7. Mission Core uses ENU/FLU. PX4 uses NED/FRD. Exactly one adapter boundary
|
||||||
|
converts frames and requires golden tests.
|
||||||
|
8. The first command boundary permits only rover speed+steering and
|
||||||
|
speed+yaw-rate profiles with sequence, TTL, heartbeat, watchdog and
|
||||||
|
stop-on-loss behavior. Direct actuator setpoints are rejected.
|
||||||
|
9. Source-of-record, derived artifacts and presentation state remain separate.
|
||||||
|
Reset creates a new immutable run/episode.
|
||||||
|
10. Ground truth is evaluator-only and is not visible to perception or planning.
|
||||||
|
11. SIM S0 targets the reviewed Windows 11/WSL2 AI worker with all mutable bytes
|
||||||
|
physically under `D:\NDC_MISSIONCORE`.
|
||||||
|
12. S1 uses exact `px4_msgs` directly. The experimental
|
||||||
|
`px4-ros2-interface-lib` is not a mandatory S1 dependency.
|
||||||
|
13. A top-level Polygon UI is gated on accepted contracts and a proven S1
|
||||||
|
backend lifecycle with persisted run history.
|
||||||
|
14. Real actuator authority requires a new decision and physical safety gate.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Perception can be measured by downstream decisions without granting it hidden
|
||||||
|
behavior authority.
|
||||||
|
- Standard planners and future Mission Core planners can be compared on the
|
||||||
|
same scenario and metric profiles.
|
||||||
|
- Replay cannot be presented as closed-loop safety evidence.
|
||||||
|
- Provider upgrades require explicit compatibility and evidence refresh.
|
||||||
|
- More up-front domain and artifact work is required before a visual demo.
|
||||||
|
- The AI worker must satisfy D-only, resource and coexistence constraints before
|
||||||
|
installation is accepted.
|
||||||
|
- Simulation and HIL reduce risk but do not certify the real trike.
|
||||||
|
|
||||||
|
## First implementation
|
||||||
|
|
||||||
|
The first implementation is deliberately read-only:
|
||||||
|
|
||||||
|
- `simulation/s0/qualification-profile.yaml` is the candidate stack,
|
||||||
|
storage, clock, authority, port and process contract.
|
||||||
|
- `k1link.simulation.s0` strictly validates that contract and optional
|
||||||
|
digest-bound worker evidence.
|
||||||
|
- `missioncore-sim s0 doctor` inspects the current host without installing,
|
||||||
|
starting or modifying anything.
|
||||||
|
- MISSIONCOR-40 tracks factual worker qualification.
|
||||||
|
|
||||||
|
The doctor reports `INCOMPLETE` until it is running on the reviewed target with
|
||||||
|
resolved/accepted pins and all required evidence. It cannot infer `GO` from the
|
||||||
|
repository state.
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
# SIM S0 AI worker runbook
|
||||||
|
|
||||||
|
This runbook is for MISSIONCOR-40. It does not authorize system installation,
|
||||||
|
real vehicle control or a `GO` verdict by itself.
|
||||||
|
|
||||||
|
## Safety and placement
|
||||||
|
|
||||||
|
- Target: reviewed Windows 11 worker with WSL2 Ubuntu 24.04.
|
||||||
|
- All mutable bytes must be under `D:\NDC_MISSIONCORE`.
|
||||||
|
- Do not place a WSL distribution, Docker VHD, source, build, cache, log, bag,
|
||||||
|
ULog, Gazebo artifact or dataset on C.
|
||||||
|
- Do not expose S0 services beyond loopback.
|
||||||
|
- Do not download full external datasets.
|
||||||
|
- Keep `actuator_authority=false`.
|
||||||
|
|
||||||
|
## Repository preflight
|
||||||
|
|
||||||
|
From the repository-local Python 3.12 environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync --frozen --group dev
|
||||||
|
uv run missioncore-sim s0 doctor --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Before target evidence exists, the expected verdict is `INCOMPLETE`.
|
||||||
|
|
||||||
|
## Required target inventory
|
||||||
|
|
||||||
|
Record a redacted artifact for each of:
|
||||||
|
|
||||||
|
1. Windows release, WSL version, Ubuntu version and architecture.
|
||||||
|
2. GPU/driver, CPU, RAM and VRAM.
|
||||||
|
3. Physical locations of the WSL distribution and Docker data/VHD.
|
||||||
|
4. D total/free bytes before installation.
|
||||||
|
5. Existing Docker images, WSL distributions and simulation-related processes.
|
||||||
|
6. Existing listeners and the candidate port registry.
|
||||||
|
7. Existing Frigate/Ollama/Triton resource use and whether S0 is coexistence or
|
||||||
|
exclusive-profile testing.
|
||||||
|
|
||||||
|
Do not record credentials, tokens or private network secrets.
|
||||||
|
|
||||||
|
## Candidate stack
|
||||||
|
|
||||||
|
Use `simulation/s0/qualification-profile.yaml` as the input. Resolve and record:
|
||||||
|
|
||||||
|
- exact ROS 2 Jazzy package versions;
|
||||||
|
- exact Gazebo Harmonic package versions;
|
||||||
|
- PX4 v1.17.0 commit;
|
||||||
|
- matching `px4_msgs` commit;
|
||||||
|
- Micro XRCE-DDS Agent commit/version;
|
||||||
|
- Nav2 Jazzy commit/package versions;
|
||||||
|
- PX4 Gazebo model commit;
|
||||||
|
- licenses and image digests where containers are used.
|
||||||
|
|
||||||
|
Change every component to `accepted` only after target build/runtime evidence
|
||||||
|
exists. Updating a pin changes the profile SHA-256 and invalidates evidence for
|
||||||
|
the prior generation.
|
||||||
|
|
||||||
|
## Runtime cases
|
||||||
|
|
||||||
|
The S0 factual cases are:
|
||||||
|
|
||||||
|
1. Launch one stock rover headless.
|
||||||
|
2. Observe PX4 health and ROS 2 telemetry.
|
||||||
|
3. Prove Gazebo `/clock` is authoritative.
|
||||||
|
4. Prove pause freezes simulation time.
|
||||||
|
5. Prove one single-step advances the declared amount.
|
||||||
|
6. Prove 1× and 2× speed behavior and measure real-time factor.
|
||||||
|
7. Stop through the intended lifecycle.
|
||||||
|
8. Prove no owned process remains.
|
||||||
|
9. Repeat from a clean start.
|
||||||
|
|
||||||
|
QGroundControl is diagnostic only and cannot become the Mission Core command
|
||||||
|
owner.
|
||||||
|
|
||||||
|
## Evidence manifest
|
||||||
|
|
||||||
|
Create a private/redacted evidence directory outside normal Git. Its manifest:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schema_version: missioncore.simulation-s0-evidence/v1
|
||||||
|
profile_sha256: <sha256 of qualification-profile.yaml>
|
||||||
|
checks:
|
||||||
|
- id: worker-inventory
|
||||||
|
status: pass
|
||||||
|
artifact: worker-inventory.redacted.json
|
||||||
|
sha256: <artifact sha256>
|
||||||
|
note: Redacted host and resource inventory.
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest must contain exactly all IDs from `required_evidence`. Artifact
|
||||||
|
paths are relative, confined regular files. The doctor rejects missing,
|
||||||
|
additional, changed or cross-generation evidence.
|
||||||
|
|
||||||
|
Validate without writing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run missioncore-sim s0 doctor \
|
||||||
|
--profile simulation/s0/qualification-profile.yaml \
|
||||||
|
--evidence /path/to/redacted/evidence.yaml \
|
||||||
|
--json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verdict rules
|
||||||
|
|
||||||
|
- `GO`: target host matches, every pin is accepted, D-only and disk thresholds
|
||||||
|
pass, required tools exist and all digest-bound evidence validates.
|
||||||
|
- `INCOMPLETE`: work remains or the doctor is running on a development host.
|
||||||
|
- `BLOCKED`: contract, storage, tools or evidence fail validation.
|
||||||
|
|
||||||
|
Publish the factual result to MISSIONCOR-40 with exact repository commit,
|
||||||
|
profile SHA-256, files touched and validation performed.
|
||||||
|
|
@ -30,6 +30,7 @@ missioncore-plugin-sdk = { path = "packages/plugin-sdk", editable = true }
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
k1link = "k1link.device_plugins.xgrids_k1.cli:app"
|
k1link = "k1link.device_plugins.xgrids_k1.cli:app"
|
||||||
|
missioncore-sim = "k1link.simulation.cli:app"
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
schema_version: missioncore.simulation-s0-profile/v1
|
||||||
|
profile_id: ai-worker-px4-gazebo-v1
|
||||||
|
|
||||||
|
target_host:
|
||||||
|
windows_release: windows-11
|
||||||
|
virtualization: wsl2
|
||||||
|
linux_distribution: ubuntu-24.04
|
||||||
|
architecture: x86_64
|
||||||
|
|
||||||
|
storage:
|
||||||
|
windows_root: 'D:\NDC_MISSIONCORE'
|
||||||
|
wsl_root: /mnt/d/NDC_MISSIONCORE
|
||||||
|
minimum_free_gib: 300
|
||||||
|
stop_below_gib: 200
|
||||||
|
forbid_windows_system_drive: true
|
||||||
|
mutable_paths:
|
||||||
|
- id: wsl
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\wsl'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/wsl
|
||||||
|
- id: docker
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\docker'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/docker
|
||||||
|
- id: source
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\source'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/source
|
||||||
|
- id: build
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\build'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/build
|
||||||
|
- id: cache
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\cache'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/cache
|
||||||
|
- id: runtime
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\runtime'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/runtime
|
||||||
|
- id: artifacts
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\artifacts'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/artifacts
|
||||||
|
- id: datasets
|
||||||
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\datasets'
|
||||||
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/datasets
|
||||||
|
|
||||||
|
clock:
|
||||||
|
authority: gazebo:/clock
|
||||||
|
ros_use_sim_time: true
|
||||||
|
px4_uxrce_dds_sync_enabled: false
|
||||||
|
canonical_unit: nanoseconds
|
||||||
|
utc_role: provenance-only
|
||||||
|
|
||||||
|
authority:
|
||||||
|
simulation_or_shadow_only: true
|
||||||
|
actuator_authority: false
|
||||||
|
navigation_or_safety_accepted: false
|
||||||
|
direct_actuator_setpoints_allowed: false
|
||||||
|
allowed_setpoint_profiles:
|
||||||
|
- rover-speed-steering/v1
|
||||||
|
- rover-speed-yaw-rate/v1
|
||||||
|
|
||||||
|
ros:
|
||||||
|
domain_id: 42
|
||||||
|
namespace: /missioncore/polygon/s0
|
||||||
|
|
||||||
|
components:
|
||||||
|
- id: ros2
|
||||||
|
source_url: https://docs.ros.org/en/jazzy/
|
||||||
|
requested_ref: jazzy
|
||||||
|
resolved_version:
|
||||||
|
resolved_commit:
|
||||||
|
license: mixed-apache-2.0-bsd
|
||||||
|
qualification_state: pending-worker-resolution
|
||||||
|
- id: gazebo
|
||||||
|
source_url: https://gazebosim.org/docs/harmonic/
|
||||||
|
requested_ref: harmonic
|
||||||
|
resolved_version:
|
||||||
|
resolved_commit:
|
||||||
|
license: apache-2.0
|
||||||
|
qualification_state: pending-worker-resolution
|
||||||
|
- id: px4-autopilot
|
||||||
|
source_url: https://github.com/PX4/PX4-Autopilot
|
||||||
|
requested_ref: v1.17.0
|
||||||
|
resolved_version: v1.17.0
|
||||||
|
resolved_commit: d6f12ad1c4f70ad3230afd7d86e971421e02fef4
|
||||||
|
license: bsd-3-clause
|
||||||
|
qualification_state: candidate
|
||||||
|
- id: px4-msgs
|
||||||
|
source_url: https://github.com/PX4/px4_msgs
|
||||||
|
requested_ref: v1.17.0
|
||||||
|
resolved_version: v1.17.0
|
||||||
|
resolved_commit: 86d8239e962f6939e05c3737784f60c02fa884db
|
||||||
|
license: bsd-3-clause
|
||||||
|
qualification_state: candidate
|
||||||
|
- id: px4-gazebo-models
|
||||||
|
source_url: https://github.com/PX4/PX4-gazebo-models
|
||||||
|
requested_ref: main@2026-07-24
|
||||||
|
resolved_version:
|
||||||
|
resolved_commit: e00d3b9cde682dbcb3bf6f30a2f2b8ef4325dae8
|
||||||
|
license: bsd-3-clause
|
||||||
|
qualification_state: candidate
|
||||||
|
- id: micro-xrce-dds-agent
|
||||||
|
source_url: https://github.com/eProsima/Micro-XRCE-DDS-Agent
|
||||||
|
requested_ref: v2.4.3
|
||||||
|
resolved_version: v2.4.3
|
||||||
|
resolved_commit: 73622810d984349b80bbac0ef55fc0b694d62222
|
||||||
|
license: apache-2.0
|
||||||
|
qualification_state: candidate
|
||||||
|
- id: nav2
|
||||||
|
source_url: https://github.com/ros-navigation/navigation2
|
||||||
|
requested_ref: jazzy@2026-07-24
|
||||||
|
resolved_version:
|
||||||
|
resolved_commit: c92ea2fd9008f50c0ec8447610800214b2a0dafb
|
||||||
|
license: apache-2.0
|
||||||
|
qualification_state: candidate
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- id: micro-xrce-dds
|
||||||
|
host_scope: worker-wsl
|
||||||
|
protocol: udp
|
||||||
|
bind: 127.0.0.1
|
||||||
|
port: 8888
|
||||||
|
owner: micro-xrce-dds-agent
|
||||||
|
- id: px4-mavlink-sitl
|
||||||
|
host_scope: worker-wsl
|
||||||
|
protocol: udp
|
||||||
|
bind: 127.0.0.1
|
||||||
|
port: 14540
|
||||||
|
owner: px4-sitl
|
||||||
|
- id: qgroundcontrol-reference
|
||||||
|
host_scope: worker-wsl
|
||||||
|
protocol: udp
|
||||||
|
bind: 127.0.0.1
|
||||||
|
port: 14550
|
||||||
|
owner: px4-sitl
|
||||||
|
- id: simulation-orchestrator-api
|
||||||
|
host_scope: worker-wsl
|
||||||
|
protocol: tcp
|
||||||
|
bind: 127.0.0.1
|
||||||
|
port: 18080
|
||||||
|
owner: simulation-orchestrator
|
||||||
|
- id: simulation-telemetry
|
||||||
|
host_scope: worker-wsl
|
||||||
|
protocol: tcp
|
||||||
|
bind: 127.0.0.1
|
||||||
|
port: 18081
|
||||||
|
owner: simulation-orchestrator
|
||||||
|
|
||||||
|
processes:
|
||||||
|
- id: simulation-orchestrator
|
||||||
|
parent:
|
||||||
|
shutdown_order: 60
|
||||||
|
- id: gazebo-server
|
||||||
|
parent: simulation-orchestrator
|
||||||
|
shutdown_order: 50
|
||||||
|
- id: px4-sitl
|
||||||
|
parent: simulation-orchestrator
|
||||||
|
shutdown_order: 40
|
||||||
|
- id: micro-xrce-dds-agent
|
||||||
|
parent: simulation-orchestrator
|
||||||
|
shutdown_order: 30
|
||||||
|
- id: ros2-bridge
|
||||||
|
parent: simulation-orchestrator
|
||||||
|
shutdown_order: 20
|
||||||
|
- id: nav2
|
||||||
|
parent: simulation-orchestrator
|
||||||
|
shutdown_order: 10
|
||||||
|
|
||||||
|
required_tools:
|
||||||
|
- git
|
||||||
|
- docker
|
||||||
|
- gz
|
||||||
|
- make
|
||||||
|
- colcon
|
||||||
|
- ros2
|
||||||
|
- MicroXRCEAgent
|
||||||
|
|
||||||
|
required_evidence:
|
||||||
|
- worker-inventory
|
||||||
|
- d-only-physical-placement
|
||||||
|
- px4-stock-rover-launch
|
||||||
|
- ros2-telemetry
|
||||||
|
- authoritative-simulation-clock
|
||||||
|
- pause-step-speed
|
||||||
|
- clean-stop-no-orphans
|
||||||
|
- resource-baseline-1x
|
||||||
|
- resource-baseline-2x
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""Mission Core qualification and simulation boundaries."""
|
||||||
|
|
||||||
|
from k1link.simulation.s0 import (
|
||||||
|
CheckStatus,
|
||||||
|
DoctorVerdict,
|
||||||
|
S0DoctorReport,
|
||||||
|
S0Profile,
|
||||||
|
S0ProfileError,
|
||||||
|
load_s0_profile,
|
||||||
|
run_s0_doctor,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CheckStatus",
|
||||||
|
"DoctorVerdict",
|
||||||
|
"S0DoctorReport",
|
||||||
|
"S0Profile",
|
||||||
|
"S0ProfileError",
|
||||||
|
"load_s0_profile",
|
||||||
|
"run_s0_doctor",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
from k1link.simulation import S0ProfileError, run_s0_doctor
|
||||||
|
|
||||||
|
app = typer.Typer(
|
||||||
|
name="missioncore-sim",
|
||||||
|
help="Fail-closed qualification tooling for the Mission Core Polygon.",
|
||||||
|
no_args_is_help=True,
|
||||||
|
)
|
||||||
|
s0_app = typer.Typer(
|
||||||
|
help="SIM S0 compatibility and infrastructure qualification.",
|
||||||
|
no_args_is_help=True,
|
||||||
|
)
|
||||||
|
app.add_typer(s0_app, name="s0")
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def _default_profile() -> Path:
|
||||||
|
return Path(__file__).resolve().parents[3] / "simulation" / "s0" / "qualification-profile.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PROFILE = _default_profile()
|
||||||
|
|
||||||
|
|
||||||
|
@s0_app.command("doctor")
|
||||||
|
def s0_doctor(
|
||||||
|
profile: Annotated[
|
||||||
|
Path,
|
||||||
|
typer.Option(
|
||||||
|
"--profile",
|
||||||
|
help="Strict SIM S0 qualification profile.",
|
||||||
|
exists=True,
|
||||||
|
dir_okay=False,
|
||||||
|
readable=True,
|
||||||
|
),
|
||||||
|
] = DEFAULT_PROFILE,
|
||||||
|
evidence: Annotated[
|
||||||
|
Path | None,
|
||||||
|
typer.Option(
|
||||||
|
"--evidence",
|
||||||
|
help="Optional digest-bound target-worker evidence manifest.",
|
||||||
|
exists=True,
|
||||||
|
dir_okay=False,
|
||||||
|
readable=True,
|
||||||
|
),
|
||||||
|
] = None,
|
||||||
|
json_output: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option("--json", help="Emit the complete report as JSON."),
|
||||||
|
] = False,
|
||||||
|
) -> None:
|
||||||
|
"""Inspect contracts and local target readiness without changing the host."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
report = run_s0_doctor(profile, evidence_path=evidence)
|
||||||
|
except (OSError, S0ProfileError) as exc:
|
||||||
|
if json_output:
|
||||||
|
console.print_json(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": "missioncore.simulation-s0-doctor/v1",
|
||||||
|
"verdict": "blocked",
|
||||||
|
"error": str(exc),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
console.print(f"[red]SIM S0 profile rejected:[/red] {exc}")
|
||||||
|
raise typer.Exit(code=2) from exc
|
||||||
|
|
||||||
|
payload = report.to_dict()
|
||||||
|
if json_output:
|
||||||
|
console.print_json(json.dumps(payload))
|
||||||
|
return
|
||||||
|
|
||||||
|
table = Table(title=f"SIM S0 doctor — {report.profile_id}")
|
||||||
|
table.add_column("Check")
|
||||||
|
table.add_column("Status")
|
||||||
|
table.add_column("Detail")
|
||||||
|
colors = {"pass": "green", "fail": "red", "unknown": "yellow"}
|
||||||
|
for check in report.checks:
|
||||||
|
status = check.status.value
|
||||||
|
styled_status = f"[{colors[status]}]{status}[/{colors[status]}]"
|
||||||
|
table.add_row(check.identifier, styled_status, check.detail)
|
||||||
|
console.print(table)
|
||||||
|
console.print(f"Verdict: [bold]{report.verdict.value.upper()}[/bold]")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,227 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
from k1link.simulation import (
|
||||||
|
CheckStatus,
|
||||||
|
DoctorVerdict,
|
||||||
|
S0ProfileError,
|
||||||
|
load_s0_profile,
|
||||||
|
run_s0_doctor,
|
||||||
|
)
|
||||||
|
from k1link.simulation.cli import app
|
||||||
|
|
||||||
|
PROFILE = Path(__file__).resolve().parents[1] / "simulation" / "s0" / "qualification-profile.yaml"
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
def _profile_document() -> dict[str, object]:
|
||||||
|
loaded = yaml.safe_load(PROFILE.read_text(encoding="utf-8"))
|
||||||
|
assert isinstance(loaded, dict)
|
||||||
|
return loaded
|
||||||
|
|
||||||
|
|
||||||
|
def _write_profile(tmp_path: Path, document: dict[str, object]) -> Path:
|
||||||
|
path = tmp_path / "profile.yaml"
|
||||||
|
path.write_text(yaml.safe_dump(document, sort_keys=False), encoding="utf-8")
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_s0_profile_is_strict_and_safe() -> None:
|
||||||
|
profile = load_s0_profile(PROFILE)
|
||||||
|
|
||||||
|
assert profile.profile_id == "ai-worker-px4-gazebo-v1"
|
||||||
|
assert profile.storage.windows_root.drive.upper() == "D:"
|
||||||
|
assert profile.storage.wsl_root.as_posix() == "/mnt/d/NDC_MISSIONCORE"
|
||||||
|
assert profile.clock.authority == "gazebo:/clock"
|
||||||
|
assert profile.clock.ros_use_sim_time is True
|
||||||
|
assert profile.clock.px4_uxrce_dds_sync_enabled is False
|
||||||
|
assert profile.authority.simulation_or_shadow_only is True
|
||||||
|
assert profile.authority.actuator_authority is False
|
||||||
|
assert profile.authority.navigation_or_safety_accepted is False
|
||||||
|
assert profile.authority.direct_actuator_setpoints_allowed is False
|
||||||
|
assert profile.ros.domain_id == 42
|
||||||
|
assert {item.identifier for item in profile.components} >= {
|
||||||
|
"px4-autopilot",
|
||||||
|
"px4-msgs",
|
||||||
|
"gazebo",
|
||||||
|
"nav2",
|
||||||
|
}
|
||||||
|
assert {item.identifier for item in profile.processes} == {
|
||||||
|
"simulation-orchestrator",
|
||||||
|
"gazebo-server",
|
||||||
|
"px4-sitl",
|
||||||
|
"micro-xrce-dds-agent",
|
||||||
|
"ros2-bridge",
|
||||||
|
"nav2",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_profile_rejects_real_actuator_authority(tmp_path: Path) -> None:
|
||||||
|
document = _profile_document()
|
||||||
|
authority = document["authority"]
|
||||||
|
assert isinstance(authority, dict)
|
||||||
|
authority["actuator_authority"] = True
|
||||||
|
|
||||||
|
with pytest.raises(S0ProfileError, match="cannot grant real"):
|
||||||
|
load_s0_profile(_write_profile(tmp_path, document))
|
||||||
|
|
||||||
|
|
||||||
|
def test_profile_rejects_mutable_c_drive_path(tmp_path: Path) -> None:
|
||||||
|
document = _profile_document()
|
||||||
|
storage = document["storage"]
|
||||||
|
assert isinstance(storage, dict)
|
||||||
|
mutable = storage["mutable_paths"]
|
||||||
|
assert isinstance(mutable, list)
|
||||||
|
first = mutable[0]
|
||||||
|
assert isinstance(first, dict)
|
||||||
|
first["windows_path"] = r"C:\NDC_MISSIONCORE\wsl"
|
||||||
|
|
||||||
|
with pytest.raises(S0ProfileError, match="escapes D-only root"):
|
||||||
|
load_s0_profile(_write_profile(tmp_path, document))
|
||||||
|
|
||||||
|
|
||||||
|
def test_profile_rejects_duplicate_scoped_port(tmp_path: Path) -> None:
|
||||||
|
document = _profile_document()
|
||||||
|
ports = document["ports"]
|
||||||
|
assert isinstance(ports, list)
|
||||||
|
duplicate = dict(ports[0])
|
||||||
|
duplicate["id"] = "duplicate-port"
|
||||||
|
duplicate["owner"] = "simulation-orchestrator"
|
||||||
|
ports.append(duplicate)
|
||||||
|
|
||||||
|
with pytest.raises(S0ProfileError, match="duplicates a scoped binding"):
|
||||||
|
load_s0_profile(_write_profile(tmp_path, document))
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_never_claims_go_without_target_evidence(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"k1link.simulation.s0._target_host_match",
|
||||||
|
lambda target: (False, "test host is not target"),
|
||||||
|
)
|
||||||
|
|
||||||
|
report = run_s0_doctor(PROFILE)
|
||||||
|
|
||||||
|
assert report.verdict is DoctorVerdict.INCOMPLETE
|
||||||
|
assert report.target_host_match is False
|
||||||
|
assert any(
|
||||||
|
check.identifier == "factual-evidence" and check.status is CheckStatus.UNKNOWN
|
||||||
|
for check in report.checks
|
||||||
|
)
|
||||||
|
payload = report.to_dict()
|
||||||
|
assert payload["authority"] == {
|
||||||
|
"simulation_or_shadow_only": True,
|
||||||
|
"actuator_authority": False,
|
||||||
|
"navigation_or_safety_accepted": False,
|
||||||
|
"direct_actuator_setpoints_allowed": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_validates_digest_bound_evidence(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"k1link.simulation.s0._target_host_match",
|
||||||
|
lambda target: (False, "test host is not target"),
|
||||||
|
)
|
||||||
|
profile = load_s0_profile(PROFILE)
|
||||||
|
checks: list[dict[str, object]] = []
|
||||||
|
for identifier in profile.required_evidence:
|
||||||
|
artifact = tmp_path / f"{identifier}.txt"
|
||||||
|
artifact.write_text(f"evidence for {identifier}\n", encoding="utf-8")
|
||||||
|
checks.append(
|
||||||
|
{
|
||||||
|
"id": identifier,
|
||||||
|
"status": "pass",
|
||||||
|
"artifact": artifact.name,
|
||||||
|
"sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(),
|
||||||
|
"note": "synthetic unit-test evidence",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
evidence = tmp_path / "evidence.yaml"
|
||||||
|
evidence.write_text(
|
||||||
|
yaml.safe_dump(
|
||||||
|
{
|
||||||
|
"schema_version": "missioncore.simulation-s0-evidence/v1",
|
||||||
|
"profile_sha256": hashlib.sha256(PROFILE.read_bytes()).hexdigest(),
|
||||||
|
"checks": checks,
|
||||||
|
},
|
||||||
|
sort_keys=False,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
report = run_s0_doctor(PROFILE, evidence_path=evidence)
|
||||||
|
|
||||||
|
assert report.verdict is DoctorVerdict.INCOMPLETE
|
||||||
|
assert any(
|
||||||
|
check.identifier == "factual-evidence" and check.status is CheckStatus.PASS
|
||||||
|
for check in report.checks
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_rejects_changed_evidence_artifact(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"k1link.simulation.s0._target_host_match",
|
||||||
|
lambda target: (False, "test host is not target"),
|
||||||
|
)
|
||||||
|
profile = load_s0_profile(PROFILE)
|
||||||
|
checks: list[dict[str, object]] = []
|
||||||
|
artifacts: list[Path] = []
|
||||||
|
for identifier in profile.required_evidence:
|
||||||
|
artifact = tmp_path / f"{identifier}.txt"
|
||||||
|
artifact.write_text("original\n", encoding="utf-8")
|
||||||
|
artifacts.append(artifact)
|
||||||
|
checks.append(
|
||||||
|
{
|
||||||
|
"id": identifier,
|
||||||
|
"status": "pass",
|
||||||
|
"artifact": artifact.name,
|
||||||
|
"sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(),
|
||||||
|
"note": "synthetic unit-test evidence",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
evidence = tmp_path / "evidence.yaml"
|
||||||
|
evidence.write_text(
|
||||||
|
yaml.safe_dump(
|
||||||
|
{
|
||||||
|
"schema_version": "missioncore.simulation-s0-evidence/v1",
|
||||||
|
"profile_sha256": hashlib.sha256(PROFILE.read_bytes()).hexdigest(),
|
||||||
|
"checks": checks,
|
||||||
|
},
|
||||||
|
sort_keys=False,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
artifacts[0].write_text("changed\n", encoding="utf-8")
|
||||||
|
|
||||||
|
report = run_s0_doctor(PROFILE, evidence_path=evidence)
|
||||||
|
|
||||||
|
assert report.verdict is DoctorVerdict.BLOCKED
|
||||||
|
assert any(
|
||||||
|
check.identifier == "factual-evidence"
|
||||||
|
and check.status is CheckStatus.FAIL
|
||||||
|
and "digest changed" in check.detail
|
||||||
|
for check in report.checks
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulation_doctor_cli_emits_json_without_side_effects() -> None:
|
||||||
|
result = runner.invoke(app, ["s0", "doctor", "--profile", str(PROFILE), "--json"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
assert payload["schema_version"] == "missioncore.simulation-s0-doctor/v1"
|
||||||
|
assert payload["profile_id"] == "ai-worker-px4-gazebo-v1"
|
||||||
|
assert payload["verdict"] in {"incomplete", "blocked"}
|
||||||
|
assert payload["authority"]["actuator_authority"] is False
|
||||||
Loading…
Reference in New Issue