Commit Graph

18 Commits

Author SHA1 Message Date
É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é
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
Bryan Frimin
bd04f1812a Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 14:07:37 -07:00
Bryan Frimin
b39d880c0f Switch AuthorizationAttributes to batch and add AuthorizeBatch
Change AuthorizationAttributer.AuthorizationAttributes to take a slice
of resource ids and return policy.AttributesByID, so a single SQL
round-trip can load condition attributes for a whole batch. All
coredata implementations are migrated to a single
`WHERE id = ANY(@resource_ids::text[])` query that returns only the
rows it finds.

Authorizer gains:
  - AuthorizeBatch — all-or-nothing across a homogeneous (same entity
    type, same organization) resource set; rejects mixed entity types,
    mixed organizations, and empty batches with structured errors.
  - AuthorizeMulti — heterogeneous evaluation that returns one error
    per item and writes audit log entries in a single bulk insert.

The single-resource Authorize is rewired to delegate to AuthorizeBatch
so all paths share the same condition evaluation and audit logging.
recordAuditLog is split into buildAuditLogEntry plus a batch insert.

Tests cover the new batch and multi paths, mixed/empty/unsupported
resource cases, audit log batching, and dry-run behaviour.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:36 -07:00
Émile Ré
5abd670707 Update console tracker page
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-22 10:20:07 +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é
d6f268cb06 Add tracker mapping worker and initiator domain extraction
Poll-based worker that maps org-scoped tracker patterns to the
common knowledge base via pattern matching and domain-based
attribution. Populates initiator_domain on detected trackers
at report time. Resolves org-scoped vendors through the common
third party link.

Signed-off-by: Émile Ré <emile@getprobo.com>
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 19:05:07 +04:00
Émile Ré
60ee8da348 Wire new FK columns into existing coredata models
Add CommonTrackerPatternID and ThirdPartyID to TrackerPattern,
CommonThirdPartyID to Vendor, InitiatorDomain to DetectedTracker.
Register CommonThirdPartyDomain (93) and CommonTrackerPattern (94)
entity types.

Signed-off-by: Émile Ré <emile@getprobo.com>
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 19:05:06 +04:00
Émile Ré
6c6137f9f2 Fix deadlock when updating tracker pattern last_matched_at
Replace per-row UPDATE inside the detected-tracker loop with a single
bulk UPDATE ... WHERE id = ANY(...) after the loop. The old approach
locked pattern rows in request-dependent order, causing deadlocks
under concurrent ReportDetectedTrackers calls.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-13 12:49:12 +04:00
Émile Ré
6bf1e54a50 Fix SQL corruption in FindMatchingPattern by using strings.Replace
fmt.Sprintf interprets the literal % characters in the LIKE escape
clause as format verbs, corrupting the query and causing a 500 on
the /report endpoint.  Reorder tracker type / source filters in the
trackers page.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 17:42:51 +04:00
Émile Ré
5e6220a67c Detect variable tokens in tracker pattern names
The pattern analysis worker now recognises UUID-like, hash-like,
and long numeric tokens as variable parts and replaces them with
wildcards heuristically, even from a single observation. This
prevents site-specific identifiers from being treated as static
text while meaningful suffixes (window_id, posthog, …) get
incorrectly wildcarded.

Also upgrades globMatch and the FindMatchingPattern SQL query
to support multiple wildcards in a single pattern.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 16:36:15 +04:00
Émile Ré
8393e4bfb1 Fix unknown function ends_with in pg
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:53 +04:00
Émile Ré
70d86d68af Add GLOB match type to replace PREFIX for tracker patterns
Introduces a wildcard-based match type that supports prefix, suffix,
and sandwich patterns (e.g. ph_phc_*_posthog). The SQL matching uses
starts_with/ends_with on the parts split at '*', avoiding LIKE and
its underscore escaping issues. Existing PREFIX rows are migrated to
GLOB with a trailing '*'.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:52 +04:00
Émile Ré
93cf5a5986 Add duration-aware tracker pattern merging
Trackers sharing a prefix but with materially different lifetimes
(e.g. session vs 1-year) were incorrectly merged into a single
prefix pattern. Port the snap table from cookie-utils.ts into Go
and use it to bucket durations so only trackers that display the
same human-readable lifetime can merge. Update the unique index
to include COALESCE(max_age_seconds, -1) so prefix patterns with
different durations can coexist.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:48 +04:00
Émile Ré
af6e420f54 Remove cookie_patterns legacy, migrate to tracker_patterns
Delete coredata.CookiePattern and all associated CRUD methods,
rename shared types (CookiePatternOrderField, CookiePatternFilter,
CookiePatternMatchType) to TrackerPattern equivalents, and migrate
all API surfaces (GraphQL, MCP, CLI, n8n) to tracker_pattern naming.

The worker was already migrated in the base branch; this commit
completes the removal by dropping the old GraphQL schema/resolvers,
service methods, CLI commands, and n8n operations that operated on
the legacy cookie_patterns table.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-06 12:26:12 +04:00
Émile Ré
21f92352d5 Migrate detection page to TrackerPattern GraphQL types
- Add TrackerPattern type, connection, and filter to GraphQL schema
- Add uncategorisedTrackerPatterns field on CookieBanner
- Add updateTrackerPattern, deleteTrackerPattern, moveTrackerPatternToCategory mutations
- Implement all resolvers backed by tracker_patterns table
- Add TrackerPattern service methods (CRUD + list/count)
- Add LoadUncategorisedByCookieBannerID on TrackerPatterns coredata
- Update detection page to use TrackerPattern fragment, queries, and mutations
- CookieCategory resolver uses dataloader (not just struct ID)

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-06 12:26:11 +04:00
Émile Ré
b0610cac24 Migrate pattern analysis worker to tracker patterns
The worker now operates on TrackerPattern/DetectedTrackers
instead of CookiePattern/Cookies, with TrackerType included
in merge group keys to prevent cross-type merging.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-05 17:28:57 +04:00
Émile Ré
f046c2967e Add unified tracker detection backend
- TrackerType enum (cookie, local_storage, session_storage, indexed_db, script, iframe)
- TrackerPattern model with EXACT + PREFIX matching for all types
- DetectedTracker model with upsert on conflict
- ReportDetectedTrackers service method handling cookies, storage, and resources
- POST /detected-trackers endpoint on cookie-banner v1 API
- buildSnapshot() now reads from tracker_patterns (cookie type only)
- Entity types registered (89, 90)

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-05 17:28:56 +04:00