# ADR-0003: Consent flow uses synchronous check with 5-minute cache TTL; check at entry point only

- **Date:** 2025-11-12
- **Status:** accepted
- **Reversibility:** medium-low (changing the cache TTL or moving to async would require Compliance Engineering re-approval; sync-with-cache is the pattern Compliance signed off on)
- **Deciders:** {{compliance_eng_lead}} (Compliance Engineering), RevOps-AI eng lead, {{platform_eng_lead}} (Platform Engineering, for cache infrastructure approval), Privacy Counsel (legal review)
- **Scope:** instance (also affects how downstream platforms consume consent state)

## Context

L2 philosophy Red Line 1 prohibits enriching MQLs with revoked consent (`policies/philosophy.md`). The implementation has multiple credible patterns:

- **Sync check, no cache:** check consent-service on every enrichment call; freshest data, no propagation delay; latency cost ~110ms p99 per call; consent-service load scales with enrichment volume
- **Sync check with TTL cache:** check on first call, cache result for some TTL; reduces consent-service load and latency; introduces propagation delay equal to TTL
- **Async eventual-consistency:** subscribe to consent-revocation events; treat consent as granted unless we have a known revocation; lowest latency; longest worst-case propagation delay

Plus a question of where to check: at every platform that touches PII (defense-in-depth) or only at the entry point (mql-enrichment-service) with downstream platforms reading the result from the enrichment record.

Upstream constraints:

- GDPR Art. 7 + 17: consent must be revocable in a timely manner; no specific TTL is mandated, but "reasonable" propagation is required and audited under SOC 2
- L3 framework contract: per-call cost ceiling $0.08; consent-service costs add to the budget
- Consent-service current SLA: p99 110ms per call; propagation of revocation events to subscribers under 30 seconds

## Decision

Synchronous check with 5-minute cache TTL, performed at the entry point (mql-enrichment-service) only. Downstream platforms (mql-router, sdr-handoff-helper) read `consent_check_result` from the enrichment record produced by mql-enrichment-service; they do not re-check.

The 5-minute TTL is invalidated proactively by subscribing to the `consent-revocations` topic; cache entries for revoked subjects are evicted on event receipt, typically within 30 seconds.

The output guardrail at mql-enrichment-service re-checks before write to handle the rare in-flight case where revocation lands during enrichment processing.

## Options considered

### Option A: Sync with 5-minute TTL cache + revocation-event invalidation, entry-point-only check [CHOSEN]

- **Summary:** Cache hit avoids consent-service call; cache miss does sync check; revocation events evict affected entries; downstream platforms read result from enrichment record
- **Pros:** Lowest sustained consent-service load; revocation propagates within ~30 seconds (not full TTL); single check at entry point is consistent (no race conditions between platforms); satisfies GDPR Art. 7 with propagation under 30 sec average, 5 min worst case
- **Cons:** Cache infrastructure adds operational complexity (Redis dependency); cache invalidation has its own failure mode (event subscription must be live); if revocation event fails to deliver, worst case is 5-minute stale cache

### Option B: Sync, no cache, entry-point check

- **Summary:** Every enrichment call hits consent-service; no cache; downstream reads result from enrichment record
- **Pros:** Freshest data; no cache invalidation surface; simpler
- **Cons:** Consent-service load scales 1:1 with enrichment volume (~12K MQLs / quarter; consent-service capacity adequate but headroom thin); per-call latency budget tightens by ~100ms (still feasible against $0.08 + 1.2s budgets but reduces margin)

### Option C: Async eventual consistency, defense-in-depth check at every platform

- **Summary:** Subscribe to consent events; treat consent as granted by default; every platform checks its own subscription state; revocation reaches downstream platforms via event
- **Pros:** Lowest latency per call (no consent-service round trip); defense-in-depth at multiple layers
- **Cons:** Worst-case propagation could exceed 1 minute if event subscription lags or temporarily fails; defense-in-depth check at every platform increases cost and complexity without strong benefit (Option A's single-entry-point check plus output-guardrail re-check covers the in-flight case adequately); Privacy Counsel preferred a positive-confirmation-of-consent model over presume-granted

## Rationale

Option C was rejected by Privacy Counsel on the presume-granted concern; under GDPR Art. 7, consent must be a positive act, and a system that processes by default until told otherwise creates audit trail concerns. Option B is feasible but consumes more consent-service capacity than necessary; given Option A's propagation guarantees (under 30 seconds typical), the freshness benefit of Option B does not justify the cost.

Option A balances all constraints: GDPR-compliant propagation, manageable consent-service load, in-flight-case coverage via output guardrail re-check, single-source-of-truth at entry point. Compliance Engineering and Privacy Counsel signed off on the 5-minute TTL given the proactive revocation-event invalidation.

Tradeoff explicitly accepted: 5-minute worst-case stale cache (vs. 0 in Option B; vs. potentially-longer in Option C). Mitigation: revocation-event subscription is monitored as a P1 alert; failure to receive events for > 5 minutes triggers immediate cache flush.

## Consequences

### Positive

- Per-call latency budget preserved; consent check averages ~18ms (cache hit) vs. ~110ms (cache miss)
- Consent-service load reduced by ~87% vs. Option B
- Revocation propagation typical < 30 seconds, worst case 5 minutes
- Single check at entry point eliminates race conditions between platforms
- Output guardrail re-check covers in-flight case

### Negative

- Cache infrastructure (Redis) operational complexity
- Cache invalidation depends on `consent-revocations` topic subscription staying live; subscription health is itself a monitored concern
- Compliance audit must verify the propagation discipline annually; SOC 2 control evidence captured per cycle

### Followups

- Implement cache infrastructure on `{{platform_team_name}}/ai-kit/cache` (already exists; not building new)
- Subscribe to `consent-revocations` topic; emit cache-invalidation event handler
- Quarterly revocation-propagation drill (first drill happened 2026-04-22; resulted in incident response; see `sessions/2026-04-22-consent-flow-incident.md`)
- Output guardrail re-check implementation in `kit/guardrails/consent-output-recheck` (mirrored from {{platform_team_name}}/ai-kit)
- SOC 2 control evidence: capture annual; first capture during 2026-Q3 audit cycle

## Reproducibility note

This decision rests on:

- Consent-service current SLA: p99 110ms per call; propagation of revocation events under 30 seconds (verified 2025-11-08; re-verified 2026-04-22 during incident response)
- Privacy Counsel review notes 2025-11-10 (Confluence > `revops-ai/compliance-reviews/2025-11`)
- Estimate of enrichment volume: ~12K MQLs / quarter trajectory (FY26 plan); cache hit rate 87% derived from this volume

If consent-service SLA degrades or enrichment volume changes by 2x+, re-evaluation is warranted. Re-evaluation produces a new ADR.

## Cross-references

- Layer this decision affects: L2 (philosophy Red Line 1 enforcement), L7 (consent flow concern)
- Related ADRs: ADR-0001 (build); ADR-0002 (kit mirror)
- `cross-platform/consent-flow.md`
- `policies/philosophy.md` Red Line 1
- `policies/framework-contract.md` input + output guardrail layers
- `sessions/2026-04-22-consent-flow-incident.md` (post-decision drill that exercised this)
