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.

is_vendor_consent_allowed, is_vendor_legitimate_interest_allowed, is_vendor_allowed_for_flexible_purpose, plus strict mode.

Phase 1 -- TCF v2.3 segments and parser robustness

Disclosed Vendors / Allowed Vendors segments, multi-segment safety, has_vendor_disclosure, has_publisher_restrictions, the vendor_id filter on TO_JSON, and the automated release workflow.

Phase 2 -- Declarative validator interface

GDPR::IAB::TCFv2::Validator with validate / validate_all and Validator::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::CMPValidator with 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::Reason codes, Validator::Failure objects, 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 by Validator->new.

  • from_gvl($gvl_doc, $vendor_id) -- look up the vendor in a parsed GVL document and return a fully configured Validator; 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 / -F from the GVL entry.

  • Tests: golden GVL fixture covering vendors with and without flexible purposes; round-trip from_gvl_vendor_entry against a hand-crafted entry.

  • Freshness contract: the GVL loader must accept the max_age_days / on_stale knobs 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 $PWD or $HOME (plus .env-style loading if present). Documented precedence: CLI > env > file > built-in defaults.

  • iabtcfv2 config (or validate --print-config) to dump the resolved configuration as JSON for debugging.

  • **Tests:** a CLI subtest that sets the env vars, runs validate without 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::Error exception objects.

  • Overload stringification to provide clean messages while retaining metadata (code, file, line).

  • Refactor croak / die calls 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_days and on_stale constructor arguments to CMPValidator. on_stale accepts '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 lastUpdated field inside the JSON payload. Filesystem mtime is intentionally not consulted (it lies across cp -p, container rebuilds, and image bakes; the IAB lastUpdated field 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 and network_ok => 1, _check_age auto-triggers reload() instead of just carp/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 + file simultaneously (today they are mutually exclusive). When both are present, reload() tries url first, falls back to file on 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_stale branch, for the URL-then-file fallback, for reload() against a stub HTTP client, and for the reload_on_stale integration. A new test file covers GVL freshness once Phase 7 lands.

  • Documentation: update CMPValidator POD to describe the knobs and the lastUpdated contract; 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_stale if upstream is persistently unreachable -- minimum back-off interval, max retry count, or stateless "try once per validation"?

  • Whether reload_on_stale should be a no-op (rather than croak) when no url was 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_VERSION to 5.010000. Adopt use v5.10; everywhere, enabling state variables 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/131

  • Phase 12.1b -- Perl 5.12 Baseline. Update MIN_PERL_VERSION to 5.012000. Adopt use v5.12; everywhere, implicitly enabling strict. Modernize inheritance with use parent instead of use base. Adopt the modern package declaration syntax (package Name VERSION;). See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/132

  • Phase 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/125

  • Phase 12.3 -- Dist::Zilla. Migrate from ExtUtils::MakeMaker to Dist::Zilla. Automate versioning, manifest management, and release workflows. See: https://github.com/peczenyj/GDPR-IAB-TCFv2/issues/126

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_basis already asserts the stricter spec semantics; pairing it with the parser's strict mode 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_basis always checks a specific vendor's permissions across multiple purposes. For range-encoded TC strings, repeated vendor_consent / vendor_legitimate_interest calls walk the range list O(N) each. Passing prefetch => $vendor_id to Parse pre-walks the ranges once and caches answers (see lib/GDPR/IAB/TCFv2.pm Parse() 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_all overrides to the three purpose-id lists (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, and cmp_validator -- those become constructor-only. Unrecognized keys should croak with 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 with strict => $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_all receives a pre-parsed GDPR::IAB::TCFv2 object, the strict/prefetch decision was already made by the caller; the validator cannot retro-apply it. Document the caller contract in the POD for both strict_legal_basis and the $tc_string_or_object argument, 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 != 2 fixture) passed with strict_legal_basis true -- expect a parse-time croak, not a silent validation pass; (b) a range-encoded fixture to confirm prefetch is honored; (c) a regression asserting that validate($tc, vendor_id => $other) now croaks, replacing the existing per-call vendor_id tests (currently at lines 63, 74, 120). Same migration applies to the strict_legal_basis per-call test (line 260) and the cmp_validator per-call test in t/14-cmp-validator.t (line 156).

  • Documentation: update the strict_legal_basis POD entry in lib/GDPR/IAB/TCFv2/Validator.pm to spell out the coupling, and update the validate / validate_all override list in the POD to reflect the narrowed surface. Add a short MIGRATING section to the Changes/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 Validate that detects class vs instance invocation, builds a transient $self on class invocation, and calls $self->validate($tc_string).

  • Extend t/06-validator.t with a subtest that exercises both Validator->new(%opts)->validate($s) and Validator->Validate($s, %opts) against the same fixture and asserts identical Validator::Result output.

  • Update Validator.pm POD 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 to Validator::Reason: REASON_INVALID_CMP (CMP ID not in registry), REASON_CMP_DELETED (retired CMP, deletedDate honored), REASON_CMP_UNKNOWN (registry loaded but ID unclassifiable). Update reason_string() to cover them.

  • Refactor CMPValidator (or its consumer wrapper inside the main Validator rule) to emit Validator::Failure objects with the matching codes instead of plain croaks.

  • Backwards compat: the human-readable text emitted by Validator::Result stringification 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 the make dist tarball. Produces libgdpr-iab-tcfv2-perl_VERSION_all.deb. Confirm the resulting debian/control picks up bin/iabtcfv2 as an executable and respects the META recommends.

  • .rpm: cpanspec on the tarball to produce a .spec, then rpmbuild -ba inside a fedora:latest container. Produces perl-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

Nice to have

HOW TO CLAIM AN ITEM

  1. 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.

  2. Wait for a maintainer to assign the issue. This avoids duplicate effort and surfaces any spec questions early.

  3. Follow the patching workflow in CONTRIBUTING.pod: branch from devel, run the full test suite (prove -lr t plus AUTHOR_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::Tiny path inside CMPValidator).

  • 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.