---
id: 03-b-ast-eslint-rules
epic: conformance-system-v1
title: AST-aware ESLint rules (no-undeclared-event-publish, no-undeclared-audit)
type: technical-story
status: done
feature: core-eslint
depends-on: [03-a-structural-eslint-rules]
blocks: [04-ci-drift-gate]
---

## Goal
Ship two AST-aware rules that catch manifest ↔ use-case drift inside factory bodies:
- `no-undeclared-event-publish`: `bus.publish("X")` in a factory must match `manifest.useCases[name].publishes`
- `no-undeclared-audit`: `auditLog.record({ type: "X" })` must match `manifest.useCases[name].audits`

## Why
Boot assertion + structural rules can't see what happens inside a factory body. AST-aware rules catch publish/audit drift the moment a developer (or agent) saves the file.

## Done when
- Manifest AST parser extracts per-use-case publishes/audits arrays
- Two rules registered in the conformance plugin
- Tests cover positive (declared event) and negative (undeclared event) cases for each rule
- `pnpm lint` passes (auth's signUp has empty publishes/audits today → no false positives)

## In scope
- `_manifest-ast.js` helper using `@typescript-eslint/parser` to extract per-use-case arrays
- `_usecase-name.js` helper (file slug → camelCase use-case key)
- The two rules + RuleTester tests
- Plugin + base.js wiring

## Out of scope
- Bus / auditLog parameter detection beyond the conventional names `bus` and `auditLog`
- Detection of dynamic event names (`bus.publish(eventVar, payload)` is allowed without warning)
- Conditional / nested calls — rules only check top-level CallExpressions in factory bodies

## Tasks
- [x] Story 03.b scaffold
- [x] Manifest AST parser + tests
- [x] Use-case name helper + tests
- [x] `no-undeclared-event-publish` rule + tests
- [x] `no-undeclared-audit` rule + tests
- [x] Plugin update + base.js wiring
- [x] Verify `pnpm lint` passes
- [x] Final verification + story closeout (tick 03 + 03.b in epic)
