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

@@ -62,17 +62,59 @@ const (
// rather than the pattern, so the stored row confidence is a
// constant like the other heuristic signals (domain, sibling).
agentSourceConfidence = 0.8
// trustedAttributionConfidence is the bar a catalog row must meet for
// its third party to be adopted deterministically by another pattern
// (the existing-link and matchByPattern paths). Only curated/seed rows
// and operator links (confidence 1.0) clear it; agent (0.8) and
// domain/sibling (0.7) attributions do not, so a single low-confidence
// guess never becomes an authoritative precedent that auto-propagates
// across organizations. Such rows are reused as hints only: the
// pattern falls through to the evidence-guarded agent, which can
// corroborate the guess (promoting the row to this tier) or override
// it.
trustedAttributionConfidence float32 = 0.9
)
//go:embed prompts/tracker_identification.txt.tmpl
var trackerIdentificationPrompt string
// Tracker-mapping evidence kinds. The agent must report which concrete
// evidence backs a vendor attribution; an attribution without one of the
// substantive kinds (i.e. "none" or empty) is discarded so the agent
// never attributes a vendor from general knowledge or vague similarity.
const (
evidenceSourceDatabaseMatch = "database_match"
evidenceSourceNamingConvention = "naming_convention"
evidenceSourceWebSearch = "web_search"
evidenceSourceBrowserPage = "browser_page"
evidenceSourceNone = "none"
)
// TrackerMappingAgentResult is the structured output the tracker-mapping
// agent returns.
type TrackerMappingAgentResult struct {
ThirdPartyName string `json:"third_party_name" jsonschema:"Name of the company or service that sets this tracker (e.g. 'Google Analytics', 'Meta Pixel'). Empty string if truly unknown."`
Category coredata.ThirdPartyCategory `json:"category" jsonschema:"Third party category"`
ThirdPartyConfidence float64 `json:"third_party_confidence" jsonschema:"Confidence (0.0 to 1.0) in which company or service set this tracker, independent of whether the artifact is a classic web tracker. Set below 0.5 if unsure who set it."`
EvidenceSource string `json:"evidence_source" jsonschema:"The concrete evidence that backs the attribution: 'database_match' (exact pattern in the database), 'naming_convention' (the tracker's meaningful prefix or an embedded vendor name), 'web_search' (a web result naming the setter), 'browser_page' (a page you opened that names the setter), or 'none' when there is no concrete evidence. Must be 'none' whenever third_party_name is empty."`
IsFirstParty bool `json:"is_first_party" jsonschema:"True when the artifact has no third party at all: it is the scanned site's own tracker, a generic library or log key (e.g. 'loglevel'), a browser-extension key that embeds the scanned site's origin, or otherwise not attributable to any external vendor. Leave false when a vendor is or might be responsible."`
}
// evidenceSupportsAttribution reports whether the agent supplied a
// concrete evidence kind for a vendor attribution. An empty value or
// "none" (or any unrecognized value) does not support an attribution.
func evidenceSupportsAttribution(evidenceSource string) bool {
switch evidenceSource {
case
evidenceSourceDatabaseMatch,
evidenceSourceNamingConvention,
evidenceSourceWebSearch,
evidenceSourceBrowserPage:
return true
}
return false
}
// buildTrackerMappingAgent builds the tracker-mapping agent. extraTools