Skip to Content

Connectors

Three connectors carry the pilot: an ELN connector (category eln), a QMS/SOP connector (category documents) and the open literature and knowledge connector (literature_search). All three are read-only: agents never write into a notebook or a QMS, drafts stay in Trovensa as proposals, and scientists carry them over themselves with the provenance footer. All fetching runs in the API or the worker; the agent container’s only configured endpoint is the tool server. Every connector is designed until the R1 gates pass.

Adapter shape

IntegrationAdapter (with IntegrationContext) keeps its phase-1 methods (describe, begin_connect, complete_connect, health_check, sync, handle_webhook, disconnect) and gains verify_webhook, list_changes, fetch_object, search, render_launch and budget; defaults raise NotSupported. ProviderSpec gains event_verification, write_policy, budget_scope, launchable, credential_class, poll_interval_seconds and api_version_default; IntegrationContext gains budget: BudgetGate and documents: DocumentIngestor.

Provider idCategoryCredential classEventsLaunchableDefault API versionWrite policy
benchlingelnserviceecdsa_jwks signed webhooksyes (notebook canvas)v2read_only
veeva_qualitydocsdocumentsservicenone; 15-minute delta poll and nightly reconciliationnov26.2read_only
literature_searchliteratureplatform keysnonenoper sourceread only

Connections

Connections stay program-scoped (ADR 0018): a partner registers one app or integration user per program and scope_rules (project ids, document types) are enforced in Trovensa even where the vendor token is tenant-wide. ConnectionOut gains scope_rules, poll_interval_seconds, credential_class, write_policy, hook_url, api_version, consented_by and consented_at; health is written on every sync. A bump of api_version is a controlled change that writes an audit row. Reads run under one integration identity per connection, so a console user could see content their own vendor permissions deny; the integration user is provisioned with a minimal profile (effective SOPs of the mapped document types, read-only access to the mapped notebook projects), and every surfaced record names its source system.

connector_objects (ConnectorObjectOut) mirror what a connection has seen: external_type, external_id, external_version, payload_hash, external_modified_at, first_seen_at, last_seen_at, deleted_at, mapped_type, mapped_id, state; unique per connection, type and id.

ELN connector

Provider benchling, read-only. Verified vendor facts (Benchling developer documentation: app authentication, webhooks, App Canvas and rate limits; verified 2026-09-14 and recorded in PRODUCT-CONTRACT.md 10.10): POST /api/v2/token with client credentials issues tokens with expires_in 900 s; webhooks are signed with ECDSA P-256 over id.timestamp.body and carry the headers Webhook-Id, Webhook-Timestamp and Webhook-Signature; the verifying keys come from a JWKS per app definition, refreshed at most every 6 h, and a delivery whose timestamp is outside a 5-minute tolerance is rejected; canvas events v2.canvas.created, v2.canvas.initialized and v2.canvas.userInteracted expect an answer within 3 s; rate limits are 300 requests per 30 s per app and 1000 per 30 s per tenant across all apps, reported in x-rate-limit-* headers; v2 entries are days[].notes[] with modifiedAt and no version counter (v3 uses parts).

  • Token: cached in worker memory, refreshed at 12 minutes (the vendor lifetime is 900 s).
  • Events: POST /api/v1/hooks/benchling/{connection_id} verifies the signature before parsing (JWKS cached and refreshed at most every 6 h, 5-minute timestamp tolerance), answers canvas events with 202 inside the 3 s window and does the work afterwards, writes webhook_deliveries (signature_ok, replay, outcome in enqueued, ignored, rejected), de-duplicates on delivery id inside the replay window, treats payloads as pointers and re-fetches the canonical object.
  • Versions: external_version = <payload_hash>@<modifiedAt>, where the hash is sha256 of canonical JSON of the entry content (days in order, notes in order, table cells row-major, mentions expanded to type:id, attachments as id:sha256); target_ref = benchling:entry:<id>@<hash prefix>.
  • Synced: entries (metadata, modifiedAt, review state mirrored to connector_objects.state and shown as “recorded in the notebook”), entry content flattened into documents as customer_internal with the days structure kept in content_json, attachment metadata (bytes on demand under 25 MB), registry entities linked by id only. Mapping: entry -> sources (eln_entry), experiments.eln_source_id.
  • Budgets: an organisation rate_budgets row of 900 per 30 s; per-connection rows of 250 per 30 s draw from it; vendor x-rate-limit-* headers drive adaptive throttling.
  • Launch: the app canvas renders “Check this entry against the effective SOP”; a click mints a one-time launch_ids row (10-minute expiry) and the canvas gains Open in Trovensa (how-to).
  • Onboarding order: create the connection, copy hook_url, the partner admin creates the app from the manifest with the canvas feature, store client id and secret, run the health check (how-to).
  • Fake server: api/scripts/fake_benchling.py (port 8190) implements signed webhooks, the canvas lifecycle and entries with days[].notes[] for CI and the local demo; results from it are labelled illustrative.

QMS/SOP connector

Provider veeva_qualitydocs, read-only ingestion of controlled SOP versions. Verified vendor facts (Vault REST API reference v26.2: authentication, documents, document versions, renditions, VQL and API limits; verified 2026-09-14 and recorded in PRODUCT-CONTRACT.md 10.10): REST base https://{vaultDNS}/api/v{YY.N} with three frozen versions a year and v26.2 current; POST /api/{version}/auth returns sessionId and vaultId; documents carry major.minor versions and per-Vault lifecycle labels; VQL supports FROM ALLVERSIONS documents with version_modified_date__v, major_version_number__v, minor_version_number__v, status__v and document_number__v; the version text endpoint GET .../versions/{major}/{minor}/text returns text/plain and GET .../renditions/{rendition_type} streams the viewable file; the only throttling headers are X-VaultAPI-BurstLimitRemaining (5-minute window) and X-VaultAPI-ResponseDelay, because the X-VaultAPI-DailyLimit* headers were removed in v21.1, so there is no daily-limit budget; /auth has its own one-minute window; the client header X-VaultAPI-ClientID (at most 100 characters) is sent as trovensa-review-connector-client-vault.

  • Health check (R1c exit gate): validates each field of the per-connection VQL template, the lifecycle map and ALLVERSIONS availability, verifies vaultId, and reports gaps before the first sync.
  • Ingestion: 15-minute delta poll filtered version_modified_date__v > watermark (poll_qms); nightly reconciliation over ALLVERSIONS and the document events endpoint (reconcile_qms); the version text endpoint is preferred over rendition parsing for requirement extraction; renditions are downloaded only when (major, minor, rendition_hash) changed; a connection-scoped rate_budgets row follows the burst header and honours ResponseDelay; connection.health is written on every sync.
  • Versions (sop_versions): major, minor, lifecycle_state mapped per connection (draft, in_review, effective, superseded, obsolete, unmapped), effective_at from the effective-date field when present, else first observation with effective_on_observed = true, superseded_at from the source or the successor’s effective_at, rendition_hash, document_id, external_version_label. Only effective versions bind checks; unmapped states are ineligible and surfaced in health.
  • Authority: the QMS is authoritative for content and lifecycle state; POST .../review/sops/{sop_id}/approve applies to local SOPs only (409 sop_external otherwise); the binding decision for connector SOPs is the requirement-set acceptance (how-to).
  • Pilot reads: the partner’s sandbox in R1c, production QualityDocs through a dedicated read-only integration user at R2 after partner sign-off; the connection records which and the pinned api_version.
  • Fake server: api/scripts/fake_vault.py (port 8191) implements the template query, ALLVERSIONS, renditions and lifecycle labels; its output is labelled illustrative.

Open literature and knowledge connector

Provider literature_search. Platform keys live in trovensa/<env>/providers/literature_search; budgets are platform-scoped with per-organisation fair shares. Licence-gated sources are excluded in phase 2 and, when added, take tenant-supplied keys through connection secrets, never a shared platform key.

SourceKeyProvenance schemesBudget ruleSnapshot stamp
Europe PMCnonePMID, PMCID, DOI, PPRself-cap 5 requests per second (no numeric limit published; bulk automated download forbidden, so bulk full text comes from the PMC Open Data bucket); the licence is read from the license field of resultType=core results (for example cc by); full text from /{PMCID}/fullTextXMLretrieved_at
Crossrefmailto (polite pool; User-Agent also accepted)DOIheader-driven (x-rate-limit-limit 3 per x-rate-limit-interval 1 s observed live); mailto is always sentnone
OpenAlexkey (api_key parameter or bearer)W-id, DOIheader-driven on X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Credits-Used and X-RateLimit-Reset; 429 above 100 requests per second; daily credit budget, stop at 90 % of it (the polite pool is no longer documented); the licence is read from primary_location.license and best_oa_location.license, not from open_accessnone
PubMed E-utilities and PMCkeyPMID, PMCID8 requests per second; full text read anonymously (--no-sign-request) from the public PMC Open Data bucket pmc-oa-opendata in us-east-1, which is not split by licence directory, so the worker filters per article version on the license_code, is_pmc_openaccess and is_manuscript fields of the metadata prefixnone
ClinicalTrials.gov v2noneNCT3 requests per seconddataTimestamp
openFDAkeyreport id, NDC120 000 requests per daymeta.last_updated
Open TargetsnoneEFO, Ensembl3 requests per secondmeta.dataVersion
ChEMBLnoneCHEMBL id, InChIKey3 requests per secondchembl_db_version

Query policy (visible in the run rail): Europe PMC search first, Crossref for DOI metadata, OpenAlex for the citation graph and open-access location, PMC full text only for cc0, cc_by and cc_by_sa; 25 results per query and three queries per turn by default (programs.settings.evidence_query_budget overrides). Sources are de-duplicated per cluster (cluster_key is the DOI when known; PMID, PMCID and W-id are aliases) with per-provider snapshot stamps in provenance. Excluded with reason: Semantic Scholar, DrugBank, Lens, bioRxiv (preprint successors are resolved from Europe PMC PPR records). Licence handling per field is on the licence codes page. Numbers in this table are vendor statements verified on 2026-09-14 in the Europe PMC RESTful API documentation, the OpenAlex API documentation (works, rate limits and authentication), the Crossref REST API documentation (etiquette and rate limits) and the PMC Open Data bucket registry entry, and recorded in PRODUCT-CONTRACT.md 10.10; live smoke tests run behind LIVE=1.

Budgets and events in common

Agent tool calls and syncs draw from the same rate_budgets rows (scope in platform, organization, connection; limit_per_window, window_seconds, used, remote_remaining from vendor headers); exhaustion returns a typed RateLimited that the rail shows as “budget exhausted for source X, retry after N s”. The inbound gateway POST /hooks/{provider_id}/{connection_id} takes no bearer, caps bodies at 1 MB, verifies before parsing, stores rejected deliveries without payload and rate-limits them per source address, and answers 202 in under 2 s. Retention: webhook_deliveries 90 days (rejected 30), launch_ids swept 24 h after expiry.

Source: api/app/integrations/base.py, api/app/integrations/registry.py, api/app/integrations/secret_store.py, api/app/db/models/connection.py, api/app/db/models/connectors.py, api/app/db/models/review.py, api/app/worker.py, web/lib/api/types.ts, shared/knowledge-attributions.json, PRODUCT-CONTRACT.md sections 10.7 and 10.10, PRODUCT-PLAN.md Part 4 section 6, decisions/0015-read-only-connectors-and-agent-service-identity.md, decisions/0018-program-scoped-connections-and-pinned-api-versions.md; vendor documentation pages as named in PRODUCT-CONTRACT.md section 10.10 (Vault REST API reference v26.2, Benchling developer documentation, Europe PMC RESTful API, OpenAlex API, Crossref REST API, PMC Open Data bucket registry entry), read on 2026-09-14