C++ library to read and write MISB KLV data — ST 0601 (UAS Datalink Local
Set) + ST 0903 (VMTI) — from/to MPEG-TS containers via gstreamer (file or
stream; real-time insertion via appsrc). Video passthrough can generate ST 0604
Precision Time Stamp SEI on request (ADR 0024 — off by default, so passthrough
video is byte-identical); the rest of ST 0604 (ES-layer timestamp reading, H.265
Nano, Commercial time code) and an ffmpeg backend are deferred — see ADRs
0008/0009.
This file is the canonical, vendor-neutral copy — edit it here.
CLAUDE.mdis a one-line@AGENTS.mdimport, since Claude Code auto-loads that filename. Adding support for another agent means adding another thin pointer, never a second copy of these rules.
The project's working knowledge lives in context/ as an
OKF
v0.2 bundle.
- Read
context/index.mdfirst — the bundle catalog. context/CONVENTIONS.md— frontmatter, type vocabulary, ingest/query/lint rules. Read before editingcontext/.context/is agent-owned.references/is append-only: read it, add a snapshot on a directed ingest, never edit one that's already there.- For the live plan and status, read
planning/PROGRESS.mdandplanning/ROADMAP.md.
When you implement a significant change (new feature, milestone, decision):
- ✅ Code changes staged
- ✅
context/log.mdupdated (append dated entry) - ✅
planning/PROGRESS.mdupdated (Now/In-progress/Next reflect current state) - ✅ If decision made: ADR written,
context/decisions/index.mdupdated
Never commit code without updating the docs that describe it. If you're about
to commit and haven't touched log.md or PROGRESS.md, stop and update them first.
What goes in each doc — and the rules behind this checklist — is Planning hygiene below.
Each doc has one job — keep them from re-narrating each other. The why behind
each rule is in
context/workflow-rationale.md — read it before
relaxing a rule. A rule without its reason gets deleted the first time it's
inconvenient.
context/log.md— the durable chronological record: what landed when, milestone/decision detail, routine ingests/lint. History lives here, newest-first.context/decisions/index.md— the decided register: fork # ↔ ADR ↔ status. The single source of truth for what's decided.planning/PROGRESS.md— present state only (Now / In-progress / Next). Volatile; rewrite each session. No "Done" history (that'slog.md); "Next" is the 1–3 immediate actions and points to ROADMAP for the candidate backlog — it doesn't re-list it. "Now" is where the work is, not a feature inventory: if a sentence would still be true after a month of no work, it's durable knowledge — it belongs in acontext/concept or an ADR, with only a pointer here.planning/ROADMAP.md— scope, phases, and the single backlog of open + candidate forks (a fork = a decision point that needs a choice — a fork in the road, not a git fork; see CONVENTIONS § Decisions). Defers the decided register (and the fork count) todecisions/index.md; never enumerates a fork range.
So:
- On a significant decision (a fork resolved or changed): write/update the ADR
in
context/decisions/per the lifecycle incontext/CONVENTIONS.md(decision_status: proposed → accepted / superseded / deferred), add/update its row in the decided register (decisions/index.md), append one thinlog.mdline (chronology + link — the ADR owns the rationale; see CONVENTIONS § Decisions), and refresh PROGRESS's present state. Touch ROADMAP only if it opens/closes an open fork or shifts a phase. - On implementing a significant change: append a
log.mdentry (the detail) and refreshplanning/PROGRESS.md's present state (Now / In-progress / Next) — a thin pointer, not a history. Do this in the same commit as the implementation: a doc updated "later" is wrong in between. - Closing scrubs the future: when you resolve a fork or complete a candidate / deferred / "Next" item, delete its forward-looking mentions (ROADMAP candidates, PROGRESS "Next") — not just the present-state bullet you're editing. Open work has one home; a resolved item leaves it. (Forward-looking claims drift the same way history does — this is the future-tense half of "one job per doc".)
- No live tallies in durable prose: never bake a running count (test cases,
item totals, "N of M done", fork ranges like "1–N") into present-tense docs
(PROGRESS, ROADMAP) or ADR bodies — it drifts. Say "all CTest cases green", not
"19"; "none open, see the register", not "forks 1–17". A specific number belongs
only in a dated
log.mdsnapshot (history, frozen at write time). Same for any state that moves — describe the state, don't tally it.
Preferred: cmake --preset release && cmake --build --preset release && ctest --test-dir build/release
(also debug, sanitize — see README.md's preset matrix; these are the same
presets CI uses). Fallback without presets:
cmake -S . -B build && cmake --build build && ctest --test-dir build.
Normal builds consume committed generated fixtures and need no Python. Python
3.11+ is needed only for the optional registry/fixture regeneration targets.
klv_bench is a timing harness, built by default and deliberately not a
CTest case — a timing threshold in CI measures the runner, not the library. Run
it by hand when a number is needed, and read it as ranges rather than point
values (codec rows move ~15% run to run on a normal desktop):
./build/release/klv_bench test/fixtures/synthetic-basic.klv test/fixtures/synthetic-timed-0x06.ts [MiB]
Format changed C/C++ lines with git clang-format origin/main (preview with
git clang-format --diff origin/main); the checked-in .clang-format owns the
style. Handwritten C++ has a formatter-clean baseline; generated registry
headers opt out in their directory because the generator owns their bytes.
Do not turn an unrelated change into a whole-tree formatting sweep.
The formatting-only baseline commit is listed in .git-blame-ignore-revs.
GitHub honors it automatically; for the same local blame view, run
git config blame.ignoreRevsFile .git-blame-ignore-revs.
Two options: MISBKLV_GSTREAMER (default ON) builds the gstreamer media backend
— the KLV core builds and tests without it, and that separation is load-bearing
(see context/backend-scope.md). MISBKLV_SANITIZE
(default OFF) builds with -fsanitize=address,undefined; the core is kept clean
under it.
What the suites guard, so a change lands in the right one:
st0601_examples— the standard's own per-item worked examples. The authority on scales and encoding for the 0601 registry; a scale change that passes everything else must pass this.hardening— multi-byte BER-OID tags (≥128), Report-on-Change trimmed packets, and malformed/adversarial input (overflow guards in length arithmetic, length validation incodec::decode).message,roundtrip,imapb,nested_vmti,vtarget_series,standalone_vmti— core encode/decode and the ST 1201 IMAPB path.gst_*andstream_*— the backend: extraction and insertion, file and live, against project-owned synthetic fixtures undertest/fixtures/. The developer-provided media indata/is not required by the suite.api_stream,stream_stop— the high-level API and prompt cancellation (ADR 0019).jmisb_crosscheck— our output read back by an independent implementation.
CI runs build+test, a consumer smoke test (find_package(misbklv COMPONENTS gst) against a real out-of-tree build), a sanitizer job (core only), and
generated-output drift checks for the registry tables and synthetic fixtures
(ADR 0012,
ADR 0028).
American English, everywhere prose appears — code comments, doc strings, CLI
help and error text, test messages, Markdown, commit messages. behavior not
behaviour, center not centre, -ize/-ization not -ise/-isation,
meters not metres, license (noun and verb), judgment, analyze. This is
consistency, not correctness: the standards this library implements (MISB ST
0601, "Frame Center") are American-spelled, so matching them keeps our prose
and the item names we quote from disagreeing on the same page.
references/ is exempt, and no snapshot already there is ever restyled —
it is append-only source-of-truth input: an ingest may add a new PDF/.txt
snapshot, but nothing already deposited is edited, reformatted, or restyled to
match. So is any vendored third-party code: read it, never restyle it.
No Co-Authored-By trailer — keep history clean. When committing on the user's
behalf, write the message without the Claude co-authorship line.
When an agent posts a PR body, an issue, or a review comment, it identifies
itself — the generating model — as the author, e.g.
Author: Claude Code (model: claude/opus-5). This is separate from the git
commit author, which stays the human or machine that invoked git, and the
commit message's author field is not the place for it (see Commit messages
above). It goes in the PR/issue/comment prose so a reader can tell which model
produced what, and a comment's author line names the model that wrote that
comment — not the model that opened the PR. More than one agent works this repo,
so an unattributed comment is genuinely ambiguous.
Spell the model as <producer>/<model> — the vendor or harness that
generated the text, then the model: claude/opus-5, openai/gpt-5,
opencode-go/muse-spark-1.2-contributor. One pair, no exceptions. That is the
same pair the knowledge bundle's generated.by actor form uses
(context/CONVENTIONS.md § Actors), written for a
narrower case, so an agent spells itself identically in frontmatter and in
prose. Don't exempt an agent whose harness and model are distinct: two
spellings for one agent is the failure this rule exists to prevent, and a
<producer> slot that accepts a harness already fits it.
The rule applies going forward; comments predating it are left as they are.
data/ holds developer-provided media that may carry real location and identity
data, and is gitignored. Never commit a file from data/, and never copy
what one contains — coordinates, platform or sensor identity, tail numbers,
absolute times — into code, tests, fixtures, commit messages, documentation, or
agent memory. test/fixtures/ is project-authored and synthesized by
generate_synthetic_fixtures.py, never derived from supplied media.
Scope: media this project holds, and anything derived from it. The rule
protects recordings; it is not a rule about citing external material. Published
facts about third-party media the project does not hold — where a sample came
from, who holds the rights, why its terms were judged unclear — are bibliography.
context/data-samples.md is exactly that: the record
of why the historical corpus was removed
(ADR 0028), describing
files this repository never contained and no longer references. It is subject to
this rule for accuracy, not for redaction — and it is the evidence that the
removal was deliberate, so it is retained.
The sibling repository parrot-to-klv carries this same rule, scoped the same
way, where data/ holds real flight recordings. The same class of data crosses
both repositories, so the two are kept aligned deliberately — change one and
change the other.
references/— MISB standards (PDF +.txtextract). Source of truth, append-only: read; add a snapshot on a directed ingest; never edit what's there.context/— agent knowledge bundle (maintain this).docs/— human-facing guides (terse).data/— ignored developer-provided media; seedata/README.md.test/fixtures/— project-owned deterministic KLV/MPEG-TS fixtures and the generator used to create them.planning/— live plan + progress (ROADMAP.md, PROGRESS.md). Read first for "what now".