---
id: 04-ci-drift-gate
epic: conformance-system-v1
title: CI drift gate — pnpm conformance with cross-feature event closure
type: technical-story
status: done
feature: scripts
depends-on: [03-b-ast-eslint-rules]
blocks: [05-generator-updates]
---

## Goal
`pnpm conformance` aggregates cross-feature checks that no single-file
ESLint rule can perform — most importantly, event closure: every event
declared in any manifest's `consumes` must have at least one matching
`publishes` somewhere in the repo.

## Why
Per-file lint can't see cross-feature contracts. Without this gate, a
feature can declare it consumes `X` while no feature publishes `X` —
silent until the broken handler is exercised in prod.

## Done when
- `pnpm conformance` exits 0 when manifests are consistent; non-zero
  with a clear error message on orphan consumers
- Wired into `turbo.json` as the `conformance` task
- Wired into `.github/workflows/ci.yml` after `pnpm lint`

## In scope
- `scripts/conformance.mjs` — orphan-consumer check
- Tests via vitest
- turbo.json + CI wiring

## Out of scope
- Scaffold drift check (regenerate via `turbo gen feature`, diff against
  on-disk state) — depends on generator updates landing
- Repository write outside use-cases check — separate concern
- Reverse check (orphan publishers — events nothing consumes) — many
  events are intentionally "fire and forget"; not a closure violation

## Tasks
- [x] Story scaffold
- [x] `scripts/conformance.mjs` implementation
- [x] Tests for the script
- [x] Wire into root package.json + turbo.json
- [x] Wire into ci.yml
- [x] Final verification + closeout
