flightopsDocumentation
Guides

Existing project intake

10 min readdocs/PROJECT-INTAKE.md (source repository)

lcc intake creates a context record, an external asset registry and a scenario draft for a narrow CPU simulation. It reads the source project and the installed environment. It does not execute launch files, run ROS/Gazebo, install dependencies or approve acceptance criteria.

The supported shape has an explicit local SDF world, a separate SDF robot model, a bridge YAML file, and a controller launch file. The existing gz-sim adapter owns starting Gazebo, spawning the robot and starting the bridge. This avoids changing the frozen scenario contract or requiring source changes in the imported project.

For source navigation without preparing a simulator, use flightops-tui workspace --project PATH. Its declared source inventory (source repository) is a separate bounded reader: it does not produce an intake scenario, resolve arbitrary launch code or prove which file started a running node.

Create the draft

Run in the environment that will run the simulation. Output must be a new directory outside the source project, and its parent must already exist.

bash
lcc intake \
  --project /work/my-robot \
  --world worlds/test.sdf \
  --robot models/robot.sdf \
  --controller launch/control.launch.py \
  --bridge config/bridge.yaml \
  --ros-distro jazzy --gazebo harmonic \
  --require-topic /odom --require-topic /scan \
  --config config/controller.yaml \
  --output /work/my-robot-intake

lcc and launchcommit are the same command. Intake has no MCP tool and does not need the terminal.

ArgumentDefault and meaning
--project PATHRequired existing source directory.
--world PATHRequired SDF world inside the project.
--robot PATHRequired separate SDF model to spawn in that world.
--controller PATHRequired supported .launch.py or .launch.xml controller launch.
--bridge PATHRequired bridge YAML with an explicit Gazebo-to-ROS /clock mapping.
--ros-distro NAMERequired humble or jazzy; pairing restrictions appear below.
--gazebo NAMERequired fortress or harmonic.
--output PATHRequired new directory outside the source tree. The parent must exist.
--name NAMEimported-simulation. A lowercase scenario slug, at most 64 characters.
--require-topic TOPICRepeatable. Defaults to /odom only when none are supplied; explicit topics replace that default.
--config PATHRepeatable additional configuration to inventory and hash; no default.
--require-package NAMERepeatable additional literal ROS package name; no default.
--arg NAME=VALUERepeatable unique declared launch argument; at most 32 entries.
--assumption TEXTRepeatable physical assumption, at most 32; always labeled unreviewed.
--timeout SECONDS30.0, finite and between 1 and 3600. Bounds the later scenario run.
--seed INTEGERUnset. If supplied, must be nonnegative.

Paths for world, robot, controller, bridge and additional configuration resolve against --project. All source files and resolved local asset references must remain inside that project. Asset references in SDF resolve against the referring SDF file. Controller parameter-file strings resolve against the project directory, which must also be the working directory when the reviewed scenario runs.

--arg name=value preserves declared controller launch arguments. JSON strings, numbers, booleans and supported flat scalar arrays work. Non-JSON text becomes a string; objects, nested arrays and null are refused. The importer supplies use_sim_time=true and requires the controller to declare that argument. Actual use by every node is unverified. --timeout bounds the later run, between 1 and 3600 seconds. --seed records a requested seed; adapter support and repeatability still require execution. --assumption preserves an explicitly unreviewed physical assumption.

The command prints one JSON object after collection. Exit 0 means status: draft with no static blockers found. Exit 3 means status: blocked; the packet still exists. Exit 2 means input or publication was refused. Collection refusals print flightops.intake/0 with status: refused and error; argparse usage errors print usage to stderr. No exit status means the robot passed a test.

Review the packet

FileMeaning
context.jsonflightops.context/0 with declared inputs, observed facts, unknowns and a deterministic identity.
assets.yamlExternal asset registry for the existing Gazebo adapter. Source references are absolute local paths.
scenario.draft.yamlA validated schema-v0 draft with explicitly labeled transport smoke criteria.
intake.jsonBlockers, artifact hashes, review steps and runtime_verified: false.

The intake report has context_identity, criteria_status, runtime_verified, source_unchanged, issues, artifacts and next_steps. Each issue supplies a code, subject and suggested fix. artifacts maps each generated context, registry and draft filename to its SHA-256 identity and byte count; it does not include a self-hash of intake.json. The stdout report matches that saved report.

When installed dependencies are missing, the report can still include a scenario draft. When source files are missing or their constructs are unsupported, it emits context and intake records without a scenario or asset registry.

The generated criteria ask for two messages on each required topic after 0.5 seconds. Topic names are sorted and deduplicated; generated criterion IDs are draft_topic_1, draft_topic_2, and so on. The draft records those topics, disables video and sets stop_when_satisfied: false. These criteria are a starting point for review, not acceptance of the robot's behavior. Review the criteria, add measurable task outcomes, and inspect the launch/configuration before executing. The existing scenario runner does not enforce a draft approval workflow; the draft label is explicit provenance, not an execution interlock.

After review, from the original project directory:

bash
export FLIGHTOPS_ASSETS=/work/my-robot-intake/assets.yaml
lcc probe --adapter gz-sim --scenario /work/my-robot-intake/scenario.draft.yaml --json
lcc scenario run /work/my-robot-intake/scenario.draft.yaml

The intake packet points to source files; it is not a portable source bundle. Regenerate it on another machine and compare context records. Run artifacts and their canonical proofs remain the responsibility of the existing runner.

What the context establishes

The context identity covers the declared inputs, observed facts and explicit unknowns. It excludes the display-only project root. It records content hashes for the inspected launch, configuration and asset files, available package manifests and named plugin binaries. Changing a relevant input produces a new identity. An environment change affects the applicability of earlier evidence, not its historical validity.

Context fieldContents
schemaflightops.context/0.
identitysha256: followed by the digest of canonical JSON containing schema, declared, observed and unknowns.
declaredSource inputs, pairing, required topics/packages, launch arguments, timeout, seed and unreviewed physical assumptions.
observedHost facts, selected environment values, Git facts, installed dependency observations, inspected launch/assets and content-hashed files.
unknownsExplicit field/reason pairs for unresolved runtime, source or environment facts.
locationsDisplay location of the project root, outside the identity calculation. Other paths that appear in observed facts still contribute to identity.

The Git probe records HEAD, status identity and tracked diff identity through the existing bounded subprocess runner. Other untracked content is explicitly unknown; this record is not a complete workspace snapshot. The environment block labels ROS_DISTRO, overlays and RMW_IMPLEMENTATION as environment values. It does not infer the effective RMW implementation from an unset variable.

Before publishing, intake repeats the bounded Git observations and checks the identity, size, modification time and change time of every inspected source file. A changed or removed input refuses publication. This check establishes consistency through final collection; it does not lock the source against later edits. Repeat intake after editing a referenced file.

validate_context(record) validates flightops.context/0, rejects non-finite JSON and unknown top-level fields, and verifies the content identity. The new context schema lives in launchcommit/intake/context.py; existing scenario, fixture and proof schemas are unchanged.

Supported inputs and limits

  • Controller Python permits direct imports of LaunchDescription, Node, DeclareLaunchArgument and LaunchConfiguration, literal package/executable names, assignments and generate_launch_description. XML permits launch, arg, node, param and remap. Includes, arbitrary processes, opaque functions, dynamic package names and whole-simulation/bridge launches are refused. Python Node.parameters must be a literal list or tuple of file paths and dictionaries. Variables and dynamic parameter-file references are refused so they cannot silently disappear from the context inventory.
  • SDF permits explicit project-local files. Remote URLs, model://, package://, substitutions and asset paths outside the project are refused. Rendered camera and GPU lidar sensor inputs require a later rendering-specific intake path.
  • The bridge must explicitly map /clock from Gazebo to ROS with the correct declared generation's message type. Mentioning clock in a comment is insufficient.
  • Declared pairings are Humble/Fortress, Humble/Harmonic and Jazzy/Harmonic. Installed versions, plugin loading, transitive dependencies, lifecycle readiness, actual topic delivery, progressing clocks, reset behavior and task criteria remain unverified. Package markers and executable/plugin paths are static observations.
  • Inputs are capped at 128 files, 1 MiB per parsed document, 32 MiB per binary asset, and 64 MiB of cumulative source reads, including repeated reads. Installed manifest/plugin inventory has its own 64 MiB cap. There are at most 32 referenced SDF documents per SDF inspection, 64 packages/plugins, 32 overlay paths and 32 required topics. YAML aliases and XML DTD/entity declarations are refused. Repeated SDF references reuse the inspected document. Each Git command has a five-second deadline, up to three commands per observation and two observations per intake; incomplete output is rejected. Context JSON is capped at 2 MiB and generated files together at 8 MiB.
  • Intake stages all output before reserving the destination exclusively and atomically publishing the packet. Existing output is never overwritten.

Two independently prepared real projects and clean-machine reproduction have not been accepted by these unit tests. CPU runtime execution, deliberate regression, missing dependency, paused clock and interrupted-run cleanup remain the next acceptance checks. No hardware execution is added by intake.

Python and CLI integration

python
from pathlib import Path
from launchcommit.intake import IntakeSpec, create_intake

report = create_intake(
    IntakeSpec(
        project=Path("/work/my-robot"), world="worlds/test.sdf",
        robot="models/robot.sdf", controller="launch/control.launch.py",
        bridge="config/bridge.yaml", ros_distro="jazzy", gazebo="harmonic",
        required_topics=("/odom",),
    ),
    "/work/my-robot-intake",
)

create_intake returns the intake report dictionary after publishing the packet. It raises IntakeError, ValueError or OSError on refusal. Its optional environ and runtime keyword arguments let tests supply environment observations and a bounded command runner. Normal callers should use the actual target environment; substituting those inputs changes what the context can establish.

To validate a saved context without accessing the project:

python
import json
from pathlib import Path
from launchcommit.intake import validate_context

record = json.loads(Path("/work/my-robot-intake/context.json").read_text())
validate_context(record)

This validates the contract and recomputes its identity. It does not rehash source files or refresh installed dependencies. lcc intake is the refresh operation.

The context and intake records are inspected as JSON. They are not inputs to the saved proof/query viewer; flightops-results and flightops-tui currently refuse those schemas. Render the resulting canonical run proof after reviewed execution.

Troubleshooting and verification

Result or symptomNext step
missing_package, missing_plugin, missing_executableSource the intended existing ROS environment or provide the dependency, then collect into a new packet directory. A present marker alone does not prove a loadable dependency.
ros_environment_mismatchCompare ROS_DISTRO with --ros-distro; do not change the declaration just to hide an incorrect environment.
unsupported_or_missing_inputRead the issue's subject and fix. Use a separate controller launch and explicit project-local assets. Intake does not execute Python to resolve dynamic constructs.
undeclared_launch_argumentsDeclare the argument in the controller launch or remove the supplied --arg.
Source changed during collectionFinish source edits and rerun into a new destination. No stable packet is published from that mixed collection.
Existing output or output inside the source treeChoose a new sibling directory whose parent exists. Intake never replaces a packet.
Draft exists but runtime is unknownReview task criteria, probe the adapter, and execute the reviewed scenario. File inspection cannot resolve runtime observations.

The focused regression suite needs a source checkout: it is in launchcommit/tests/, which is not in the install. From the repository root:

bash
.venv/bin/python -m pytest launchcommit/tests/test_project_intake.py -q

It covers supported local inputs, draft/blocker semantics, bounds, unsupported launch constructs, source changes during collection and publication refusal. Passing these tests validates importer behavior. It does not establish the real project and clean-machine acceptance cases described above.