Consent is a record with a timestamp, not a boolean

A marketing_opt_in column stores the current answer and nothing else, and the question that gets asked is always “when, and to what exactly did they agree”.

CREATE TABLE consents (
  id            BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id       BIGINT NOT NULL,
  purpose       VARCHAR(64) NOT NULL,   -- 'marketing_email', 'analytics'
  granted       TINYINT(1) NOT NULL,
  policy_version VARCHAR(16) NOT NULL,
  source        VARCHAR(64) NOT NULL,   -- 'signup_form', 'preferences'
  recorded_at   DATETIME NOT NULL,
  INDEX (user_id, purpose, recorded_at)
);

Append-only is the point: a withdrawal is a new row rather than an update, so the history survives. The policy_version column is what lets you answer whether a given consent still covers what you are doing now, which is the question that arrives after a wording change. Reading the current state is a query for the latest row per purpose, which is exactly what ROW_NUMBER is for.