Re-enqueue unmapped siblings after mapping

The tracker-mapping worker processes one pattern at a time and
matchBySiblingOrigin only reads already-resolved siblings, so vendor
propagation across a banner was forward-only. A sibling processed
before its peer resolved a vendor (for example, one that failed the
agent and fell back to an unmatched catalog row) was never revisited,
even once a later sibling clearly identified the same third party.

When a Process run newly establishes a common third party, re-arm
mapping_requested_at on same-banner siblings that share an initiator
domain and are still unpromoted and non-extension-sourced. The worker
re-claims them and matchBySiblingOrigin now finds the freshly mapped
pattern. Guarding on third_party_id IS NULL, mapping_requested_at IS
NULL, and a not-pre-existing common third party keeps cascades finite.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-29 01:10:59 +02:00
parent c11bc57c36
commit 8c10997681
3 changed files with 471 additions and 1 deletions

View File

@@ -131,6 +131,7 @@ func (h *trackerMappingHandler) Process(ctx context.Context, tp coredata.Tracker
return fmt.Errorf("cannot load cookie banner for domain filtering: %w", err)
}
// FIXME: remove
banner.Origin = "https://t.probo.com"
var (
@@ -160,12 +161,23 @@ func (h *trackerMappingHandler) Process(ctx context.Context, tp coredata.Tracker
}
}
// Whether a catalog third party was already known before the
// signal pipeline ran this round. Re-enqueuing siblings is
// only useful when this run is the one that resolves the
// vendor; a pre-existing link adds no new signal and gating
// on it keeps cascades finite.
commonThirdPartyPreexisted := commonThirdPartyID != nil
var domains []string
if commonThirdPartyID == nil {
domains, err := h.loadInitiatorDomains(ctx, tx, tp)
loaded, err := h.loadInitiatorDomains(ctx, tx, tp)
if err != nil {
return err
}
domains = loaded
// Sibling matching is an org-local co-occurrence signal:
// two patterns served from the same origin on the same
// banner are likely the same vendor, even when that origin
@@ -268,6 +280,17 @@ func (h *trackerMappingHandler) Process(ctx context.Context, tp coredata.Tracker
log.String("pattern", tp.Pattern),
log.String("tracker_pattern_id", tp.ID.String()),
)
// This run newly resolved a catalog third party, so
// same-banner siblings that share an initiator domain but
// were processed earlier and left unmatched can now match
// against it. Re-arm their mapping so the worker revisits
// them; the guards keep already-mapped siblings untouched.
if commonThirdPartyID != nil && !commonThirdPartyPreexisted {
if err := h.reenqueueUnmappedSiblings(ctx, tx, tp, domains); err != nil {
return err
}
}
}
return nil
@@ -275,6 +298,43 @@ func (h *trackerMappingHandler) Process(ctx context.Context, tp coredata.Tracker
)
}
// reenqueueUnmappedSiblings re-arms mapping_requested_at on same-banner
// siblings sharing an initiator domain with tp that are still unpromoted,
// so the worker re-evaluates them now that tp resolved a vendor.
func (h *trackerMappingHandler) reenqueueUnmappedSiblings(
ctx context.Context,
tx pg.Tx,
tp coredata.TrackerPattern,
domains []string,
) error {
scope := coredata.NewScopeFromObjectID(tp.ID)
var patterns coredata.TrackerPatterns
count, err := patterns.RequestMappingForUnmappedSiblings(
ctx,
tx,
scope,
tp.CookieBannerID,
tp.ID,
domains,
)
if err != nil {
return fmt.Errorf("cannot re-enqueue unmapped siblings: %w", err)
}
if count > 0 {
h.logger.InfoCtx(
ctx,
"re-enqueued unmapped sibling tracker patterns",
log.String("tracker_pattern_id", tp.ID.String()),
log.Int64("count", count),
)
}
return nil
}
// firstNonNil returns a when it is set, otherwise b. It keeps the first
// catalog row id resolved by the pipeline stable: later signals upsert
// the same row (same key) and return the same id, but the explicit guard