RFC-0001: Provider Egress and Credential Custody Options
Status: Proposed Author: Roberto Garcia Navarro Date: 2026-10-03
Problem
TAP rooms use personal and workspace provider credentials. A turn needs one
approved credential without exposing vendor material to room members,
AgentSessionDO, or ze-harness. Standalone ze-harness must retain direct
provider support.
Target contract
ze-harness composes ProviderDefinition and ModelService. TAP injects a
Gateway provider definition; standalone hosts use direct provider definitions.
declare const valueObject: unique symbol;
type ValueObject<Value, Kind extends string> = Value & {
readonly [valueObject]: Kind;
};
type CredentialBindingId = ValueObject<string, 'CredentialBindingId'>;
type TurnEgressLease = ValueObject<string, 'TurnEgressLease'>;
interface GatewayTurnControlPort {
admitTurn(input: {
roomId: RoomId;
turnId: TurnId;
bindingId: CredentialBindingId;
}): Promise<TurnEgressLease>;
}
interface GatewayInvocationPort {
stream(input: {
lease: TurnEgressLease;
call: ModelCallIdentity;
request: ModelRequest;
signal: AbortSignal;
}): Stream.Stream<ModelEvent, ModelFailure>;
}
CredentialBinding stays in Custody. The room coordinator stores the opaque
lease in memory for its turn. GatewayProviderDefinition reads that lease at
ModelService.stream, then calls GatewayInvocationPort. It never uses
ProviderCredentialPort or receives vendor material.
Gateway maps each lease and ModelCallIdentity to one internal invocation
grant. turnId, logicalCallId, and attemptId identify the invocation for
deduplication. This covers tool continuations and retries within one turn.
Option A: Cloudflare-native Gateway egress
Gateway player: the Cloudflare Worker. It owns turn admission, D1 credential resolution, and provider egress. D1 stores ciphertext and delegation metadata; a Cloudflare secret-backed application key protects the records.
Fit: one operational boundary. Limit: Cloudflare owns custody and key policy; a later AWS move requires storage and crypto migration.
Option B: AWS custody-first Gateway egress
Gateway player: AWS Custody. It accepts ze-harness calls, redeems grants, resolves credentials, and calls providers. Cloudflare only admits turns and returns leases. Vendor material never returns to Cloudflare or ze-harness.
The hierarchical keyring uses one KMS root per environment and region, DynamoDB branch keys, and a bounded local cache of branch material. It creates a unique data key per encrypted credential record.
DynamoDB model
tap-credential-custody:PK = BINDING#<bindingId>,SK = CURRENT | REV#<revision>.GSI1PK = SCOPE#<personal|workspace>#<owner>andGSI1SK = PROVIDER#<providerId>#BINDING#<bindingId>.tap-credential-key-store: AWS Encryption SDK key store only. Required keys arebranch-key-idandtype. No application records or GSI.
Egress reads by CredentialBindingId. Grants, turns, and delegation policy
remain Gateway-control state. Branch keys use account:<id> or
workspace:<id>, never room or request IDs. Encryption context binds tenant,
credential reference, provider, and revision.
Cache and access rules
- Cloudflare sends a lease and call identity to AWS. AWS owns DynamoDB, KMS, and vendor access. Streaming, cancellation, and backpressure cross this boundary.
- Only AWS Custody caches plaintext branch material. TTL, capacity, and clearing are explicit controls.
- Prompt-cache affinity is scoped to one credential binding revision and harness session. Personal bindings never share affinity; a workspace binding may share it among authorized users.
- Grants stay out of prompts. For OpenRouter, Egress sends an opaque affinity
ID as
session_id. Response caching is disabled for agent turns.
Fit: independent custody and KMS policy. Limit: AWS service ownership, DynamoDB operations, key-cache controls, and vault migration.
Comparison
| Concern | Option A | Option B |
|---|---|---|
| Gateway | Cloudflare Worker | AWS custody service |
| Vault | D1 | DynamoDB plus key store |
| Root key | Cloudflare secret-backed key | AWS KMS root |
| Key cache | Application-defined | Hierarchical keyring |
| Fit | Fastest path | Long-term custody |
Direction
Prefer Option B. Gateway calls providers, Custody owns vendor material,
and TAP uses a Gateway ProviderDefinition.
Current branch gaps
- Gateway is a Cloudflare Worker with D1. AWS custody and egress do not exist.
- Gateway decrypts an API key and injects it into ze-harness.
- ze-harness stores live credentials as
providerIdto API key. Two owners of one provider can collide. - The compatible-provider adapter captures credentials at model acquisition; it cannot attach one grant per model call.
- The branch has no AWS vault migration, rollback path, or key-cache policy.
Acceptance criteria
- Two people using the same provider never mix credentials.
- TAP uses a memory-only turn lease and per-invocation authorization.
- ze-harness receives no vendor credential material.
- Revocation blocks the next unadmitted turn with a typed denial.
- Rotation changes later turns, not an admitted turn.
- No vendor material appears in logs, traces, contracts, or room state.