autotel-eventcatalog 1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/CONTRIBUTING.md +212 -0
  3. package/README.md +307 -0
  4. package/action.yml +155 -0
  5. package/dist/cli.cjs +1071 -0
  6. package/dist/cli.cjs.map +1 -0
  7. package/dist/cli.d.cts +2 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +1065 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/index.cjs +794 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.cts +267 -0
  14. package/dist/index.d.ts +267 -0
  15. package/dist/index.js +764 -0
  16. package/dist/index.js.map +1 -0
  17. package/docs/CONTRACT.md +280 -0
  18. package/docs/EXTENDING.md +248 -0
  19. package/docs/TROUBLESHOOTING.md +220 -0
  20. package/docs/UPGRADING.md +202 -0
  21. package/package.json +78 -0
  22. package/schemas/README.md +44 -0
  23. package/schemas/drift-report-v0.1.0.json +107 -0
  24. package/schemas/drift-report-v0.2.0.json +137 -0
  25. package/schemas/drift-summary-v0.1.0.json +74 -0
  26. package/schemas/drift-summary-v0.2.0.json +74 -0
  27. package/schemas/stamp-summary-v0.1.0.json +54 -0
  28. package/src/__fixtures__/drift-report-all.golden.json +33 -0
  29. package/src/__fixtures__/drift-summary-clean.golden.json +17 -0
  30. package/src/__fixtures__/drift-summary-drifty.golden.json +17 -0
  31. package/src/__fixtures__/stamp-summary-noop.golden.json +10 -0
  32. package/src/catalog.test.ts +63 -0
  33. package/src/catalog.ts +169 -0
  34. package/src/cli.e2e.test.ts +310 -0
  35. package/src/cli.ts +402 -0
  36. package/src/contract.test.ts +395 -0
  37. package/src/diff-vs-base.test.ts +145 -0
  38. package/src/diff-vs-base.ts +242 -0
  39. package/src/diff.test.ts +384 -0
  40. package/src/diff.ts +296 -0
  41. package/src/index.ts +73 -0
  42. package/src/policy.test.ts +75 -0
  43. package/src/policy.ts +41 -0
  44. package/src/renderers/index.ts +35 -0
  45. package/src/renderers/json.ts +33 -0
  46. package/src/renderers/markdown.ts +223 -0
  47. package/src/renderers/renderers.test.ts +79 -0
  48. package/src/renderers/terminal.ts +30 -0
  49. package/src/renderers/types.ts +26 -0
  50. package/src/report.test.ts +205 -0
  51. package/src/report.ts +27 -0
  52. package/src/snapshot.ts +25 -0
  53. package/src/stamp.test.ts +283 -0
  54. package/src/stamp.ts +232 -0
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ # autotel-eventcatalog
2
+
3
+ Keep your [EventCatalog](https://www.eventcatalog.dev) honest about what
4
+ the code actually does at runtime.
5
+
6
+ > **New here?** [autotel](https://github.com/jagreehal/autotel) is an
7
+ > ergonomic OpenTelemetry wrapper for Node.js: you call `trace()`, `span()`
8
+ > and `track(eventName, payload)` in your service code and it emits spans
9
+ > and domain events. The `ArchitectureSnapshotSubscriber` from
10
+ > `autotel-subscribers` listens to those events during a test run and
11
+ > writes a `snapshot.json` describing every event that fired, the fields
12
+ > in its payload, the runtime types of those fields, who produced it and
13
+ > on what channel. This package reads that snapshot and compares it to
14
+ > your catalog.
15
+
16
+ Two tools:
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. |
22
+
23
+ Both share inputs: an autotel snapshot JSON file and an EventCatalog
24
+ directory. Both ship a versioned JSON summary you can gate CI on. The
25
+ `drift` command also ships as a one-line GitHub Action with a sticky PR
26
+ comment.
27
+
28
+ Same model as Pact, for event architectures.
29
+
30
+ ## What this package does NOT do
31
+
32
+ To keep the scope tight:
33
+
34
+ - **Does not produce snapshots.** Snapshots come from
35
+ [`autotel-subscribers`'s `ArchitectureSnapshotSubscriber`](../autotel-subscribers).
36
+ This package only consumes them.
37
+ - **Does not run any web server or dashboard.** Live dashboards live in
38
+ 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.
42
+ - **Does not modify catalog files outside the stamp markers.** Everything
43
+ the `stamp` command writes is between `<!-- autotel:stamp-start -->`
44
+ and `<!-- autotel:stamp-end -->`. Outside those markers is yours.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pnpm add -D autotel-eventcatalog
50
+ ```
51
+
52
+ Requires that your services use autotel and `ArchitectureSnapshotSubscriber`
53
+ from `autotel-subscribers/architecture-snapshot` to produce a snapshot.
54
+
55
+ ## Use
56
+
57
+ ### From the CLI
58
+
59
+ ```bash
60
+ autotel-eventcatalog drift \
61
+ --snapshot ./services/test/snapshot.json \
62
+ --catalog ./catalog \
63
+ --output ./drift.md \
64
+ --summary-output ./drift-summary.json \
65
+ --policy all \
66
+ --fail-on-drift # exit 1 on drift; wire this into CI
67
+ ```
68
+
69
+ ### From code
70
+
71
+ ```typescript
72
+ import {
73
+ loadSnapshot,
74
+ readCatalogState,
75
+ diffCatalogAgainstSnapshot,
76
+ renderMarkdown,
77
+ renderTerminal, // plain-text, for Slack / log files / non-MD terminals
78
+ renderJson, // versioned envelope for downstream tooling
79
+ countDriftReport, // per-category counts that match the dashboard hero badge
80
+ hasDrift,
81
+ stampCatalog, // write runtime evidence into your catalog's event mdx
82
+ buildStampSummary, // count of inserts / replaces / no-ops / skipped
83
+ } from 'autotel-eventcatalog';
84
+
85
+ const snapshot = await loadSnapshot('./services/test/snapshot.json');
86
+ const catalog = await readCatalogState('./catalog');
87
+ const report = diffCatalogAgainstSnapshot(snapshot, catalog);
88
+
89
+ console.log(renderMarkdown(report));
90
+ console.log('findings:', countDriftReport(report).total);
91
+ if (hasDrift(report)) process.exit(1);
92
+ ```
93
+
94
+ ### What a run actually prints
95
+
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:
99
+
100
+ ```
101
+ # Architecture drift report
102
+
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.
116
+ ```
117
+
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.
124
+
125
+ Type drift handles the JSON Schema ↔ JavaScript impedance mismatch
126
+ deliberately: a declared `integer` accepts an observed `number` at the
127
+ type level, then sample values are checked against `Number.isInteger`
128
+ so a runtime `1.5` against a declared `integer` still flags. No false
129
+ positives, no swept-under-the-rug genuine signal.
130
+
131
+ ### As a GitHub Action
132
+
133
+ The package ships a composite action so any repository can wire drift checking
134
+ into its PR pipeline with a single step:
135
+
136
+ ```yaml
137
+ # .github/workflows/eventcatalog-drift.yml
138
+ name: eventcatalog drift
139
+ on:
140
+ pull_request:
141
+ branches: [main]
142
+ jobs:
143
+ drift:
144
+ runs-on: ubuntu-latest
145
+ permissions:
146
+ contents: read
147
+ pull-requests: write
148
+ steps:
149
+ - uses: actions/checkout@v4
150
+ with: { fetch-depth: 0 } # so the action can read the base branch
151
+
152
+ # Produce the snapshot however you like; typically by running your
153
+ # integration tests with ArchitectureSnapshotSubscriber wired in.
154
+ - run: pnpm install --frozen-lockfile
155
+ - run: pnpm services:snapshot
156
+
157
+ - uses: jagreehal/autotel-eventcatalog@v0
158
+ with:
159
+ snapshot: ./services/test/snapshot.json
160
+ catalog: ./catalog
161
+ base-ref: origin/${{ github.base_ref }} # compare to PR base
162
+ fail-on-drift: true # fail the PR on new drift
163
+ comment-on-pr: true # post a sticky comment
164
+ ```
165
+
166
+ CLI policy modes:
167
+
168
+ - `--policy all`: fail on any drift in the current snapshot.
169
+ - `--policy new-only`: fail only on drift introduced vs `--base-snapshot`.
170
+
171
+ What lands on the PR:
172
+
173
+ - A sticky comment titled "Architecture drift: what this change introduces"
174
+ with sections for new events, removed events, field-path drift, type drift,
175
+ value drift, and drift the PR resolved.
176
+ - The check fails only when this PR introduces _new_ drift. Pre-existing
177
+ drift is reported for context but does not block.
178
+ - The drift report is also written to `$RUNNER_TEMP/autotel-eventcatalog-drift.md`
179
+ and printed in the job log.
180
+
181
+ ## What gets caught
182
+
183
+ | Drift class | Example finding |
184
+ | ---------------------------------------- | ---------------------------------------------------------- |
185
+ | **Events observed but undocumented** | `order.cancelled` emitted by code; no entry in catalog |
186
+ | **Events documented but never observed** | `LegacyEvent` in catalog; never seen in tests |
187
+ | **Field-path drift (extra)** | `personalization_seed` in payload; not declared in schema |
188
+ | **Field-path drift (missing)** | `customerId` declared in schema; never present in payloads |
189
+ | **Type drift** | `amount` declared `number`; observed `string` |
190
+ | **Value drift (enum mismatch)** | `status: "placed"` observed; schema enum excludes it |
191
+ | **Services observed but undocumented** | `OrdersService` is a producer; no service page |
192
+ | **Channels observed but undocumented** | `orders.events` carries messages; no channel page |
193
+
194
+ **How type and value drift work in practice.**
195
+
196
+ 1. `ArchitectureSnapshotSubscriber` records `fieldStats` per event during
197
+ a test run — for each dotted path it captures the runtime types it
198
+ saw (`string`, `number`, `object`, …) and up to 20 primitive sample
199
+ values. Verified by
200
+ [`snapshot-fieldstats.integration.test.ts`](../../apps/example-eventcatalog/services/test/snapshot-fieldstats.integration.test.ts),
201
+ which runs the real `placeOrder` → `handleOrderPlaced` →
202
+ `handlePaymentCaptured` → `generateRecommendation` flow and asserts
203
+ `fieldStats.totalCents.types` contains `"number"`,
204
+ `fieldStats.currency.sampleValues` contains `"GBP"`, etc.
205
+ 2. `readCatalogState` parses each event's JSON Schema (via `schemaPath`
206
+ in the catalog frontmatter) into a map of declared `types` and `enum`
207
+ values per path.
208
+ 3. `diffCatalogAgainstSnapshot` cross-joins the two: any path whose
209
+ observed runtime type is not in the declared types becomes a
210
+ `typeDrift` entry; any sample value not in the declared enum becomes
211
+ a `valueDrift` entry. Both feed `countDriftReport` and the markdown
212
+ renderer.
213
+
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.
216
+
217
+ ## Public JSON contract
218
+
219
+ Three versioned JSON shapes are shipped with the package as JSON Schema
220
+ files (`schemas/` directory). Downstream tooling (your own GitHub Actions,
221
+ dashboards, Slack bots) should validate against these:
222
+
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 |
228
+
229
+ Every envelope carries a `spec: 'autotel-eventcatalog-…/vX.Y.Z'` field
230
+ that downstream code can use to refuse unknown major versions. The shapes
231
+ are locked by golden contract tests inside this package: a PR that
232
+ changes them without bumping the spec version will fail CI.
233
+
234
+ ## Renderers (advanced)
235
+
236
+ `drift --format <name>` dispatches through a small renderer registry. The
237
+ built-ins are `markdown`, `terminal`, `json`. The registry is exported
238
+ from the library so applications and other tooling can add their own:
239
+
240
+ ```typescript
241
+ import { RENDERERS, type Renderer } from 'autotel-eventcatalog';
242
+
243
+ const sarifRenderer: Renderer = {
244
+ name: 'sarif',
245
+ description: 'Static Analysis Results Interchange Format',
246
+ renderReport(report) {
247
+ /* ... */ return '';
248
+ },
249
+ renderDelta(delta) {
250
+ /* ... */ return '';
251
+ },
252
+ };
253
+ // Programmatic consumers can plug it in. The CLI is also extensible via
254
+ // a follow-up release that supports loading renderers from a config file.
255
+ ```
256
+
257
+ The core diff and policy modules are renderer-agnostic; adding a new
258
+ output target should never require touching `diff.ts` or `policy.ts`.
259
+
260
+ ## What writes to the catalog
261
+
262
+ `drift` is **read-only**. It diffs the snapshot against the catalog and
263
+ reports findings. It never modifies catalog files.
264
+
265
+ `stamp` is the **write path**. By design, it injects a runtime evidence
266
+ block into each event mdx between idempotent markers
267
+ (`<!-- autotel:stamp-start -->` / `<!-- autotel:stamp-end -->`). Re-runs
268
+ replace the content between the markers; nothing outside the markers is
269
+ touched. Run with `--dry-run` to preview the plan first.
270
+
271
+ Pass `--summary-output stamp.json` to write a versioned summary file
272
+ (`autotel-eventcatalog-stamp-summary/v0.1.0`) describing how many files
273
+ were inserted, replaced, no-op'd, or skipped. Useful for CI checks that
274
+ need to gate on "did this PR forget to re-stamp?".
275
+
276
+ ## Working example
277
+
278
+ See [`apps/example-eventcatalog`](../../apps/example-eventcatalog) in the
279
+ monorepo. It has a working e-commerce catalog and a snapshot. Running
280
+ `pnpm catalog:drift` from that app prints a drift report that surfaces
281
+ two findings, including a deliberately-introduced `personalization_seed`
282
+ field that the schema does not declare.
283
+
284
+ ## Documentation
285
+
286
+ | If you want to… | Read |
287
+ | ------------------------------------------------------ | ---------------------------------------------------- |
288
+ | Consume the JSON output from your own tool | [`docs/CONTRACT.md`](docs/CONTRACT.md) |
289
+ | Write a custom renderer (SARIF, Slack, your dashboard) | [`docs/EXTENDING.md`](docs/EXTENDING.md) |
290
+ | Debug a CLI exit code or a CI failure | [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) |
291
+ | Upgrade between versions safely | [`docs/UPGRADING.md`](docs/UPGRADING.md) |
292
+ | See what shipped in each release | [`CHANGELOG.md`](CHANGELOG.md) |
293
+ | Contribute to the package | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
294
+ | Validate the published JSON shapes | [`schemas/README.md`](schemas/README.md) |
295
+
296
+ ## Used by
297
+
298
+ _If you adopt this in production, open a PR adding your team to this
299
+ section. We'd like to know who depends on the contract before shipping
300
+ breaking changes, and seeing a name here helps future adopters gauge
301
+ the project's maturity._
302
+
303
+ - _no public adopters yet; be the first_
304
+
305
+ ## License
306
+
307
+ MIT.
package/action.yml ADDED
@@ -0,0 +1,155 @@
1
+ name: 'autotel-eventcatalog drift'
2
+ description: |
3
+ Diff your autotel architecture snapshot against an EventCatalog and surface
4
+ drift on the pull request. With base-ref input, only drift the PR introduces
5
+ fails the check — pre-existing drift is reported but ignored.
6
+
7
+ branding:
8
+ icon: 'activity'
9
+ color: 'orange'
10
+
11
+ inputs:
12
+ snapshot:
13
+ description: 'Path to the architecture snapshot JSON (the PR head version).'
14
+ required: true
15
+ catalog:
16
+ description: 'Path to the EventCatalog directory (containing eventcatalog.config.* and domains/).'
17
+ required: true
18
+ base-ref:
19
+ description: |
20
+ Git ref to read the baseline snapshot from (typically the PR base branch,
21
+ e.g. `origin/main`). When set, the action reports only drift the PR
22
+ introduces — pre-existing drift is shown for context but does not fail.
23
+ required: false
24
+ default: ''
25
+ fail-on-drift:
26
+ description: 'Fail the check when drift is detected. With base-ref, fails only on *new* drift.'
27
+ required: false
28
+ default: 'true'
29
+ comment-on-pr:
30
+ description: 'Post the drift report as a sticky comment on the pull request.'
31
+ required: false
32
+ default: 'true'
33
+ comment-header:
34
+ description: 'Header tag for the sticky comment (use this if you have multiple drift checks).'
35
+ required: false
36
+ default: 'autotel-eventcatalog'
37
+ package-version:
38
+ description: |
39
+ Semver range for autotel-eventcatalog. Defaults to `^0` so a new
40
+ major release won't silently land on your CI — pin to an exact
41
+ version (e.g. `0.1.0`) for fully reproducible runs.
42
+ required: false
43
+ default: '^0'
44
+ continue-on-comment-failure:
45
+ description: |
46
+ When `true`, a failure of the sticky-comment step (e.g. GitHub API
47
+ rate limit, transient 5xx, permissions) is downgraded to a warning
48
+ and the workflow keeps running. The drift check itself is
49
+ unaffected. Default `true` because a missed comment is annoying;
50
+ a failed job because of a missed comment is worse.
51
+ required: false
52
+ default: 'true'
53
+
54
+ outputs:
55
+ drift-detected:
56
+ description: '"true" if any drift was detected, "false" otherwise.'
57
+ value: ${{ steps.run.outputs.drift-detected }}
58
+ report-path:
59
+ description: 'Path to the generated markdown drift report.'
60
+ value: ${{ steps.run.outputs.report-path }}
61
+
62
+ runs:
63
+ using: composite
64
+ steps:
65
+ - name: Resolve base snapshot
66
+ id: base
67
+ if: ${{ inputs.base-ref != '' }}
68
+ shell: bash
69
+ run: |
70
+ set -euo pipefail
71
+ BASE_SNAPSHOT="$RUNNER_TEMP/autotel-eventcatalog-base-snapshot.json"
72
+ if git show "${{ inputs.base-ref }}:${{ inputs.snapshot }}" > "$BASE_SNAPSHOT" 2>/dev/null; then
73
+ echo "Found base snapshot at ${{ inputs.base-ref }}:${{ inputs.snapshot }}"
74
+ echo "path=$BASE_SNAPSHOT" >> "$GITHUB_OUTPUT"
75
+ else
76
+ echo "::notice::No base snapshot at ${{ inputs.base-ref }}:${{ inputs.snapshot }} — first run on this branch?"
77
+ echo "path=" >> "$GITHUB_OUTPUT"
78
+ fi
79
+
80
+ - name: Run drift check
81
+ id: run
82
+ shell: bash
83
+ env:
84
+ SNAPSHOT: ${{ inputs.snapshot }}
85
+ CATALOG: ${{ inputs.catalog }}
86
+ BASE_SNAPSHOT_PATH: ${{ steps.base.outputs.path }}
87
+ PACKAGE_VERSION: ${{ inputs.package-version }}
88
+ run: |
89
+ set -euo pipefail
90
+ REPORT="$RUNNER_TEMP/autotel-eventcatalog-drift.md"
91
+ SUMMARY="$RUNNER_TEMP/autotel-eventcatalog-summary.json"
92
+ echo "report-path=$REPORT" >> "$GITHUB_OUTPUT"
93
+
94
+ # Always pass --fail-on-drift internally so the exit code is the
95
+ # authoritative drift signal. The user-facing `fail-on-drift` input
96
+ # only controls whether we re-emit that failure as a workflow
97
+ # failure further down — the action itself needs the signal either
98
+ # way (for the drift-detected output, the sticky comment, etc.).
99
+ POLICY="all"
100
+ ARGS=(--snapshot "$SNAPSHOT" --catalog "$CATALOG" --output "$REPORT" --summary-output "$SUMMARY" --fail-on-drift)
101
+ if [ -n "${BASE_SNAPSHOT_PATH:-}" ]; then
102
+ ARGS+=(--base-snapshot "$BASE_SNAPSHOT_PATH")
103
+ POLICY="new-only"
104
+ fi
105
+ ARGS+=(--policy "$POLICY")
106
+
107
+ # Run the CLI — non-zero exit means drift was detected.
108
+ set +e
109
+ npx --yes "autotel-eventcatalog@$PACKAGE_VERSION" drift "${ARGS[@]}"
110
+ STATUS=$?
111
+ set -e
112
+
113
+ if [ $STATUS -ne 0 ] && [ $STATUS -ne 1 ]; then
114
+ # Anything else (2 = bad args, etc.) is a hard error; surface it.
115
+ echo "::error::autotel-eventcatalog drift CLI failed with exit $STATUS"
116
+ exit $STATUS
117
+ fi
118
+
119
+ if [ ! -f "$SUMMARY" ]; then
120
+ echo "::error::autotel-eventcatalog did not produce a summary output."
121
+ exit 1
122
+ fi
123
+ DRIFT_DETECTED="$(node -e "const fs=require('fs');const s=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write(s.shouldFail ? 'true':'false')" "$SUMMARY")"
124
+ echo "drift-detected=$DRIFT_DETECTED" >> "$GITHUB_OUTPUT"
125
+
126
+ # Always show the report in the job log for easy review.
127
+ echo "::group::Drift report"
128
+ cat "$REPORT"
129
+ echo "::endgroup::"
130
+
131
+ # Sticky-comment action pinned to a specific commit SHA. This is a
132
+ # supply-chain control — a future malicious tag wouldn't affect anyone
133
+ # using this action. Update the SHA deliberately when bumping the
134
+ # external action's version. Currently pinned to v2.9.4 (2024-12-03).
135
+ - name: Post sticky PR comment
136
+ id: comment
137
+ if: ${{ inputs.comment-on-pr == 'true' && github.event_name == 'pull_request' }}
138
+ continue-on-error: ${{ inputs.continue-on-comment-failure == 'true' }}
139
+ uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728
140
+ with:
141
+ header: ${{ inputs.comment-header }}
142
+ path: ${{ steps.run.outputs.report-path }}
143
+
144
+ - name: Warn on comment failure
145
+ if: ${{ steps.comment.outcome == 'failure' }}
146
+ shell: bash
147
+ run: |
148
+ echo "::warning::Could not post the drift report as a PR comment. The drift check itself completed and the report is available at ${{ steps.run.outputs.report-path }}."
149
+
150
+ - name: Fail on drift
151
+ if: ${{ inputs.fail-on-drift == 'true' && steps.run.outputs.drift-detected == 'true' }}
152
+ shell: bash
153
+ run: |
154
+ echo "::error::autotel-eventcatalog detected drift between the catalog and the snapshot."
155
+ exit 1