Skip to content

Phase 2 follow-up -- couple Validator strict_legal_basis to strict parse + vendor prefetch (narrows validate() override surface) #122

@peczenyj

Description

@peczenyj

Why this matters

GDPR::IAB::TCFv2::Validator currently calls
GDPR::IAB::TCFv2->Parse($input) unconditionally
(lib/GDPR/IAB/TCFv2/Validator.pm, around line 99). That means:

  • Correctness gap. When the caller sets strict_legal_basis => 1,
    the validator asserts the stricter spec semantics on the consent
    bits, but the parser still happily accepts a v1 string or a v2.3
    string missing the Disclosed Vendors segment. A malformed input can
    reach the legal-basis check and produce a misleading verdict
    instead of being rejected up front.
  • Performance gap. strict_legal_basis checks a specific vendor's
    permissions across multiple purposes. For range-encoded TC strings,
    each is_vendor_consent_allowed / is_vendor_legitimate_interest_allowed
    call walks the range list O(N). Parse(..., prefetch => [ $vendor_id ])
    pre-walks the ranges once and caches the answer — see
    lib/GDPR/IAB/TCFv2.pm's Parse() and
    lib/GDPR/IAB/TCFv2/RangeSection.pm.

Goal

Have validate / validate_all build their parsed object with
strict => 1, prefetch => [ $vendor_id ] whenever appropriate, so a
single Validator instance gives both the spec-tight rejection
and the O(1) hot-vendor lookup users implicitly expect from
strict_legal_basis.

This is only possible if vendor_id, strict_legal_basis, and
cmp_validator are known at construction time. Today they can be
overridden per call via %overrides, which makes the parse-time
decision ambiguous: which vendor do you prefetch? which strictness do
you parse under? So this issue also narrows the per-call override
surface
to the three purpose-id lists.

Scope

Narrow validate / validate_all overrides

  • Allowed %overrides keys become: consent_purpose_ids,
    legitimate_interest_purpose_ids, flexible_purpose_ids.
  • Drop per-call support for: vendor_id, strict_legal_basis,
    verify_disclosed_vendors, min_tcf_policy_version, cmp_validator.
    These become constructor-only.
  • Unrecognized keys must croak with an explicit message — silent
    no-op would let existing callers lose their override on upgrade.

Couple strict_legal_basis to parser flags

  • When the input is a raw $tc_string, parse with
    strict => $self->{strict_legal_basis} ? 1 : 0, prefetch => [ $self->{vendor_id} ].
  • When the input is a pre-parsed GDPR::IAB::TCFv2 object, the
    strict/prefetch decision was the caller's; the validator cannot
    retro-apply it. Document this contract in the POD.

Tests (t/06-validator.t, t/14-cmp-validator.t)

  • Regression: a non-strict-rejected TC string (e.g. a version != 2
    fixture) passed with strict_legal_basis true must produce a
    parse-time croak, not a silent validation pass.
  • Range-encoded fixture to confirm prefetch is honored (assertion
    shape TBD — likely just a sanity check that the call succeeds and
    the parsed object's vendor cache is populated).
  • New regression: $validator->validate($tc, vendor_id => $other)
    must croak. Replaces existing per-call vendor_id tests
    (t/06-validator.t lines 63, 74, 120).
  • Same migration for the per-call strict_legal_basis test
    (t/06-validator.t:260) and per-call cmp_validator test
    (t/14-cmp-validator.t:156).

Documentation

  • lib/GDPR/IAB/TCFv2/Validator.pm:
    • Update the strict_legal_basis POD entry to spell out the
      coupling (strict parse + prefetch).
    • Update the validate / validate_all POD to reflect the
      narrowed override surface.
    • Document the pre-parsed-object escape hatch for "parse once,
      validate many vendors" — that pattern moves out of the override
      machinery and into the documented $tc_string_or_object API.
  • CHANGELOG.md / release notes: add a MIGRATING section
    covering the breaking change.

Out of scope

  • Adding new constructor knobs to control parse flags directly —
    the coupling is structural, not an extra surface.
  • Auto-detecting whether a pre-parsed object was constructed with
    strict / prefetch. Possible future enhancement under an
    opt-in flag, but not part of this issue.

Open questions (TBD)

  1. Pre-parsed object guard rails. Should the validator
    warn (carp) when it sees a pre-parsed object built without
    strict/prefetch while strict_legal_basis is set? Default off,
    opt-in flag, or leave silent?
  2. prefetch shape. Parse accepts prefetch => [ ids... ];
    single-vendor case is [ $vendor_id ]. Confirm the array form is
    the only supported shape today (vs. a bare scalar) before
    committing to it in the validator.
  3. Breaking-change vehicle. Ship this in the next minor or hold
    for a major bump? The narrowed override surface is unambiguously
    breaking; the strict+prefetch coupling on its own is not. The
    tracked memory and roadmap currently assume both ship together
    under a major bump.

Definition of done

  • validate / validate_all accept only the three purpose-id keys
    as overrides; everything else croaks.
  • The raw-string path parses with the coupled flags; the
    pre-parsed-object path is documented as caller-owned.
  • t/06-validator.t and t/14-cmp-validator.t cover the new
    croak-on-unknown-key behavior and the strict+prefetch regression.
  • lib/GDPR/IAB/TCFv2/Validator.pm POD reflects the new contract.
  • CHANGELOG.md carries a MIGRATING note.

References

  • Roadmap entry: TODO.pod, "Phase 2 follow-up -- couple Validator
    strict_legal_basis to strict parse + vendor prefetch, narrow
    per-call overrides to purpose lists"
  • Current code: lib/GDPR/IAB/TCFv2/Validator.pm, especially _run_validation (the unconditional Parse at ~line 99) and the
    per-call override resolution that follows it.
  • Related: lib/GDPR/IAB/TCFv2.pm Parse() (strict and prefetch
    named arguments) and lib/GDPR/IAB/TCFv2/RangeSection.pm (where
    the prefetch cache lives).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is neededroadmapPlanned roadmap items now open for community contributionvalidatorDeclarative validator (lib/GDPR/IAB/TCFv2/Validator/*)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions