Events carry a schema identifier; additive optional fields are the default evolution; rename is a breaking change; consumers ignore unknown fields; compatibility is verified against a registry in CI.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Every published domain event includes a schema identifier and a schema version. Compatible evolution may add optional fields only when every listed supported consumer is known to tolerate unknown fields. Renaming, removing, or retyping a field that any supported consumer reads is a breaking change and requires a new schema id or major version with a dual-publish period. A schema registry (files in the repo are enough) is checked in CI against the actual supported-consumer set.
Scope
- Event payload compatibility, schema ids, additive versus breaking change, consumer ignore-unknown, and CI registry checks.
- Backend event-driven systems that consume outbox-published events.
- Alignment with HTTP API versioning where the same team owns both.
Outside this block
- How the outbox relay delivers bytes to the broker.
- RPC/OpenAPI compatibility for synchronous APIs except as a related discipline.
Contract
- Envelopes include
schema_idandschema_versionthat resolve in the registry. - Adding an optional field with a default of absent is compatible only when the supported-consumer matrix proves that consumers tolerate unknown fields.
- Renaming a field, changing its type, or making a previously optional consumed field required is breaking.
- Consumers deserialize with ignore-unknown (or equivalent) so additive producer fields do not crash old consumers.
- CI fails if a producer branch is incompatible with the union of consumer schemas still marked supported.
- Breaking changes publish a new
schema_idor majorschema_versionand may dual-write old and new during migration.
Implementation
- Store schemas in the repo; list supported consumers and the fields they read, then compute compatibility from that set.
- Prefer new fields over overloading existing ones with sentinel meanings.
Failure handling
- On an unknown
schema_id, a consumer may perform one bounded registry refresh to handle propagation lag. If the schema remains unknown, the event goes directly to a dead-letter path; blind processing retries cannot make an invalid schema known. - A producer that cannot attach a registry-known schema id must not publish.
Verification
- Add an optional field and run old consumer fixtures; they must still apply.
- Rename a consumed field without a major bump and expect CI failure.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.
References
- Envelope attributes may follow CloudEvents 1.0.2.
- Event interface documents may use AsyncAPI 3.0; compatibility still depends on the supported-consumer matrix rather than the document format alone.
- Registry tooling is a project decision; no specific vendor is required.