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

@@ -1184,3 +1184,68 @@ WHERE id = @id
return nil
}
// RequestMappingForUnmappedSiblings re-arms mapping_requested_at on
// sibling tracker patterns of the same banner that share an initiator
// domain with the just-mapped pattern but are still unpromoted. It is
// the backward-propagation counterpart to the mapping worker's
// sibling-origin matching: when a pattern newly resolves a vendor, its
// siblings that were processed earlier and left unmatched can now be
// re-evaluated against it.
//
// Only unpromoted (third_party_id IS NULL), not-already-queued
// (mapping_requested_at IS NULL), non-extension siblings are touched, so
// a fully mapped banner re-enqueues nothing. detected_trackers is used
// only as a filtering subquery. Returns the number of siblings
// re-enqueued.
func (tps *TrackerPatterns) RequestMappingForUnmappedSiblings(
ctx context.Context,
tx pg.Tx,
scope Scoper,
cookieBannerID gid.GID,
excludePatternID gid.GID,
domains []string,
) (int64, error) {
if len(domains) == 0 {
return 0, nil
}
q := `
UPDATE tracker_patterns
SET
mapping_requested_at = NOW(),
updated_at = NOW()
WHERE
%[1]s
AND cookie_banner_id = @cookie_banner_id
AND id != @exclude_pattern_id
AND third_party_id IS NULL
AND mapping_requested_at IS NULL
AND (source IS NULL OR source != @extension_source)
AND id IN (
SELECT DISTINCT tracker_pattern_id
FROM detected_trackers
WHERE %[1]s
AND cookie_banner_id = @cookie_banner_id
AND initiator_domain = ANY(@domains)
AND tracker_pattern_id IS NOT NULL
)
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"cookie_banner_id": cookieBannerID,
"exclude_pattern_id": excludePatternID,
"extension_source": CookieSourceExtension,
"domains": domains,
}
maps.Copy(args, scope.SQLArguments())
result, err := tx.Exec(ctx, q, args)
if err != nil {
return 0, fmt.Errorf("cannot request mapping for unmapped siblings: %w", err)
}
return result.RowsAffected(), nil
}