Skip to main content

The Synapse Conformance Suite

Fifteen checks, A through O, over one adapter. Six of them are the harness's — the same six cdm-harness has printed since 1.0.0 — and nine were added beside them. Every check returns PASS, FAIL or SKIP, and a SKIP is never a PASS: it means this was not assessed, and the report says which declaration made it inapplicable.

pip install synapse-cdm

synapse conformance run --adapter cat021
synapse conformance run --adapter cat021 --format json
synapse conformance run --adapter cat021 --require A,B,C,D,F,G,H,J,K,L
synapse conformance list

python -m synapse_cdm.suite conformance run … is the same entry point, for a caller who would rather not depend on a name being on PATH.

The fifteen​

checkwhat it assertswhen it is SKIP
Atranslateevery fixture decodes into CDM objectsnever
Bschemaevery object validates against the published JSON Schemanever
Cprovenanceevery object carries the source, adapter and identifiers §4.5 requiresnever
Dlosslesswith MAPPINGS declared, no declared source leaf is LOST under the path-bound preservation ledger (details.basis: ledger); without them, no scalar in the source payload is absent from the output and undeclared (details.basis: heuristic, which is not proof)a bytes fixture has no leaf structure to compare
Eroundtripsource → CDM → source reproduces the source under the tolerance the adapter declares: octet for octet (bytes, the default), or no value lost after re-ingest (values, for XML)the adapter is ingest-only
Fgoldenthe output is byte-identical to the committed golden fileno golden file exists yet
Gdeterministicthree decodes of one payload under one frozen clock hash equalnever
Hmalformed-inputevery payload under malformed/ is refused, safely, in time — each case parsed in a spawned worker process under a real deadline, and every outcome but PARSER_REJECTED (PARSER_ACCEPTED, PARSER_TIMEOUT, PARSER_CRASH, WORKER_INIT_FAILED, HARNESS_ERROR) FAILSthe adapter declares no malformed/ set
Iunknown-preservationan injected unknown field survives into the outputthe adapter declares unknown_fields: none
Jtemporaltimestamps are RFC 3339 UTC, are not the epoch, and a time taken from now() says sothe adapter emits no timestamp — a DECLARED inapplicability when the adapter carries a structured limitation with id no-source-time (its format states no instant; geojson is the first), an undeclared one otherwise
Kidentityidentifiers are equal across two decodes and are uuid5, not drawnthe adapter emits no identifier
Lversionevery object's schema_version is assessed against this package's by version.assess() — the verdict, direction and basis are reported, a malformed version is a finding and not a crash — and is inside the manifest's cdm.supportedthe adapter emits no object
Mstreaminga payload fed in two chunks produces the output the whole one does; the SKIP carries details.streaming, the four statuses (chunk framing, partial messages, reassembly, backpressure) that say what is not implemented and what is not applicableno adapter declares the capability today
Nparser robustnessa payload truncated at every offset raises rather than crashes — in the same spawned worker as H, with the same outcome codes; a deadline that fires is PARSER_TIMEOUT, never a durationthe adapter ships no byte fixture
Oresource limitsone byte over max_input_bytes is refused before decode; every shipped manifest declares the bound with its basis in declared_becauseno bound is declared (the manifest's absent_because says why)

G hashes the serialisation this framework fixed once: json.dumps(obj, sort_keys=True, indent=2), UTF-8, trailing newline, sha256 over those bytes. N truncates at every offset for a fixture under 4 KiB and at 4096 evenly spaced offsets above it; the cap is printed in the report.

What a SKIP costs, and what it does not​

A maturity level is a claim about evidence, so the suite computes eligibility from the results rather than reading a declaration:

  • L1 needs A; L2 needs A B; L3 needs A B C; L4 needs A B C E; L5 needs every check applicable to this adapter.
  • A required check that is PASS satisfies its requirement.
  • A required check that is SKIP from a declared inapplicability does not block the rung. An ingest-only adapter is not blocked by the round-trip check: there is no egress direction for information to be lost in.
  • A required check that is FAIL, or SKIP with no declared inapplicability, blocks that rung and every rung above it.
  • L6 is never computed from this repository's own evidence: every fixture here is synthetic.

The two are printed separately on purpose. synapse conformance list shows what each adapter declares; a run prints MATURITY ELIGIBLE, which is what this repository's evidence supports today. They are allowed to differ — a declared rung above the eligible one is a defect, a declared rung below it is a reading — and where they do, the manifest's maturity.basis says what the difference rests on. Every adapter here computes L5 and declares less: the three ingest-only adapters declare L3 because E is inapplicable to them rather than passed, and the eleven that emit declare L4 because L5 rests on M being inapplicable to every adapter in this repository. A rung passed vacuously is not a rung declared.

E is computed for the eleven emitters, not taken on trust. Until 2026-09-16 the suite could not read non-JSON egress, reported SKIP, and pointed at a round-trip test in tests/ that a wheel does not carry — so every emitter declared L4 where the suite computed L3. Now the harness makes the comparison itself under the tolerance the class declares in ROUNDTRIP_TOLERANCE: bytes (nine of the eleven, from_cdm(to_cdm(raw)) equals the fixture octet for octet, AIS's NMEA TAG block excepted by a printed roundtrip_reference) or values (TAK and STANAG 4676, whose XML cannot promise octet order, so what was emitted is re-ingested and no source value may be missing, with the two values NITS egress re-stamps declared in ROUNDTRIP_TRANSFORMS). Each fixture pair carries the verdict on one half — the byte fixture under bytes, the parsed twin under values — and the other half reports SKIP with a sentence saying so, exactly as D skips the byte fixture and relies on the twin.

Strict mode​

--require names the letters a caller depends on. If any of them is FAIL or SKIP the command exits non-zero — a check that could not be evaluated is not a check that passed. The verdicts in the report are never rewritten to match: a reader has to be able to tell an adapter that failed a check from one nobody could evaluate.

exitmeaning
0the run completed and no required check failed or was skipped
1a required check FAILed, or a required check came back SKIP
2the invocation was wrong — an unknown adapter, or no fixtures where one was named

Malformed fixtures​

Every adapter ships at least two payloads under <fixtures>/<adapter>/malformed/: a truncation and one format-specific case — an invalid declared length, a checksum that does not match, XML that is not well formed, JSON that is not JSON. They are a subdirectory, so checks A–F never see them: the harness selects immediate children that are files.

A payload must be refused, and refused safely: any exception except SystemExit, KeyboardInterrupt, MemoryError or RecursionError, inside the time bound, with no object returned. The exception class is recorded in the report. A declared error hierarchy — so that a caller can catch one class rather than Exception — is Part 2 of this specification and is a stated limitation until then.