router

package
v0.0.0-...-f657113 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	ProviderStatusActive   = tmproto.ProviderStatusActive
	ProviderStatusInactive = tmproto.ProviderStatusInactive
	ProviderStatusDraining = tmproto.ProviderStatusDraining
)
View Source
const DefaultContextCacheMaxEntries = 10_000

DefaultContextCacheMaxEntries is the fallback cap when the operator does not configure MaxEntries. Sized well above any realistic {property_rid × placement × provider × seller × country} product for a real deployment.

View Source
const DefaultContextCacheTTL = 5 * time.Minute

DefaultContextCacheTTL is the router's default cache lifetime for a per-provider Context Match response, per spec §Caching. Providers can override it via ContextMatchResponse.CacheTTL.

View Source
const MaxContextCacheTTL = 24 * time.Hour

MaxContextCacheTTL is the schema-enforced ceiling on provider-supplied cache_ttl (spec §Caching: "schema-enforced maximum is 86400 seconds"). The router clamps here as a defense-in-depth in case a future provider sends a value that escaped upstream validation.

View Source
const MaxRequestBodyBytes = 64 * 1024

MaxRequestBodyBytes caps an inbound request body the router reads before validating + fan-out. Sized to match the verifier's identity-match default (tmproto.VerifyOptions BodyLimit); raise here AND in the per-agent verifier config if you ever ship larger request shapes.

Variables

This section is empty.

Functions

func MatchesContextProvider

func MatchesContextProvider(req *tmproto.ContextMatchRequest, p *ProviderConfig) bool

MatchesContextProvider checks if a context match request should be sent to this provider. Assumes the caller has already filtered by status (e.g., via ProviderSet.Active()).

func MatchesIdentityProvider

func MatchesIdentityProvider(req *tmproto.IdentityMatchRequest, p *ProviderConfig) bool

MatchesIdentityProvider checks if an identity match request should be sent to this provider. Filters by country and uid_type when configured on the provider. Assumes the caller has already filtered by status (e.g., via ProviderSet.Active()).

func ValidateContextRequest

func ValidateContextRequest(req *tmproto.ContextMatchRequest) error

ValidateContextRequest ensures required fields are present on a context match request.

func ValidateIdentityRequest

func ValidateIdentityRequest(req *tmproto.IdentityMatchRequest) error

ValidateIdentityRequest ensures required fields are present on an identity match request.

func ValidateProviderConfig

func ValidateProviderConfig(p *ProviderConfig, latencyBudget time.Duration) error

ValidateProviderConfig checks that a provider registration is valid. latencyBudget of 0 disables the timeout check.

func ValidateProviderEndpoint

func ValidateProviderEndpoint(endpoint string) error

ValidateProviderEndpoint checks that a provider endpoint URL is safe (not pointing at localhost, metadata services, or private/RFC-1918 ranges).

Types

type CacheConfig

type CacheConfig struct {
	// Disabled turns the cache off entirely — the router fans out on
	// every request. Useful for A/B comparisons and for operators
	// terminating cache at an upstream layer.
	Disabled bool `json:"disabled,omitempty"`
	// DefaultTTLSeconds sets the fallback TTL when a provider response
	// omits cache_ttl. Spec recommends 5 minutes; zero or unset
	// collapses to that default.
	DefaultTTLSeconds int `json:"default_ttl_seconds,omitempty"`
	// MaxEntries caps the number of live cache entries so a caller
	// varying placement/seller/country cannot grow the map without
	// bound. Zero or unset applies DefaultContextCacheMaxEntries.
	MaxEntries int `json:"max_entries,omitempty"`
}

CacheConfig controls the per-provider Context Match response cache (spec §Caching). When Disabled is false, the router runs a cache with DefaultTTL applied on every entry whose provider does not specify a cache_ttl override.

func (CacheConfig) DefaultTTL

func (c CacheConfig) DefaultTTL() time.Duration

DefaultTTL returns the fallback TTL as a duration, honoring the spec's 5-minute recommendation when unset. Zero or negative values yield DefaultContextCacheTTL.

func (CacheConfig) MaxEntriesResolved

func (c CacheConfig) MaxEntriesResolved() int

MaxEntriesResolved returns the cap the cache should apply, honoring the operator's override when set and falling back to the default otherwise.

type ContextCache

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

ContextCache is an in-memory, per-provider cache of Context Match responses. Keyed on {property_rid, placement_id, provider_id, seller_agent_url, country}.

The spec's recommended cache key at §Caching lists only the first three components. The additional seller and country dimensions are added because this repository's own targeting engine (targeting/engine.go: ActivePackages(ctx, canonicalSeller, propertyID, country, placementID, ...)) scopes the active package set per seller and per country — different sellers or geos on the same placement return different offers. Keying only on placement would let one seller's cached offers be served to another seller on the same placement during the TTL window, disclosing competitor brands, pricing, and creative manifests across tenants. seller_agent_url is compared using the AdCP URL canonicalization rules (urlcanon.Canonicalize), the same normalization the engine applies before its lookup.

Responses are deeply cloned on read so callers can freely mutate Offer pointer/slice/map members without corrupting the cached entry. (Nested any values inside Signals stay shared — see the note on cloneContextResponse.)

Spec cache_ttl semantics (see Put for the enforcement code):

  • absent (nil) → router uses its configured default TTL
  • explicit 0 → provider is disabling caching; entry not stored
  • explicit > 0 → override, clamped to MaxContextCacheTTL

The tri-state depends on tmproto.ContextMatchResponse.CacheTTL being a pointer type so absent-field is distinguishable from present-zero (docs/sdk-typing-policy.md).

The router is stateless and horizontally scaled, so this cache is per-instance — restarts clear it and instances behind a load balancer each maintain their own view. That matches how the reference agents deploy (no shared cache) and keeps the router deployment story simple (no Redis dependency).

func NewContextCache

func NewContextCache(defaultTTL time.Duration, opts ...ContextCacheOption) *ContextCache

NewContextCache builds a cache with the given default TTL applied whenever a provider response omits or zeroes cache_ttl. TTLs of zero or less collapse to DefaultContextCacheTTL — a caller that truly wants caching disabled should skip constructing the cache and not wire it into the router (or pass WithContextCache(nil)).

func (*ContextCache) Get

func (c *ContextCache) Get(propertyRID, placementID, providerID, sellerAgentURL, country string) (*tmproto.ContextMatchResponse, bool)

Get looks up a cached response. Returns (nil, false) on miss or expiration; the caller falls back to a live fan-out call. The returned response is a defensive copy — callers may overwrite RequestID or Signals without corrupting the cached entry.

sellerAgentURL and country participate in the key so a request from one seller never returns a response the router cached for another seller (see the ContextCache doc for the rationale). Callers should pass sellerAgentURL already normalized via urlcanon.Canonicalize — the cache does not canonicalize on the hot path.

func (*ContextCache) Put

func (c *ContextCache) Put(propertyRID, placementID, providerID, sellerAgentURL, country string, resp *tmproto.ContextMatchResponse)

Put stores a response under the spec's canonical cache key. The TTL is derived from the response's cache_ttl per spec §Caching:

  • cache_ttl absent (nil pointer) → use the cache's configured default TTL (5 min out of the box).
  • cache_ttl == 0 → provider is disabling caching (e.g. after a targeting-config change). The entry is NOT stored; subsequent requests fan out live until the provider raises the TTL again.
  • cache_ttl > 0 → override, clamped to MaxContextCacheTTL. Clamping happens in seconds first to avoid a Duration multiplication overflowing int64 for pathologically large values that escaped upstream schema validation.

func (*ContextCache) Size

func (c *ContextCache) Size() int

Size returns the number of live entries. Includes entries whose TTL has expired but which have not been evicted yet — used mostly by tests and operational metrics, not by hot-path logic.

type ContextCacheMetrics

type ContextCacheMetrics interface {
	IncHit(providerID string)
	IncMiss(providerID string)
}

ContextCacheMetrics is the observability hook for the per-provider Context Match cache. Deployments wire this through prommetrics (or noop) via WithContextCache. Bounded labels only — providerID is the stable configured identifier, not user input.

type ContextCacheOption

type ContextCacheOption func(*ContextCache)

ContextCacheOption configures the cache.

func WithContextCacheMaxEntries

func WithContextCacheMaxEntries(n int) ContextCacheOption

WithContextCacheMaxEntries caps the number of live entries. Zero or negative values disable the cap. On Put once the cap is hit the cache sweeps expired entries first, then evicts the oldest insert if the cache is still full — bounding memory against a caller that varies placement/seller/country to grow the working set forever.

func WithContextCacheMetrics

func WithContextCacheMetrics(m ContextCacheMetrics) ContextCacheOption

WithContextCacheMetrics installs a metrics sink. Without this the cache runs with a no-op sink.

type Discovery

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

Discovery polls an HTTP endpoint for provider registrations and reconciles the router's provider set.

func NewDiscovery

func NewDiscovery(providers *ProviderSet, health *ProviderHealth, cfg DiscoveryConfig, latencyBudget time.Duration, opts ...DiscoveryOption) *Discovery

NewDiscovery creates a provider discovery poller.

func (*Discovery) Start

func (d *Discovery) Start()

Start begins background discovery polling. Call Stop to shut down.

func (*Discovery) Stop

func (d *Discovery) Stop()

Stop gracefully shuts down discovery.

type DiscoveryConfig

type DiscoveryConfig struct {
	Endpoint        string `json:"endpoint"`
	IntervalSeconds int    `json:"interval_seconds"` // default 30
	TimeoutSeconds  int    `json:"timeout_seconds"`  // default 10
}

DiscoveryConfig controls dynamic provider discovery. When Endpoint is empty, discovery is disabled.

type DiscoveryOption

type DiscoveryOption func(*Discovery)

DiscoveryOption configures a Discovery instance.

func WithDiscoveryClient

func WithDiscoveryClient(c *http.Client) DiscoveryOption

WithDiscoveryClient sets a custom HTTP client. For tests only — production should use the default client with safeDialContext.

func WithDiscoveryLogger

func WithDiscoveryLogger(l *slog.Logger) DiscoveryOption

WithDiscoveryLogger sets the logger for discovery.

type FanOutMetrics

type FanOutMetrics interface {
	IncExcluded(providerID string)
}

FanOutMetrics is called during fan-out to record provider exclusions. Out-of-tree implementations that satisfy this interface continue to work; the spec-aligned per-provider metrics added in v3.1 live on FanOutMetricsExt and are reached through a runtime type assertion so an existing FanOutMetrics impl does not need to be updated.

type FanOutMetricsExt

type FanOutMetricsExt interface {
	// ObserveProviderDuration records the per-provider end-to-end call latency
	// for a successful fan-out leg in milliseconds (tmp_provider_duration_ms).
	ObserveProviderDuration(providerID string, ms float64)
	// IncProviderTimeout records a per-provider call that exceeded its timeout
	// budget (tmp_provider_timeout_total).
	IncProviderTimeout(providerID string)
	// IncProviderError records a non-timeout per-provider call failure
	// (tmp_provider_error_total).
	IncProviderError(providerID string)
	// AddOffers records offers emitted in a Context Match response after the
	// per-provider responses are merged (tmp_offers_total).
	AddOffers(n int)
}

FanOutMetricsExt extends FanOutMetrics with the spec-named per-provider series from docs/trusted-match/router-architecture.mdx §Monitoring. An implementation that satisfies this interface (in addition to FanOutMetrics) will receive the additional callbacks; an impl that satisfies only FanOutMetrics still works and is the pre-existing surface.

type HealthCheckConfig

type HealthCheckConfig struct {
	IntervalSeconds int `json:"interval_seconds"` // default 30
	TimeoutSeconds  int `json:"timeout_seconds"`  // per-check timeout, default 5
}

HealthCheckConfig controls active provider health polling.

type HealthCheckMetrics

type HealthCheckMetrics interface {
	SetHealthStatus(providerID string, healthy bool)
	ObserveCheckDuration(providerID string, ms float64)
	IncExcluded(providerID string)
	IncRecovered(providerID string)
}

HealthCheckMetrics is the metrics interface used by the health checker. Implemented by the cmd layer to bridge to prommetrics without a direct dependency.

type HealthChecker

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

HealthChecker polls provider health endpoints in the background.

func NewHealthChecker

func NewHealthChecker(providers *ProviderSet, health *ProviderHealth, cfg HealthCheckConfig, opts ...HealthCheckerOption) *HealthChecker

NewHealthChecker creates a health checker that polls provider /health endpoints.

func (*HealthChecker) Preflight

func (hc *HealthChecker) Preflight(ctx context.Context)

Preflight checks all providers once. Logs warnings for unreachable providers but does not block startup. Does not record failures against the circuit breaker.

func (*HealthChecker) Start

func (hc *HealthChecker) Start()

Start begins background health polling. Call Stop to shut down.

func (*HealthChecker) Stop

func (hc *HealthChecker) Stop()

Stop gracefully shuts down the health checker.

type HealthCheckerOption

type HealthCheckerOption func(*HealthChecker)

HealthCheckerOption configures a HealthChecker.

func WithHealthCheckClient

func WithHealthCheckClient(c *http.Client) HealthCheckerOption

WithHealthCheckClient sets a custom HTTP client. For tests only — production should use the default client with safeDialContext.

func WithHealthCheckLogger

func WithHealthCheckLogger(l *slog.Logger) HealthCheckerOption

WithHealthCheckLogger sets the logger for the health checker.

func WithHealthCheckMetrics

func WithHealthCheckMetrics(m HealthCheckMetrics) HealthCheckerOption

WithHealthCheckMetrics sets the metrics callback for the health checker.

type HealthConfig

type HealthConfig struct {
	FailureThreshold int `json:"failure_threshold"`
	CooldownSeconds  int `json:"cooldown_seconds"`
}

HealthConfig controls circuit breaker behavior.

type ProviderConfig

type ProviderConfig struct {
	ID            string         `json:"id"`
	Endpoint      string         `json:"endpoint"`
	Status        ProviderStatus `json:"status,omitempty"` // default: active
	ContextMatch  bool           `json:"context_match"`
	IdentityMatch bool           `json:"identity_match"`
	WireFormats   []string       `json:"wire_formats"`

	// Provider-side filters — router skips this provider for non-matching requests.
	PropertyIDs        []string `json:"property_ids,omitempty"`         // Match on publisher's property_id slug (empty = all)
	PropertyRIDs       []string `json:"property_rids,omitempty"`        // Match on registry property_rid UUID (empty = all). Populated by discovery from ProviderRegistration.Properties.
	ExcludePropertyIDs []string `json:"exclude_property_ids,omitempty"` // Never send these (matches property_id slug)
	PropertyTypes      []string `json:"property_types,omitempty"`       // Only these types (empty = all)
	PackageIDs         []string `json:"package_ids,omitempty"`          // Only send these packages (empty = all)

	// Identity match routing — required when IdentityMatch is true.
	Countries []string `json:"countries,omitempty"` // ISO 3166-1 alpha-2 codes this provider serves
	UIDTypes  []string `json:"uid_types,omitempty"` // Identity types this provider can resolve

	// Verified-identity attestation (experimental, trusted_match.verified_identity).
	// HPKE key ids this provider can open. The router forwards a
	// sealed_credentials[] entry only to the provider whose AudienceKIDs
	// contains the entry's audience_kid — never broadcast — so a network-scoped
	// credential reaches only its owning audience. A provider that declares none
	// receives no sealed credentials.
	AudienceKIDs []string `json:"audience_kids,omitempty"`

	Timeout time.Duration `json:"timeout"`

	// Priority is documented in the schema (tmp/provider-registration.json) and
	// the spec config sample. Accepted on the wire so spec-aligned configs
	// parse, but has no effect on routing today: the upstream spec contradicts
	// itself between provider-registration.json ("router keeps the offer from
	// the higher-priority provider") and router-architecture.mdx (first-received
	// wins on duplicate package_id) — tracked at
	// https://github.com/adcontextprotocol/adcp/issues/5722. Wiring priority
	// through dedup/conflict-resolution waits on that resolution.
	Priority int `json:"priority,omitempty"`
}

ProviderConfig defines a registered TMP provider. Core fields (ID, Endpoint, Status, etc.) align with the schema-generated tmproto.ProviderRegistration type. Router-specific fields (PropertyIDs, ExcludePropertyIDs, etc.) extend the registration for routing logic.

func ProviderConfigFromRegistration

func ProviderConfigFromRegistration(r *tmproto.ProviderRegistration) ProviderConfig

ProviderConfigFromRegistration converts a schema-generated ProviderRegistration (the wire format from discovery endpoints) into a router ProviderConfig.

Note: Priority is not currently used by the router. It is captured on ProviderRegistration in the spec for future use (merge conflict resolution, adaptive timeout allocation) but has no effect on routing today.

func (*ProviderConfig) EffectiveStatus

func (p *ProviderConfig) EffectiveStatus() ProviderStatus

EffectiveStatus returns the provider's status, defaulting to active when empty.

func (*ProviderConfig) UnmarshalJSON

func (p *ProviderConfig) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts both the impl-internal field names (id, timeout) and the schema-aligned spec field names (provider_id, timeout_ms) so a config file that mirrors the wire schema in router-architecture.mdx parses without silently dropping fields. The schema names take precedence when both appear.

type ProviderHealth

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

ProviderHealth tracks success/failure/timeout stats and circuit breaker state.

func NewProviderHealth

func NewProviderHealth(failureThreshold int, cooldown time.Duration) *ProviderHealth

NewProviderHealth creates a health tracker. failureThreshold: consecutive failures before circuit opens. cooldown: how long circuit stays open before auto-recovery.

func (*ProviderHealth) DecrInflight

func (h *ProviderHealth) DecrInflight(providerID string)

DecrInflight decrements the in-flight request count for a provider.

func (*ProviderHealth) IncrInflight

func (h *ProviderHealth) IncrInflight(providerID string)

IncrInflight increments the in-flight request count for a provider.

func (*ProviderHealth) Inflight

func (h *ProviderHealth) Inflight(providerID string) int64

Inflight returns the current in-flight request count for a provider.

func (*ProviderHealth) IsCircuitOpen

func (h *ProviderHealth) IsCircuitOpen(providerID string) bool

IsCircuitOpen returns true if the provider's circuit breaker is open.

func (*ProviderHealth) RecordFailure

func (h *ProviderHealth) RecordFailure(providerID string)

RecordFailure records a provider failure and potentially opens the circuit.

func (*ProviderHealth) RecordSuccess

func (h *ProviderHealth) RecordSuccess(providerID string)

RecordSuccess records a successful provider call.

func (*ProviderHealth) RecordTimeout

func (h *ProviderHealth) RecordTimeout(providerID string)

RecordTimeout records a provider timeout (counted as failure for circuit breaker).

func (*ProviderHealth) Snapshot

func (h *ProviderHealth) Snapshot() map[string]ProviderStatsSnapshot

Snapshot returns a snapshot of all provider stats. Reads circuit state directly to avoid the write side-effect of IsCircuitOpen and to prevent a re-entrant lock attempt on the same RWMutex.

type ProviderSet

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

ProviderSet holds the current set of providers with atomic read access. Reads (Active, All) are lock-free via atomic.Value. Writes (Swap, SetStatus) are serialized by a mutex.

func NewProviderSet

func NewProviderSet(initial []ProviderConfig) *ProviderSet

NewProviderSet creates a ProviderSet with the given initial providers.

func (*ProviderSet) Active

func (ps *ProviderSet) Active() []ProviderConfig

Active returns providers with effective status "active". This is a cached snapshot — no allocation on the read path.

func (*ProviderSet) All

func (ps *ProviderSet) All() []ProviderConfig

All returns a snapshot of all providers.

func (*ProviderSet) Get

func (ps *ProviderSet) Get(id string) (ProviderConfig, bool)

Get returns the config for a single provider by ID.

func (*ProviderSet) SetStatus

func (ps *ProviderSet) SetStatus(id string, status ProviderStatus) bool

SetStatus updates a single provider's status via copy-on-write. Returns true if the provider was found.

func (*ProviderSet) Swap

func (ps *ProviderSet) Swap(next []ProviderConfig)

Swap atomically replaces the entire provider set.

type ProviderStatsSnapshot

type ProviderStatsSnapshot struct {
	Successes           int64 `json:"successes"`
	Failures            int64 `json:"failures"`
	Timeouts            int64 `json:"timeouts"`
	ConsecutiveFailures int64 `json:"consecutive_failures"`
	CircuitOpen         bool  `json:"circuit_open"`
	Inflight            int64 `json:"inflight"`
}

ProviderStatsSnapshot is a point-in-time snapshot of provider health stats.

type ProviderStatus

type ProviderStatus = tmproto.ProviderStatus

ProviderStatus is the schema-generated provider lifecycle type. Aliased here so the router package can use it without importing tmproto everywhere.

type Registry

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

Registry provides fast property_id → property_rid lookups and metadata resolution. Thread-safe for concurrent reads with periodic writes from sync.

func NewRegistry

func NewRegistry(snapshotURL, incrementalURL string) *Registry

NewRegistry creates a registry with the given sync endpoints.

func (*Registry) ApplyUpdate

func (r *Registry) ApplyUpdate(update *RegistryUpdate)

ApplyUpdate applies a single incremental update.

func (*Registry) AttachSigningKey

func (r *Registry) AttachSigningKey(propertyRID string, key tmproto.SigningKey) bool

AttachSigningKey adds a signing key to the property record for propertyRID. Idempotent on (kid, propertyRID): replaces any existing key with the same kid on that property. The key also becomes resolvable via LookupKey. Returns false if propertyRID is unknown.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of properties in the registry.

func (*Registry) HandleSnapshot

func (r *Registry) HandleSnapshot(w http.ResponseWriter, req *http.Request)

HandleSnapshot serves the current registry as a JSON snapshot. Agents call this to bootstrap or refresh their local copy.

func (*Registry) LoadFromData

func (r *Registry) LoadFromData(properties []RegistryProperty, sequence uint64)

LoadFromData replaces the entire registry with the given properties and sequence number. Callers pass a snapshot they have assembled elsewhere (a remote feed sync loop, a hand-built test fixture, etc.); the internal maps are swapped atomically. Any state a prior loader attached via ApplyUpdate / AttachSigningKey is dropped by the swap — callers that layer supplementary state (e.g. the router's own signing keys) must re-attach it after each LoadFromData call.

func (*Registry) LoadSnapshot

func (r *Registry) LoadSnapshot() error

LoadSnapshot fetches and applies a full registry snapshot.

func (*Registry) LookupByDomain

func (r *Registry) LookupByDomain(domain string) (string, bool)

LookupByDomain returns a property_id for a domain. O(1).

func (*Registry) LookupByID

func (r *Registry) LookupByID(propertyID string) (*RegistryProperty, bool)

LookupByID returns a property by its string ID. O(1).

func (*Registry) LookupByRID

func (r *Registry) LookupByRID(rid string) (*RegistryProperty, bool)

LookupByRID returns a property by its registry ID. O(1).

func (*Registry) LookupKey

func (r *Registry) LookupKey(kid string) (*tmproto.SigningKey, bool)

LookupKey resolves a kid to its SigningKey by scanning every property's signing-key list. Implements tmproto.KeyStore so a Registry can drive a verifier directly.

func (*Registry) PropertyRID

func (r *Registry) PropertyRID(propertyID string) string

PropertyRID returns the registry ID for a property_id. Returns "" if not found (unregistered property).

func (*Registry) Sequence

func (r *Registry) Sequence() uint64

Sequence returns the current registry sequence number.

type RegistryConfig

type RegistryConfig struct {
	FeedURL             string `json:"feed_url"`
	FeedToken           string `json:"feed_token,omitempty"`
	PollIntervalSeconds int    `json:"poll_interval_seconds,omitempty"`
	BootstrapLimit      int    `json:"bootstrap_limit,omitempty"`
	FeedLimit           int    `json:"feed_limit,omitempty"`
}

RegistryConfig points the router at the AdCP registry feed so it can serve real property metadata from `/registry/snapshot` instead of a stub. Left empty, the router falls back to seeding only the property RIDs it is authorized to sign for (dev-mode default).

The feed endpoint is the same one the reference context/identity agents consume: `${feed_url}/api/registry/feed` with bearer authentication. The router keeps the resulting property index in memory only — the router is stateless by design (see docs/trusted-match/router-architecture.mdx) so no persistent backend is exposed here.

func (RegistryConfig) Enabled

func (r RegistryConfig) Enabled() bool

Enabled reports whether the router should stand up a registry feed sync.

func (RegistryConfig) PollInterval

func (r RegistryConfig) PollInterval() time.Duration

PollInterval returns the poll interval as a duration, falling back to 30s when unset. Matches the default in the internal `registry` package so a router operator gets the same behavior as the reference agents.

type RegistryProperty

type RegistryProperty struct {
	PropertyID   string               `json:"property_id"`
	PropertyRID  string               `json:"property_rid"`
	PropertyType string               `json:"property_type"`
	Domain       string               `json:"domain"`
	Placements   []string             `json:"placements,omitempty"`
	SigningKeys  []tmproto.SigningKey `json:"signing_keys,omitempty"`
}

RegistryProperty represents a property in the registry.

type RegistrySnapshot

type RegistrySnapshot struct {
	Sequence   uint64             `json:"sequence"`
	Timestamp  time.Time          `json:"timestamp"`
	Properties []RegistryProperty `json:"properties"`
}

RegistrySnapshot is a full point-in-time view of the registry.

type RegistryUpdate

type RegistryUpdate struct {
	Sequence uint64           `json:"sequence"`
	Action   string           `json:"action"` // "add", "update", "remove"
	Property RegistryProperty `json:"property"`
}

RegistryUpdate is an incremental change to the registry.

type Router

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

Router fans out TMP requests to registered providers and merges responses.

func NewRouter

func NewRouter(providers []ProviderConfig, registry *Registry, health *ProviderHealth, opts ...RouterOption) (*Router, error)

NewRouter creates a router with the given provider configuration and registry. Returns an error if any provider endpoint fails SSRF validation.

Provider fan-outs are signed per the TMP spec §"Request Authentication" (Ed25519 over X-AdCP-Signature / X-AdCP-Key-Id). Pass WithTMPSigner to supply the signing key — without it, fan-outs go out unsigned and providers configured to require signatures will reject the requests.

func (*Router) DrainProvider

func (r *Router) DrainProvider(ctx context.Context, providerID string) error

DrainProvider sets a provider to draining status and waits for in-flight requests to complete. Once drained, the provider is set to inactive. Respects context cancellation — if the context expires, the provider is forcibly set to inactive.

func (*Router) HandleContextMatch

func (r *Router) HandleContextMatch(w http.ResponseWriter, req *http.Request)

HandleContextMatch processes a context match request.

func (*Router) HandleIdentityMatch

func (r *Router) HandleIdentityMatch(w http.ResponseWriter, req *http.Request)

HandleIdentityMatch processes an identity match request.

func (*Router) Providers

func (r *Router) Providers() *ProviderSet

Providers returns the router's provider set for use by health checkers and discovery.

type RouterConfig

type RouterConfig struct {
	ListenAddr string           `json:"listen_addr"`
	Providers  []ProviderConfig `json:"providers"`
}

RouterConfig defines the router's runtime configuration.

type RouterOption

type RouterOption func(*Router)

RouterOption configures a Router.

func WithContextCache

func WithContextCache(c *ContextCache) RouterOption

WithContextCache attaches a per-provider Context Match response cache (spec §Caching). Pass nil to disable caching — the router will fan out on every request.

func WithFanOutMetrics

func WithFanOutMetrics(m FanOutMetrics) RouterOption

WithFanOutMetrics sets the metrics callback for fan-out exclusion tracking.

func WithHTTPClient

func WithHTTPClient(c *http.Client) RouterOption

WithHTTPClient sets the HTTP client used for provider calls. Use this to inject custom TLS configuration, tracing middleware, or connection pooling when embedding the router in another system.

func WithLatencyBudget

func WithLatencyBudget(d time.Duration) RouterOption

WithLatencyBudget sets the overall fan-out latency budget. Per-provider timeouts are clamped to this value.

func WithLogger

func WithLogger(l *slog.Logger) RouterOption

WithLogger sets the logger for the router. Defaults to slog.Default().

func WithTMPSigner

func WithTMPSigner(signer *tmproto.Signer) RouterOption

WithTMPSigner attaches an Ed25519 signer that the router uses to sign every outbound /context and /identity request per the TMP specification. Required for any deployment that talks to spec-conformant providers. The router holds onto signer for the rest of its lifetime.

func WithoutEndpointValidation

func WithoutEndpointValidation() RouterOption

WithoutEndpointValidation disables SSRF validation of provider endpoints. For use in tests only — never use in production.

type ServerConfig

type ServerConfig struct {
	Addr            string            `json:"addr"`
	LatencyBudgetMs int               `json:"latency_budget_ms"`
	Providers       []ProviderConfig  `json:"providers"`
	Health          HealthConfig      `json:"health"`
	HealthCheck     HealthCheckConfig `json:"health_check"`
	Discovery       DiscoveryConfig   `json:"discovery"`
	Shutdown        ShutdownConfig    `json:"shutdown"`
	Signing         SigningConfig     `json:"signing"`
	TLS             TLSConfig         `json:"tls"`
	Registry        RegistryConfig    `json:"registry"`
	Cache           CacheConfig       `json:"cache"`
}

ServerConfig is the JSON/YAML config file format for the router.

func DefaultServerConfig

func DefaultServerConfig() *ServerConfig

DefaultServerConfig returns sensible defaults with no providers configured. Providers must be supplied via a config file or programmatically.

func LoadServerConfig

func LoadServerConfig(path string) (*ServerConfig, error)

LoadServerConfig reads a JSON or YAML config file and returns the config. Format is selected by file extension: .yaml/.yml use YAML, anything else (including .json and no extension) uses JSON. YAML is converted to JSON internally so all struct tags and custom UnmarshalJSON implementations on nested types (e.g. ProviderConfig schema-name aliases) are honored uniformly across formats. Invalid providers are logged and skipped rather than causing a hard error.

func (*ServerConfig) LatencyBudget

func (c *ServerConfig) LatencyBudget() time.Duration

LatencyBudget returns the latency budget as a time.Duration.

func (*ServerConfig) UnmarshalJSON

func (c *ServerConfig) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts the top-level field names from the spec config sample (`router-architecture.mdx`) — `listen` for the bind address and `health_check_interval_sec` for the active-check interval — so a verbatim copy of the documented sample doesn't silently bind to the default port or run the health checker at the default cadence. Schema-aligned names take precedence when both forms appear.

type ShutdownConfig

type ShutdownConfig struct {
	DrainSeconds int `json:"drain_seconds"`
}

ShutdownConfig controls graceful shutdown.

type SigningConfig

type SigningConfig struct {
	KeyID          string   `json:"key_id"`
	PrivateKeyPath string   `json:"private_key_path"`
	PropertyRIDs   []string `json:"property_rids,omitempty"`
	Disabled       bool     `json:"disabled,omitempty"`
}

SigningConfig configures the TMP request-authentication signer the router attaches to every provider fan-out, per the spec.

Deployers MUST set KeyID and PrivateKeyPath unless Disabled is true (dev only). PropertyRIDs lists the registry properties this signer is authorized to sign for; the router publishes its public key on each listed property so providers can verify by looking up the property → signing keys.

type TLSConfig

type TLSConfig struct {
	CertPath string `json:"cert"`
	KeyPath  string `json:"key"`
}

TLSConfig configures TLS termination in the router binary. When both CertPath and KeyPath are set, the router serves HTTPS. When both are empty, the router serves cleartext HTTP (typical when TLS is terminated by an upstream ingress/load balancer). Setting only one is a configuration error and Validate reports it.

The field names `cert` and `key` match the top-level `tls:` block in the spec's sample deployment YAML (docs/trusted-match/router-architecture.mdx).

func (TLSConfig) Enabled

func (t TLSConfig) Enabled() bool

Enabled reports whether TLS should be enabled based on the presence of both cert and key paths.

func (TLSConfig) Validate

func (t TLSConfig) Validate() error

Validate reports a configuration error when exactly one of cert/key is set, which almost always indicates a missing env var or a typo the operator would rather find at startup than after their next cert rotation.

Jump to

Keyboard shortcuts

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