Skip to content

Events from JSON Schema

document:docs/events/json-schema

Passive discovery over the schema files already in a repository.

View as Markdown

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.

Terminal window
ac scan . --out ./events
  • Files that look like JSON Schema are candidates: $schema, type with properties, $ref / allOf, and similar shapes
  • Noise is skipped: package.json, tsconfig*.json, lockfiles
  • Identity comes from $id, the filename, or an x-autocatalog or x-eventcatalog block or *.autocatalog.json sidecar
  • Fields that cannot be established are marked needsReview, with provenance inferred — the catalog says it guessed instead of pretending it knew
  • Draft 2020-12 is preferred; older drafts are accepted and left as they are
{
"$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.

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"
}
}
  • guarantee is at-least-once, at-most-once or exactly-once. With at-least-once, consumers must expect duplicates.
  • orderingKey is 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.
  • idempotencyKey is the field that identifies a repeat, so a consumer can drop an event it has already handled.
  • correlationKey is 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.
  • maxLateness is 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.

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:

Terminal window
ac scan . --out ./events --allow-code-execution

Candidates from every plugin merge by event id, and conflicts resolve by provenance and confidence: extracted beats inferred for the same id.

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.

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.

Owned by MaintainersLast changed 4 Nov 2026SourceEdit this page