contract connector · 40-connector.md · 41-secrets.md · 42-drive-extensions.md · guide · card
Connectors and the built-in sources
A connector brings external data into a run. Connector implementers use this contract for
component interfaces, host permissions, vendor quotas, model calls, artifact loading and
compiled-in sources. spec/41-secrets.md specifies outbound credentials.
Component and compiled-in connectors use the host’s HTTP client for vendor and model requests:
export
Connector interfaces: shared types, source and destination worlds, optional interfaces and compiled-in sources.
authoring-pathtestEvery 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.type-taxonomytestThe shared types interface carries a schema (name, fields, primary key) and a field (name, data type, nullability) overboolean,int32,int64,float64,string,bytes,timestamp-millisandjson.batch-encodingtestA batch crosses the boundary as Arrow IPC bytes, and a position as opaque bytes beside a declared cursor kind.arrow-typestestThe host reads ArrowBinaryorFixedSizeBinarycolumns as bytes of that width andFixedSizeListofFloat32orFloat16as vectors. Each lands in itsbinary-and-vectortype without text conversion.read-handletestA source exports its cursor kind, discovery call and open call taking a table and optional position. The handle’snextyields one batch or exhaustion;positionreports the current position.optional-worldtestOperator configuration and failure attribution each use an additional world interface. The host probes instances by interface name. A base-world guest instantiates unchanged; neither interface changes the world version.partition-attributiontesttestA guest exporting failure attribution answers with the byte-exact values of the outermost partition column a failed read belongs to. The host reads it on failure and success alike; an empty list keeps the unattributed refusal.Why
because a read holding one partition back and landing the rest returns successattribution-budgettestFailure attribution carries at most 512 entries per session, each value at most 512 B.
A connector failure crosses as a variant of failure-taxonomy.
Scenarios
connector.export.shared-failure: WHEN an attributing guest fails a read withauth-expiredwhile naming two partitions, THEN the session answers no failed partition and the read fails whole.connector.export.shared-failure: WHEN an attach header fails to hydrate during a read, THEN the session answers no failed partition and the read fails whole.
import
Host imports and the configuration table the host forwards to a guest.
empty-contexttestA guest standard library pulling broader interfaces links against an empty context: no preopens, no environment, no arguments.forwarded-configtestThe pipeline’s guest configuration table is the one part of the source configuration crossing inward, as one JSON object delivered once per session ahead of discovery.config-shapetestAhead of any I/O, a guest configuration value that is not a table, a serialization over 64 KiB, or a credential or environment reference inside it raisesConnectorConfigRejected.config-unclaimedtestWhere the artifact is local and probed, a guest table declared against a guest exporting no configuration interface raisesConnectorConfigUnclaimed.Why
P1unusable-keytestA guest raises a permanent failure for an unsupported configuration key or value.Why
P1config-hashingtestThe forwarded table folds into the connector’s content hash; with no table forwarded, the artifact digest is that hash verbatim.
The outer source configuration follows config-key.
declare-capability
Manifest-declared host access, the probe that checks a bound credential’s granted scopes, and the window a connector’s destination deduplicates a key for.
declared-granttestA 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.undeclared-accesstestA connector reaching for access its manifest does not list fails to load, raisingConnectorUndeclaredAccessnaming that access.host-allowlisttestThe outbound allowlist holds exact hosts and subdomain wildcards matched as suffixes; a wildcard entry covers subdomains and not the apex.allowlist-shapetestAn empty allowlist, a bare wildcard, an empty entry, and an entry carrying a scheme, port or path raiseConnectorAllowlistRejected.scope-probetesttestA manifest may declare an identity endpoint, the response header carrying granted scopes, and the grant it expects. Ahead of the first read, the host calls it with the bound credential over TLS or loopback on an allowlisted host.probe-shapetestA scope probe declaring an empty scopes header or a header outside HTTP field-name token syntax raisesConnectorScopeProbeRejected.Why
because a header no response can carry refuses every session as though the vendor withheld the grantscope-exceededtestA granted scope outside the declared expectation, compared byte for byte, raisesConnectorScopeExceeded, and the session does not open.scope-unverifiedtestA probe response carrying no granted-scopes header, or one naming no scope or holding a byte outside visible ASCII, raisesConnectorScopeUnverified.Why
because cannot-verify is not verifieddedup-windowtesttesttestA connector manifest or a plan’s[connector]table declares an optionaldedup_window_secs, the whole seconds its destination deduplicates a repeated idempotency key; each pull’s guard records it underidentity,nonewhen absent.Why
because only the connector’s author knows how long its vendor holds a key
unsettled: Which manifest keys declare environment names and a shared quota, and how does a component load bind each to the operator’s reference and [limiters.<quota>] table? owner: connector affects: connector.declare-capability
Scenarios
connector.declare-capability.undeclared-access: WHEN a component importingwasi:clocks/wall-clockloads under a manifest withoutclock = true, THEN the load raisesConnectorUndeclaredAccessnaming the interface and writes no run row.connector.declare-capability.scope-probe: WHEN the identity endpoint grants a scope outside the manifest’s expectation, THEN the session refuses before discovery and the vendor receives no request.
widen
Comparison of a candidate connector manifest with its admitted predecessor, and operator approval of additional host access.
host-inclusiontesttheoremHost inclusion succeeds when every candidate entry has a covering predecessor entry underhost-allowlist. Success admits no host the predecessor refuses.host-witnesstesttheoremEvery reported host witness passes the candidate’s outbound matcher and fails the predecessor’s outbound matcher.
unsettled: Which operator authorization action and durable approval store serve local reload and control-plane apply? owner: connector affects: connector.widen
Scenarios
connector.widen.host-inclusion: WHEN*.eu.example.comreplaces*.example.com, THEN host inclusion succeeds.connector.widen.host-witness: WHEN*.example.comreplacesx.example.com, THEN a host witness differs from the already admitted exact host.
meter
Vendor quota declarations, limiter bindings, pre-send checks, permit reservations, denials and usage reports.
limiter-declarationtestA connector declares a limiter capability naming the shared vendor quota, the traffic class of its requests, and the vendor quota-state response headers to forward.forward-credentialtestA limiter declaration forwardingauthorization,proxy-authorization,cookieorset-cookieraisesConnectorForwardRejectedat load.Why
because the report carries forwarded headers verbatim to a party outside the vendor credential’s scopelimiter-bindingtestThe operator binds that quota name to an endpoint, a bearer token held by reference, and a permit batch size.quota-unboundtestA declared quota with no binding raisesConnectorQuotaUnboundat load.binding-transporttestA limiter endpoint that is neither HTTPS nor loopback or carries a query, fragment or userinfo, and a limiter token that is not asecret://reference, raiseConnectorLimiterBindingRejectedat load.limiter-addresstestA limiter endpoint may resolve to a private or loopback address; the engine’s check of vendor-host addresses does not apply to it.Why
because the operator authors the limiter endpoint and no vendor answer supplies itreservation-pointtesttestThe host reserves at the mediation point, the guest’s outgoing-HTTP import or the engine’s shared client. One permit covers one outbound request, and the limiter counts requests and prices none.Why
because one call issues any number of requests for paging, retries and token refreshpermit-batchtestAn acquire may grant a batch of permits under a short TTL. Each request spends one, and the next report surrenders the unspent.acquiretestAcquire is an authenticated POST of quota, class and permit count, answered granted with permits and a TTL, or denied with a retry-after. A bare429withRetry-After, and a zero-permit grant, read as denials.acquire-calltestEach limiter call takes at most 5 s. A reservation does not sleep: denial returnsRateLimitedwithout waiting. The run’s retry schedule performs the specified wait.unreadable-answertestAn unreadable limiter response raisesConnectorLimiterUnreadableand grants no permits.denial-ceilingtestA limiter wait, whether a retry-after or a zero-permit grant’s TTL, reads as at most the ceiling ofretry-after.Why
because an unbounded wait named by the limiter parks the run past any retry its schedule allowsreporttestReport is an authenticated POST of quota, class, granted, spent, one entry per vendor response (status, retry-after, verbatim headers), an observation instant and the run id.report-deliverytestThe run delivers reports at least once with bounded backoff and makes one delivery attempt before each refill. An undelivered report accompanies the next. The run audits a report that remains undelivered without failing anything.unmetered-requesttestUnder a declared limiter, a vendor request without a granted reservation raisesConnectorUnmetered, including for an unreachable, unbound or failing limiter. These failures do not indicate expired vendor credentials.pre-send-hooktestEach outbound hop passes one pre-send hook after the allowlist and before name resolution, carrying the hop’s intent: method, scrubbed URL, host, port, traffic class, run id and request body bytes.hook-compositiontesttestThe limiter reservation is the pre-send hook’s innermost implementation, behind the allowlist and any operator hook, so a request either refuses makes no limiter call and spends no permit.hook-refusaltestA hook refusing an intent raisesConnectorEgressRefused, naming the host and reason. The host resolves no name, and the retry schedule does not retry the request.Why
P6hook-settletestThe hook receives each admitted hop’s outcome after the body read or the failure: the status or transport failure, the request body bytes sent, and the response body bytes the host read. A followed redirect is two intents.held-back-requesttestA guest swallowing a held-back request still fails its call, and a fault inside the reservation machinery fails the request rather than passing it unmetered.Why
P2synthesized-throttletestA denied reservation returns to the guest as a synthesized429carrying the retry-after. An unreachable limiter fails the request as a transport failure.Why
because a connector mapping vendor throttles needs no second interface
unsettled: Where does an operator declare the pre-send hook a command-line run composes in front of a compiled-in source’s reservation? owner: connector affects: connector.meter
infer
Model requests: endpoint selection, data boundary markers and output provenance labels.
model-endpointtestEvery 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.mediated-calltestA model call sends through the mediated client undermediation-covers-every-egress, its allowlist the configured endpoint host alone, and passespre-send-hooklike a vendor hop.data-fencetestNo 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.fence-is-not-a-boundarytestData boundary markers lower the success rate of injected instructions and bound nothing. No clause treats the output of a marked call as trusted.Why
because spotlighting reduces injection and guarantees nothingoutput-tainttestModel output carries the least-trusted provenance label among its marked inputs, and lands under that label.Why
because output re-landing as fresh data launders injected textprovenance-ordertestProvenance labels order by trust:operatoraboveingested:first-partyaboveingested:third-party. A call marking no value carriesoperator.Why
because the least-trusted input is defined only over a total order, and a call marking nothing sends the operator’s template alonemarker-derivationtestEvery marker of one call carries one token of 16 B, derived from every label and value the call marks, as marked.Why
because closing one’s own block then requires content containing the digest of a batch that contains itreplaytestThe same values, labels, cap and rules mark to the same bytes, and a change to any value or label moves the token.Why
because a replayed call sends an identical prompt, and a token read from one batch closes no othervalue-captesttestMarking removes control characters other than newline and tab from a value, and a value past its declared character cap keeps its first cap characters and a truncation mark, its closing line still following.label-hygienetestMarking truncates a provenance label at 200 chars and flattens the label to one line without brackets. The label opens or closes no block.Why
because a label is built from connector-supplied table names and row idsempty-batchtestA call marking no value renders an explicit no-data line instead of an empty section.cutoffstestA model reference carries a pinned snapshot id and pre-training and post-training cutoffs, each taggeddocumentedorestimated. An undeclared cutoff compares as the call’s own instant.Why
because tuning adds knowledge after pre-training, and an estimated cutoff often falls before the reported onelogprobstesttestA call record carries the input and output token counts of the endpoint’susagebeside the content, and token log-probabilities where the endpoint exposes them; an answer without counts records none.Why
because probe-based leakage estimates read first-token probabilitiesmodel-spantestA traced call records one client span under the caller’s span:gen_ai.operation.name,gen_ai.request.model,gen_ai.response.model,gen_ai.usage.input_tokens,gen_ai.usage.output_tokens,server.addressand, on failure,error.type. The span excludes message content. The request’straceparentnames this span.hosted-toolstestA model call under a credential carryinghorizonthat declares a provider-hosted tool, such as search, browse or fetch, raisesInferHostedToolUnderHorizon, naming the tool, before any byte leaves.Why
because search inside the provider never reaches the mediated client, so no hook sees it
unsettled: Does a call to an endpoint naming an alias rather than a pinned snapshot refuse under an availability horizon? owner: connector affects: connector.infer
Scenarios
connector.infer.cutoffs: WHEN a model reference carries a pre-training cutoff and no post-training cutoff, THEN the post-training cutoff compares as the call’s own instant.connector.infer.logprobs: WHEN an endpoint returns token log-probabilities, THEN the call record carries them beside the response.
package
Distribution form, digest pinning, per-connector resource bounds and world versioning.
distribution-formtestA connector resolves in one of four forms: in-tree by name, a project-local artifact path, an HTTPS URL, or an OCI reference.built-in-registrytestThe names resolving to compiled-in sources form one enumerated list. A listed name carries no manifest or artifact, and a feature-gated entry stays listed and answers with a rebuild hint.remote-transporttestHTTPS artifact, OCI manifest and OCI layer requests pass through the mediated client with only the reference’s authority admitted, undertransport-port, beforedigest-mismatch.oci-referencetestAn OCI artifact reference isoci://<registry>/<repository>[:<tag>|@sha256:<digest>], withlatestfor an absent selector; an empty registry, repository or selector raisesConnectorOciReferenceInvalidbefore network access.Why
because a malformed reference must not turn a registry name into an unintended request destinationoci-component-layertestAn OCI reference resolves a schema-2 image manifest with exactly one layer, whose media type isapplication/vnd.wasm.content.layer.v1+wasm; another shape raisesConnectorOciArtifactUnsupportedbefore any component compiles.oci-layer-integritytestThe fetched OCI layer matches its manifest descriptor’s SHA-256 digest and the source pin underdigest-mismatch; a descriptor mismatch raisesConnectorOciLayerMismatchbefore cache publication.oci-registry-bearertestAn optionalconfig.toml[connector.registry."<authority>"] authorization = "Bearer ${secret://<name>}"binds one OCI registry authority; hydration reaches manifest and layer requests alone underhydration-is-just-in-time.remote-cachetestA remote artifact admitted underdigest-mismatchenters the project cache at.contextful/artifacts/sha256/<digest>atomically; a later fire reads matching cached bytes before any network request.remote-cache-corrupttestCached artifact bytes whose SHA-256 differs from their cache key raiseConnectorArtifactCacheCorrupt; the host neither fetches a replacement nor compiles those bytes.Why
because a replay must refuse when its recorded artifact bytes are damagedremote-fetch-failuretestIf fetching a remote artifact’s manifest or bytes fails, the host raisesConnectorArtifactFetchFailed, naming the scrubbed reference. The host writes no run row and compiles no component.Why
because a failed installation cannot be mistaken for a source returning no rowsremote-unpinnedtestA remote artifact carrying no 64-hex content pin raisesConnectorRemoteUnpinnedat parse.insecure-artifacttestA plain-HTTP artifact reference raisesConnectorInsecureArtifact.digest-mismatchtestThe host re-hashes the resolved bytes and raisesConnectorDigestMismatchon a difference, before the bytes reach the engine.artifact-cachetestOn Unix, afterdigest-mismatchadmission, the host caches precompiled components by artifact digest and engine compatibility hash; an intact host-written entry deserializes, while a missing, stale or altered entry recompiles.pin-requirementtestEither of two switches requires a local artifact pin: store-wide[connector] require_pinin the store’sconfig.toml, orrequire_wasm_pinin the connector manifest.local-unpinnedtestWith either switch set, an unpinned local artifact raisesConnectorLocalUnpinnedat build, carrying the digest of the bytes found.
build
The connector pin verb builds a guest on one fixed platform from its manifest’s one build input.
manifest-posturetestAn adopted manifest carries a digest from the fixed builder platform. A template manifest carries a pin requirement and placeholder. The parser accepts a template; the loader refuses it.build-sourcetestA manifest’sbuild.sourceis its only build input; the fixed builder platform holds image, target, compiler, package manager and command, and abuild.sourceleaving the manifest directory refuses undermanifest-keys.pin-verbtestcontextful connector pin <manifest>builds the guest in a digest-pinned container on one fixed platform and writes the artifact and its digest.--verifyrebuilds and compares without writing.host-built-pintest--localbuilds with the host toolchain and prints a host-local digest; writing a pin from a host build raisesConnectorHostPinRefused.path-remaptestEvery build remaps the source root and the package cache, so checkout location is not a digest input.
components
A component source loads under its manifest’s grant, runs on the native or interpreted target, and holds to fixed resource bounds.
interpreted-targettestA host built with thepulleyfeature runs components on the interpreted target as portable bytecode, mapping no executable memory, under the same deadlines and mediated client; the compiled native target stays the default.Why
because a hardened runtime refusing writable-then-executable pages loads no compiled guest, and interpretation trades throughput for loading thereinterpreted-target-absenttestA host built without thepulleyfeature refuses the interpreted target at construction, naming the feature, before any component compiles.component-sourcetestA pipeline source named by an artifact path, HTTPS URL or OCI reference runs as a component, its config readingsha256,allow,attach,guest,memory_bytesandmanifest.manifest-keystestA component source’s manifest takesRaw; an absent manifest, another key at any depth, or a value off its shape raisesConnectorManifestRejectednaming the key.Why
P1manifest-pintestA manifest’swasm_sha256pins the artifact its component source loads; a sourcesha256differing from it refuses undermanifest-keysbefore the artifact loads.Why
because one artifact admitted against two pins holds no single reviewed digestmanifest-host-granttestA component source’smanifestnames a project-relative TOML file; at load, eachallowhost must fit its[capabilities] allow_hostsdeclaration or refuses underundeclared-access, naming the excess host.Why
because an operator grant cannot extend past the connector’s declared host accesscomponent-loadtestpipeline runresolves a component source, admits it against its pin and loads it once per fire, before any run row, and records itsconfig-hashingcontent hash as each run’s connector hash.component-granttestA component session’s grant is its declaredallowhosts andattachheaders alone, with credential attachment governed byper-request-hydration; a source declaring noallowreaches no host.component-validatetestpipeline validateresolves a local component artifact against the same directory aspipeline run, then loads it and runs discovery. Missing exports or world mismatches fail before runs. Validation checks remote artifacts without I/O.Why
P1component-positiontesttesttesttestincrementalbeside a component source, thefileor drive source, or thes3source underskip_unchanged = trueraisesConnectorPositionOwnedat validation; the source’s own position is the run’s position.Why
because a watermark over one field replaces the cursor the guest resumes from, so its next read opens at a position it never reportedcomponent-targettestpipeline runandpipeline validatecompile components for the interpreted target when--component-target pulleyorCONTEXTFUL_COMPONENT_TARGET=pulleyselects it; native stays the default.linear-memorytestA connector runs under 256 MiB of linear memory by default, raised per connector to at most 2 GiB.call-deadlinetestA 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.session-budgettestA session carries a 1 MiB logging budget, dropping and counting messages past it, and holds at most 8 requests outbound at once.request-bodytestA guest’s outbound request body carries at most 8 MiB. The host stops reading past it and fails the call as a deterministic refusal before the request reaches the vendor.Why
because the body buffers in host memory, outside the guest’s linear-memory cap
A connector striking a declared bound follows bound-hit-is-transient.
Scenarios
connector.package.remote-transport: WHEN the host fetches a pinned HTTPS or OCI artifact, THEN the mediated client admits only the reference authority before compilation.connector.package.oci-reference: WHEN an OCI reference has an empty repository, THEN validation raisesConnectorOciReferenceInvalidwithout a request.connector.package.oci-component-layer: WHEN an OCI manifest has two component layers, THEN the run raisesConnectorOciArtifactUnsupportedbefore compilation.connector.package.oci-layer-integrity: WHEN the layer bytes disagree with the manifest descriptor, THEN the run raisesConnectorOciLayerMismatchand caches nothing.connector.package.oci-registry-bearer: WHEN the registry requires a bearer, THEN manifest and layer requests carry its resolved binding while guest requests do not.connector.package.remote-cache: WHEN a second fire names a cached pin, THEN it loads matching bytes without a registry or HTTPS request.connector.package.remote-cache-corrupt: WHEN cached bytes disagree with their key, THEN the run raisesConnectorArtifactCacheCorruptwithout fetching or compiling.connector.package.remote-fetch-failure: WHEN a remote endpoint refuses a required fetch, THEN the run raisesConnectorArtifactFetchFailedand writes no run row.connector.package.manifest-keys: WHEN a component source names nomanifest, THEN validation raisesConnectorManifestRejectednamingmanifest.connector.package.manifest-keys: WHEN a manifest declares[capabilities.limiter], THEN the load raisesConnectorManifestRejectednamingcapabilities.limiter, and the guest performs no unmetered execution.connector.package.manifest-pin: WHEN a manifest pins one digest and the component source’ssha256another, THEN the load raisesConnectorManifestRejectednamingwasm_sha256and writes no run row.connector.package.build-source: WHEN a manifest declares[build] source = "../guest", THENconnector pinraisesConnectorManifestRejectednamingbuild.sourceand builds nothing.
source
The declared behavior of each source compiled into the engine.
parse-containmenttestA compiled-in source decodes behindparse-boundary.
http
The generic HTTP source binds credentials, walks its pages under one pagination shape, and expands rows by follow-up requests.
http-headerstestThe generic HTTP source binds credentials through aheaderstable whose values are templates in thevalue-templategrammar, hydrated per read.http-page-pulltestUnder page-number, next-cursor or no pagination and without an incremental field, one generic HTTP source pull reads one page and carries the next page’s token as its position. The walk’s last page carries a position naming the first page.Why
because a failed walk resumes at the page it stopped on, and the next run walks the whole table againhttp-url-walktestUnder next-URL or Link-header pagination, one generic HTTP source pull walks every page, and no position carries a page URL.Why
because a next URL’s query may carry a credential, and a position lands unscrubbed in the journalhttp-watermark-walktestUnder an incremental field without next-URL or Link-header pagination, one generic HTTP source pull reads one page from the stored watermark; its position carries that watermark and the next page’s token, and the walk’s last page carries none.http-limitertestThe generic HTTP source reads itslimiter-declarationfrom alimiterconfig table, and the project manifest holds eachlimiter-bindingunder[limiters.<quota>].http-scope-probetestThe generic HTTP source reads itsscope-probefrom ascope_probeconfig table and carries its first reference-bound header as the bound credential.http-compressiontestThe generic HTTP source decompresses a body only whencompressiondeclaresgziporzip, bounded byobject-expansion. The URL extension does not select compression.Why
because a URL path names a route, not how the vendor stored the bytesconditional-gettesttestconditional = trueissues one request sending the position’setagasIf-None-Matchandlast_modifiedasIf-Modified-Since, and those validators are the whole position. A304lands no rows and holds it; a2xxcommits the validators served.conditional-rejectedtestconditionalbeside a pagination shape or a declaredincrementalraisesConnectorConditionalRejectedat build.Why
because a validator names one document, and a watermark position leaves the validators nowhere to commitpaginationtestA 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.pagination-ambiguitytesttestDeclaring two pagination shapes raisesConnectorPaginationAmbiguous, and declaring both expansion pointer forms raisesConnectorPointerAmbiguous.Why
P1page-captestOne walk issues at most 1000 requests; a watermark walk counts them from its last page whose every clock passes every clock the walk served before.page-looptestA vendor returning a token or link it already served raisesConnectorPageLoop.Why
P4next-link-origintestThe host checks a vendor-supplied next link as a hop underweakened-hop.table-patterntestA table pattern binds table-name segments into the request URL, percent-encoded, and each table holds its own position.table-unmatchedtesttesttestA table not matching the declared pattern, binding.or..into a placeholder, afiletable other thandocuments, or a drive table other thanfilesorpagesraisesConnectorTableUnmatchedahead of any request or walk.Why
P1placeholder-unboundtestA URL or bound-column placeholder naming neither{table}nor a table-pattern field raisesConnectorPlaceholderUnboundat build.Why
P1bound-columnstestbound_columnsmaps a column name to a template over{table}and the table pattern’s fields; every row a read fetches carries each column at its filled value, unencoded.bound-column-occupiedtestA fetched row already carrying a bound column raisesConnectorBoundColumnOccupied, failing the read.Why
because the vendor’s value and the table’s value otherwise disagree with nothing recording which one landedgithub-recipetesttestrecipes/github.tomllands issues without pull requests, keyed onid, and commits, keyed onsha, per<owner>/<repo>table, following theLinkheader, stampingrepo_full_name, and clocking commits at/commit/committer/date.expansiontestAn expansion block names its pointer as a URL template over the landed row’s scalar columns, percent-encoded, or as a column holding a URL, and names the column the fetched document lands under.expansion-ordertestAfter filtering by watermark, the source expands rows with one follow-up request at a time.Why
because a rejected row then costs no vendor request and no batch bursts one hostfollow-up-failuretestA failed follow-up raisesConnectorExpansionFailedfor the whole read.Why
because a position committed past a row missing its detail strands that rowexpansion-budgettestOne expanding read issues at most 200 requests of follow-up, judged before the first, buffers at most 256 MiB, and runs at most 600 s. Each budget refuses rather than truncating.Why
P4template-shapetestA pointer template carrying no placeholder, or binding one into the URL authority, raisesConnectorTemplateRejectedat build.Why
because a response body then names a credential’s destinationpointer-column-missingtestA placeholder naming a column the rows lack, or carry as an array, raisesConnectorPointerColumnMissingon the first row, ahead of its request.Why
P1target-column-occupiedtestA target column any landed index row already carries raisesConnectorTargetColumnOccupiedahead of the walk.Why
P1
formats
A body’s declared format selects its decoder: JSON, delimited text, a feed, an office container or a workbook.
body-formattestformatselects thecontextful-decodedecoder —jsonby default,json-object,jsonl,csv,feedor a workbook — for HTTP, file and object sources. HTTP declares formats; file and object sources infer them from extensions compared without case.json-record-shapetestjsonrequires an array of objects;json-objectrequires one object. Both selectrecordswhen declared, otherwise the root; a missing selection or wrong shape is unreadable input underunreadable-input.json-object-recordtestjson-objectemits one row even when the selected object or its nested arrays are empty; decoding retains the full parsed body for pagination pointers.json-body-pathstestrecords,next_cursor_pathandnext_url_pathread JSON pointers in either JSON mode.json-object-paginationtestjson-objectadmits cursor, next-URL and Link-header pagination; page-number pagination refuses underformat-key-mismatchbefore I/O.Why
because one object never supplies the empty record array that terminates page-number paginationformat-key-mismatchtestA JSON record path, a pagination shape, or a decode key declared against a format that does not read it raisesConnectorFormatKeyRejectedat build.Why
P1delimited-celltestA delimited source lands every cell as a string and an empty unquoted field as null.Why
because a dimension file’s schema then depends on no datadeclared-encodingtestA non-UTF-8 delimited body decodes through a declared encoding label, and bytes invalid under it raiseConnectorEncodingInvalid.Why
P1delimitertestOnformat = "csv",delimiternames one field delimiter character, defaulting to,. Validation accepts a tab and refuses a quote, line break or longer value at build.Why
because a field delimiter that also opens a quote or ends a record leaves every line two readingsdeclared-columnstestcolumnsonformat = "csv"names a headerless body’s columns in order. A first record whose field count differs refuses underconfiguration-fault; a later one is unreadable input underunreadable-input, naming its line.Why
because a headerless body under a short list otherwise lands a shifted cell under the wrong nameclock-column-spellingtestA delimited clock column other than an RFC 3339 instant or a fixed-width digit stamp raisesConnectorClockColumnRejected.Why
because the watermark compares textfeed-formattestformat = "feed"decodes an RSS or Atom document into one row per item or entry carryingentry_id,title,link,published_at,summary,authorandcategories, each null where the entry holds none.feed-entry-idtestA feed row’sentry_idis the Atomidor the RSSguid, else the entry’s link; an entry carrying neither is unreadable input underunreadable-input.Why
because a primary key overentry_idthen collapses every re-fetched entry onto one rowfeed-published-attestpublished_atis the Atompublished, elseupdated, or the RSSpubDate, folded to an RFC 3339 instant in UTC; a date that is neither RFC 3339 nor RFC 2822 is unreadable input.Why
because a watermark overpublished_atthen orders instants, whatever offset the feed spellsfeed-categoriestestcategoriesis JSON array text ofdomainandtermobjects in document order: each RSScategorytext and itsdomain, or Atomtermandscheme; an absent domain is null, an empty term skipped.Why
because a model then selects items by a facet throughjson_extractover one declared columnoffice-part-selectiontestThe decoder reads an office container by exact part name. The decoder opens no other part and resolves no external entity.decompression-budgettestA multi-part office read decompresses at most 64 MiB in total, judged against the archive directory’s claim and against the bytes that arrive.worksheet-landingtestOne worksheet read holds at most 64 MiB — resolved cell text and keys, plus 32 B for each shared string and each stored cell, counted as they land — and lands at most 1048576 rows.Why
because shared-string fan-out turns a small input into an unbounded outputcell-out-of-rangetestA cell holding a value past the header’s width raisesConnectorCellOutOfRange; a cell holding none widens nothing.external-referencetestAn external-reference declaration, an external-links part, or a relationship marked external raisesConnectorExternalReference.workbook-cell-typingtestThe decoder stores a workbook cell as a string and a date cell as its serial number. The decoder stores cached formula values without evaluating formulas.workbook-incrementaltestAn incremental position against a workbook raisesConnectorIncrementalUnsupported.Why
because a variable-width serial number advances a lexicographic watermark past unlanded rows
objects
The s3 source and directory walks address, decompress and dead-letter each object they read.
walk-boundarytestA directory walk reaches the configured root and nothing outside it, skips dot-entries, and traverses in sorted order. A decode-selecting key narrows nothing.Why
because a sorted walk over an unchanged tree reproduces across hostsdeclined-tallytestA directory walk records on the run record what it declined, tallied by extension, and reports the total as itsskipped-count.Why
P4object-addresstestThes3source reads onebucketby exactly one ofkeyorprefix; declaring both or neither raisesConnectorObjectAddressRejectedat build.Why
because a key beside a prefix leaves which objects land to a guess, and neither names no objectprefix-listingtestA prefix read lists every key under the prefix in key order, narrowed by an optionalsuffix, and lands each object through one decoder.pick = "latest"lands the greatest listed key alone.object-gziptesttestWithcompressionundeclared, a key ending.gzreads asgzipand one ending.zipaszip.gzipdecompresses an object before decode,zipdecodes one archive member, andnonereads it as stored.zip-membertestThe decoder reads an archive’s single member or the member selected bymember. Multiple members without a selection, or a missing selected member, refuse the read underconfiguration-fault.Why
because landing one member of several by archive order picks rows the declaration never namedobject-expansiontestOne object or HTTP body decompresses to at most 256 MiB, counted as the bytes arrive. Past it the body lands nothing, named as the object or the scrubbed URL, and an HTTP read refuses whole.object-unreadabletestAn unreadable object, or one exceedingobject-expansion, lands no data rows and onedead-letterrow namings3://<bucket>/<key>. The rest of the read lands.unreadable-alltestAn object or file read in which every input it reaches is unreadable, none landing or held unchanged, refuses whole underunreadable-input, naming each input.Why
because every input failing names a declaration fault, such as a wrongformat, rather than damaged inputsconfiguration-faulttestA compiled-out decoder, unknownencoding, missing selected archive member, unselected multi-member archive, or first record mismatchingcolumnsfails the whole object or file read as a configuration fault. The source names the error and dead-letters nothing.Why
because dead-lettering each changed input under a declaration none satisfies records one fault as many damaged inputsunreadable-positiontestWithskip_unchanged = truea position holds a dead-lettered object at its ETag, and thefileposition holds a dead-lettered file at its digest, so a later read skips it until it changes.Why
because a dead letter re-landing on every fire fills the dead-letter table with one faultdead-letter-replaytestreplay_dead_letters = trueon thes3orfilesource re-reads every input its position holds dead-lettered, whatever its ETag or digest, landing it or dead-lettering it again.Why
because a fixed decoder reads unchanged bytes, which no ETag or digest change announcesunreadable-incompletetestA read that dead-letters an input reports no complete snapshot, so a replacing table retires none of that input’s earlier rows.Why
because a complete snapshot claim tombstones every row the read omitsetag-skiptestWithskip_unchanged = true, the object source’s position maps each object key to its ETag in place ofskip-unchanged‘s digest; an object whose ETag matches lands nothing, and a read matching every object closes a zero-row success.object-credentialstesttestThes3source declaresaccess_key_idandsecret_access_keytogether, plus an optional session token for a temporary credential, each onesecret://<name>reference hydrated per read. Without them, requests go unsigned; a literal refuses undermaterial-in-a-declaration.object-roletestcredentials = "role"on thes3source signs underrole-credentialsin place of the key pair, refusing alongside a key underrole-conflict.object-transporttestThes3source presigns each request through the S3 bucket adapter ofaddressingand sends it through the mediated client, allowlisting the endpoint host alone; a skip compares listed or head-request ETags and downloads no object.Why
because one S3 client then signs every bucket request while the mediated client stays the one attachment point for source egressobject-cleartexttestAt build, the host refuses ans3source binding a key pair to a cleartext endpoint outside loopback undercleartext-endpoint.Why
because a presigned URL is a bearer capability for its lifetime
documents
The file source and the document decoder land one row per page or heading, keyed by slug and ordinal.
document-graintesttestA document source lands one row per page, or one row per heading past a length threshold, each identified by a slug plus an ordinal, chunked or not. With a base URL, each row links to its own page anchor.document-unreadabletestAn encrypted document or a document with no extractable text on any page counts as unreadable input underunreadable-input. The source stores no empty pages for these documents.Why
P2frontmatter-shapetestA nested map, a block scalar, a key carrying the reserved producer prefix, or, compared without case, a repeated key or a key other thantitlenaming a column the source lands, in a note’s frontmatter raisesConnectorFrontmatterRejected.conversion-requiredtestA compound-binary office container, detected by an office extension or by magic bytes under an extension the source reads, raisesConnectorConversionRequirednaming the conversion command.file-sourcetestThefilesource resolvesrootagainst the project directory and walks into onedocumentstable:.mdand.markdownnotes,.txtplain text,.pdfpages. The source declines other extensions without opening them.file-globstestThe source reads a root-relative path if noexcludeglob matches and either anincludeglob matches or none is declared.*and?match within one segment; a**segment matches any number.file-no-followtestThefilewalk follows no symbolic link, to a file or a directory.Why
because a link is the one path out of the root that a sorted walk of names cannot seedocument-slugtestA document’s slug is its root-relative path without extension, lowercased, each run of characters other than ASCII letters and digits in a segment folded to-. Two files folding to one slug fail the read naming both.Why
because a slug is the identity a link and a citation carry, and two files under one identity overwrite each other’s rowsheading-thresholdtestA note whose body past its frontmatter exceeds 8192 B lands one row per heading section, the text before its first heading as its own row; a shorter note and a text file land one row.document-columnstestA document row carriesrow_id, slug, ordinal, root-relative path, kind, title, heading, text, url, the file’s SHA-256 and aremovedflag. The title is the frontmattertitle, else the first level-one heading, else the file stem.frontmatter-columnstestEach other frontmatter key lands lowercased as a string column on every row of its note: a scalar as its text, a list as a JSON array.document-urltestWithbase_url, a row’s url is the base joined with its slug, then#page=<n>on a PDF page or the heading’s anchor on a heading section; withoutbase_urlthe url is null.file-positiontestThefileposition maps each landed path to its digest and row count. A matching digest lands nothing, a changed file re-lands whole and tombstones ordinals past its new count, and a vanished file tombstones every row.file-unreadabletestAn unreadable file lands no data rows and onedead-letterrow. The source retains earlier rows and continues the read.file-renametestA file renamed onto its old slug re-lands under its new path, and the old path tombstones only ordinals past the renamed file’s row count.Why
because a tombstone on an identity the read lands live removes the renamed documentfile-captestThe source declines a file over 64 MiB without reading past the leading bytes identifying its format.Why
because a row-per-page document past that size is an export, not a note, and one file must not exhaust the read’s memoryfile-pdf-absenttestA PDF the walk reaches in a build without the PDF decoder fails the read as a configuration fault naming the feature to rebuild with.
reports
Async vendor reports land through the report source, one watermark per partition.
report-id-committesttestAn async-report source commits the vendor run id when creation returns. Exhausting the polling budget ends the read successfully. The source recreates an expired vendor run for the same window.Why
P4page-draintesttestReport paging drains a fixed number of pages per read, one batch per page, each carrying the position after it.page-cursor-missingtestA response announcing another page while naming its cursor in neither the cursor field nor the next link raisesConnectorPageCursorMissing.Why
P4report-watermarktesttestThe source holds report watermarks per partition: account, grain, breakdown and any split dimension. One position contains the versioned watermark map.partition-isolationtesttesttestA retryable failure on one partition holds it at its committed position and keeps the pages other partitions drained.Why
P4metric-graintestA metric read at a grain where the vendor estimates rather than computes it raisesConnectorMetricGrainRejected.Why
P4restated-windowtestA caught-up partition re-opens its lookback window every run, the pull window is at least that wide, and each row carries a deterministic key over partition, entity, date and breakdown.report-event-timetestA report row’s event time is an RFC 3339 instant at midnight UTC of the window-start day, null where the vendor gave no usable date.ordering-columntestOrdering a report stream by the engine-injected ingest stamp raisesConnectorOrderingColumnRejected.Why
because a late seed then outranks a live restatementfield-drifttestReport rows enter the batch as untyped JSON. The source recognizes throttling from a throttle status or an error body containing a throttle code.report-authtestAn authentication failure on a report read raisesConnectorAuthTerminal, terminal against the retry schedule.Why
P6report-declarationtesttestAreportsource declares its vendor asVendorDeclarationbeside the keys ofReportConfigand lands one table; a missing, unknown or malformed key fails validation naming it.Why
because a request shape found malformed at its first fire fails a scheduled run hours after the edit that broke itreport-templatestesttest{account},{grain},{breakdown},{split.<key>},{start},{end}and{metrics}, comma-joined, fill every template, and{id}thepollandresultURLs, percent-encoded in a URL; another placeholder, or one in a scheme or host, fails validation.Why
because a partition value placed in a host chooses where a bound credential goesreport-origintestEveryreportrequest passes the mediated client with the create URL’s host as its one allowlisted host; apollorresultURL on another origin fails validation, and a vendor-named rows URL off it refuses underweakened-hop.Why
because a credential attaches to one exact host, and a URL the vendor names must not carry it elsewherereport-polltestA poll reads the status atstate.pointer: adonevalue readies the run, anexpiredvalue expires it, and any other value, or none, leaves it pending. Two polls of one run wait at leastintervalapart.report-failedtestA poll reading afailedvalue raisesConnectorReportFailednaming the run id, and the partition’s committed position drops that run, so the next fire creates a new one.Why
because a vendor never rebuilds a failed report, so polling its id again fails every later firereport-resulttesttestA ready run’s pages come fromresult.url, or from the URL its done poll names atresult.pointer; each page after the first carries its cursor as thecursor_paramquery parameter.
images, search and receive
Images, web search results and pushed receive batches each land through their own source.
image-rowtesttestAn image source lands path, content hash, header width and height, embedded capture instant, file modification time, a modality marker and a null body. No pixel decode runs on ingest.image-headertestAn image header that does not parse raisesConnectorImageHeaderUnreadablenaming the path.quotability-markertesttestWhatever fills an image body records in a companion column whether the text is verbatim extraction or a model’s reading.marker-body-pairingtesttestAn image body and its quotability marker that are not null together raiseConnectorQuotabilityMismatch.image-bodytestAn image source declaringbodyasBodyDeclarationposts each image’s bytes to itsurlthrough the mediated client, allowlisting that host alone, and replaces the null body ofimage-rowand its marker with the answer at its pointers.provider-origintesttesttestEach search call site pins its provider’s host. A non-TLS transport or another host raisesConnectorProviderOriginRejected, except on loopback.search-configtesttestAsearchsource declaresSearchDeclaration, at least one query and a positive result ceiling, and lands one table: one row per result object, carrying its query’s name insearch_query.search-templatestesttest{query}and{since}, percent-encoded, fill theurltemplate’s path and query, which holds both; another placeholder, a missing one, or one in the scheme or host fails validation.Why
because a query placed in a host chooses where a bound credential goessearch-pagingtesttestA query’s pass follows the cursor each page names atcursor, sent as thecursor_paramquery parameter, one batch per page carrying the position after it, until a page names none or lands no result, or the pass reachesmax_results.search-watermarktesttestThe source holds one watermark per query, the greatest instant at thetimefield its finished passes landed;{since}fills from it, or from the fire’s instant lessfreshnesswhile none is held.search-resumetestA pass stopped with a cursor resumes at that cursor under the{since}it began with, and the query’s watermark advances only when the pass finishes.Why
because a cursor names a page of one result set, which another{since}changessearch-degradationtestA provider5xx, a throttle or a transport failure on one query holds that query at its committed position, recorded on the run’s audit, and the read lands the other queries.receive-formtesttestAreceivesource declaresformat = "jsonl"and a positivemax_batch_bytes,max_queue_depthandkey_retention_secs, none defaulted, and noschedule; it serves every declared table and requests nothing from outside.Why
because a ceiling the sender fills must be the operator’s choice, not a guess the binary carriesreceive-recordedtestThe engine bounds and records a delivered batch before storing its rows.receive-bodytestAreceivebody holds one JSON object per line,tablenaming a declared table androwits row object; another shape fails the whole batch asunreadable-input, naming the line.
drive
The drive source walks selected Drive folders and lands one row per file and one per PDF page.
drive-walktestThe drive source walksfolder_idbreadth-first throughfiles.list, paging each folder withindrive_idwhen declared. The source lists each folder once, sorts children by name and id, and follows no shortcut.Why
because the source keeps a per-file position and mints its token host-side, which a guest holding no credential bytes cannotdrive-root-settestExactly one offolder_idorfolder_idsselects roots;folder_idsholds 1 to 16 entries, all distinct, under one requireddrive_id. An empty, oversized, repeated or ambiguous selection raisesConnectorDriveRootsInvalidbefore listing.drive-root-validationtestWithfolder_ids, every selected root names a folder in the declareddrive_id. A missing or outside-drive root raisesConnectorDriveRootRejectedbefore the source lists any selected folder.drive-root-walktestWithfolder_ids, the drive source appliesdrive-walkto each root while confining descendants to the declared drive, listing each folder once across overlaps.drive-roottestAfolder_idnaming no folder, or one outside a declareddrive_id, fails the read as a configuration fault before any listing.drive-overlaptestA file reachable through multiple selected roots lands once byfile_id. Itsresolved_rootis the lexicographically least selected root id reaching the file.drive-tablestestThe drive source servesfiles, one row per file keyed byfile_idwith its version, name, path from the root, MIME type,modifiedTime, Drive’smd5Checksumand the SHA-256 of the bytes read, andpages.drive-modetestmodeselectsbytes-and-pagesby default ormetadata-only; another spelling raisesConnectorDriveModeUnknownbefore any request.drive-capture-recordtestEvery file row carriesresolved_root,export_mime_type, capturedbytesandcapture_status:captured,skippedorremoved. A removed row or skipped row without captured bytes has nullsha256.drive-metadata-onlytestInmetadata-onlymode, the source hashes exact downloaded or exported bytes, then discards the bytes after recording the digest.drive-version-consistencytestAfter a metadata-only download or export, the source re-reads the file version; a change from the listed version raisesConnectorDriveVersionMovedbefore any row or cursor commits.Why
because a digest of newer bytes cannot witness the version named by the file rowdrive-exporttestA Google Doc, Sheet or Slides deck lands as itsfiles.exportPDF and any other file as itsalt=mediabytes; another Google-native type lands askippedreason and no bytes.drive-page-graintestInbytes-and-pagesmode, each PDF page lands onepagesrow underdocument-grain, decoded behindparse-boundary; bytes land in no column. Inmetadata-onlymode, no page content row lands.Why
because a page is what retrieval ranks and a citation names, and a row column holding a whole file inflates every scan of the tabledrive-bytestestInbytes-and-pagesmode, every whole exported or downloaded body lands aslanded-blob, named by its file row’ssha256; inmetadata-onlymode, no blob lands.drive-file-captestFor a file overmax_file_bytes, 64 MiB by default, the source stores a file row with askippedreason naming the cap, without pages. The source reads no bytes past the cap and continues.Why
because a row naming the skipped file answers for it, where truncated bytes read as the whole documentdrive-unreadabletestA PDF body failing to decode behindparse-boundarylands its file row with askippedreason naming the failure and no pages, and the read continues.Why
because one unreadable file failing the fire holds back every other file’s row and the position behind themdrive-export-limittestA Doc, Sheet or Slides deck whose export Drive answers403 exportSizeLimitExceededlands its file row with askippedreason naming Drive’s export limit and no pages, and the read continues.Why
because Drive refuses that export on every fire, so failing the read stalls the whole tree behind one filedrive-skip-counttestEach drive pull reports the files it lands with askippedreason as itsskipped-count, so a fire’sfilesrun andpagesrun each carry the tally.drive-selection-positiontestThe position binds the declared drive, sorted root set and mode; changing any of them revalidates every root and rewalks the selected trees, rereading files without reusing an earlier frontier.drive-selection-removalstestA complete selected-root walk lands tombstones for files removed, moved outside the roots or deselected, and for excess prior pages; an incomplete walk commits no removals.drive-root-reassignmenttestFor selected roots, the position also holds each file’sversionandresolved_root. A complete walk re-reads the file when either changes, even with matchingmodifiedTimeand path.Why
because a changed version needs a fresh byte witness even when the file id stays stabledrive-incrementaltestThe drive position holds each file’smodifiedTime, path and page count. A read re-lands a file whose time or path changed, and lands a tombstone for a file gone from the tree and for each page past its new count.drive-list-boundtestA selected-root walk sharespage-capacross roots; reaching it with a pending folder or page token raisesConnectorDriveListingExceeded, naming the folder and committing no selection removals.Why
because an incomplete listing cannot prove a file left the selected treesdrive-firetestOne fire shares one minted token, one walk and one read of each file’s bytes across thefilesandpagestables.drive-oauthtestThe drive source mints its access token from a refresh token and client credentials bound assecret://references, once per fire and again on a401, holding it in memory and writing it to no store, journal or record.Why
because Google’s refresh token does not turn over, so the minted token is the one rotating material, and it lapses within the hourdrive-oauth-shapetestBefore any request, the source checks thatoauth.refresh_token,oauth.client_idandoauth.client_secreteach hold exactly one${secret://<name>}reference and nothing else.drive-origintestThe drive API and token endpoints arewww.googleapis.comandoauth2.googleapis.comover TLS on the default port; another host, port or scheme refuses asprovider-origin, loopback excepted.
unsettled: Does a job whose after names a receive pipeline fire after each delivered batch commits, or does validation refuse it? owner: connector affects: connector.source
Scenarios
connector.source.receive-form: WHEN areceivesource omitsmax_queue_depth, THEN validation refuses naming the key.connector.source.receive-body: WHEN line 2 of a body names an undeclared table, THEN the batch fails naming line 2 and no table lands.connector.source.drive-root-set: WHENfolder_idsis empty or accompaniesfolder_id, THEN validation raisesConnectorDriveRootsInvalidbefore listing.connector.source.drive-root-validation: WHEN one selected root belongs to another Shared Drive, THENConnectorDriveRootRejectednames it before the source lists any selected folder.connector.source.drive-overlap: WHEN two selected roots reach one file, THEN onefilesrow carries the least root id asresolved_root.connector.source.drive-metadata-only: WHEN a downloaded version has known bytes, THEN its SHA-256 matches those bytes, which the source discards after capture.connector.source.drive-bytes: WHEN mode ismetadata-only, THEN no landed blob holds the downloaded bytes.connector.source.drive-page-grain: WHEN mode ismetadata-only, THEN no page content row lands.connector.source.drive-selection-position: WHEN a configured pipeline changes frombytes-and-pagestometadata-only, THEN the next read rewalks its roots and rereads retained files.connector.source.drive-metadata-only: WHEN the source downloads the separately retained named version, THEN its byte SHA-256 reproduces thefilesrow’s digest.connector.source.drive-version-consistency: WHEN a file changes between listing and download, THENConnectorDriveVersionMovedrefuses the capture without a file row or cursor.connector.source.drive-file-cap: WHEN a selected file exceeds the byte cap, THEN its skipped row names the file and cap.connector.source.drive-selection-removals: WHEN a complete new selection excludes a formerly captured file, THEN itsfile_idlands a tombstone.connector.source.drive-selection-removals: WHEN mode changes frombytes-and-pagestometadata-only, THEN old pages receive tombstones and no page content row remains.connector.source.drive-root-reassignment: WHEN a retained file’s version changes with identicalmodifiedTimeand path, THEN the read captures its new bytes and digest.connector.source.drive-list-bound: WHEN listing reaches the request cap with a next-page token but no queued folders, THEN the read refuses and lands no removal tombstone.
unsettled: Does a next-URL walk resume mid-walk through a position holding the URL with every credential-bearing query parameter removed? owner: connector affects: connector.source
unsettled: Does an incremental HTTP pull carry its page token beside the watermark, so a watermarked walk resumes mid-walk rather than from the watermark? owner: connector affects: connector.source
unsettled: Does a conditional read also carry a watermark in one position, so a feed both skips an unchanged document and filters entries by published_at? owner: connector affects: connector.source
unsettled: Does a feed entry with an unreadable date land with a null published_at rather than refusing the whole document? owner: connector affects: connector.source
unsettled: Does the default build link the PDF decoder, so a document folder holding a PDF lands without a rebuild? owner: connector affects: connector.source
unsettled: Does an object read without skip_unchanged hold its dead letters at their SHA-256, so an unchanged unreadable object stops landing a dead letter every fire? owner: connector affects: connector.source
Shapes
A drive source, built with the drive feature, capturing metadata of two folders in one Shared Drive:
[[pipeline]]
id = "team-metadata"
tables = [{ name = "files", primary_key = ["file_id"] }]
[pipeline.source]
name = "drive"
[pipeline.source.config]
drive_id = "0AExampleDrive"
folder_ids = ["1FirstRoot", "2SecondRoot"]
mode = "metadata-only"
[pipeline.source.config.oauth]
refresh_token = "${secret://drive-refresh}"
client_id = "${secret://drive-client-id}"
client_secret = "${secret://drive-client-secret}"
A document folder, its PDF pages decoding in a build with the pdf feature:
[[pipeline]]
id = "handbook"
tables = [{ name = "documents", primary_key = ["slug", "ordinal"] }]
[pipeline.source]
name = "file"
[pipeline.source.config]
root = "handbook" # resolved against the project directory
include = ["**/*.md", "policies/**"]
exclude = ["drafts/**"]
base_url = "https://handbook.example.org"
An async-report source over a declared HTTP vendor, one table holding every partition’s rows:
[[pipeline]]
id = "ads"
tables = ["insights"]
[pipeline.source]
name = "report"
[pipeline.source.config]
partitions = [{ account = "a1", grain = "day", breakdown = "age" }]
metrics = ["spend", "clicks"]
entity = "ad_id"
date = "date_start"
lookback_days = 3
headers = { Authorization = "Bearer ${secret://vendor-token}" }
[pipeline.source.config.create]
url = "https://api.vendor.example/v1/{account}/reports"
body = { since = "{start}", until = "{end}", fields = "{metrics}" }
id = "/report/id"
[pipeline.source.config.poll]
url = "https://api.vendor.example/v1/reports/{id}"
interval = "30s"
[pipeline.source.config.state]
pointer = "/status"
done = "done"
failed = ["failed", "error"]
[pipeline.source.config.result]
url = "https://api.vendor.example/v1/reports/{id}/rows"
A connector manifest in the adopted manifest posture:
name = "vendor-metrics"
version = "1.4.0"
world = "source-connector@1.2.0"
wasm = "vendor-metrics.wasm"
wasm_sha256 = "9f2c1b7ad0e4…" # 64 hex, written by `connector pin`
require_wasm_pin = true
[build]
source = "guest" # the guest crate and its lockfile, beside this file
[capabilities]
allow_hosts = ["api.vendor.example"] # one exact host: the pipeline's attach binds a credential
clock = true
[capabilities.scope_probe]
endpoint = "https://api.vendor.example/v1/identity"
scopes_header = "X-Granted-Scopes"
expect = ["reports.read", "accounts.read"]
One outbound request, end to end: