flightops_tools.check_catalog is a deterministic, ROS-free selector for three bounded sensor observations. It describes existing capabilities and checks whether the caller supplied enough facts to consider each one. It does not connect to ROS, run a command, publish, or authorize execution.
from flightops_tools.check_catalog import check_catalog, select_checks
catalog = check_catalog()
selection = select_checks(context)check_catalog() -> dict[str, Any] returns schema, the fixed-order checks array, and a frozen evaluator record. The order is endpoint presence, QoS compatibility, then bounded message delivery. Every check declares its prerequisites, observed and unknown conditions, observation bound, message limit, cost class, side-effect class, required authorization scopes, and the existing capability it binds to.
select_checks(context: Mapping[str, Any]) -> SelectionResult returns JSON data with the schema flightops.sensor-check-selection/1. status is selected when at least one check has its technical prerequisites, or no_eligible_checks when none do. selected_check_ids keeps catalog order. Each checks entry carries eligible, exact reasons, observed and unknown conditions, and a separate authorization summary. A no_eligible_checks result is an ordinary typed result. Malformed JSON shape, unknown fields, unsupported schema values, invalid ROS names, and inconsistent graph summaries raise InvalidCheckContextError, a ValueError subclass.
Context
The input schema is flightops.sensor-check-context/1. Only schema is required. Missing sections and explicitly unknown observations are valid and produce reasons such as source_topic_unknown, graph_unknown, configuration_unknown, or clock_unknown.
{
"schema": "flightops.sensor-check-context/1",
"source": {"topic": "/scan"},
"graph": {
"status": "observed",
"topics": [
{"name": "/scan", "types": ["sensor_msgs/msg/LaserScan"],
"publisher_count": 1, "subscriber_count": 1}
],
"edges": [
{"node": "/camera", "topic": "/scan", "direction": "pub",
"type": "sensor_msgs/msg/LaserScan",
"qos": {"reliability": "BEST_EFFORT", "durability": "VOLATILE"}},
{"node": "/viewer", "topic": "/scan", "direction": "sub",
"type": "sensor_msgs/msg/LaserScan",
"qos": {"reliability": "RELIABLE", "durability": "VOLATILE"}}
]
},
"configuration": {
"status": "known", "ros_distribution": "jazzy",
"rmw_implementation": "rmw_fastrtps_cpp"
},
"clock": {"status": "available", "time_basis": "monotonic-wall"},
"authorization": {"declared_scopes": ["ros.graph.observe"]}
}source.topic must be an absolute ROS name. For graph.status: "observed", topics and edges may each be a full array or an onscope bounded summary of the form {"length": N, "sample": [...], "elided": true|false}; onscope's elided summaries also include shown, which must match the sample length. A full context accepts at most 200 topic rows and 500 endpoint rows. Message types use ROS package/msg/Type names. An absent topic in a complete topic list is usable evidence for an endpoint-absence observation. If a bounded sample was elided before the source topic, absence is unknown and the check is ineligible. QoS selection requires complete rows for every publisher and subscriber of the source topic; an elided graph can still provide those rows when its sample includes all endpoints for that topic and the sample counts match the topic row. Repeated rows with the same node/topic/direction (or identical rows without a node) leave QoS coverage unknown: node names cannot distinguish multiple endpoints from duplicate data.
Configuration is needed only for message delivery. It needs known ros_distribution and rmw_implementation strings. The delivery deadline requires an available monotonic wall timer; ROS time and payload timestamps are not used for that deadline. Endpoint and QoS observations do not require any clock section.
Checks and boundaries
| Check | Existing binding | Bound | Required scope |
|---|---|---|---|
endpoint_presence | onscope.graph.capture_graph(settle_s=1) | 1 second graph-discovery settle window; 0 messages | ros.graph.observe |
qos_compatibility | The same graph capture plus onscope.qos.incompatibilities and unresolved_policies | 1 second graph-discovery settle window; 0 messages | ros.graph.observe |
bounded_message_delivery | flightops_tools.public_readiness.LaunchReadiness through the existing after-entrypoint readiness sidecar | At most 5 seconds and 1 received message | ros.graph.observe, ros.topic.receive_one |
The graph duration is the existing ROS discovery settle window. Process startup and output rendering are not represented by that observation bound. The message delivery executor must configure the existing shared readiness timeout to five seconds or less and one topic condition. The existing readiness capability receives at most one complete message for a successful --once condition; its documented sidecar and attribution limits still apply. See launch readiness.
For every returned check, technical eligibility is distinct from authorization. authorization.status reports whether all required scopes appear in the caller's authorization.declared_scopes; it is descriptive input, not a grant. execution_authorized is always false, including when all scopes are declared. The caller must apply its own authorization flow.
The evaluator is revision 2 with a content digest over the catalog and the exact installed selector and QoS-rule source bytes. Changing either implementation changes its identity. Source files must be available; missing source is an error. Both measurement and isolation boundaries are explicitly unverified: selection does not measure a live system, and the eventual observer is not independently isolated from the host ROS domain. Graph presence does not identify which process owns a publisher. A message arrival does not establish publisher attribution, payload-clock relationships, application health, or hardware safety. Unknown QoS policies remain unknown; no selection result converts them into compatibility or health claims.
The message binding reuses public launch readiness's after-entrypoint observer. That capability is available as part of a caller-selected launch workflow; this catalog does not start a launch or create a separate executor. The observation itself creates a local subscriber, publishes nothing, and may receive an existing same-name publisher's message.