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:
@@ -15,11 +15,14 @@
|
||||
package cookiebanner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
func TestNameMatchesSiteDomain(t *testing.T) {
|
||||
@@ -130,6 +133,204 @@ func TestNameIsCookieDatabaseAggregator(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvidenceSupportsAttribution(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
evidence string
|
||||
expected bool
|
||||
}{
|
||||
{name: "database match supports", evidence: evidenceSourceDatabaseMatch, expected: true},
|
||||
{name: "naming convention supports", evidence: evidenceSourceNamingConvention, expected: true},
|
||||
{name: "web search supports", evidence: evidenceSourceWebSearch, expected: true},
|
||||
{name: "browser page supports", evidence: evidenceSourceBrowserPage, expected: true},
|
||||
{name: "none does not support", evidence: evidenceSourceNone, expected: false},
|
||||
{name: "empty does not support", evidence: "", expected: false},
|
||||
{name: "unknown value does not support", evidence: "vibes", expected: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name,
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, tt.expected, evidenceSupportsAttribution(tt.evidence))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretCatalogRow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vendorID := gid.New(gid.NilTenant, coredata.CommonThirdPartyEntityType)
|
||||
|
||||
t.Run(
|
||||
"first party verdict is terminal",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
adopt, untrusted, firstParty := interpretCatalogRow(coredata.CommonTrackerPattern{
|
||||
CommonThirdPartyID: &vendorID,
|
||||
Confidence: 1,
|
||||
Attribution: coredata.CommonTrackerPatternAttributionFirstParty,
|
||||
})
|
||||
|
||||
assert.Nil(t, adopt)
|
||||
assert.Nil(t, untrusted)
|
||||
assert.True(t, firstParty)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"trusted vendor is adopted",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
adopt, untrusted, firstParty := interpretCatalogRow(coredata.CommonTrackerPattern{
|
||||
CommonThirdPartyID: &vendorID,
|
||||
Confidence: trustedAttributionConfidence,
|
||||
Attribution: coredata.CommonTrackerPatternAttributionThirdParty,
|
||||
})
|
||||
|
||||
require.NotNil(t, adopt)
|
||||
assert.Equal(t, vendorID, *adopt)
|
||||
assert.Nil(t, untrusted)
|
||||
assert.False(t, firstParty)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"low-confidence vendor is untrusted, not adopted",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
adopt, untrusted, firstParty := interpretCatalogRow(coredata.CommonTrackerPattern{
|
||||
CommonThirdPartyID: &vendorID,
|
||||
Confidence: agentSourceConfidence,
|
||||
Attribution: coredata.CommonTrackerPatternAttributionThirdParty,
|
||||
})
|
||||
|
||||
assert.Nil(t, adopt)
|
||||
require.NotNil(t, untrusted)
|
||||
assert.Equal(t, vendorID, *untrusted)
|
||||
assert.False(t, firstParty)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"unlinked row yields nothing",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
adopt, untrusted, firstParty := interpretCatalogRow(coredata.CommonTrackerPattern{
|
||||
Confidence: 0.5,
|
||||
Attribution: coredata.CommonTrackerPatternAttributionUndetermined,
|
||||
})
|
||||
|
||||
assert.Nil(t, adopt)
|
||||
assert.Nil(t, untrusted)
|
||||
assert.False(t, firstParty)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestIsPreExistingSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
preExisting := coredata.CookieSourcePreExisting
|
||||
script := coredata.CookieSourceScript
|
||||
|
||||
assert.True(t, isPreExistingSource(coredata.TrackerPattern{Source: &preExisting}))
|
||||
assert.False(t, isPreExistingSource(coredata.TrackerPattern{Source: &script}))
|
||||
assert.False(t, isPreExistingSource(coredata.TrackerPattern{Source: nil}))
|
||||
}
|
||||
|
||||
func TestVendorAttributionRejected(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := newMappingHandler(nil)
|
||||
ctx := context.Background()
|
||||
tp := coredata.TrackerPattern{Pattern: "_x", TrackerType: coredata.TrackerTypeCookie}
|
||||
|
||||
confident := func(mut func(*TrackerMappingAgentResult)) TrackerMappingAgentResult {
|
||||
r := TrackerMappingAgentResult{
|
||||
ThirdPartyName: "Acme Analytics",
|
||||
Category: coredata.ThirdPartyCategoryAnalytics,
|
||||
ThirdPartyConfidence: 0.9,
|
||||
EvidenceSource: evidenceSourceNamingConvention,
|
||||
}
|
||||
if mut != nil {
|
||||
mut(&r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
t.Run(
|
||||
"accepts a confident, evidence-backed attribution",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.False(t, h.vendorAttributionRejected(ctx, tp, confident(nil), "https://example.com"))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects below confidence threshold",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := confident(func(r *TrackerMappingAgentResult) { r.ThirdPartyConfidence = 0.3 })
|
||||
assert.True(t, h.vendorAttributionRejected(ctx, tp, r, "https://example.com"))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects empty name",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := confident(func(r *TrackerMappingAgentResult) { r.ThirdPartyName = "" })
|
||||
assert.True(t, h.vendorAttributionRejected(ctx, tp, r, "https://example.com"))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects when evidence source is none",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := confident(func(r *TrackerMappingAgentResult) { r.EvidenceSource = evidenceSourceNone })
|
||||
assert.True(t, h.vendorAttributionRejected(ctx, tp, r, "https://example.com"))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects when evidence source is empty",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := confident(func(r *TrackerMappingAgentResult) { r.EvidenceSource = "" })
|
||||
assert.True(t, h.vendorAttributionRejected(ctx, tp, r, "https://example.com"))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects when name matches scanned site",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := confident(func(r *TrackerMappingAgentResult) { r.ThirdPartyName = "Example" })
|
||||
assert.True(t, h.vendorAttributionRejected(ctx, tp, r, "https://example.com"))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects cookie-database aggregator",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := confident(func(r *TrackerMappingAgentResult) { r.ThirdPartyName = "Cookiepedia" })
|
||||
assert.True(t, h.vendorAttributionRejected(ctx, tp, r, "https://example.com"))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestBuildAgentPrompt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user