cFS MsgID Sentinel (cfs-msgid-sentinel)
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)
Quick start (30 seconds)
Run locally (from repo)
python -m pip install .
cfs-msgid-sentinel --scan-path .
Run locally (editable dev install)
python -m pip install -e ".[dev]"
cfs-msgid-sentinel --scan-path .
Run in CI (GitHub Actions)
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: both
cFS collision lab (local, repo-internal)
This 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.
1) Clone cFS into this repo (gitignored)
git clone --depth 1 https://github.com/nasa/cFS.git cFS
Also ensure cFS/ remains ignored (see .gitignore).
2) Generate 10 collision-heavy apps + per-app MESSAGES.md
python scripts/generate_collision_lab_apps.py --cfs-root cFS --count 10 --prefix msgid_collision
Each generated app contains:
fsw/inc/<app>_topicids.hMESSAGES.md(expected TopicID/MsgID values for that app)
3) Expected collisions
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).
Usage
CLI examples
# 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
GitHub Actions examples
Typical cFS mission repo
- uses: ./
with:
scan-paths: "."
topicid-pattern: "**/*_topicids.h"
msgid-pattern: "**/*_msgids.h"
fail-on-collision: "true"
near-miss-gap: "0"
report-format: both
Consume JSON output (allocation-map)
- uses: ./
id: sentinel
with:
report-format: json
fail-on-collision: "false"
- name: Print summary
run: |
echo '${{ steps.sentinel.outputs.allocation-map }}' | jq '.summary'
Configuration: how it finds headers (and what it expects)
1) What gets scanned
- 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:
2) How MsgIDs are computed
cfs-msgid-sentinel computes:
Final MsgID = Base | TopicID
Topic IDs are collision-checked within each of the four channels:
PLATFORM_CMDPLATFORM_TLMGLOBAL_CMDGLOBAL_TLM
3) Where base addresses come from
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
4) Expected project structure (minimal)
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
...
GitHub Action inputs / outputs
Inputs
| 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 |
Outputs
| 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 |
Troubleshooting
-
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
Development
Local development
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
pytest
Manual smoke run (fixtures)
cfs-msgid-sentinel --scan-path tests/fixtures --topicid-pattern "**/real/*_topicids.h"
Required real-workspace validation
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/apps
Build / distribution smoke
python -m pip install build
python -m build
python -m pip install dist/*.whl
cfs-msgid-sentinel --help
License
Apache-2.0. See LICENSE.
Release files for cfs-msgid-sentinel 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cfs_msgid_sentinel-1.0.2.tar.gz | 31.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cfs_msgid_sentinel-1.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.6 kB
Release files / cfs_msgid_sentinel-1.0.2.tar.gz
| Download URL | cfs_msgid_sentinel-1.0.2.tar.gz |
|---|---|
| Size | 31.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
079400de9b7141ac24f126ea2f8131290a22b894bce180724b6cf80b44dfb7c1
|
|
BLAKE2b-256 checksum How to use checksums |
f41aadfeb18dc6fda0e658dd874abbc48ad4d3792f78ac360ae6f92404f849cf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / cfs_msgid_sentinel-1.0.2-py3-none-any.whl
| Download URL | cfs_msgid_sentinel-1.0.2-py3-none-any.whl |
|---|---|
| Size | 32.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b77f9cbeb172026d5fdbefbe7a2bd624704a25aa932f8e68b19bd87bd8c580c3
|
|
BLAKE2b-256 checksum How to use checksums |
0f05e717b15dde091c11956d7117bf3dd23932efe4f16a88093bd5be1e0f4077
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|