autotel-eventcatalog 1.0.1 → 2.0.1

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,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9fbbc3a: Close the loop from "code declares the event contract" through to "catalog reflects the runtime" — schemas declared at the `track()` call site flow through telemetry into the catalog generator and drift detector with no inference guesswork.
8
+
9
+ ### `autotel`
10
+ - **New: `defineEvent(name, schema, options?)`.** Returns a `DefinedEvent` that validates the payload at runtime (via the schema's `safeParse`) and carries the JSON Schema and a stable SHA-256 schema hash through `track()` as part of the `EventTrackingOptions`. Designed for Zod (`{ toJsonSchema: (s) => z.toJSONSchema(s) }`) but accepts any schema with a `safeParse` method. Imported from `autotel`.
11
+ - **New `schema?: EventSchemaMetadata` field** on `EventTrackingOptions`. The `EventQueue` carries it onto the `EventPayload` so any contract-aware subscriber (`ArchitectureSnapshotSubscriber`, custom subscribers) sees the declared schema verbatim. Optional and backwards-compatible — bare `track()` calls continue to work.
12
+ - **PII redaction now only applies to string values.** The default sensitive-key patterns (`/token/i`, `/auth/i`, …) used to overwrite _any_ matching value — including numbers and booleans — with the literal string `"[REDACTED]"`. That broke type stability for fields like `promptTokens` / `completionTokens` (LLM usage counters) and gave nothing in return: secrets in user code are overwhelmingly strings. Numeric and boolean attributes now pass through untouched. Same change applied to `AttributeRedactingProcessor`. Existing tests that asserted booleans got redacted have been updated to reflect the new (correct) behaviour.
13
+
14
+ ### `autotel-subscribers`
15
+ - **`ArchitectureSnapshotSubscriber` records `fieldStats` for every observed event.** For each dotted field path it tracks the runtime types it saw (`string`, `number`, `object`, …) and up to 20 primitive sample values, merging across observations. The new `FieldStats` type is added to `EventObservation`. Existing snapshots stay valid — `fieldStats` is optional.
16
+ - **Captures declared schemas from `defineEvent`.** When a `track()` call originated from `defineEvent`, the subscriber stores the declared `{ source: 'zod', jsonSchema, hash }` on the observation as `EventObservation.schema?`. Snapshots that go through bare `track()` are unchanged.
17
+ - **Captures consumer-service attribution.** A new optional `_autotel.consumers: string[]` convention on the event attributes is read into `EventObservation.consumers?`, so the snapshot now describes consumer relationships in addition to producer / channel.
18
+
19
+ ### `autotel-eventcatalog`
20
+ - **New `generate` command** — scaffolds services, events, channels, and producer/consumer/channel-routing edges from a snapshot. Skip-if-exists: catalog files that already exist are left completely untouched. When the snapshot's `EventObservation.schema?.jsonSchema` is present (declared at the `track()` call site), it is written verbatim to the event's `schema.json`. Otherwise the schema is inferred from runtime `fieldStats` as a fallback — captured in the operations log as `schemaSource: 'declared' | 'inferred'`. CLI flags: `--snapshot`, `--catalog`, `--dry-run`, `--edges-only`, `--version`, `--summary-output`. Versioned summary envelope (`schemas/generate-summary-v0.1.0.json`) pinned by a contract test.
21
+ - **Type drift and value drift** — `diffCatalogAgainstSnapshot` now consumes `fieldStats` to detect runtime-vs-declared mismatches. Drift detector handles the JSON Schema `integer` vs JS `number` impedance mismatch deliberately: declared `integer` accepts observed `number` at the type level, but sample values are checked against `Number.isInteger` — so a runtime `1.5` against an `integer` declaration still flags. No false positives on integer fields, no swept-under-the-rug genuine signal. New `drift-report-v0.2.0.json` and `drift-summary-v0.2.0.json` schemas pin the richer wire format; v0.1.0 envelopes are still emitted for backwards-compatible consumers.
22
+ - **`SnapshotDiff` interop renderer** — `toSnapshotDiffFromReport(report)` and `toSnapshotDiffFromDelta(delta)` produce EventCatalog's own `SnapshotDiff` shape (with `ResourceChange[]` and `RelationshipChange[]`), so drift findings can flow into upstream catalog tooling that already understands that format. Exposed as the new `eventcatalog-snapshot-diff` renderer.
23
+ - **Catalog state now read via `@eventcatalog/sdk`** instead of a bespoke filesystem walker. `CatalogEvent`, `CatalogService` and `CatalogChannel` extend the SDK's `Event`, `Service` and `Channel` types directly, so any field the SDK adds in future is picked up without changes here. The package gains `@eventcatalog/sdk` as a runtime dependency.
24
+ - **Workaround for an upstream SDK bug** — `addEventToChannel` in `@eventcatalog/sdk@2.21.2` corrupts catalog layout (turns `index.mdx` into a directory) because of a string-vs-regex bug in path splitting. `generate` sets the channel pointer directly on the event's frontmatter when calling `writeEvent`, sidestepping the bad code path. The fix is filed upstream as [event-catalog/eventcatalog#2567](https://github.com/event-catalog/eventcatalog/pull/2567); the workaround can be removed once that ships.
25
+
26
+ ### What this means in practice
27
+
28
+ The reference app (`apps/example-eventcatalog`) has been migrated to `defineEvent` for all five domain events. Running `pnpm services:snapshot && pnpm catalog:drift` against the resulting catalog now prints `No drift detected. Catalog and runtime agree.` That's the steady-state goal — every additional drift finding from here is genuine signal of code-vs-catalog divergence.
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [9fbbc3a]
33
+ - autotel-subscribers@33.0.0
34
+
3
35
  ## 1.0.0
4
36
 
5
37
  ### Minor Changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Jag Reehal 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -13,12 +13,13 @@ the code actually does at runtime.
13
13
  > on what channel. This package reads that snapshot and compares it to
14
14
  > your catalog.
15
15
 
16
- Two tools:
16
+ Three tools:
17
17
 
18
- | Command | Mode | What it does |
19
- | ----------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
- | **`drift`** | read-only | Diffs the catalog against a snapshot. Reports findings as Markdown, JSON, or plain text. The PR check that catches "you added an event but forgot to document it." |
21
- | **`stamp`** | write | Writes a runtime evidence block (counts, last-seen, field paths) into each event's `index.mdx` between idempotent markers. Keeps the static catalog page reflecting production behaviour. |
18
+ | Command | Mode | What it does |
19
+ | -------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | **`drift`** | read-only | Diffs the catalog against a snapshot. Reports findings as Markdown, JSON, or plain text. The PR check that catches "you added an event but forgot to document it." |
21
+ | **`generate`** | write | Scaffolds EventCatalog resources from a snapshot: services, events, channels, inferred JSON Schemas, and producer↔event↔channel relationships. |
22
+ | **`stamp`** | write | Writes a runtime evidence block (counts, last-seen, field paths) into each event's `index.mdx` between idempotent markers. Keeps the static catalog page reflecting production behaviour. |
22
23
 
23
24
  Both share inputs: an autotel snapshot JSON file and an EventCatalog
24
25
  directory. Both ship a versioned JSON summary you can gate CI on. The
@@ -36,9 +37,11 @@ To keep the scope tight:
36
37
  This package only consumes them.
37
38
  - **Does not run any web server or dashboard.** Live dashboards live in
38
39
  example apps. This package is a CLI plus library plus action.
39
- - **Does not infer contract schemas from payload samples.** Field-path drift
40
- is set-difference on dotted paths. Type/value drift is checked only against
41
- declared schema constraints, not inferred contracts.
40
+ - **Does not infer drift contracts from payload samples during `drift`.**
41
+ Field-path drift is set-difference on dotted paths. Type/value drift is
42
+ checked only against declared schema constraints. (`generate` can scaffold
43
+ schemas from snapshot evidence; `drift` still compares against declared
44
+ schemas.)
42
45
  - **Does not modify catalog files outside the stamp markers.** Everything
43
46
  the `stamp` command writes is between `<!-- autotel:stamp-start -->`
44
47
  and `<!-- autotel:stamp-end -->`. Outside those markers is yours.
@@ -66,11 +69,75 @@ autotel-eventcatalog drift \
66
69
  --fail-on-drift # exit 1 on drift; wire this into CI
67
70
  ```
68
71
 
72
+ ```bash
73
+ autotel-eventcatalog generate \
74
+ --snapshot ./services/test/snapshot.json \
75
+ --catalog ./catalog
76
+ ```
77
+
78
+ Re-runnable: existing catalog files are skipped, never overwritten. New
79
+ events / services / channels from the snapshot are added on top.
80
+ 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.
85
+
86
+ ### Schema sources
87
+
88
+ When `generate` writes an event's `schema.json`, it picks the schema
89
+ in this order:
90
+
91
+ 1. **Declared schema** — if the snapshot's event observation carries a
92
+ `schema.jsonSchema` (recorded by `ArchitectureSnapshotSubscriber`
93
+ when a `track()` call was made through [`defineEvent`](#defineevent-zod-schemas-at-the-call-site)),
94
+ that schema is used verbatim.
95
+ 2. **Inferred schema** — otherwise, the schema is inferred from observed
96
+ `fieldStats` (runtime types per dotted path) as a fallback. This
97
+ gets a new project from zero to a usable catalog on the first run;
98
+ `defineEvent` is the path to a robust, single-source-of-truth
99
+ contract once you're ready.
100
+
101
+ The choice is recorded in the operations log and the `generate-summary`
102
+ JSON envelope as `schemaSource: 'declared' | 'inferred'`, so CI can
103
+ surface adoption.
104
+
105
+ #### `defineEvent`: Zod schemas at the call site
106
+
107
+ In your service code, replace bare `track('event.name', payload)` calls
108
+ with `defineEvent` and a Zod schema:
109
+
110
+ ```typescript
111
+ import { defineEvent } from 'autotel';
112
+ import { z } from 'zod';
113
+
114
+ export const orderPlacedEvent = defineEvent(
115
+ 'order.placed',
116
+ z.object({
117
+ orderId: z.string(),
118
+ customerId: z.string(),
119
+ totalCents: z.number(),
120
+ items: z.array(z.object({ sku: z.string(), quantity: z.number() })),
121
+ }),
122
+ { toJsonSchema: (schema) => z.toJSONSchema(schema) },
123
+ );
124
+
125
+ // At the call site:
126
+ orderPlacedEvent.track({ orderId, customerId, totalCents, items });
127
+ ```
128
+
129
+ The schema is now the single source of truth: TypeScript catches drift
130
+ at compile time, `safeParse` validates payloads at runtime, and
131
+ `ArchitectureSnapshotSubscriber` carries the JSON Schema forward into
132
+ the snapshot — so `generate` writes the _same_ schema your code
133
+ enforces, no inference guesswork.
134
+
69
135
  ### From code
70
136
 
71
137
  ```typescript
72
138
  import {
73
139
  loadSnapshot,
140
+ generateCatalogFromSnapshot,
74
141
  readCatalogState,
75
142
  diffCatalogAgainstSnapshot,
76
143
  renderMarkdown,
@@ -83,6 +150,12 @@ import {
83
150
  } from 'autotel-eventcatalog';
84
151
 
85
152
  const snapshot = await loadSnapshot('./services/test/snapshot.json');
153
+ await generateCatalogFromSnapshot({
154
+ snapshot,
155
+ catalogPath: './catalog',
156
+ dryRun: false,
157
+ });
158
+
86
159
  const catalog = await readCatalogState('./catalog');
87
160
  const report = diffCatalogAgainstSnapshot(snapshot, catalog);
88
161
 
@@ -93,34 +166,21 @@ if (hasDrift(report)) process.exit(1);
93
166
 
94
167
  ### What a run actually prints
95
168
 
96
- The example app in this monorepo (`apps/example-eventcatalog`) ships with
97
- two deliberate drift conditions so the CLI has something real to find.
98
- Running `pnpm catalog:drift` there prints:
169
+ After the example app's services have been migrated to `defineEvent`
170
+ and the catalog's schemas align with what the code emits, running
171
+ `pnpm catalog:drift` from `apps/example-eventcatalog` prints:
99
172
 
100
173
  ```
101
174
  # Architecture drift report
102
175
 
103
- ## Events documented but never observed
104
-
105
- - `PaymentFailed`
106
-
107
- ## Field-path drift
108
-
109
- ### `recommendation.generated`
110
-
111
- **Extra fields in payloads (not in declared schema):**
112
-
113
- - `personalization_seed`
114
-
115
- Drift detected in current snapshot.
176
+ No drift detected. Catalog and runtime agree.
116
177
  ```
117
178
 
118
- Exit code 1, CI fails. Two genuine findings, zero noise:
119
-
120
- - **`PaymentFailed` documented but never observed** the failure path
121
- isn't exercised by the integration tests. A real coverage gap.
122
- - **`personalization_seed` extra field** the service emits it; the
123
- catalog schema doesn't declare it. Real schema drift.
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.
124
184
 
125
185
  Type drift handles the JSON Schema ↔ JavaScript impedance mismatch
126
186
  deliberately: a declared `integer` accepts an observed `number` at the
@@ -128,6 +188,12 @@ type level, then sample values are checked against `Number.isInteger`
128
188
  so a runtime `1.5` against a declared `integer` still flags. No false
129
189
  positives, no swept-under-the-rug genuine signal.
130
190
 
191
+ The example app exercises both the happy path and the payment-failure
192
+ path so the snapshot covers every documented event. Value drift would
193
+ fire if, for instance, a `declineCode` outside the declared enum
194
+ appeared at runtime — try replacing `card_declined` with something else
195
+ in `build-snapshot.ts` and re-running to see it.
196
+
131
197
  ### As a GitHub Action
132
198
 
133
199
  The package ships a composite action so any repository can wire drift checking
@@ -211,20 +277,22 @@ What lands on the PR:
211
277
  a `valueDrift` entry. Both feed `countDriftReport` and the markdown
212
278
  renderer.
213
279
 
214
- A planned next step is first-class Zod metadata at `track()` call sites
215
- so contracts can be declared in code rather than only in catalog schemas.
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.
216
283
 
217
284
  ## Public JSON contract
218
285
 
219
- Three versioned JSON shapes are shipped with the package as JSON Schema
286
+ Four versioned JSON shapes are shipped with the package as JSON Schema
220
287
  files (`schemas/` directory). Downstream tooling (your own GitHub Actions,
221
288
  dashboards, Slack bots) should validate against these:
222
289
 
223
- | Schema | Emitted by | Consumed by |
224
- | ----------------------------------- | ---------------------------- | ---------------------------------------- |
225
- | `schemas/drift-report-v0.1.0.json` | `drift --format json` | Any downstream parser |
226
- | `schemas/drift-summary-v0.1.0.json` | `drift --summary-output ...` | CI gating, dashboards |
227
- | `schemas/stamp-summary-v0.1.0.json` | `stamp --summary-output ...` | "Did this PR forget to re-stamp?" checks |
290
+ | Schema | Emitted by | Consumed by |
291
+ | -------------------------------------- | ------------------------------- | ---------------------------------------- |
292
+ | `schemas/drift-report-v0.2.0.json` | `drift --format json` | Any downstream parser |
293
+ | `schemas/drift-summary-v0.2.0.json` | `drift --summary-output ...` | CI gating, dashboards |
294
+ | `schemas/stamp-summary-v0.1.0.json` | `stamp --summary-output ...` | "Did this PR forget to re-stamp?" checks |
295
+ | `schemas/generate-summary-v0.1.0.json` | `generate --summary-output ...` | scaffold/edge generation auditing |
228
296
 
229
297
  Every envelope carries a `spec: 'autotel-eventcatalog-…/vX.Y.Z'` field
230
298
  that downstream code can use to refuse unknown major versions. The shapes
@@ -234,7 +302,8 @@ changes them without bumping the spec version will fail CI.
234
302
  ## Renderers (advanced)
235
303
 
236
304
  `drift --format <name>` dispatches through a small renderer registry. The
237
- built-ins are `markdown`, `terminal`, `json`. The registry is exported
305
+ built-ins are `markdown`, `terminal`, `json`, `eventcatalog-snapshot-diff`.
306
+ The registry is exported
238
307
  from the library so applications and other tooling can add their own:
239
308
 
240
309
  ```typescript