SDK-to-CLI Fallback Policy¶
Status: Accepted and implemented (0.2.0+)
Created: 2026-08-03
Author: OpsDev.nz Platform Engineering
Summary¶
When the SDK path is configured (OP_SERVICE_ACCOUNT_TOKEN is set), any SDK
failure is a hard failure — no fallback to the CLI. This prevents silent
principal switching. Fallback is only allowed when the SDK path is not
configured (token absent, SDK not installed).
Problem (historical)¶
Previously, resolve_secret() fell back from the Service Account SDK to the
op CLI on any SecretError, and the OctoDNS hook forced prefer_cli=True,
which inverts the order to CLI-first.
A process configured with a narrowly scoped service-account token could silently
resolve through a developer's broader, locally authenticated op CLI session.
Authentication and authorization failures should not switch principals without
the caller knowing.
Implemented Behaviour¶
The resolver distinguishes between two error states:
SdkNotConfiguredError— fallback to CLI allowed: token absent, or the SDK import fails.SdkAuthError— hard failure, no fallback: any error fromClient.authenticate()orclient.secrets.resolve()while the SDK path is configured.
The rule is binary: once the SDK path is configured, any failure — including transient ones such as rate limits — is a hard failure. No principal switching.
Resolution flow (default, prefer_cli=False)¶
SDK configured (OP_SERVICE_ACCOUNT_TOKEN set)?
├── Yes → Try SDK
│ ├── Success → done (source="sdk")
│ └── Failure (SdkAuthError) → HARD FAIL, no CLI fallback
└── No (SdkNotConfiguredError) → CLI fallback allowed if `op` available
Resolution flow (CLI-first, prefer_cli=True)¶
Used by provider adapters (e.g., octodns_metaname.op_opsdevnz_hooks:resolve)
on workstations where the signed-in op session is the intended principal:
Try CLI first
├── Success → done (source="cli")
└── Failure → Try SDK
├── Success → done (source="sdk")
└── Failure → re-raise CLI error
CI behaviour¶
With OP_SERVICE_ACCOUNT_TOKEN set, the CLI authenticates as the same
service-account principal the SDK would use. Both paths are interchangeable
in CI — the identity-switch risk from issue #8 is a workstation concern, where
a human op session can sit next to a service-account token.
Why two error classes?¶
The SDK exposes no typed exceptions for "not configured" versus "auth failed":
a rejected token raises a bare Exception("invalid user input: ..."), and the
only typed exceptions are DesktopSessionExpiredException (desktop-app only)
and RateLimitExceededException. So the resolver's pre-checks decide:
- Token absent? →
SdkNotConfiguredError - SDK import fails? →
SdkNotConfiguredError - Any error from
Client.authenticate()orclient.secrets.resolve()? →SdkAuthError
Implementation Order (historical)¶
ADR-001 (SDK integration) landed before ADR-003 (this policy). Until the SDK
path worked, _resolve_via_sdk() always raised SecretError("onepassword-sdk
not installed") — which would have been misclassified as SdkAuthError (hard
failure) if ADR-003 shipped first. Sequencing ADR-001 first avoided the
transitional rule entirely.
Resolved Questions¶
- Pinning a required source. Deferred. Callers can already assert
resolution.source == "sdk"after the fact, and this ADR's binary rule removes the dangerous silent fallback. - Which SDK exceptions map to "not configured" versus "auth failed".
None do: the SDK raises a bare
Exceptionfor token rejection. Classification is by configuration state, decided by the resolver's pre-checks.