BM ↔ MMDVMHost conformance corpus — format, provenance & PII/licensing policy
Status: policy DECIDED, corpus EMPTY. This file exists so the rules are settled before any capture lands in git (Bench Spike 0, leg (b) — epics.md:334). The capture itself is operator-run on the bench; nothing is committed here until it conforms to the policy below. This corpus is the test oracle for the Epic 1 engine port — the M1 mitigation (risk-seam-map.adoc, row M1).
What this corpus is for
The Epic 1 MMDVMHost frame-relay engine (crates/engine) is the critical path (risk M1). Its correctness is judged against real BrandMeister ↔ MMDVMHost traffic rather than against our own assumptions. A small, anonymised golden subset captured from a live session becomes the replayable oracle: feed the recorded frames at their recorded timing, assert the engine produces the expected relayed output.
That means a record must carry two things, not one:
-
The frame bytes (DMRD data frames + the control frames around them), and
-
The inter-frame arrival timing — leg (a) and the engine’s slot/hang-timer logic are timing-sensitive, so a corpus of bytes alone is not a sufficient oracle.
Format (decided)
Encoding: JSON Lines (.jsonl) — one JSON object per frame, newline-delimited. Chosen over length-prefixed binary because the committed subset is small, must be human-reviewable for PII before it lands, and diffs legibly in git. One file per captured session.
Each line is one frame:
{"seq": 0, "t_us": 0, "dir": "bm->host", "kind": "DMRD", "len": 55, "hex": "444d52440000…"}
{"seq": 1, "t_us": 60120, "dir": "bm->host", "kind": "DMRD", "len": 55, "hex": "444d52440100…"}
{"seq": 2, "t_us": 120080, "dir": "host->bm", "kind": "control", "len": 11, "hex": "52505449…"}
| Field | Meaning |
|---|---|
|
0-based frame index within the session (monotonic). |
|
Arrival timestamp in microseconds, relative to the first frame ( |
|
|
|
Frame class — |
|
Byte length of the original frame (pre-redaction). |
|
Frame bytes, lowercase hex — after the redaction in the PII policy below. |
A capture also commits a sibling PROVENANCE.md (template at the end of this file) recording who/what/when/where, so the oracle’s origin is auditable.
PII / RF-capture-licensing policy (decided — the conservative posture)
Real amateur DMR traffic carries other operators' callsigns and DMR-IDs. Those are personal data (PII), and capturing/redistributing third-party RF may carry licensing and consent implications that vary by jurisdiction. Therefore:
-
No raw RF captures in git — ever. Raw captures stay on the operator’s machine. They are
.gitignore-guarded (docs/interop/bm-corpus/raw/+*.dmrcap) so they cannot be committed by accident. (Micro-decision #3.) -
Only a documented, anonymised oracle subset is committed, and only after manual review. Permitted sources for the committed subset, in order of preference:
-
Synthetic frames hand-built to exercise a conformance case (no third-party PII at all); or
-
The operator’s own callsign/DMR-ID traffic (self-captured, self-owned); or
-
Permission-cleared third-party frames (explicit consent recorded in
PROVENANCE.md).
-
-
Anonymisation rule for any non-synthetic frame: third-party DMR-IDs and callsigns in the
hexpayload are replaced with reserved placeholder IDs (e.g. the DMR-ID test range) before commit; the substitution is noted inPROVENANCE.md. Timing (t_us) and frame structure are preserved — those are what the oracle tests; identities are not. -
The committed subset is minimal — only enough frames to pin the conformance cases the engine must satisfy, not a bulk recording.
If the operator’s licensing/consent situation differs, they may adjust this posture — but the change is recorded here in writing before any affected capture is committed.
Layout
docs/interop/bm-corpus/ ├── README.md ← this policy (committed) ├── raw/ ← raw captures live here; .gitignore-guarded, NEVER committed └── <session>.jsonl ← anonymised oracle subset (committed by hand) + <session>.PROVENANCE.md
Capture procedure (operator runbook — leg (b))
-
Establish a live BM ↔ stock-MMDVMHost session on the rig (real board, real TG, your DMR-ID).
-
Capture frame bytes with inter-frame arrival timing into
raw/(e.g. a UART/UDP tap or stock-MMDVMHost frame logging; the.dmrcap/raw/path is git-ignored). Record enough of a QSO to cover the conformance cases (voice superframe start/continue/end + control). -
Convert the raw capture to the
.jsonlformat above, applying the anonymisation rule. -
Manually review every line for residual PII. Write
<session>.PROVENANCE.md. -
Commit only the reviewed
.jsonl+ itsPROVENANCE.mdtodocs/interop/bm-corpus/. -
Record in the spike report (leg (b) result slot) that the oracle landed and where.
PROVENANCE.md template
# Provenance — <session>.jsonl - Captured by : <callsign / operator> - Date (UTC) : <when> - Rig : <MMDVM board model> on <Pi 4 | Pi 5>, MMDVMHost <version> - Source class : synthetic | self-owned | permission-cleared - Consent : <n/a (synthetic/self) | who consented, when, how> - Anonymisation : <which IDs/callsigns were replaced, with what placeholders> - Notes : <conformance cases this subset is meant to pin>