flightopsDocumentation
Guides

Parameter provenance, evidence timelines and change impact

7 min readdocs/INVESTIGATION-WORKFLOWS.md (source repository)

These commands create bounded local investigation records. Open their JSON with flightops-results or the existing saved-results terminal reader. They preserve assurance: none; the frozen scenario v0 and existing proof verdicts are unchanged. The commands are flightops-tui provenance, flightops-tui timeline and flightops-tui impact.

Supervised parameter provenance

flightops.parameter-provenance/0 records declarations accepted by an explicitly instrumented Python node. The supported adapter creates the node with global ROS arguments disabled and applies its documented explicit-files-then-inline/0 policy: adapter defaults, matching parameter files in argument order, then explicit inline values. This policy is not an interpretation of arbitrary ros2 launch behavior.

In a sourced ROS environment:

bash
flightops-tui provenance capture \
  --project /absolute/project --node /parameter_example \
  --output /absolute/new-provenance-directory --duration 5 -- \
  flightops-tui provenance fixture --node /parameter_example \
    --default gain:=1 --override gain:=5 --seconds 2

This fixture creates a non-actuating ROS node. Add --parameter-file config.yaml before the inline override to retain that source's relative path, SHA-256 and line. Add --set gain:=7 to exercise an accepted runtime change, or additionally --reject gain to exercise a rejected write. These are controlled fixture actions; the tool does not expose a general write operation against arbitrary nodes.

Application code can use the same adapter after initializing rclpy:

python
from flightops_tools.provenance import create_instrumented_node

node = create_instrumented_node(
    "/robot/camera", defaults={"gain": 1},
    parameter_files=["config/camera.yaml"], overrides={"gain": 5},
)
# The application owns spinning and destroying this node.

Run the application as the direct child of provenance capture. Linux kernel peer credentials bind private datagrams to that child PID; per-run and node-instance identities prevent accidental mixing. A wrapper that forks a different node process is unsupported. The trusted adapter records an accepted declaration and its actual typed input chain, not a coincidental match between a dump and a YAML value. It does not authenticate the honesty of application code running in that process.

Later accepted changes become effective_origin: unresolved, because a post-set callback does not identify the caller. Rejected fixture writes retain the prior accepted value. Rows describe the last recorded declaration/change, not a continuous guarantee of current state. Direct internal mutations outside the instrumented API, composed nodes, arbitrary launch evaluators and built-in use_sim_time declaration origins remain unsupported. Existing configuration comparisons remain unresolved.

Matching duplicate file keys/selectors, untyped values, mixed node instances, event gaps, changed source files and malformed events cannot establish a complete origin. The capture retains intent before spawning, process ownership, bounded log tail, accepted events, failure reasons and process-group cleanup outcome. Opening saved results never starts a node. The new output directory must not already exist.

Limits: 300 seconds, 256 tracked parameters, 16 source files, 2,048 events, 64 KiB per private datagram, 1 MiB collected event bytes and 64 KiB retained logs. Capture requires at least 6 GiB free disk at preflight. Input files must remain inside the project. Producer/runtime support was exercised on the selected Jazzy image; other distributions must support and pass post-set callback acceptance independently. The Jazzy rclpy implementation defines the node creation and parameter callback APIs used by the adapter.

Saved evidence timeline

Create a request listing up to eight saved sources under an explicit evidence root:

json
{
  "schema": "flightops.timeline-request/0",
  "tracks": [
    {"id": "odom", "label": "Odometry", "source": "odom-window.json"},
    {"id": "tf", "label": "Message transform", "source": "message-tf.json"},
    {"id": "check", "label": "Test attempt", "source": "check.json"}
  ]
}
bash
flightops-tui timeline request.json --root /absolute/evidence \
  --output /absolute/new-timeline.json
flightops-results /absolute/new-timeline.json -o /absolute/new-timeline.html

Supported sources are validated evidence-window, message-TF, configuration-diff, workspace-check, development-task and parameter-provenance records. A bounded flightops.timeline-events/0 import also accepts explicit event annotations:

json
{
  "schema": "flightops.timeline-events/0",
  "clock": {"id": "run-clock", "kind": "ROS", "epoch": "trial-1"},
  "events": [
    {"label": "Observed dropout", "start_ns": 1700000000000000010,
     "status": "fail", "details": {"scope": "caller annotation"}}
  ]
}

Imported labels are caller declarations; reusing a clock label does not merge lanes. Queries from the same identified MCAP log clock can share a lane. ROS-header and UTC collection clocks remain separate when host/epoch compatibility is unproven. Raw nanoseconds remain exact in JSON and browser details. Backward timestamp jumps split segments. Missing timestamps stay unplaced, and evicted TF history is not recreated.

A request can add an explicit offset mapping with bounded source-clock validity:

json
{
  "track": "tf", "target": "odom", "offset_ns": 1234,
  "uncertainty_ns": 500, "method": "Documented clock anchor observations",
  "validity": {"start_ns": 1700000000000000000, "end_ns": 1700000001000000000},
  "evidence": ["clock-anchor-observations.json"]
}

Place this object in the request's mappings array. Referenced evidence must exist inside the root; the result retains its identity. Structural validation does not authenticate the mapping or prove synchronization. Outside the validity interval, events retain their native lanes. Mapping chains and mappings across detected backward jumps are refused; split those epochs into separate source tracks.

An optional per-track window supplies start_ns and exclusive end_ns, at most 300 seconds apart. Without one, the view starts at the earliest recorded timestamp and is bounded to 300 seconds. Long intervals retain raw bounds but are clipped for display. The request has a 16 MiB aggregate input budget and 10,000 retained-event cap. Each original JSON file is bounded to 4 MiB. There are no background MCAP scans.

The desktop report offers clock selection, event inspection, a two-second focus window and reset. It plots at most 200 events per track, explicitly retaining the full bounded record. Live capture, automatic clock calibration and ros2_tracing ingestion are not available; the timeline composes saved evidence only.

Change impact explorer

bash
flightops-tui impact --project /absolute/project \
  --graph /absolute/graph.json --changed launch/camera.launch.py \
  --direction downstream --depth 3 --bookmark node:/robot/processor \
  --output /absolute/new-impact.json

Repeat --changed for reviewed project-relative files, or use --entity node:/name or --entity topic:/name for explicit graph seeds. --inventory saved-project.json uses a saved flightops.project/0 inventory instead of reading the current project. --baseline-inventory must be paired with --baseline-graph; inspected-file hash differences then identify added, changed and removed inputs automatically. Baseline relationships always use baseline declarations and capture identity.

Traversal joins changed inspected launch/parameter files to declaration candidates, then follows captured publication/subscription edges. It never proves ownership from a matching name. Arbitrary source-code changes with no supported mapping stay unknown. Namespace filters use --namespace /robot; directions are upstream, downstream and both. Cycles are bounded by visited entities. Limits default to 200 entities/500 edges, with hard caps of 500/2,000, depth 12 and 10,000 input edges. Depth/node/edge/namespace frontiers remain visible, including incomplete results.

Optional --checks check-map.json suggests explicit coverage without executing it:

json
{
  "schema": "flightops.check-map/0", "author": "Project maintainer",
  "checks": [{"id": "camera-consumer-check", "sources": ["config/camera.yaml"],
              "entities": ["node:/robot/processor"],
              "scope": "Declared coverage of the camera input contract"}]
}

Unknown coverage is not permission to skip checks. The desktop report supports current/baseline selection, namespace filtering, source/edge details and path bookmarks bound to capture identity. Bookmarks can be supplied in saved JSON or retained in the report URL. Live bridge refresh and external-project usefulness acceptance remain separate work; current commands operate on explicit snapshots.

All three commands return 0 for a complete scoped result, 3 for retained incomplete evidence and 2 for invalid input/execution setup errors. Output publication refuses replacement. Input/result consistency validation does not authenticate producers.