Source-install direct launcher activation — 2026-08-12
Status: LOCAL_DUAL_DISTRO_PASS / PUBLICATION_PENDING
Remote mutations performed: none
User-facing finding
The source quickstart already detected Humble or Jazzy, installed repository
dependencies, built the six maintained packages, and ran the verified demo.
Its next-terminal instruction still began with
source <workspace>/install/setup.bash, however. Calling the absolute installed
lidarslam-map path without that remembered activation loaded the curated
Python CLI but did not activate ROS Python or package paths.
This was not always a loud startup failure. A pre-repair installed artifact
returned exit 0 for doctor on a two-message PointCloud2+Imu bag, but fell
back from unavailable rosbag2_py to a less capable reader. It reported
pointcloud-inspection-unavailable and
timestamp-inspection-unavailable, selected no maintained profile, and hid a
valid RKO-LIO route. A command that appears to work while losing its safe
recommendation is a worse beginner failure than a clear setup error.
The executable regression captured the same boundary more directly. Before
the repair, merged-install, isolated-install, and source-layout launcher probes
all delegated without the setup marker (3 failed).
Repair
The shared scripts/lidarslam launcher now activates the product environment
inside its own process before delegating:
- an installed launcher derives its prefix from the selected curated
share/lidarslam/product/scripts/lidarslam_cli.pyresource and accepts only that prefix's aggregatesetup.bashor the immediate isolated-workspace aggregate; - a repo-local launcher considers the documented parent workspace and existing local layouts, but accepts one only when its install tree contains the matching curated CLI resource and then delegates to that installed resource, keeping the activated runtime and command implementation together;
- no arbitrary filesystem search, shell startup-file edit, or unrelated parent workspace sourcing is performed;
- relative or absolute symlinks are resolved with a bounded depth before locating resources;
- setup failure returns the product's internal/tooling exit code
70with the exact failed setup path; and - activation changes only the launcher and child environment, never the caller's shell; and
- delegated Python processes disable bytecode writes, preserving the installed prefix as a read-only product surface.
source_quickstart.sh now also rejects a post-build lidarslam-map resolved
outside the selected workspace, requires the command to be executable, and
prints its absolute direct path as the primary next-terminal action. Explicit
workspace sourcing remains available for the short command name and other ROS
tools.
The clean installed-product checker now removes inherited ROS, colcon, Python,
and library-path variables for its real-bag doctor invocation. Future clean
prefix evidence therefore fails if the absolute launcher cannot reconstruct
its own environment or silently loses the maintained recommendation.
It also snapshots Python cache state across the whole install prefix. CMake
excludes development bytecode from launch/, the checker disables bytecode for
its own dynamic imports, and successful validation now fails if any cache path,
size, mtime, or content changes.
Actual installed execution
Two fresh Jazzy installs were built from the local candidate: an isolated
colcon install and a merged clean prefix. Both builds completed. The isolated
absolute command was then invoked with AMENT_PREFIX_PATH,
CMAKE_PREFIX_PATH, COLCON_PREFIX_PATH, ROS_DISTRO, ROS version variables,
PYTHONPATH, and LD_LIBRARY_PATH removed.
On the synthetic PointCloud2+Imu rosbag2 fixture, the repaired direct command:
- returned exit
0fromdoctor --json; - inspected the
timestamppoint field throughrosbag2_py; - proved monotonic PointCloud2 and Imu header timestamps;
- selected
rko_lio_graph_public_path; and - ran
start --dry-run --viewer noneto the expected calibration-review card, returned exit0, and wrote no files.
The merged install passed the complete check_installed_product_cli.py
contract after its doctor step was changed to start from that clean
environment. This includes installed resources, command help, compatibility
shim, demo plan, sessions, recovery, support, migration, and dry-run behavior;
the activation assertion is not a standalone mock-only claim.
Humble follow-up
The same candidate then passed in the locally available immutable Humble image
ghcr.io/rsasaki0109/lidar_slam_ros2@sha256:f1a894d81b5cb7b4e2e55a7b3fc17e538722b59c07b0bec066f2ad499a5e8447.
The container used --network none, mounted the candidate source read-only,
ran as the host UID/GID, and rebuilt lidarslam into a fresh non-symlinked
merged prefix against the image's six-package underlay.
The first build completed but its checker correctly rejected unknown source
identity. This clone uses a Git object alternate outside the mounted source,
so the container could read HEAD but could not resolve its objects. The final
build used the documented Git-free packager interface to bind exact revision
3f4dd70cdc58ad421192559213cdee0bdc41eba8 and dirty=true; the installed
product-build-info.json reported source: override, and the checker required
that exact revision. No clean-revision claim is made for this cumulative local
candidate.
Humble's base setup.bash also reproduced a distribution-specific reason for
the launcher's bounded set +u section: sourcing it directly under set -u
failed on an unset AMENT_TRACE_SETUP_FILES. The repaired launcher handled the
same setup from a fresh process without weakening nounset for the delegated
command.
With all inherited ROS, colcon, Python, and library-path variables removed,
the installed Humble command matched the source launcher SHA-256
48277513dab87837c808d41a63a9ccdb4ed172cad5376add05f5f2f75d374550,
selected rko_lio_graph_public_path, fully inspected the time point field and
header order, and returned the calibration-review start --dry-run card without
creating the requested output. The complete installed-product checker passed
from the same prefix.
All-six source follow-up
The next controlled run built all six maintained source packages into new merged prefixes in both fixed images, still with the candidate mounted read-only, network disabled, and exact dirty source identity bound. Humble finished in 302 seconds and Jazzy in 273 seconds. Their source and installed package lists are identical, and both complete installed checkers plus direct fresh-terminal real-bag paths pass.
That follow-up also found and repaired Python cache leakage at both build and
runtime boundaries. After reinstall, each complete prefix contains zero
__pycache__, .pyc, or .pyo artifacts before and after validation. See the
dual-distro all-source evidence.
Verification
| Check | Result |
|---|---|
| pre-repair launcher activation regression | 3 failed as expected |
| merged, isolated, source-layout, symlink, mismatch, and setup-failure launcher regressions | PASS |
| source quickstart regression | 8 passed |
| launcher/source/CLI-contract regression group | 36 passed |
| docs-entrypoint regression | 15 passed |
| fresh isolated Jazzy package build | PASS |
direct isolated doctor --json from removed ROS environment |
PASS; rko_lio_graph_public_path selected |
direct isolated start --dry-run --viewer none |
PASS; calibration review, no files written |
| fresh merged Jazzy package build | PASS |
| clean merged Jazzy installed-product CLI checker | PASS |
network-disabled Humble lidarslam build against immutable image underlay |
PASS; exact dirty source identity bound |
direct merged Humble doctor --json from removed ROS environment |
PASS; rko_lio_graph_public_path selected |
direct merged Humble start --dry-run --viewer none |
PASS; calibration review, requested output absent |
| clean merged Humble installed-product CLI checker | PASS |
| network-disabled Humble all-six source build/install | PASS; 302 s; exact six-package source/install lists |
| network-disabled Jazzy all-six source build/install | PASS; 273 s; exact six-package source/install lists |
| CMake package-content and runtime bytecode write exclusion | PASS; zero cache artifacts before and after complete checker on both distros |
| canonical unsourced contributor gate | graph: 1,428 passed, 13 skipped, 11 warnings; lidarslam: 626 passed; PASS: all |
ament_flake8 on changed Python tests/checker |
PASS |
bash -n on both launchers |
PASS |
CLI JSON parse and git diff --check |
PASS |
The two package-scoped product processes passed 2,054 tests in total. The existing graph warnings are the previously recorded ImageIO v3 notices.
Limits and next gate
This closes the remembered-activation gap and the local all-six source-overlay
build question; it is not a substitute for a package-manager install or a
complete cold-machine source quickstart. The immutable images still supply
ROS, system dependencies, and build tooling. The normal short
lidarslam-map spelling still requires explicit PATH activation until ROS
buildfarm packages exist. NDT upstream convergence, Jazzy RKO-LIO main sync,
binary package-manager E2E, public candidate publication, dependency bootstrap,
the full public demo, and comparable external onboarding timing remain separate
gates.
No shell profile, commit, branch, pull request, issue, label, release, image, package, review reply, or external repository was changed.