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:
|
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;
|
- SIM S0 has a strict candidate qualification profile and read-only doctor;
|
||||||
- no target-worker S0 evidence has been admitted;
|
- the dedicated D-only worker distribution has been created and the candidate
|
||||||
- PX4/Gazebo/ROS 2/Nav2 have not been installed or accepted by this repository;
|
PX4/Gazebo/ROS 2/Nav2 stack has been installed without using Docker;
|
||||||
- no stock rover run has been accepted;
|
- 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`;
|
- `actuator_authority=false`;
|
||||||
- `navigation_or_safety_accepted=false`.
|
- `navigation_or_safety_accepted=false`.
|
||||||
|
|
||||||
|
|
@ -369,7 +372,8 @@ incompatible.
|
||||||
The S0 candidate line is:
|
The S0 candidate line is:
|
||||||
|
|
||||||
- Windows 11 AI worker;
|
- 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;
|
- ROS 2 Jazzy;
|
||||||
- Gazebo Harmonic;
|
- Gazebo Harmonic;
|
||||||
- PX4 Autopilot v1.17.0;
|
- PX4 Autopilot v1.17.0;
|
||||||
|
|
@ -378,9 +382,10 @@ The S0 candidate line is:
|
||||||
- Nav2 Jazzy;
|
- Nav2 Jazzy;
|
||||||
- PX4 Gazebo stock rover models.
|
- PX4 Gazebo stock rover models.
|
||||||
|
|
||||||
The exact source commits already resolved in the candidate profile do not become
|
The exact source commits and package versions are resolved in the candidate
|
||||||
accepted merely because they are present. ROS/Gazebo package versions and every
|
profile. Target installation, PX4 build, DDS telemetry and repeated stock-rover
|
||||||
candidate build remain pending target-worker qualification.
|
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
|
S1 uses direct exact `px4_msgs` integration. The experimental
|
||||||
`px4-ros2-interface-lib` is evaluated later and is not on the S1 critical path.
|
`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
|
All mutable worker bytes must be physically placed under
|
||||||
`D:\NDC_MISSIONCORE`, including:
|
`D:\NDC_MISSIONCORE`, including:
|
||||||
|
|
||||||
- WSL distribution/VHD;
|
- the dedicated `MissionCore-Sim` WSL distribution/VHD;
|
||||||
- Docker data and images;
|
- Docker data and images if a later profile elects to use containers; the S0
|
||||||
|
profile explicitly does not;
|
||||||
- sources and build products;
|
- sources and build products;
|
||||||
- package/model caches;
|
- package/model caches;
|
||||||
- runtime and logs;
|
- 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
|
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.
|
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
|
## 16. Delivery plan
|
||||||
|
|
||||||
| Phase | Estimate | Exit condition |
|
| Phase | Estimate | Exit condition |
|
||||||
|
|
@ -435,7 +448,10 @@ Implementation cards follow this order:
|
||||||
SIM S0 requires all of the following factual evidence:
|
SIM S0 requires all of the following factual evidence:
|
||||||
|
|
||||||
- worker inventory;
|
- 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;
|
- exact component/package pins and licenses;
|
||||||
- one stock PX4 rover launch;
|
- one stock PX4 rover launch;
|
||||||
- ROS 2 telemetry;
|
- ROS 2 telemetry;
|
||||||
|
|
@ -444,7 +460,7 @@ SIM S0 requires all of the following factual evidence:
|
||||||
- clean stop with no orphan processes;
|
- clean stop with no orphan processes;
|
||||||
- 1× resource baseline;
|
- 1× resource baseline;
|
||||||
- 2× 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.
|
The repository profile and doctor implement preflight and evidence admission.
|
||||||
They do not install packages, start processes, open ports or write a target
|
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.
|
9. Source-of-record, derived artifacts and presentation state remain separate.
|
||||||
Reset creates a new immutable run/episode.
|
Reset creates a new immutable run/episode.
|
||||||
10. Ground truth is evaluator-only and is not visible to perception or planning.
|
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
|
11. SIM S0 targets the reviewed Windows 11 worker through a dedicated
|
||||||
physically under `D:\NDC_MISSIONCORE`.
|
`MissionCore-Sim` WSL2 Ubuntu 24.04 distribution physically under
|
||||||
12. S1 uses exact `px4_msgs` directly. The experimental
|
`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.
|
`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.
|
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
|
## Consequences
|
||||||
|
|
||||||
|
|
@ -67,7 +74,7 @@ reports easy to misuse.
|
||||||
|
|
||||||
## First implementation
|
## 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,
|
- `simulation/s0/qualification-profile.yaml` is the candidate stack,
|
||||||
storage, clock, authority, port and process contract.
|
storage, clock, authority, port and process contract.
|
||||||
|
|
@ -77,6 +84,12 @@ The first implementation is deliberately read-only:
|
||||||
starting or modifying anything.
|
starting or modifying anything.
|
||||||
- MISSIONCOR-40 tracks factual worker qualification.
|
- 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
|
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
|
resolved/accepted pins and all required evidence. It cannot infer `GO` from the
|
||||||
repository state.
|
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
|
## 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`.
|
- All mutable bytes must be under `D:\NDC_MISSIONCORE`.
|
||||||
- Do not place a WSL distribution, Docker VHD, source, build, cache, log, bag,
|
- Do not place a WSL distribution, source, build, cache, log, bag, ULog,
|
||||||
ULog, Gazebo artifact or dataset on C.
|
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.
|
- 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.
|
- Do not download full external datasets.
|
||||||
- Keep `actuator_authority=false`.
|
- Keep `actuator_authority=false`.
|
||||||
|
|
||||||
|
|
@ -24,13 +30,34 @@ uv run missioncore-sim s0 doctor --json
|
||||||
|
|
||||||
Before target evidence exists, the expected verdict is `INCOMPLETE`.
|
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
|
## Required target inventory
|
||||||
|
|
||||||
Record a redacted artifact for each of:
|
Record a redacted artifact for each of:
|
||||||
|
|
||||||
1. Windows release, WSL version, Ubuntu version and architecture.
|
1. Windows release, WSL version, Ubuntu version and architecture.
|
||||||
2. GPU/driver, CPU, RAM and VRAM.
|
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.
|
4. D total/free bytes before installation.
|
||||||
5. Existing Docker images, WSL distributions and simulation-related processes.
|
5. Existing Docker images, WSL distributions and simulation-related processes.
|
||||||
6. Existing listeners and the candidate port registry.
|
6. Existing listeners and the candidate port registry.
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ profile_id: ai-worker-px4-gazebo-v1
|
||||||
target_host:
|
target_host:
|
||||||
windows_release: windows-11
|
windows_release: windows-11
|
||||||
virtualization: wsl2
|
virtualization: wsl2
|
||||||
|
wsl_distribution: MissionCore-Sim
|
||||||
linux_distribution: ubuntu-24.04
|
linux_distribution: ubuntu-24.04
|
||||||
architecture: x86_64
|
architecture: x86_64
|
||||||
|
container_runtime: none
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
windows_root: 'D:\NDC_MISSIONCORE'
|
windows_root: 'D:\NDC_MISSIONCORE'
|
||||||
|
|
@ -15,11 +17,8 @@ storage:
|
||||||
forbid_windows_system_drive: true
|
forbid_windows_system_drive: true
|
||||||
mutable_paths:
|
mutable_paths:
|
||||||
- id: wsl
|
- id: wsl
|
||||||
windows_path: 'D:\NDC_MISSIONCORE\wsl'
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\wsl'
|
||||||
wsl_path: /mnt/d/NDC_MISSIONCORE/wsl
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/wsl
|
||||||
- id: docker
|
|
||||||
windows_path: 'D:\NDC_MISSIONCORE\docker'
|
|
||||||
wsl_path: /mnt/d/NDC_MISSIONCORE/docker
|
|
||||||
- id: source
|
- id: source
|
||||||
windows_path: 'D:\NDC_MISSIONCORE\simulation\source'
|
windows_path: 'D:\NDC_MISSIONCORE\simulation\source'
|
||||||
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/source
|
wsl_path: /mnt/d/NDC_MISSIONCORE/simulation/source
|
||||||
|
|
@ -56,24 +55,24 @@ authority:
|
||||||
- rover-speed-yaw-rate/v1
|
- rover-speed-yaw-rate/v1
|
||||||
|
|
||||||
ros:
|
ros:
|
||||||
domain_id: 42
|
domain_id: 0
|
||||||
namespace: /missioncore/polygon/s0
|
namespace: /missioncore/polygon/s0
|
||||||
|
|
||||||
components:
|
components:
|
||||||
- id: ros2
|
- id: ros2
|
||||||
source_url: https://docs.ros.org/en/jazzy/
|
source_url: https://docs.ros.org/en/jazzy/
|
||||||
requested_ref: 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:
|
resolved_commit:
|
||||||
license: mixed-apache-2.0-bsd
|
license: mixed-apache-2.0-bsd
|
||||||
qualification_state: pending-worker-resolution
|
qualification_state: candidate
|
||||||
- id: gazebo
|
- id: gazebo
|
||||||
source_url: https://gazebosim.org/docs/harmonic/
|
source_url: https://gazebosim.org/docs/harmonic/
|
||||||
requested_ref: 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:
|
resolved_commit:
|
||||||
license: apache-2.0
|
license: apache-2.0
|
||||||
qualification_state: pending-worker-resolution
|
qualification_state: candidate
|
||||||
- id: px4-autopilot
|
- id: px4-autopilot
|
||||||
source_url: https://github.com/PX4/PX4-Autopilot
|
source_url: https://github.com/PX4/PX4-Autopilot
|
||||||
requested_ref: v1.17.0
|
requested_ref: v1.17.0
|
||||||
|
|
@ -90,9 +89,9 @@ components:
|
||||||
qualification_state: candidate
|
qualification_state: candidate
|
||||||
- id: px4-gazebo-models
|
- id: px4-gazebo-models
|
||||||
source_url: https://github.com/PX4/PX4-gazebo-models
|
source_url: https://github.com/PX4/PX4-gazebo-models
|
||||||
requested_ref: main@2026-07-24
|
requested_ref: PX4-Autopilot/v1.17.0-submodule
|
||||||
resolved_version:
|
resolved_version: px4-v1.17.0-submodule
|
||||||
resolved_commit: e00d3b9cde682dbcb3bf6f30a2f2b8ef4325dae8
|
resolved_commit: b6127f4ec20de867e215fb5f78ae88b80f371909
|
||||||
license: bsd-3-clause
|
license: bsd-3-clause
|
||||||
qualification_state: candidate
|
qualification_state: candidate
|
||||||
- id: micro-xrce-dds-agent
|
- id: micro-xrce-dds-agent
|
||||||
|
|
@ -105,38 +104,38 @@ components:
|
||||||
- id: nav2
|
- id: nav2
|
||||||
source_url: https://github.com/ros-navigation/navigation2
|
source_url: https://github.com/ros-navigation/navigation2
|
||||||
requested_ref: jazzy@2026-07-24
|
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
|
resolved_commit: c92ea2fd9008f50c0ec8447610800214b2a0dafb
|
||||||
license: apache-2.0
|
license: apache-2.0
|
||||||
qualification_state: candidate
|
qualification_state: candidate
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- id: micro-xrce-dds
|
- id: micro-xrce-dds
|
||||||
host_scope: worker-wsl
|
host_scope: loopback-only-netns
|
||||||
protocol: udp
|
protocol: udp
|
||||||
bind: 127.0.0.1
|
bind: 0.0.0.0
|
||||||
port: 8888
|
port: 8888
|
||||||
owner: micro-xrce-dds-agent
|
owner: micro-xrce-dds-agent
|
||||||
- id: px4-mavlink-sitl
|
- id: px4-mavlink-onboard
|
||||||
host_scope: worker-wsl
|
host_scope: loopback-only-netns
|
||||||
protocol: udp
|
protocol: udp
|
||||||
bind: 127.0.0.1
|
bind: 0.0.0.0
|
||||||
port: 14540
|
port: 14580
|
||||||
owner: px4-sitl
|
owner: px4-sitl
|
||||||
- id: qgroundcontrol-reference
|
- id: px4-mavlink-normal
|
||||||
host_scope: worker-wsl
|
host_scope: loopback-only-netns
|
||||||
protocol: udp
|
protocol: udp
|
||||||
bind: 127.0.0.1
|
bind: 0.0.0.0
|
||||||
port: 14550
|
port: 18570
|
||||||
owner: px4-sitl
|
owner: px4-sitl
|
||||||
- id: simulation-orchestrator-api
|
- id: simulation-orchestrator-api
|
||||||
host_scope: worker-wsl
|
host_scope: loopback-only-netns
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
bind: 127.0.0.1
|
bind: 127.0.0.1
|
||||||
port: 18080
|
port: 18080
|
||||||
owner: simulation-orchestrator
|
owner: simulation-orchestrator
|
||||||
- id: simulation-telemetry
|
- id: simulation-telemetry
|
||||||
host_scope: worker-wsl
|
host_scope: loopback-only-netns
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
bind: 127.0.0.1
|
bind: 127.0.0.1
|
||||||
port: 18081
|
port: 18081
|
||||||
|
|
@ -164,9 +163,10 @@ processes:
|
||||||
|
|
||||||
required_tools:
|
required_tools:
|
||||||
- git
|
- git
|
||||||
- docker
|
|
||||||
- gz
|
- gz
|
||||||
- make
|
- make
|
||||||
|
- cmake
|
||||||
|
- ninja
|
||||||
- colcon
|
- colcon
|
||||||
- ros2
|
- ros2
|
||||||
- MicroXRCEAgent
|
- 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
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
@ -42,8 +43,10 @@ class DoctorVerdict(StrEnum):
|
||||||
class TargetHost:
|
class TargetHost:
|
||||||
windows_release: str
|
windows_release: str
|
||||||
virtualization: str
|
virtualization: str
|
||||||
|
wsl_distribution: str
|
||||||
linux_distribution: str
|
linux_distribution: str
|
||||||
architecture: str
|
architecture: str
|
||||||
|
container_runtime: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
|
|
@ -308,22 +311,34 @@ def _parse_target_host(value: object) -> TargetHost:
|
||||||
required={
|
required={
|
||||||
"windows_release",
|
"windows_release",
|
||||||
"virtualization",
|
"virtualization",
|
||||||
|
"wsl_distribution",
|
||||||
"linux_distribution",
|
"linux_distribution",
|
||||||
"architecture",
|
"architecture",
|
||||||
|
"container_runtime",
|
||||||
},
|
},
|
||||||
context="target_host",
|
context="target_host",
|
||||||
)
|
)
|
||||||
target = TargetHost(
|
target = TargetHost(
|
||||||
windows_release=_string(data["windows_release"], "target_host.windows_release"),
|
windows_release=_string(data["windows_release"], "target_host.windows_release"),
|
||||||
virtualization=_string(data["virtualization"], "target_host.virtualization"),
|
virtualization=_string(data["virtualization"], "target_host.virtualization"),
|
||||||
|
wsl_distribution=_string(
|
||||||
|
data["wsl_distribution"],
|
||||||
|
"target_host.wsl_distribution",
|
||||||
|
),
|
||||||
linux_distribution=_string(
|
linux_distribution=_string(
|
||||||
data["linux_distribution"],
|
data["linux_distribution"],
|
||||||
"target_host.linux_distribution",
|
"target_host.linux_distribution",
|
||||||
),
|
),
|
||||||
architecture=_string(data["architecture"], "target_host.architecture"),
|
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":
|
if target.windows_release != "windows-11" or target.virtualization != "wsl2":
|
||||||
raise S0ProfileError("S0 target must remain the reviewed Windows 11 WSL2 worker")
|
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":
|
if target.linux_distribution != "ubuntu-24.04" or target.architecture != "x86_64":
|
||||||
raise S0ProfileError("S0 target Linux distribution or architecture drifted")
|
raise S0ProfileError("S0 target Linux distribution or architecture drifted")
|
||||||
return target
|
return target
|
||||||
|
|
@ -583,7 +598,12 @@ def _parse_ports(value: object) -> tuple[PortBinding, ...]:
|
||||||
owner = _safe_id(data["owner"], f"ports[{index}].owner")
|
owner = _safe_id(data["owner"], f"ports[{index}].owner")
|
||||||
if protocol not in {"tcp", "udp"}:
|
if protocol not in {"tcp", "udp"}:
|
||||||
raise S0ProfileError(f"port {identifier!r} has unsupported protocol")
|
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")
|
raise S0ProfileError(f"port {identifier!r} must remain loopback-only in S0")
|
||||||
if not 1 <= port <= 65535:
|
if not 1 <= port <= 65535:
|
||||||
raise S0ProfileError(f"port {identifier!r} is outside 1..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"
|
os_release.get("id") == "ubuntu" and os_release.get("version_id") == "24.04"
|
||||||
)
|
)
|
||||||
architecture_match = machine in {"x86_64", "amd64"}
|
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:
|
if matched:
|
||||||
return True, (
|
return True, (
|
||||||
f"matched {target.windows_release}/{target.virtualization}/"
|
f"matched {target.windows_release}/{target.virtualization}/"
|
||||||
f"{target.linux_distribution}/{target.architecture}"
|
f"{target.wsl_distribution}/{target.linux_distribution}/{target.architecture}"
|
||||||
)
|
)
|
||||||
return False, (
|
return False, (
|
||||||
"doctor is not running inside the reviewed Windows 11 WSL2 Ubuntu 24.04 "
|
"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():
|
if artifact_candidate.is_symlink():
|
||||||
raise S0ProfileError(f"evidence artifact {identifier!r} cannot be a symlink")
|
raise S0ProfileError(f"evidence artifact {identifier!r} cannot be a symlink")
|
||||||
artifact = artifact_candidate.resolve(strict=True)
|
artifact = artifact_candidate.resolve(strict=True)
|
||||||
if (
|
if not artifact.is_relative_to(evidence_root) or not artifact.is_file():
|
||||||
not artifact.is_relative_to(evidence_root)
|
|
||||||
or not artifact.is_file()
|
|
||||||
):
|
|
||||||
raise S0ProfileError(f"evidence artifact {identifier!r} is not a regular file")
|
raise S0ProfileError(f"evidence artifact {identifier!r} is not a regular file")
|
||||||
expected_sha256 = _string(
|
expected_sha256 = _string(
|
||||||
entry["sha256"],
|
entry["sha256"],
|
||||||
|
|
@ -966,8 +985,7 @@ def _tool_list(value: object, context: str) -> tuple[str, ...]:
|
||||||
|
|
||||||
def _string_list(value: object, context: str) -> tuple[str, ...]:
|
def _string_list(value: object, context: str) -> tuple[str, ...]:
|
||||||
result = tuple(
|
result = tuple(
|
||||||
_string(item, f"{context}[{index}]")
|
_string(item, f"{context}[{index}]") for index, item in enumerate(_sequence(value, context))
|
||||||
for index, item in enumerate(_sequence(value, context))
|
|
||||||
)
|
)
|
||||||
if len(set(result)) != len(result):
|
if len(set(result)) != len(result):
|
||||||
raise S0ProfileError(f"{context} values must be unique")
|
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)
|
profile = load_s0_profile(PROFILE)
|
||||||
|
|
||||||
assert profile.profile_id == "ai-worker-px4-gazebo-v1"
|
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.windows_root.drive.upper() == "D:"
|
||||||
assert profile.storage.wsl_root.as_posix() == "/mnt/d/NDC_MISSIONCORE"
|
assert profile.storage.wsl_root.as_posix() == "/mnt/d/NDC_MISSIONCORE"
|
||||||
assert profile.clock.authority == "gazebo:/clock"
|
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.actuator_authority is False
|
||||||
assert profile.authority.navigation_or_safety_accepted is False
|
assert profile.authority.navigation_or_safety_accepted is False
|
||||||
assert profile.authority.direct_actuator_setpoints_allowed 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} >= {
|
assert {item.identifier for item in profile.components} >= {
|
||||||
"px4-autopilot",
|
"px4-autopilot",
|
||||||
"px4-msgs",
|
"px4-msgs",
|
||||||
"gazebo",
|
"gazebo",
|
||||||
"nav2",
|
"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} == {
|
assert {item.identifier for item in profile.processes} == {
|
||||||
"simulation-orchestrator",
|
"simulation-orchestrator",
|
||||||
"gazebo-server",
|
"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))
|
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:
|
def test_doctor_never_claims_go_without_target_evidence(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"k1link.simulation.s0._target_host_match",
|
"k1link.simulation.s0._target_host_match",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue