Events from JSON Schema
document:docs/events/json-schemaPassive discovery over the schema files already in a repository.
If your contracts already exist as JSON Schema — hand-written, or emitted by another tool — point the scanner at the repository. Nothing else to install, and no repository code is executed.
ac scan . --out ./eventsDiscovery rules
Section titled “Discovery rules”- Files that look like JSON Schema are candidates:
$schema,typewithproperties,$ref/allOf, and similar shapes - Noise is skipped:
package.json,tsconfig*.json, lockfiles - Identity comes from
$id, the filename, or anx-autocatalogorx-eventcatalogblock or*.autocatalog.jsonsidecar - Fields that cannot be established are marked
needsReview, with provenanceinferred— the catalog says it guessed instead of pretending it knew - Draft 2020-12 is preferred; older drafts are accepted and left as they are
Embedding metadata
Section titled “Embedding metadata”{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "id": { "type": "string" } }, "x-autocatalog": { "id": "payment.completed", "version": "1.0.0", "domain": "payments", "classification": "integration", "producers": ["PaymentService"] }}classification says who an event is for, narrowest to widest: internal
(inside the producing service), domain (inside its domain), integration
(across domains) or public (outside the organisation). It is optional; a
wider event is a harder contract to change, and saying so lets a
policy require it.
Delivery
Section titled “Delivery”A schema says what a payload looks like, not how it arrives. delivery says
what consumers may rely on about that:
"x-autocatalog": { "id": "payment.completed", "delivery": { "guarantee": "at-least-once", "orderingKey": "paymentId", "idempotencyKey": "paymentId", "correlationKey": "meta.traceId", "maxLateness": "PT5M" }}guaranteeisat-least-once,at-most-onceorexactly-once. With at-least-once, consumers must expect duplicates.orderingKeyis the field events arrive in order by. Two events with the same value arrive in the order they were sent; nothing is promised across values.idempotencyKeyis the field that identifies a repeat, so a consumer can drop an event it has already handled.correlationKeyis the field that ties the event to the journey it is part of, so a trace can follow it. Without one, a field under a common name (correlationId,traceId,traceparent,causationId) is used.maxLatenessis how long after it happened an event can still arrive, as an ISO 8601 duration in days or less (PT30S,PT5M,P1D). A consumer that waits this long has seen everything for that moment.
Keys are payload paths, written as in consumerFields. ac validate fails a
key the schema does not have, and warns about one the schema leaves optional,
since an event without it has no order or cannot be deduplicated. The event
page shows the promise under its details, and drift treats it as part of the
contract: see
the severity model.
Keep it in a sidecar instead if you do not own the schema file:
payment.completed.autocatalog.json beside it.
Passive by default
Section titled “Passive by default”ac scan reads data files and does not execute repository code. Runtime
plugins, such as Zod, have to evaluate code to recover a schema, so
they only run when you say so:
ac scan . --out ./events --allow-code-executionCandidates from every plugin merge by event id, and conflicts resolve by
provenance and confidence: extracted beats inferred for the same id.
One catalog, or none
Section titled “One catalog, or none”A scan publishes transactionally. A failing plugin or an invalid event leaves the previous catalog exactly as it was, rather than half-replacing it. The mechanics — staging, locks, backups, recovery — are in production operation.
Writing your own plugin
Section titled “Writing your own plugin”The discovery interface is deliberately small:
import type { DiscoveryAdapter } from 'autocatalog-core';
export const protobufDiscoveryAdapter: DiscoveryAdapter = { name: 'protobuf', async discover({ root }) { return { candidates: await findProtobufEvents(root), errors: [] }; },};Advertise it from the plugin package and ac finds it in node_modules:
{ "name": "autocatalog-plugin-protobuf", "autocatalog": { "plugin": { "discovery": { "entry": "./dist/index.js", "export": "protobufDiscoveryAdapter" } } }}Each candidate carries its event and the source evidence together. Core owns conflict resolution, enrichment, validation, stale cleanup and publication; a plugin only translates its source format into the shared JSON Schema representation.