Prevent silent runtime failures caused by cFS MsgID collisions.
cfs-msgid-sentinel scans NASA cFS header files, extracts DEFAULT_*_TOPICID definitions, classifies each topic into one of the four collision domains (channels), computes MsgIDs, and reports collisions/near-misses as:
- CLI output (table / JSON / Markdown summary)
- GitHub Actions annotations (
::error/::warning) - GitHub Job Summary (
$GITHUB_STEP_SUMMARY) - GitHub Action outputs (
$GITHUB_OUTPUT)
python -m pip install .
cfs-msgid-sentinel --scan-path .python -m pip install -e ".[dev]"
cfs-msgid-sentinel --scan-path .Add .github/workflows/msgid-check.yml:
name: MsgID collision check
on: [push, pull_request]
jobs:
msgid-sentinel:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./
with:
scan-paths: "."
topicid-pattern: "**/*_topicids.h"
msgid-pattern: "**/*_msgids.h"
fail-on-collision: "true"
near-miss-gap: "2"
report-format: bothThis repo includes a local-only collision lab that clones upstream NASA cFS into ./cFS/ (gitignored), then generates 10 apps under cFS/apps/ with intentionally duplicated TopicIDs so cfs-msgid-sentinel can be validated against a real-looking tree.
git clone --depth 1 https://github.com/nasa/cFS.git cFSAlso ensure cFS/ remains ignored (see .gitignore).
python scripts/generate_collision_lab_apps.py --cfs-root cFS --count 10 --prefix msgid_collisionEach generated app contains:
fsw/inc/<app>_topicids.hMESSAGES.md(expected TopicID/MsgID values for that app)
Collision design:
- Apps:
msgid_collision_01…msgid_collision_10 - Bases (defaults):
PLATFORM_CMD:0x1800PLATFORM_TLM:0x0800
- Intentional duplicates: TopicIDs
0x0001–0x0004are reused across all 10 apps for both CMD-like and TLM-like names.
Expected collision groups (8 total):
| Channel | TopicID | MsgID |
|---|---|---|
PLATFORM_CMD |
0x0001 |
0x1801 |
PLATFORM_CMD |
0x0002 |
0x1802 |
PLATFORM_CMD |
0x0003 |
0x1803 |
PLATFORM_CMD |
0x0004 |
0x1804 |
PLATFORM_TLM |
0x0001 |
0x0801 |
PLATFORM_TLM |
0x0002 |
0x0802 |
PLATFORM_TLM |
0x0003 |
0x0803 |
PLATFORM_TLM |
0x0004 |
0x0804 |
The machine-readable expected-collisions file is expected_collisions.json, which is generated
by scripts/generate_collision_lab_apps.py and gitignored (local artifact).
# Basic scan
cfs-msgid-sentinel --scan-path .
# If your headers live under specific subtrees
cfs-msgid-sentinel --scan-path apps --topicid-pattern "**/fsw/inc/*_topicids.h"
# Add near-miss warnings (IDs within N of each other, per channel)
cfs-msgid-sentinel --scan-path . --near-miss-gap 3
# Machine-readable output
cfs-msgid-sentinel --scan-path . --format json
# CI-friendly (no ANSI), but still return success even if collisions exist
cfs-msgid-sentinel --scan-path . --no-color --no-fail-on-collision
# Emit an audit report (also writes collusion-report.txt)
cfs-msgid-sentinel --scan-path . --report- uses: ./
with:
scan-paths: "."
topicid-pattern: "**/*_topicids.h"
msgid-pattern: "**/*_msgids.h"
fail-on-collision: "true"
near-miss-gap: "0"
report-format: both- uses: ./
id: sentinel
with:
report-format: json
fail-on-collision: "false"
- name: Print summary
run: |
echo '${{ steps.sentinel.outputs.allocation-map }}' | jq '.summary'- Root(s):
- CLI:
--scan-path <path>(comma-separated roots) - Action:
scan-paths(comma-separated roots)
- CLI:
- Topic ID headers:
- CLI:
--topicid-pattern(default**/*_topicids.h) - Action:
topicid-pattern(default**/*_topicids.h) - The parser looks for:
- CLI:
#define DEFAULT_<NAME>_TOPICID <hex_or_decimal>- MsgID headers (Tier-1 channel classification):
- CLI:
--msgid-pattern(default**/*_msgids.h) - Action:
msgid-pattern(default**/*_msgids.h) - From
msgid-pattern, the scanner also derives a sibling pattern for*_msgid_values.h.
- CLI:
cfs-msgid-sentinel computes:
Final MsgID = Base | TopicID
Topic IDs are collision-checked within each of the four channels:
PLATFORM_CMDPLATFORM_TLMGLOBAL_CMDGLOBAL_TLM
Base addresses come from (highest priority first):
- Explicit overrides (CLI flags / Action inputs):
cmd-base,tlm-base,global-cmd-base,global-tlm-base - Auto-detected mapping header:
default_cfe_core_api_msgid_mapping.h(if present anywhere under your scan roots) - Built-in defaults:
PLATFORM_CMD:0x1800PLATFORM_TLM:0x0800GLOBAL_CMD:0x1860GLOBAL_TLM:0x0860
You don’t need a special layout—just ensure the scan root(s) include your headers:
<mission_root>/
apps/
<app>/
fsw/inc/<app>_topicids.h
...
cfe/
... *_msgids.h
... *_msgid_values.h
...
| Input | Description | Default |
|---|---|---|
scan-paths |
Root directories to scan (comma-separated) | . |
topicid-pattern |
Glob pattern(s) for topic ID headers (comma-separated) | **/*_topicids.h |
msgid-pattern |
Glob pattern(s) for MsgID headers (comma-separated) | **/*_msgids.h |
cmd-base |
Platform command MsgID base address | 0x1800 |
tlm-base |
Platform telemetry MsgID base address | 0x0800 |
global-cmd-base |
Global command MsgID base address | 0x1860 |
global-tlm-base |
Global telemetry MsgID base address | 0x0860 |
fail-on-collision |
Fail the workflow if a collision is detected | true |
near-miss-gap |
Warn about topic IDs within N of each other (0 to disable) | 0 |
report-format |
Output format: summary, json, or both |
both |
| Output | Description |
|---|---|
collision-count |
Number of collisions found |
has-collisions |
true if any collisions were found |
allocation-map |
JSON string containing summary + full allocation map |
-
No topic ID files found
- Check you’re scanning the right root (
--scan-path/scan-paths). - Tighten or loosen
--topicid-pattern/topicid-pattern.
- Check you’re scanning the right root (
-
Lots of heuristic classification
- Ensure your
*_msgids.hheaders are included by--msgid-pattern/msgid-pattern. - If your repo uses different filenames, set a custom
msgid-pattern.
- Ensure your
-
Different base addresses than the defaults
- Prefer committing a correct
default_cfe_core_api_msgid_mapping.hunder the scan roots. - Or override bases via CLI flags / Action inputs.
- Prefer committing a correct
Create a virtualenv, install dev deps, and run the safety-critical quality gate:
python -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
python -m pip check
ruff format --check .
ruff check .
pylint src/cfs_msgid_sentinel
mypy src
bandit -q -r src
lizard -l python -C 10 src
pytestcfs-msgid-sentinel --scan-path tests/fixtures --topicid-pattern "**/real/*_topicids.h"CI runners cannot access your local cFS tree, so this is a required local gate:
python scripts/validate_real_workspace.py --scan-path /home/aero/Desktop/cFS/appspython -m pip install build
python -m build
python -m pip install dist/*.whl
cfs-msgid-sentinel --helpApache-2.0. See LICENSE.