NODEDC_MISSION_CORE/docs/adr/0015-simulation-polygon-qua...

150 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR 0015: Simulation Polygon as a qualification boundary
## Status
Accepted for architecture; SIM S0 qualification returned `GO` 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 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.
14. A top-level Polygon UI is gated on accepted contracts and a proven S1
backend lifecycle with persisted run history.
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
- 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 qualification doctor is deliberately read-only:
- `simulation/s0/qualification-profile.yaml` is the accepted S0 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 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.
`simulation/s0/gazebo_time_control.py` owns the S0 pause/single-step/1×/2× RTF
and resource acceptance probe. `simulation/s0/build_evidence_manifest.py`
admits only exact-profile, D-confined, passing 1×/2× runs into the digest-bound
evidence generation. These scripts are qualification tooling; the S1
Simulation Orchestrator remains the future product lifecycle owner.
The doctor reports `INCOMPLETE` until it is running on the reviewed target with
resolved/accepted pins and all required evidence. On the accepted profile
generation and nine-claim confined evidence set it returned `GO`; it cannot
infer that result from repository state alone.
## S1A implementation
`k1link.simulation.contracts` now defines the first product-owned
`QualificationRun`, provider/authority/artifact provenance, canonical
Ackermann and Differential rover setpoints, and qualification events.
`k1link.simulation.run_store` persists them as an immutable manifest plus
one-file-per-sequence append-only journals. Exclusive creation and file/directory
fsync prevent partially acknowledged records; loading rejects gaps, identity
drift, non-regular records and illegal lifecycle history.
The aggregate derives its current state by replay. Terminal runs cannot accept
new events, commands or artifacts. A restart never silently resumes an active
run: reconciliation terminates it as `failed` with
`orchestrator-recovery-interrupted`. Reset requires a terminal parent and
creates a new linked run/episode. Commands are admitted only for a running
closed-loop virtual run with the current authority generation and bounded
simulation-time TTL. Replay/shadow commands, direct actuator authority and
ungated HIL/controller-in-loop admission fail closed.
This increment proves the local lifecycle/repository contract. It does not yet
own provider processes, transmit a PX4 setpoint, execute heartbeat/watchdog
failsafe behavior, or qualify navigation/safety behavior. The target
orchestrator must place the caller-supplied repository root under the accepted
D-only worker boundary.
## S1B ownership foundation
`k1link.simulation.orchestrator` now owns the application lifecycle through a
transport-neutral worker port. Start and stop requests are persisted with
idempotency identity, one active run owns a worker at a time, and
pause/resume/step/reset cannot bypass run-state validation. Provider start
failure, S0 profile drift, stop residue and restart interruption all become
explicit run events and terminal failures.
`k1link.simulation.process_supervisor` provides the worker-local POSIX ownership
primitive: shell-free argv, dedicated provider PGIDs, run-scoped logs, atomic
process registry, partial-start rollback and ascending shutdown order matching
the accepted S0 dependency graph. `k1link.simulation.worker` binds that
primitive to the exact accepted S0 profile digest and canonical D-only S1 run
artifact and process-runtime layouts.
This is not target-worker acceptance. Provider specifications, loopback network
namespace creation, live readiness probes, restart-safe PID/start-token
reconciliation and Gazebo world-control/PX4 command adapters remain open. The
worker port deliberately fixes ownership semantics without prematurely fixing
the eventual transport.