Documentation
¶
Index ¶
- Constants
- func MatchesContextProvider(req *tmproto.ContextMatchRequest, p *ProviderConfig) bool
- func MatchesIdentityProvider(req *tmproto.IdentityMatchRequest, p *ProviderConfig) bool
- func ValidateContextRequest(req *tmproto.ContextMatchRequest) error
- func ValidateIdentityRequest(req *tmproto.IdentityMatchRequest) error
- func ValidateProviderConfig(p *ProviderConfig, latencyBudget time.Duration) error
- func ValidateProviderEndpoint(endpoint string) error
- type CacheConfig
- type ContextCache
- type ContextCacheMetrics
- type ContextCacheOption
- type Discovery
- type DiscoveryConfig
- type DiscoveryOption
- type FanOutMetrics
- type FanOutMetricsExt
- type HealthCheckConfig
- type HealthCheckMetrics
- type HealthChecker
- type HealthCheckerOption
- type HealthConfig
- type ProviderConfig
- type ProviderHealth
- func (h *ProviderHealth) DecrInflight(providerID string)
- func (h *ProviderHealth) IncrInflight(providerID string)
- func (h *ProviderHealth) Inflight(providerID string) int64
- func (h *ProviderHealth) IsCircuitOpen(providerID string) bool
- func (h *ProviderHealth) RecordFailure(providerID string)
- func (h *ProviderHealth) RecordSuccess(providerID string)
- func (h *ProviderHealth) RecordTimeout(providerID string)
- func (h *ProviderHealth) Snapshot() map[string]ProviderStatsSnapshot
- type ProviderSet
- type ProviderStatsSnapshot
- type ProviderStatus
- type Registry
- func (r *Registry) ApplyUpdate(update *RegistryUpdate)
- func (r *Registry) AttachSigningKey(propertyRID string, key tmproto.SigningKey) bool
- func (r *Registry) Count() int
- func (r *Registry) HandleSnapshot(w http.ResponseWriter, req *http.Request)
- func (r *Registry) LoadFromData(properties []RegistryProperty, sequence uint64)
- func (r *Registry) LoadSnapshot() error
- func (r *Registry) LookupByDomain(domain string) (string, bool)
- func (r *Registry) LookupByID(propertyID string) (*RegistryProperty, bool)
- func (r *Registry) LookupByRID(rid string) (*RegistryProperty, bool)
- func (r *Registry) LookupKey(kid string) (*tmproto.SigningKey, bool)
- func (r *Registry) PropertyRID(propertyID string) string
- func (r *Registry) Sequence() uint64
- type RegistryConfig
- type RegistryProperty
- type RegistrySnapshot
- type RegistryUpdate
- type Router
- type RouterConfig
- type RouterOption
- func WithContextCache(c *ContextCache) RouterOption
- func WithFanOutMetrics(m FanOutMetrics) RouterOption
- func WithHTTPClient(c *http.Client) RouterOption
- func WithLatencyBudget(d time.Duration) RouterOption
- func WithLogger(l *slog.Logger) RouterOption
- func WithTMPSigner(signer *tmproto.Signer) RouterOption
- func WithoutEndpointValidation() RouterOption
- type ServerConfig
- type ShutdownConfig
- type SigningConfig
- type TLSConfig
Constants ¶
const ( ProviderStatusActive = tmproto.ProviderStatusActive ProviderStatusInactive = tmproto.ProviderStatusInactive ProviderStatusDraining = tmproto.ProviderStatusDraining )
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.
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.
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.
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 ¶
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 ¶
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.
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 ¶
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) 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 ¶
LoadSnapshot fetches and applies a full registry snapshot.
func (*Registry) LookupByDomain ¶
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 ¶
PropertyRID returns the registry ID for a property_id. Returns "" if not found (unregistered property).
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 ¶
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 ¶
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).