ROS2 Markers and Observability

The AutonomyOps ROS2 governance stack emits observable PASS markers to stdout at each governance checkpoint. These markers are simultaneously written to a WAL (Write-Ahead Log) for durable audit. CI harnesses, demo scripts, and operators all rely on these exact strings.

PASS marker format

PASS <label> context=<context>
  • label — kebab-case identifier for the checkpoint (stable across versions)

  • context — execution path: container, native, or sim

Example:

PASS ros2-telemetry-active context=container
PASS ros2-runtime-container-ready context=container
PASS ros2-stack-start context=container
PASS ros2-policy-enforced context=container
PASS ros2-wal-recording context=container

Marker reference

Marker label

Context

When emitted

ros2-telemetry-active

container / native

WAL emitter initialised — durable recording is live

ros2-runtime-container-ready

container only

Container execution path selected; Docker is reachable

ros2-stack-start

container / native

About to dispatch to ros2.RunGoverned

ros2-policy-enforced

container / native

Policy evaluation passed AND execution returned nil

ros2-wal-recording

container / native

WAL has a record of this execution (post-exec)

Container vs native context

ros2-runtime-container-ready is emitted only on the container path. It is absent when Docker is unavailable and the native fallback is taken.

CI harnesses that assert all five markers must first confirm that Docker is present. If running on a host without Docker, assert only the four markers whose context is native:

# Container path — assert all five
grep -F 'PASS ros2-telemetry-active context=container' output.txt
grep -F 'PASS ros2-runtime-container-ready context=container' output.txt
grep -F 'PASS ros2-stack-start context=container' output.txt
grep -F 'PASS ros2-policy-enforced context=container' output.txt
grep -F 'PASS ros2-wal-recording context=container' output.txt

# Native path — assert four (container-ready is absent)
grep -F 'PASS ros2-telemetry-active context=native' output.txt
grep -F 'PASS ros2-stack-start context=native' output.txt
grep -F 'PASS ros2-policy-enforced context=native' output.txt
grep -F 'PASS ros2-wal-recording context=native' output.txt

When markers are NOT emitted

Situation

Markers emitted

Policy denied (ErrPolicyDenied)

ros2-telemetry-active, ros2-runtime-container-ready (if container), ros2-stack-start

ErrImageRequired

ros2-telemetry-active only

ErrNeitherAvailable

ros2-telemetry-active only

Execution succeeded

All five

ros2-policy-enforced and ros2-wal-recording are post-execution markers — they are never emitted for denied or failed executions.

WAL recording

Every PASS marker is backed by a WAL entry before the stdout line is written. The WAL:

  • Survives process crashes between marker emission and the final ros2-wal-recording

  • Is stored in a temporary directory (/tmp/adk-ros2-wal-<random>/) for the duration of the governance session

  • Is drained to the orchestrator after execution completes

The WAL entry format (JSONL):

{"kind":"PASS","label":"ros2-telemetry-active","context":"container","ts":"2026-04-07T10:00:00.123456Z"}

Querying WAL entries after a run

With a running orchestrator:

# Fetch recent log entries from the orchestrator
autonomy logs --orchestrator-url http://localhost:8888 --limit 50

# Filter to this node only
autonomy logs --orchestrator-url http://localhost:8888 \
    --node my-edge-01 --limit 50

# Stream new entries as they arrive
autonomy logs --orchestrator-url http://localhost:8888 --follow

WALPass — the marker primitive

PASS markers are emitted via markers.WALPass(label, context, emitter) in cmd/autonomy/pkg/markers/:

// WALPass writes a PASS marker to both the WAL and stdout.
// It calls emitter.EmitPASS first (durable) then markers.Pass (stdout).
// Returns an error if either write fails.
func WALPass(label, context string, emitter PassEmitter) error

The PassEmitter interface is satisfied by *telemetry.Emitter. Tests can inject a lightweight in-memory emitter to verify marker emission without a real WAL directory.

CE-tier markers (autonomy run ros2.launch)

The CE dispatch path (autonomy run ros2.launch) emits the same five PASS markers using the same WAL-backed mechanism. The execution context is resolved by dualpath.Resolve at call time, so context annotation is accurate even when Docker availability changes between calls.

Observability pipeline

PASS marker (stdout)
        │
        ▼
markers.WALPass ──▶ telemetry.Emitter.EmitPASS ──▶ WAL (BoltDB)
                                                         │
                                                   orchestrator drain
                                                         │
                                                  GET /v1/logs
                                                         │
                                               autonomy logs / SSE

The WAL acts as a durable buffer: even if the orchestrator is temporarily unreachable, markers are persisted locally and drained when connectivity is restored.

Demo script pattern

Demo scripts and CI harnesses that need to assert PASS markers can use:

#!/bin/bash
set -euo pipefail

output=$(autonomy ros2 run \
    --image ghcr.io/autonomyops/adk-ros2-runtime:local \
    launch demo_robot arm_demo.launch.py 2>&1) || true

for marker in \
    "PASS ros2-telemetry-active context=container" \
    "PASS ros2-runtime-container-ready context=container" \
    "PASS ros2-stack-start context=container" \
    "PASS ros2-policy-enforced context=container" \
    "PASS ros2-wal-recording context=container"
do
    if ! echo "$output" | grep -qF "$marker"; then
        echo "FAIL: marker not found: $marker" >&2
        exit 1
    fi
done

echo "All PASS markers present"

Troubleshooting

ros2-policy-enforced missing

The governance stack executed but the policy denied the action (or the subprocess returned a non-zero exit code). Check:

  1. The policy file (demo/bundles/ros2/policies/ros2_safety.rego) allows the requested package and launch file.

  2. The --image matches the build in the bundle activation.entrypoint.

  3. Run autonomy bundle inspect demo/bundles/ros2.tar --local --show-policy to verify the policy text.

ros2-runtime-container-ready missing

Docker is not reachable. Confirm:

docker info

If Docker is absent, the native path is taken. The marker is not emitted on the native path — this is expected behaviour, not an error.

WAL entries not appearing in autonomy logs

Requires a running orchestrator:

autonomy-orchestrator serve --listen 0.0.0.0:8888 --data-dir /tmp/orch
autonomy logs --orchestrator-url http://localhost:8888 --follow

Share snippet

A compact, copy-pasteable summary of this demo. Suitable for an email, issue, sales note, or proof artifact.

Prerequisites

  • autonomy CLI on PATH

  • Docker reachable (for the container path; the native fallback works without it but does not emit ros2-runtime-container-ready)

Run it

output=$(autonomy ros2 run \
    --image ghcr.io/autonomyops/adk-ros2-runtime:local \
    launch demo_robot arm_demo.launch.py 2>&1) || true

for marker in \
    "PASS ros2-telemetry-active context=container" \
    "PASS ros2-runtime-container-ready context=container" \
    "PASS ros2-stack-start context=container" \
    "PASS ros2-policy-enforced context=container" \
    "PASS ros2-wal-recording context=container"
do
    echo "$output" | grep -qF "$marker" \
        && echo "OK:      $marker" \
        || echo "MISSING: $marker"
done

Expected proof markers

  • All five PASS ros2-* strings present in the captured output

  • WAL entries durable to /tmp/adk-ros2-wal-<random>/ for the session

  • On the native fallback path, ros2-runtime-container-ready is absent — this is expected behaviour, not a failure

What this proves

The ROS2 governance pipeline emits five deterministic, exact-string PASS markers at the checkpoints CI harnesses care about: telemetry active, runtime ready, stack start, policy enforced, WAL recorded. Each marker is WAL-backed before stdout is written, so the audit log survives crashes; markers are never emitted for denied or failed executions — absence is itself a signal.

See also

  • ROS2 Governance — dual-path execution and policy gates

  • Robotics Quickstart — end-to-end run

  • cmd/autonomy/pkg/markers/ — marker primitive package

  • telemetry/ — WAL emitter package