Skip to content

Runtime evidence with autotel

document:docs/runtime-evidence

The contracts say what a service promises; a snapshot from its tests says what it sent.

View as Markdown

Autocatalog builds the catalog from source: what each service declares it sends. autotel records what the service sent while its tests ran. Autocatalog reads that record and puts the two side by side on the site, in the drift gate and in the MCP tools.

The service emits events with autotel’s track(). The _autotel metadata is optional; it names the producer, channel and consumers when the event name alone does not:

import { track } from 'autotel';
track('order.placed', {
...order,
_autotel: { producer: 'OrderService', channel: 'orders.events', consumers: ['BillingService'] },
});

The test suite attaches ArchitectureSnapshotSubscriber and writes the snapshot when it finishes:

import { flush, init } from 'autotel';
import { ArchitectureSnapshotSubscriber } from 'autotel-subscribers/architecture-snapshot';
const snapshot = new ArchitectureSnapshotSubscriber({ service: 'orders-service' });
init({ service: 'orders-service', subscribers: [snapshot] });
after(async () => {
await flush();
await snapshot.writeToFile('.autotel/snapshot.json');
});

.autotel/snapshot.json lists each event that fired: how often, its field paths and their types, who produced it on which channel, which consumers saw it, and a few sample trace IDs. Commit it, and the tools below read it from the repository.

SurfaceWhat it does with the snapshot
ac siteEvent pages show the count, first and last seen, sources per producer and channel, consumers declared vs observed, and trace IDs
Site mapsEdges that ran are solid and weighted by volume, declared ones never seen are dashed, undeclared ones are marked; Replay and Live modes
ac drift --snapshotFails CI on payloads the contract forbids, and on events, fields, producers or channels the code uses but the catalog does not declare
Flow pagesShow how the saga that carries out the flow ran: its runs, how each ended, and each step’s failures and compensations
ac from-snapshotWrites draft contracts from what ran, for a repository that has none yet
ac scan --snapshotJoins the evidence into the catalog the MCP server reads; get_investigation_bundle hands trace IDs to autotel-mcp

Event names ignore case and ., _, -, so track('order.placed') is the catalog’s OrderPlaced. Field types reach the catalog and sample values stay out of it. Drift reads them in memory to check enums and integers, and its report counts the values it rejects without printing them. A consumer the snapshot saw but the catalog never declared shows as observed, and a declared consumer it never saw shows as never observed. Treat both as leads: a test run covers what your tests exercise.

With --base-ref, ac drift also reads the snapshot the base committed, so it reports a gap the default branch already has without failing your pull request (only what the change introduced).

A dead-letter queue on its own is storage. To see why a message landed there, the consumer records each outcome on its span with autotel’s traceConsumer context: ctx.recordRetry(attempt, max) before a retry, ctx.recordDLQ(reason, queue, { reasonCategory, attemptCount }) when it gives up, and ctx.recordReplay() when a replayed message comes back. With the snapshot’s spanProcessor registered, each consumer it saw receiving an event carries what happened to those deliveries:

{
"consumer": "PaymentService",
"channel": "orders.events",
"count": 6,
"failed": 4,
"retried": 2,
"deadLettered": 1,
"replayed": 1,
"deadLetterReasons": [
{
"reason": "payment provider unavailable",
"category": "transient",
"queue": "orders-dlq",
"count": 1
}
]
}

ac scan --snapshot keeps the consumers with a failure, retry, dead letter or replay. The event page says what happened beside each consumer, 6 deliveries: 4 failed, 2 retried, 1 dead-lettered, 1 replayed., lists why deliveries were dead-lettered, and names the queue a consumer declares for them. get_investigation_bundle hands the same outcomes to an agent, with the trace IDs to follow.

A saga run with autotel’s traceWorkflow and traceStep records each run and each step on its spans, and the snapshot’s spanProcessor turns them into workflows:

"FulfilOrder": {
"name": "FulfilOrder",
"runs": 4,
"completed": 3,
"compensated": 1,
"steps": [
{ "name": "ReserveStock", "count": 4, "compensated": 1 },
{ "name": "ShipOrder", "count": 4, "failed": 1 }
],
"sampleTraceIds": ["00f067aa0ba902b7a3ce929d0e0e4736"]
}

The flow the saga carries out shows its runs, and each step’s runs beside the step of the same id or title. A step that failed to compensate is the one to look at first: the saga left something undone.

The site’s maps start static, with the snapshot’s evidence drawn on each edge. Replay sends markers across the edges that ran, more often where the snapshot saw more, and needs no server. Live moves an edge each time a server-sent events frame names its event: run ac site --live-url <url> (the company site). The site only listens. With reduced motion, an edge brightens in place.

Look up the trace IDs on an event page in your tracing backend.

Experiment 05 runs the whole loop: tests write the snapshot, ac drift gates on it, and the site and EventCatalog both show it.

Owned by MaintainersLast changed 30 Oct 2026SourceEdit this page