synapse-cdm 2.2.0
synapse-cdm 2.2.0
Rendered by synapse release-notes from the tree at this release's tag. Every
section below except major changes is a derivation; that one is quoted from
RELEASE_NOTES.md, which is written by a person. MIGRATIONS.md condition 4 is
unchanged by this file: it is still a person's, and this only makes the nine
mechanical fields impossible to mistype.
Versions
| axis | version |
|---|---|
| package version | 2.2.0 |
| CDM version | 2.1.0 |
| SC-OES version | 0.1.0 |
| Adapter API version | 2.1.0 |
| manifest schema version | 1.2.0 |
Major changes
The audit release. Between the 2.1.2 release of 2026-09-12 and this one, nothing was added to
what the package translates: the same fourteen adapters, the same 538 fixture verdicts, the same
six published CDM schemas at schema_version 2.1.0. What moved is what SOIF Part 1 — the Synapse
Open Interoperability Framework's Foundation & Assurance part, a private specification whose
section numbers this file cites as §N — exists to make checkable: two adapters that crashed on a
valid document nested a thousand elements deep now refuse it at a declared bound, and four more
declare the same bound; a maturity rung that eleven manifests typed is now computed by the
harness; an evidence record generated on one machine now reproduces on another; CI runs the suite
on every interpreter the package claims, lints, and runs the wheel gate on every push; the release
pipeline's witness verifier re-derives the digests it used to check for shape; the two npm
advisory exceptions are deleted on the trigger their own files named; no tracked file points a
reader at a private document; and every sentence the audit found false is corrected where it
stands. Nothing is removed, renamed or narrowed, so a 2.1.2 consumer keeps working without doing
anything — with the three refusals listed below, each of which replaces a crash or a wrong
exception on input no shipped fixture ever carried.
If you are upgrading from 2.1.2 — which is what the index serves — read this as the MINOR it
is. If you are upgrading from 2.0.0, read the 2.1.2 notes first: they are the body of the
v2.1.2 Release, and everything they describe is still here.
Why the number is 2.2.0
The number is the derived floor, and for the first time since v2.1.0 it moved for content.
gates/bump_derivation.py reads the arc from v2.1.2 and derives MINOR from importable names
that did not exist at that tag, with nothing removed: adapter.InputTooDeep,
json_nesting_depth, container_depth, enforce_depth_bound, is_shipped and shipped; the
ROUNDTRIP_TOLERANCE, ROUNDTRIP_TRANSFORMS and roundtrip_reference() members of Adapter;
canonical.py, a new module; harness.select_fixtures and fixtures_required_message;
version.SEMVER_RE and is_semver; six *_MAX_DEPTH module constants; and a [lint] extra in
pyproject.toml, which is the table's optional-dependency row. The eighty-three units the table
cannot classify on its own — bodies that moved with no name added or removed, and import blocks
the gate keys by position — are every one ruled in MIGRATIONS.md's 2.2.0 section, and the gate
reads those rulings and reports nothing unruled. The two correctives before this release each
moved the number for a workflow's defect; this one moves it for what a consumer receives.
Package version 2.2.0 · CDM schema_version 2.1.0 · Adapter API 2.1.0. The first two are
unequal by a MINOR now, and that is the ordinary case and not a signal: the package moved on
version.py's table for new importable names; the schema moved by nothing, because no field and
no published schema changed, and no golden moved but the ten named below, by one citation string.
ADAPTER_API_VERSION moved 2.0.0 -> 2.1.0 on
VERSIONING.md §3's own row, for three additive members on the base class whose defaults are the
behaviour they replaced. synapse_cdm/version.py states the nine version axes and their
independence in one place, and tests/test_cdm_packaging.py sweeps the package for an assignment
that would derive one number from another. A package at 2.2.0 does not mean SC-OES 2.2:
SC_OES_VERSION is a third axis, still 0.1.0 and still a Draft.
What changed on the wire, and what a 2.1.2 consumer must do
Nothing on the wire. schema_version stays 2.1.0; git diff v2.1.2..HEAD -- schemas/ is
four files and five lines, every one a description string; and every golden file in the package
is byte-identical to the 2.1.2 release except ten under fixtures/klv/golden/ — the five VMTI
fixtures' .cdm.json and .parsed.cdm.json — in which the one string that quoted a private
round brief by path, the VMTI identity ruling every VMTI object carries in its attributes, now
says the ruling's source is private; no other byte of any golden moved (git diff v2.1.2..HEAD --numstat -- packages/cdm/synapse_cdm/fixtures/klv/golden is ten files, thirty-four lines each
way, every line that string). A 2.1.2 reader reads a 2.2.0 object unchanged, and
python -m synapse_cdm.schemas --check --out schemas reports CURRENT: schemas vs models at 2.1.0.
Three things a producer could do before and cannot now, each on input no shipped fixture, golden
or parsed twin ever carried:
- A JSON document or dict nesting more than sixty-four containers is refused by
adsb,ais,
legion,pntmapandtakwithadapter.InputTooDeep— aValueError— before any decoder
runs, and an XML document nesting more than sixty-four elements is refused bytakand
stanag4676the moment the tree is built. Before this releasetakandstanag4676raised
RecursionErroron a valid document about 7 KB deep, which is one of the four crash classes the
conformance suite refuses to count as a refusal, and the four JSON adapters translated any depth
the interpreter survived. Each of the six manifests now declares the bound asmax_depth, with
its basis; the other eight keep their declared reason for having none. pntmaprefuses a JSON value that is not an object — an array, a string, a number,null—
with oneValueError, where it surfaced anAttributeErrorfrom inside the decoder.- Every version field is held to one spelling.
schema_version,SourceRef.adapter_version,
a manifest'sadapter_versionandEvent.oes.spec_versionacceptMAJOR.MINOR.PATCHwith no
leading zero, no prefix, no suffix and no surrounding whitespace —version.SEMVER_REunder
fullmatch— where three of the four accepted01.0.0or a trailing newline. Every value this
tree has ever written passes; the published schemas carry no newpattern, because a pattern on
a published type is the schema table's "a type narrowed" and a MAJOR.
packages/cdm/synapse_cdm/MIGRATIONS.md's 2.2.0 section carries the records, unit by unit, and
the rulings the derivation rests on. There is no migration tooling, because there is nothing to
migrate.
Parser safety: a declared depth bound
The parser-safety policy — docs/docs/security/parser-safety.mdx, the page SECURITY.md names as
its home — gains its fifth reading beside the four of the 2.1.0 arc, and the JSON reading of
2026-09-17 in the prose beside them.
libexpat builds a tree of any depth without recursing, and everything the two XML adapters did
with the tree afterwards recursed once per level; on CPython 3.11 json.loads itself recurses
once per container and fails a little under a thousand deep. So the bound sits in front of the
decoder: the base class measures JSON text off its characters in one pass, decoded the way
json.loads would decode it, and a dict or list off its containers, and refuses past the declared
bound before any decoder runs; an XML tree stays the adapter's to measure, because only the
adapter holds it. Sixty-four is an implementation cap and every manifest says so — every CoT
fixture nests three elements deep, the deepest path AEDP-12's class model admits is eight
elements, no shipped JSON document nests more than seven containers, and sixty-four keeps every
walker under two hundred Python frames. Three malformed/ directories gain the payload that
exercises the change, so check H records the refusal on every run.
The harness computes L4
At 2.1.2 the harness's roundtrip column read SKIP for every adapter, because it compared JSON
structurally and every shipped emitter returns bytes, so every L4 in a manifest rested on a
per-adapter test the wheel does not carry — a typed rung, which §3.6's first sentence does not
admit. The column now compares egress octets under a tolerance each adapter declares and the
report prints: bytes, the default, means from_cdm(to_cdm(raw)) must equal
roundtrip_reference(raw) octet for octet; values means what was emitted is re-ingested and no
source value may be missing, with the adapter's declared transforms excused — the tolerance XML
needs, and the one tak and stanag4676 declare. The readings: 16, 11, 20, 17, 17, 41, 28, 16
and 63 byte fixtures octet-exact for adsb, ais, cat021, cat023, cat034, cat048,
cat062, gmti and stanag4609; 6 and 17 parsed twins value-complete for tak and
stanag4676; zero FAIL. Every adapter now computes maturity_eligible: L5 and every adapter
declares less — L3 for the three ingest-only adapters, L4 for the eleven emitters — and
tests/test_cdm_manifests.py derives the rung from a suite run and requires declared ≤ eligible.
A third word, an exemption under bytes, or a declaration on an ingest-only adapter is a
TypeError at import.
Evidence that reproduces elsewhere
A 2.1.2 evidence record carried the runner's absolute fixture directory and its source_commit
with a -dirty suffix, because the gate job wrote the conformance artefact into the checkout root
before the evidence step ran there, so synapse evidence verify from any other checkout reported
DIFFERS on three fields none of which says anything about the tree. A record now carries the
packaged directory as <packaged>/<directory>, records the interpreter and platform as
environment and never compares them, and publish.yml refuses a record whose source_commit is
not the bare HEAD. The proof is in the suite: fourteen records generated from one tree and
verified from a copy of the package at a different absolute path, every one REPRODUCED. Every
manifest declares evidence.available: true, because the 2.1.2 records are attached to the
v2.1.2 Release and retrievable by anybody; the field says the records exist and where, and
nothing about the wheel's contents, which still carry no record. Thirty-nine PROVENANCE.json
records — one in every fixture directory a tracked test reads — say where the fixture data came
from.
CI, and the release pipeline
ci.ymlruns the suite on 3.11, 3.12, 3.13 and 3.14 — every interpreter the classifiers
declare, where it ran one — and gains alintjob and awheeljob on every push. The lint
stage is pinned once,ruff==0.16.6in a[lint]extra, and selectsE9,F821: the previous set
was exactly one rule and had never caught the undefined name its comments promised. The docs job
runs the site's own gates rather than a bare build.publish.yml'sreleasejob reads the CodeQL and pip-audit verdicts off the gate rather than
restating them, the pip-audit verdict is read off the stream pip-audit writes it to, and
rc-build.ymltakes its SBOMs over the clean install aspublish.ymldoes.gates/witness_verify.pyre-derives the four non-PyPI assets and the Release'sSHA256SUMS
offline with--assets, and with--downloadre-hashes the bytes the index and the Release
serve and the wheel's attestation bundle; at 2.1.2 it checked those digests for shape. The
witnessjob carriesactions: readanddeployments: read, takes the approval instant from
thepypideployment's own status history rather than from a key the approvals endpoint never
sent, fetches the attestation store by the wheel's digest, and hands the verifier the assets
and a token.releases/witness/2.1.2.json, the first record in that directory, was committed
from the bytes the Release serves; the job that produced its predecessor failed at its own
verification step, and this release's tag push is the first execution of the repaired job.
Security, dependencies and the supply chain
SECURITY.md: the reporting path, and a parser-safety policy with declared limits on all
fourteen adapters and a declared depth bound on six.- Secret scanning on the platform,
.gitleaks.tomlin the tree, and a CI job over the full
history reachable from every push. pip-audit --stricttwice — over the environment and over the wheel's frozen closure — with
exactly one distribution excluded by name,synapse-cdmitself, and every third-party line
under--strict. Plus an npm audit at high over the documentation tree:image-size2.0.4 is
pinned throughdocs/package.json'soverrides, the two advisory exceptions that named that
fix as their removal trigger are deleted,security/exceptions/holds no exception file, and
npm audit --jsonreads high 0, critical 0.- CodeQL, and a gate that refuses a blocking alert, selecting the analysis by commit.
- An SBOM in both SPDX and CycloneDX, built by the release pipeline over the clean-install
environment, plus a second tool's cross-check. - Build provenance: Sigstore attestation over the built artefacts, verified in the same run that
produced them and again, by digest, by the witness verifier.
What else moved
- The public tree names no private document by path. Every citation of the round apparatus —
a fixture spec record, a shippedmalformed/README.md, a constant's source, a comment — either
names the symbol it means or says the document is private, andSOIFis expanded once in the
rootREADME.mdand on two pages of the documentation site. - One serialiser, written once.
canonical.pyholds §6.2's serialisation and the seven
sites that restated it call it; the fixture predicate isharness.select_fixturesand the two
modules that restated it call that; the shipped-adapter test isadapter.is_shipped. No
golden, manifest or evidence digest moves for it, because the bytes cannot differ. - The unused imports are gone — twenty-seven against the 2.1.2 tree, eight of them in the
distribution — andruff --select F401reads 0. - The package
README.mdlists every module the package has carried since SC-OES and SOIF
Part 1, says the exporter writes eight JSON Schemas, and describesgeo.pywith its multi-
forms; the package's own docstring names the fourteenth adapter it enumerated as thirteen. - No runtime dependency changed.
pydanticandjsonschema, as before;rdflibis a test
extra;ruffis a lint extra and nothing undersynapse_cdm/imports it. - No adapter was added or removed, and no adapter's translation was changed to make a
conformance verdict come out differently: check E moved from SKIP to PASS because the harness
learned to compare, not because an adapter learned to emit.
Fourteen adapters, all harness-verified
python -m synapse_cdm.harness --adapter <name> --json, run over the roster with no --fixtures.
The table is the live registry, and
tests/test_cdm_release.py::test_the_release_notes_roster_table_is_the_registry requires both
directions to agree — a table missing an adapter tells a reader the roster is smaller than it is.
The roster did not move this arc, which is derived here rather than carried over: discover()
and roster() each return fourteen, the same fourteen names in the same two directions as 2.1.2,
and the totals below were summed from the harness on this tree.
| Adapter | Direction | Fixture verdicts |
|---|---|---|
adsb |
bidirectional | 32 |
ais |
bidirectional | 22 |
cat021 |
bidirectional | 40 |
cat023 |
bidirectional | 34 |
cat034 |
bidirectional | 34 |
cat048 |
bidirectional | 82 |
cat062 |
bidirectional | 56 |
gmti |
bidirectional | 32 |
legion |
ingest | 6 |
pntmap |
ingest | 4 |
stanag4586 |
ingest | 24 |
stanag4609 |
bidirectional | 126 |
stanag4676 |
bidirectional | 34 |
tak |
bidirectional | 12 |
538 fixture verdicts, 0 failed across the fourteen adapters, against the published schemas —
the same 538 as 2.1.2, and the roundtrip column that read SKIP for all of them now reads PASS
for the eleven emitters and a declared SKIP for the three that emit nothing. gates/wheel_install.py
reports 1076 over the same roster, which is these 538 run in each of two schema modes, from a
wheel installed into a venv with nothing of this repository on its path.
Published by CI over OIDC, as 1.1.0 through 2.1.2 were
No API token. .github/workflows/publish.yml builds on the tagged tree, gates that build with
gates/wheel_install.py --mutation-check, runs twine check --strict, checks that the tag names
the tree's PACKAGE_VERSION, and uploads those same files through PyPI Trusted Publishing after a
required reviewer approves the pypi environment. PUBLICATION.md ledger entry 6 records the
configuration, and entry 19 records the 2.1.2 upload and the two tags before it that released
nothing.
Artefacts
An sdist and a wheel, built once by the workflow, gated as that build, and uploaded as those same
files. Their SHA-256 digests are recorded in PUBLICATION.md's ledger together with the
workflow run that produced them. The GitHub Release additionally carries both SBOMs, the evidence
set, the conformance report, the witness record and these notes — so the evidence a claim rests
on is retrievable with the release rather than only as a workflow artefact with a retention
window.
They are deliberately not committed here, for the reason this file has given since 1.1.0. A digest
is a property of one build rather than of the tree: two builds of one tree have identical payloads
but differ in their generated metadata, so a digest written here before the tag would not be the
digest of the file PyPI serves, and one written after the tag could never be inside the tree the tag
names. 1.3.0 measured that the hard way — a local build and the published wheel came out at the
same byte count and were different files — so the digests to compare a download against are the
workflow's, never a rebuild's. Everything else in this document is readable off that tree, which is
what condition 4 of the release procedure asks for.
pip install synapse-cdm==2.2.0
python -m synapse_cdm.harness --list-adaptersAdapter status
14 adapters ship in this distribution. declared is the rung the
adapter's manifest claims; eligible is the rung this release's conformance
sweep allows it. A declared rung above an eligible one is a defect, not a note.
| adapter | version | direction | declared | eligible | claim | result |
|---|---|---|---|---|---|---|
adsb |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
ais |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
cat021 |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
cat023 |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
cat034 |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
cat048 |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
cat062 |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
gmti |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
legion |
1.0.0 | ingest | L3 | L5 | VERIFIED | CONFORMANT |
pntmap |
1.0.0 | ingest | L3 | L5 | VERIFIED | CONFORMANT |
stanag4586 |
1.0.0 | ingest | L3 | L5 | VERIFIED | CONFORMANT |
stanag4609 |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
stanag4676 |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
tak |
1.0.0 | bidirectional | L4 | L5 | VERIFIED | CONFORMANT |
Security status
| control | state |
|---|---|
| GitHub secret scanning | active |
| Secret-scanning push protection | active |
| Private vulnerability reporting | active |
| Secret scanning in CI (gitleaks) | active |
| No secret in the repository's history | active |
| Input bounds on every adapter | active |
| Parser-safety policy | active |
| Signed commits | active |
| Dependabot alerts and the dependency graph | active |
| Dependabot security updates | active |
| Dependabot version updates | active |
| Dependency review on pull requests | active |
| Python dependency audit | active |
npm dependency audit over docs/ |
active |
Pinned floors on transitive npm dependencies of docs/ |
active |
| CodeQL, with a gate that fails the build | active |
| SBOM, two formats | active |
| Build attestation, no long-lived key | active |
| Documented, time-bounded security exceptions | active |
| Secret-scanning non-provider patterns | not enabled in this round |
| Secret-scanning validity checks | not enabled in this round |
| Cryptographic signing of CDM objects | not provided |
| Defence against a producer asserting false semantics | not provided |
- CodeQL, this release's run: 0 result(s), 0 blocking at or above 7.0; gate exit 0 over 4 analysis file(s) of 5c53e75
pip-audit, this release's run: No known vulnerabilities found; strict, over 43 third-party distribution(s), ignore-vuln flags in force: []- Security exceptions in force: none. No finding is excepted; every gate in the pipeline passed on its own terms.
Known limitations
Stated by the adapters themselves, in their manifests. This is not a survey of what
the package cannot do; it is what each adapter says it does not do.
adsb
- the edition this adapter is written against is NOT STATED by any document in this repository — no pin record exists for RTCA DO-260x or ICAO Annex 10, both of which
fixtures/adsb/spec/adsb_terms.jsonrecords as priced, soformat.versionis null rather than guessed - DF17 and DF18 only; every other downlink format is out of scope
- a single frame carries no unambiguous position — the 17-bit CPR fields need a second frame of the opposite parity or a reference position, and no frame carries a time at all
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
ais
- the edition this adapter is written against is NOT STATED by any document in this repository — the format has two publishers, ITU-R (M.1371) and NMEA (0183), and
fixtures/ais/spec/ais_terms.jsonrecords both without naming the edition this implementation targets, soformat.versionis null rather than guessed - message types 1, 2, 3, 4, 5, 18, 19 and 21 are in scope; every other type is named in FORMAT_COVERAGE.md with the reason it is out
- type 24 static data is out of scope, so a vessel's name and call sign arrive in no message this adapter reads
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
cat021
- ADS-B MOPS versions 0, 1 and 2 in full; version 3 (ED-102B / DO-260C) only partially, and mostly in the Reserved Expansion Field
- Edition 2.6 states on its own cover that it is not backwards compatible to Edition 2.1 or earlier, and this adapter does not read those editions
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
cat023
- the smallest specification pinned in this repository: nine data items, and nothing outside them is decoded
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
cat034
- the pinned edition is 1.29 and it is NOT the latest:
fixtures/cat034/spec/cat034_pin.jsonrecords Edition 1.30 as available, and this adapter is written against 1.29 - leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
cat048
- the Reserved Expansion field is carried verbatim and never interpreted: no edition of this category defines its contents
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
cat062
- the input is already the output of a fusion process, so the CDM objects this adapter emits carry judgements a tracker made and this adapter neither repeats nor re-decides
- items the specification marks implementation-dependent are parked verbatim and not interpreted
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
gmti
- the Controlled Extension FIELD DEFINITIONS are not implemented and cannot be: AEDP-4607's §L.4, which is where their field tables belong, reads "(TO BE PROVIDED)" in the promulgated Edition A Version 1
- the standard itself declines to specify error handling (§2.2), so a malformed packet's treatment is this adapter's decision and is documented at the site rather than cited
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
legion
- a VENDOR API rather than a ratified standard:
fixtures/legion/spec/openapi_pin.jsonrecords that it "can change between deploys, and its info.version demonstrably does not move when it does", sov3names the endpoint family and not a frozen edition - ingest only — this adapter does not emit Legion documents and never acquires one: no HTTP client, no token, no cursor, no base URL
- only the resources the pinned OpenAPI document names are in scope
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
pntmap
- no document in this repository DEFINES the PNTMAP alert payload — FORMAT_COVERAGE.md carries no PNTMAP section — so there is no edition to name and
format.versionis null; the licence class rests on the payload being first-party rather than on a licensing sentence about the format - ingest only: this adapter does not emit PNTMAP alerts
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
stanag4586
- Edition 4 is current (promulgated as AEP-84 Edition A) and is NOT implemented: FORMAT_COVERAGE.md records that
nso.nato.intanswers HTTP 403 on every route tried, that the mirror carrying this family lists exactly Editions 2 and 3, and that the commercial distributors holding Edition 4 serve it paywalled and DRM-wrapped. No sentence here claims an Edition 3 decoder reads an Edition 4 feed - ingest only: this adapter does not emit DLI
- whether the 5-octet millisecond timestamp steps at a leap second is not stated by the document, and every object carries
attributes.time_basissaying so - leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
stanag4609
- ST 0601.14 is the authoritative tag table; ST 0601.19 is pinned as CONTEXT ONLY and is never a source of tag semantics here
- a length-divergent item's original octets are carried verbatim so that egress is byte exact, which means this adapter reproduces a defect rather than correcting it
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces ONE —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5). The other four are still absent, each with its own reason incapabilities.limits.absent_because; a depth, object-count, decompression or wall-clock bound is not enforced here today
stanag4676
- Edition A (the STANAG 4676 Edition 1 generation) is read for the edition delta and is never a basis: Edition B §2.1.1.1 declares the two incompatible
- the binary encoding of AEDP-12.1 Annex F is not implemented
- leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces TWO —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5), andmax_depth, declared the same way and refused byparse_documentimmediately after the XML parse and before either reader recurses into the tree (2026-09-16). The other three are still absent, each with its own reason incapabilities.limits.absent_because; an object-count, decompression or wall-clock bound is not enforced here today - XML is parsed with the standard library's
xml.etree.ElementTreeand NOT withdefusedxml, which is not a dependency of this package (M's F5.5 ruling, round P5). An EXTERNAL entity is not resolved and an external DTD is not fetched — the parser never reads the external subset — but an INTERNAL entity IS expanded, and what stops an entity bomb is libexpat's own input-amplification limit rather than anything in this package. That protection belongs to the RUNTIME's expat build (2.4.0 and later, on by default), so a deployment on an older expat loses it without this package changing;max_input_bytesbounds the document either way. tests/test_cdm_parser_safety.py reads the linked version and takes every one of these readings rather than asserting them
tak
- the CoT schema edition this adapter is written against is NOT STATED by any document in this repository —
fixtures/tak/spec/tak_terms.jsonrecords the Base-Event Schema's own version as its publisher states it, which is a reading of that page and not a statement that this implementation targets it, soformat.versionis null rather than guessed - contact atoms only on ingest; CoT types outside
a-.-...are out of scope - leftovers are parked in
Entity.attributes/Event.payloadundersource_extras(lossless.residual()), not in the origin-identifying container ARCHITECTURE.md §5 gives to P3 — the Part 1 stance that section rules for the adapters already shipped - the evidence RECORD for this adapter is not IN the distribution:
evidence/is untracked and unpackaged, CI generates the set on every run and the release pipeline attaches it to the GitHub Release.evidence.availableis true because the records for 2.1.2 are attached to thev2.1.2Release and retrievable by a third party, and it says nothing about what the wheel contains - of §3.5's five resource limits this adapter enforces TWO —
max_input_bytes, declared incapabilities.limitswith its basis beside it and refused before decode by the base class (round P5), andmax_depth, declared the same way and refused by this module immediately after the XML parse and before anything recurses into the tree (2026-09-16). The other three are still absent, each with its own reason incapabilities.limits.absent_because; an object-count, decompression or wall-clock bound is not enforced here today - XML is parsed with the standard library's
xml.etree.ElementTreeand NOT withdefusedxml, which is not a dependency of this package (M's F5.5 ruling, round P5). An EXTERNAL entity is not resolved and an external DTD is not fetched — the parser never reads the external subset — but an INTERNAL entity IS expanded, and what stops an entity bomb is libexpat's own input-amplification limit rather than anything in this package. That protection belongs to the RUNTIME's expat build (2.4.0 and later, on by default), so a deployment on an older expat loses it without this package changing;max_input_bytesbounds the document either way. tests/test_cdm_parser_safety.py reads the linked version and takes every one of these readings rather than asserting them
Conformance summary
Checks A–O of the Synapse Conformance Suite over every
shipped adapter, from this release's own sweep.
| adapter | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | result |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
adsb |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
ais |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
cat021 |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
cat023 |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
cat034 |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
cat048 |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
cat062 |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
gmti |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
legion |
PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | SKIP | PASS | CONFORMANT |
pntmap |
PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | SKIP | PASS | CONFORMANT |
stanag4586 |
PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
stanag4609 |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
stanag4676 |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |
tak |
PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | SKIP | PASS | PASS | CONFORMANT |