What consent receipts must accompany user data through an agentic pipeline?

**Date of Record:** 2026 October 26 **Scope:** Applies when an agentic session processes user data (PII, behavioral data, or content) that requires explicit consent under GDPR, CCPA, or similar regulations, and the data flows through a multi-step pipeline of sub-agents, models, or external services. Does not apply to strictly internal data that the user has not provided, or data already classified as public.

Core Distinction

The separation between **consent collection** (the user clicking "I agree" at the start of a session) and **consent propagation** (the cryptographic proof of that consent traveling with the data to every downstream processor). Relying solely on initial consent creates the **Consent Amnesia Hazard**: the user consented to a specific scope (e.g., "process my data for billing"), but a sub-agent processes the same data for a different scope (e.g., model training), and the violation is invisible because the consent receipt never propagated to the downstream processor.

What Was Observed

In multi-agent data pipelines, failing to propagate consent receipts produces three primary failure modes:

  1. **Scope Creep:** The user consented to "analysis" but a downstream sub-agent uses the data for "advertising." Both operations look identical at the data level, but only one is authorized.
  2. **Third-Party Leakage:** The data is passed to a third-party API (e.g., an LLM provider). Without a consent receipt restricting third-party sharing, the pipeline violates the user's consent even if the primary processor was authorized.
  3. **Consent Revocation Blindness:** The user revokes consent. But the data has already been copied to five sub-agents, none of which receive the revocation notification because they are not bound to the consent system.

Recorded Elements

  1. **`consent_receipt_signature`:** The cryptographic signature binding the user's identity to the specific consent scope (purpose, duration, third-party sharing allowed).
  2. **`consent_scope_hash`:** The hash of the exact purpose strings, data categories, and recipient types the consent covers, preventing interpretation drift.
  3. **`revocation_propagation_receipt`:** The proof that the consent receipt was broadcast to all downstream processors, allowing revocation to propagate.
  4. **`consent_verification_chain`:** The chain of signatures from the original consent issuer through every processor, proving the receipt was not tampered with.

Condition of Validity

* **Applies when:** The user's data flows through multiple processors (sub-agents, LLM providers, third-party APIs), and consent must be enforced at every step, not just at initial collection. * **Does not apply when:** The data is strictly anonymous, the pipeline is contained within a single processor, or the data is explicitly exempt from consent requirements (e.g., security logs under a legal obligation).

Decision Boundary

  1. **Is a valid `consent_receipt_signature` present?** If no, the data has not been legitimately consented for any processing. The instance must halt and re-collect consent.
  2. **Does the current processing scope fall within the `consent_scope_hash`?** If no, the processing is out of scope. The instance must reject the operation or re-request consent.
  3. **Has the `revocation_propagation_receipt` been recorded?** If no, the consent state is stale, and any revocation may not have propagated. The instance must verify consent freshness.
  4. **Conversely, when can a processor proceed without verifying consent?** Only when the operation is strictly necessary for the performance of a contract the user has already entered into, or the processing is strictly for the detection of security threats (e.g., fraud detection under a legal basis).

Operational Effect

  1. **Scope Enforcement:** Prevents downstream processors from using data outside the scope the user consented to.
  2. **Revocation Propagation:** Ensures consent revocations reach every sub-agent, preventing data from lingering in unauthorized systems.
  3. **Audit Trail:** Provides a cryptographic chain of custody for consent, satisfying regulatory audit requirements.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class ConsentVerdict(Enum):
    CONSENT_VALID = auto()
    SCOPE_VIOLATION = auto()
    CONSENT_MISSING = auto()
    STALE_CONSENT = auto()

@dataclass(frozen=True)
class ConsentReceipt:
    user_id_hash: str
    consent_scope_hash: str
    receipt_signature: str
    issued_epoch: float
    revocation_propagated: bool

@dataclass(frozen=True)
class ProcessingScope:
    purpose: str
    allowed_purposes: frozenset[str]
    current_epoch: float
    max_consent_age_sec: float

@dataclass(frozen=True)
class ConsentManifest:
    verdict: ConsentVerdict
    directive: str
    manifest_digest: str

class ConsentPropagationGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_processing(
        cls, receipt: ConsentReceipt, scope: ProcessingScope
    ) -> ConsentManifest:
        
        # Gate 1: Receipt presence
        if not receipt.receipt_signature or not receipt.consent_scope_hash:
            return ConsentManifest(
                verdict=ConsentVerdict.CONSENT_MISSING,
                directive="HALT: Consent receipt missing or unsigned.",
                manifest_digest=cls._hash("NO_RECEIPT")
            )

        # Gate 2: Scope check
        if scope.purpose not in scope.allowed_purposes:
            return ConsentManifest(
                verdict=ConsentVerdict.SCOPE_VIOLATION,
                directive=f"BLOCKED: Purpose {scope.purpose} not in allowed set.",
                manifest_digest=cls._hash(f"SCOPE:{scope.purpose}")
            )

        # Gate 3: Freshness check
        age = scope.current_epoch - receipt.issued_epoch
        if age > scope.max_consent_age_sec:
            return ConsentManifest(
                verdict=ConsentVerdict.STALE_CONSENT,
                directive=f"HALT: Consent is {age:.0f}s old, exceeding {scope.max_consent_age_sec:.0f}s limit.",
                manifest_digest=cls._hash(f"STALE:{age:.0f}")
            )

        # Gate 4: Revocation propagation
        if not receipt.revocation_propagated:
            return ConsentManifest(
                verdict=ConsentVerdict.STALE_CONSENT,
                directive="HALT: Revocation propagation receipt missing. Cannot verify current consent state.",
                manifest_digest=cls._hash("NO_REVOCATION_RECEIPT")
            )

        # Gate 5: Valid
        digest = cls._hash(json.dumps({
            "user": receipt.user_id_hash,
            "scope": receipt.consent_scope_hash,
            "signature": receipt.receipt_signature,
            "purpose": scope.purpose
        }, sort_keys=True))
        
        return ConsentManifest(
            verdict=ConsentVerdict.CONSENT_VALID,
            directive="PROCEED: Consent receipt valid for this processing scope.",
            manifest_digest=digest
        )