Metadata-Version: 2.4
Name: cfs-msgid-sentinel
Version: 1.0.0
Summary: Detect NASA cFS MsgID collisions by scanning topic IDs
License: Apache-2.0
Keywords: nasa,cfs,cfe,msgid,collision,spacecraft
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: icontract>=2.7
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-bdd>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: hypothesis>=6.100; extra == "dev"
Requires-Dist: icontract>=2.7; extra == "dev"
Requires-Dist: lizard>=1.17; extra == "dev"
Requires-Dist: bandit>=1.7; extra == "dev"
Requires-Dist: pylint>=3.2; extra == "dev"
Requires-Dist: crosshair-tool>=0.0.80; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5.1; extra == "dev"
Dynamic: license-file

# 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)

```bash
python -m pip install .
cfs-msgid-sentinel --scan-path .
```

### Run locally (editable dev install)

```bash
python -m pip install -e ".[dev]"
cfs-msgid-sentinel --scan-path .
```

### Run in CI (GitHub Actions)

Add `.github/workflows/msgid-check.yml`:

```yaml
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)

```bash
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`

```bash
python scripts/generate_collision_lab_apps.py --cfs-root cFS --count 10 --prefix msgid_collision
```

Each generated app contains:

- `fsw/inc/<app>_topicids.h`
- `MESSAGES.md` (expected TopicID/MsgID values for that app)

### 3) Expected collisions

Collision design:

- **Apps**: `msgid_collision_01` … `msgid_collision_10`
- **Bases (defaults)**:
  - `PLATFORM_CMD`: `0x1800`
  - `PLATFORM_TLM`: `0x0800`
- **Intentional duplicates**: TopicIDs `0x0001`–`0x0004` are 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

```bash
# 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

```yaml
- 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`)

```yaml
- 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)
- **Topic ID headers**:
  - **CLI**: `--topicid-pattern` (default `**/*_topicids.h`)
  - **Action**: `topicid-pattern` (default `**/*_topicids.h`)
  - The parser looks for:

```c
#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`.

### 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_CMD`
- `PLATFORM_TLM`
- `GLOBAL_CMD`
- `GLOBAL_TLM`

### 3) Where base addresses come from

Base addresses come from (highest priority first):

1. **Explicit overrides** (CLI flags / Action inputs): `cmd-base`, `tlm-base`, `global-cmd-base`, `global-tlm-base`
2. **Auto-detected mapping header**: `default_cfe_core_api_msgid_mapping.h` (if present anywhere under your scan roots)
3. **Built-in defaults**:
   - `PLATFORM_CMD`: `0x1800`
   - `PLATFORM_TLM`: `0x0800`
   - `GLOBAL_CMD`: `0x1860`
   - `GLOBAL_TLM`: `0x0860`

### 4) Expected project structure (minimal)

You don’t need a special layout—just ensure the scan root(s) include your headers:

```text
<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`.

- **Lots of heuristic classification**
  - Ensure your `*_msgids.h` headers are included by `--msgid-pattern` / `msgid-pattern`.
  - If your repo uses different filenames, set a custom `msgid-pattern`.

- **Different base addresses than the defaults**
  - Prefer committing a correct `default_cfe_core_api_msgid_mapping.h` under the scan roots.
  - Or override bases via CLI flags / Action inputs.

---

## Development

### Local development

Create a virtualenv, install dev deps, and run the safety-critical quality gate:

```bash
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)

```bash
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:

```bash
python scripts/validate_real_workspace.py --scan-path /home/aero/Desktop/cFS/apps
```

### Build / distribution smoke

```bash
python -m pip install build
python -m build
python -m pip install dist/*.whl
cfs-msgid-sentinel --help
```

---

## License

Apache-2.0. See `LICENSE`.

