---
id: 03-core-consent-foundation
epic: dsr-consent-and-cookie-banner
title: core-consent foundation — types, brand, withConsent wrapper, conformance extension, ESLint rule
type: technical-story
status: done
feature: core-consent
depends-on: [02-audit-enum-amendment]
blocks: [04-core-consent-implementation]
created: 2026-05-19T12:00:00Z
updated: 2026-05-19T11:54:41.015Z
---

## Goal

Scaffold `@repo/core-consent`, define the public type surface (`ConsentCategory`, `IConsent`), establish the `ConsentChecked` brand + `withConsent` wrapper, wire the `requiresConsent` manifest field into `assertFeatureConformance`, and add the `no-undeclared-consent-check` ESLint rule — the full conformance-layer foundation that all subsequent consent implementation and use-case integration depends on.

## Why

Manifest-first ordering requires that the consent type surface and structural enforcement land before any runtime implementation. The `ConsentChecked` brand must exist in `core-shared/conformance/brands.ts` before `assertFeatureConformance` can check it; the `requiresConsent` manifest field must be schema-valid before any feature can declare it; the ESLint rule must exist before use cases can be lint-gated. Landing both tasks in this story keeps the conformance layer coherent before any optional-core impl lands.

## Done when

- `pnpm turbo gen core-package consent` produces a green package shell.
- `core-consent/consent-types.ts` exports `ConsentCategory`, `ConsentState`, `UserConsentState`.
- `core-consent/consent.interface.ts` exports `IConsent`.
- `core-shared/conformance/brands.ts` exports `ConsentChecked` brand + `isConsentChecked` helper.
- `core-consent/with-consent.ts` exports `withConsent` wrapper attaching `ConsentChecked` brand at bind time; unit tests assert brand is attached and factory passthrough is preserved.
- Feature manifest schema gains `requiresConsent: ConsentCategory[]` (default `[]`); existing features declare it as empty array without errors.
- `assertFeatureConformance` boot check requires `ConsentChecked` brand when `requiresConsent.length > 0`; synthetic fixture test asserts failure when brand is absent.
- `core-eslint/rules/no-undeclared-consent-check.js` is registered at warn severity; `_manifest-ast.js` extracts `requiresConsent` field; RuleTester fixtures cover: matching call (pass), undeclared category in call site (warn), unused manifest declaration (warn), non-use-case file (no-op).
- Conformance ESLint rule count in CLAUDE.md and `docs/guides/conformance-quickref.md` advances from 11 → 12.
- `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff` all pass after each task.

## In scope

- `pnpm turbo gen core-package consent` scaffold.
- `ConsentCategory` (string-literal union with escape hatch), `ConsentState`, `UserConsentState` types.
- `IConsent` interface (`isGranted`, `grant`, `withdraw`, `getCategories`).
- `ConsentChecked` brand in `core-shared/conformance/brands.ts`.
- `withConsent` wrapper (composes innermost — order: `withSpan → withCapture → withAudit → withAnalytics → withConsent → factory(deps)`).
- `requiresConsent: ConsentCategory[]` manifest field (schema + default).
- `assertFeatureConformance` extension for `ConsentChecked` brand check.
- `no-undeclared-consent-check` ESLint rule + `_manifest-ast.js` `requiresConsent` extraction.
- CLAUDE.md + `docs/guides/conformance-quickref.md` rule-count bump (11 → 12).

## Out of scope

- `PayloadConsent` Payload-backed implementation (Story 04).
- DI binders, handlers, tRPC router (Story 04).
- React subpath (Story 05).
- Anonymous migration helpers (Story 04).

## Tasks

- [x] Run `pnpm turbo gen core-package consent` + add `ConsentCategory`, `ConsentState`, `UserConsentState` types in `core-consent/consent-types.ts` + `IConsent` interface in `core-consent/consent.interface.ts` + `ConsentChecked` brand + `isConsentChecked` helper in `core-shared/conformance/brands.ts` + `withConsent` wrapper in `core-consent/with-consent.ts` with unit tests asserting brand attachment and factory passthrough + `requiresConsent: ConsentCategory[]` field in the feature manifest schema (default `[]`) + extend `assertFeatureConformance` to require `ConsentChecked` brand when `requiresConsent.length > 0` with a synthetic fixture test asserting the boot failure; all gates pass.
- [x] Add `no-undeclared-consent-check` ESLint rule (warn severity) in `packages/core-eslint/rules/` + extend `_manifest-ast.js` to extract `requiresConsent` + add RuleTester fixtures; update CLAUDE.md and `docs/guides/conformance-quickref.md` rule count 11 → 12; all gates pass.
