Add the full GraphQL surface for the new tracker_resources table:
- TrackerResourceType enum (SCRIPT, IFRAME), TrackerResource node type
with connection/edge/order/filter, fields on CookieBanner
(uncategorisedTrackerResources) and CookieCategory (trackerResources).
- Mutations: createTrackerResource, updateTrackerResource,
deleteTrackerResource, moveTrackerResourceToCategory with
inputs and payloads.
- Resolvers for all mutations, connection fields, field resolvers
(cookieCategory, permission), and totalCount.
- IAM actions: core:tracker-resource:{get,list,create,update,delete}.
Signed-off-by: Émile Ré <emile@getprobo.com>
Wire resource ingestion and add full CRUD + list/count service methods
for the new tracker_resources table.
- reportDetectedResource splits the URL into origin/path and upserts
into tracker_resources with the uncategorised category.
- CreateTrackerResource, GetTrackerResource, UpdateTrackerResource,
DeleteTrackerResource, MoveTrackerResourceToCategory mirror the
tracker-pattern service surface.
- ListTrackerResourcesForCategory, CountTrackerResourcesForCategory,
ListUncategorisedTrackerResources, CountUncategorisedTrackerResources
provide paginated access.
- Request structs with Validate() and dedicated error sentinels.
Signed-off-by: Émile Ré <emile@getprobo.com>
Move resource tracking (scripts, iframes) out of the pattern-based
tracker_patterns/detected_trackers machinery into its own
tracker_resources table keyed by (banner, type, origin, path).
- Add migration that creates the tracker_resource_type enum, the
tracker_resources table with a unique index, drops existing
SCRIPT/IFRAME rows (not yet in production), and recreates the
tracker_type enum without those values.
- Add TrackerResource coredata model with full CRUD, Upsert (bumps
last_detected_at on conflict), list/count/move operations, filter,
and order field support.
- Register TrackerResourceEntityType (91) in the entity type registry.
- Drop TrackerTypeScript/TrackerTypeIframe from TrackerType enum.
- Update handler to use TrackerResourceType for resource detection.
- Temporarily stub out resource ingestion in ReportDetectedTrackers
pending the service-layer wiring in the next commit.
- Drop SCRIPT/IFRAME from the GraphQL TrackerType enum.
Signed-off-by: Émile Ré <emile@getprobo.com>
Rewrite worker unit tests: TestTemplateCandidates, TestGlobMatch,
TestSplitTokens, and updated TestFindMergeGroups with sandwich pattern
cases. Update e2e test to use GLOB instead of PREFIX.
Signed-off-by: Émile Ré <emile@getprobo.com>
Replace prefix-only merge logic with token-template analysis that
discovers sandwich patterns (e.g. ph_phc_*_posthog). The worker now
emits GLOB patterns, adoption uses globMatch, and validation enforces
exactly one wildcard for GLOB patterns.
Signed-off-by: Émile Ré <emile@getprobo.com>
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>
The displayName field was always predictable from pattern + matchType
and allowing edits added unnecessary complexity. Remove displayName
from UpdateTrackerPatternInput across all surfaces (GraphQL, MCP, CLI,
n8n) and make the frontend show it as non-editable text.
Signed-off-by: Émile Ré <emile@getprobo.com>
Progressive enhancement for Chromium browsers: listen on the
CookieStore change event to catch cookies set by Set-Cookie HTTP
response headers, which the document.cookie setter hook cannot see.
Adds a new "http" cookie source through the full stack.
Signed-off-by: Émile Ré <emile@getprobo.com>
Strip query params and send origin+pathname so the backend can
distinguish resources served from the same domain but different paths
(e.g. gtm.js vs recaptcha/api.js on googletagmanager.com).
Signed-off-by: Émile Ré <emile@getprobo.com>
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>
Address review feedback:
- Move ErrSuspendForCheckpoint from checkpoint.go to errors.go
next to the rest of the agent error declarations; drop the
colon in the error string so it matches the existing
`agent run <event>` style used by the supervisor sentinels.
- Replace the inline `outerCtx := ctx; ctx = context.WithoutCancel(ctx)`
pattern with a small `suspendShield` helper in context.go used
by coreLoop, resumeWithOpts, and resumeNested. Reads more
cleanly and stops surfacing the WithoutCancel mechanism at
every call site.
- Trim the doc comments on Run, RunStreamed, Resume, Restore, the
ErrSuspendForCheckpoint declaration, and the saveCtx comment in
restoreNestedSuspended down to the contract bullet.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Two additions:
- agent_test.go's "context cancellation triggers graceful suspend"
now also asserts the input messages land in the suspension
checkpoint — verifies the embedded-Checkpoint path that fires
when no Checkpointer is configured.
- cancel_test.go gets a third subtest that parks the LLM provider
inside ChatCompletion via a release channel, cancels ctx while
the call is in flight, then confirms the LLM call still saw a
non-cancelled ctx and the just-completed turn lands in the
persisted checkpoint. Proves the framework's WithoutCancel
shielding works end-to-end at the unit level.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The sentinel is part of the agent cancellation contract — the only
caller that needs it (the supervisor) imports pkg/agent already, so
keeping it next to SuspendedError prevents the upward dependency
that would arise if any future agent.Run caller wanted to trigger
graceful suspend. Update pkg/probo/agent_run_handler.go to
reference agent.ErrSuspendForCheckpoint.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Rewrite the WithStopSignal-driven test in restore_test.go to use a
cancellable ctx. Update agent_test.go's "context cancellation"
case from asserting "cannot complete" failure to asserting a
SuspendedError. Add cancel_test.go covering both pre-first-turn
cancel (no LLM call, empty checkpoint persisted) and mid-run
cancel from inside a tool (just-completed turn preserved in the
checkpoint, second LLM call suppressed).
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Process now has the per-run forwarder goroutine call
cancelRun(ErrSuspendForCheckpoint) when h.shutdownCh closes,
rather than closing a separate stopCh and embedding it via
agent.WithStopSignal. The agent loop's new ctx-cancel = graceful
suspend contract covers the rest. h.shutdownCh and signalShutdown
stay as the supervisor-level broadcast (still observable through
ShutdownBroadcastForTests).
The lease-loss path keeps its existing cancelRun call; under the
new contract that triggers a best-effort save before executeRun
detects ErrAgentRunLeaseLost and skips the row commit, which is
race-safe because Worker B can only claim the row after stale
recovery — by then our save has long landed.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Collapse the dual-mechanism (ctx.Done() = abort + WithStopSignal =
graceful suspend) into a single signal: ctx.Done() now means
graceful suspend. coreLoop shadows the incoming ctx with
context.WithoutCancel(ctx) on entry and uses the shadow for every
downstream call (LLM, tools, hooks, guardrails, save), keeping the
original ctx only for the at-boundary cancellation check.
restoreNestedSuspended applies the same shadow to its
saveProgress closure so partial nested-restore writes survive a
graceful cancel. Resume and resumeNested mirror the pattern so
their pre-loop tool dispatch is non-cancellable while coreLoop
still detects the cancel at its first turn boundary. The dedicated
stop signal API (WithStopSignal / stopSignalFrom) is removed.
There is no longer an in-process hard-abort path; tool authors
who need a deadline must derive it themselves. Document the new
contract on Run, RunStreamed, Resume, and Restore.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
In OPT_OUT mode the button_opt_out text was mapped to
button_customize, which opens the preference panel. Map it
to button_reject_all instead so the button performs a
one-click reject for all OPT_OUT regulations.
Signed-off-by: Émile Ré <emile@getprobo.com>
Add the missing MICROSOFT_365 value to the connector_provider enum
so the connector loader stops failing with SQLSTATE 22P02.
Scope each Identity Provider card to its own SCIMBridge type so
connecting Microsoft 365 no longer marks Google Workspace as
connected (and vice versa).
Filter Microsoft Graph /users to userType eq 'Member' so the bridge
only syncs home-tenant members and skips B2B guest accounts that
were polluting the synced People list.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Add proper enum types for Regulation and CountryCode in GraphQL
(with @goModel/@goEnum directives) and MCP (as standalone reusable
schemas with $ref). Update CLI, console UI, and n8n to include
the new fields.
Signed-off-by: Émile Ré <emile@getprobo.com>
Store the visitor's resolved country code alongside regulation in
cookie_consent_records so downstream surfaces can expose it.
Signed-off-by: Émile Ré <emile@getprobo.com>
Replace TRUNCATE+COPY inside a single transaction with a staging table
approach so that SELECT queries from the cookie banner handler are never
blocked during import. The ACCESS EXCLUSIVE lock is now only held for
the sub-millisecond DROP+RENAME swap.
Signed-off-by: Émile Ré <emile@getprobo.com>
The server now resolves regulation-specific translations
(opt-out notice for CCPA, simple notice when no regulation
applies) and remaps text keys before returning the config.
The client hides buttons whose text is empty, so the banner
layout adapts without client-side consent-mode logic.
Signed-off-by: Émile Ré <emile@getprobo.com>
Move cookie banner types (CookieItem, Category, Regulation, BannerConfig,
etc.) into a dedicated types.ts file. Add a coredata.Regulation type with
parsing, JSON marshaling, and database scanning methods. Hardcode the
geoloc-import data directory since the submodule path is fixed.
Signed-off-by: Émile Ré <emile@getprobo.com>
Add a regulation column to cookie_consent_records so each consent
captures which privacy law was in effect. Thread the value from the
handler's geoloc resolution through the service into the DB insert.
On the SDK side, add a Regulation union type to BannerConfig and expose
it via a getter on CookieBannerClient and in the probo-ready event
detail so themed-banner consumers can adapt their UI per regulation.
Signed-off-by: Émile Ré <emile@getprobo.com>
Resolve the visitor's IP to a country code via the geoloc service and
map it to the applicable privacy regulation (GDPR, UK GDPR, FADP, CCPA,
PIPEDA, LGPD, LFPDPPP, POPIA, PDPA, PIPL, PIPA, APPI, DPDP, PDPL).
The regulation and its implied consent mode (OPT_IN / OPT_OUT) are
injected into the GET /config response so the SDK can adapt its behavior.
Also makes geoloc.Service self-contained: LookupCountry and IsPopulated
now manage their own DB connections instead of requiring callers to pass
a pg.Querier.
Signed-off-by: Émile Ré <emile@getprobo.com>
Extract struct and query functions into
pkg/coredata/ip_country_block.go following the convention that
all raw SQL lives in coredata. The geoloc service now delegates
to these functions.
Signed-off-by: Émile Ré <emile@getprobo.com>
Introduce a geoloc package that stores CIDR-to-country mappings in
PostgreSQL using the native cidr type with a GiST index for fast
containment lookups. Data comes from the ipverse/country-ip-blocks
dataset added as a git submodule.
A standalone geoloc-import command reads the TXT files from disk
and bulk-loads them via COPY. probod wires the service and logs a
warning when the table is empty.
Signed-off-by: Émile Ré <emile@getprobo.com>
The application always sets status explicitly when creating a campaign,
so the database does not need to provide a default.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Source fetch failures stay surfaced on the source fetch (status and
last error) instead of failing the entire campaign, so reviewers can
proceed on the sources that succeeded.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Title, document type, and classification on generated documents could
not be changed: any version-tracked field on a GENERATED write-mode
document was rejected with ErrDocumentVersionGenerated. The error now
fires only when content is being changed, so manual metadata edits
flow through the same draft-on-edit path as authored documents and
produce a draft version that the user can review and publish.
The CLI document update --document-type enum gains
STATEMENT_OF_APPLICABILITY (which generated SoA documents already
use), and the GraphQL resolver maps the content-edit rejection to a
Conflict instead of falling through to a generic Internal error.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
The SCIM bridge requested admin.directory.userschema.readonly during
OAuth consent, which is a Google Workspace-only entitlement. Cloud
Identity-only admins could not grant it, so the connect flow failed
before any sync ran. The scope was also unused: the provider only
calls Users.List, never the schemas, groups, or customers endpoints.
Trim the requested scopes down to admin.directory.user.readonly so
the integration works for Workspace and Cloud Identity (Free and
Premium) tenants. Switch Users.List to projection=full so standard
extended fields (Organizations, ExternalIds, Relations, Languages)
are populated on synced users; full projection does not require any
extra OAuth scope. Relabel the connector UI to "Google Workspace /
Cloud Identity" to reflect the broader support.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Microsoft 365's native SCIM endpoint is unreliable, so mirror the
Google Workspace bridge over Microsoft Graph: a new MICROSOFT_365
OAuth2 connector, a SCIM bridge provider listing /v1.0/users with
$select pagination, and an access review driver that derives admin
status from /directoryRoles members. Refactor the bridge runner to
share OAuth2 plumbing across providers and surface the new bridge
type, scopes, UI card, and bootstrap env wiring.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>