targeting

package
v0.0.0-...-c8f541b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package targeting provides data-driven targeting engines for TMP agents.

Two engines live here: ContextEngine evaluates context-match requests against a ContextStorage (property bitmaps, package configs, topic sets, signal-targeting keys, suppression markers), and IdentityEngine (in identity_engine.go) evaluates identity-match requests against an audience service. They are deployed as separate processes — the context agent and identity agent — so that user-token data never traverses the context path.

Index

Constants

View Source
const (
	StagePropertyBitmap  = "property_bitmap"
	StageSuppression     = "suppression"
	StageSignature       = "signature"
	StageTopicMatch      = "topic_match"
	StageTopicNoTaxonomy = "topic_no_taxonomy"
	StageAudience        = "audience"
	StageSignalMatch     = "signal_match"
)

Evaluation stage constants.

StageTopicNoTaxonomy is emitted instead of StageTopicMatch when a TopicTargets package is short-circuited because the engine has no accepted taxonomies configured. The separate label lets on-call distinguish a configuration drop (no taxonomies declared) from a genuine miss (taxonomy declared, no matching topic) in dashboards.

View Source
const (
	PreCheckOK         = ""            // passed local checks; safe to call the verifier
	PreCheckMalformed  = "malformed"   // nil attestation / unparseable
	PreCheckNoBinding  = "no_binding"  // signal_binding empty — fail closed (replay defense)
	PreCheckExpired    = "expired"     // expires_at present and in the past
	PreCheckRPMismatch = "rp_mismatch" // relying_party_id != the RP this receiver acts as
)

Pre-check drop reasons. The local pre-check runs BEFORE the (possibly network) verifier and before any expensive crypto on the verifier's side; each reason is a bounded metric label.

View Source
const (
	// MaxSealedCredentials caps sealed_credentials entries processed per
	// request (schema maxItems). Enforced before any crypto so a flood of
	// garbage entries cannot force N HPKE opens + verifier round-trips.
	MaxSealedCredentials = 8
	// MaxSealedPayloadBytes caps one sealed payload's wire length (schema
	// maxLength 8192) before opening.
	MaxSealedPayloadBytes = 8192
)
View Source
const (
	// DropMalformedSeal is a sealed_credentials entry missing audience_kid or
	// payload — the envelope is malformed before any attestation is parsed.
	DropMalformedSeal = "malformed_sealed"
	// DropOpenFailed is an oversized payload, a failed HPKE open, or plaintext
	// that does not decode to an attestation.
	DropOpenFailed = "open_failed"
	// DropRPMismatchPostVerify is the verifier returning an identity bound to a
	// different relying party than the recipient key commits to. Distinct from
	// the pre-verify PreCheckRPMismatch: this branch is defense-in-depth and
	// should never fire — when it does, the verifier itself is misbehaving, not
	// a forwarded proof.
	DropRPMismatchPostVerify = "rp_mismatch_postverify"
)

Receiver drop reasons emitted by OpenAndVerify that are not already covered by the PreCheck* constants. Each is a bounded metric label.

View Source
const GeoCountryKey = "country"

GeoCountryKey is the ContextMatchRequest.Geo map key carrying the ISO 3166-1 alpha-2 country code. Hoisted to a constant so a future move to a typed Geo struct is a single grep target.

Variables

This section is empty.

Functions

func IsClosedClaim

func IsClosedClaim(c tmproto.AttestationClaim) bool

IsClosedClaim reports whether c is one of the closed attestation-claim values. The wire format tolerates unrecognized (additive) claim values; the buyer-side age gate only acts on the closed set, so non-canonical values are dropped here before they can reach eligibility logic.

func LocalPreCheck

func LocalPreCheck(att *tmproto.Attestation, vctx VerifyContext) string

LocalPreCheck enforces the invariants the SDK can check locally and fail-closed, before handing the attestation to the pluggable verifier. Returns PreCheckOK ("") when the attestation may proceed to Verify, or a drop reason otherwise. A dropped attestation MUST be treated as absent.

These checks are deliberately NOT delegated to the verifier:

  • signal_binding non-empty: the field is optional on the wire, so an attacker could omit it; requiring it here closes the omit-and-replay path regardless of verifier quality.
  • expires_at: a present horizon must be in the future.
  • relying_party_id receiver-binding: the audience_kid that selected our recipient key tells us which RP we are; a proof for another RP is a forwarded/replayed proof and is rejected here, cheaply and locally.

func NormalizeClaims

func NormalizeClaims(claims []tmproto.AttestationClaim) map[tmproto.AttestationClaim]struct{}

NormalizeClaims projects a claim slice onto the closed attestation-claim set, dropping any value not in the vocabulary. The verified-identity pipeline only ever stores normalised claims, so downstream age logic (parseAgeOver) only ever sees canonical values.

func RejectByVerifiedIdentity

func RejectByVerifiedIdentity(reqs map[string]VerifiedIdentityRequirement, verified []VerifiedIdentity) map[string]struct{}

RejectByVerifiedIdentity returns the package IDs rejected by the verified-identity gate: a package that requires a verified human, or an age threshold, that no verified identity satisfies. Fail-closed — required-but-absent ⇒ rejected. A package with no requirement is never rejected here.

Types

type AgeResolver

type AgeResolver interface {
	ResolveRequiredAge(ctx context.Context, pkgID, country string) (claim tmproto.AttestationClaim, ok bool)
}

AgeResolver resolves whether a package requires an age threshold for a given geo, and which closed-set claim satisfies it. ok=false means no age requirement applies. The production implementation resolves (age policy, geo) → threshold via the AdCP Policy Registry; it is pluggable so the registry integration lands behind this interface without changing the pipeline.

type AttestationVerifier

type AttestationVerifier interface {
	Verify(ctx context.Context, att *tmproto.Attestation, vctx VerifyContext) (VerifiedIdentity, error)
}

AttestationVerifier validates a wire Attestation against the AdCP conformance invariants for its scheme. adcp-go cannot itself verify a World ID zk proof, so verification is pluggable: a deployment wires a concrete verifier (e.g. an HTTP call to a World ID verifier service, an mDL validator). Verify returns the relying-party-scoped nullifier and the verified claim set ONLY when every scheme invariant holds.

  • Verify MUST validate scheme + issuer + proof per the scheme rules and confirm the proof was minted for vctx.ExpectedRelyingPartyID (the SDK also enforces relying_party_id == ExpectedRelyingPartyID before calling Verify, but the verifier owns the cryptographic binding + brand.json ownership lookup).
  • Verify MUST confirm the attestation's signal_binding commits to vctx.RequestID (the SDK guarantees signal_binding is non-empty but cannot check the scheme-specific hash).
  • It SHOULD dedupe nullifiers within the freshness window (nullifier-reuse tracking; not yet enforced SDK-side).
  • Any failure returns a non-nil error; callers treat that as "no attestation".

There is intentionally no default verifier: a Service with a nil verifier treats every attestation as absent (fail-closed), so trust-without-verify is unreachable by configuration omission.

type Bitmap

type Bitmap interface {
	Contains(v string) bool
}

Bitmap is a set of string values with O(1) membership test. For small sets (<10K), MapBitmap is a stdlib-only alternative.

type ContextEngine

type ContextEngine struct {
	// contains filtered or unexported fields
}

ContextEngine evaluates context-match requests. The storage backend supplies every piece of data the engine consults at request time; caching, persistence, and refresh policy live in the storage impl, not here.

func NewContextEngine

func NewContextEngine(cfg ContextEngineConfig) *ContextEngine

NewContextEngine creates a context-match engine. The caller's cfg.AcceptedTaxonomies slice is copied so post-construction mutation cannot reach into engine state.

func (*ContextEngine) Evaluate

Evaluate runs the context-match pipeline:

  1. Global property bitmap pre-filter.
  2. Property / geo suppression checks via the storage.
  3. Resolve the active package set from Storage.ActivePackages for this seller / property / country / placement at e.now(). This happens on EVERY request — not only when req.PackageIDs is omitted — because the request's PackageIDs list is a publisher- supplied restriction, not a substitute for the provider's authoritative active inventory. A stale PackageContextConfig for an expired media buy must not produce offers just because a publisher names it.
  4. Resolve the candidate set: when req.PackageIDs is present, the intersection of the active set and req.PackageIDs (by analogy to IdentityMatchRequest.PackageIDs, which the AdCP spec documents as "intersection of registered active set and package_ids" — ContextMatchRequest.PackageIDs is not currently spelled out in the spec, but the same provider-controls-the-active-set principle applies); when omitted, the full active set.
  5. Per package: load context config, check per-package property bitmap, check signal targeting, check topic match (publisher topics short-circuited first, then per-artifact / per-taxonomy storage lookups).

Storage errors on any per-package dimension are recorded via StoreError and fail-closed for that package — the package's dimension check returns false and the package is skipped. The request continues evaluating the rest of its packages. A suppression error at the top of the pipeline is logged and the request continues — the alternative would be denying every request during a partial storage outage, which is the wrong fail mode for a kill switch you can't otherwise un-stick.

type ContextEngineConfig

type ContextEngineConfig struct {
	// ProviderID is the publisher-assigned identifier for this provider
	// registration. Used in logs, metrics, and suppression keys (see
	// tmproto.ProviderRegistration.ProviderID). Stable for the engine's
	// lifetime; rotate by restarting with a new value.
	ProviderID string

	// Properties is the registry-derived global (and optionally
	// per-package) property bitmap. Requests whose property_rid is not
	// in Properties.Global short-circuit before any storage lookup.
	Properties PropertyList

	// Storage is the read surface for media-buy resolution, package
	// configs, topic data, signal targeting, and suppression. Required.
	Storage ContextStorage

	// AcceptedTaxonomies enumerates the topic taxonomies this deployment
	// trusts on inbound ContextSignals and consults on storage artifact
	// / package topic lookups. Empty disables topic targeting: every
	// TopicTargets package fails the topic check (fail-closed) — a
	// deployment that wants topic targeting MUST declare at least one
	// accepted taxonomy.
	//
	// A publisher who sends ContextSignals.Topics with an empty
	// TaxonomySource gets a Taxonomy{Source: ""} key that
	// Taxonomy.Validate rejects and that no deployment can configure as
	// accepted. Those topics are silently dropped.
	AcceptedTaxonomies []topicstore.Taxonomy

	// Now is the clock the engine uses for media-buy date filtering.
	// Defaults to time.Now; overridable for tests.
	Now func() time.Time

	Metrics Metrics // nil = noop
}

ContextEngineConfig holds all configuration for creating a ContextEngine.

type ContextResult

type ContextResult struct {
	RequestID string
	Offers    []tmproto.Offer
	Signals   map[string]any
}

ContextResult holds the output of context evaluation.

type ContextStorage

type ContextStorage interface {
	// ActivePackages returns the package IDs the deployment offers for
	// the given (sellerAgentURL, propertyID, country, placementID)
	// tuple at `now` — i.e., every package whose media buy is active
	// (date / geo / property) and that itself is willing to serve the
	// placement.
	//
	// The engine consults this on every request, not only when
	// req.PackageIDs is omitted. When req.PackageIDs IS present the
	// engine intersects this set with the inbound list, per the TMP
	// spec's "intersection of registered active set and package_ids"
	// rule (see IdentityMatchRequest.PackageIDs docstring in
	// tmproto/types_gen.go — the principle applies to context-match
	// too). Skipping this step would let a stale PackageContextConfig
	// for an expired media buy still produce offers if a publisher
	// names it explicitly.
	ActivePackages(ctx context.Context, sellerAgentURL, propertyID, country, placementID string, now time.Time) ([]string, error)

	// ContextConfig returns the package's context-side configuration.
	// `ok == false` (with err == nil) means no config is stored for that
	// package — the engine skips it.
	//
	// Storage implementations MAY also implement an optional
	// `ContextConfigs(ctx, []string) ([]*PackageContextConfig, error)`
	// method that returns every requested package's config in a
	// single round-trip; the engine feature-detects that method to
	// collapse per-package fetches into one MGet when planning
	// signal-targeting lookups. Implementations that do not provide
	// it fall back to repeated ContextConfig calls.
	ContextConfig(ctx context.Context, packageID string) (cfg *PackageContextConfig, ok bool, err error)

	// ArtifactTopics returns the raw topic ids stored for `ref` under
	// `tax`. The engine namespaces them via topicstore.NamespaceTopic
	// before joining; storage returns the un-namespaced ids it has on
	// hand. nil (with err == nil) means no topics are stored.
	ArtifactTopics(ctx context.Context, tax topicstore.Taxonomy, ref string) ([]string, error)

	// PackageTopics returns the raw topic ids `packageID` targets under
	// `tax`. Same shape as ArtifactTopics.
	PackageTopics(ctx context.Context, tax topicstore.Taxonomy, packageID string) ([]string, error)

	// IsPropertySuppressed reports whether `propertyRID` is currently
	// suppressed for `providerID`. Suppressions are deployment-scoped
	// kill switches; a true return short-circuits the entire request.
	IsPropertySuppressed(ctx context.Context, providerID, propertyRID string) (bool, error)

	// IsGeoSuppressed reports whether `country` is currently suppressed
	// for `providerID`. Same kill-switch semantics as
	// IsPropertySuppressed.
	IsGeoSuppressed(ctx context.Context, providerID, country string) (bool, error)

	// SignalMGet fetches the raw CSV-encoded signal-id strings stored
	// at each requested signal:* key. The returned slice is aligned 1:1
	// with the input; empty string at index i means "missing key".
	// Used by the engine to evaluate context-attribute signal targeting
	// in a single round-trip across every candidate package per request.
	SignalMGet(ctx context.Context, keys ...string) ([]string, error)
}

ContextStorage is the data surface the context engine consults at request time. Implementations decide how data is fetched and cached; the engine treats every call as "ask the storage, trust the answer."

Two implementations ship with this repo:

  • targeting/contextstorage.InMemory — a snapshot-style impl backed by plain maps; used by engine tests, the reference agent, and any embedder that wants to build a ContextStorage by hand.
  • targeting/contextagent's bundle — the production impl. Layers a per-domain Service (mediabuystore, pkgconfigstore, suppressionstore, topicstore) optionally wrapped in an LRU cache decorator. The engine never sees those packages directly.

All methods are called from request hot paths and MUST respect the passed context. Returning a non-nil error is a "this lookup failed" signal; the engine fails-closed on the affected dimension for the current request (under-match) and continues, mirroring how the pre-storage engine handled per-key Valkey errors.

type CredentialObserver

type CredentialObserver interface {
	// Dropped fires once per credential treated as absent, with a PreCheck* or
	// Drop* reason label.
	Dropped(ctx context.Context, reason string)
	// VerifierFailed fires when the verifier itself returned an error — kept
	// distinct from Dropped so a down/misconfigured verifier is observable, not
	// mistaken for organic "no humans verified".
	VerifierFailed(ctx context.Context)
}

CredentialObserver receives per-credential observations from OpenAndVerify so a receiver can record metrics without OpenAndVerify owning a metric vocabulary. A nil observer is replaced with a no-op.

type IdentityEngine

type IdentityEngine struct {
	// contains filtered or unexported fields
}

IdentityEngine evaluates identity-match requests. It reads pre-resolved package identity configs and consults an audience.Service for segment membership. It does not touch the targeting context-side storage — identity data and context data live in separate processes (identity agent / context agent) so user-token signals never traverse the context path.

func NewIdentityEngine

func NewIdentityEngine(cfg IdentityEngineConfig) *IdentityEngine

NewIdentityEngine creates an identity-match engine.

func (*IdentityEngine) EvaluateIdentityResolved

func (e *IdentityEngine) EvaluateIdentityResolved(ctx context.Context, resolved *ResolvedPackages, req *tmproto.IdentityMatchRequest) (*IdentityResult, error)

EvaluateIdentityResolved evaluates package eligibility for an identity match request using pre-resolved identity configs supplied by the identity agent's bundle. Returns one PackageEligibility per requested package ID, preserving order.

type IdentityEngineConfig

type IdentityEngineConfig struct {
	Audience *audience.Service // nil = identity evaluation is segment-blind
	Metrics  Metrics           // nil = noop
}

IdentityEngineConfig holds all configuration for creating an IdentityEngine.

type IdentityResult

type IdentityResult struct {
	RequestID   string
	Eligibility []tmproto.PackageEligibility

	// Verified carries the identities the verified-identity stage validated
	// (verify-before-trust). The TMPX seal step encodes their nullifiers onto
	// the wire so the buyer can frequency-cap / unique-human gate on its own
	// relying-party-scoped pseudonym. Empty when no verifier is wired or no
	// attestation passed verification — the audience-only engine never sets
	// it. Never populated from sender-asserted inbound identities.
	Verified []VerifiedIdentity
}

IdentityResult holds the output of identity evaluation.

type MapBitmap

type MapBitmap map[string]struct{}

MapBitmap is a stdlib-only Bitmap backed by a map.

func NewMapBitmap

func NewMapBitmap(values ...string) MapBitmap

NewMapBitmap creates a MapBitmap from a slice of values.

func (MapBitmap) Contains

func (m MapBitmap) Contains(v string) bool

Contains reports whether v is in the bitmap.

type Metrics

type Metrics interface {
	ContextEvaluated(ctx context.Context, stage string, passed bool)
	IdentityEvaluated(ctx context.Context, stage string, passed bool)
	StoreError(ctx context.Context, operation string, err error)
	Latency(ctx context.Context, stage string, d time.Duration)
}

Metrics receives instrumentation callbacks from the targeting engine. Implementations can map these to Prometheus counters or structured logging. The noop default adds zero overhead.

Each method receives the request-scoped context so implementations can attach trace/baggage data to the emitted record. Implementations MUST NOT retain the context past the call.

Labels are intentionally low-cardinality. Per-package metrics would explode cardinality at the scale of hundreds of thousands of packages for negligible operational value; callers that need per-package data should sample request logs instead.

type OfferConfigJSON

type OfferConfigJSON struct {
	DealID       string             `json:"deal_id,omitempty"`
	Brand        json.RawMessage    `json:"brand,omitempty"`
	Price        tmproto.OfferPrice `json:"price"`
	Summary      string             `json:"summary,omitempty"`
	ManifestType string             `json:"manifest_type,omitempty"`
	Macros       map[string]string  `json:"macros,omitempty"`
}

OfferConfigJSON is one competing brand offer for a package. When a package activates with multiple Offers configured, each entry produces a separate tmproto.Offer in the response and the publisher mediates.

type PackageContextConfig

type PackageContextConfig struct {
	PackageID    string   `json:"package_id"`
	MediaBuyID   string   `json:"media_buy_id,omitempty"`
	TopicTargets bool     `json:"topic_targets,omitempty"`
	PropertyRIDs []string `json:"property_rids,omitempty"`
	EmitSegments []string `json:"emit_segments,omitempty"`

	// ContextSignals gates the package on context-attribute signal
	// targeting. Nil or empty profile passes vacuously. Restricted to
	// context attributes — see signalstore.AllowedKeyTypes;
	// identity-keyed cfgs are rejected by signalstore.Profile.Validate.
	//
	// Topic cfgs key on a namespaced value, NOT the bare topic id: the
	// engine prefixes each topic with its taxonomy as
	// "{taxonomy_source}:{taxonomy_id}:{topic_id}" (the
	// topicstore.Taxonomy.String() form) so the same topic id under
	// different taxonomies stays distinct. The spec carries the
	// taxonomy once on the ContextSignals envelope rather than per
	// topic, so anyone authoring these cfgs out-of-band MUST apply the
	// same prefix to a topic cfg's stored signal value.
	ContextSignals *signalstore.Profile `json:"context_signals,omitempty"`

	Offers           []OfferConfigJSON  `json:"offers,omitempty"`
	Brand            json.RawMessage    `json:"brand,omitempty"`
	Price            tmproto.OfferPrice `json:"price"`
	Summary          string             `json:"summary,omitempty"`
	ManifestType     string             `json:"manifest_type,omitempty"`
	CreativeManifest json.RawMessage    `json:"creative_manifest,omitempty"`
	Macros           map[string]string  `json:"macros,omitempty"`
	// contains filtered or unexported fields
}

PackageContextConfig is the context-side configuration for a package. The context engine reads it through ContextStorage; the writer side owns persistence (see targeting/pkgconfigstore).

The snake_case JSON tags describe the writer's canonical serialization when stored in Valkey at config:pkg:{package_id}:context. They are intentionally distinct from the camelCase wire shape used by targeting/identityconfig/scope3, which is a separate external contract.

func (*PackageContextConfig) ContainsPropertyRID

func (c *PackageContextConfig) ContainsPropertyRID(rid string) bool

ContainsPropertyRID reports whether rid passes the config's PropertyRIDs gate. An empty PropertyRIDs list is "no gate" and returns true. Uses the materialized bitmap when present; falls back to a linear slice scan otherwise so directly-constructed configs keep working without an explicit MaterializePropertyBitmap call.

func (*PackageContextConfig) MaterializePropertyBitmap

func (c *PackageContextConfig) MaterializePropertyBitmap()

MaterializePropertyBitmap builds the O(1) membership index over PropertyRIDs so subsequent ContainsPropertyRID calls do not rebuild a set on every check. Storage decoders SHOULD call this once after unmarshaling; callers that construct configs directly (e.g. tests) can call it explicitly or omit it — ContainsPropertyRID has a slice-scan fallback. Idempotent; the last call wins.

type PackageIdentityConfig

type PackageIdentityConfig struct {
	TargetSegments *SegmentRule

	// RequiresVerifiedIdentity, when true, makes the package ineligible
	// unless a verified attestation is present for the request (fail-closed).
	// This is a presence gate only; the required age threshold (if any) is
	// resolved per (package, geo) through the Service's AgeResolver, not
	// stored here, so age policy can change without re-publishing package
	// config.
	//
	// The eligibility engine honors this field today. Populating it from the
	// identityconfig snapshot (a new identityconfig.Entry field + carrying it
	// through Lookup/GetBySeller, which currently index only *SegmentRule) is
	// a follow-up; until then the age gate via AgeResolver is the live
	// verified-identity gate.
	RequiresVerifiedIdentity bool
}

PackageIdentityConfig is the identity-side configuration for a package, keyed by (seller_agent_url, package_id) in the in-memory identityconfig service.

type PropertyList

type PropertyList struct {
	// Global is the set of all property RIDs this agent handles.
	// A request for a property not in Global is rejected immediately.
	// Nil means no global filter (all properties pass).
	Global Bitmap

	// ByPackage maps package_id to a property RID bitmap.
	// If a package has an entry, only those properties are eligible.
	// If a package has no entry, all properties are eligible for it.
	ByPackage map[string]Bitmap
}

PropertyList defines property-level targeting using bitmaps.

func (*PropertyList) ContainsGlobal

func (p *PropertyList) ContainsGlobal(rid string) bool

ContainsGlobal checks if a property RID passes the global filter.

func (*PropertyList) ContainsPackage

func (p *PropertyList) ContainsPackage(packageID string, rid string) bool

ContainsPackage checks if a property RID is eligible for a package. Returns true if no per-package targeting is configured.

type RecipientKey

type RecipientKey struct {
	PrivateKey     *ecdh.PrivateKey
	RelyingPartyID string
}

RecipientKey is the HPKE recipient identity for one audience_kid: the X25519 private key that opens sealed_credentials addressed to that kid, plus the relying party THIS receiver acts as for that audience. RelyingPartyID is the receiver-binding anchor — a sealed attestation whose relying_party_id does not equal it is a forwarded/replayed proof and is rejected (LocalPreCheck). The audience_kid that selected this key is how the receiver knows which RP it is, so the binding is enforced locally and fail-closed rather than deferred to the verifier.

type ResolvedPackages

type ResolvedPackages struct {
	IdentityConfigs map[string]*PackageIdentityConfig // pkgID → config
}

ResolvedPackages carries the per-package identity-side configuration the identity agent consults to evaluate eligibility. It is constructed by the identity agent's bundle from the identityconfig snapshot — identity-match keeps this shape stable, and the context engine no longer touches it.

type SegmentRule

type SegmentRule struct {
	AllOf  []string
	AnyOf  []string
	NoneOf []string
}

SegmentRule expresses audience-segment criteria for a package as a single AND-of-clauses rule. A user matches the rule when:

  • they belong to every segment listed in AllOf,
  • they belong to at least one segment listed in AnyOf (vacuously satisfied when AnyOf is empty),
  • they belong to none of the segments listed in NoneOf.

A nil *SegmentRule on PackageIdentityConfig means no audience gating: every user is eligible for the package.

func (*SegmentRule) Clone

func (r *SegmentRule) Clone() *SegmentRule

Clone returns a deep copy of the rule, including independent backing arrays for AllOf, AnyOf, and NoneOf. Callers receiving a rule from an authoritative source (e.g. identityconfig.Service) that want to mutate it without affecting the source must clone first. A nil receiver returns nil.

func (*SegmentRule) IsEmpty

func (r *SegmentRule) IsEmpty() bool

IsEmpty reports whether the rule declares no clauses — i.e., it references no segment IDs in AllOf, AnyOf, or NoneOf. An empty rule trivially matches every user; callers can short-circuit audience lookups when IsEmpty is true. A nil receiver is treated as empty.

func (*SegmentRule) Matches

func (r *SegmentRule) Matches(userSegments map[string]struct{}) bool

Matches reports whether the given user-segment set satisfies the rule. A nil rule trivially matches every user.

func (*SegmentRule) Segments

func (r *SegmentRule) Segments() []string

Segments returns the deduplicated union of every segment ID referenced by the rule across AllOf, AnyOf, and NoneOf. Returns nil for an empty or nil rule. Callers use this to scope audience-membership lookups to segments the rule actually mentions.

type UserIdentity

type UserIdentity struct {
	UserToken string `json:"user_token"`
	UIDType   string `json:"uid_type"`
}

UserIdentity represents a user identity token with its type.

type VerifiedIdentity

type VerifiedIdentity struct {
	// Nullifier is the relying-party-scoped, Sybil-resistant pseudonym. It is
	// the frequency-cap key (namespaced by RelyingPartyID, see CapKey) — never
	// used raw, so it cannot collide across relying parties.
	Nullifier string
	// RelyingPartyID is the RP scope this nullifier and these claims belong
	// to. The receiver confirmed it matches the RP this receiver acts as.
	RelyingPartyID string
	// Claims is the verified claim set, normalised to the closed
	// attestation-claim vocabulary.
	Claims map[tmproto.AttestationClaim]struct{}
}

VerifiedIdentity is the post-verification result for one attestation that passed every check: a relying-party-scoped pseudonym plus the claims the receiver is allowed to trust. It is the ONLY way claims enter eligibility; an unverified or absent attestation never produces one.

func OpenAndVerify

OpenAndVerify is the verify-before-trust receiver loop. For each inbound sealed credential addressed to a held recipient key it HPKE-opens the payload, enforces the local fail-closed pre-checks, calls the pluggable verifier, and returns the verified identities with claims normalized to the closed claim set.

Fail-closed by construction. With no verifier or no recipient keys it returns nil immediately (no opening, no allocation). Opening a credential proves only that it was sealed to our key — never that the claims are true. Claims become eligible inputs only after the verifier validates the proof AND the local pre-checks pass (signal_binding present, not expired, relying_party_id == the RP we act as). The count is bounded before any crypto, and an undecryptable blob costs at most local work because the verifier is consulted only after a successful open.

vctx supplies RequestID, Country, and Now; ExpectedRelyingPartyID is filled per-credential from the matched recipient key.

func VerifyAttestations

func VerifyAttestations(ctx context.Context, identities []tmproto.IdentityToken, verifier AttestationVerifier, vctx VerifyContext, obs CredentialObserver) []VerifiedIdentity

VerifyAttestations is the in-band sibling of OpenAndVerify: it runs the verify-before-trust checks on the attestations carried directly on the request's identity entries (req.Identities[].Attestation), with no HPKE seal to open, and returns the verified identities. The same rule holds — claims are trusted only from the verifier, never the asserted attestation — and the expected relying party is vctx.ExpectedRelyingPartyID, the single RP this receiver acts as; an attestation bound to any other RP is dropped by LocalPreCheck.

Fail-closed by construction: with no verifier or no expected relying party it returns nil without consulting the verifier. An identity entry without an attestation is an ordinary token, not a verified identity, and is skipped.

func (VerifiedIdentity) CapKey

func (vi VerifiedIdentity) CapKey() string

CapKey returns the frequency-cap key for this verified identity. The key is namespaced by relying party so the same nullifier byte-string under two different relying parties yields two distinct keys (no cross-RP cap contamination), and the "vid:" prefix segregates verified-identity keys from raw UserToken cap keys (no cap-poisoning by sending a guessed nullifier as an ordinary unverified token).

func (VerifiedIdentity) ClaimsSatisfy

func (vi VerifiedIdentity) ClaimsSatisfy(required tmproto.AttestationClaim) bool

ClaimsSatisfy reports whether the verified claim set satisfies a required claim. For an age threshold, a higher age claim satisfies a lower one (age_over_21 satisfies a required age_over_18). For unique_human, the set must contain unique_human.

type VerifiedIdentityRequirement

type VerifiedIdentityRequirement struct {
	// RequiresVerifiedHuman gates the package on at least one present verified
	// identity (a verified unique human).
	RequiresVerifiedHuman bool
	// RequiresAge gates the package on a verified age threshold. When set, a
	// verified identity must satisfy RequiredAge. RequiredAge may be empty even
	// when RequiresAge is set (the policy applies but resolved no closed-set
	// claim); no verified identity can satisfy an empty claim, so the package
	// is then rejected — fail-closed. Carried separately from RequiredAge so
	// "age required, threshold empty" is not collapsed into "no age
	// requirement".
	RequiresAge bool
	// RequiredAge is the age claim a verified identity must satisfy when
	// RequiresAge is set.
	RequiredAge tmproto.AttestationClaim
}

VerifiedIdentityRequirement is one package's verified-identity requirement, resolved by the caller from its own package config and age policy.

type VerifyContext

type VerifyContext struct {
	// RequestID is the anchor the attestation's signal_binding must commit to.
	RequestID string
	// Country is ISO-3166-1 alpha-2, for geo-scoped verifier/age logic.
	Country string
	// ExpectedRelyingPartyID is the relying party THIS receiver acts as for
	// the audience that selected the recipient key. The attestation's
	// relying_party_id must equal it (enforced SDK-side before Verify) so a
	// proof minted for another RP cannot be replayed here.
	ExpectedRelyingPartyID string
	// Now is the reference time for expiry checks.
	Now time.Time
}

VerifyContext carries the request-scoped values a verifier needs to bind a proof to THIS request and THIS receiver (replay + provenance defense) without handing it the whole IdentityMatchRequest.

Directories

Path Synopsis
Package audience provides storage and lookup for audience membership.
Package audience provides storage and lookup for audience membership.
Package contextagent assembles a production-ready TMP context-match service.
Package contextagent assembles a production-ready TMP context-match service.
Package contextstorage provides an in-memory targeting.ContextStorage implementation suitable for engine unit tests, the reference context-agent, and any embedder that wants to build a ContextStorage by hand without standing up a real backing store.
Package contextstorage provides an in-memory targeting.ContextStorage implementation suitable for engine unit tests, the reference context-agent, and any embedder that wants to build a ContextStorage by hand without standing up a real backing store.
Package fcap provides frequency-cap state storage for the targeting engine.
Package fcap provides frequency-cap state storage for the targeting engine.
Package glidestore provides Valkey-backed implementations of targeting.ContextStore, targeting/fcap.Store, and targeting/audience.Store using valkey-glide/go/v2.
Package glidestore provides Valkey-backed implementations of targeting.ContextStore, targeting/fcap.Store, and targeting/audience.Store using valkey-glide/go/v2.
Package identityagent assembles a production-ready TMP identity-match service.
Package identityagent assembles a production-ready TMP identity-match service.
Package identityconfig serves PackageIdentityConfig entries keyed by (seller_agent_url, package_id) out of an in-memory snapshot.
Package identityconfig serves PackageIdentityConfig entries keyed by (seller_agent_url, package_id) out of an in-memory snapshot.
scope3
Package scope3 implements an identityconfig.Source backed by a Scope3 HTTP endpoint that returns the seller-keyed identity configs.
Package scope3 implements an identityconfig.Source backed by a Scope3 HTTP endpoint that returns the seller-keyed identity configs.
Package identityhash holds the canonical user-identity hash used by every targeting sub-package that keys storage on a user token.
Package identityhash holds the canonical user-identity hash used by every targeting sub-package that keys storage on a user token.
internal
clusterslot
Package clusterslot implements the Valkey/Redis Cluster CRC16 slot algorithm and the slot-to-shard-ordinal mapping that adcp-go Stores use when fanning out reads across independent standalone replicas (shadow-replica mode).
Package clusterslot implements the Valkey/Redis Cluster CRC16 slot algorithm and the slot-to-shard-ordinal mapping that adcp-go Stores use when fanning out reads across independent standalone replicas (shadow-replica mode).
liveramp
Package liveramp is a thin HTTP client for a LiveRamp mapping sidecar.
Package liveramp is a thin HTTP client for a LiveRamp mapping sidecar.
Package mediabuystore owns the storage layout for the context engine's media-buy data.
Package mediabuystore owns the storage layout for the context engine's media-buy data.
Package pkgconfigstore owns the storage layout for per-package context-side configuration.
Package pkgconfigstore owns the storage layout for per-package context-side configuration.
Package prommetrics provides a Prometheus-compatible implementation of targeting.Metrics using only the Go standard library.
Package prommetrics provides a Prometheus-compatible implementation of targeting.Metrics using only the Go standard library.
Package redisstore provides Valkey-backed implementations of targeting.ContextStore, targeting/fcap.Store, and targeting/audience.Store using github.com/redis/go-redis/v9.
Package redisstore provides Valkey-backed implementations of targeting.ContextStore, targeting/fcap.Store, and targeting/audience.Store using github.com/redis/go-redis/v9.
Package signalstore evaluates context-side signal targeting profiles against the signal:* keyspace populated by a signal writer.
Package signalstore evaluates context-side signal targeting profiles against the signal:* keyspace populated by a signal writer.
Package suppressionstore owns the storage layout for the context engine's per-provider property and geo suppression kill-switches.
Package suppressionstore owns the storage layout for the context engine's per-provider property and geo suppression kill-switches.
Package tmpxdecoders converts an IdentityToken.UserToken string into the binary token TMPX packs into its encrypted plaintext, one decoder per UID type.
Package tmpxdecoders converts an IdentityToken.UserToken string into the binary token TMPX packs into its encrypted plaintext, one decoder per UID type.
Package topicstore owns taxonomy-aware key construction for the TMP context-engine's content-topic data.
Package topicstore owns taxonomy-aware key construction for the TMP context-engine's content-topic data.
valkeystore module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL