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 +32 -0
- package/LICENSE +21 -0
- package/README.md +108 -39
- package/dist/cli.cjs +614 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +614 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +505 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -1
- package/dist/index.d.ts +122 -1
- package/dist/index.js +497 -2
- package/dist/index.js.map +1 -1
- package/docs/CONTRACT.md +13 -12
- package/index.js +1 -1
- package/package.json +17 -17
- package/schemas/README.md +4 -3
- package/schemas/generate-summary-v0.1.0.json +144 -0
- package/src/cli.e2e.test.ts +29 -0
- package/src/cli.ts +135 -2
- package/src/contract.test.ts +88 -0
- package/src/eventcatalog-snapshot-diff.test.ts +61 -0
- package/src/eventcatalog-snapshot-diff.ts +170 -0
- package/src/generate.test.ts +195 -0
- package/src/generate.ts +559 -0
- package/src/index.ts +27 -0
- package/src/policy.test.ts +6 -0
- package/src/renderers/eventcatalog-snapshot-diff.ts +46 -0
- package/src/renderers/index.ts +8 -0
- package/src/renderers/renderers.test.ts +12 -1
- package/src/report.test.ts +39 -0
- package/src/report.ts +4 -0
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
|
-
|
|
16
|
+
Three tools:
|
|
17
17
|
|
|
18
|
-
| Command
|
|
19
|
-
|
|
|
20
|
-
| **`drift`**
|
|
21
|
-
| **`
|
|
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
|
|
40
|
-
is set-difference on dotted paths. Type/value drift is
|
|
41
|
-
declared schema constraints
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
215
|
-
so contracts can be declared in code
|
|
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
|
-
|
|
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
|
|
224
|
-
|
|
|
225
|
-
| `schemas/drift-report-v0.
|
|
226
|
-
| `schemas/drift-summary-v0.
|
|
227
|
-
| `schemas/stamp-summary-v0.1.0.json`
|
|
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`.
|
|
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
|