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.
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| cfs_msgid_sentinel-1.0.2.tar.gz | 31.6 kB | Without provenance | Details for cfs_msgid_sentinel-1.0.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cfs_msgid_sentinel-1.0.2-py3-none-any.whl | Python 3 | none | any | Details for cfs_msgid_sentinel-1.0.2-py3-none-any.whl |
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
|
Security for cfs-msgid-sentinel 1.0.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.