feat(simulation): qualify D-only stock rover smoke
This commit is contained in:
parent
81761067e9
commit
6290fcb2ce
|
|
@ -51,11 +51,14 @@ 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;
|
||||
- P0 architecture and SRS are committed on the Polygon branch;
|
||||
- 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;
|
||||
- the dedicated D-only worker distribution has been created and the candidate
|
||||
PX4/Gazebo/ROS 2/Nav2 stack has been installed without using Docker;
|
||||
- PX4 v1.17.0 and the stock Ackermann rover have completed repeated factual
|
||||
smoke runs with ROS 2 vehicle status, Gazebo clock and clean shutdown;
|
||||
- the complete digest-bound S0 evidence manifest has not been admitted;
|
||||
- pause/step/speed and accepted 1×/2× resource evidence remain open;
|
||||
- `actuator_authority=false`;
|
||||
- `navigation_or_safety_accepted=false`.
|
||||
|
||||
|
|
@ -369,7 +372,8 @@ incompatible.
|
|||
The S0 candidate line is:
|
||||
|
||||
- Windows 11 AI worker;
|
||||
- WSL2 Ubuntu 24.04;
|
||||
- dedicated `MissionCore-Sim` WSL2 Ubuntu 24.04 distribution physically on D;
|
||||
- no Docker dependency in S0, so active shared Docker co-tenants remain untouched;
|
||||
- ROS 2 Jazzy;
|
||||
- Gazebo Harmonic;
|
||||
- PX4 Autopilot v1.17.0;
|
||||
|
|
@ -378,9 +382,10 @@ The S0 candidate line is:
|
|||
- 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.
|
||||
The exact source commits and package versions are resolved in the candidate
|
||||
profile. Target installation, PX4 build, DDS telemetry and repeated stock-rover
|
||||
smoke evidence now exist, but components do not become `accepted` until the
|
||||
remaining S0 behavior/resource cases and digest-bound evidence generation pass.
|
||||
|
||||
S1 uses direct exact `px4_msgs` integration. The experimental
|
||||
`px4-ros2-interface-lib` is evaluated later and is not on the S1 critical path.
|
||||
|
|
@ -390,8 +395,9 @@ S1 uses direct exact `px4_msgs` integration. The experimental
|
|||
All mutable worker bytes must be physically placed under
|
||||
`D:\NDC_MISSIONCORE`, including:
|
||||
|
||||
- WSL distribution/VHD;
|
||||
- Docker data and images;
|
||||
- the dedicated `MissionCore-Sim` WSL distribution/VHD;
|
||||
- Docker data and images if a later profile elects to use containers; the S0
|
||||
profile explicitly does not;
|
||||
- sources and build products;
|
||||
- package/model caches;
|
||||
- runtime and logs;
|
||||
|
|
@ -402,6 +408,13 @@ 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.
|
||||
|
||||
S0 runtime processes execute in a new loopback-only Linux network namespace.
|
||||
Micro XRCE-DDS Agent v2.4.3 has no address-selection flag and therefore binds
|
||||
UDP wildcard inside that namespace; the namespace contains only `lo`, so the
|
||||
effective exposure remains loopback-only. ROS domain 0 is isolated by the same
|
||||
boundary. This distinction is part of the port evidence and must not be
|
||||
misreported as a host-wide `127.0.0.1` bind.
|
||||
|
||||
## 16. Delivery plan
|
||||
|
||||
| Phase | Estimate | Exit condition |
|
||||
|
|
@ -435,7 +448,10 @@ Implementation cards follow this order:
|
|||
SIM S0 requires all of the following factual evidence:
|
||||
|
||||
- worker inventory;
|
||||
- physical D-only placement, including WSL and Docker VHD;
|
||||
- physical D-only placement of the dedicated WSL VHD, sources, builds, caches
|
||||
and artifacts;
|
||||
- confirmation that S0 uses the dedicated D-only WSL and does not use the
|
||||
existing C-backed Docker Desktop runtime;
|
||||
- exact component/package pins and licenses;
|
||||
- one stock PX4 rover launch;
|
||||
- ROS 2 telemetry;
|
||||
|
|
@ -444,7 +460,7 @@ SIM S0 requires all of the following factual evidence:
|
|||
- clean stop with no orphan processes;
|
||||
- 1× resource baseline;
|
||||
- 2× resource baseline;
|
||||
- explicit `GO`, `PAUSE` or `BLOCKED` report.
|
||||
- explicit `GO`, `INCOMPLETE` 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
|
||||
|
|
|
|||
|
|
@ -44,13 +44,20 @@ reports easy to misuse.
|
|||
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
|
||||
11. SIM S0 targets the reviewed Windows 11 worker through a dedicated
|
||||
`MissionCore-Sim` WSL2 Ubuntu 24.04 distribution physically under
|
||||
`D:\NDC_MISSIONCORE`.
|
||||
12. SIM S0 does not depend on the existing Docker Desktop runtime. Its WSL
|
||||
backend is C-backed and currently serves unrelated co-tenants.
|
||||
13. 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
|
||||
14. 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.
|
||||
15. Real actuator authority requires a new decision and physical safety gate.
|
||||
16. S0 processes run inside an ephemeral loopback-only Linux network namespace.
|
||||
Micro XRCE-DDS Agent's UDP wildcard bind is acceptable only inside that
|
||||
boundary; no S0 listener may be reachable from the worker LAN or Windows
|
||||
host network.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
|
@ -67,7 +74,7 @@ reports easy to misuse.
|
|||
|
||||
## First implementation
|
||||
|
||||
The first implementation is deliberately read-only:
|
||||
The qualification doctor is deliberately read-only:
|
||||
|
||||
- `simulation/s0/qualification-profile.yaml` is the candidate stack,
|
||||
storage, clock, authority, port and process contract.
|
||||
|
|
@ -77,6 +84,12 @@ The first implementation is deliberately read-only:
|
|||
starting or modifying anything.
|
||||
- MISSIONCOR-40 tracks factual worker qualification.
|
||||
|
||||
The first target bootstrap also includes `simulation/s0/worker-smoke.sh`. It
|
||||
creates the reviewed loopback-only namespace, starts only the pinned stock-rover
|
||||
providers, observes ROS 2 vehicle status and Gazebo clock, requests PX4 shutdown,
|
||||
checks for owned-process residue and writes D-only factual artifacts. It never
|
||||
arms the vehicle or sends an actuator command.
|
||||
|
||||
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,126 @@
|
|||
# Lab 006: SIM S0 worker bootstrap and stock-rover smoke
|
||||
|
||||
Date: 2026-07-24 (Europe/Moscow).
|
||||
|
||||
## Scope
|
||||
|
||||
The reviewed Windows 11 AI worker received a dedicated Docker-free
|
||||
`MissionCore-Sim` WSL2 Ubuntu 24.04 distribution physically stored at
|
||||
`D:\NDC_MISSIONCORE\simulation\wsl\MissionCore-Sim`. Source, build, cache,
|
||||
runtime, artifacts and datasets are all under the same D-only root. Existing
|
||||
Docker Desktop co-tenants were inventoried but not stopped, reconfigured or
|
||||
used.
|
||||
|
||||
This lab grants no real or simulated actuator authority. Every smoke run kept
|
||||
`actuator_authority=false`; it observed the stock rover while disarmed.
|
||||
|
||||
## Redacted worker inventory
|
||||
|
||||
- Windows 11 Pro, build 26200;
|
||||
- WSL2 kernel `6.18.33.1-microsoft-standard-WSL2`;
|
||||
- Ubuntu 24.04.4 LTS, x86_64;
|
||||
- 13th Gen Intel Core i9-13900KF, 32 WSL-visible logical CPUs;
|
||||
- 50,427,760,640 WSL-visible RAM bytes and 12,884,901,888 swap bytes;
|
||||
- NVIDIA GeForce RTX 4090, driver 610.47, 24,564 MiB VRAM;
|
||||
- D capacity 2,000,381,014,016 bytes;
|
||||
- D free after bootstrap and builds: 392,758,685,696 bytes.
|
||||
|
||||
The remaining D capacity is above the 300 GiB S0 start threshold and 200 GiB
|
||||
stop threshold. This does not waive continuous disk monitoring.
|
||||
|
||||
## Installed candidate generation
|
||||
|
||||
- ROS 2 Jazzy `ros-base=0.11.0-1noble.20260616.084325`;
|
||||
- `ros-gz=1.0.22-1noble.20260616.074726`;
|
||||
- Gazebo Harmonic `1.0.0-1~noble`, Gazebo Sim `8.14.0-1~noble`,
|
||||
SDFormat `14.9.0-1~noble`;
|
||||
- PX4 Autopilot v1.17.0 commit
|
||||
`d6f12ad1c4f70ad3230afd7d86e971421e02fef4`;
|
||||
- `px4_msgs` v1.17.0 commit
|
||||
`86d8239e962f6939e05c3737784f60c02fa884db`;
|
||||
- PX4 Gazebo models commit
|
||||
`b6127f4ec20de867e215fb5f78ae88b80f371909`;
|
||||
- Micro XRCE-DDS Agent v2.4.3 commit
|
||||
`73622810d984349b80bbac0ef55fc0b694d62222`;
|
||||
- Nav2 `navigation2=1.3.12-1noble.20260615.181551` and
|
||||
`nav2-bringup=1.3.12-1noble.20260616.082701`.
|
||||
|
||||
PX4 SITL and `px4_msgs` were built successfully from the pinned D-only source.
|
||||
The Micro XRCE-DDS Agent was built from source and installed under the D-only
|
||||
runtime root. Nav2 is installed but has not yet passed an S2 navigation case.
|
||||
|
||||
## Observed smoke result
|
||||
|
||||
Two consecutive canonical runs and one preliminary resource run passed:
|
||||
|
||||
- `20260724T134400Z-stock-rover`;
|
||||
- `20260724T134600Z-stock-rover-repeat`;
|
||||
- `20260724T135000Z-stock-rover-baseline`.
|
||||
|
||||
The final smoke-script generation also passed as
|
||||
`20260724T135800Z-stock-rover-final` and added a runtime listener inventory plus
|
||||
the point resource snapshot.
|
||||
|
||||
Each run started PX4 v1.17.0 with Gazebo world `rover` and model
|
||||
`rover_ackermann_0`, established a Micro XRCE-DDS session, observed
|
||||
`/fmu/out/vehicle_status_v1`, observed Gazebo `/clock`, requested PX4 shutdown
|
||||
and found no owned runtime residue. The captured vehicle state was disarmed,
|
||||
`failsafe=false`, with pre-flight checks passing.
|
||||
|
||||
Each provider set ran inside a new Linux network namespace containing only
|
||||
`127.0.0.1` and `::1`. The Agent's UDP `0.0.0.0:8888` bind was therefore
|
||||
effective only inside that loopback boundary. No PX4, Gazebo or Agent service
|
||||
was exposed to the LAN.
|
||||
|
||||
The two canonical artifact directories are 68 KiB each. The preliminary
|
||||
resource run completed in 38.75 seconds. One point sample observed approximately
|
||||
202 MiB RSS for headless Gazebo, 19 MiB for PX4 and 17 MiB for the Agent. The
|
||||
worker GPU was already occupied by unrelated co-tenants; no S0 GPU allocation
|
||||
was attributable in this headless run. These point observations are not the
|
||||
accepted 1× or 2× resource baselines.
|
||||
|
||||
## Diagnostics retained
|
||||
|
||||
Earlier bootstrap attempts remain outside Git under the D-only artifact root:
|
||||
|
||||
- one check raced the asynchronous DDS connection even though the session
|
||||
subsequently established;
|
||||
- one used an unsupported ROS 2 Jazzy long option;
|
||||
- one used ROS domain 42 while PX4 v1.17.0 published on domain 0.
|
||||
|
||||
The smoke contract now waits for the Agent session plus the PX4
|
||||
`vehicle_status_v1` writer, uses Jazzy `topic list -t`, and relies on the
|
||||
loopback-only namespace to isolate domain 0.
|
||||
|
||||
An initial malformed interactive log repeatedly captured PX4 terminal repaint
|
||||
output and reached approximately 1.1 GiB. That generated diagnostic artifact
|
||||
was removed, then the case was rerun non-interactively; the valid standalone
|
||||
PX4 log was approximately 8 KiB.
|
||||
|
||||
## Target doctor
|
||||
|
||||
The exact candidate profile was executed through the read-only doctor inside
|
||||
`MissionCore-Sim`. Profile SHA-256:
|
||||
`70bf0df44c85dd57eb3cedb98a474a46ba498c6ffc60ce0d8a06829e28587df1`.
|
||||
|
||||
Target identity, authority, clock, port/process registries, all seven D-only
|
||||
mutable roots, the disk threshold and all eight required executables passed.
|
||||
Version acceptance and the nine-item digest-bound evidence set remain
|
||||
incomplete, so the doctor correctly returned `INCOMPLETE`.
|
||||
|
||||
## Verdict
|
||||
|
||||
`INCOMPLETE`.
|
||||
|
||||
Worker compatibility, D-only placement, exact candidate pins, PX4 build,
|
||||
stock-rover launch, DDS telemetry, authoritative clock observation and repeated
|
||||
clean lifecycle are factually proven. S0 still requires:
|
||||
|
||||
- pause/resume and deterministic single-step evidence;
|
||||
- 1× speed/real-time-factor acceptance;
|
||||
- isolated 2× resource/coexistence baseline;
|
||||
- a complete digest-bound evidence manifest;
|
||||
- final `GO`, `INCOMPLETE` or `BLOCKED` decision.
|
||||
|
||||
This lab does not accept Nav2 behavior, navigation safety, real-time guarantees,
|
||||
or real actuator control.
|
||||
|
|
@ -5,11 +5,17 @@ real vehicle control or a `GO` verdict by itself.
|
|||
|
||||
## Safety and placement
|
||||
|
||||
- Target: reviewed Windows 11 worker with WSL2 Ubuntu 24.04.
|
||||
- Target: reviewed Windows 11 worker with the dedicated `MissionCore-Sim` WSL2
|
||||
Ubuntu 24.04 distribution physically installed on D.
|
||||
- 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 place a WSL distribution, source, build, cache, log, bag, ULog,
|
||||
Gazebo artifact or dataset on C.
|
||||
- Do not use or reconfigure the existing Docker Desktop runtime in S0. Its
|
||||
backend is C-backed and serves unrelated co-tenants.
|
||||
- Do not expose S0 services beyond loopback.
|
||||
- Run providers inside the ephemeral loopback-only network namespace created
|
||||
by `simulation/s0/worker-smoke.sh`. Micro XRCE-DDS Agent binds UDP wildcard
|
||||
inside that namespace because v2.4.3 has no address-selection option.
|
||||
- Do not download full external datasets.
|
||||
- Keep `actuator_authority=false`.
|
||||
|
||||
|
|
@ -24,13 +30,34 @@ uv run missioncore-sim s0 doctor --json
|
|||
|
||||
Before target evidence exists, the expected verdict is `INCOMPLETE`.
|
||||
|
||||
## Stock-rover smoke
|
||||
|
||||
Copy the reviewed script to the D-only worker source root, then invoke it as
|
||||
root inside `MissionCore-Sim` with a unique immutable run ID:
|
||||
|
||||
```bash
|
||||
simulation/s0/worker-smoke.sh <UTC-run-id>-stock-rover
|
||||
```
|
||||
|
||||
The script:
|
||||
|
||||
- creates a new network namespace containing only loopback;
|
||||
- starts Micro XRCE-DDS Agent, PX4 SITL and the stock Gazebo Ackermann world;
|
||||
- verifies the DDS session, `/fmu/out/vehicle_status_v1` and Gazebo `/clock`;
|
||||
- records listeners and a non-acceptance resource point sample;
|
||||
- sends the PX4 `shutdown` command and rejects owned-process residue.
|
||||
|
||||
Do not reuse a run ID. A failed run remains diagnostic evidence; rerun with a
|
||||
new ID after correction.
|
||||
|
||||
## 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.
|
||||
3. Physical location of the dedicated WSL distribution and confirmation that
|
||||
the Docker-free S0 profile is active.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ profile_id: ai-worker-px4-gazebo-v1
|
|||
target_host:
|
||||
windows_release: windows-11
|
||||
virtualization: wsl2
|
||||
wsl_distribution: MissionCore-Sim
|
||||
linux_distribution: ubuntu-24.04
|
||||
architecture: x86_64
|
||||
container_runtime: none
|
||||
|
||||
storage:
|
||||
windows_root: 'D:\NDC_MISSIONCORE'
|
||||
|
|
@ -15,11 +17,8 @@ storage:
|
|||
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
|
||||
windows_path: 'D:\NDC_MISSIONCORE\simulation\wsl'
|
||||
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/wsl
|
||||
- id: source
|
||||
windows_path: 'D:\NDC_MISSIONCORE\simulation\source'
|
||||
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/source
|
||||
|
|
@ -56,24 +55,24 @@ authority:
|
|||
- rover-speed-yaw-rate/v1
|
||||
|
||||
ros:
|
||||
domain_id: 42
|
||||
domain_id: 0
|
||||
namespace: /missioncore/polygon/s0
|
||||
|
||||
components:
|
||||
- id: ros2
|
||||
source_url: https://docs.ros.org/en/jazzy/
|
||||
requested_ref: jazzy
|
||||
resolved_version:
|
||||
resolved_version: ros-base=0.11.0-1noble.20260616.084325;ros-gz=1.0.22-1noble.20260616.074726
|
||||
resolved_commit:
|
||||
license: mixed-apache-2.0-bsd
|
||||
qualification_state: pending-worker-resolution
|
||||
qualification_state: candidate
|
||||
- id: gazebo
|
||||
source_url: https://gazebosim.org/docs/harmonic/
|
||||
requested_ref: harmonic
|
||||
resolved_version:
|
||||
resolved_version: gz-harmonic=1.0.0-1~noble;gz-sim=8.14.0-1~noble;sdformat=14.9.0-1~noble
|
||||
resolved_commit:
|
||||
license: apache-2.0
|
||||
qualification_state: pending-worker-resolution
|
||||
qualification_state: candidate
|
||||
- id: px4-autopilot
|
||||
source_url: https://github.com/PX4/PX4-Autopilot
|
||||
requested_ref: v1.17.0
|
||||
|
|
@ -90,9 +89,9 @@ components:
|
|||
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
|
||||
requested_ref: PX4-Autopilot/v1.17.0-submodule
|
||||
resolved_version: px4-v1.17.0-submodule
|
||||
resolved_commit: b6127f4ec20de867e215fb5f78ae88b80f371909
|
||||
license: bsd-3-clause
|
||||
qualification_state: candidate
|
||||
- id: micro-xrce-dds-agent
|
||||
|
|
@ -105,38 +104,38 @@ components:
|
|||
- id: nav2
|
||||
source_url: https://github.com/ros-navigation/navigation2
|
||||
requested_ref: jazzy@2026-07-24
|
||||
resolved_version:
|
||||
resolved_version: navigation2=1.3.12-1noble.20260615.181551;nav2-bringup=1.3.12-1noble.20260616.082701
|
||||
resolved_commit: c92ea2fd9008f50c0ec8447610800214b2a0dafb
|
||||
license: apache-2.0
|
||||
qualification_state: candidate
|
||||
|
||||
ports:
|
||||
- id: micro-xrce-dds
|
||||
host_scope: worker-wsl
|
||||
host_scope: loopback-only-netns
|
||||
protocol: udp
|
||||
bind: 127.0.0.1
|
||||
bind: 0.0.0.0
|
||||
port: 8888
|
||||
owner: micro-xrce-dds-agent
|
||||
- id: px4-mavlink-sitl
|
||||
host_scope: worker-wsl
|
||||
- id: px4-mavlink-onboard
|
||||
host_scope: loopback-only-netns
|
||||
protocol: udp
|
||||
bind: 127.0.0.1
|
||||
port: 14540
|
||||
bind: 0.0.0.0
|
||||
port: 14580
|
||||
owner: px4-sitl
|
||||
- id: qgroundcontrol-reference
|
||||
host_scope: worker-wsl
|
||||
- id: px4-mavlink-normal
|
||||
host_scope: loopback-only-netns
|
||||
protocol: udp
|
||||
bind: 127.0.0.1
|
||||
port: 14550
|
||||
bind: 0.0.0.0
|
||||
port: 18570
|
||||
owner: px4-sitl
|
||||
- id: simulation-orchestrator-api
|
||||
host_scope: worker-wsl
|
||||
host_scope: loopback-only-netns
|
||||
protocol: tcp
|
||||
bind: 127.0.0.1
|
||||
port: 18080
|
||||
owner: simulation-orchestrator
|
||||
- id: simulation-telemetry
|
||||
host_scope: worker-wsl
|
||||
host_scope: loopback-only-netns
|
||||
protocol: tcp
|
||||
bind: 127.0.0.1
|
||||
port: 18081
|
||||
|
|
@ -164,9 +163,10 @@ processes:
|
|||
|
||||
required_tools:
|
||||
- git
|
||||
- docker
|
||||
- gz
|
||||
- make
|
||||
- cmake
|
||||
- ninja
|
||||
- colcon
|
||||
- ros2
|
||||
- MicroXRCEAgent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,259 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Run the SIM S0 stock-rover smoke case inside a loopback-only network
|
||||
# namespace. The script writes factual runtime artifacts to D and never grants
|
||||
# actuator authority.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
readonly EXPECTED_DISTRO="MissionCore-Sim"
|
||||
readonly D_ROOT="${MISSIONCORE_SIM_D_ROOT:-/mnt/d/NDC_MISSIONCORE/simulation}"
|
||||
readonly PX4_ROOT="${MISSIONCORE_SIM_PX4_ROOT:-${D_ROOT}/source/PX4-Autopilot}"
|
||||
readonly AGENT_PREFIX="${MISSIONCORE_SIM_AGENT_PREFIX:-${D_ROOT}/runtime/micro-xrce-dds-agent/2.4.3}"
|
||||
readonly PX4_MSGS_SETUP="${MISSIONCORE_SIM_PX4_MSGS_SETUP:-${D_ROOT}/build/ros2/px4_msgs/install/setup.bash}"
|
||||
readonly ARTIFACT_ROOT="${MISSIONCORE_SIM_ARTIFACT_ROOT:-${D_ROOT}/artifacts/s0}"
|
||||
readonly RUN_ID="${1:-$(date -u +%Y%m%dT%H%M%SZ)}"
|
||||
readonly RUN_DIR="${ARTIFACT_ROOT}/${RUN_ID}"
|
||||
|
||||
if [[ "${MISSIONCORE_S0_NETNS:-0}" != "1" ]]; then
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "error: initial worker-smoke invocation must run as root to create the network namespace" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "${WSL_DISTRO_NAME:-}" != "${EXPECTED_DISTRO}" ]]; then
|
||||
echo "error: expected WSL distro ${EXPECTED_DISTRO}, observed ${WSL_DISTRO_NAME:-unset}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exec unshare --net -- bash -c '
|
||||
set -e
|
||||
ip link set lo up
|
||||
exec runuser -u missioncore -- env MISSIONCORE_S0_NETNS=1 "$@"
|
||||
' bash "$0" "$@"
|
||||
fi
|
||||
|
||||
if [[ "${WSL_DISTRO_NAME:-}" != "${EXPECTED_DISTRO}" ]]; then
|
||||
echo "error: smoke case escaped the reviewed WSL distribution" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for required_path in "${PX4_ROOT}" "${AGENT_PREFIX}" "${PX4_MSGS_SETUP}" "${ARTIFACT_ROOT}"; do
|
||||
if [[ "${required_path}" != /mnt/d/NDC_MISSIONCORE/* ]]; then
|
||||
echo "error: mutable or executable path escaped D-only root: ${required_path}" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
readonly AGENT_BIN="${AGENT_PREFIX}/bin/MicroXRCEAgent"
|
||||
readonly AGENT_LOG="${RUN_DIR}/micro-xrce-dds-agent.log"
|
||||
readonly PX4_LOG="${RUN_DIR}/px4-rover.log"
|
||||
readonly BRIDGE_LOG="${RUN_DIR}/ros-gz-clock-bridge.log"
|
||||
readonly TOPICS_LOG="${RUN_DIR}/ros-topics.txt"
|
||||
readonly VEHICLE_STATUS_LOG="${RUN_DIR}/vehicle-status.yaml"
|
||||
readonly CLOCK_LOG="${RUN_DIR}/clock.yaml"
|
||||
readonly NETWORK_LOG="${RUN_DIR}/network.txt"
|
||||
readonly RESOURCE_LOG="${RUN_DIR}/resource-snapshot.txt"
|
||||
readonly RESULT_LOG="${RUN_DIR}/result.env"
|
||||
|
||||
for required_file in \
|
||||
"${PX4_ROOT}/build/px4_sitl_default/bin/px4" \
|
||||
"${PX4_ROOT}/Tools/simulation/gz/worlds/rover.sdf" \
|
||||
"${AGENT_BIN}" \
|
||||
"/opt/ros/jazzy/setup.bash" \
|
||||
"${PX4_MSGS_SETUP}"; do
|
||||
if [[ ! -e "${required_file}" ]]; then
|
||||
echo "error: required S0 runtime input is missing: ${required_file}" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "${RUN_DIR}"
|
||||
|
||||
agent_pid=""
|
||||
px4_pgid=""
|
||||
bridge_pgid=""
|
||||
|
||||
stop_process_group() {
|
||||
local pgid="${1:-}"
|
||||
if [[ -z "${pgid}" ]]; then
|
||||
return
|
||||
fi
|
||||
if kill -0 -- "-${pgid}" 2>/dev/null; then
|
||||
kill -INT -- "-${pgid}" 2>/dev/null || true
|
||||
for _ in {1..20}; do
|
||||
if ! kill -0 -- "-${pgid}" 2>/dev/null; then
|
||||
return
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
kill -TERM -- "-${pgid}" 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -KILL -- "-${pgid}" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
exec 3>&-
|
||||
stop_process_group "${bridge_pgid}"
|
||||
stop_process_group "${px4_pgid}"
|
||||
if [[ -n "${agent_pid}" ]] && kill -0 "${agent_pid}" 2>/dev/null; then
|
||||
kill -TERM "${agent_pid}" 2>/dev/null
|
||||
wait "${agent_pid}" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
wait_for_pattern() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
local attempts="$3"
|
||||
for ((attempt = 0; attempt < attempts; attempt += 1)); do
|
||||
if grep -Fq "${pattern}" "${file}" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
env \
|
||||
LD_LIBRARY_PATH="${AGENT_PREFIX}/lib" \
|
||||
"${AGENT_BIN}" udp4 -p 8888 -v 4 >"${AGENT_LOG}" 2>&1 &
|
||||
agent_pid="$!"
|
||||
sleep 1
|
||||
if ! kill -0 "${agent_pid}" 2>/dev/null; then
|
||||
echo "error: Micro XRCE-DDS Agent exited during startup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "network_namespace=loopback-only"
|
||||
ip -brief address
|
||||
ss -lunp
|
||||
} >"${NETWORK_LOG}"
|
||||
|
||||
coproc PX4_PROCESS {
|
||||
exec setsid env HEADLESS=1 make -C "${PX4_ROOT}" \
|
||||
px4_sitl gz_rover_ackermann >"${PX4_LOG}" 2>&1
|
||||
}
|
||||
px4_pgid="${PX4_PROCESS_PID}"
|
||||
exec 3>&"${PX4_PROCESS[1]}"
|
||||
|
||||
if ! wait_for_pattern "${PX4_LOG}" "Startup script returned successfully" 120; then
|
||||
echo "error: PX4 stock rover did not reach startup completion" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 3
|
||||
printf '%s\n' \
|
||||
"ver all" \
|
||||
"commander status" >&3
|
||||
|
||||
if ! wait_for_pattern "${AGENT_LOG}" "session established" 60 \
|
||||
|| ! wait_for_pattern \
|
||||
"${PX4_LOG}" \
|
||||
"successfully created rt/fmu/out/vehicle_status_v1 data writer" \
|
||||
60; then
|
||||
echo "error: PX4 uXRCE-DDS client did not connect to the loopback agent" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "uxrce_dds_client status" >&3
|
||||
if ! wait_for_pattern "${PX4_LOG}" "Running, connected" 20; then
|
||||
echo "error: PX4 did not report the established uXRCE-DDS session" >&2
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo
|
||||
echo "runtime_listeners=loopback-only-netns"
|
||||
ss -lunp
|
||||
} >>"${NETWORK_LOG}"
|
||||
|
||||
set +u
|
||||
# shellcheck source=/dev/null
|
||||
source /opt/ros/jazzy/setup.bash
|
||||
# shellcheck source=/dev/null
|
||||
source "${PX4_MSGS_SETUP}"
|
||||
set -u
|
||||
export ROS_DOMAIN_ID=0
|
||||
export ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST
|
||||
unset ROS_LOCALHOST_ONLY
|
||||
|
||||
setsid ros2 run ros_gz_bridge parameter_bridge \
|
||||
'/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock' >"${BRIDGE_LOG}" 2>&1 &
|
||||
bridge_pgid="$!"
|
||||
|
||||
timeout 20s ros2 topic list -t >"${TOPICS_LOG}"
|
||||
vehicle_status_topic="/fmu/out/vehicle_status"
|
||||
if grep -Fq "/fmu/out/vehicle_status_v1" "${TOPICS_LOG}"; then
|
||||
vehicle_status_topic="/fmu/out/vehicle_status_v1"
|
||||
fi
|
||||
timeout 20s ros2 topic echo --once \
|
||||
"${vehicle_status_topic}" >"${VEHICLE_STATUS_LOG}"
|
||||
timeout 20s ros2 topic echo --once \
|
||||
/clock rosgraph_msgs/msg/Clock >"${CLOCK_LOG}"
|
||||
|
||||
{
|
||||
echo "sample=non-acceptance-point-snapshot"
|
||||
free -b
|
||||
ps -g "${px4_pgid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
|
||||
ps -p "${agent_pid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
|
||||
ps -g "${bridge_pgid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
|
||||
if command -v nvidia-smi >/dev/null; then
|
||||
nvidia-smi \
|
||||
--query-gpu=memory.used,utilization.gpu \
|
||||
--format=csv,noheader,nounits
|
||||
fi
|
||||
} >"${RESOURCE_LOG}"
|
||||
|
||||
printf '%s\n' "shutdown" >&3
|
||||
if ! wait_for_pattern "${PX4_LOG}" "Exiting NOW." 20; then
|
||||
echo "error: PX4 did not acknowledge the shutdown command" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup
|
||||
trap - EXIT INT TERM
|
||||
|
||||
px4_status="fail"
|
||||
dds_status="fail"
|
||||
vehicle_status="fail"
|
||||
clock_status="fail"
|
||||
lifecycle_status="fail"
|
||||
|
||||
grep -Fq "world: rover, model: rover_ackermann_0" "${PX4_LOG}" && px4_status="pass"
|
||||
if grep -Fq "session established" "${AGENT_LOG}" \
|
||||
&& grep -Fq \
|
||||
"successfully created rt/fmu/out/vehicle_status_v1 data writer" \
|
||||
"${PX4_LOG}"; then
|
||||
dds_status="pass"
|
||||
fi
|
||||
grep -Fq "arming_state:" "${VEHICLE_STATUS_LOG}" && vehicle_status="pass"
|
||||
grep -Fq "clock:" "${CLOCK_LOG}" && clock_status="pass"
|
||||
if ! pgrep -f "${PX4_ROOT}/build/px4_sitl_default/bin/px4" >/dev/null \
|
||||
&& ! pgrep -f "gz sim.*rover.sdf" >/dev/null \
|
||||
&& ! pgrep -f "${AGENT_BIN}" >/dev/null; then
|
||||
lifecycle_status="pass"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "run_id=${RUN_ID}"
|
||||
echo "network_scope=loopback-only-netns"
|
||||
echo "actuator_authority=false"
|
||||
echo "px4_stock_rover=${px4_status}"
|
||||
echo "uxrce_dds=${dds_status}"
|
||||
echo "ros2_vehicle_status=${vehicle_status}"
|
||||
echo "gazebo_clock=${clock_status}"
|
||||
echo "clean_lifecycle=${lifecycle_status}"
|
||||
} >"${RESULT_LOG}"
|
||||
|
||||
if [[ "${px4_status}" != "pass" \
|
||||
|| "${dds_status}" != "pass" \
|
||||
|| "${vehicle_status}" != "pass" \
|
||||
|| "${clock_status}" != "pass" \
|
||||
|| "${lifecycle_status}" != "pass" ]]; then
|
||||
echo "error: one or more S0 smoke checks failed; inspect ${RUN_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SIM S0 smoke PASS: ${RUN_DIR}"
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
|
|
@ -42,8 +43,10 @@ class DoctorVerdict(StrEnum):
|
|||
class TargetHost:
|
||||
windows_release: str
|
||||
virtualization: str
|
||||
wsl_distribution: str
|
||||
linux_distribution: str
|
||||
architecture: str
|
||||
container_runtime: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
@ -308,22 +311,34 @@ def _parse_target_host(value: object) -> TargetHost:
|
|||
required={
|
||||
"windows_release",
|
||||
"virtualization",
|
||||
"wsl_distribution",
|
||||
"linux_distribution",
|
||||
"architecture",
|
||||
"container_runtime",
|
||||
},
|
||||
context="target_host",
|
||||
)
|
||||
target = TargetHost(
|
||||
windows_release=_string(data["windows_release"], "target_host.windows_release"),
|
||||
virtualization=_string(data["virtualization"], "target_host.virtualization"),
|
||||
wsl_distribution=_string(
|
||||
data["wsl_distribution"],
|
||||
"target_host.wsl_distribution",
|
||||
),
|
||||
linux_distribution=_string(
|
||||
data["linux_distribution"],
|
||||
"target_host.linux_distribution",
|
||||
),
|
||||
architecture=_string(data["architecture"], "target_host.architecture"),
|
||||
container_runtime=_string(
|
||||
data["container_runtime"],
|
||||
"target_host.container_runtime",
|
||||
),
|
||||
)
|
||||
if target.windows_release != "windows-11" or target.virtualization != "wsl2":
|
||||
raise S0ProfileError("S0 target must remain the reviewed Windows 11 WSL2 worker")
|
||||
if target.wsl_distribution != "MissionCore-Sim" or target.container_runtime != "none":
|
||||
raise S0ProfileError("S0 must use the dedicated Docker-free MissionCore-Sim distro")
|
||||
if target.linux_distribution != "ubuntu-24.04" or target.architecture != "x86_64":
|
||||
raise S0ProfileError("S0 target Linux distribution or architecture drifted")
|
||||
return target
|
||||
|
|
@ -583,7 +598,12 @@ def _parse_ports(value: object) -> tuple[PortBinding, ...]:
|
|||
owner = _safe_id(data["owner"], f"ports[{index}].owner")
|
||||
if protocol not in {"tcp", "udp"}:
|
||||
raise S0ProfileError(f"port {identifier!r} has unsupported protocol")
|
||||
if bind != "127.0.0.1":
|
||||
if bind == "0.0.0.0":
|
||||
if host_scope != "loopback-only-netns":
|
||||
raise S0ProfileError(
|
||||
f"port {identifier!r} wildcard bind requires loopback-only-netns scope"
|
||||
)
|
||||
elif bind != "127.0.0.1":
|
||||
raise S0ProfileError(f"port {identifier!r} must remain loopback-only in S0")
|
||||
if not 1 <= port <= 65535:
|
||||
raise S0ProfileError(f"port {identifier!r} is outside 1..65535")
|
||||
|
|
@ -666,15 +686,17 @@ def _target_host_match(target: TargetHost) -> tuple[bool, str]:
|
|||
os_release.get("id") == "ubuntu" and os_release.get("version_id") == "24.04"
|
||||
)
|
||||
architecture_match = machine in {"x86_64", "amd64"}
|
||||
matched = is_wsl2 and distribution_match and architecture_match
|
||||
distro_name_match = os.environ.get("WSL_DISTRO_NAME") == target.wsl_distribution
|
||||
matched = is_wsl2 and distribution_match and architecture_match and distro_name_match
|
||||
if matched:
|
||||
return True, (
|
||||
f"matched {target.windows_release}/{target.virtualization}/"
|
||||
f"{target.linux_distribution}/{target.architecture}"
|
||||
f"{target.wsl_distribution}/{target.linux_distribution}/{target.architecture}"
|
||||
)
|
||||
return False, (
|
||||
"doctor is not running inside the reviewed Windows 11 WSL2 Ubuntu 24.04 "
|
||||
f"x86_64 target (observed system={system!r}, machine={machine!r})"
|
||||
f"x86_64 MissionCore-Sim target (observed system={system!r}, machine={machine!r}, "
|
||||
f"wsl_distribution={os.environ.get('WSL_DISTRO_NAME')!r})"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -822,10 +844,7 @@ def _evidence_check(
|
|||
if artifact_candidate.is_symlink():
|
||||
raise S0ProfileError(f"evidence artifact {identifier!r} cannot be a symlink")
|
||||
artifact = artifact_candidate.resolve(strict=True)
|
||||
if (
|
||||
not artifact.is_relative_to(evidence_root)
|
||||
or not artifact.is_file()
|
||||
):
|
||||
if not artifact.is_relative_to(evidence_root) or not artifact.is_file():
|
||||
raise S0ProfileError(f"evidence artifact {identifier!r} is not a regular file")
|
||||
expected_sha256 = _string(
|
||||
entry["sha256"],
|
||||
|
|
@ -966,8 +985,7 @@ def _tool_list(value: object, context: str) -> tuple[str, ...]:
|
|||
|
||||
def _string_list(value: object, context: str) -> tuple[str, ...]:
|
||||
result = tuple(
|
||||
_string(item, f"{context}[{index}]")
|
||||
for index, item in enumerate(_sequence(value, context))
|
||||
_string(item, f"{context}[{index}]") for index, item in enumerate(_sequence(value, context))
|
||||
)
|
||||
if len(set(result)) != len(result):
|
||||
raise S0ProfileError(f"{context} values must be unique")
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ 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.target_host.wsl_distribution == "MissionCore-Sim"
|
||||
assert profile.target_host.container_runtime == "none"
|
||||
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"
|
||||
|
|
@ -46,13 +48,23 @@ def test_canonical_s0_profile_is_strict_and_safe() -> None:
|
|||
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 profile.ros.domain_id == 0
|
||||
assert "docker" not in profile.required_tools
|
||||
assert {item.identifier for item in profile.components} >= {
|
||||
"px4-autopilot",
|
||||
"px4-msgs",
|
||||
"gazebo",
|
||||
"nav2",
|
||||
}
|
||||
components = {item.identifier: item for item in profile.components}
|
||||
assert components["px4-gazebo-models"].resolved_commit == (
|
||||
"b6127f4ec20de867e215fb5f78ae88b80f371909"
|
||||
)
|
||||
assert components["ros2"].qualification_state == "candidate"
|
||||
assert components["gazebo"].qualification_state == "candidate"
|
||||
micro_xrce = next(item for item in profile.ports if item.identifier == "micro-xrce-dds")
|
||||
assert micro_xrce.host_scope == "loopback-only-netns"
|
||||
assert micro_xrce.bind == "0.0.0.0"
|
||||
assert {item.identifier for item in profile.processes} == {
|
||||
"simulation-orchestrator",
|
||||
"gazebo-server",
|
||||
|
|
@ -100,6 +112,20 @@ def test_profile_rejects_duplicate_scoped_port(tmp_path: Path) -> None:
|
|||
load_s0_profile(_write_profile(tmp_path, document))
|
||||
|
||||
|
||||
def test_profile_rejects_wildcard_bind_outside_loopback_namespace(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
document = _profile_document()
|
||||
ports = document["ports"]
|
||||
assert isinstance(ports, list)
|
||||
first = ports[0]
|
||||
assert isinstance(first, dict)
|
||||
first["host_scope"] = "worker-wsl"
|
||||
|
||||
with pytest.raises(S0ProfileError, match="wildcard bind requires loopback-only-netns"):
|
||||
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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue