Skip to content

API and wire stability

SYNAPSE CHANNEL is in its pre-1.0 (0.x) line. This page states what counts as a stable surface, how each surface is guarded against accidental change, and what 1.0.0 will lock. It is the contract an integrator can rely on when pinning a version — in particular an out-of-tree consumer that builds on the core, such as the commercial fleet tier, which speaks the wire across hubs and imports the federation primitives directly.

See the 0.x to 1.0 migration guide for the operator upgrade runbook and release-cut checklist.

Versioning

Current 0.x releases do not promise backward compatibility across minor releases. A minor release may change a public surface, but only as a reviewed edit to the pin that guards it (below), with a changelog entry and migration notes — never silently. Operators and out-of-tree integrators that need a fixed target must pin the exact package version.

1.0.0 locks the stable public Python API and the wire contract. After it, a breaking stable public Python API change requires a package major release, while a wire-incompatible change bumps the independently versioned wire protocol.

The wire-protocol version is decoupled from the package version. synapse_channel.core.protocol.WIRE_PROTOCOL_VERSION (an integer, currently 2) changes only on a wire-incompatible change, so it is a stable compatibility signal rather than a release counter. The hub advertises it in the welcome handshake as protocol_version and in /health; a client reads the peer's version on connect as hub_protocol_version. Version mismatches are accepted with warning and negotiate down to the lowest common wire version. A peer that omits the field is treated as legacy wire version 1; a peer that advertises a future version is served at the local version until this build learns the newer capabilities.

The stable surfaces and how they are guarded

Each stable surface is pinned by a test, so a rename, removal, or value change fails CI rather than reaching a release:

Surface What is frozen Guard
Public Python API the exact package __all__ export list tests/test_public_api.py
Wire message vocabulary every MessageType name→value pair, the envelope's reserved keys, and the wire-protocol version tests/test_wire_surface_freeze.py
Federation primitives the deep core.* symbols out-of-tree consumers import — the persisted event shape, the multihub follower and its fetcher, claim forwarding, the federation store and its peer records, the TLS pin helper tests/test_federation_consumer_contract.py
CLI surface every subcommand is classified into a stability tier tests/test_surface_taxonomy.py
First-use and usage profiles exact concepts, journey, dependency extras, activation/deactivation boundaries, and full-surface preservation tests/test_surface_taxonomy.py, tests/test_cli_e2e_journey.py
Capability counts class, module, wire-type, subcommand, and test counts the capability manifest (tools/capability_manifest.py --check)
Error taxonomy codes every domain exception's class→code pair tests/test_core_errors.py

The manifest pins counts, which catches an addition or removal; the freeze tests pin identities and values, which catches a rename that keeps a count constant. The two together close the gap either leaves open alone.

Error taxonomy

Every domain exception derives from synapse_channel.core.errors.SynapseError and carries a stable machine-readable code (snake_case), so a boundary layer — the CLI, the A2A bridge, the MCP server, an embedding application — can classify a failure without matching on message text (synapse_channel.core.errors.error_code(exc) returns the code, or "" for a foreign exception). Each class keeps its historical built-in base (ValueError, RuntimeError, PermissionError) through multiple inheritance, so pre-existing except clauses keep catching exactly what they caught before the taxonomy landed. A released code never changes meaning and is never reused; the registry test freezes the full map, and an AST drift gate refuses any new *Error class that does not join the taxonomy.

The taxonomy code belongs to the class: error_code(exc) reads type(exc).code. Setup exceptions retain their existing instance exc.code for the concrete refusal reason used in CLI documents and receipts. This instance reason does not change the class-level classification. Constructors, exception messages and setup refusal codes remain compatible.

Boundary projections are explicit and frozen by focused tests. A2A validation, not-found, conflict, quota, and store codes map to HTTP 400, 404, 409, 429, and 500 respectively. Outbound MCP config, access, and tool codes map to CLI exits 2, 3, and 1. Foreign exceptions take the boundary caller's explicit default; neither projection parses exception text or infers semantics from wording. Caller-actionable 4xx responses retain their detail, while mapped 5xx responses omit exception text so internal paths and storage diagnostics stay server-side.

Stability tiers

Every CLI subcommand carries a tier (synapse_channel.surface_taxonomy):

  • stable — daily-safe coordination core with a stable wire and CLI surface.
  • analysis — read-only inspection and reporting with no coordination side effects.
  • governance — advisory governance: policy, approvals, access control, release integrity.
  • adapter — bridges to other ecosystems and tools; optional extras, not core.
  • experimental — newer or advisory surfaces still settling; shape may change before 1.0.

An experimental surface is explicitly outside the stability guarantee until it graduates to another tier.

The additive usage-profile contract is exposed by synapse commands --profile NAME --json. It measures discovery and package boundaries; it does not remove a command, grant authority, or silently start an optional process. During 0.x, a profile change follows the same reviewed changelog and migration discipline as the classified CLI surface.

Deprecation

Within 0.x, a surface removed or changed is a reviewed edit to its guard plus a CHANGELOG entry; a wire change also bumps WIRE_PROTOCOL_VERSION. After 1.0.0, a stable surface is removed only across a major version, with the prior behaviour kept for a deprecation window where feasible.