Service Account Driver Credential Delivery Flow
Overview
OCC starts with Installation Backend composition, then creates a Namespace-owned account, separately issues its provider-backed credential, and deploys an associated dedicated Codex Agent. The API owns the Backend client and upstream account calls; worker reconciliation repeats metadata checks before Compute projects the account token to Codex. This flow stops after Codex starts with the projected access token.
Entry Points
- Trigger:
POST /namespaces/:namespaceId/service-accounts, thenPOST /namespaces/:namespaceId/service-accounts/:serviceAccountId/credentials, Agent association, and deployment. - Sources:
apps/controller/src/api-startup.ts:startApi,apps/controller/src/drivers/service-account/chatgpt.ts:ChatGPTServiceAccountDriver,packages/occ/src/index.ts:OpenClawController. - Requires PostgreSQL, a ready Namespace, exact OCC permissions, the selected ChatGPT Backend and member ServiceAccount Driver, an API-only credential for the configured ChatGPT workspace, and a dedicated Codex runtime for managed access-token deployment.
Flow
graph TD
subgraph Composition["Installation composition"]
A["Validate Backend and selected Driver"] --> B["API builds ChatGPT client"]
B --> C["Inject Backend into ServiceAccount Driver"]
A --> D["Worker keeps nonsecret Backend metadata"]
end
subgraph API["OCC API"]
C --> E["Authorize and create OCC account"]
E --> F["Create upstream account and private binding"]
F --> G["Authorize separate credential issuance"]
G --> H["Issue token and store account Secret"]
H --> I["Persist credential ID and Secret reference"]
I --> J["Save nullable Agent backendId"]
J --> K["Validate binding and freeze Agent revision"]
end
subgraph Worker["Worker and Compute"]
D --> L["Reauthorize deployment actor"]
K --> L
L --> M["Recheck Backend and binding metadata"]
M -->|valid dedicated Codex| N["Project account token into Codex"]
M -->|mismatch| O["Fail candidate"]
N --> P["Codex authenticates token and starts app server"]
endExecution Trace
1. Compose the Backend and its ServiceAccount Driver
apps/controller/src/api-startup.ts:startApi
loadInstallationConfiguration validates the singular backend array and
requires each ChatGPT Backend to declare the selected service_account member
Driver. api-startup.ts:startApi then reads the mounted apiKeyPath, constructs
Backend<ChatGPTClient>, and injects it into the bundled
ChatGPTServiceAccountDriver factory. That client and admin key stay on the API
side. The worker receives only nonsecret Backend definitions so it can reject
stale or mismatched deployment snapshots before Compute effects.
2. Create the account and private Backend binding
packages/occ/src/index.ts:OpenClawController.createServiceAccount
OpenClawController.createServiceAccount authorizes the exact Namespace and
allocates its sa_* identity. ChatGPTServiceAccountDriver.create creates the
upstream account, registers rollback, and persists its private Backend binding
in the same PostgreSQL transaction, including Backend, Driver, Namespace,
account, and workspace identity.
apps/controller/src/backends/chatgpt.ts:ChatGPTClient.request owns each
upstream HTTP response. It consumes successful replies and the structured 404
replies needed to confirm already-absent resources. Before rejecting another
HTTP status or a response declared larger than its 4 MiB allowance, it cancels
the unused body. That releases transport capacity before the next operation;
a cancellation failure does not replace the sanitized Backend error.
Successful and structured 404 response bodies are read incrementally. A
chunked body is cancelled as soon as received bytes cross the same 4 MiB
allowance, before parsing or retaining the overflowing chunk.
Account creation and credential issuance are separate operations. Creating the account does not issue a token, and later issuance or deletion requires that exact binding to match the current configured Backend and member Driver.
Before deletion, OCC locks the Namespace and account, then checks Agent drafts,
selected versions, queued or claimed Agent revision work, and queued or running Agent
provisioning plans through serviceAccounts.hasReferences. A conflict returns
before any Driver call can revoke the credential or remove its Secret. Namespace locking serializes this
check with draft changes and deployment admission; the PostgreSQL reference
query observes active pointers and pending work together during worker cutover.
With no ServiceAccount Driver selected (no ChatGPT Backend), an account holding
an issued access token answers 409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED
naming the fix and stays, since nothing can revoke its token. With ?force=true
OCC instead reads the binding's Backend, workspace, account, and credential IDs, removes
the token Secret through KubernetesComputeDriver.deleteServiceAccountCredential
and the account, and audits revocation: "skipped"; with a Driver selected,
force is ignored and revocation runs.
3. Issue the credential and create one account Secret
apps/controller/src/drivers/service-account/chatgpt.ts:ChatGPTServiceAccountDriver.createCredential
OpenClawController.createServiceAccountCredential authorizes account update
before ChatGPTServiceAccountDriver.createCredential issues a Codex-scoped
token and registers one rollback callback. The callback first confirms revocation
of that exact provider credential, then removes only the account-owned Secret
whose token and credential ID both match. Unconfirmed revocation leaves the
Secret available for recovery. The callback runs after PostgreSQL releases the
account lock, so deletion uses uid and resourceVersion preconditions to protect
a later issuance.
KubernetesComputeDriver.storeServiceAccountCredential stores the token in one
account-owned control-plane Secret. If the create reply is lost, an exact
readback of ownership, token, credential ID, uid, and resourceVersion confirms
the stored state without repeating or deleting the Secret. An absent or
mismatched object leaves the create failure in force; an unreadable or
unidentified object remains an unknown outcome. The Driver owns compensation
after either storage failure or a later known transaction failure.
The private Backend binding retains workspace metadata. The private credential
ID, internal credential reference, and audit changes commit together.
The Secret also records the provider credential ID. A crash or an unknown
COMMIT that failed runs no compensation, so before issuing, and when deleting an
account with no recorded credential, the Driver reads the Secret through
KubernetesComputeDriver.findServiceAccountCredential, revokes the credential it
records, and deletes it only while it still records that credential.
With no ServiceAccount Driver selected (no ChatGPT Backend), issuance answers
409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED naming the fix after the grant and
account lookup; a selected Driver that fails stays 503.
4. Save Agent Backend intent and admit the Agent revision
packages/occ/src/index.ts:OpenClawController.createAgent, updateAgent, deployAgent
Agent backendId is nullable. Create omission saves null; PATCH omission
preserves the current value; explicit null clears it; and a nonnull ID must
name a configured Backend. Saving or changing the draft Agent reference makes
no upstream call. The Agent selects the issued account through
harnessAuth: { method: "codex_pat", source: { kind: "service_account", namespaceId, id: serviceAccountId } }.
deployAgent authorizes the Agent, Configuration, and associated account, then
validates access_token ownership with
validateServiceAccountBackendBinding. Managed access-token deployment requires
the exact nonnull Backend, selected member Driver, workspace, account, recorded
credential issuance, and dedicated Codex execution. An account without an issued
credential cannot deploy; without a ChatGPT Backend that refusal is
409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED. Admission freezes the account identity, exact credential
reference, private Backend binding, and Agent backendId in the Agent revision.
Supplied API keys use the same harness binding path
through an OCC Secret; native account references are not model-auth selectors.
5. Recheck metadata and project the account Secret
apps/controller/src/drivers/compute/kubernetes/index.ts:KubernetesComputeDriver.prepareRevision
ControllerWorker.resolveRevisionBackend runs after IAM reauthorization and
before Compute reconciliation. It rejects a missing configured Backend as
BACKEND_UNAVAILABLE and a metadata mismatch as
SERVICE_ACCOUNT_BACKEND_MISMATCH. Only Backend, Driver, workspace, account,
and issuance metadata leave the repository; upstream account IDs, admin keys,
and credential values stay private.
KubernetesComputeDriver.prepareRevision uses its internal prepareHarnessAuth
rendering step and CODEX_LOGIN_MODE=codex_pat, then delivers the account token
into a data-plane Secret for that Agent revision. Only dedicated Codex receives it. Embedded execution
is rejected for managed access tokens. The Gateway receives no model credential.
The trusted worker reads the CP source and manages the DP projection; workload
ServiceAccounts receive no Secret API permission. Workspace ownership remains
part of control-plane validation, without a runtime workspace override.
6. Authenticate Codex with the account token
apps/controller/src/drivers/compute/kubernetes/runtime-entrypoints.ts:AGENT_RUNTIME_ENTRYPOINT
AGENT_RUNTIME_ENTRYPOINT uses the same native login for Driver-issued account
tokens and directly supplied codex_pat credentials:
codex -c cli_auth_credentials_store=file login --with-access-tokenCodex derives account identity from the token. The entrypoint clears the token environment and starts its authenticated app server. Refresh, rotation, and automated reconciliation remain deferred.
Debugging and Verification
- Run
node --test tests/conformance/service-account-driver.test.tsfor controller lifecycle coverage and native HTTPS response-release regressions. The latter call the actual Backend with built-in fetch, a single-connection pool and a loopback TLS server. They prove recovery after rejected 429, 503 and oversized declared responses. The same suite checks that an oversized chunked body is cancelled at its byte limit. These cases do not prove live provider accounts or model execution. - Run the focused
postgres-platformCI selection fortests/integration/postgres-service-account-compensation.test.tswith the PostgreSQL prerequisites. It exercises the real Controller HTTP routes, PostgreSQL transactions, ChatGPT Backend, and Kubernetes Compute through owned loopback TLS fixtures. It covers retained recovery receipts, exact deletion, account-lock races, and unknown COMMIT outcomes. This component proof does not qualify a live provider or cluster. - Verify the provider account, private credential ID, exact-account Secret, Codex-only projection for that Agent revision, and genuine model response. For ownership, compensation, and ambiguous commits, see the service-account guide and security model.
