Harden catalog vendor resolution and agent prompt

Address review feedback on the agent-driven tracker catalog path:

- Return initiator-domain load failures instead of swallowing them,
  so the worker retries rather than running the agent on partial
  context.
- In the resolver, treat only ErrResourceNotFound as a catalog miss
  and propagate genuine name/slug lookup errors.
- Insert the new vendor inside a savepoint and, on the slug
  unique-violation race, reload and return the winning row instead of
  aborting the caller's transaction.
- Stop seeding common_third_party_domains from observed initiator
  domains. They are a co-occurrence signal, not verified ownership,
  and writing them into the global cross-tenant catalog pollutes the
  domain-based matcher. The curated seed owns that data.
- Warn the mapping agent that observed domains may belong to shared
  CDNs, tag managers, or hosting infrastructure rather than the
  vendor, so it does not attribute on that basis alone.
- Extract a shared tracker-identification prompt helper and move the
  common-pattern identification prompt next to the enrichment agent.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-29 20:04:58 +02:00
parent c8b7615046
commit 54c05ebe6a
6 changed files with 70 additions and 64 deletions

View File

@@ -137,40 +137,19 @@ func trackerMappingInstructions(_ context.Context, _ *agent.Agent) string {
)
}
func buildAgentPrompt(tp coredata.TrackerPattern, domains []string) string {
// buildTrackerIdentificationPrompt renders the base mapping-agent input
// shared by live tracker patterns and global catalog patterns: the four
// XML signal tags plus the max-age preamble. Callers append any extra
// signals (e.g. observed domains) to the returned prompt.
func buildTrackerIdentificationPrompt(
pattern string,
trackerType coredata.TrackerType,
matchType coredata.TrackerPatternMatchType,
maxAgeSeconds *int,
) string {
maxAge := "session"
if tp.MaxAgeSeconds != nil {
maxAge = fmt.Sprintf("%d seconds", *tp.MaxAgeSeconds)
}
prompt := fmt.Sprintf(
"Identify the following tracker:\n\n"+
"<pattern> %s </pattern>\n"+
"<type> %s </type>\n"+
"<match_type> %s </match_type>\n"+
"<max_age> %s </max_age>\n",
tp.Pattern,
tp.TrackerType,
tp.MatchType,
maxAge,
)
if len(domains) > 0 {
prompt += fmt.Sprintf("<observed_domains> %s </observed_domains>\n", strings.Join(domains, ", "))
}
return prompt
}
// buildCommonPatternIdentificationPrompt builds the mapping-agent input
// for a global catalog pattern. Catalog rows carry no observed domains,
// so the prompt omits the <observed_domains> signal and relies on the
// pattern name, type, and naming conventions. It lets the enrichment
// worker reuse the mapping agent to attribute a vendor before describing.
func buildCommonPatternIdentificationPrompt(cp coredata.CommonTrackerPattern) string {
maxAge := "session"
if cp.MaxAgeSeconds != nil {
maxAge = fmt.Sprintf("%d seconds", *cp.MaxAgeSeconds)
if maxAgeSeconds != nil {
maxAge = fmt.Sprintf("%d seconds", *maxAgeSeconds)
}
return fmt.Sprintf(
@@ -179,9 +158,19 @@ func buildCommonPatternIdentificationPrompt(cp coredata.CommonTrackerPattern) st
"<type> %s </type>\n"+
"<match_type> %s </match_type>\n"+
"<max_age> %s </max_age>\n",
cp.Pattern,
cp.TrackerType,
cp.MatchType,
pattern,
trackerType,
matchType,
maxAge,
)
}
func buildAgentPrompt(tp coredata.TrackerPattern, domains []string) string {
prompt := buildTrackerIdentificationPrompt(tp.Pattern, tp.TrackerType, tp.MatchType, tp.MaxAgeSeconds)
if len(domains) > 0 {
prompt += fmt.Sprintf("<observed_domains> %s </observed_domains>\n", strings.Join(domains, ", "))
}
return prompt
}