Add async third-party vetting
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>
This commit is contained in:
@@ -17,30 +17,31 @@ package probod
|
||||
import "go.probo.inc/probo/pkg/probodconfig"
|
||||
|
||||
type (
|
||||
FullConfig = probodconfig.FullConfig
|
||||
Config = probodconfig.Config
|
||||
UnitConfig = probodconfig.UnitConfig
|
||||
MetricsConfig = probodconfig.MetricsConfig
|
||||
TracingConfig = probodconfig.TracingConfig
|
||||
ESignConfig = probodconfig.ESignConfig
|
||||
TrustCenterConfig = probodconfig.TrustCenterConfig
|
||||
APIConfig = probodconfig.APIConfig
|
||||
CorsConfig = probodconfig.CorsConfig
|
||||
ProxyProtocolConfig = probodconfig.ProxyProtocolConfig
|
||||
AuthConfig = probodconfig.AuthConfig
|
||||
OAuth2ServerConfig = probodconfig.OAuth2ServerConfig
|
||||
OAuth2SigningKeyConfig = probodconfig.OAuth2SigningKeyConfig
|
||||
CookieConfig = probodconfig.CookieConfig
|
||||
PasswordConfig = probodconfig.PasswordConfig
|
||||
AWSConfig = probodconfig.AWSConfig
|
||||
ConnectorConfig = probodconfig.ConnectorConfig
|
||||
ConnectorConfigOAuth2 = probodconfig.ConnectorConfigOAuth2
|
||||
CustomDomainsConfig = probodconfig.CustomDomainsConfig
|
||||
ACMEConfig = probodconfig.ACMEConfig
|
||||
LLMProviderConfig = probodconfig.LLMProviderConfig
|
||||
LLMAgentConfig = probodconfig.LLMAgentConfig
|
||||
EvidenceDescriberConfig = probodconfig.EvidenceDescriberConfig
|
||||
AgentsConfig = probodconfig.AgentsConfig
|
||||
FullConfig = probodconfig.FullConfig
|
||||
Config = probodconfig.Config
|
||||
UnitConfig = probodconfig.UnitConfig
|
||||
MetricsConfig = probodconfig.MetricsConfig
|
||||
TracingConfig = probodconfig.TracingConfig
|
||||
ESignConfig = probodconfig.ESignConfig
|
||||
TrustCenterConfig = probodconfig.TrustCenterConfig
|
||||
APIConfig = probodconfig.APIConfig
|
||||
CorsConfig = probodconfig.CorsConfig
|
||||
ProxyProtocolConfig = probodconfig.ProxyProtocolConfig
|
||||
AuthConfig = probodconfig.AuthConfig
|
||||
OAuth2ServerConfig = probodconfig.OAuth2ServerConfig
|
||||
OAuth2SigningKeyConfig = probodconfig.OAuth2SigningKeyConfig
|
||||
CookieConfig = probodconfig.CookieConfig
|
||||
PasswordConfig = probodconfig.PasswordConfig
|
||||
AWSConfig = probodconfig.AWSConfig
|
||||
ConnectorConfig = probodconfig.ConnectorConfig
|
||||
ConnectorConfigOAuth2 = probodconfig.ConnectorConfigOAuth2
|
||||
CustomDomainsConfig = probodconfig.CustomDomainsConfig
|
||||
ACMEConfig = probodconfig.ACMEConfig
|
||||
LLMProviderConfig = probodconfig.LLMProviderConfig
|
||||
LLMAgentConfig = probodconfig.LLMAgentConfig
|
||||
EvidenceDescriberConfig = probodconfig.EvidenceDescriberConfig
|
||||
ThirdPartyVettingWorkerConfig = probodconfig.ThirdPartyVettingWorkerConfig
|
||||
AgentsConfig = probodconfig.AgentsConfig
|
||||
|
||||
TrackerMappingWorkerConfig = probodconfig.TrackerMappingWorkerConfig
|
||||
CommonPatternEnrichmentWorkerConfig = probodconfig.CommonPatternEnrichmentWorkerConfig
|
||||
|
||||
@@ -177,6 +177,11 @@ func New() *Implm {
|
||||
StaleAfter: 300,
|
||||
MaxConcurrency: 10,
|
||||
},
|
||||
ThirdPartyVetting: ThirdPartyVettingWorkerConfig{
|
||||
Interval: 10,
|
||||
StaleAfter: 1500,
|
||||
MaxConcurrency: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -309,7 +314,7 @@ func (impl *Implm) Run(
|
||||
return err
|
||||
}
|
||||
|
||||
thirdPartyAssessor, err := impl.buildThirdPartyAssessor(l, tp, r)
|
||||
thirdPartyVetter, err := impl.buildThirdPartyVetter(l, tp, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -522,7 +527,6 @@ func (impl *Implm) Run(
|
||||
esignService,
|
||||
defaultConnectorRegistry,
|
||||
time.Duration(impl.cfg.Auth.InvitationConfirmationTokenValidity)*time.Second,
|
||||
thirdPartyAssessor,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create probo service: %w", err)
|
||||
@@ -552,7 +556,7 @@ func (impl *Implm) Run(
|
||||
l.Named("access-review"),
|
||||
)
|
||||
|
||||
thirdPartyService := thirdparty.NewService(pgClient, fileService)
|
||||
thirdPartyService := thirdparty.NewService(pgClient, fileService, thirdPartyVetter)
|
||||
riskManagementService := riskmanagement.NewService(pgClient)
|
||||
|
||||
serverHandler, err := server.NewServer(
|
||||
@@ -818,6 +822,26 @@ func (impl *Implm) Run(
|
||||
},
|
||||
)
|
||||
|
||||
vettingWorker := thirdparty.NewVettingWorker(
|
||||
pgClient,
|
||||
thirdPartyVetter,
|
||||
l.Named("vetting-worker"),
|
||||
thirdparty.VettingWorkerConfig{
|
||||
StaleAfter: time.Duration(impl.cfg.ThirdPartyVetting.StaleAfter) * time.Second,
|
||||
},
|
||||
worker.WithInterval(time.Duration(impl.cfg.ThirdPartyVetting.Interval)*time.Second),
|
||||
worker.WithMaxConcurrency(impl.cfg.ThirdPartyVetting.MaxConcurrency),
|
||||
)
|
||||
vettingWorkerCtx, stopVettingWorker := context.WithCancel(context.Background())
|
||||
|
||||
wg.Go(
|
||||
func() {
|
||||
if err := vettingWorker.Run(vettingWorkerCtx); err != nil {
|
||||
cancel(fmt.Errorf("vetting worker crashed: %w", err))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
trustCenterServerCtx, stopTrustCenterServer := context.WithCancel(context.Background())
|
||||
defer stopTrustCenterServer()
|
||||
|
||||
@@ -849,6 +873,7 @@ func (impl *Implm) Run(
|
||||
stopTrackerMappingWorker()
|
||||
stopCommonPatternEnrichmentWorker()
|
||||
stopMailingListWorker()
|
||||
stopVettingWorker()
|
||||
stopEvidenceDescriptionWorker()
|
||||
stopDocumentPDFWorker()
|
||||
stopExportJobExporter()
|
||||
|
||||
@@ -18,26 +18,19 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/thirdparty"
|
||||
"go.probo.inc/probo/pkg/vetting"
|
||||
)
|
||||
|
||||
// buildThirdPartyAssessor wires the thirdParty assessment agent. It is an opt-in
|
||||
// feature: deployments that do not set `llm.third-party-assessor.provider` get a
|
||||
// DisabledThirdPartyAssessor that reports the feature as unavailable. The
|
||||
// third-party-assessor does not inherit the default provider because its
|
||||
// pipeline (LLM + browser + search) is expensive and should not be enabled
|
||||
// implicitly.
|
||||
func (impl *Implm) buildThirdPartyAssessor(
|
||||
// buildThirdPartyVetter wires the third-party vetting agent. Unset
|
||||
// third-party-vetter fields inherit from the default agent config
|
||||
// (AGENT_DEFAULT_*), same as evidence-describer and probo.
|
||||
func (impl *Implm) buildThirdPartyVetter(
|
||||
l *log.Logger,
|
||||
tp trace.TracerProvider,
|
||||
r prometheus.Registerer,
|
||||
) (probo.ThirdPartyAssessor, error) {
|
||||
if impl.cfg.Agents.ThirdPartyAssessor.Provider == "" {
|
||||
return probo.DisabledThirdPartyAssessor{}, nil
|
||||
}
|
||||
|
||||
agentCfg, llmClient, err := impl.resolveAgentClient("third-party-assessor", impl.cfg.Agents.ThirdPartyAssessor, l, tp, r)
|
||||
) (thirdparty.Vetter, error) {
|
||||
agentCfg, llmClient, err := impl.resolveAgentClient("third-party-vetter", impl.cfg.Agents.ThirdPartyVetter, l, tp, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -53,6 +46,6 @@ func (impl *Implm) buildThirdPartyAssessor(
|
||||
MaxTokens: maxTokens,
|
||||
ChromeAddr: impl.cfg.ChromeDPAddr,
|
||||
FirecrawlAPIKey: impl.cfg.Agents.Tools.FirecrawlAPIKey,
|
||||
Logger: l.Named("third-party-assessor"),
|
||||
Logger: l.Named("third-party-vetter"),
|
||||
}), nil
|
||||
}
|
||||
Reference in New Issue
Block a user