Add first-party verdict and guards to tracker mapping

The tracker-pattern catalog was binary (linked to a vendor or not), so
generic and first-party artifacts (loglevel keys, wallet-extension keys,
an org's own trackers) were retried forever and, once one row was wrongly
attributed, re-propagated to every organization with no re-check.

Give catalog rows a terminal attribution verdict (UNDETERMINED,
THIRD_PARTY, FIRST_PARTY): FIRST_PARTY short-circuits the whole mapping
pipeline so the artifact is never attributed again. Gate deterministic
vendor adoption behind a trust bar so only curated/operator rows
auto-propagate; lower-confidence agent/heuristic rows are reused as hints
and re-resolved, and an independent agent re-confirmation corroborates and
promotes them. Make the mapping agent emit an evidence source and reject
any attribution that lacks concrete evidence, and let it declare a
first-party verdict. Skip the speculative agent for PRE_EXISTING-source
patterns, whose low signal invites invented vendors.

Add proboctl "ctp mark-first-party" and an --attribution list filter to
audit and remediate existing wrong links, and a cursor rule documenting
migration naming so the timestamp is taken from date -u, not invented.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-06-16 13:51:31 +02:00
parent 1faa60bfba
commit 7723b33aec
16 changed files with 1277 additions and 48 deletions

View File

@@ -430,3 +430,179 @@ func TestCommonTrackerPattern_ResetStaleEnrichments_RespectsMaxAttempts(t *testi
reloadedExhausted := loadCommonTrackerPattern(t, ctx, client, exhausted.ID)
assert.Nil(t, reloadedExhausted.EnrichmentRequestedAt, "row at the max-attempts ceiling must not be re-queued")
}
// TestCommonTrackerPatternAttribution_IsValid pins the enum's accepted
// values.
func TestCommonTrackerPatternAttribution_IsValid(t *testing.T) {
t.Parallel()
for _, v := range coredata.CommonTrackerPatternAttributions() {
assert.True(t, v.IsValid(), "%q must be valid", v)
}
assert.False(t, coredata.CommonTrackerPatternAttribution("").IsValid())
assert.False(t, coredata.CommonTrackerPatternAttribution("nonsense").IsValid())
}
// TestCommonTrackerPattern_Upsert_RoundTripsAttribution pins that the
// attribution verdict is persisted and read back, and that an empty
// verdict defaults to UNDETERMINED.
func TestCommonTrackerPattern_Upsert_RoundTripsAttribution(t *testing.T) {
t.Parallel()
client := test.PGClient(t)
ctx := context.Background()
now := time.Now().UTC().Truncate(time.Microsecond)
cp := coredata.CommonTrackerPattern{
ID: gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType),
TrackerType: coredata.TrackerTypeLocalStorage,
Pattern: "attr_default_" + gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType).String(),
MatchType: coredata.TrackerPatternMatchTypeExact,
Confidence: 0.5,
CreatedAt: now,
UpdatedAt: now,
}
insertCommonTrackerPattern(t, ctx, client, cp)
reloaded := loadCommonTrackerPattern(t, ctx, client, cp.ID)
assert.Equal(t, coredata.CommonTrackerPatternAttributionUndetermined, reloaded.Attribution, "empty verdict must default to UNDETERMINED")
}
// TestCommonTrackerPattern_Upsert_PreservesFirstPartyVerdict pins the
// terminal contract: once a row is FIRST_PARTY, an automated upsert that
// carries a vendor neither flips the verdict nor attaches the vendor.
func TestCommonTrackerPattern_Upsert_PreservesFirstPartyVerdict(t *testing.T) {
t.Parallel()
client := test.PGClient(t)
ctx := context.Background()
party := seedCommonThirdParty(t, ctx, client)
now := time.Now().UTC().Truncate(time.Microsecond)
pattern := "first_party_terminal_" + gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType).String()
firstParty := coredata.CommonTrackerPattern{
ID: gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType),
TrackerType: coredata.TrackerTypeLocalStorage,
Pattern: pattern,
MatchType: coredata.TrackerPatternMatchTypeExact,
Confidence: 0.8,
Attribution: coredata.CommonTrackerPatternAttributionFirstParty,
CreatedAt: now,
UpdatedAt: now,
}
insertCommonTrackerPattern(t, ctx, client, firstParty)
// An automated upsert (same key) that tries to attach a vendor.
intruder := coredata.CommonTrackerPattern{
ID: gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType),
CommonThirdPartyID: &party.ID,
TrackerType: coredata.TrackerTypeLocalStorage,
Pattern: pattern,
MatchType: coredata.TrackerPatternMatchTypeExact,
Confidence: 0.7,
Attribution: coredata.CommonTrackerPatternAttributionThirdParty,
CreatedAt: now,
UpdatedAt: now.Add(time.Minute),
}
require.NoError(t, client.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
_, err := intruder.Upsert(ctx, tx)
return err
}))
reloaded := loadCommonTrackerPattern(t, ctx, client, firstParty.ID)
assert.Equal(t, coredata.CommonTrackerPatternAttributionFirstParty, reloaded.Attribution, "FIRST_PARTY verdict must survive an automated upsert")
assert.Nil(t, reloaded.CommonThirdPartyID, "a terminal first-party row must stay vendor-free")
}
// TestCommonTrackerPatterns_SetAttributionByIDs pins that the operator
// helper records the verdict and clears any vendor link.
func TestCommonTrackerPatterns_SetAttributionByIDs(t *testing.T) {
t.Parallel()
client := test.PGClient(t)
ctx := context.Background()
party := seedCommonThirdParty(t, ctx, client)
now := time.Now().UTC().Truncate(time.Microsecond)
linked := coredata.CommonTrackerPattern{
ID: gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType),
CommonThirdPartyID: &party.ID,
TrackerType: coredata.TrackerTypeCookie,
Pattern: "to_first_party_" + gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType).String(),
MatchType: coredata.TrackerPatternMatchTypeExact,
Confidence: 0.8,
Attribution: coredata.CommonTrackerPatternAttributionThirdParty,
CreatedAt: now,
UpdatedAt: now,
}
insertCommonTrackerPattern(t, ctx, client, linked)
var affected int64
require.NoError(t, client.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
var ps coredata.CommonTrackerPatterns
var err error
affected, err = ps.SetAttributionByIDs(ctx, tx, []gid.GID{linked.ID}, coredata.CommonTrackerPatternAttributionFirstParty)
return err
}))
assert.Equal(t, int64(1), affected)
reloaded := loadCommonTrackerPattern(t, ctx, client, linked.ID)
assert.Equal(t, coredata.CommonTrackerPatternAttributionFirstParty, reloaded.Attribution)
assert.Nil(t, reloaded.CommonThirdPartyID, "marking first-party must clear the vendor link")
}
// TestCommonTrackerPatterns_RelinkCommonThirdPartyByIDs_SetsAttribution
// pins that linking sets THIRD_PARTY and unlinking returns the row to
// UNDETERMINED.
func TestCommonTrackerPatterns_RelinkCommonThirdPartyByIDs_SetsAttribution(t *testing.T) {
t.Parallel()
client := test.PGClient(t)
ctx := context.Background()
party := seedCommonThirdParty(t, ctx, client)
now := time.Now().UTC().Truncate(time.Microsecond)
row := coredata.CommonTrackerPattern{
ID: gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType),
TrackerType: coredata.TrackerTypeCookie,
Pattern: "relink_attr_" + gid.New(gid.NilTenant, coredata.CommonTrackerPatternEntityType).String(),
MatchType: coredata.TrackerPatternMatchTypeExact,
Confidence: 0.5,
Attribution: coredata.CommonTrackerPatternAttributionUndetermined,
CreatedAt: now,
UpdatedAt: now,
}
insertCommonTrackerPattern(t, ctx, client, row)
require.NoError(t, client.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
var ps coredata.CommonTrackerPatterns
_, err := ps.RelinkCommonThirdPartyByIDs(ctx, tx, []gid.GID{row.ID}, &party.ID)
return err
}))
linked := loadCommonTrackerPattern(t, ctx, client, row.ID)
assert.Equal(t, coredata.CommonTrackerPatternAttributionThirdParty, linked.Attribution, "linking must set THIRD_PARTY")
require.NotNil(t, linked.CommonThirdPartyID)
assert.Equal(t, party.ID, *linked.CommonThirdPartyID)
assert.Equal(t, float32(1), linked.Confidence, "linking must bump confidence to the curated tier")
require.NoError(t, client.WithTx(ctx, func(ctx context.Context, tx pg.Tx) error {
var ps coredata.CommonTrackerPatterns
_, err := ps.RelinkCommonThirdPartyByIDs(ctx, tx, []gid.GID{row.ID}, nil)
return err
}))
unlinked := loadCommonTrackerPattern(t, ctx, client, row.ID)
assert.Equal(t, coredata.CommonTrackerPatternAttributionUndetermined, unlinked.Attribution, "unlinking must return the verdict to UNDETERMINED")
assert.Nil(t, unlinked.CommonThirdPartyID)
}