autotel-eventcatalog 2.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,74 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fb6cba7: ### autotel-pact
8
+
9
+ First public release. Runtime evidence for Pact contracts: a green Pact suite proves compatibility; this package proves relevance by recording which contracted interactions were actually exercised, by whom, and how recently. Four independent evidence sources feed a single audit matrix.
10
+
11
+ **Consumer side**
12
+ - `withPactInteraction(pact, handler, opts)` wraps `MessageConsumerPact.verify()`. It opens an autotel span with `pact.*` attributes, runs the underlying verification, and appends a `source: test, role: consumer` ledger entry.
13
+ - `withHttpPactInteraction(pact, interaction, testFn)` mirrors the same shape on `PactV3.executeTest()`.
14
+ - `autotel-pact/auto-wrap` is a vitest/jest setup entry that monkey-patches `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`. Adopting is a single config-file line. The patch is idempotent and a no-op when Pact-JS is not installed.
15
+ - Pass `interactionId: 'order.created.v1'` to keep audit rows stable across `expectsToReceive` renames. The audit matches on `interactionId` first and description as fallback.
16
+
17
+ **Provider side**
18
+ - `withProviderVerification(verifierOpts, wrapOpts)` runs the Pact Verifier inside an autotel span. On success it fans out one ledger row per interaction in the pact file with `role: 'provider'`. On failure it emits a single `provider_verification_run` record, because the failure cannot be attributed to one interaction.
19
+ - `skipVerifier: true` skips loading and calling the Verifier and emits the success-path rows from the pact file alone. For demos, smoke tests, and audit-pipeline exercises; not for production CI.
20
+
21
+ **Production observation**
22
+ - `PactLedgerSpanProcessor` plus `tagPactInteraction({...})` lets you record `source: production` evidence from any span whose handler is part of a contracted interaction. Outcome is derived from `span.status.code` (OTel `ERROR` becomes `failed`).
23
+ - Bounded queue with drop-oldest-on-full eviction so newest evidence survives sustained pressure. Throttled warning on each drop wave. Fail-open writes: a ledger I/O error reports via `onWriteError` and never breaks the app.
24
+ - `appendLedgerEntryAsync` applies producer-side backpressure. Callers await drainage past 4096 pending writes so memory cannot grow unbounded.
25
+
26
+ **Pact Broker enrichment**
27
+ - The audit CLI reads the latest Pact Broker verification per consumer/provider pair when `--broker-url` (or `PACT_BROKER_BASE_URL`) is set. Supports bearer token and basic auth.
28
+ - The audit row distinguishes "broker said no" from "broker unreachable or errored" via the `broker_error` field. `--gate=broker` exits non-zero on either condition, so a transient broker outage cannot silently pass CI.
29
+ - Broker verification is at pact-pair granularity, not per-interaction. The package documents this limitation in every surface that shows the column.
30
+
31
+ **Audit CLI**
32
+ - `autotel-pact audit` prints a nine-column table: STATUS, CONTRACTED, TEST_SEEN, PROD_SEEN, PROVIDER_VERIFIED, BROKER_VERIFIED, CONSUMER → PROVIDER, KIND, INTERACTION. Status is OK (contracted and seen in test), STALE (contracted, not seen in test), or SHADOW (seen anywhere with no contract).
33
+ - Gates: `--gate` (default) fails on STALE rows; `--gate=strict` also fails on SHADOW rows; `--gate=broker` fails when any contracted row lacks broker proof or the broker was unreachable.
34
+ - `--json` emits the v0.2.0 audit matrix; counts use names that match the v0.2 semantics: `contracted_and_test_seen`, `contracted_not_test_seen`, `test_or_prod_seen_not_contracted`.
35
+
36
+ **Schemas**
37
+ - `autotel-pact-ledger-entry/v0.2.0` and `autotel-pact-audit-matrix/v0.2.0`, published as JSON Schema under `schemas/`. Every persisted artifact carries a `spec` envelope so downstream tooling can refuse unknown majors.
38
+
39
+ **Coverage**
40
+ - 91 unit tests across 13 files. End-to-end demo in `apps/example-contract-testing` exercises every evidence path (TEST_SEEN, STALE, SHADOW, PROVIDER_VERIFIED, PROD_SEEN) and asserts the resulting matrix programmatically.
41
+
42
+ ### autotel-eventcatalog
43
+
44
+ `stamp` and `generate` commands now accept `--format json` for machine-readable stdout output, matching what `drift` already does. Default remains `text`.
45
+
46
+ Other improvements:
47
+ - `loadSnapshot` produces actionable error messages (`Snapshot not found`, `Snapshot is empty`, `Snapshot is not valid JSON`, …) instead of raw `ENOENT` / `SyntaxError`.
48
+ - The undocumented `--version` flag for `generate` is now shown in `--help`.
49
+ - The `schemas/README.md` documents why `v0.1.0` schemas are preserved alongside `v0.2.0`.
50
+ - Removed an unsafe inline type cast in `diff.ts` (`fieldStats` is already correctly typed on `EventObservation`).
51
+
52
+ ### autotel-subscribers
53
+
54
+ `ArchitectureSnapshotSubscriber.toSnapshot()` and `.writeToFile()` now accept an optional `freezeTimestamps: string` option. When supplied, every timestamp in the output (`generatedAt`, plus each event's `firstSeen` / `lastSeen`) is replaced with that value — useful when writing a snapshot intended to be committed to a repository as a stable artifact.
55
+
56
+ Production code that captures real observation timestamps should not pass this option; it exists for snapshot-as-documentation workflows where byte-stability matters more than wall-clock accuracy.
57
+
58
+ The legacy `toSnapshot(now)` form (passing a bare clock function) continues to work for backward compatibility.
59
+
60
+ ### Patch Changes
61
+
62
+ - Updated dependencies [fb6cba7]
63
+ - autotel-subscribers@34.1.0
64
+
65
+ ## 3.0.0
66
+
67
+ ### Patch Changes
68
+
69
+ - Updated dependencies [30a485b]
70
+ - autotel-subscribers@34.0.0
71
+
3
72
  ## 2.0.0
4
73
 
5
74
  ### Minor Changes
package/CONTRIBUTING.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Contributing to autotel-eventcatalog
2
2
 
3
- A short, opinionated guide. The package is small enough that you can read all
4
- of it in an afternoon; this doc is here so you don't have to.
3
+ A short, opinionated guide. The package is small enough to read in an
4
+ afternoon; this doc covers it so you don't have to.
5
5
 
6
6
  ## Where things live
7
7
 
@@ -27,9 +27,10 @@ packages/autotel-eventcatalog/
27
27
  │ └── cli.e2e.test.ts e2e tests (spawn the built CLI)
28
28
  │ └── contract.test.ts versioned-JSON contract tests
29
29
  ├── schemas/
30
- │ ├── drift-report-v0.1.0.json
31
- │ ├── drift-summary-v0.1.0.json
32
- └── stamp-summary-v0.1.0.json
30
+ │ ├── drift-report-v0.2.0.json
31
+ │ ├── drift-summary-v0.2.0.json
32
+ ├── stamp-summary-v0.1.0.json
33
+ │ └── generate-summary-v0.1.0.json
33
34
  ├── action.yml composite GitHub Action (used by external repos)
34
35
  ├── docs/ this folder
35
36
  └── README.md
@@ -59,13 +60,12 @@ cli.ts ─► everything (the only place all branches meet)
59
60
  3. `cli.ts` is the dispatcher; nothing else imports from `cli.ts`.
60
61
  4. `stamp.ts` is independent of `diff.ts`. They share inputs but no logic.
61
62
 
62
- If a future change wants to break one of these rules, that's the signal to
63
- stop and ask whether the new feature belongs in this package.
63
+ If a future change would break one of these rules, stop and ask whether the
64
+ new feature belongs in this package.
64
65
 
65
66
  ## Load-bearing invariants
66
67
 
67
- These are the rules I'd commit to keeping. Future-me, future-contributors:
68
- when in doubt, push back.
68
+ These are the rules I'd commit to keeping. When in doubt, push back.
69
69
 
70
70
  ### 1. No new top-level commands without a user
71
71
 
@@ -94,10 +94,9 @@ dashboard, a file watcher, an LSP server, an MCP server, a webhook
94
94
  listener) lives in [`autotel-subscribers`](../autotel-subscribers/) or in
95
95
  example apps.
96
96
 
97
- The reason: long-running processes have a different lifecycle from
98
- command-line tools. Process management, signal handling, restart
99
- semantics, observability. Mixing the two doubles the operational surface
100
- for no clarity gain.
97
+ Long-running processes have a different lifecycle from command-line
98
+ tools: process management, signal handling, restart semantics, observability.
99
+ Mixing the two doubles the operational surface for no clarity gain.
101
100
 
102
101
  ### 3. No domain-specific extensions to the core
103
102
 
@@ -106,11 +105,10 @@ events, services, channels, field paths. Adding a SARIF renderer should
106
105
  never require a `severity` field on `DriftCounts`. Adding a Slack
107
106
  renderer should never require a `messageColor` field on `DriftReport`.
108
107
 
109
- If a new renderer wants information that doesn't fit the existing types,
108
+ If a new renderer needs information that doesn't fit the existing types,
110
109
  the renderer should derive it locally, not push back into the core. If
111
- multiple renderers want the same derived information, _then_ it earns a
112
- place in `diff.ts`. Pause and confirm it's about drift, not about
113
- presentation.
110
+ multiple renderers need the same derived information, _then_ it earns a
111
+ place in `diff.ts`. Confirm it's about drift, not about presentation.
114
112
 
115
113
  ## How to do common things
116
114
 
@@ -166,8 +164,8 @@ produced, also update the golden fixture for that scenario.
166
164
  8. Update golden fixtures
167
165
  9. Tests for each of the above
168
166
 
169
- New categories cost a lot. The reason this list is long is to make you
170
- pause before adding one.
167
+ New categories cost a lot. This list is long to make you pause before
168
+ adding one.
171
169
 
172
170
  ## Commit conventions
173
171
 
package/README.md CHANGED
@@ -26,8 +26,6 @@ directory. Both ship a versioned JSON summary you can gate CI on. The
26
26
  `drift` command also ships as a one-line GitHub Action with a sticky PR
27
27
  comment.
28
28
 
29
- Same model as Pact, for event architectures.
30
-
31
29
  ## What this package does NOT do
32
30
 
33
31
  To keep the scope tight:
@@ -78,25 +76,29 @@ autotel-eventcatalog generate \
78
76
  Re-runnable: existing catalog files are skipped, never overwritten. New
79
77
  events / services / channels from the snapshot are added on top.
80
78
  Producer (`sends`), consumer (`receives`), and channel routing edges
81
- are wired automatically. Pass `--dry-run` to preview, `--edges-only`
82
- to re-sync relationships without touching resource bodies, or
83
- `--version 2.0.0` to override the default version for newly created
84
- resources.
79
+ are wired automatically.
80
+
81
+ | Flag | Default | Use it when… |
82
+ | -------------------- | ------- | -------------------------------------------------------------------------------------------- |
83
+ | `--dry-run` | off | You want to preview the operations without touching disk. |
84
+ | `--edges-only` | off | The catalog already has resources; you just want producer/consumer/channel wiring re-synced. |
85
+ | `--version <semver>` | `1.0.0` | Override the version assigned to newly created resources. |
86
+ | `--format json` | `text` | You want a machine-readable plan on stdout (the same shape as `--summary-output`). |
85
87
 
86
88
  ### Schema sources
87
89
 
88
90
  When `generate` writes an event's `schema.json`, it picks the schema
89
91
  in this order:
90
92
 
91
- 1. **Declared schema** if the snapshot's event observation carries a
93
+ 1. **Declared schema**: if the snapshot's event observation carries a
92
94
  `schema.jsonSchema` (recorded by `ArchitectureSnapshotSubscriber`
93
95
  when a `track()` call was made through [`defineEvent`](#defineevent-zod-schemas-at-the-call-site)),
94
96
  that schema is used verbatim.
95
- 2. **Inferred schema** otherwise, the schema is inferred from observed
97
+ 2. **Inferred schema**: otherwise, the schema is inferred from observed
96
98
  `fieldStats` (runtime types per dotted path) as a fallback. This
97
99
  gets a new project from zero to a usable catalog on the first run;
98
100
  `defineEvent` is the path to a robust, single-source-of-truth
99
- contract once you're ready.
101
+ contract when you adopt it.
100
102
 
101
103
  The choice is recorded in the operations log and the `generate-summary`
102
104
  JSON envelope as `schemaSource: 'declared' | 'inferred'`, so CI can
@@ -129,8 +131,8 @@ orderPlacedEvent.track({ orderId, customerId, totalCents, items });
129
131
  The schema is now the single source of truth: TypeScript catches drift
130
132
  at compile time, `safeParse` validates payloads at runtime, and
131
133
  `ArchitectureSnapshotSubscriber` carries the JSON Schema forward into
132
- the snapshot so `generate` writes the _same_ schema your code
133
- enforces, no inference guesswork.
134
+ the snapshot, so `generate` writes the _same_ schema your code
135
+ enforces. No inference guesswork.
134
136
 
135
137
  ### From code
136
138
 
@@ -176,22 +178,22 @@ and the catalog's schemas align with what the code emits, running
176
178
  No drift detected. Catalog and runtime agree.
177
179
  ```
178
180
 
179
- Exit code 0, CI passes. That's the steady-state goal: the catalog and
180
- the running code make the same claims and the drift detector confirms
181
- it. Every additional finding is genuine signal of code-vs-catalog
182
- divergence — a new event added without a schema, a removed producer
183
- still listed, a schema field that no longer ships.
181
+ Exit code 0, CI passes. That is the steady-state goal: the catalog and
182
+ the running code make the same claims, and the drift detector confirms
183
+ it. Each additional finding signals real code-vs-catalog divergence:
184
+ a new event added without a schema, a removed producer still listed,
185
+ a schema field that no longer ships.
184
186
 
185
187
  Type drift handles the JSON Schema ↔ JavaScript impedance mismatch
186
188
  deliberately: a declared `integer` accepts an observed `number` at the
187
- type level, then sample values are checked against `Number.isInteger`
189
+ type level, then sample values are checked against `Number.isInteger`,
188
190
  so a runtime `1.5` against a declared `integer` still flags. No false
189
- positives, no swept-under-the-rug genuine signal.
191
+ positives, no missed signal.
190
192
 
191
193
  The example app exercises both the happy path and the payment-failure
192
194
  path so the snapshot covers every documented event. Value drift would
193
195
  fire if, for instance, a `declineCode` outside the declared enum
194
- appeared at runtime try replacing `card_declined` with something else
196
+ appeared at runtime. Try replacing `card_declined` with something else
195
197
  in `build-snapshot.ts` and re-running to see it.
196
198
 
197
199
  ### As a GitHub Action
@@ -260,7 +262,7 @@ What lands on the PR:
260
262
  **How type and value drift work in practice.**
261
263
 
262
264
  1. `ArchitectureSnapshotSubscriber` records `fieldStats` per event during
263
- a test run for each dotted path it captures the runtime types it
265
+ a test run. For each dotted path it captures the runtime types it
264
266
  saw (`string`, `number`, `object`, …) and up to 20 primitive sample
265
267
  values. Verified by
266
268
  [`snapshot-fieldstats.integration.test.ts`](../../apps/example-eventcatalog/services/test/snapshot-fieldstats.integration.test.ts),
@@ -277,9 +279,9 @@ What lands on the PR:
277
279
  a `valueDrift` entry. Both feed `countDriftReport` and the markdown
278
280
  renderer.
279
281
 
280
- You can now pass first-class Zod metadata from `track()` call sites (via
281
- `defineEvent(...)` in `autotel`) so contracts can be declared in code and
282
- propagated into runtime snapshots.
282
+ You can pass Zod metadata from `track()` call sites (via `defineEvent(...)`
283
+ in `autotel`) so contracts are declared in code and propagated into runtime
284
+ snapshots.
283
285
 
284
286
  ## Public JSON contract
285
287
 
@@ -303,35 +305,51 @@ changes them without bumping the spec version will fail CI.
303
305
 
304
306
  `drift --format <name>` dispatches through a small renderer registry. The
305
307
  built-ins are `markdown`, `terminal`, `json`, `eventcatalog-snapshot-diff`.
306
- The registry is exported
307
- from the library so applications and other tooling can add their own:
308
+ You can plug in your own at runtime in two ways.
309
+
310
+ **Programmatic** (when you import the library):
308
311
 
309
312
  ```typescript
310
- import { RENDERERS, type Renderer } from 'autotel-eventcatalog';
313
+ import { registerRenderer, type Renderer } from 'autotel-eventcatalog';
311
314
 
312
315
  const sarifRenderer: Renderer = {
313
316
  name: 'sarif',
314
317
  description: 'Static Analysis Results Interchange Format',
315
318
  renderReport(report) {
316
- /* ... */ return '';
319
+ /* return SARIF JSON */ return '';
317
320
  },
318
321
  renderDelta(delta) {
319
- /* ... */ return '';
322
+ /* return SARIF JSON */ return '';
320
323
  },
321
324
  };
322
- // Programmatic consumers can plug it in. The CLI is also extensible via
323
- // a follow-up release that supports loading renderers from a config file.
325
+
326
+ registerRenderer(sarifRenderer);
327
+ ```
328
+
329
+ **From the CLI**, with `--register-renderer <module>` as a global option:
330
+
331
+ ```bash
332
+ npx autotel-eventcatalog \
333
+ --register-renderer ./renderers/sarif.mjs \
334
+ drift --snapshot snap.json --catalog catalog --format sarif
324
335
  ```
325
336
 
337
+ The loader expects the module to export either a `default` or a named
338
+ `renderer` matching the `Renderer` shape. ESM (`.mjs`) or compiled
339
+ JavaScript both work; TypeScript users compile to ESM first. Pass the
340
+ flag more than once to load multiple renderers in the same invocation.
341
+ The loader refuses to overwrite a built-in name (`markdown`, `terminal`,
342
+ `json`, `eventcatalog-snapshot-diff`): pick a unique `name` for yours.
343
+
326
344
  The core diff and policy modules are renderer-agnostic; adding a new
327
- output target should never require touching `diff.ts` or `policy.ts`.
345
+ output target never requires touching `diff.ts` or `policy.ts`.
328
346
 
329
347
  ## What writes to the catalog
330
348
 
331
349
  `drift` is **read-only**. It diffs the snapshot against the catalog and
332
350
  reports findings. It never modifies catalog files.
333
351
 
334
- `stamp` is the **write path**. By design, it injects a runtime evidence
352
+ `stamp` is the **write path**. It injects a runtime evidence
335
353
  block into each event mdx between idempotent markers
336
354
  (`<!-- autotel:stamp-start -->` / `<!-- autotel:stamp-end -->`). Re-runs
337
355
  replace the content between the markers; nothing outside the markers is
@@ -345,10 +363,26 @@ need to gate on "did this PR forget to re-stamp?".
345
363
  ## Working example
346
364
 
347
365
  See [`apps/example-eventcatalog`](../../apps/example-eventcatalog) in the
348
- monorepo. It has a working e-commerce catalog and a snapshot. Running
349
- `pnpm catalog:drift` from that app prints a drift report that surfaces
350
- two findings, including a deliberately-introduced `personalization_seed`
351
- field that the schema does not declare.
366
+ monorepo. It has a working e-commerce catalog and a committed snapshot.
367
+ Running `pnpm catalog:drift` from that app prints
368
+
369
+ ```text
370
+ No drift detected. Catalog and runtime agree.
371
+ ```
372
+
373
+ That is the steady-state goal. Each additional finding from there signals
374
+ real code-vs-catalog divergence. The same agreement is locked in as a
375
+ vitest integration test, so a service that stops emitting a documented
376
+ event or a catalog file that rots fails CI before the drift script runs.
377
+
378
+ ### Coverage caveat
379
+
380
+ Drift only sees what `ArchitectureSnapshotSubscriber` recorded in the
381
+ run that produced the snapshot. Events emitted only on paths your test
382
+ suite does not exercise will not appear; documented events your test
383
+ suite does not exercise will be flagged as `documentedButUnseen`. In
384
+ practice this gives you **catalog honesty for what your test suite
385
+ covers**, which is the property a green pipeline should buy you.
352
386
 
353
387
  ## Documentation
354
388
 
@@ -362,14 +396,12 @@ field that the schema does not declare.
362
396
  | Contribute to the package | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
363
397
  | Validate the published JSON shapes | [`schemas/README.md`](schemas/README.md) |
364
398
 
365
- ## Used by
366
-
367
- _If you adopt this in production, open a PR adding your team to this
368
- section. We'd like to know who depends on the contract before shipping
369
- breaking changes, and seeing a name here helps future adopters gauge
370
- the project's maturity._
399
+ ## Related packages
371
400
 
372
- - _no public adopters yet; be the first_
401
+ - [`autotel`](../autotel): OpenTelemetry instrumentation for Node.js.
402
+ The substrate every snapshot is built from.
403
+ - [`autotel-subscribers/architecture-snapshot`](../autotel-subscribers):
404
+ produces the snapshot this package consumes.
373
405
 
374
406
  ## License
375
407