Docs / Guides
contract connector · 40-connector.md · 41-secrets.md · 42-drive-extensions.md · guide · card
Connectors and credentials
The problem a connector solves
A vendor is an outside service, such as a reporting API. Reading its data needs code
that knows the vendor’s pages and cursors, and a credential the vendor accepts. That code
runs inside the deployment, so it gets no store handle and sees no credential value.
A connector holds the vendor knowledge; the host process runs the connector,
checks every destination, reserves quota and attaches credentials.
The example is vendor-metrics, a connector reading reports from api.vendor.example
with a bearer token named vendor-token.
Loading the connector
A connector is either a sandboxed WebAssembly component or a native source compiled into
the binary, and dispatch treats both alike (authoring-pathconnector.export.authoring-pathEvery connector implements the same trait as either a sandboxed component or a native source compiled into the binary. Dispatch does not branch on these implementation forms.).
vendor-metrics ships as a remote artifact, one of four distribution forms
(distribution-formconnector.package.distribution-formA connector resolves in one of four forms: in-tree by name, a project-local artifact path, an HTTPS URL, or an OCI reference.).
The host re-hashes the downloaded bytes and refuses a mismatch before the engine sees
them (digest-mismatchconnector.package.digest-mismatchThe host re-hashes the resolved bytes and raises ConnectorDigestMismatch on a difference, before the bytes reach the engine.). The component then runs under a memory
limit and a per-call deadline (call-deadlineconnector.package.call-deadlineA read call carries a 30 s wall-clock deadline and a discovery call a 60 s one, armed by epoch interruption at 100 ms granularity.).
Its manifest lists the hosts it reaches, and the host decides that grant at load
(declared-grantconnector.declare-capability.declared-grantA manifest lists required outbound hosts and wall-clock access. The host decides the access grant at load. The connector requests no additional permission at run time and has no escalation path.). Code reaching past the list fails to
load (undeclared-accessconnector.declare-capability.undeclared-accessA connector reaching for access its manifest does not list fails to load, raising ConnectorUndeclaredAccess naming that access.). When a new version lists
another host, the reload compares the two manifests: a candidate host succeeds only
when an admitted host already covers it (host-inclusionconnector.widen.host-inclusionHost inclusion succeeds when every candidate entry has a covering predecessor entry under host-allowlist. Success admits no host the predecessor refuses.).
What crosses the interface
The component imports outgoing HTTP, logging and a wall clock, and its standard library
sees no files, environment or arguments (empty-contextconnector.import.empty-contextA guest standard library pulling broader interfaces links against an empty context: no preopens, no environment, no arguments.). Batches cross
the interface as Arrow IPC, a binary columnar format (batch-encodingconnector.export.batch-encodingA batch crosses the boundary as Arrow IPC bytes, and a position as opaque bytes beside a declared cursor kind.).
One request, end to end
vendor-metrics asks for a report page. Every outbound request goes through the host’s
one mediated client (mediation-covers-every-egressconnector.attach.mediation-covers-every-egressA built-in source, a guest connector, a model call, a limiter call and an exec step's loopback proxy each attach through the one mediated client.). The host
resolves the host name once and connects to the address it vetted
(resolve-onceconnector.attach.resolve-onceAfter pre-send-hook admits the hop, the transport interface resolves the name once per request, the client checks each address under private-address, and the host connects to the address it vetted.). A name resolving to a private or metadata address
fails (private-addressconnector.attach.private-addressA host name resolving to a private, link-local, unique-local, loopback or cloud-metadata address raises ConnectorPrivateAddress, unless the configured host is itself a loopback name or literal.).
The credential binding allows one exact host, api.vendor.example
(bound-hostconnector.attach.bound-hostA credential-bound source declares one non-wildcard host, checked at validation and session open. A wildcard entry or table-bound endpoint host beside a bound credential raises SecretWildcardHost.). An [attach] block maps the Authorization header to
the template Bearer ${secret://vendor-token} (value-templateconnector.reference.value-templateA value embedding a credential is a template of literal text with ${secret://<name>} placeholders, such as Bearer ${secret://vendor-token}.).
The host fills that template afresh for each admitted request (per-request-hydrationconnector.attach.per-request-hydrationA guest session stores hydration handles and plain static headers. The host calls each handle for every permitted request and releases the hydrated header when the request completes.).
A redirect keeps the credential only at the configured host and port
(redirect-pinningconnector.attach.redirect-pinningWith or without credentials, a source follows a hop only at the configured host and port on the same or stronger transport, cleartext-to-TLS included, and stores response bodies from that origin alone.).
Where the credential comes from
A secret:// name is a reference, and a provider chain of adapters answers it,
starting with the lease provider (provider-chainconnector.resolve.provider-chainCONTEXTFUL_SECRETS_BACKEND names the chain's adapters in answer order, env when unset, and the lease provider joins at its head. Naming an adapter the build does not link refuses at assembly.). The first
adapter answering a name serves it for the run (first-hit-winsconnector.resolve.first-hit-winsHydration uses the first adapter answering the name. The resolver consults no later adapter for that reference during the run.). A
second adapter that also holds the name fails the lookup, so no adapter shadows another
silently (shadowed-nameconnector.resolve.shadowed-nameAt first hydration, a name answered by the serving adapter and by any adapter behind it raises SecretNameShadowed, naming the logical name and both adapters.).
A token typed into the manifest, such as X-Api-Key = "abc123", is a plaintext
credential. A profile build refuses it at read, naming the key path but not the token; a
development build, compiled with no profile, reads it (inline-outside-developmentconnector.resolve.inline-outside-developmentOutside the development profile of version-profile, a manifest string under a credential-key holding no secret:// or env:// reference and passing material-in-a-declaration raises SecretInlineCredential at declaration read, naming file and key path, never the value.).
Hydration places the resolved value into an outbound request. The host hydrates per
request, and no record or log line holds the value
(hydration-is-just-in-timeconnector.resolve.hydration-is-just-in-timeWhen building each request, the host asks the resolver for credentials. Cached material follows cache-ttl. Declarations, journal entries, audit records, run records and log lines contain no hydrated values.). A cached value lives at most 300 s
(cache-ttlconnector.resolve.cache-ttlA cache entry lives at most 300 s.).
The deployment declares vendor-token as a lease, so the lease adapter answers it first
(head-of-chainconnector.lease.head-of-chainFor a declared name, the resolver uses the lease provider without falling back to later adapters. A shadowing adapter raises under shadowed-name.). The resolver keeps a leased credential in memory
only, and evicts it at expiry (leaseconnector.lease.leaseThe resolver holds a credential lease's value and its expiry together in memory for the run. The resolver evicts both at expiry. Neither enters a file, journal entry or audit record.).
Rotation replaces the value and keeps the logical name, so no declaration or pinned
artifact changes (turnover-preserves-the-nameconnector.rotate.turnover-preserves-the-nameEvery rotation mechanism keeps the logical name, touching no declaration and no pinned connector artifact.). An operator audits
names with contextful secrets list, which shows configured bindings and reads no value
(inventoryconnector.record.inventorycontextful secrets list displays configured logical bindings with their configured adapters, operator metadata and available provider observations. The command reads no credential values and claims no complete provider inventory.).
When quota or the lease fails
The operator binds the vendor’s quota name to a limiter
(limiter-bindingconnector.meter.limiter-bindingThe operator binds that quota name to an endpoint, a bearer token held by reference, and a permit batch size.). Midway through the pull the limiter denies a
permit. The component sees an ordinary 429 with a retry delay, and the run’s retry
schedule handles it (synthesized-throttleconnector.meter.synthesized-throttleA denied reservation returns to the guest as a synthesized 429 carrying the retry-after. An unreachable limiter fails the request as a transport failure.).
Later the lease expires while its mint endpoint is down. The run then sends 0 requests
to the vendor (vendor-requests-on-failureconnector.lease.vendor-requests-on-failureA run holding an expired credential lease whose provider is unreachable issues 0 requests to the vendor.), and the transient failure
returns to the step’s schedule.
Model calls
Every model call resolves to one operator-configured endpoint
(model-endpointconnector.infer.model-endpointEvery model call resolves to one operator-configured OpenAI-compatible HTTP endpoint declared as a capability. The host owns the credential, rate limit and span; the component owns the prompt template and response schema.). Data boundary markers enclose each ingested value
(data-fenceconnector.infer.data-fenceNo ingested value reaches a model outside its data boundary markers: a pair whose opening marker carries the value's provenance label, a preamble declaring the blocks data, and a closing line restating the caller's rules.). The markers lower the success rate of injected instructions but
bound nothing
(fence-is-not-a-boundaryconnector.infer.fence-is-not-a-boundaryData boundary markers lower the success rate of injected instructions and bound nothing. No clause treats the output of a marked call as trusted.).
Model output carries the least-trusted
label among its inputs (output-taintconnector.infer.output-taintModel output carries the least-trusted provenance label among its marked inputs, and lands under that label.).
Built-in sources and Drive
The engine compiles in sources such as a paginated HTTP walk; each walk declares exactly
one pagination shape (paginationconnector.source.paginationA walk declares exactly one pagination shape: a page parameter with a start page, a next-cursor path with a cursor parameter, a next-URL path, or link-header following.).
The Drive sources read Google Drive under an end user’s grant. gdrive-picks serves
files a person selects through Picker (picker-sourceconnector.drive-picks.picker-sourceThe gdrive-picks source serves files and pages with drive-oauth and drive-page-grain for files selected through Picker.), and
gdrive-sheet captures a Sheet as rows (source-nameconnector.drive-sheet.source-nameThe gdrive-sheet source serves rows and shares drive-oauth and file-selection without changing the gdrive source's files and pages tables.).
A selection keeps a change token, committed with the first complete listing
(initial-tokenconnector.drive-changes.initial-tokenBefore the initial full listing, the source obtains a start page token. The source commits the token with the complete listing, preserving replay of changes during listing.). If Drive rejects the token, the source lists
the selection again (invalid-tokenconnector.drive-changes.invalid-tokenIf Drive rejects or expires a change token, the source starts a complete selected listing. The incomplete change response advances no removals.).
Under domain-wide delegation, each request names one admitted Workspace user as its
subject (impersonated-principalconnector.drive-domain-auth.impersonated-principalEvery delegated Drive request names one admitted Workspace user as its subject; the row and permission sweep retain that subject's identity.). A subject outside the
declared domain fails before any token mint (domain-boundaryconnector.drive-domain-auth.domain-boundaryA delegated request for a subject outside its declared domain raises ConnectorDriveDomainRejected before token minting.).
A connection binds one secret name to its tenant and principal, and a run hydrates only
that name (name-bindingconnector.principal-secrets.name-bindingA connection binds one opaque secret name to its admitted tenant and principal; a run hydrates only the name bound to its connection.).