synapse-cdm 3.0.1
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 | 3.0.1 |
| CDM version | 3.0.0 |
| SC-OES version | 0.1.0 |
| Adapter API version | 3.0.0 |
| manifest schema version | 2.1.0 |
Major changes
The corrective of the tagged-never-published 3.0.0, and the first published release of the audit
remediation arc. v3.0.0 was tagged on 2026-09-20 and its own release run (35506445471) refused
it in the build job: the gate job had run the suite green on the same commit, and the build job's
second run of it — in an interpreter that job had first loaded with twine and cyclonedx-bom,
whose dependency closure made every spawned parser worker import a Lark grammar and seven format
libraries before its first byte of input — crossed a wall-clock budget in one isolation test and
stopped, recording a count and no name. Nothing reached PyPI. What moved between 3.0.0 and 3.0.1
is the release workflow (the tooling now lives in a venv of its own, and condition 4 names the
tests it fails on), MIGRATIONS.md and version.py; the distribution is otherwise byte-for-byte
the tree v3.0.0 named, so everything below describes this release. MIGRATIONS.md's 3.0.1
section is the record, and no test budget moved to get here.
The audit remediation release. Between the 2.2.0 release of 2026-09-17 and this one, the
independent audit of 2026-09-19 raised nine findings against this package, F01 to F09, and every
one of them is answered in the tree at this tag: version compatibility is directional and rests
on frozen evidence rather than on major-number arithmetic; the lossless check's empty result is
no longer treated as proof, and a path-bound preservation ledger replaces it wherever an adapter
declares its field mappings; the conformance suite's parser deadline is a real, process-isolated
one; the published JSON Schema now states every constraint the Python models enforce; the STANAG
4676 adapter's wire binding is declared provisional in its manifest, its CLI row and its page;
resource limits are enforced where the platform can enforce them and refused where it cannot,
never claimed; evidence records carry five categories with the three external ones honestly
ABSENT; governance is an auditable derivation with a runbook; and the documentation is derived
from the tree and drift-checked. The register of all nine, with reproductions, tests, evidence
paths and what stays external, is docs/audit-remediation-report.md. Two things are removed or
narrowed, which is why this is a MAJOR on both of the first two axes — read the next two sections.
If you are upgrading from 2.2.0 — which is what the index serves — read this as the MAJOR it
is on both numbers. If you are upgrading from 2.1.2 or earlier, read the 2.2.0 notes first: they
are the body of the v2.2.0 Release, and everything they describe is still here.
Why the number is 3.0.1
The number is the derived floor twice over: a PATCH from v3.0.0 for the corrective — an arc of
the release workflow, MIGRATIONS.md and version.py, which gates/bump_derivation.py derives
as PATCH with nothing unruled — on top of the MAJOR the 3.0.0 release commit typed over v2.2.0,
the first MAJOR since 2.0.0. For that arc, gates/bump_derivation.py reads from v2.2.0 and
derives MAJOR from one shape signal —
lossless.unrepresented, an importable name removed with no alias, because the old name was a
claim of proof the function could not make — and from the units whose meaning changed and were
ruled MAJOR in MIGRATIONS.md's 3.0.0 section: version.compatible and version.parse (F01),
harness.run and evidence.badges (F02), suite.check_malformed and
suite.check_parser_robustness (F03), conformance.assess_a, conformance._validator and
models.Timestamp (F04), manifest.AdapterMetadata, Stanag4676Adapter and parse_document
(F05), harness.load_raw (F06) and evidence.EvidenceRecord (F07). The units the table cannot
classify on its own are every one ruled in that section, and the gate reads those rulings and
reports nothing unruled.
Package version 3.0.1 · CDM schema_version 3.0.0 · Adapter API 3.0.0 · manifest schema 2.1.0
· evidence schema 2.0.0. The first two were level at the 3.0.0 release commit and are one PATCH
apart at this one, and the equality was a coincidence of two separately argued majors and not a
derivation: the package moved on version.py's table for a
removed name and ruled meaning changes; the schema moved on MIGRATIONS.md's table because
finding F04 NARROWED the published contract (next section). ADAPTER_API_VERSION moved
2.1.0 -> 3.0.0 on VERSIONING.md §3's own row because AdapterMetadata.binding is required with
no default; the manifest schema moved 1.2.0 -> 2.0.0 for that required field and 2.0.0 -> 2.1.0 for
one added enum member; the evidence schema moved 1.0.0 -> 2.0.0 for three required fields. SC-OES,
the Operational Ontology and the profile versions did not move: SC_OES_VERSION is still 0.1.0
and still a Draft. synapse_cdm/version.py states the nine version axes and their independence,
and tests/test_cdm_packaging.py sweeps the package for an assignment that would derive one number
from another.
What changed on the wire, and what a 2.2.0 consumer must do
The published schema is narrower, and that is the MAJOR. The four object schemas under
schemas/ carry pattern on every schema_version and adapter_version, pattern on
Entity.symbol and uniqueItems on Entity.ontology_types. Every one of those constraints
restates what the Python models enforced already, so no document this package ever emitted becomes
invalid — but a consumer validating with the published schema alone accepted
"adapter_version": "banana" under 2.1.0 and is refused it under 3.0.0, and the bump table in
MIGRATIONS.md puts a narrowed type on the MAJOR row for exactly that reader. No path was removed, no
required list grew, no enum member went. The 2.1.0 contract stays frozen under
tests/frozen/cdm/2.1.0/ beside the 3.0.0 one under tests/frozen/cdm/3.0.0/, and
tests/test_cdm_version_matrix.py shows the narrowing on the frozen bytes.
Every golden moved by one stamp. The 538 golden files under the package's fixtures/*/golden/
and the eight reference-position goldens under fixtures/adsb/local/ carry
schema_version: "3.0.0" and differ from 2.2.0's by that line alone; the harness reads the same
538 fixture verdicts, 0 failed, on every adapter. The fourteen examples under examples/ declare
3.0.0 too (they declared 2.0.0 through two minors): synapse conformance reads a document's
schema_version against this package's, and a document of another major is refused in its
structural dimension rather than read on trust.
What to change in a 2.2.0 consumer, in one place:
- Rename
lossless.unrepresentedtolossless.value_presence_heuristic, and read its{}as
"nothing seen", not as proof. - Expect
version.compatible(written_with, read_by)to answerFalsefor any pair a major apart
(("2.1.0", "3.0.0")in either direction),Falsefor a writer newer than the reader within a
major,Falsefor a minor nobody has frozen, and to raiseValueErroron a malformed string
(a trailing newline, a leading zero, a prefix, a sign). Useversion.assess()for the verdict,
the direction and the evidence it rests on.KNOWN_CONTRACTSreads("2.0.0", "2.1.0", "3.0.0"). - Declare
binding=on every adapter's metadata:"standard-encoding"where the wire form is the
cited document's own encoding,"provisional-internal-profile"with a limitation containing
"provisional" where the element names or namespace were chosen locally. A provisional binding
claimsPROVISIONAL(new) and notVERIFIED. - Expect the harness's
losslesscolumn to FAIL on a ledger loss for an adapter that declares
MAPPINGS, and thelossless-verifiedbadge to readheuristicfor one that does not. - Expect the suite's H and N checks to carry outcome codes in
details, and N's
over_time_boundstrings to name a code rather than a duration; expect--startup-timeout,
--diagnostics,--memory-limit-bytesand--cpu-limit-secondson the suite's CLI, the last
two refused with an explicit reason on a platform that cannot enforce them. - Expect the conformance tool to refuse a string boolean, a trailing newline in a version, a
malformed UUID and a timestamp outside the wire form on the JSON path. - Expect
--list-adaptersto print abindingcolumn; expect an evidence record to carry
snapshot,evidence_categoriesandmaturity_support, and a manifest to carrybinding. - Nothing is migrated in place: manifests, schemas, evidence and goldens are regenerated from
their sources.
Two maintainer rulings, applied in this release
Preservation maturity is declared to what the evidence proves. Eleven manifests declared L4
ROUNDTRIP VERIFIED on a basis sentence that cited the lossless check as evidence — and since
F02 that check, for an adapter with no declared field mappings, rests on the value-presence
heuristic: a source value present somewhere in the output is not a value that reached its path.
"Applicable information survives source -> CDM -> source" is precisely the claim the heuristic
cannot prove, so adsb, ais, cat021, cat023, cat034, cat048, cat062, gmti,
stanag4609, stanag4676 and tak declare L3 from this release, each basis sentence says why,
and tests/test_cdm_manifests.py holds a bidirectional adapter to L3 wherever the report's
preservation.basis reads heuristic. Their roundtrip columns still read PASS and the suite's
maturity_eligible still computes L5 from the verdicts; what moved is the declaration, and an
adapter regains L4 when its field mappings are declared and the ledger reports no loss. pntmap,
the one adapter with a ledger, is ingest-only and stays at L3 for the reason it always gave.
A provisional binding claims PROVISIONAL. manifests/stanag4676.json declared
claim_status: VERIFIED beside binding: provisional-internal-profile. A gate that passes against
element names chosen in this repository verifies nothing about the standard, so ClaimStatus gains
a seventh member, PROVISIONAL (the manifest schema's 2.1.0), the adapter claims it, and
manifest.AdapterMetadata refuses the two fields apart in both directions. VERIFIED returns
with standard-encoding or normative-verified — the road to which is the register's §5 item 1.
The nine findings, and what each one left open
- F01 — version compatibility is directional and evidence-based:
assess()returns a verdict,
a direction and the frozen evidence; the 2.0.0 and 2.1.0 contracts are frozen from their tags
and 3.0.0 from this commit. Nothing external. - F02 — preservation rests on a path-bound ledger where
MAPPINGSare declared (one shipped
adapter today) and on the heuristic, said so, everywhere else. Declaring mappings for the other
thirteen is queued work, one adapter per session. - F03 — parser deadlines are process-isolated (
multiprocessingspawn overos.pipe()), with
a six-code outcome vocabulary and one refusal among them. - F04 — schema and model alignment: the published schema states what the models enforce, a
semantic-rules document and a 44-case corpus cover what a schema cannot say, and the wire path
refuses a string boolean, a trailing newline and a malformed UUID. - F05 — STANAG 4676:
bindingis a required field on every manifest, the 4676 adapter reads
provisional-internal-profile, an explicit normative mode exists and refuses with a five-step
procedure until an authorised XSD pair and a validator are supplied. The normative verification
itself is external and has not happened. - F06 — resource limits: input bounds on depth and bytes, memory and CPU limits enforced on
Linux and refused with a reason elsewhere; the Linux enforcement branch is first observed by
the remote CI of the pushed branch. - F07 — evidence: five categories per record, the three external ones ABSENT on every adapter,
a runner and a verifier for exercise reports. No partner, exercise or date is invented. - F08 — governance: one workflow defect closed, workflow properties tested, an audit gate that
says UNVERIFIED rather than guessing, two staged ruleset proposals and a runbook. Applying a
ruleset needs an administrator and has not been done. - F09 — documentation: a current-contracts page generated from the constants, a support matrix
generated from the declarations, symbol citations instead of line numbers, and a widened lint
gate with an explicit, shrink-only baseline.
What else moved
- No runtime dependency changed.
pydanticandjsonschema, as before;jsonschemawas
already a runtime dependency before F04 used it on the Python path. - No adapter was added or removed, and no adapter's translation changed: every golden moved by
itsschema_versionstamp and by nothing else. - The release rehearsal gate (
gates/release_ref_rehearsal.py) replays every ref-dependent
release step against the local tag before it is pushed, as it has since 2.1.2; it is refused
without a tag, which is the reading the release commit records before the tag exists. - The release workflow moved, and it is the reason this is 3.0.1 and not 3.0.0. The build
job'stwineandcyclonedx-pylive in a venv of their own, so the interpreter that runs
condition 4's suite receives the documented install and nothing else, and condition 4 writes the
suite's output to a file and prints the failing tests before it stops. Nothing about the ref was
involved, which is why the rehearsal gate passedv3.0.0and was right to.
Fourteen adapters, all harness-verified
python -m synapse_cdm.harness --adapter <name> --update-golden, run over the roster with no
--fixtures at this commit, and every verdict read from the run. 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. The roster did not move this arc: discover() and roster() each return
fourteen, the same fourteen names in the same two directions as 2.2.0.
| Adapter | Direction | Fixture verdicts | Declared maturity | Claim |
|---|---|---|---|---|
adsb |
bidirectional | 32 | L3 | VERIFIED |
ais |
bidirectional | 22 | L3 | VERIFIED |
cat021 |
bidirectional | 40 | L3 | VERIFIED |
cat023 |
bidirectional | 34 | L3 | VERIFIED |
cat034 |
bidirectional | 34 | L3 | VERIFIED |
cat048 |
bidirectional | 82 | L3 | VERIFIED |
cat062 |
bidirectional | 56 | L3 | VERIFIED |
gmti |
bidirectional | 32 | L3 | VERIFIED |
legion |
ingest | 6 | L3 | VERIFIED |
pntmap |
ingest | 4 | L3 | VERIFIED |
stanag4586 |
ingest | 24 | L3 | VERIFIED |
stanag4609 |
bidirectional | 126 | L3 | VERIFIED |
stanag4676 |
bidirectional | 34 | L3 | PROVISIONAL |
tak |
bidirectional | 12 | L3 | VERIFIED |
538 fixture verdicts, 0 failed across the fourteen adapters, against the published 3.0.0
schemas — the same 538 as 2.2.0. The roundtrip column reads PASS for the eleven emitters and a
declared SKIP for the three that emit nothing; the lossless column reads PASS on every one, on
the ledger for pntmap and on the heuristic, said so, for the other thirteen.
Published by CI over OIDC, as 1.1.0 through 2.2.0 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 20 records the 2.2.0 upload.
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==3.0.1
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 | L3 | L5 | VERIFIED | CONFORMANT |
ais |
1.0.0 | bidirectional | L3 | L5 | VERIFIED | CONFORMANT |
cat021 |
1.0.0 | bidirectional | L3 | L5 | VERIFIED | CONFORMANT |
cat023 |
1.0.0 | bidirectional | L3 | L5 | VERIFIED | CONFORMANT |
cat034 |
1.0.0 | bidirectional | L3 | L5 | VERIFIED | CONFORMANT |
cat048 |
1.0.0 | bidirectional | L3 | L5 | VERIFIED | CONFORMANT |
cat062 |
1.0.0 | bidirectional | L3 | L5 | VERIFIED | CONFORMANT |
gmti |
1.0.0 | bidirectional | L3 | 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 | L3 | L5 | VERIFIED | CONFORMANT |
stanag4676 |
1.0.0 | bidirectional | L3 | L5 | PROVISIONAL | CONFORMANT |
tak |
1.0.0 | bidirectional | L3 | 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 |
| Resource isolation of the conformance worker | active |
| Streaming input | not provided |
Verification of the integrity block |
not provided |
| 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 2 analysis file(s) of 89d2c70
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 expat (
pyexpatinto anxml.etree.ElementTreetree,parse_document's_parse_xml) and NOT withdefusedxml, which is not a dependency of this package (M's F5.5 ruling, round P5). Since F05 (2026-09-20) an EXTERNAL entity reference FAILS the parse at the reference and parameter-entity parsing is off, so an external DTD subset is never requested — both set on the parser rather than read off its defaults — 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 - the XML element names this adapter reads and writes are a PROVISIONAL internal profile (
binding: provisional-internal-profile): AEDP-12's UML attribute names bound through one table (ELEMENT_NAMES) in no namespace, because the normative XSD is distributed through NATO national representatives (Ed B §B.5) and is not held here. Reader and writer agreeing through that table proves the profile is self-consistent, not that it is the standard's binding; FORMAT_COVERAGE.md marks every NITS row· provisionalfor the same reason. The verified normative binding is a separate, explicit mode (SYNAPSE_CDM_NITS_BINDING=normative) that validates every document read or emitted against the authorised schema through the local-resource hookSYNAPSE_CDM_NITS_XSD_DIRand FAILS BLOCKED_EXTERNAL_EVIDENCE without it — it never falls back to the profile, and no shipped fixture, golden or evidence record was produced under it
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 |