Docs / Specification
A-connector — Connectors and credentials decisions
Status: accepted
executables-content-pinned — Executables are content-pinned and replay uses the recorded artifact
Cited byrun.own.pinned-plan-changedrun.own.pinned-plan-changedWhile an owner is pending, a changed pin — connector identity, component world, pipeline content_hash, or a host's plan reference or identities — raises ExecutionPinMismatch before replay, terminal and non-retryable, naming the scope and both pin sets.run.own.admission-pinrun.own.admission-pinA run pins its connector identity at admission and a replay resolves the artifact from that pin; a connector rebuilt later reaches no in-flight or replayed run.run.exec.missing-binaryrun.exec.missing-binaryA command[0] that is not an executable file on this machine raises DeriveBinaryMissing, naming the binary and the step.run.exec.unpinned-pathrun.exec.unpinned-pathA path-form command[0] without sha256 raises DeriveUnpinnedPath, quoting the computed digest. A bare search-path name carries no digest.run.exec.digest-mismatchrun.exec.digest-mismatchA pinned file whose bytes differ from its recorded digest raises DeriveDigestMismatch.connector.package.remote-cacheconnector.package.remote-cacheA remote artifact admitted under digest-mismatch enters the project cache at .contextful/artifacts/sha256/<digest> atomically; a later fire reads matching cached bytes before any network request.connector.package.insecure-artifactconnector.package.insecure-artifactA plain-HTTP artifact reference raises ConnectorInsecureArtifact.connector.package.digest-mismatchconnector.package.digest-mismatchThe host re-hashes the resolved bytes and raises ConnectorDigestMismatch on a difference, before the bytes reach the engine.connector.package.local-unpinnedconnector.package.local-unpinnedWith either switch set, an unpinned local artifact raises ConnectorLocalUnpinned at build, carrying the digest of the bytes found.
A live run completes against the digest its record names; a changed connector reaches new runs only.
| Option | Lost on | Cost |
|---|
| Pin by digest, hold the pin while work is pending (chosen) | — | Pinning needs a container runtime; an urgent fix waits for in-flight runs. |
| Trust version tags | Review binding | Reviewed and running programs match only while the publisher behaves. |
| Reload in place, or adopt the new build on resume | Replay fidelity | Recorded output replays into code that never produced it. |
| Digest every command, including bare names | Runnability | Each package upgrade refuses every derive pipeline until re-pinned. |
Revisit: production runs unpinned local artifacts, arguing for inverting the default; the toolchain stops embedding the host triple.
remote-artifacts-use — Remote artifacts use one verified component blob
Cited byconnector.package.remote-transportconnector.package.remote-transportHTTPS artifact, OCI manifest and OCI layer requests pass through the mediated client with only the reference's authority admitted, under transport-port, before digest-mismatch.connector.package.remote-cacheconnector.package.remote-cacheA remote artifact admitted under digest-mismatch enters the project cache at .contextful/artifacts/sha256/<digest> atomically; a later fire reads matching cached bytes before any network request.
A damaged cache entry refuses rather than replacing the bytes of a recorded run.
| Option | Lost on | Cost |
|---|
| One pinned blob over mediated HTTP or one-layer OCI, cached by digest (chosen) | — | Multi-layer packages and registry token exchanges need another contract. |
| Resolve every run from the remote reference | Replay availability | An outage stops replay of bytes once admitted locally. |
| Reuse the guest’s grant and headers for retrieval | Credential containment | Registry material reaches guest endpoints, and vendor material reaches the registry. |
| Trust an OCI tag and descriptor alone | Review binding | A moved tag changes executable bytes under an unchanged pin. |
Revisit: a required registry exposes no directly bound bearer; a package needs several blobs or an OCI index.
content-pin-trust — The content pin is the only trust root
Cited byconnector.package.oci-component-layerconnector.package.oci-component-layerAn OCI reference resolves a schema-2 image manifest with exactly one layer, whose media type is application/vnd.wasm.content.layer.v1+wasm; another shape raises ConnectorOciArtifactUnsupported before any component compiles.connector.package.oci-registry-bearerconnector.package.oci-registry-bearerAn optional config.toml [connector.registry."<authority>"] authorization = "Bearer ${secret://<name>}" binds one OCI registry authority; hydration reaches manifest and layer requests alone under hydration-is-just-in-time.connector.package.remote-unpinnedconnector.package.remote-unpinnedA remote artifact carrying no 64-hex content pin raises ConnectorRemoteUnpinned at parse.connector.package.component-loadconnector.package.component-loadpipeline run resolves a component source, admits it against its pin and loads it once per fire, before any run row, and records its config-hashing content hash as each run's connector hash.
A reviewer binds review to bytes by rebuilding with connector pin --verify and comparing digests.
| Option | Lost on | Cost |
|---|
| Pin plus reproducible rebuild (chosen) | — | An operator who neither builds nor reviews trusts whoever handed over the pin. |
| Publisher signature plus transparency log | Replay availability | Admission depends on a key custodian, a log operator and a network round trip. |
| Signature without a log, or trust on first use | Review binding | A stolen or rotated key signs bytes no reviewer saw. |
| Optional signature checked when present | Failure direction | An absent signature reads like a valid one. |
Revisit: a public registry distributes connectors to operators who neither build nor review them.
pin-builder-runs — The pin builder runs one fixed platform
Cited byconnector.package.oci-layer-integrityconnector.package.oci-layer-integrityThe fetched OCI layer matches its manifest descriptor's SHA-256 digest and the source pin under digest-mismatch; a descriptor mismatch raises ConnectorOciLayerMismatch before cache publication.connector.package.build-sourceconnector.package.build-sourceA manifest's build.source is its only build input; the fixed builder platform holds image, target, compiler, package manager and command, and a build.source leaving the manifest directory refuses under manifest-keys.connector.package.pin-verbconnector.package.pin-verbcontextful connector pin <manifest> builds the guest in a digest-pinned container on one fixed platform and writes the artifact and its digest. --verify rebuilds and compares without writing.connector.package.host-built-pinconnector.package.host-built-pin--local builds with the host toolchain and prints a host-local digest; writing a pin from a host build raises ConnectorHostPinRefused.connector.package.component-grantconnector.package.component-grantA component session's grant is its declared allow hosts and attach headers alone, with credential attachment governed by per-request-hydration; a source declaring no allow reaches no host.
PLATFORM in contextful-core::connector::package defines the platform once.
| Option | Lost on | Cost |
|---|
| One fixed platform, one manifest build input (chosen) | — | A toolchain bump re-pins every connector at once. |
| Per-manifest image and argv | Reproducibility | Each digest reproduces only on its own image. |
| Per-manifest toolchain version fields | One home per fact | The manifest and the image both state the toolchain. |
| Per-connector Dockerfile | Reviewability | Build inputs hide in a second file a reviewer reads as code. |
Revisit: a guest language other than Rust needs first-class pinning.
precompiled-components-reuse — Precompiled components reuse only compatible host output
Cited byconnector.package.artifact-cacheconnector.package.artifact-cacheOn Unix, after digest-mismatch admission, 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.
Precompiled output never travels with a pin.
| Option | Lost on | Cost |
|---|
| Private cache with compatibility key and integrity check (chosen) | — | A hardened host still links a compiler for cache misses. |
| Recompile on every process start | Startup latency | Every guest pays compilation again. |
| Deserialize any file named by the artifact digest | Input integrity | A stale engine build or altered file reaches unsafe deserialization. |
Revisit: a pinned precompile artifact and a compiler-free host build exist for every deployment target.
guest-world-targets — The guest world targets synchronous WASI 0.2
Cited byconnector.export.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.connector.import.config-shapeconnector.import.config-shapeAhead 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 raises ConnectorConfigRejected.
The call deadline, the session budget and the absent wait primitive each hold because a guest call runs to completion on one stack. Adopting 0.3 changes all three and is a major world bump.
| Option | Lost on | Cost |
|---|
| WASI 0.2 with synchronous exports (chosen) | — | A guest overlaps requests only through outgoing-handler futures. |
| WASI 0.3 native async | Runtime maturity | The host runtime gates the 0.3 interfaces as experimental. |
| Both worlds side by side | One implementation per capability | Dispatch branches on the world. |
Revisit: the host runtime ships the 0.3 interfaces without an experimental gate.
Cited bytopology.package.decode-network-freetopology.package.decode-network-freecontextful-decode reaching contextful-outbound, ureq, hyper, reqwest, rustls, curl or tokio through a normal dependency raises DecodeLinksNetwork, naming the path that pulled it.run.land.unreadable-inputrun.land.unreadable-inputInput a parser cannot read raises PipelineUnreadableInput, naming the path and the position inside it, permanent against the retry schedule and failing one table's pull unless its source dead-letters it.run.land.dead-letterrun.land.dead-letterA pull's dead letters land in the table <table>_dead_letter, one DeadLetter row per input, committed in the same run as the table's rows.run.land.partial-parserun.land.partial-parseA reader stopping partway through a multi-part input raises PipelinePartialParse over the whole input and lands none of its parts.run.land.parse-boundaryrun.land.parse-boundaryEach input whose decoder is subject to process failure decodes alone in a child process outside the serving process, which bounds the child's wall clock and memory and can terminate it.run.land.parse-crashedrun.land.parse-crashedA non-zero exit or fatal signal from the decode process raises PipelineParseCrashed naming the input; no input ends the serving process.run.fetch.charsetrun.fetch.charsetA declared character set other than UTF-8 raises DeriveCharsetUnsupported, naming the value.run.fetch.not-utf8run.fetch.not-utf8If a document declares no character set and fails UTF-8 validation, the fetch driver raises DeriveBytesNotUtf8. The validator tolerates a character split at the byte bound.connector.source.parse-containmentconnector.source.parse-containmentA compiled-in source decodes behind parse-boundary.connector.source.office-part-selectionconnector.source.office-part-selectionThe decoder reads an office container by exact part name. The decoder opens no other part and resolves no external entity.connector.source.decompression-budgetconnector.source.decompression-budgetA multi-part office read decompresses at most 64 MiB in total, judged against the archive directory's claim and against the bytes that arrive.connector.source.cell-out-of-rangeconnector.source.cell-out-of-rangeA cell holding a value past the header's width raises ConnectorCellOutOfRange; a cell holding none widens nothing.connector.source.workbook-cell-typingconnector.source.workbook-cell-typingThe 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.connector.source.frontmatter-shapeconnector.source.frontmatter-shapeA nested map, a block scalar, a key carrying the reserved producer prefix, or, compared without case, a repeated key or a key other than title naming a column the source lands, in a note's frontmatter raises ConnectorFrontmatterRejected.connector.source.conversion-requiredconnector.source.conversion-requiredA compound-binary office container, detected by an office extension or by magic bytes under an extension the source reads, raises ConnectorConversionRequired naming the conversion command.connector.source.image-headerconnector.source.image-headerAn image header that does not parse raises ConnectorImageHeaderUnreadable naming the path.connector.source.marker-body-pairingconnector.source.marker-body-pairingAn image body and its quotability marker that are not null together raise ConnectorQuotabilityMismatch.
An input lands whole and faithful or refuses by name. A native decoder is neither interruptible nor memory-bounded in process.
| Option | Lost on | Cost |
|---|
| Process boundary, whole-input refusal per table (chosen) | — | Every input pays a boundary crossing; one damaged page discards a readable document. |
| In-process unwind guard or watchdog thread | Effectiveness | A native decoder crash or runaway allocation ends the process. |
| Land the parsed parts with a truncation flag | Distinguishability | No reader consults the flag, so a fragment is quoted as the document. |
| Lossy or statistical decoding | Honesty of landed values | Replacement characters land as publisher text. |
Revisit: crossing overhead exceeds decode time on small inputs; a memory-safe compound-binary reader exists.
unreadable-objects-files — Unreadable objects and files dead-letter while the read lands the rest
Cited byconnector.source.object-unreadableconnector.source.object-unreadableAn unreadable object, or one exceeding object-expansion, lands no data rows and one dead-letter row naming s3://<bucket>/<key>. The rest of the read lands.connector.source.file-unreadableconnector.source.file-unreadableAn unreadable file lands no data rows and one dead-letter row. The source retains earlier rows and continues the read.
One damaged input refusing a prefix or folder read would hold back every other input. The dead-letter table names each input a collection lacks; a replacing table still refuses, since a partial inventory cannot replace it.
| Option | Lost on | Cost |
|---|
| Dead-letter table, position past the input (chosen) | — | A sibling table per table; a fixed decoder waits on a replay. |
| Refuse the whole read | Progress | One damaged object stalls thousands. |
| Skip and tally under a ceiling | Answerability | A count names no input. |
| Audit entries on the run record | Queryability | Capped per run; joins no table. |
host-access-quota — Host access and quota are declared and fail closed
Cited byrun.select.unmetered-grantrun.select.unmetered-grantA derive pipeline declaring a shared-quota grant for a task other than link_preview or a host task declaring pure false raises DeriveUnmeteredGrant.run.select.metered-clientrun.select.metered-clientA link_preview pipeline opening a socket outside the mediated client raises DeriveMeteredClient; every request it makes enters the run's request ledger.run.bind.reach-clientrun.bind.reach-clientA host task declaring pure false receives the run's metered client for every unit; a run binding none refuses its request as metered-client.run.bind.reach-grantrun.bind.reach-grantUnder a grant, the metered client of a host task declaring pure false reserves one permit from the grant's shared quota before each request and model call, so every pipeline naming that quota draws from one limit.connector.declare-capability.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.connector.declare-capability.undeclared-accessconnector.declare-capability.undeclared-accessA connector reaching for access its manifest does not list fails to load, raising ConnectorUndeclaredAccess naming that access.connector.declare-capability.allowlist-shapeconnector.declare-capability.allowlist-shapeAn empty allowlist, a bare wildcard, an empty entry, and an entry carrying a scheme, port or path raise ConnectorAllowlistRejected.connector.declare-capability.scope-exceededconnector.declare-capability.scope-exceededA granted scope outside the declared expectation, compared byte for byte, raises ConnectorScopeExceeded, and the session does not open.connector.meter.quota-unboundconnector.meter.quota-unboundA declared quota with no binding raises ConnectorQuotaUnbound at load.connector.meter.unreadable-answerconnector.meter.unreadable-answerAn unreadable limiter response raises ConnectorLimiterUnreadable and grants no permits.connector.meter.unmetered-requestconnector.meter.unmetered-requestUnder a declared limiter, a vendor request without a granted reservation raises ConnectorUnmetered, including for an unreachable, unbound or failing limiter. These failures do not indicate expired vendor credentials.
The operator reads every host access and quota draw in a file before anything runs; a degraded answer is a denial.
| Option | Lost on | Cost |
|---|
| Grants decided at load, fail closed (chosen) | — | A second host needs an edit, review and reload; each permit batch costs a round trip. |
| A run-time permission request | Reviewability | The grant set is a union over code paths. |
| Fail open on an unreachable limiter | Failure direction | An unmetered burst lands during the outage. |
| A local token bucket in the engine | Visibility | It caps the engine with no view of other traffic. |
Revisit: a connector class whose reachable hosts are data-dependent; permit round trips become a measurable share of read time.
outbound-transport-origin — Outbound transport is origin-pinned and never downgrades
Cited byrun.fetch.schemerun.fetch.schemeAn address whose scheme is neither http nor https raises DeriveSchemeUnsupported before any socket opens.run.fetch.address-literalrun.fetch.address-literalA host written as an address literal raises DeriveAddressLiteral.connector.source.next-link-originconnector.source.next-link-originThe host checks a vendor-supplied next link as a hop under weakened-hop.connector.source.external-referenceconnector.source.external-referenceAn external-reference declaration, an external-links part, or a relationship marked external raises ConnectorExternalReference.connector.source.provider-originconnector.source.provider-originEach search call site pins its provider's host. A non-TLS transport or another host raises ConnectorProviderOriginRejected, except on loopback.connector.lease.mint-transportconnector.lease.mint-transportThe mint client reaches its endpoint over TLS or loopback and ignores system proxy configuration.connector.lease.redirectconnector.lease.redirectA 3xx from the mint endpoint raises SecretLeaseRedirect; the mint credential is not replayed at the target.connector.attach.no-material-to-a-guestconnector.attach.no-material-to-a-guestNo host import hands credential bytes to guest code; a guest names a request and receives a response.connector.attach.unpermitted-requestconnector.attach.unpermitted-requestA request whose host the declaration does not cover raises SecretUnpermittedRequest and fails the guest call even where guest code discards the error.connector.attach.transport-portconnector.attach.transport-portThe mediated client reaches the network only through a transport interface. The send method connects to a vetted address, follows no redirect, and applies the client's proxy choice, timeout and body ceiling.connector.attach.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.connector.attach.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.connector.attach.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.connector.attach.weakened-hopconnector.attach.weakened-hopA redirect or vendor-supplied next link moving from TLS to cleartext, or to another host or port, raises SecretRedirectOffOrigin, and the read fails.connector.attach.cleartext-endpointconnector.attach.cleartext-endpointThe host marks a reference-hydrated header sensitive. Sending the header to a cleartext endpoint raises SecretCleartextEndpoint, except on IPv4 or IPv6 loopback.connector.attach.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 origin answering any outbound request equals the declared origin, over the same or a stronger transport.
| Option | Lost on | Cost |
|---|
| Pin origin at declaration and every hop (chosen) | — | A vendor moving to a www or CDN host refuses until the declaration changes. |
| Pin only credential-bearing sources | Landed-content provenance | A redirected source lands another party’s body in a trusted column. |
| Warn and send, or follow and record | Irreversibility | The record follows a disclosure it cannot undo. |
| Vet the host name, not the resolved address | Internal-range containment | A name whose DNS points inward reaches the machine’s network. |
Revisit: the declaration grammar gains a per-vendor allowed-origin set; proxy-only egress is the sole path for a class of deployments.
credentials-references-workload — Credentials are references, workload identity is the default, and leases lead
Cited byconnector.source.http-headersconnector.source.http-headersThe generic HTTP source binds credentials through a headers table whose values are templates in the value-template grammar, hydrated per read.connector.reference.foreign-placeholderconnector.reference.foreign-placeholder${env://NAME} or a bare ${NAME} inside a template raises SecretForeignPlaceholder at parse, naming the value and the span.connector.reference.environment-is-a-missconnector.reference.environment-is-a-missTemplate hydration skips the process-environment adapter unless CONTEXTFUL_SECRETS_ALLOW_ENV_TEMPLATES=1, and continues through the chain. If no adapter answers, the resolver applies unresolved-name.connector.reference.material-in-a-declarationconnector.reference.material-in-a-declarationA credential-shaped literal standing where a reference belongs raises SecretMaterialInDeclaration at validation, naming the key.connector.resolve.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.connector.resolve.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.connector.lease.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.connector.lease.unknown-scopeconnector.lease.unknown-scopeA 404 raises SecretLeaseScopeUnknown as a configuration fault. The resolver does not fall back to a stored credential for a declared name.connector.lease.bootstrap-credentialconnector.lease.bootstrap-credentialThe engine authenticates to the mint endpoint with one standing secret:// credential, hydrated only through the adapters the chain held before the lease provider joined.connector.lease.bootstrap-unservedconnector.lease.bootstrap-unservedA bootstrap name no adapter behind the lease provider answers raises SecretBootstrapUnresolved, naming the reference.connector.lease.bootstrap-declared-leasedconnector.lease.bootstrap-declared-leasedListing the bootstrap name among the leased scopes raises SecretBootstrapLeased at startup.
The engine holds the shortest-lived material the deployment mints.
| Option | Lost on | Cost |
|---|
| References only, workload identity, lease first (chosen) | — | Material sits in engine memory for a request. |
| Inline material or environment templates | Plaintext at rest | The committed file or environment becomes the secret. |
| A broker sidecar as the default | Operability | Every deployment runs a second failure domain. |
| Lease provider last, first hit wins | Precedence | A leftover environment variable wins unnoticed. |
Revisit: workload identity becomes unattestable on a needed runtime; a broker becomes near-free to operate.
credential-custody-stays — Credential custody stays with the provider and inventory labels its evidence
Cited byconnector.record.operator-recordconnector.record.operator-recordAn optional operator record describes one logical name's intended grants, account or tenancy, consuming surfaces with their binding names, and rotation location, as descriptive metadata; recording it requires no credential value or decryption key.connector.record.provider-custodyconnector.record.provider-custodyThe selected provider owns credential values, versions and rotation state; an external-manager binding requires no ciphertext copy in the repository.connector.record.record-tableconnector.record.record-tableAn operator record is a [secret.<name>] table in the discovered contextful.toml taking OperatorRecord, each key optional; expires = "never" is the one explicit no-expiry assertion.connector.record.observe-portconnector.record.observe-portAn adapter's observation interface returns presence, version, creation and expiry for a name, or unavailable with a reason. The interface holds no hydrated value. Inventory holds only observation interfaces.
Configured adapters and observed runtime attribution answer different questions, so inventory labels each. A missing expiry stays unknown.
| Option | Lost on | Cost |
|---|
| Provider custody, optional descriptive records (chosen) | — | Metadata completeness varies by adapter. |
| Mandatory encrypted entries in Git | Backend independence | External managers need duplicate ciphertext; rotation needs commits. |
| Provider enumeration as the whole inventory | Binding coverage | Unresolvable configured names disappear. |
Revisit: a shared provider metadata protocol supplies complete observations without value access.
rotation-writes-forward — Rotation writes forward to the answering adapter
Cited byconnector.lease.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.connector.rotate.division-of-laborconnector.rotate.division-of-laborA credential store contributes versioned storage, a trigger and room for an opaque expires_at hint beside the ciphertext. The host drives the OAuth loop from the connector's auth policy and writes the token back as a blind versioned put.connector.rotate.oauth-credentialconnector.rotate.oauth-credentialThe host refreshes an OAuth credential at retirement-margin from its declared expiry. The host puts the refreshed version to the answering adapter, conditioned on the hydrated version, then clears the name's cache entry.connector.rotate.write-back-deniedconnector.rotate.write-back-deniedA refused put or answering adapter without a write method raises SecretRotationWriteDenied. The host discards the refreshed token without serving it, and the run fails closed.connector.rotate.conditional-putconnector.rotate.conditional-putIf another writer stores a version first, the losing put rehydrates that version without raising an error.connector.rotate.access-tokenconnector.rotate.access-tokenThe access token name renders from the host's held token alone: no adapter answers it, no file, journal or record receives it, and the host refreshes when it holds none or at oauth-credential.connector.rotate.refresh-answerconnector.rotate.refresh-answerA success carries access_token, a positive expires_in as its declared expiry, and a replacement refresh_token the host puts under oauth-credential; an answer without a replacement puts nothing.
The environment, keychain and lease adapters have none, so operator-entered and leased credentials never write back.
| Option | Lost on | Cost |
|---|
| Conditional put to the attributed adapter (chosen) | — | A read-only manager fails the run on every refresh. |
| First writable adapter in the chain | Name join | A shadowing adapter in front hides the stored version. |
| Serve the token from memory on a refused put | Provider custody | A rotated refresh token leaves the manager’s copy dead, unrecorded. |
| Unconditional put | Atomic turnover | A slower replica overwrites a newer token. |
Revisit: a manager offers no conditional write; two resolvers share one cache across runs.
egress-passes-transport — Egress passes one transport port and one pre-send hook ahead of resolution
Cited bytopology.package.transport-optionaltopology.package.transport-optionalWithout transport-ureq, contextful-outbound excludes ureq, hyper, reqwest, rustls and curl from normal dependencies. A dependency reaching these raises TransportStackLinked, naming the dependency path.connector.meter.binding-transportconnector.meter.binding-transportA limiter endpoint that is neither HTTPS nor loopback or carries a query, fragment or userinfo, and a limiter token that is not a secret:// reference, raise ConnectorLimiterBindingRejected at load.connector.meter.pre-send-hookconnector.meter.pre-send-hookEach 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.connector.meter.hook-compositionconnector.meter.hook-compositionThe 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.connector.meter.hook-settleconnector.meter.hook-settleThe 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.connector.infer.mediated-callconnector.infer.mediated-callA model call sends through the mediated client under mediation-covers-every-egress, its allowlist the configured endpoint host alone, and passes pre-send-hook like a vendor hop.
Allowlist precedence decided the hook’s position: no host lookup precedes a refusal, and one attachment point serves every hop.
| Option | Lost on | Cost |
|---|
| Allowlist, one intent hook, port-owned resolution (chosen) | — | Each adapter re-implements proxy choice, timeout and body ceiling. |
| Hook ahead of the allowlist | Allowlist precedence | An undeclared host reaches the operator hook and the limiter. |
| An egress gate beside the meter | One attachment point | Three pre-send hooks, each a call site to miss. |
| A send-only port, system resolution | Lookup containment | A refused hop has already sent its host name to a resolver. |
Revisit: a transport that resolves remotely, such as a proxy-only deployment.
exec-egress-proxied — An exec step’s vendor traffic passes a host loopback proxy
Cited byrun.exec.env-credentialrun.exec.env-credentialAn env value embedding a ${secret://<name>} placeholder raises DeriveEnvCredential, naming the engine and variable; a step reaches its vendor through exec-proxy.connector.attach.exec-environmentconnector.attach.exec-environmentAn exec step's environment, cleared by no-shell, holds no credential under env-credential. A binding declaring endpoint_host adds CONTEXTFUL_EGRESS_URL, replacing any env entry of that name.connector.attach.exec-proxyconnector.attach.exec-proxyCONTEXTFUL_EGRESS_URL names a host loopback proxy, http://127.0.0.1:<port>, open for one pull. The proxy sends each request's method, path, query, headers and body to https://<endpoint_host> through the mediated client, following no redirect.connector.attach.exec-proxy-attachconnector.attach.exec-proxy-attachThe proxy renders the binding's attach templates per request under per-request-hydration, replacing a header of the same name the step sent, and forwards no hop-by-hop header.
A child process holds no credential: the host attaches it per request, under the same allowlist, address check, hook and meter as every other request.
| Option | Lost on | Cost |
|---|
| Loopback proxy attaching per request (chosen) | — | A step speaks plain HTTP to the proxy, and a step dialing the vendor itself bypasses it with no credential to send; no kernel filter stops the dial. |
| Hydrate the credential into the step’s environment | Custody | The child copies the material past redaction, per-request hydration and the bound host. |
A forward proxy through HTTPS_PROXY | Attachment | A CONNECT tunnel carries TLS end to end, so the host cannot add a header. |
| A network namespace with a socket bridge | Portability | Linux user namespaces alone provide it, and a vendor tool must speak to a Unix socket. |
Revisit: a portable kernel egress filter; a vendor serving on a port other than 443.
manifest-widening-binds — Manifest widening binds approval to both declarations
Cited byconnector.widen.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.connector.widen.host-witnessconnector.widen.host-witnessEvery reported host witness passes the candidate's outbound matcher and fails the predecessor's outbound matcher.
The comparison makes no claim about vendor scope implications, DNS or credential bytes.
| Option | Lost on | Cost |
|---|
| Compare grants, bind approval to both digests (chosen) | — | Opaque scope strings over-report semantic widening. |
| Treat every reload as approval | Review binding | A program update silently gains access. |
| Compare textual entries alone | Semantic inclusion | A covering wildcard hides the newly reachable hosts. |
| Solve the host grammar with SMT | Dependency budget | A solver adds a second trusted interpretation. |
Revisit: host grants gain ports, methods, paths or address ranges.
drive-capture-binds — Drive capture binds bounded roots and mode to the position
Cited byconnector.source.drive-root-setconnector.source.drive-root-setExactly one of folder_id or folder_ids selects roots; folder_ids holds 1 to 16 entries, all distinct, under one required drive_id. An empty, oversized, repeated or ambiguous selection raises ConnectorDriveRootsInvalid before listing.connector.source.drive-root-validationconnector.source.drive-root-validationWith folder_ids, every selected root names a folder in the declared drive_id. A missing or outside-drive root raises ConnectorDriveRootRejected before the source lists any selected folder.connector.source.drive-root-walkconnector.source.drive-root-walkWith folder_ids, the drive source applies drive-walk to each root while confining descendants to the declared drive, listing each folder once across overlaps.connector.source.drive-overlapconnector.source.drive-overlapA file reachable through multiple selected roots lands once by file_id. Its resolved_root is the lexicographically least selected root id reaching the file.connector.source.drive-modeconnector.source.drive-modemode selects bytes-and-pages by default or metadata-only; another spelling raises ConnectorDriveModeUnknown before any request.connector.source.drive-metadata-onlyconnector.source.drive-metadata-onlyIn metadata-only mode, the source hashes exact downloaded or exported bytes, then discards the bytes after recording the digest.connector.source.drive-selection-positionconnector.source.drive-selection-positionThe 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.connector.source.drive-selection-removalsconnector.source.drive-selection-removalsA 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.
Native exports carry no checksum for the exported representation, so capture hashes the bytes.
| Option | Lost on | Cost |
|---|
| Bounded roots and explicit capture mode (chosen) | — | A changed selection rewalks its roots; metadata-only still downloads each body. |
| One pipeline per root | Overlap identity | One file lands in several tables. |
| Arbitrary roots and shortcuts | Scope review | One declaration reaches unrelated drives. |
| Drive’s checksum alone | Byte identity | Native exports lack a checksum. |
Revisit: Drive offers a verifiable export digest without reading bytes, or a tenant needs more roots than the ceiling.
picker-files-bind — Picker files bind selection and principal to one position
Cited byconnector.principal-secrets.write-serializationconnector.principal-secrets.write-serializationConcurrent refreshes of one name serialize across resolvers through the adapter's conditional version; the losing refresh rehydrates the winner under conditional-put.connector.drive-picks.file-selectionconnector.drive-picks.file-selectionOn gdrive-picks and gdrive-sheet, file_ids selects distinct individual files without a folder root; folder_id and folder_ids are absent.connector.drive-picks.file-withdrawnconnector.drive-picks.file-withdrawnA withdrawn id stays selected without refusing later fires. A fire finding the file inaccessible stores nothing for that file. When access returns, the source captures the file as new and removes its id from the withdrawn set.connector.drive-picks.file-positionconnector.drive-picks.file-positionThe position binds sorted file_ids and mode; changing either rereads retained files, and a complete read tombstones deselected files and their pages.
Under drive.file, a selected folder may not grant its children’s bytes, so a selection admits individual files.
| Option | Lost on | Cost |
|---|
| Explicit files, versioned credential names (chosen) | — | Selection changes reread retained files. |
| A picked folder grants its descendants | Proven access | A child’s bytes may sit outside the grant. |
| One global refresh lock | Distributed correctness | Another process still rotates the same credential. |