Commit Graph

2306 Commits

Author SHA1 Message Date
Émile Ré
ff0369dc4f Backfill empty third-party fields on name dedupe
The tracker policy document deduped third parties by name and kept the
first-seen row wholesale. Org third parties are appended before catalog
common vendors so user-editable data wins, but an org row may leave the
description or privacy policy URL empty. In that case the early return
discarded the later common-vendor row entirely, dropping metadata that
would have completed the document.

Track the kept row by name and backfill only its empty fields from later
duplicates, so org data still wins while common-vendor metadata is no
longer lost.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:23 +02:00
É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
Émile Ré
9a7bc26d49 Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:22 +02:00
Émile Ré
941584424a Fix wrong struct field
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:22 +02:00
Émile Ré
e2d6972da5 Add importThirdPartyFromCommon GraphQL mutation
Expose the explicit import action over the console API. The mutation
takes an organization and a common third party, authorizes as a
third-party create, and delegates to ThirdPartyService.ImportFromCommon,
returning the org ThirdParty edge plus a created flag so the client can
tell a fresh import from a re-import.

Add an end-to-end test covering the two behaviours that matter: the
first import seeds the org vendor from the catalog and backfills the
linked tracker pattern's third_party_id, and a second import is
idempotent, returning the same row with created=false.

The gqlgen-generated types and execution code are build artifacts (not
tracked), so only the schema and the resolver change here.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:22 +02:00
Émile Ré
11c74617d0 Dedupe org third parties and add unique index
Add the partial unique index that enforces one org ThirdParty per catalog
vendor per organization, on (organization_id, common_third_party_id)
where common_third_party_id is not null. Manually created vendors (null
common id) stay unconstrained.

Existing data may already hold duplicates left by the old auto-create
race, which would make the index build fail, so a preceding DO block
merges them first. It maps each duplicate onto the earliest-created
survivor and, driven by pg_constraint / pg_index introspection, repoints
every foreign key that references third_parties(id) onto the survivor,
removing link rows that would collide on a referencing table's unique
key before repointing, then deletes the extras.

The dedupe is destructive and introspection-driven; validate it against a
production dump before deploying.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:21 +02:00
Émile Ré
27ff38df7d Remove third-party disambiguation agent and matching
The tracker-mapping worker no longer auto-creates or auto-links org
third parties, so the heuristic ranker, the disambiguation agent, and
the catalog-to-org seeding helpers are dead code.

Delete pkg/thirdparty/match.go (RankCandidates, ScoredCandidate,
LinkToCommon, CreateFromCommon, suffix stripping, score thresholds) and
disambiguation_agent.go, along with their tests. Drop the
ThirdPartyDisambiguation agent slot and the worker's
DisambiguationAgentTimeout from probodconfig, the builder env wiring, and
the builder tests.

Remove the matching helm surface too: the thirdPartyDisambiguation agent
values, the disambiguationAgentTimeout worker tuning, and the
AGENT_THIRD_PARTY_DISAMBIGUATION_* / TRACKER_MAPPING_DISAMBIGUATION_AGENT
_TIMEOUT environment mappings, with a chart changelog note. The probod
config is built from env lookups with defaults, so a lingering value in
an older deployment is simply ignored.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:21 +02:00
Émile Ré
ed2dbe92c9 Surface catalog vendors in tracker policy doc
The generated tracker policy listed only org ThirdParty rows linked to
the banner's patterns. Now that the mapping worker no longer auto-creates
those rows, a banner whose vendors have not been imported would render an
empty third-party section.

Build the list from the same union the banner's linkedThirdParties
resolver exposes: org ThirdParty rows plus the CommonThirdParty catalog
entries reached through patterns that carry no org link. Dedupe by name
with org rows taking precedence, so the policy stays complete and
accurate whether or not a vendor has been imported into the register.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:21 +02:00
Émile Ré
c25e7c6c40 Add explicit third-party import from catalog
With the tracker-mapping worker no longer materializing org third
parties, add the deliberate action that does: ThirdPartyService.Import
FromCommon seeds an org ThirdParty from a CommonThirdParty catalog entry
or returns the one the organization already imported, making it
idempotent on the (organization_id, common_third_party_id) pair.

On both the create and reuse paths it backfills tracker_patterns.third_
party_id for the organization's unlinked patterns whose catalog row
resolves to the same common third party, via the new TrackerPatterns.Link
ThirdPartyByCommonThirdPartyID. Patterns that previously surfaced the
catalog entry then surface the managed org vendor in the trackers UI and
the tracker-policy document. Only unlinked patterns are touched, so the
backfill is idempotent and picks up newly detected patterns on re-import.

End-to-end coverage (idempotency and pattern backfill) lands with the
GraphQL mutation in a following commit.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:21 +02:00
Émile Ré
0f5ac03e70 Stop auto-creating org third parties on mapping
The tracker-mapping worker materialized a per-org ThirdParty for every
categorized tracker, linking or creating one through heuristic and
disambiguation-agent matching. Concurrent mapping of two patterns for
the same common third party raced the load-then-create check and left
duplicate org third parties with the same name.

Reduce the worker to catalog resolution only: it resolves the shared
common_tracker_pattern_id / common_third_party_id link and leaves
third_party_id untouched, preserving any link set elsewhere. Org third
parties will instead be created through an explicit per-vendor import
action added in a later commit.

Remove resolveOrgThirdParty, prepareOrgThirdParty, the sibling
direct-link signal, and the disambiguation-agent wiring (including its
constructor parameter and buildTrackerAgents return), and update the
worker tests to assert the catalog link is resolved while third_party_id
is preserved.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-11 12:08:21 +02:00
Sacha Al Himdani
04a34c9757 Require explicit approver_ids when publishing a document major version
The publish flow ignored a document's stored default approvers and only
requested approval when approver_ids were passed in the call, so a major
publish with no approver_ids silently published directly without routing
through the approval flow — there was no way to tell "caller forgot
approvers" (null) from "caller wants no approval" (empty).

Make approver_ids an explicit choice, enforced once in the service so it
covers every caller (console, MCP, n8n):
- major publish: approver_ids must be set; an empty list publishes
  directly, a non-empty list requests approval.
- minor publish: approver_ids must be omitted (approvers are ignored).

Validate this in PublishDocumentRequest.Validate(), update the console
publish dialog and the n8n publish node to honour the contract, document
it in the MCP tool spec, and cover it with e2e tests.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-11 11:41:30 +02:00
Ludovic Vielle
3475dd0560 Switch console file fields to File download URLs
Replace presigned URL string fields (logoUrl, fileUrl, ndaFileName,
etc.) with nested File references resolved through /api/files/v1/.
Update console Relay queries and e2e coverage accordingly.

Route NDA upload through filemanager.PutFile and return stable IAM
org logo URLs for consistency with the files API.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-11 10:47:28 +02:00
Bryan Frimin
003fd2baf1 Drop DEFAULT TRUE from active column after backfill
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-11 10:42:55 +02:00
Bryan Frimin
4b0d0cf0bd Default active to TRUE for access entries
Drivers that cannot determine account status leave Active nil.
Rather than surfacing NULL in the database, treat an absent signal
as active (the account appeared in the source listing).

- Migration: NOT NULL DEFAULT TRUE on the active column
- Insert/upsert SQL: COALESCE(@active, TRUE) on write
- Fix new(true) in test (not valid Go; use &activeTrue)

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-11 10:42:55 +02:00
Bryan Frimin
c28287f307 Fix wsl_v5 blank line before if statements
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-11 10:42:55 +02:00
Bryan Frimin
c913e97c35 Add active status field to access entries
Track whether an account is active (enabled) or disabled at the
source system. The field is nullable so existing entries without
this data remain valid.

- DB migration adds active BOOLEAN column to access_entries
- Coredata read/write/upsert/filter wiring for the new column
- Review engine propagates Active from source accounts
- GraphQL schema exposes active on AccessEntry and AccessEntryFilter
- MCP spec, types, and resolvers expose active and fix missing
  account_type filter that was wired in GraphQL but not MCP
- CLI list command adds --active filter flag and ACTIVE output column
- Console campaign detail table shows Active/Disabled status badge
- E2e and unit tests updated to cover the new field

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-11 10:42:53 +02:00
Bryan Frimin
95a12d338b Retry DNS TXT lookup over TCP on truncated UDP response
When a domain has multiple TXT records (SPF, DKIM, etc.), the UDP
response can exceed 512 bytes and the server sets the TC bit. The
verifier was not handling this case, so any truncated response that
omitted the probo-verification record would silently fail as a
mismatch.

Fix by checking resp.Truncated after the UDP exchange and retrying
over TCP when set. Also pass the caller's context instead of
context.Background(), and simplify message construction with
dns.NewMsg.

Closes #1335

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-11 09:49:38 +02:00
Émile Ré
2db063bc60 Drop progress reporting from banner tracker reset
The ResetProgressFunc callback streamed per-phase messages from inside
the reset transaction, so a rollback could leave already-printed
progress describing work that never committed. Remove the callback
entirely; the final result summary printed by proboctl still conveys
the outcome without the misleading intermediate output.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 18:28:21 +02:00
Émile Ré
ed9831a734 Default cookie consent to GDPR and track its source
When IP geolocation does not resolve a country, or resolves one with no
known cookie-consent regulation (common on localhost and unmapped
regions), the banner previously fell back to OPT_OUT with no recorded
regulation. Apply GDPR (OPT_IN) as the safe default in that case so the
strictest consent model wins when origin is unknown.

To keep consent records auditable, stamp each one with a regulation
source of DETECTED (resolved from geolocation) or DEFAULT (fell back to
GDPR). The shared cookiebanner.ResolveRegulation helper centralizes the
decision for both the config and consent endpoints, and the new value is
exposed through GraphQL, MCP, the CLI, the n8n node, and the console
consent-records views.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:41 +02:00
Émile Ré
1e3c1342e3 Clear stale cookie-banner SDK initiator URLs
A pre-fix bug in the cookie-banner SDK walked its own bundle frame
when computing a tracker's initiator, so cookies and storage written
by third parties, malware, or browser extensions were attributed to
cookie-banner.iife.js. The report upsert keeps the existing value on
re-detection (initiator_url = COALESCE(new, old)), so rows whose
corrected initiator is NULL would keep the stale bundle URL forever.

Null initiator_url and initiator_domain on detected_trackers rows that
point at the @probo/cookie-banner bundle. Genuine third-party rows
repopulate the correct initiator on the next detection.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:41 +02:00
Émile Ré
7c09e19d0c update confidence when relinking common tracker to third party
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:41 +02:00
Émile Ré
31c4a0b39c Add proboctl catalog upsert, link, and describe commands
Operators previously had no way to curate the global tracker catalog
beyond inspection and banner-scoped resets. Add three proboctl commands
backed by small coredata helpers:

- common-third-party upsert: create or update a vendor keyed by slug,
  with partial-merge so an unset flag never blanks an existing column.
- common-tracker-pattern link/unlink: repoint catalog rows at a common
  third party (re-arming enrichment and remapping the uncategorised org
  trackers so the mapping worker re-resolves the vendor) or detach them.
  Unlinking skips enrichment and remap since there is no new vendor.
- common-tracker-pattern set-description: write a description, mark the
  row enriched, and backfill linked org patterns lacking one.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:40 +02:00
Émile Ré
ae61c20791 Match cookie-database denylist on domain forms
The cookie-database aggregator backstop normalised the agent's
third-party name and looked it up against bare brand keys, but
normalizeAlnum folds the eTLD into the key (cookiedatabase.org ->
cookiedatabaseorg). Domain- and URL-form attributions therefore
slipped past the exact lookup, letting noisy aggregator names be
accepted instead of discarded.

Add uri.DomainLabel to reduce a host-like string to its primary
registrable label and check it alongside the normalised name, so
both brand ("Cookiepedia") and domain forms ("cookiedatabase.org",
"https://www.cookiepedia.co.uk/list") resolve to the same key.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:40 +02:00
Émile Ré
a68c2ef108 Ignore cookie-database sites in tracker mapping
Cookie-database and consent-directory sites (Cookifi, Cookiepedia,
cookiedatabase.org, CookieServe, ...) rank highly in web search only
because they catalog cookies, not because they set them. The mapping
agent could attribute a tracker to the directory operator itself
instead of the vendor the page names.

Instruct the agent to treat such results as reference directories and
extract the named vendor, never the operator, while keeping a CMP's own
product cookie attributable (OptanonConsent -> OneTrust, CookieConsent
-> Cookiebot). Add a conservative code backstop that discards
attributions to pure aggregators, scoped to exclude CMP vendors so
legitimate own-cookie attributions survive.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:40 +02:00
Émile Ré
e88ad152df Suppress own-domain tracker attribution
Patterns like "ethereum-https://letaido.com" embed the scanned site's
own origin, appended either by a browser extension (e.g. a wallet
injecting window.ethereum) or by an owner-set tracker. The mapping
agent read that embedded domain and attributed the site to itself as a
third party ("Letaido"), but a site owner is never a third party of its
own site.

Pass the scanned site's eTLD+1 to the agent as a <scanned_site> signal
and instruct it to ignore a domain embedded in the pattern that matches
the site, returning low confidence when that is the only cue. Add a
deterministic backstop that discards any agent attribution whose vendor
name resolves to the site's own domain, so the pattern falls through to
the unmatched fallback regardless of whether the model honored the
prompt. Deterministic catalog, naming, and sibling signals run before
the agent and are untouched, so an independent strong vendor signal
still attributes normally.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:40 +02:00
Émile Ré
faca86a022 Scope reset-trackers by keyword and report progress
The reset-trackers operator command reset every uncategorised,
non-excluded pattern of a banner and printed only a single summary
line once the transaction committed, giving no feedback during long
rebuilds.

Add a --keyword flag that scopes both the glob decomposition and the
mapping reset to patterns whose pattern or display name contains the
substring. The match lives in a new TrackerPatternFilter.WithPatternKeyword
field so it runs in SQL and is shared by the glob load and the
ResetAndRequestMappingByCookieCategoryID update, keeping the two in
lockstep. The banner-wide pattern-analysis re-arm is left unscoped.

Thread an optional progress callback through ResetBannerTrackers so the
command streams per-phase updates (category load, per-glob decomposition,
mapping reset, analysis re-arm) as the work runs.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:40 +02:00
Émile Ré
5415b2d438 Allow reenrich without a selection anchor
The reenrich command forced callers to pass exactly one selection
anchor (--id, --linked-banner, --linked-org, or --common-third-party)
before any pattern could be re-described. That made the common case of
re-enriching every catalog row with a blank description impossible
without an artificial anchor.

Drop the anchor-required guard while keeping anchors mutually
exclusive. With no anchor, the filtering flags now select across the
whole catalog, so --without-description re-enriches every pattern that
lacks a description.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-10 17:49:40 +02:00
Ludovic Vielle
3f484ac330 Compute stable URL in types.NewFile, wire baseURL into console v1 Resolver
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-10 15:53:35 +02:00
Ludovic Vielle
6693ee4bf4 Delete pkg/file, fully absorbed into pkg/filemanager
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-10 15:53:33 +02:00
Ludovic Vielle
b679107fa5 Migrate all callers to unified filemanager.Service
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-10 15:53:32 +02:00
Ludovic Vielle
6f8af6f62e Add pkg/filemanager/internal/s3, rewrite unified filemanager service
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-10 15:53:31 +02:00
Sacha Al Himdani
b6781d3de0 Scope sub-third-parties per parent
Replace the many-to-many junction table with a direct
parent_third_party_id foreign key on third_parties. Each
sub-third-party now belongs to exactly one parent, making
duplicates across parents independent entities.

Replace the firstLevel boolean with an integer level field
(1 = direct, 2+ = parent level + 1) to support arbitrary
nesting depth.

Remove the createThirdPartyThirdPartyMapping and
deleteThirdPartyThirdPartyMapping mutations, the CLI
link/unlink commands, and the corresponding MCP tools.
Creating a child third party now just requires passing
parentThirdPartyId on the existing createThirdParty mutation.

The frontend walks the parentThirdParty chain to build
display names like "Name (Ancestor1/Ancestor2)" and shows
clickable ancestor links on the detail page.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-10 14:15:01 +02:00
Aurélien Sibiril
ec858e58df Add Neon access review driver support
Register Neon as a connector provider and add a new access review
driver that fetches organization members from the Neon API with
cursor-based pagination.

Neon's OAuth is partner-gated, so the connector is API-key only
(Bearer, the default scheme). A personal or organization API key can
belong to several organizations; the operator supplies the ID of the
one to review. The members endpoint exposes per-user MFA state
(has_mfa) and deactivation, which map to the access entry MFA status
and active flag; the stable account UUID (user_id) is used as the
external ID over the membership ID.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-10 00:55:54 +02:00
Aurélien Sibiril
7640376d32 Add Render access review driver support
Register Render as an API-key connector provider and add an access
review driver that fetches workspace members from the Render API
(GET /v1/owners/{ownerId}/members).

Render exposes no partner OAuth program, so the connector authenticates
with a read-scoped API key (Authorization: Bearer) plus the customer's
Workspace ID. The flat members endpoint reports an explicit account
status and MFA flag, surfaced as the Active and MFAStatus fields; the
stable "usr-" id becomes ExternalID. There is no picker -- the
workspace is captured up front via ExtraSettings -- so
SetOrganizationSettings is omitted.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-09 23:54:55 +02:00
Cursor Agent
7a43acd3c2 Add Qovery access review driver support
Register Qovery as a connector provider and add a new access review
driver that fetches organization members from the Qovery API.

Extend API key connection handling with a configurable Authorization
token scheme so Qovery can use "Token" while existing providers
continue to default to Bearer.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-09 19:43:47 +02:00
Ludovic Vielle
60435f277b Serve brand assets as static files via /api/files/v1/static instead of S3
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-09 17:42:18 +02:00
Ludovic Vielle
c6c35a542e Wire file.Service into api.Config, delete pkg/filesign
Replace the FileSign field in api.Config and server.Config with File
(*file.Service). Pass the new file.Service and other required deps to
files_v1.NewMux. Remove the now-redundant filesign package. Update
the favicon URL path to /api/files/v1/public/ in server.go.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-09 17:42:17 +02:00
Ludovic Vielle
e51382ce70 Rewrite files/v1 handler with public and private endpoints
Add GET /public/{fileID} (unauthenticated, public files only) and
GET /{fileID} (session+API key+OAuth2, all files, core:file:get IAM
check). IAM and not-found errors both return 404 to prevent leaking
whether a file exists.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-09 17:42:16 +02:00
Ludovic Vielle
d94864fe2d Remove ActionFileDownloadUrl, replace with ActionFileGet
The two actions expressed the same permission. Consolidate on
core:file:get and remove the now-redundant core:file:download-url
constant, policy entries, and all three call sites.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-09 17:42:14 +02:00
Ludovic Vielle
6f83d9be48 Rename GenerateFileTempURL to GenerateFileURL, remove S3 presign duplication
Replace the inline PresignGetObject logic in probo.FileService with a
call to fileManager.GenerateFileUrl. Update the two callers.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-09 17:42:13 +02:00
Ludovic Vielle
14c500b1b2 Extend file.Service with filemanager dep and presign methods
Add GeneratePublicPresignedURL and GeneratePresignedURL methods to
pkg/file. Update the GenerateFileURL path to /api/files/v1/public/
to reflect the upcoming endpoint rename. Pass fileManagerService to
file.NewService in probod.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-09 17:42:12 +02:00
Bryan Frimin
5b79b52e23 Promote connector provider infos to a root-level access-review drivers query
Move connectorProviderInfos from Organization to a new root query field
accessReviewDrivers, backed by a deployment-scoped policy so any
authenticated identity can list it without an org-scoped permission check.
Delete the now-unused helper file and update the frontend and e2e tests.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-09 17:19:43 +02:00
Aurélien Sibiril
29b72ebc3b Add Better Stack access review connector
Better Stack exposes team members and pending invitations through its
Uptime API. Wire it as an access-review connector so a Better Stack
team can be reviewed in access-review campaigns.

Better Stack has no third-party OAuth app for listing members (its
OAuth is an end-user MCP sign-in), so the connector authenticates with
a Bearer API token plus the team name that scopes the team-members
listing. The driver paginates /api/v2/team-members, maps roles and
invitation records into account records, and the source name is
resolved from the configured team.

This wires the full surface: the provider enum and migration, the
connector settings, the registry registration with the team-name extra
setting, the GraphQL input and resolver marshaling, the frontend field
mapping and connector logo, and cassette-backed driver tests.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-09 17:07:08 +02:00
Émile Ré
c71a090fe4 Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-09 17:00:07 +02:00
Émile Ré
87e95e8993 Reject --before combined with --first
NewCursorFromFlags validated --first/--last, --after/--before, and
--after/--last as mutually exclusive, but missing the symmetric
--first/--before guard. Passing --first with --before fell into the
backward branch, where size defaulted from the unset --last and the
direction silently flipped to Tail, discarding --first and reversing
pagination. Reject the combination up front like the other invalid
pairings.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-09 17:00:07 +02:00
Émile Ré
60a1b1c661 Gate cookie banner policy regeneration on its own action
RegenerateCookieBannerTrackerPolicy authorized against the generic
cookie-banner update action, conflating policy regeneration with banner
edits. Add a dedicated regenerate-policy action and authorize both the
console and MCP resolvers against it so the capability can be granted
independently.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-09 17:00:06 +02:00
Émile Ré
ab3750fca4 Add cursor pagination flags to proboctl list commands
Replace the limit-driven auto-walking Paginate helper with explicit
cursor-pagination flags (--first/--after, --last/--before) that mirror
the GraphQL connection arguments. List commands now return a single
keyset page with its page info, and emit cursors so callers can page
forward and backward. --before no longer requires --last: both --first
and --last default to 50 when omitted.

Also split the tracker-pattern stats into enriched with and without a
description so the enrichment backlog is visible at a glance.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-09 17:00:06 +02:00
Émile Ré
4f6fcb42f9 Remove sync common tracker pattern reenriching
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-09 17:00:06 +02:00
Émile Ré
83d36d345f Propagate DB errors and clipboard failures
The common tracker pattern show command swallowed every error from
loading the linked common third party, hiding real database failures
behind silent partial output. Only tolerate ErrResourceNotFound now and
wrap any other error so the command fails loudly.

In the console, the copy-to-clipboard button reported success
unconditionally. Await the clipboard write and surface an error toast
when it rejects, guarding against a missing common tracker id.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-09 17:00:06 +02:00
Émile Ré
8349ca3be3 Fix tests
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-09 17:00:06 +02:00