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)
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
inside the Mission Core shell. It can open a compatible RRD over same-origin
|
||||
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 |
|
||||
| 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 |
|
||||
| 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
|
||||
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
|
||||
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
|
||||
|
||||
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]
|
||||
k1link = "k1link.device_plugins.xgrids_k1.cli:app"
|
||||
missioncore-sim = "k1link.simulation.cli:app"
|
||||
|
||||
[dependency-groups]
|
||||
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