Skip to content

Modular AirStack 2/9: module CLI, workspace overlay, Docker layer composition - #389

Open
andrewjong wants to merge 6 commits into
rfc/s1-observability-manifestfrom
rfc/s2-module-cli-layers
Open

Modular AirStack 2/9: module CLI, workspace overlay, Docker layer composition#389
andrewjong wants to merge 6 commits into
rfc/s1-observability-manifestfrom
rfc/s2-module-cli-layers

Conversation

@andrewjong

Copy link
Copy Markdown
Member

Stacked PR — merge the stack bottom-up with merge commits (squash-merging a lower PR orphans the commits every higher PR builds on). When the PR below merges, GitHub retargets this one to develop automatically. Each PR bumps VERSION (0.20.0-alpha.N), so check-version-increment passes at every level and each merge publishes images.

Stack 2/9 · base: rfc/s1-observability-manifest

The module machinery: pull a capability from an external repo into this checkout, overlay it into the workspace, and compose its Docker dependency layers — without the monolith ever knowing its name at rest.

What's inside

  • airstack module CLI (add <url> --version <tag|sha> — branches refused, local paths allowed; list|sync|remove|create --in-tree|doctor [--drift]), synced by vcs2l (ros-infrastructure's maintained vcstool successor) from a pinned modules.repos into gitignored modules/.
  • Workspace overlay: module ROS packages symlinked into robot/ros_ws/src/modules/; Isaac assets keyed off manifest targets (not module type); module compose: fragments merged into a generated override that airstack up includes automatically.
  • Docker layer composition (tools/compose_module_layers.py + airstack module lock [--build]): tier-1 apt/pip RUN-per-module, tier-2 Dockerfile.module (ARG BASE_IMAGE chains), tier-3 published overlays; deterministic modules.lock; composed tags get a -m<planhash> suffix.
  • Zero-module identity rule: with no modules, the composed tag equals today's tag — the existing docker-build/cosign pipeline is untouched.

Validation

  • Overlay contract test (add fixture module → symlink appears → build sees it → remove → gone) and layer-plan determinism/zero-module-identity contract tests, all in the unit suite.
  • build_packages identical with no modules configured; fixture-module compose + liveliness green.

🤖 Generated with Claude Code

andrewjong and others added 6 commits August 22, 2026 04:02
- .airstack/modules/module.sh: module add|remove|list|sync|create|doctor —
  drop-in command group (zero core-dispatcher edits); pinned modules.repos
  (tags/SHAs only — branch refs refused per RFC), vcs2l-based import
  (ros-infrastructure/vcs2l, the maintained vcstool successor), host_setup
  hook runner with --no-hooks
- tools/module_overlay.py: symlinks ros packages into robot/ros_ws/src/modules/
  and isaac launch scripts into launch_scripts/modules/<name>/; mounts Kit
  extensions; merges module compose fragments into
  .airstack/generated/docker-compose.modules.yaml (absolute host paths,
  regenerated each sync); --check/--remove
- closes the asm_dfm2_disturbances pilot friction-log gaps: compose-fragment
  merge, launch-script exposure, exts mounting, AIRSTACK_LAUNCH_SCRIPTS_DIR
- 12 hermetic contract tests (unit mark); developer guide at
  docs/development/modules.md; gitignore entries for all overlay artifacts

Full unit suite: 252 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ck up

With modules synced, cmd_up appends -f .airstack/generated/
docker-compose.modules.yaml (opt out: AIRSTACK_NO_MODULE_COMPOSE=1). Absent
file = no modules = byte-identical behavior. This closes the gap where module
mounts never reached containers brought up by airstack test / the reusable
module CI workflow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nclude

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- tools/compose_module_layers.py: three dep tiers per RFC #379 §6 — tier-1
  rosdep/apt/pip (one RUN per module per package manager → per-module layer
  cache; pip uses --no-cache-dir --break-system-packages per trunk's PEP 668
  house pattern), tier-2 Dockerfile.module chained via ARG BASE_IMAGE, tier-3
  prebuilt overlay (used as-is only when sole docker-relevant module on the
  host; fragment is source of truth otherwise). Deterministic plan
  (.airstack/generated/layer_plan.json), gitignored modules.lock (dep hashes +
  plan hash, byte-identical for identical inputs), --check-conflicts static
  apt/pip pin-conflict gate (doctor hard gate #1 — sync fails on conflict),
  --build executes the chain and points robot services at the composed tag
- ZERO-MODULE IDENTITY RULE: no docker-relevant modules ⇒ every host keeps
  today's exact image tag; no image: overrides emitted. Published trunk images
  are untouched (composed -m<hash> tags are per-checkout artifacts), so
  docker_image_plan.py needs no change
- validated end-to-end locally: heavy_module fixture built a real 2-step chain
  on v0.19.0-alpha.18_robot-x86-64_dev (apt cowsay + pip tabulate + tier-2
  marker verified in-container); identity restored on removal
- 18 contract tests; full unit suite 270 passed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A ros_package module that also targets isaac-sim (asm_optitrack: robot-side
NatNet client + in-sim emulator extension) ships launch scripts and Kit
extensions too; placement now follows targets so hybrid modules resolve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant