Repository navigation
ci: land OSMO ephemeral runners and system-test harness on develop - #382
Merged
Merged
Conversation
…IA OSMO Replace the OpenStack-Nova spawn/reap backend with OSMO workflow submission. The GitHub side is unchanged (self-hosted/airstack-ephemeral labels, single-use JIT runner tokens, same-repo fork guard) and the one-job-per-worker destroy-after model is preserved; only the spawn target moved from creating a Nova VM to submitting an OSMO workflow. orchestrator.py: submit/query/cancel/list via the osmo CLI, job_id -> workflow_id state, re-login-on-auth-failure, orphan sweep via osmo workflow list; drop floating-IP/boot-volume/placement/keypair/security-group logic. runner.Dockerfile + runner-entrypoint.sh + runner-workflow.yaml.j2: prebaked privileged docker-in-docker + GPU GitHub runner image/task (replaces cloud-init.yaml.j2). config.example.yaml, setup.sh, airstack-orchestrator.service, requirements.txt: OSMO service-account token auth, install the osmo CLI, drop openstacksdk. Docs (AGENTS.md, tests/README.md, orchestrator README) updated to OSMO. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve uuid/live name after submit (OSMO returns name-only + suffix), default config to the Keycloak-backed airstack pool and Harbor runner image, and add scripts to build/push airstack-ci-runner on OSMO DinD. Co-authored-by: Cursor <cursoragent@cursor.com>
Fills in the empty ci_cd.md stub with an end-to-end guide to how CI runs the full AirStack stack on ephemeral OSMO GPU pods: architecture and job lifecycle diagrams, runner pod anatomy, the three trigger paths, what each pytest mark catches, the metrics regression gate, the security model, and layer-by-layer troubleshooting. Adds the page to the mkdocs nav (it was previously unreachable) and cross-links it from tests/README.md and the testing index. Co-authored-by: Cursor <cursoragent@cursor.com>
Every build_docker and build_packages test failed on the OSMO backend because the inner dockerd kept its data-root on the pod's overlayfs rootfs. Linux rejects a directory on overlayfs as an overlay upperdir, so image pulls still succeeded -- containerd unpacks layers with plain writes -- while every build step needing a real mount died with "mount source: overlay ... err: invalid argument", surfacing as unrelated-looking apt-get and WORKDIR failures. runner-entrypoint.sh now picks a storage backend by attempting a real overlay mount rather than trusting the filesystem type, preferring a loopback ext4 data-root (real overlay2, sparse, dies with the pod) and falling back to a pod-mounted filesystem, fuse-overlayfs, then vfs. vfs is a last resort only: it copies the whole filesystem per layer and would exhaust the storage request on the sim images. Also bumps the GitHub Actions runner to 2.336.0, since 2.334.0 stops being able to run jobs on 2026-08-10. Co-authored-by: Cursor <cursoragent@cursor.com>
Versioned cache_from entries always miss on PRs because VERSION is forced up; add a stable cache_* tag published only by docker-build.yml so system tests can reuse layers without writing the shared cache. Co-authored-by: Cursor <cursoragent@cursor.com>
Skip full compose rebuilds when a service's content fingerprint matches the previous versioned image label; registry-retag instead and only rebuild services whose Docker inputs changed. Co-authored-by: Cursor <cursoragent@cursor.com>
docker_image_plan was feeding NUM_ROBOTS with a trailing comment into compose config, which broke strconv.Atoi for deploy.replicas. Co-authored-by: Cursor <cursoragent@cursor.com>
Publish successful images even when a sibling (e.g. isaac-sim) fails, and still cosign whatever was retagged or pushed in the same run. Co-authored-by: Cursor <cursoragent@cursor.com>
Seeded gcs/ms-airsim/robot images carry content-fingerprint labels; this bump should registry-retag those digests without rebuilding. Co-authored-by: Cursor <cursoragent@cursor.com>
Isaac's PX4 ubuntu.sh fails dpkg configure on the NVIDIA base; pre-fix ca-certificates, drop software-properties-common, and skip NuttX/Gazebo like ms-airsim. Pin pytest<8.1 and disable launch_testing for colcon unit tests so ROS Jazzy's outdated pytest hook no longer aborts CI. Co-authored-by: Cursor <cursoragent@cursor.com>
A single quoted blob made pytest treat "-p no:launch_testing" as part of the -m expression, which broke lidar_point_cloud_filter colcon tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Nested single quotes around 'not linter' terminated the outer bash -ic string early, so pytest saw 'not' as a path. Use shlex.quote for the whole command and list-form pytest_args in the YAML. Co-authored-by: Cursor <cursoragent@cursor.com>
colcon --pytest-args is a single nargs='*' option, so repeating it dropped -p and pytest treated no:launch_testing as a file path. Set PYTEST_ADDOPTS with docker exec -e instead. Co-authored-by: Cursor <cursoragent@cursor.com>
conftest functions named pytest_* are registered as hooks. pytest_addopts_env caused PluginValidationError and exit code 3. Co-authored-by: Cursor <cursoragent@cursor.com>
Pull and retag cache_* images instead of baking isaac/airsim on every colcon/pytest iteration. /pytest --no-image-build does the same for other marks. compose up --no-build when AIRSTACK_NO_IMAGE_BUILD=1. Co-authored-by: Cursor <cursoragent@cursor.com>
-p no:launch_testing is applied after setuptools entrypoints load, so pytest 8.1+ still crashes on launch_testing's path= hook. Set PYTEST_DISABLE_PLUGIN_AUTOLOAD so cache_* robot images (unpinned pytest) can run lidar tests without a rebuild. Co-authored-by: Cursor <cursoragent@cursor.com>
PYTEST_ADDOPTS -m not linter never reached ament pytest, so copyright / flake8 / pep257 still ran after the unit tests passed. Ignore those modules in setup.cfg and collect_ignore. Co-authored-by: Cursor <cursoragent@cursor.com>
PR-open and bare /pytest were sweeping both sims. Default --sim to isaacsim; msairsim is opt-in via --sim msairsim. Co-authored-by: Cursor <cursoragent@cursor.com>
Bring the OSMO CI branch onto current develop (harness split, waypoint flight, notebooks, robot-deployment fixes). Keep Isaac PX4 apt fix, isaacsim-only --sim default, and colcon pytest env passing. Bump VERSION to 0.19.0-alpha.13. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
Test Metrics —
|
| Test | Pass | Fail | Skip | Rate |
|---|---|---|---|---|
| isaac-sim.extensions.PegasusSimulator.extensions.pegasus.simulator.pegasus.simulator.tests.test_hello_world | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.replicator.pose_generation.pose_tests.test_utils | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.benchmark.services.test_no_rendering | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_determinism | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_root | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_delete_in_contact | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_rendering | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_save_stage | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_time_stepping | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.cortex.framework.cortex_bringup_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.ros1.bridge.test_carter_lidar | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_carter_camera_multi_robot_nav | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_people_sim | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.physics.contact_sensor_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_lidar_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_radar_test | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_createstage_config | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extension_count | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_external | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extra_args | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fabric_frame_delay | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fetch_results | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_frame_delay | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ogn | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ovd | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_syntheticdata | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_unsaved_on_exit | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.omni.isaac.dynamic_control.test_zero_step | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.omni.replicator.agent.test_scripting | 0 | 1 | 0 | 0% |
| isaac-sim.standalone_examples.testing.omni.syntheticdata.test_basic | 0 | 1 | 0 | 0% |
| ros_packages.gui.rviz.rviz_behavior_tree_panel.scripts.test_behavior_tree_publisher | 0 | 1 | 0 | 0% |
| ros_packages.logging.bag_recorder_pid.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_packages.logging.bag_recorder_pid.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_packages.logging.bag_recorder_pid.test.test_pep257 | 0 | 1 | 0 | 0% |
| ros_ws.src.local.planners.takeoff_landing_planner.test.scripts.test_takeoff_landing_planner | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Baseline.DPVO.Pangolin.components.pango_python.pybind11.tests | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_circular_buffer | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_loadable | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_macvo | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_modules | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_sequence | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_tartanvo | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_frontend | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_matching | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_performance_macvo | 0 | 1 | 0 | 0% |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_stereo_depth | 0 | 1 | 0 | 0% |
| ros_ws.src.ros2tak_tools.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_ws.src.ros2tak_tools.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_ws.src.ros2tak_tools.test.test_pep257 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.camera_param_server.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.camera_param_server.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.camera_param_server.test.test_pep257 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_copyright | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_flake8 | 0 | 1 | 0 | 0% |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_pep257 | 0 | 1 | 0 | 0% |
Metrics
| Test | Metric | Value |
|---|---|---|
| ros_packages.gui.rviz.rviz_behavior_tree_panel.scripts.test_behavior_tree_publisher | duration_s | 0s |
| ros_packages.logging.bag_recorder_pid.test.test_copyright | duration_s | 0s |
| ros_packages.logging.bag_recorder_pid.test.test_flake8 | duration_s | 0s |
| ros_packages.logging.bag_recorder_pid.test.test_pep257 | duration_s | 0s |
| ros_ws.src.ros2tak_tools.test.test_copyright | duration_s | 0s |
| ros_ws.src.ros2tak_tools.test.test_flake8 | duration_s | 0s |
| ros_ws.src.ros2tak_tools.test.test_pep257 | duration_s | 0s |
| ros_ws.src.local.planners.takeoff_landing_planner.test.scripts.test_takeoff_landing_planner | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Baseline.DPVO.Pangolin.components.pango_python.pybind11.tests | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_circular_buffer | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_loadable | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_macvo | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_modules | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_sequence | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_config_tartanvo | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_frontend | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_matching | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_performance_macvo | duration_s | 0s |
| ros_ws.src.perception.macvo_ros2.macvo_ros2.macvo.Scripts.UnitTest.test_stereo_depth | duration_s | 0s |
| ros_ws.src.sensors.camera_param_server.test.test_copyright | duration_s | 0s |
| ros_ws.src.sensors.camera_param_server.test.test_flake8 | duration_s | 0s |
| ros_ws.src.sensors.camera_param_server.test.test_pep257 | duration_s | 0s |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_copyright | duration_s | 0s |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_flake8 | duration_s | 0s |
| ros_ws.src.sensors.gimbal_stabilizer.test.test_pep257 | duration_s | 0s |
| isaac-sim.extensions.PegasusSimulator.extensions.pegasus.simulator.pegasus.simulator.tests.test_hello_world | duration_s | 0s |
| isaac-sim.standalone_examples.replicator.pose_generation.pose_tests.test_utils | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.benchmark.services.test_no_rendering | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_determinism | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_articulation_root | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_delete_in_contact | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_rendering | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_save_stage | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.core.api.test_time_stepping | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.cortex.framework.cortex_bringup_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.ros1.bridge.test_carter_lidar | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_carter_camera_multi_robot_nav | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.ros2.bridge.test_people_sim | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.physics.contact_sensor_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_lidar_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.sensors.rtx.rtx_radar_test | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_createstage_config | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extension_count | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_external | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_extra_args | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fabric_frame_delay | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_fetch_results | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_frame_delay | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ogn | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_ovd | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_syntheticdata | duration_s | 0s |
| isaac-sim.standalone_examples.testing.isaacsim.simulation_app.test_unsaved_on_exit | duration_s | 0s |
| isaac-sim.standalone_examples.testing.omni.isaac.dynamic_control.test_zero_step | duration_s | 0s |
| isaac-sim.standalone_examples.testing.omni.replicator.agent.test_scripting | duration_s | 0s |
| isaac-sim.standalone_examples.testing.omni.syntheticdata.test_basic | duration_s | 0s |
andrewjong
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What features did you add and/or bugs did you address?
This is the official PR for the OSMO CI backend replacing the OpenStack System with the Osmo GPUs plus the CI/system-test harness changes needed for that backend to actually pass on develop.
GPU CI jobs still use runs-on: [self-hosted, airstack-ephemeral]. The spawn target is now an NVIDIA OSMO workflow (one privileged GPU pod per job, destroyed after), not a Nova VM.
On top of that migration, this branch also:
This is a Infra PR so there are no flight videos. Architecture / lifecycle diagrams are in docs/development/intermediate/testing/ci_cd.md. Existing system-test overview video remains in tests/README.md.
How did you implement it?
Drop-in backend swap: GitHub labels, JIT runner tokens, and the same-repo fork guard are unchanged. Only the spawn/reap path moved from OpenStack to OSMO.
Orchestrator (.github/orchestrator/)
Layer cache and publish
Harness / CI workflow
How do you run and use it?
This is CI infra. Contributors do not launch OSMO themselves. Maintainers trigger tests on the PR; lab-admins operate the orchestrator host.
Below are the instructions to manually trigger the CI/CD on a PR.
On the PR, comment (write access required) - this also depends on what suite you are actually trying to run:
/pytest -m "unit or build_packages"
/pytest -m "liveliness or sensors" --sim isaacsim --num-robots 1 --stress-iterations 1
/pytest -m takeoff_hover_land --sim isaacsim --num-robots 1
/pytest -m build_packages --no-image-build
Opt in to AirSim:
/pytest -m liveliness --sim msairsim --num-robots 1
Day-to-day (any maintainer with write access)
/pytest -m "unit or build_packages"
/pytest -m liveliness --sim isaacsim --num-robots 1 --stress-iterations 1
/pytest -m takeoff_hover_land --sim isaacsim --num-robots 1
Optional: same commands locally (GPU box or interactive OSMO pod):
airstack test -m "unit or build_packages" -v
airstack test -m liveliness --sim isaacsim --num-robots 1 --stress-iterations 1 -v
Preview the CI docs (the diagrams for this PR):
docker compose up docs
(open http://localhost:8000 → Development → Testing → CI/CD Pipeline)
One-time lab setup (orchestrator host)
Build and push the runner image:
Or without local Docker:
osmo workflow submit .github/orchestrator/build-runner-on-osmo.yaml
--pool airstack --priority HIGH
Copy credentials to the always-on host:
Install and start the service on that host:
You should see orchestrator started (OSMO backend) and osmo login succeeded.
Verify end-to-end:
On a live job, confirm Docker storage is healthy:
WF=<workflow-id-from-list-or-state.json>
osmo workflow logs "$WF" --task runner | grep -E 'runner-entrypoint|storage'
*Expect loopback / overlay2, not vfs.
Testing with PyTest
No new pytest modules. Existing marks are what this PR makes runnable on OSMO:
unit: Host hermetic tests (lidar_point_cloud_filter, natnet_ros2)
build_docker: Images actually build on the DinD overlay fix
build_packages: colcon build + colcon test inside robot (pytest autoload / linter skip)
liveliness / sensors / takeoff_hover_land / autonomy / waypoint_flight: Full stack on a cold OSMO GPU pod, Isaac default
airstack test -m ...Fast CI loop (no sim bake):
airstack test -m "unit or build_packages" -v
Stack on Isaac (this PR’s default):
airstack test -m "liveliness or sensors or takeoff_hover_land"
--sim isaacsim --num-robots 1 --stress-iterations 1 -v
On the PR:
/pytest -m "unit or build_packages"
/pytest -m "liveliness or takeoff_hover_land" --sim isaacsim --num-robots 1 --stress-iterations 1
Version check. This should pass. The version in .env must be higher than on develop (this PR: 0.19.0-alpha.12 → 0.19.0-alpha.13).
Run Tests. When this job is green, pytest passed. GitHub then posts a metrics comment on the PR and uploads a test-results-* zip with summary.txt, results.xml, and metrics.json.
Orchestrator. The always-on CI machine should log that it submitted an OSMO job. Inside the GPU pod, look for [runner-entrypoint] storage:. It should say loopback overlay2. If it says vfs, Docker builds will be slow and may run out of disk.
How to trigger a real run. Opening the PR runs pytest with no test filter, which is too broad. Comment /pytest with an explicit -m (as above) so only the intended marks run.
Metrics Report. After a filtered run, the comment on the PR is the source of truth. A failed or unfiltered run can post a noisy table — wait for a marked run that produces a real metrics.json.
Documentation
Added CI/CD Pipeline → docs/development/intermediate/testing/ci_cd.md. Also updated .github/orchestrator/README.md, tests/README.md, AGENTS.md, testing index / end-to-end pages, and the bump-version / run-system-tests skills.
Yes for two audiences: contributors (/pytest, marks, cache vs retag) in ci_cd.md; lab-admins (pool, privileged mode, setup.sh, token rotation, DinD overlay) in the orchestrator README.
Architecture, job lifecycle, pod anatomy, and troubleshooting diagrams in ci_cd.md; ASCII architecture in the orchestrator README.
Versioning
.envfile according to semantic versioning?Yes. .env VERSION is 0.19.0-alpha.13.
main: 0.18.0
develop: 0.19.0-alpha.12
This PR: label bump alpha.12 → alpha.13