Replace the immediate per-document approval email and the manual
"send signing notifications" action with a single debounced worker that
batches pending requests per recipient and organization.
The worker (go.gearno.de/kit/worker) polls on an interval (default 5m)
and claims one (organization, recipient) group at a time, sending one
consolidated signing email and/or one approval email per recipient/org
that lists every document awaiting their signature or approval. The
claim is a conditional UPDATE that doubles as concurrency-safe dedup, so
several workers never email the same group twice.
Each request is notified once it has been pending past the debounce
delay (default 15m), then reminded at 1x, 2x and 3x the reminder
interval (default 1 day) after the previous email, after which it stops.
New last_notified_at and notification_count columns on signatures and
approval decisions drive the debounce, the widening reminder cadence and
the four-email cap.
Email copy lists each document with its title, type and a deep link to
the employee page. Removed the inline approval-on-publish email, the
SendSigningNotifications service method/mutation/MCP tool, its IAM action,
and the related console UI and n8n operation.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Make common_tracker_patterns and common_third_parties share one
enrichment-tracking model and fix the misleading proboctl status.
Both tables now carry the enrichment JSONB provenance payload, an
enrichment_attempts counter, and a last_enrichment_attempt_at clock.
On common_tracker_patterns the enriched_at done-flag is renamed to
last_enrichment_attempt_at and stamped at claim time, so it is truthful
to "attempt" rather than "success". A row is considered to have been
through the workflow when it carries an enrichment payload, not when a
timestamp is set, which lets stale recovery key off the payload being
absent with budget remaining, exactly like common_third_parties.
The claim path reads the attempt counter and timestamp back via
RETURNING so the in-memory receiver matches the database clock instead
of a separate app-side time.Now.
The enricher builds a per-field provenance payload (description and
third-party outcomes plus the mapping attribution) and persists it via
UpdateEnrichment, named to mirror the common-third-party sibling. The
common pattern enrichment worker gains a max-attempts ceiling so a
permanently failing row stops looping.
proboctl now shows "enriched" only when every field the last run
recorded an outcome for resolved a value, otherwise "partial (X/Y)",
replacing the misleading "enriched (no description)" label.
Signed-off-by: Émile Ré <emile@probo.com>
Decouple each campaign from the live access-review sources it was started
with by introducing a per-campaign source snapshot table
(access_review_campaign_sources). The snapshot captures the source name,
category, and connector at start time, so a review remains coherent even
after the underlying source is edited or deleted. Fetch tracking becomes
an append-only log (access_review_campaign_source_fetch_attempts) that
preserves every attempt with its own status and error rather than
overwriting a single row.
Rename the shared access-review tables and enums to use a consistent
access_review_ prefix throughout:
access_entries → access_review_entries
access_sources → access_review_sources
access_source_category → access_review_source_category
access_entry_* → access_review_entry_*
The same rename propagates to every coredata type, service, GraphQL
schema, MCP specification, CLI command, frontend component, and e2e test.
The accessreview package gains dedicated actions.go and policies.go files
for its own IAM policy set, mirroring the agentrun package pattern.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The tracker-mapping and common-pattern enrichment agents only had
web search, which returns title/url/snippet, so they could never
open a cookie-database or cookie-policy page to read which vendor
actually sets a tracker. This mis-attributed setters whose snippet
is misleading (e.g. _li_* read as LinkedIn rather than LiveIntent).
Wire the read-only headless-browser toolset into both agents, gated
on a configured Chrome endpoint, mirroring the common-third-party
enrichment worker: agent construction moves into the run path so each
run can carry a per-run browser that is closed when the run returns.
The prompts now direct the agent to open a promising result and read
the named setter from the full page text. Both agents stay unchanged
when no Chrome endpoint is configured.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce a poll-based worker that fills the global common_third_parties
catalog (URLs, headquarter address, legal name, certifications, logo)
so each tenant no longer starts from sparse, name-only rows. Enrichment
is requested at row creation by ResolveOrCreateCommonThirdParty; curated
seed rows are not enqueued, to avoid a re-seed storm.
The pipeline uses two specialized agents plus a deterministic logo step.
Agent A (company profile) resolves legal name, headquarter address, and
the canonical website over web search; its website and legal name feed
Agent B and the logo step. Agent B (compliance docs) resolves the legal
document URLs, trust/security/status pages, and certifications using the
browser read-only toolset (gated on ChromeDPAddr) plus web search. The
logo step restores pkg/webinspect as a pure deterministic package and
stores the discovered icon in S3, linked via logo_file_id.
Each agent returns per-field value/confidence/source_url. The worker
writes a column only when confidence clears a configurable threshold and
the field is not externally owned (seed or human), and always records
full per-field provenance in a new enrichment JSONB column so re-runs
fill only gaps and human edits are never clobbered. New bookkeeping
columns (enrichment_requested_at, enrichment, enrichment_attempts) back
the claim queue and stale recovery; agents run outside transactions and
results persist in one final transaction.
The worker is opt-in: it no-ops unless its agent provider is configured.
Signed-off-by: Émile Ré <emile@probo.com>
Replace the bare `getprobo.com` domain and the `www.getprobo.com`
marketing host with `probo.com` / `www.probo.com` across the codebase.
Functional subdomains (app, console, notification, custom, test,
cookie-banner, compliance) keep their existing `getprobo.com` hosts,
and changelog entries are left untouched.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
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>
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>
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>
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>
The tracker-mapping, common-pattern enrichment, and third-party
disambiguation agents were all built from one shared
TrackerAgentsConfig fed by a single tracker-mapping config slot. That
forced a single AgentTimeout to be reused and patched per worker, and
two unrelated max-turns fields to share one struct.
Split the in-code config into TrackerMappingAgentConfig,
TrackerEnrichmentAgentConfig, and DisambiguationAgentConfig, each with
its own timeout and max-turns, and add dedicated tracker-enrichment and
third-party-disambiguation provider slots (the latter resolving next to
third-party-vetter). Enrichment and disambiguation fall back to the
tracker-mapping slot when their own provider is unset, preserving
single-config deployments.
Drop the shared pkg/agentsbuild package and duplicate its small wiring
into probod and proboctl so the two executables stay decoupled. Wire
the new env vars, builder test coverage, and Helm values.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce an API-style data layer for the global common tracker pattern
and common third party catalogs: typed filters, order fields, CursorKey,
cursor-paginated Load and CountAll, plus by-id enrichment re-queue and a
scoped reset/remap helper for a banner's tracker patterns. These reuse
the same page.Cursor/filter/order types the GraphQL API consumes, so a
future proboctl API can back them unchanged.
Extract the common-pattern enrichment logic out of the worker into a
CommonPatternEnricher service so it can run either from the background
queue or synchronously over a known set of ids; the worker becomes a
thin poller that delegates to it.
Extract the LLM client and tracker-agents config wiring into
pkg/agentsbuild so probod and other binaries build agents identically;
probod now delegates to it.
Signed-off-by: Émile Ré <emile@probo.com>
Replace the single-case switch in the agent-run handler with an if,
and add the blank lines wsl_v5 requires around statements in the
approval checkpoint, worker tests, and policy-set registration.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The agent-run actions and policies lived in the core probo policy set,
which forced every authorization change for the agent-run domain to
touch unrelated core files. Move the actions and the OWNER/ADMIN and
VIEWER/AUDITOR policies into the agentrun package and have it expose a
PolicySet that probod registers into the authorizer at composition
time, so the rules live alongside the domain logic they govern.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Move agent-run orchestration from the legacy supervisor path into the new
agentrun worker/service package and wire it through coredata, server,
policies, and GraphQL resolvers.
This consolidates run lifecycle handling around lease-aware workers and
aligns API surface with the new agent-run domain model so reviewers can
follow one coherent execution path.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Replace the hand-rolled credential chain (static, ECS endpoint, EC2
role) with config.LoadDefaultConfig, which handles the full standard
AWS credential resolution chain automatically.
Co-authored-by: Neil McGibbon <code@neilmcgibbon.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Google Workspace SMTP relay (smtp-relay.gmail.com) rejects generic
EHLO identifiers such as "localhost", which is Go net/smtp's default.
Operators can now set SMTP_HELLO_NAME to a valid hostname so that
c.Hello() is called before StartTLS, satisfying strict relay policies.
Fixes https://github.com/getprobo/probo/issues/1284
Signed-off-by: Bryan Frimin <bryan@probo.com>
Public-client (CIMD) providers need no operator credentials: derive
their client_id from the deployment base URL and their state-signing key
from the active OAuth2 server signing key, registering them at startup
unless the operator configured them explicitly.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
CommonThirdParty.logoUrl and TrustCenterReference.logoUrl were
returning expiring S3 presigned URLs, which break if cached or
shared past their TTL.
Replace with stable /api/files/v1/{id} application URLs.
file.Service now generates these via baseurl; a new filesign
package owns presigning for the files/v1 HTTP handler that
does the internal redirect.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Add a poll-based tracker policy worker that claims banners flagged for
policy generation, clears the flag, and rebuilds the cookie and
tracking technologies policy from the latest published snapshot. A
banner that lost its published version is skipped rather than failed.
Wire the worker into probod with crash propagation and graceful
shutdown alongside the other tracker workers.
Signed-off-by: Émile Ré <emile@probo.com>
Queue vetting on third_parties with PENDING, PROCESSING,
COMPLETED, and FAILED states. Expose enqueue and status through
GraphQL, MCP, CLI, and n8n, validate vet requests, tune the
worker via config, and poll the detail page while vetting runs.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
ResolveAgent aliased the default config's Temperature and MaxTokens
pointers into every agent that left them unset, so all resolved
agents shared one backing value. A mutation through any of those
pointers would corrupt the default and every other agent. It also
dereferenced the default unconditionally even though it can be nil.
Allocate a fresh pointer holding a copy of the default value, and
guard against a nil default so each resolved agent owns independent
state.
Signed-off-by: Émile Ré <emile@probo.com>
The tracker-mapping worker clears mapping_requested_at at claim time, so
a crash or hard failure between Process phases left the pattern dequeued,
unmapped, and with nothing to re-trigger it. Only an incidental sibling
remap could rescue it, so a lone pattern could stay stranded forever.
Implement the worker.StaleRecoverer interface, mirroring the enrichment
worker. ResetStaleMappings re-arms rows that were claimed but never
assigned a catalog row (common_tracker_pattern_id IS NULL) once idle past
a configurable window; a successful Process always assigns one via the
unmatched fallback, so the predicate cleanly detects interrupted runs and
self-heals after a single pass. ClearMappingRequestedAt now bumps
updated_at so the stale clock starts at claim time and the sweep never
recycles an in-flight claim.
Plumb a StaleAfter knob (default 600s) through the config struct, builder
env var, probod wiring, and Helm templates.
Signed-off-by: Émile Ré <emile@probo.com>
The tracker-mapping and common-pattern-enrichment workers ran with the
kit/worker defaults (interval 10s, max-concurrency 5 each) and dropped
the resolved per-agent max-tokens/temperature, so up to ten LLM
pipelines could run unbounded on one OpenAI client. The mapping worker
also held a FOR UPDATE transaction across the LLM and Firecrawl calls
while its DB search tools acquired a second pooled connection, risking
pool exhaustion under concurrency.
Plumb max-tokens, temperature, agent timeout, and per-worker max-turns
through TrackerAgentsConfig and DisambiguationConfig into all three
agent builders, replacing the hard-coded constants with config-fed
fields and package fallbacks. Expose worker interval, concurrency,
stale-after, agent timeout, and max-turns as config (env, Helm values,
deployment template) mirroring the evidence-describer pattern, and
apply them at registration.
Refactor Process into deterministic-read, agent (no transaction), and
persist phases so neither the mapping agent nor disambiguation runs
inside an open transaction, removing the row locks held across network
latency and the nested-connection pressure.
Signed-off-by: Émile Ré <emile@probo.com>
Tracker descriptions were only filled on the agent-identification path,
so patterns resolved by domain, sibling, or fallback stayed without one,
and empty mapping upserts could clobber a researched description on the
shared catalog row.
Move description ownership to a dedicated, global common-pattern
enrichment worker. New catalog rows are queued on insert; the worker
researches a compliance-grade description with web search, records it on
the common pattern, and fans it out to every linked tracker pattern. The
mapping worker no longer generates descriptions and only propagates an
already-enriched one at link time.
Rename TrackerMappingConfig to TrackerAgentsConfig since the mapping and
enrichment agents now share it.
Signed-off-by: Émile Ré <emile@probo.com>
Manual moves of a non-extension TrackerPattern lacking a ThirdPartyID
now request mapping, which the tracker-mapping worker resolves with a
four-stage pipeline: exact common_third_party_id link, heuristic
ranking, agent disambiguation, and finally CreateFromCommon. Existing
fuzzy-matched org rows are tagged with common_third_party_id so the
next promotion takes the O(1) exact-link path.
The matching primitives live in pkg/thirdparty (RankCandidates,
LinkToCommon, CreateFromCommon, ScoredCandidate, threshold constants)
so the disambiguation agent and the heuristic share one candidate
type. Cookiebanner orchestrates them; cookie-banner-specific concerns
(pattern -> common-pattern -> common-party navigation, the EXTENSION
gate, and structured logs) stay in the worker.
Signed-off-by: Émile Ré <emile@probo.com>
Three follow-ups from review of the registry consolidation:
- Build Vercel's authorization URL with net/url instead of a
hand-rolled "{integration_slug}" placeholder resolved by
strings.ReplaceAll. The slug is escaped via url.PathEscape in a
per-provider Registration.BuildAuthURL closure, and the unused
AuthURLParams plumbing on Registration and OAuth2Connector is
removed (OAuth2Connector now carries a typed IntegrationSlug).
- Drop the SettingsInput union type and the per-provider
MarshalSettings closures. The create resolvers now build the typed
coredata.*ConnectorSettings directly from the gqlgen input, the
same way the OAuth callback path already does, so there is no
shared catch-all DTO and no stringly-typed boundary.
- Restore ConnectorProviders() to a plain ordered slice literal; the
intermediate map + slices.Sort added nondeterminism and a sort for
no benefit.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The console previously dispatched per-provider logic through a fan of
init()-side-effect maps (driver names, OAuth2 metadata, probe URLs,
display names, settings switches), spread across pkg/connector,
pkg/accessreview/drivers and the console v1 resolvers. Adding a new
provider required edits in every one of those places and a corresponding
switch arm in CreateConnectorRequest. The same per-provider knowledge
also leaked into Helm templates as hand-rolled environment-variable
blocks per connector.
This commit collapses the dispatch surface into a single typed
*provider.Registry. The registry is constructed once by
NewBuiltinRegistry at probod startup and threaded as an explicit
dependency into every consumer (accessreview service, console v1
resolver, OAuth2 wiring). There is no package-level state. Each
provider lives in one file under pkg/connector/provider/ that exposes
a private xxxRegistration() *Registration constructor; NewBuiltinRegistry
enumerates them.
CreateConnectorRequest loses its per-provider settings fields and
takes a single RawSettings json.RawMessage produced by the
per-provider MarshalSettings closure. The 1Password SCIM bridge URL
is validated at create time (http(s) scheme + non-empty host) so a
malformed value fails fast at the resolver boundary. The Helm chart
gains probo.connectorEnv and probo.connectorSecretEntries templates
so adding a connector requires zero Helm changes. Access-review name
resolution moves into the same Registration value to keep one
authoritative dispatch table.
Tests cover every Registration (DisplayName, NewDriver wired),
Register error paths (nil, empty Provider, empty DisplayName,
duplicate), All / ProviderDisplayName / ProviderOAuth2Scopes /
ProbeURL hit and miss paths, the ApplyOAuth2Defaults templating and
PKCE branches, and ConnectorSettings[T] round-trip plus malformed-JSON
error path. The pre-refactor ApplyProviderDefaults test in
pkg/connector is replaced by the equivalent in
pkg/connector/provider.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Apply five style rules: convert iota string enums to typed
string constants, replace errors.As with errors.AsType,
merge three-group imports into two groups, fix multiline
parameter/argument formatting, and replace fmt.Sprintf URL
construction with net/url.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce a hierarchical risk assessment model with six entity types:
- Risk Assessment: top-level container scoped to an organization
- Risk Assessment Scope: sub-container for scoping threat modeling
exercises within an assessment
- Risk Assessment Node: DFD elements typed as ENTITY, BOUNDARY,
ASSET, or DATA within a scope
- Risk Assessment Process: directed data flows between two nodes
- Risk Assessment Threat: descriptive threats attached to a process
with a free-text category (e.g. Confidentiality, Integrity)
- Risk Scenario: thin join linking a threat to a risk from the
register, carrying only a name and description
Risk scoring (likelihood, impact, treatment) remains on the existing
Risk entity. Threats are purely descriptive. Risk Scenarios connect
the threat model to the risk register without duplicating scores.
Backend: migration with PG enum for node types, coredata structs,
service layer with full CRUD and validation, GraphQL schema with
18 mutations and paginated connections, authorization actions and
policies, and base_resolvers.go Node dispatch for all entity types.
Frontend: Risk Assessments list page with create dialog, detail page
showing scopes as cards with nodes/processes/threats tables, inline
create/edit/delete actions on all entities, and a Scenarios tab on
the Risk detail page linking threats to risks. Existing RiskGraph.ts
hook file removed in favor of colocated queries in page files.
E2E tests cover CRUD for all entity types, RBAC, and tenant
isolation.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Firecrawl is a tool used by agents (tracker mapping, third-party
assessor), so its configuration belongs under AgentsConfig rather than
as a standalone Config field. Adds AgentToolsConfig to hold agent tool
credentials and updates all config propagation consumers.
Signed-off-by: Émile Ré <emile@probo.com>
Firecrawl has a single public API at https://api.firecrawl.dev/v2.
The endpoint was configurable but never varied across environments,
so hardcode it as a package-level const and remove the Endpoint
field from FirecrawlConfig and all downstream wiring (bootstrap,
Helm chart, probod, vetting, cookiebanner).
Signed-off-by: Émile Ré <emile@probo.com>
SearXNG was a fallback search backend that added complexity without
being used in practice. All search-dependent features (web search,
government DB checks, vetting orchestrator, tracker mapping) now use
Firecrawl exclusively. Removes the SEARCH_ENDPOINT config plumbing
from probodconfig, bootstrap, Helm charts, and all callers.
Signed-off-by: Émile Ré <emile@probo.com>
Group firecrawl-endpoint and firecrawl-api-key under a nested firecrawl
config key. Add env var mappings (FIRECRAWL_ENDPOINT, FIRECRAWL_API_KEY,
SEARCH_ENDPOINT, AGENT_TRACKER_MAPPING_*) to the bootstrap builder with
test coverage. Wire the new values through the Helm chart (values,
deployment, secret, production example).
Signed-off-by: Émile Ré <emile@probo.com>
Firecrawl provides higher quality search results than SearXNG.
When configured (firecrawl-endpoint + firecrawl-api-key), the
tracker-mapping agent and search toolset prefer it over the
SearXNG backend. Also improves the tracker identification prompt
with multi-strategy search queries that leverage domain signals
and adapt to tracker type.
Signed-off-by: Émile Ré <emile@probo.com>
When both pattern matching and domain matching fail to identify a
tracker, an opt-in LLM agent can now attempt identification using
internal database searches and optional web search. The agent returns
structured output (third party name, category, description, confidence)
and the worker auto-creates CommonThirdParty records when needed.
The feature is gated behind the `llm.tracker-mapping.provider` config
field; when unset the worker behaves exactly as before.
Signed-off-by: Émile Ré <emile@probo.com>
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>
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>
Renames the user-facing 'vendor' concept to 'third party' across the
entire codebase. The shared common_third_parties reference table is
unchanged.
Migration. Renames the vendor_category enum, the vendors and
vendor_<entity> tables (contacts, services, compliance_reports,
business_associate_agreements, data_privacy_agreements,
risk_assessments) and their vendor_id columns, the asset_vendors /
data_vendors / processing_activity_vendors junction tables,
generated_documents.vendors_document_id, the webhook_event_type
'vendor:<verb>' values, and the snapshots_type 'VENDORS' value.
Backend. Renames coredata models and SQL queries, probo services,
GraphQL / MCP API surface, console / trust / webhook resolvers and
types, the CLI (prb vendor* -> prb third-party*; pkg/cmd/vendormgmt
-> pkg/cmd/thirdpartymgmt), the document generator, vetting agent
prompts, and the common-third-parties-import command.
Frontend, packages, n8n, e2e. Renames apps/console pages, components,
hooks, routes, dialogs, and tabs; the shared @probo/vendors package
(now @probo/third-parties); the @probo/ui Vendors atoms (now
ThirdParties, VendorLogo -> ThirdPartyLogo); the n8n community node
actions/vendor folder (now actions/thirdParty); and the e2e Go test
suite (console and MCP). Filesystem and URL paths use kebab-case
(third-parties), GraphQL fields and TypeScript identifiers use
camelCase (thirdParty / thirdParties), Go types use PascalCase
(ThirdParty), and human-facing text uses 'third party' with a space.
Co-authored-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Fetch favicons at import time instead of calling Google's favicon
service per page load. Logos are stored as public files in S3 and
served through the existing /api/files/v1/{id} endpoint.
Signed-off-by: Émile Ré <emile@getprobo.com>
The CreateVendorDialog previously loaded the entire @probo/vendors
JSON bundle client-side and used MiniSearch for fuzzy search. This
replaces it with a GraphQL query against the common_third_parties
database table, searched server-side via ILIKE filtering.
Backend: adds CommonThirdParty GraphQL type, a pkg/thirdparty
service, and a commonThirdParties(name) root query. Frontend:
splits into CommonThirdPartyCombobox (display) and an @inline
fragment read on selection via readInlineData.
Signed-off-by: Émile Ré <emile@getprobo.com>