Commit Graph

17 Commits

Author SHA1 Message Date
Émile Ré
145aeaf402 Restore tracker mapping linking, drop only create
The tracker-mapping worker had been reduced to catalog resolution only,
which removed not just the auto-creation of an org ThirdParty but also
the auto-linking of an existing one. Only the creation needed to go: it
raced the load-then-create check and produced duplicate vendors.

Restore the full org ThirdParty resolution (exact common-id link,
sibling direct-link, high-confidence heuristic, and the disambiguation
agent) and remove only the CreateFromCommon branch and its
categorisation gate. When nothing matches, the worker now leaves
third_party_id unset rather than creating a vendor; creation happens
exclusively through the explicit ImportFromCommon action. Drop the
now-dead CreateFromCommon helper and rename match.go to common_match.go.

Fix a latent test bug surfaced by actually running the DB-backed suite
(skipped in CI without Postgres): the heuristic-match candidate lacked
Level 1, so the level-filtered candidate loader excluded it and the old
fallback create masked the miss.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:22 +02:00
Sacha Al Himdani
9ac71f948f Update contact email to hello@probo.com
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-09 16:45:23 +02:00
Émile Ré
591a051e0a Split tracker patterns on colon and dot separators
The pattern-analysis tokenizer split names only on underscore first,
then on dash. A key like "name_done:<uuid>" left the colon glued to
the leading hex group, so the trailing UUID was never recognised as a
single token and instead shredded on dash into short hex anchors. The
derived template kept those anchors fixed, producing a distinct glob
per UUID and preventing any merge.

Treat colon and dot as primary separators alongside underscore so the
embedded UUID is isolated, matched by isUUIDShape, and collapsed to a
wildcard. Extend templateHasFixedAnchor to ignore the new separators
so a separator-only template stays rejected by the anti-overmerge
guard.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-08 18:38:45 +02:00
Émile Ré
8e0dc0b7eb Inherit mapping when merging exacts into glob
The pattern-analysis worker created the merged glob blank and re-armed
mapping, discarding the org ThirdParty and description already resolved
on the exacts it absorbed. That forced a full re-map (LLM/web-search)
and opened a window where an in-flight exact could vanish mid-mapping.

Seed the glob from the merged exacts when they unanimously agree on a
single third party, carrying its description too, while still re-arming
mapping so the glob derives its own catalog row. With the third party
pre-set, the mapping worker skips the expensive org/disambiguation
resolution. Conflicting or unresolved groups stay blank as before.

The catalog link is deliberately not inherited: it is keyed on the
exact pattern string, not the glob template, so the mapping worker
resolves the right row itself.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-01 13:17:17 +02:00
Émile Ré
62aa4a2dc4 Re-trigger mapping when a tracker source is promoted
A tracker pattern's source ratchets PRE_EXISTING -> EXTENSION -> SCRIPT
as stronger detections arrive, but that promotion was never reflected
back to the mapping pipeline. The detection that promotes the source
also brings a fresh initiator domain that matchByDomain and
matchBySiblingOrigin can use, and an EXTENSION -> SCRIPT promotion lifts
the creationAllowed gate that blocks org third-party creation. Yet the
pattern's mapping_requested_at was already cleared after its first pass,
so the worker never revisited it.

Re-arm mapping_requested_at via SetMappingRequested at each
source-promotion site (reportDetectedTracker plus the glob-merge and
adoption paths in the pattern-analysis worker). Update's SET clause does
not cover mapping_requested_at, so assigning the field before Update
would be a silent no-op; SetMappingRequested only writes when the column
is NULL, keeping already-queued patterns from being double-enqueued.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-29 10:07:33 +02:00
Émile Ré
dc92fd238f Fold PromoteSource into Update
Every PromoteSource caller already loaded the tracker pattern under
the same transaction, so a dedicated single-column UPDATE only
duplicated machinery and forced callers to learn a second mutation
verb. Add `source = @source` to Update's SET clause, mutate
Source/UpdatedAt on the receiver, and call Update at the three
promotion sites (worker merge loop, worker adoption loop, and
reportDetectedTracker). The shouldPromoteSource gate still ranks the
candidate against the loaded value; Update is now the single write
path that can advance source, with a doc comment spelling out the
load-first contract.

Re-cast the coredata tests around Update: WritesSource pins the
round-trip from receiver to DB, NotFoundForMissingRow preserves the
ErrResourceNotFound contract callers rely on. The old
OnlyTouchesSourceAndUpdatedAt test was a property of the narrow
PromoteSource UPDATE and no longer applies — Update intentionally
rewrites the full editable column set from the receiver.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:55 +02:00
Émile Ré
27a9f83825 Promote on detection
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:54 +02:00
Émile Ré
05cbab7258 Promote glob source and trigger draft on adoption
The pattern-analysis worker dropped two signals on every run. When
InsertIfNotExists hit a pre-existing glob, the computed bestSource
was discarded by the LoadByBannerIDTypeAndPattern fallback, so the
SCRIPT > EXTENSION > PRE_EXISTING precedence advertised on
bestSource was only ever enforced at first insert. Subsequent
batches with stronger sources could not promote the glob, even
though the page-script-wins rule already lives in detected_trackers
at the row level.

Separately, adoptUncategorisedPatterns returned an adopted bool
that the worker discarded; the function moves detected trackers
from uncategorised exact patterns into categorised globs, which is
a real consent transition, but no draft banner version was created
on adoption-only runs.

Add a focused TrackerPattern.PromoteSource that only updates the
source and updated_at columns. Express the precedence as a pure-Go
shouldPromoteSource helper alongside bestSource so the rule is
unit-testable without a database. The worker now calls
InsertIfNotExists, then on conflict loads, skips when the slot is
held by an exact pattern or a user-recategorised glob, and only
calls PromoteSource when the candidate source ranks above the
existing one.

The skip branch is now documented: adoptUncategorisedPatterns is
the safety net that re-homes uncategorised exacts into the existing
glob via globMatch. Capture its adopted return value and use it
(instead of the previous over-eager consentChanged flag) to gate
ensureDraftVersionForBanner. Merging exacts into a glob in their
own category never changes visitor consent, so the prior flag
produced redundant draft versions on every non-uncategorised merge.

Cover the new pieces with three test layers: pure-unit cases for
shouldPromoteSource (precedence matrix including HTTP/nil collapse
and equal-rank no-write), DB-backed tests for PromoteSource (touch
only source + updated_at, ErrResourceNotFound for missing rows),
and end-to-end worker tests for source promotion on an existing
glob, draft-on-adoption, and the merge-only no-draft case.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:54 +02:00
Émile Ré
1c3ce56b48 Mark page-world extension writes with EXTENSION source
The previous cleanup deleted every isExtensionCaller() site, including
the one in cookie/storage detectors that did fire reliably for the
residual case: page-world extensions (MV3 main world, userscripts with
@grant none) whose stack contains a chrome-/moz-/safari-web-extension
frame at the synchronous write. Recover that signal for free by
returning fromExtension from getInitiatorURL (it already walks the
stack and discards extension frames via continue), and have the cookie
and storage detectors report source: "extension" instead of "script"
when the flag is set.

End-to-end plumbing reuses the existing source column: extend the
cookie_source Postgres enum with EXTENSION, add the CookieSourceExtension
constant with a doc block describing each bucket's actual semantics,
add the handler.go switch cases, expose EXTENSION on the GraphQL and
MCP CookieSource enums, and add the Extension option to the console
source filter.

Update bestSource in the pattern analysis worker so a glob merging
only extension-attributed exact patterns is no longer silently rolled
up to PRE_EXISTING. New precedence is SCRIPT > EXTENSION > PRE_EXISTING,
matching the upsert SQL's "page-script wins" rule and the asymmetric
signal strength of each bucket.

Out of scope: any behavioural use of EXTENSION (auto-exclusion,
denylist classification, dashboard surfacing) -- that belongs in the
follow-up backend denylist plan.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:48 +02:00
Émile Ré
67122891b8 Fix lint
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:48 +02:00
Émile Ré
a5ee0209f8 Filter separator-only glob templates in tracker pattern analysis
Unrelated third-party trackers that happened to share a leading
separator run (e.g. __support__, __darkreader__wasEnabledForHost,
__EXT_APP_REFRESH_BLACK_SUB_DOMAINS__) were being merged under
overly broad globs such as __* because templateCandidates emitted
every prefix at each '_' or '-' position without requiring any
fixed anchor.

Add a templateHasFixedAnchor helper and apply it to both
templateCandidates loops and the heuristicTemplate result so
candidates consisting solely of '_', '-', and '*' are rejected.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:47 +02:00
Émile Ré
9156d6a16a Add wsl linter and fix
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-20 09:27:28 +04:00
Émile Ré
091be2653a Enforce multiline rule for single multiline arguments
Update go-style guide and cursor rule to clarify that even a single
argument spanning multiple lines must break after the opening
parenthesis. Fix six violations across the branch.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-19 09:47:52 +04:00
Émile Ré
ecb726e724 Go fix
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 19:05:13 +04:00
Émile Ré
05dbd1b476 Split cookie names on both _ and - separators
splitTokens previously picked a single separator (_ winning over -).
Cookies like __Secure-1PSID were split on _ only, leaving
"Secure-1PSID" as one token that falsely passed looksVariable,
producing the bogus ___* heuristic pattern.

The function now splits by _ first, then sub-splits each non-UUID
part by -, tracking per-gap separators. heuristicTemplate trims
leading/trailing empty tokens before analysis so that prefix
underscores are preserved in the output but do not pollute the
variable detection.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 19:05:13 +04:00
Émile Ré
80237eb39c Request mapping for glob patterns created by pattern analysis
The pattern analysis worker creates glob patterns (e.g. prism_*) by
merging exact patterns but never set MappingRequestedAt, so the tracker
mapping worker never picked them up to match against common tracker
patterns.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 19:05:12 +04:00
Émile Ré
81707fe429 Move tracker mapping worker to cookiebanner and rename worker files
Move tracker_mapping_worker.go from pkg/probo to pkg/cookiebanner and
rename worker.go to pattern_analysis_worker.go to reflect the worker
name.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 19:05:12 +04:00