Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,31 @@ and the execution command reference.
platform services. Prefer deterministic local fixtures for signing vectors.
- Add regression tests for bug fixes before or alongside the fix when practical.

### Public Error Contract Tests

- Follow the detailed rules in `TESTING.md` before adding or updating public
error contract tests.
- Use `docs/error-contracts.md` as the audit matrix for public SDK error
surfaces, recovery semantics, `upstreamError` expectations, and owning tests.
- Keep serialized public error shape assertions centralized in
`PublicErrorContractsTest`; focused tests should cover behavior or edge cases
without duplicating the full matrix.
- Exercise real public runtime APIs and mock only external boundaries.
- Do not assert manually constructed `OmsSdkException` subclasses unless the
error class or helper is the unit under test.
- Assert stable public fields only; do not assert raw `cause`, stacks, generated
internals, headers, timestamps, or full backend payloads as public contract.
- Keep backend and upstream mapping tests representative rather than exhaustive
per method; cover each transport family through real public calls.
- Include `upstreamError` only when the path crosses a remote service response
or transport boundary. SDK-local failures should not expose upstream details.
- Android storage and Keystore signer internals belong in focused platform
tests unless they are intentionally normalized through documented public SDK
errors.
- Serialized contract changes are not automatically regressions. First decide
whether the new error shape is intended, then update the assertion and related
docs or fix the implementation.

## Generated Files and External Artifacts

- `oms-client-kotlin-sdk/src/main/java/com/omsclient/kotlin_sdk/generated/waas/WaasWalletClient.kt`
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,30 @@ Transaction values are raw base-unit integers. Use `parseUnits` to convert
human-entered decimal values before sending. Import the helpers from
`com.omsclient.kotlin_sdk.utils`.

## Errors

Public SDK APIs throw `OmsSdkException` subclasses with stable fields such as
`code`, `operation`, `status`, nullable `retryable`, and `txnId`. When a failure comes
from a remote OMS service response or transport failure, the error also includes
`upstreamError` with normalized WaaS or indexer details for logging and
service-specific troubleshooting. Application logic should usually branch on the
SDK-level `code`.

For transaction writes, `TransactionExecutionUnconfirmed` means the SDK has a
`txnId` from preparation, but the execute request failed before the SDK could
confirm whether the transaction was submitted; do not blindly resend the same
write. `TransactionStatusLookupFailed` means the transaction was submitted but
status polling failed, so retry status lookup with the returned `txnId`.
`retryable` describes the failed SDK operation, not the whole user intent.

```kotlin
try {
client.wallet.startEmailAuth("user@example.com")
} catch (error: OmsSdkException) {
println("${error.code} ${error.operation?.id} ${error.upstreamError}")
}
```

For raw token amount formatting and parsing:

```kotlin
Expand Down
33 changes: 33 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,39 @@ Key subdirectories:
tests that only assert private call ordering.
- Add a regression test for bug fixes before or alongside the fix when practical.

### Public Error Contract Tests

- Use `docs/error-contracts.md` as the audit matrix for public SDK error surfaces, recovery
semantics, `upstreamError` expectations, and owning tests.
- Keep serialized public error shape assertions centralized in
`PublicErrorContractsTest`; focused tests should cover local behavior or edge cases without
duplicating the full public-field matrix.
- Exercise real public runtime APIs such as `client.wallet.*`, `client.indexer.*`, auth result
actions, and public exception classes.
- Do not assert manually constructed `OmsSdkException` subclasses unless the error class or helper
is the unit under test.
- Mock only external boundaries: network responses, time, randomness, Android platform services, or
signer behavior.
- Assert stable public fields only: exception class, `code`, `operation`, `message`, `status`,
`retryable`, `txnId`, and `upstreamError`.
- Do not assert raw `cause`, stacks, generated WebRPC internals, request headers, timestamps, or
full backend payloads as public error contract fields.
- Keep backend and upstream mapping tests representative rather than exhaustive per method; cover
each transport family through real public calls.
- Include `upstreamError` only when the tested path truthfully crosses a remote service or transport
boundary. SDK-local validation, session, and wallet-selection failures should assert no upstream
details.
- Android storage and Keystore signer classes are internal platform boundaries, not separate public
SDK error surfaces. Cover their failures in focused JVM or instrumented tests unless a failure is
intentionally normalized through a documented public `OmsSdkException`.
- Serialized contract changes are not automatically regressions. Decide whether the new error shape
is the intended public contract: if correct, update the assertion and related docs; if accidental,
fix the implementation. Never update expectations blindly.
- Treat `code` and `operation` as stronger contract fields than `message`. Message changes are
allowed when intentional, but they should be reviewed as user-visible API/UX changes.
- `retryable` describes the failed SDK operation, not the whole user intent. A retryable status
lookup failure does not mean the original transaction write should be blindly resent.

## Execution Summary

| Goal | Command |
Expand Down
46 changes: 42 additions & 4 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ enum class OmsSdkErrorCode {
WalletSelectionStale,
WalletSelectionUnavailable,
WalletSelectionInFlight,
TransactionExecutionUnconfirmed,
TransactionStatusLookupFailed,
ValidationError,
}
Expand All @@ -577,27 +578,47 @@ open class OmsSdkException(
val operation: OmsSdkOperation?,
val status: Int?,
val txnId: String?,
val retryable: Boolean,
val retryable: Boolean?,
val upstreamError: OmsUpstreamError?,
) : RuntimeException
```

```kotlin
enum class OmsUpstreamService {
Waas,
Indexer,
}

data class OmsUpstreamError(
val service: OmsUpstreamService,
val name: String?,
val code: String?,
val message: String?,
val status: Int?,
)
```

```kotlin
enum class OmsSdkOperation(
val id: String,
) {
PendingWalletSelection("pendingWalletSelection"),
PendingWalletSelectionCreateAndSelectWallet("pendingWalletSelection.createAndSelectWallet"),
PendingWalletSelectionSelectWallet("pendingWalletSelection.selectWallet"),
PendingWalletSelection("wallet.pendingWalletSelection"),
PendingWalletSelectionCreateAndSelectWallet("wallet.pendingWalletSelection.createAndSelectWallet"),
PendingWalletSelectionSelectWallet("wallet.pendingWalletSelection.selectWallet"),
IndexerGetNativeTokenBalance("indexer.getNativeTokenBalance"),
IndexerGetTokenBalances("indexer.getTokenBalances"),
WalletCallContract("wallet.callContract"),
WalletCompleteEmailAuth("wallet.completeEmailAuth"),
WalletCreateWallet("wallet.createWallet"),
WalletExecute("wallet.execute"),
WalletGetIdToken("wallet.getIdToken"),
WalletHandleOidcRedirectCallback("wallet.handleOidcRedirectCallback"),
WalletGetTransactionStatus("wallet.getTransactionStatus"),
WalletIsValidMessageSignature("wallet.isValidMessageSignature"),
WalletIsValidTypedDataSignature("wallet.isValidTypedDataSignature"),
WalletListAccess("wallet.listAccess"),
WalletListAccessPage("wallet.listAccessPage"),
WalletListAccessPages("wallet.listAccessPages"),
WalletListWallets("wallet.listWallets"),
WalletRevokeAccess("wallet.revokeAccess"),
WalletSendTransaction("wallet.sendTransaction"),
Expand All @@ -606,6 +627,7 @@ enum class OmsSdkOperation(
WalletSignTypedData("wallet.signTypedData"),
WalletStartEmailAuth("wallet.startEmailAuth"),
WalletStartOidcRedirectAuth("wallet.startOidcRedirectAuth"),
WalletTransactionStatus("wallet.transactionStatus"),
WalletUseWallet("wallet.useWallet"),
}
```
Expand All @@ -614,6 +636,22 @@ enum class OmsSdkOperation(
error codes newer than the generated WaaS client. `InvalidResponse` is reserved
for malformed or unparseable responses.

`upstreamError` is normalized diagnostic detail from a remote OMS service response
or transport failure. Use SDK-level `code` for app branching; use
`upstreamError` for logging and service-specific troubleshooting. SDK-local
validation, session, and wallet-selection failures do not include upstream
details.

`TransactionExecutionUnconfirmed` means transaction preparation succeeded and
the SDK has a `txnId`, but the execute request failed before the SDK could
confirm whether the transaction was submitted. Do not blindly resend the same
write solely because the upstream failure looked temporary.

`TransactionStatusLookupFailed` means the transaction was submitted, but
post-submit status polling failed. Retry by calling `getTransactionStatus` with
the returned `txnId`; `retryable` describes that status lookup operation, not
the original write.

## Public Models

```kotlin
Expand Down
57 changes: 57 additions & 0 deletions docs/error-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Public Error Contracts

This matrix is the audit surface for SDK error behavior. It documents which public runtime
surfaces can fail, what error shape users should see, what recovery decision the error supports,
whether `upstreamError` should be present, and which tests own the contract.

## Terms

- `upstreamError` is normalized diagnostic detail from a remote OMS service response or transport
failure. It is for logging and service-specific troubleshooting. Application logic should usually
branch on the SDK-level `code`.
- `retryable` is nullable and describes the failed SDK operation when retry semantics apply. A
retryable status lookup failure does not mean the original transaction write should be blindly
resent.
- `TransactionExecutionUnconfirmed` means transaction preparation succeeded, but the execute request
failed before the SDK could confirm whether the transaction was submitted. Do not blindly resend
the same write solely because the upstream failure looked temporary.
- `TransactionStatusLookupFailed` means the transaction was submitted, but post-submit status
polling failed. Retry by checking transaction status with the returned `txnId`.

## Maintenance Approach

- Update this matrix, the centralized `PublicErrorContractsTest`, and public docs together when a
public SDK method gains, removes, or intentionally changes an error contract.
- Keep backend and upstream mapping tests representative rather than exhaustive per method. Cover
each transport or response family through real public calls instead of duplicating the same
matrix across focused tests.
- Do not assert manually constructed `OmsSdkException` subclasses unless the error class or helper
is the unit under test. Public runtime APIs should own runtime error contract coverage.
- Android storage and Keystore signer classes are internal platform boundaries in this SDK, not
separate public error surfaces. Cover their failures in focused platform tests unless a failure is
intentionally normalized through a documented public `OmsSdkException`.
- Serialized contract changes are not automatically regressions. Decide whether the new error shape
is the intended public contract: if correct, update the assertion and related docs; if accidental,
fix the implementation. Never update expectations blindly.
- Use `code` and `operation` as the primary compatibility contract. Treat message changes as
intentional user-visible API/UX changes, even when they do not change recovery behavior.

## SDK Matrix

| Public surface | Failure family | User-facing error | Recovery meaning | `upstreamError` | Covering test |
|---|---|---|---|---|---|
| `client.wallet.startEmailAuth`, representative WaaS methods | WaaS transport failure | `OmsRequestException`, `RequestFailed`, operation-specific, retryable when transport/5xx | Retry the same read/auth request when appropriate | Present | `PublicErrorContractsTest` |
| `client.wallet.completeEmailAuth` | WaaS domain error | SDK-specific code such as `AuthCommitmentConsumed` | Follow the SDK code; for consumed commitments, restart auth | Present | `PublicErrorContractsTest` |
| `client.wallet.*`, representative WaaS methods | WaaS HTTP error | `OmsRequestException`, `HttpError`, status, retryable for 5xx | Use SDK code/status for branching; log upstream detail | Present | `PublicErrorContractsTest` |
| `client.wallet.completeEmailAuth` and pending wallet selection actions | Local auth/session/selection state | `OmsSessionException` or `OmsWalletSelectionException` | Fix local flow state or restart auth; do not look for backend diagnostics | Absent | `PublicErrorContractsTest` |
| OIDC redirect/id-token auth methods | Local OIDC config, callback, storage, or state mismatch | `OmsSessionException`, `OmsValidationException`, or `OidcRedirectAuthResult.Failed` with `OmsSdkException` | Fix redirect config/state or restart OIDC flow | Absent | `PublicErrorContractsTest` |
| Protected wallet methods: `getIdToken`, `signMessage`, `signTypedData`, `sendTransaction`, `callContract`, `getTransactionStatus`, `listAccess`, `listAccessPages`, `revokeAccess` | Missing, expired, or stale local session | `OmsSessionException` | Authenticate again or recover local session; no remote request was made | Absent | `PublicErrorContractsTest` |
| `client.wallet.signMessage`, `signTypedData`, `getIdToken`, `sendTransaction`, `callContract` | SDK-local validation or fee-selection failure | `OmsValidationException` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `PublicErrorContractsTest` |
| `client.wallet.isValidMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OmsRequestException` or `OmsResponseException` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` |
| `client.wallet.sendTransaction`, `callContract` | Execute request fails after prepare | `OmsTransactionException`, `TransactionExecutionUnconfirmed`, `retryable = false`, `txnId` | Do not blindly resend the write; preserve `txnId` and upstream detail for diagnostics | Present when execute crossed transport/upstream boundary | `PublicErrorContractsTest` |
| `client.wallet.sendTransaction`, `callContract` | Submitted transaction status polling fails | `OmsTransactionException`, `TransactionStatusLookupFailed`, `retryable = true`, `txnId` | Retry status lookup, not the original write | Present when polling crossed transport/upstream boundary | `PublicErrorContractsTest` |
| `client.wallet.getTransactionStatus` | Direct status lookup backend failure | `OmsRequestException` or `OmsResponseException` with status operation | Retry status lookup or surface backend status to the user | Present | `PublicErrorContractsTest` |
| `client.wallet.listAccess`, `listAccessPages`, `revokeAccess` | WaaS access backend failure | `OmsRequestException` or `OmsResponseException` with access operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` |
| `client.indexer.getTokenBalances`, `getNativeTokenBalance` | Indexer backend, transport, malformed JSON, or malformed payload | `OmsRequestException` or `OmsResponseException` with indexer operation | Retry based on SDK code/status; log upstream detail | Present for remote/transport response failures | `PublicErrorContractsTest` |
| `client.indexer.getTokenBalances`, `getNativeTokenBalance` | Indexer non-JSON HTTP body | `OmsRequestException`, `HttpError`, sanitized message | Do not expose raw upstream HTML/text bodies; log normalized detail | Present, sanitized | `PublicErrorContractsTest` |
| Public `OmsSdkException` classes and upstream fields | Error class field contract | Stable public fields on constructed errors | Use only when the error class/helper is the unit under test | As constructed | `PublicErrorContractsTest` |
Loading
Loading