Skip to content

feat: wallet instance lifecycle - list, suspend/revoke, deactivate (SID-AUTH-06) - #135

Merged
leifj merged 4 commits into
mainfrom
feat/wallet-instance-lifecycle
Sep 8, 2026
Merged

leifj merged 4 commits into
mainfrom
feat/wallet-instance-lifecycle

Conversation

@leifj

@leifj leifj commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Client half of sirosfoundation/go-wallet-backend#319 (design in sirosfoundation/go-wallet-backend#195; Android twin: siros-sdk-kotlin PR to follow). Tracked on the shared rollout page: https://claude.ai/code/artifact/49eb6eed-38ed-41b8-8168-3b5a300c33da

What changes

  • Passkey link at WIA generation. BackendApiClient.generateWIA gains credentialId, and ensureWalletInstanceAttestation passes sessionStore.credentialId. The backend records it as WalletInstance.CredentialID, so suspending or revoking this installation's instance also refuses login with its passkey. Without it, only whole-wallet deactivation gates login.
  • WalletInstance type (SirosAuth), and BackendApiClient.listWalletInstances(), setWalletInstanceStatus(instanceId:status:reason:), revokeAllWalletInstances(reason:) over GET/PUT /user/session/instances… and POST …/revoke-all. Adds the client's first PUT primitive.
  • Facade: SirosWallet.listWalletInstances(), setWalletInstanceStatus(...), deactivateWallet(reason:). Deactivate revokes every instance server-side, then forgets the cached account and logs out, since the vault it decrypts no longer exists.
  • Login refusal: SirosError.walletLifecycleRefusal reads WALLET_SUSPENDED / WALLET_REVOKED off a 403 .backendApi error. A helper rather than a new enum case, so apps' exhaustive switches keep compiling. Neither refusal is retryable; apps should say so instead of retrying.

Compatibility

Old backends ignore credential_id and answer 404 on the new endpoints (surfaced as SirosError.backendApi). New backends work with old SDKs. No lockstep release needed.

Tests

BackendApiClientTests: credential_id present only when given; list decodes the backend shape and skips unknown statuses; PUT body and path; revoke-all count. SirosErrorLifecycleTests: the helper reads both codes and returns nil for other 403s, non-403s, non-JSON bodies and other error cases. Linux swift build and the two suites pass; macOS swift test and the iOS build run in CI. No facade test: accountRegistry is Keychain-backed and private.

Sample-app UI is out of scope here; the SDK exposes the calls.

🤖 Generated with Claude Code

…ID-AUTH-06)

Client half of go-wallet-backend#319 (issue go-wallet-backend#195).

- generateWIA sends the logged-in passkey's credential_id so the backend
  links the wallet instance to the passkey and a suspended or revoked
  instance also refuses that passkey at login.
- WalletInstance type; BackendApiClient.listWalletInstances /
  setWalletInstanceStatus / revokeAllWalletInstances (+ a PUT primitive).
- SirosWallet.listWalletInstances(), setWalletInstanceStatus(),
  deactivateWallet(reason:) - the last revokes every instance server-side
  and then forgets the local account, since the vault it decrypts no longer
  exists.
- SirosError.walletLifecycleRefusal reads WALLET_SUSPENDED / WALLET_REVOKED
  off a 403 backendApi error without adding an enum case, so exhaustive
  switches keep compiling.

Older backends ignore credential_id and answer 404 on the new endpoints,
surfaced as the usual backendApi error; nothing needs to ship in lockstep.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new lifecycle API methods include response-handling behaviors that can mask malformed backend responses and return partially populated models, which should be corrected before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Implements the client-side of wallet instance lifecycle management (SID-AUTH-06) in the Swift SDK, including backend API support, wallet facade methods, and lifecycle-based login refusal parsing.

Changes:

  • Add WalletInstance plus BackendApiClient endpoints for listing instances, updating instance status (PUT), and revoking all instances (deactivate).
  • Link WIA generation to the active passkey via credential_id so backend instance suspension/revocation can refuse login for that passkey.
  • Add SirosError.walletLifecycleRefusal helper and targeted unit tests for lifecycle refusal parsing and new backend calls.
File summaries
File Description
Tests/SirosCredentialsTests/SirosErrorLifecycleTests.swift Adds unit tests for parsing lifecycle refusal codes from 403 backend errors.
Tests/SirosAuthTests/BackendApiClientTests.swift Adds tests for generateWIA(credentialId:), listing instances, PUT status updates, and revoke-all behavior.
Sources/SirosWallet/SirosWallet+Issuance.swift Passes sessionStore.credentialId into WIA generation to link instance ↔ passkey.
Sources/SirosWallet/SirosWallet.swift Exposes facade methods for listing instances, setting instance status, and deactivating the wallet.
Sources/SirosCredentials/SirosError.swift Adds WalletLifecycleRefusal and a helper to extract refusal codes from 403 backend errors without adding a new enum case.
Sources/SirosAuth/WalletInstance.swift Introduces a public WalletInstance model and JSON decoding initializer for backend responses.
Sources/SirosAuth/BackendApiClient.swift Adds lifecycle endpoints, extends WIA generation to include credential_id, and introduces a PUT HTTP primitive.
CHANGELOG.md Documents the new lifecycle APIs and behavior in the Unreleased section.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/SirosAuth/BackendApiClient.swift
Comment thread Sources/SirosAuth/BackendApiClient.swift Outdated
Comment thread Sources/SirosWallet/SirosWallet.swift
…tus reply, facade tests

Copilot review on #135: a response without the instances array is a
malformed reply, not an empty wallet; a status update decodes the whole
WalletInstance when the backend returns one; and the facade's not-logged-in
and forwarding paths are now covered without touching the Keychain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 8, 2026 14:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

deactivateWallet may not actually log out/clear session state when an active account exists, and revokeAllWalletInstances can silently misreport malformed responses by defaulting the revoked count to 0.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

Sources/SirosAuth/BackendApiClient.swift:337

  • revokeAllWalletInstances(reason:) returns 0 when the backend response is missing/invalid for the revoked field, which can silently misreport the outcome (0 is a valid value). It’s safer to treat a missing count as a malformed response and surface a SirosError.backendApi parse error instead of defaulting.
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread Sources/SirosWallet/SirosWallet.swift
…malformed reply

Copilot on #135: AccountRegistry.removeAccount clears the active id, so
forgetAccount's check after removal never matched and the active account
was forgotten without logging out - deactivateWallet inherited that. Decide
before removing. And a revoke-all reply without the count is an error, not
zero revoked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 8, 2026 14:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The new APIs are covered by focused unit tests, error handling for malformed responses is exercised, and the account/logout edge case in deactivation is addressed.

Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

…ce keeps a two-member init (Sonar S1075, S107)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 8, 2026 14:38
@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It introduces new auth/lifecycle behaviors and backend write calls (PUT/POST) that affect login eligibility and account deactivation, warranting final human review despite good test coverage.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

Tests/SirosWalletTests/SirosWalletSignRequestTests.swift:382

  • Recorder is marked @unchecked Sendable and its @Sendable httpFn mutates calls/responses without any synchronization. If the client ever issues requests concurrently (or tests are run with concurrency checking), this can introduce data races; additionally, removeFirst() will trap if the response queue is exhausted, crashing the test instead of failing cleanly.
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@leifj
leifj merged commit 621fbd3 into main Sep 8, 2026
15 checks passed
@leifj leifj mentioned this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants