NAME
TODO - Roadmap, ecosystem, and help-wanted items for GDPR::IAB::TCFv2
STATUS
GDPR-IAB-TCFv2 entered maintenance mode on 2026-05-09 with the v0.400 release. The core parser, validator, and CMP-validator surfaces are considered feature-complete for IAB TCF v2.3.
In maintenance mode the maintainer commits to:
bug fixes,
security fixes,
CPAN-tester regression triage,
tracking IAB-spec updates (TCF v2.4 / v3 if and when they ship).
Larger feature work -- the three remaining roadmap phases below, the distribution items, and the sister-distribution ideas -- is now tracked as help-wanted issues on GitHub. Patches and PRs from the community are still welcome and will continue to be reviewed.
COMPLETED PHASES
The phases below shipped between v0.270 and v0.400. They are listed here for historical context only; details live in CHANGELOG.md and the linked PRs.
- Phase 0 -- Core legal-basis predicates
-
is_vendor_consent_allowed,is_vendor_legitimate_interest_allowed,is_vendor_allowed_for_flexible_purpose, plusstrictmode. - Phase 1 -- TCF v2.3 segments and parser robustness
-
Disclosed Vendors / Allowed Vendors segments, multi-segment safety,
has_vendor_disclosure,has_publisher_restrictions, thevendor_idfilter onTO_JSON, and the automated release workflow. - Phase 2 -- Declarative validator interface
-
GDPR::IAB::TCFv2::Validatorwithvalidate/validate_allandValidator::Result. - Phase 3 -- Alignment and cleanup
-
Purpose names normalized to TCF v2.3 wording; POD streamlined.
- Phase 4 -- Performance pass
-
Bitfield decode optimizations and golden-corpus benchmarks.
- Phase 5 -- CMP validator
-
GDPR::IAB::TCFv2::CMPValidatorwith file / JSON / URL loading, staleness warning, and integration into the main validator as a rule. - Phase 6 -- Structured failure reporting
-
Five sub-phases (6.1 -- 6.5) delivering
Validator::Reasoncodes,Validator::Failureobjects, the TCF LI carve-out, distinct publisher-restriction reasons, and per-call list overrides.
HELP WANTED -- ROADMAP PHASES
Three roadmap phases were originally planned for inclusion in this distribution but are now open for community contribution. Each is self-contained and can ship as an independent PR; pick one and open (or claim) the corresponding help-wanted + roadmap issue on GitHub.
Phase 7 -- GVL-Aware Validator
Bridge the IAB Global Vendor List schema to the Phase 2 validator so callers do not have to translate vendor entries by hand. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/77
Scope:
from_gvl_vendor_entry($vendor_entry)-- accept a single GVL vendor entry ({ id, purposes, legIntPurposes, flexiblePurposes }) and return the keyword-argument list expected byValidator->new.from_gvl($gvl_doc, $vendor_id)-- look up the vendor in a parsed GVL document and return a fully configuredValidator; fail fast if the vendor ID is missing.Flexible GVL input: file path, raw JSON string, or pre-parsed hashref.
CLI integration:
iabtcfv2 validate --gvl path/to/gvl.json -v 32 ...should derive-C/-L/-Ffrom the GVL entry.Tests: golden GVL fixture covering vendors with and without flexible purposes; round-trip
from_gvl_vendor_entryagainst a hand-crafted entry.Freshness contract: the GVL loader must accept the
max_age_days/on_staleknobs defined in "Phase 11 -- Registry freshness policy and CMP-list hot-reload". Coordinate the default value with that issue.
Phase 8 -- Features, Special Features, Special Purposes
Extend the validator beyond standard purposes to cover the rest of the TCF taxonomy. Should consume the REASON_* codes introduced in Phase 6. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/78
Scope:
Validator support for Special Features (opt-in, e.g. precise geolocation): require the bit in the TC string when listed.
Validator support for Features (vendor-declared): no consent required, but cross-check the vendor's GVL declaration once Phase 7 lands.
Validator support for Special Purposes: legitimate-interest-only by spec; check vendor declaration without requiring a consent bit.
Surface on the CLI as
--special-features/--features/--special-purposes(comma-separated, same shape as-C/-L).Tests: extend t/06-validator.t with subtests per category; extend t/10-cli-iabtcfv2.t with CLI subtests.
Phase 9 -- CLI Configuration Loading
Reduce CLI boilerplate by letting common flags come from the environment or a config file. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/79
Scope:
Map a curated set of env vars to CLI flags:
IABTCFV2_VENDOR_ID,IABTCFV2_CONSENT_PURPOSES,IABTCFV2_LEGITIMATE_INTEREST_PURPOSES,IABTCFV2_FLEXIBLE_PURPOSES,IABTCFV2_MIN_POLICY_VERSION. Explicit CLI flags always win.Optional config-file discovery: .iabtcfv2rc in
$PWDor$HOME(plus.env-style loading if present). Documented precedence: CLI > env > file > built-in defaults.iabtcfv2 config(orvalidate --print-config) to dump the resolved configuration as JSON for debugging.**Tests:** a CLI subtest that sets the env vars, runs
validatewithout the matching flags, and asserts the same outcome as the explicit invocation.
Phase 10 -- Advanced Error Handling
Modernize exception handling across the library. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/104
Scope:
Implement
GDPR::IAB::TCFv2::Errorexception objects.Overload stringification to provide clean messages while retaining metadata (code, file, line).
Refactor
croak/diecalls in the library to throw objects.Update CLI to catch objects and simplify its sanitization logic.
Phase 11 -- Registry freshness policy and CMP-list hot-reload
Make CMP list and GVL freshness configurable, symmetric, and opt-in-strict, and add a hot-reload capability to CMPValidator so long-running services can refresh their snapshot without restarting. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/114
Background:
CMPValidator currently emits a hardcoded carp when its snapshot is older than 28 days (see _check_age). The threshold is not configurable, there is no escalation to croak, and the GVL pipeline introduced in Phase 7 has no equivalent freshness contract. This Phase makes the policy a first-class API surface on both registries.
Scope:
Add
max_age_daysandon_staleconstructor arguments toCMPValidator.on_staleaccepts'warn'(current behavior, default),'croak', or'ignore'. Defaults match v0.400 exactly:max_age_days => 28,on_stale => 'warn'. Configurability is the new feature; no v0.400 user sees a behavior change.Add the same two arguments to whatever entry point Phase 7 introduces for GVL loading (e.g.
from_gvl_file,from_gvl_url). Defaults should likewise be conservative -- TBD per Phase 7's eventual shape; the issue records the contract that GVL must honor an identically-named knob even if the default value differs.Freshness clock measures the
lastUpdatedfield inside the JSON payload. Filesystem mtime is intentionally not consulted (it lies acrosscp -p, container rebuilds, and image bakes; the IABlastUpdatedfield is the canonical contract).Hot-reload (bonus, CMP only):
$cmp_validator->reload()-- explicit, caller-driven, parameterless. Reuses the source(s) the validator was constructed with.reload_on_stale => 1-- when set andnetwork_ok => 1,_check_ageauto-triggersreload()instead of justcarp/croaking. Off by default.No background/timer-based refresh. Perl is single-threaded and this distribution will not introduce scheduling primitives.
Source fallback: the constructor accepts
url+filesimultaneously (today they are mutually exclusive). When both are present,reload()triesurlfirst, falls back tofileon network failure. This is also the behavior at first construction when both are passed.
Tests: t/14-cmp-validator.t grows subtests for each
on_stalebranch, for the URL-then-file fallback, forreload()against a stub HTTP client, and for thereload_on_staleintegration. A new test file covers GVL freshness once Phase 7 lands.Documentation: update
CMPValidatorPOD to describe the knobs and thelastUpdatedcontract; document that the existing 28-day default is preserved.
CLI surface (env vars / flags) is deliberately out of scope for Phase 11 and tracked under "Phase 9 -- CLI Configuration Loading". Phase 9 will pick up IABTCFV2_CMP_LIST_MAX_AGE / IABTCFV2_CMP_LIST_ON_STALE (and GVL counterparts) once Phase 11 lands.
Open questions (TBD, deferred to whoever picks the work up):
Exact behavior of
reload()on failure: croak vs return false vs return previous-state-with-warning?Loop-prevention semantics for
reload_on_staleif upstream is persistently unreachable -- minimum back-off interval, max retry count, or stateless "try once per validation"?Whether
reload_on_staleshould be a no-op (rather than croak) when nourlwas configured -- file-only setups can never auto- reload from the network.GVL-side default for
max_age_days: GVL updates more frequently than the CMP list, so 28 days may be too lax. Pin once Phase 7's loading API is finalized.
Phase 12 -- Modernization
Once all previous functional phases are complete, the library will transition to a modern Perl baseline, dropping support for legacy Perl versions and adopting industry-standard dependencies.
Scope:
Phase 12.1a -- Perl 5.10 Baseline. Update
MIN_PERL_VERSIONto 5.010000. Adoptuse v5.10;everywhere, enablingstatevariables and the defined-or operator (//). Remove 32-bit/Math::BigInt fallbacks and manual bit-manipulation hacks that target ancient Perls. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/131Phase 12.1b -- Perl 5.12 Baseline. Update
MIN_PERL_VERSIONto 5.012000. Adoptuse v5.12;everywhere, implicitly enablingstrict. Modernize inheritance withuse parentinstead ofuse base. Adopt the modern package declaration syntax (package Name VERSION;). See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/132Phase 12.2 -- Modern OO and Dependencies. Replace
bless-based hashes with Moo or Object::Pad. Use Types::Standard for attribute validation. Adopt JSON::MaybeXS for transparent performance. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/125Phase 12.3 -- Dist::Zilla. Migrate from
ExtUtils::MakeMakertoDist::Zilla. Automate versioning, manifest management, and release workflows. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/126
Phase 2 follow-up -- couple Validator strict_legal_basis to strict parse + vendor prefetch, narrow per-call overrides to purpose lists
When GDPR::IAB::TCFv2::Validator is invoked with strict_legal_basis and given a raw $tc_string, its internal call to GDPR::IAB::TCFv2->Parse should pass strict => 1, prefetch => $vendor_id rather than parsing in default (non-strict) mode without prefetch. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/122
This coupling only works cleanly if vendor_id, strict_legal_basis, and cmp_validator are fixed at construction -- per-call overrides of those keys make the parse-time decision ambiguous (which vendor do you prefetch? which strictness do you parse under?), so this issue also narrows the validate / validate_all override surface to the three purpose-id lists as a precondition.
Three coupled motivations:
Correctness.
strict_legal_basisalready asserts the stricter spec semantics; pairing it with the parser'sstrictmode rejects malformed or wrong-version TC strings up front instead of letting them slip into the legal-basis check. Without strict parse, a v1 string -- or a v2.3 string missing the Disclosed Vendors segment -- can reach the validator and produce a misleading verdict.Performance.
strict_legal_basisalways checks a specific vendor's permissions across multiple purposes. For range-encoded TC strings, repeatedvendor_consent/vendor_legitimate_interestcalls walk the range list O(N) each. Passingprefetch => $vendor_idtoParsepre-walks the ranges once and caches answers (see lib/GDPR/IAB/TCFv2.pmParse()and lib/GDPR/IAB/TCFv2/RangeSection.pm).API coherence. The current per-call override surface conflates "properties of the policy" (vendor identity, strictness, CMP rule, policy floor, disclosed-vendors check) with "properties of the check" (purpose lists). Allowing the former to vary per call defeats the strict+prefetch optimization (the prefetched vendor isn't the one being checked) and muddies the API contract.
Scope:
Narrow
validate/validate_alloverrides to the three purpose-id lists (consent_purpose_ids,legitimate_interest_purpose_ids,flexible_purpose_ids). Drop per-call support forvendor_id,strict_legal_basis,verify_disclosed_vendors,min_tcf_policy_version, andcmp_validator-- those become constructor-only. Unrecognized keys shouldcroakwith an explicit message rather than silently no-op, so existing callers learn at upgrade time instead of silently losing their override.In
validate/validate_all, when the input is a raw$tc_string, parse withstrict => $self->{strict_legal_basis} ? 1 : 0, prefetch => [ $self->{vendor_id} ]. Today the call site is unconditional:GDPR::IAB::TCFv2->Parse($input)(see lib/GDPR/IAB/TCFv2/Validator.pm, around line 99).When
validate/validate_allreceives a pre-parsedGDPR::IAB::TCFv2object, the strict/prefetch decision was already made by the caller; the validator cannot retro-apply it. Document the caller contract in the POD for bothstrict_legal_basisand the$tc_string_or_objectargument, and treat the pre-parsed-object path as the documented escape hatch for the "parse once, validate many vendors" pattern.Tests: extend t/06-validator.t with (a) a regression covering a non-strict-rejected TC string (e.g. a
version != 2fixture) passed withstrict_legal_basistrue -- expect a parse-time croak, not a silent validation pass; (b) a range-encoded fixture to confirm prefetch is honored; (c) a regression asserting thatvalidate($tc, vendor_id => $other)nowcroaks, replacing the existing per-callvendor_idtests (currently at lines 63, 74, 120). Same migration applies to thestrict_legal_basisper-call test (line 260) and thecmp_validatorper-call test in t/14-cmp-validator.t (line 156).Documentation: update the
strict_legal_basisPOD entry in lib/GDPR/IAB/TCFv2/Validator.pm to spell out the coupling, and update thevalidate/validate_alloverride list in the POD to reflect the narrowed surface. Add a short MIGRATING section to theChanges/release notes covering the breaking change.
Backwards-compatibility note: this is a breaking change to the validate / validate_all contract and ships under a major version bump. The "one Validator policy, many vendors" pattern documented today (t/06-validator.t:120) becomes "parse the TC string once, construct one Validator per vendor, pass the parsed object in" -- both documented examples and tests should change in lockstep.
v0.510 follow-up -- Validator->Validate class-method shortcut
Additive (no BC concerns), tiny: ~5 LOC plus one test subtest.
Add a class-method front door Validator->Validate($tc_string, %opts) that is shorthand for Validator->new(%opts)->validate($tc_string). Mirrors the existing GDPR::IAB::TCFv2::Parser->Parse($s, %opts) shape and prepares the symmetry that the v0.600 Parser/VendorConsent refactor will complete.
Scope:
In lib/GDPR/IAB/TCFv2/Validator.pm, add
sub Validatethat detects class vs instance invocation, builds a transient$selfon class invocation, and calls$self->validate($tc_string).Extend t/06-validator.t with a subtest that exercises both
Validator->new(%opts)->validate($s)andValidator->Validate($s, %opts)against the same fixture and asserts identicalValidator::Resultoutput.Update
Validator.pmPOD to document both invocation forms and note they are equivalent.
Phase 6 follow-up -- CMPValidator structured failures (non-blocking)
Round out Phase 6 by migrating GDPR::IAB::TCFv2::CMPValidator (Phase 5) onto the structured failure-reporting model the rest of the validator already uses. Today CMP-level failures are stringly-typed croaks; this item brings them onto Validator::Failure with stable machine-readable codes. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/112
Scope:
Add a
REASON_CMP_*family toValidator::Reason:REASON_INVALID_CMP(CMP ID not in registry),REASON_CMP_DELETED(retired CMP,deletedDatehonored),REASON_CMP_UNKNOWN(registry loaded but ID unclassifiable). Updatereason_string()to cover them.Refactor
CMPValidator(or its consumer wrapper inside the mainValidatorrule) to emitValidator::Failureobjects with the matching codes instead of plain croaks.Backwards compat: the human-readable text emitted by
Validator::Resultstringification must stay the same so existing CLI output is unchanged.Tests: extend t/14-cmp-validator.t (and/or t/16-validator-failures.t) with subtests asserting each new code, reusing the fixed reference dates already in place for determinism.
HELP WANTED -- DISTRIBUTION
These items are about packaging the existing code, not about adding features.
Linux package artifacts (.deb and .rpm)
Wire up a GitHub Actions workflow that, on every v* tag, builds a .deb and a .rpm from the released tarball and uploads both as assets on the corresponding GitHub Release. Both packages are pure Perl (noarch / all), so a single Linux runner per format is enough; no build matrix, no cross-arch concerns. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/81
Suggested toolchain:
.deb:
dh-make-perl --build --cpan-mirror=file:./against themake disttarball. Produceslibgdpr-iab-tcfv2-perl_VERSION_all.deb. Confirm the resultingdebian/controlpicks upbin/iabtcfv2as an executable and respects the METArecommends..rpm:
cpanspecon the tarball to produce a.spec, thenrpmbuild -bainside afedora:latestcontainer. Producesperl-GDPR-IAB-TCFv2-VERSION-1.noarch.rpm.Upload both via
gh release upload "$GITHUB_REF_NAME" *.deb *.rpm.
Out of scope (separate, larger efforts -- not part of this item): filing an ITP bug to ship the package via Debian proper, hosting a signed apt repo, or maintaining a Fedora COPR. The deliverable here is artifacts on the Release page, downloadable with wget + dpkg -i or dnf install ./perl-GDPR-IAB-TCFv2-VERSION-1.noarch.rpm.
Homebrew tap (macOS + Linux)
Stand up a self-hosted Homebrew tap repository (peczenyj/homebrew-tap) with a Formula/iabtcfv2.rb formula that installs the CLI from the released CPAN tarball. Users opt in with: See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/88
brew tap peczenyj/tap
brew install iabtcfv2
Maintenance contract: a small CI step on this repository's release event that opens a PR against the tap repo bumping the formula's url + sha256. Self-hosted (not homebrew-core), so no external review queue and no homebrew-core's "notable + stable + popular" gate.
Same formula works on Homebrew on Linux (Linuxbrew), so this single channel covers macOS plus a slice of Linux developer machines for free.
Snap package (Ubuntu / cross-distro Linux)
Author a snapcraft.yaml that packages bin/iabtcfv2 and the Perl runtime, and publish to snapcraft.io on every v* tag. Users install with: See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/89
sudo snap install iabtcfv2
Heads-up for the contributor: snaps run under confinement, so any file paths the CLI reads (config files, GVL JSON dumps) need to be inside the snap-allowed locations or a personal-files / system-files plug must be declared and connected. Path the release notes accordingly so first-time users do not hit a silent "file not found" inside the sandbox.
AUR package (Arch Linux)
Publish a PKGBUILD for Arch's user repository. The recipe is short -- makepkg drives a perl Makefile.PL && make && make install against the release tarball. Best handled by a community co-maintainer rather than this repo's CI; volunteer contributions welcome. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/90
ECOSYSTEM -- SISTER DISTRIBUTIONS
The following ideas are intentionally separate CPAN distributions rather than features of this one. Each adds a runtime dependency on its host framework, so packaging them separately keeps GDPR::IAB::TCFv2's own deps lean.
Highest priority
GDPR::IAB::TCFv2::Validator::LIVR See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/82
LIVR rule-engine binding for JSON-shaped TC payloads.
GDPR::IAB::TCFv2::Validator::TypeTiny See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/83
Reusable Type::Tiny constraints (parameterized by purpose / vendor sets) for Moo, Moose, or pure-Perl callers that prefer type-level enforcement.
Plack::Middleware::GDPR::TCFv2 See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/84
Plack middleware that decodes a TC string from a request header or cookie, attaches the parsed
GDPR::IAB::TCFv2object to$env, and short-circuits the response when consent is missing or invalid.
Nice to have
GDPR::IAB::TCFv2::Validator::Moose See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/85
Moose attribute traits and role-based validation for projects that already use Moose end-to-end.
GDPR::IAB::TCFv2::Validator::FormValidator See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/86
Data::FormValidatorprofile glue for legacy applications that drive business validation through DFV.GDPR::IAB::TCFv1 See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/92
A standalone parser for the legacy IAB TCF v1 / v1.1 consent string format (April 2018 -- August 2020, superseded by TCF v2.0). The wire format is unrelated to v2 -- different bit layout, no segment structure, smaller fixed core -- so it does not belong inside this distribution.
Audience is intentionally narrow: log spelunkers, audit teams, and the historically curious. A useful contribution would expose a similar surface to GDPR::IAB::TCFv2 (
Parse,TO_JSON, simple purpose / vendor predicates) without dragging in v2's validator machinery.
HOW TO CLAIM AN ITEM
Open a GitHub issue (or comment on the existing one) referencing the phase or sister-distribution name above. Include a rough scoping sketch: what API you are proposing, what tests you will add, and which existing module the work will touch.
Wait for a maintainer to assign the issue. This avoids duplicate effort and surfaces any spec questions early.
Follow the patching workflow in CONTRIBUTING.pod: branch from
devel, run the full test suite (prove -lr tplusAUTHOR_TESTING=1 prove -lr xt), open a PR.
OUT OF SCOPE
The following are intentionally not on the roadmap:
TCF v1 / v1.1 support inside this distribution. The two wire formats share nothing but the IAB acronym, so v1 belongs in a sibling distribution rather than as a code path here. See the GDPR::IAB::TCFv1 entry under "ECOSYSTEM -- SISTER DISTRIBUTIONS" if you are interested in claiming it.
Network-side discovery of CMP or GVL endpoints. The library accepts pre-loaded data; fetching is the caller's responsibility (or the optional
HTTP::Tinypath insideCMPValidator).Persistent caching of parsed strings. TC strings are cheap to parse; caching is a deployment concern, not a library concern.
SEE ALSO
GDPR::IAB::TCFv2, GDPR::IAB::TCFv2::Validator, GDPR::IAB::TCFv2::CMPValidator, CONTRIBUTING.pod, CHANGELOG.md.