Skip to content

Add Convos Vault technical implementation plan#558

Closed
yewreeka wants to merge 16 commits intographite-base/558from
jarod/convos-vault-impl
Closed

Add Convos Vault technical implementation plan#558
yewreeka wants to merge 16 commits intographite-base/558from
jarod/convos-vault-impl

Conversation

@yewreeka
Copy link
Copy Markdown
Contributor

@yewreeka yewreeka commented Mar 5, 2026

Add Convos Vault technical implementation plan

Phase 1: Add conversationType protobuf field and Vault content types

  • Add optional conversationType field (6) to ConversationCustomMetadata
    protobuf for identifying Vault conversations
  • DeviceKeyBundleCodec: full key export sent when new device joins Vault
  • DeviceKeyShareCodec: single key share sent on conversation creation
  • DeviceRemovedCodec: notification when a device is removed from Vault
  • All three codecs use JSON encoding with ISO8601 dates
  • shouldPush returns false (Vault messages are device-sync only)
  • 25 tests covering encode/decode, edge cases, forward compatibility

Regenerate protobuf with --swift_opt=Visibility=Public

Use protoc-gen-swift's Visibility=Public flag instead of manually
editing the generated file.

Create ConvosVault package, move Vault content types from ConvosCore

New ConvosVault Swift package for multi-device key sync:

  • Depends on ConvosAppData and XMTPiOS (same pattern as ConvosProfiles)
  • Moved DeviceKeyBundleCodec, DeviceKeyShareCodec, DeviceRemovedCodec
    from ConvosCore/Custom Content Types/ to ConvosVault/ContentTypes/
  • Moved VaultContentTypeTests to ConvosVault/Tests/
  • ConvosCore now depends on ConvosVault package
  • InboxStateMachine imports ConvosVault for codec registration

Add KeychainKeyStore for generic key storage with iCloud support

New KeychainKeyStoreProtocol and implementation for storing
KeychainIdentityKeys by arbitrary identifier with configurable
accessibility:

  • afterFirstUnlockThisDeviceOnly: local keychain only (default)
  • afterFirstUnlock: syncs via iCloud Keychain

This enables the Vault to store its key in both local and iCloud
keychain, while keeping KeychainIdentityStore unchanged for
conversation identity management.

Includes MockKeychainKeyStore for testing and 11 unit tests.

Add VaultClient — lightweight XMTP client wrapper for Vault

Simplified alternative to InboxStateMachine with 4 states
(disconnected, connecting, connected, error) instead of 10.

  • connect: builds or creates XMTP client, finds or creates Vault
    group (identified by conversationType='vault' in appData)
  • disconnect: cancels stream, clears state
  • send: sends typed content with codec to Vault group
  • members/addMember/removeMember: group membership management
  • Message streaming dispatches to VaultClientDelegate for
    DeviceKeyBundle, DeviceKeyShare, and DeviceRemoved content types
  • Uses OSAllocatedUnfairLock for thread-safe state access

Add VaultManager — key sync orchestration layer

Sits on top of VaultClient and VaultIdentityStoreProtocol:

  • shareKey: sends a single key share to all Vault devices
  • shareAllKeys: bundles all local identities into a DeviceKeyBundle
  • listDevices: enumerates Vault group members
  • removeDevice: sends DeviceRemoved message then removes from group
  • Incoming key shares/bundles are auto-imported to identity store
    with duplicate detection (existing keys are not overwritten)

Supporting types:

  • VaultIdentityKeys: raw key pair for vault identity generation
  • VaultIdentityEntry: conversation-level identity (id, inbox, keys)
  • VaultDevice: device info model for UI display
  • VaultIdentityStoreProtocol: abstraction for key persistence
  • VaultManagerDelegate: callbacks for import/removal/errors

8 unit tests covering import, dedup, error cases, and models.

Add VaultKeyStore — dual-keychain storage for Vault identity

Wraps two KeychainKeyStore instances (local + iCloud) to provide
automatic redundancy for the Vault key:

  • save: writes to local (ThisDeviceOnly) and iCloud (AfterFirstUnlock)
  • load: reads from local first, falls back to iCloud (and caches locally)
  • exists: checks both stores
  • delete: removes from both

This enables Vault key recovery when restoring to a new device
with the same Apple ID, while keeping a local copy for performance
and offline access.

9 unit tests covering dual-store save, load priority, iCloud
fallback with local caching, missing key errors, and deletion.

Add PairingCoordinator and VaultKeyShareNotifier

Phase 3 + 4 foundations:

PairingCoordinator:

  • 6-digit confirmation code generation and validation
  • Pairing state machine (idle, waitingForScan, waitingForConfirmation,
    sharingKeys, completed, failed, expired)
  • PairingError types for all failure modes
  • Input cleaning (strips spaces/dashes from confirmation codes)

VaultKeyShareNotifier:

  • Protocol for auto-sharing keys on conversation creation
  • ConversationKeyInfo carries identity data for sharing
  • VaultManager conforms and auto-sends DeviceKeyShare when
    connected and a new conversation key is created

14 pairing tests + 47 total ConvosVault tests passing.

Add VaultSessionIntegration protocol for SessionManager hookup

Provides the interface SessionManager will use to start/stop the
Vault and notify it of new conversation key creation:

  • startVault: connects with signing key and XMTP options
  • stopVault: disconnects cleanly
  • notifyConversationCreated: triggers auto key share

VaultManager conforms, bridging to connect/disconnect/conversationKeyCreated.

Remove KeychainKeyStore, reuse KeychainIdentityStore instead

KeychainKeyStore duplicated keychain CRUD from KeychainIdentityStore.
Instead, add configurable service + accessibility params to
KeychainIdentityStore's init (defaulting to existing behavior).

VaultKeyStore now uses two KeychainIdentityStore instances:

  • Local: service='vault', accessibility=ThisDeviceOnly
  • iCloud: service='vault-icloud', accessibility=AfterFirstUnlock

Removes 249 lines of duplication. 340 ConvosCore tests passing.

VaultKeyStore uses real inboxId/clientId instead of sentinels

The Vault identity is a proper KeychainIdentity with its own
inboxId and clientId (assigned when the XMTP client is created),
enabling push notification registration and notification extension
processing — same as any conversation identity.

Changes:

  • save/load/delete all take inboxId/clientId params
  • loadFromAnyStore: finds the Vault identity from either store
    without needing the inboxId upfront (for first-time recovery)
  • deleteAll: clears both stores entirely

This also prepares for device backups — when enabled, all
conversation keys from the main KeychainIdentityStore will be
mirrored to an iCloud-accessible store using the same pattern.

13 unit tests covering all paths.

Expose vaultInstallationId on VaultManager for push registration

Remove vaultInstallationId — push registration is device-level, not inbox-level

Simplify to local-only Vault key (no iCloud sync)

Each device gets its own Vault key via explicit pairing (QR code).
No iCloud Keychain sync — avoids the complexity of one inboxId
with multiple installations vs multiple members.

One device = one member = one inboxId. Simple.

Changes:

  • VaultKeyStore: single store (no local+iCloud dual store)
  • KeychainIdentityStore: revert accessibility param, keep service param
  • VaultManager: track member count, only share keys when > 1 member
  • conversationKeyCreated skips if solo device (nobody to share with)
  • refreshMemberCount on connect and after removeDevice

55 tests passing (47 ConvosVault + 8 VaultKeyStore).

Add app lifecycle handling to VaultClient

VaultClient now handles background/foreground transitions:

  • pause: cancels message stream, drops local DB connection
  • resume: reconnects DB, syncs conversations + group, restarts stream
  • Lifecycle observation via NotificationCenter (vaultDidEnterBackground,
    vaultWillEnterForeground) — started on connect, stopped on disconnect

The app posts these notifications from its scene delegate, and
VaultClient responds automatically. On resume, member count is
also refreshed in case devices were added/removed while backgrounded.

VaultSessionIntegration exposes pauseVault/resumeVault for
SessionManager to call directly if needed.

Note

Add ConvosVault package for multi-device key sync via vault group conversations

  • Introduces a new ConvosVault Swift package with content codecs (DeviceKeyBundleCodec, DeviceKeyShareCodec, DeviceRemovedCodec) for serializing key material and device events over XMTP messages.
  • Adds VaultClient to manage lifecycle of a dedicated 'vault' XMTP group conversation, streaming and decoding incoming key messages, and VaultManager to orchestrate key sharing, import, and device membership.
  • Extends ConversationCustomMetadata proto with an optional conversationType field (tag 6) to identify vault group conversations.
  • Makes KeychainIdentityStore service name overridable and adds VaultKeyStore as an async keychain façade for vault identities.
  • Includes a PairingCoordinator for 6-digit confirmation code generation and validation, and a convos-vault-implementation.md technical plan document.
  • Adds unit tests for codecs, pairing, vault manager, and key store behaviors.
📊 Macroscope summarized 1cbf8de. 11 files reviewed, 13 issues evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

yewreeka added 16 commits March 4, 2026 18:25
- Add optional conversationType field (6) to ConversationCustomMetadata
  protobuf for identifying Vault conversations
- DeviceKeyBundleCodec: full key export sent when new device joins Vault
- DeviceKeyShareCodec: single key share sent on conversation creation
- DeviceRemovedCodec: notification when a device is removed from Vault
- All three codecs use JSON encoding with ISO8601 dates
- shouldPush returns false (Vault messages are device-sync only)
- 25 tests covering encode/decode, edge cases, forward compatibility
Use protoc-gen-swift's Visibility=Public flag instead of manually
editing the generated file.
New ConvosVault Swift package for multi-device key sync:
- Depends on ConvosAppData and XMTPiOS (same pattern as ConvosProfiles)
- Moved DeviceKeyBundleCodec, DeviceKeyShareCodec, DeviceRemovedCodec
  from ConvosCore/Custom Content Types/ to ConvosVault/ContentTypes/
- Moved VaultContentTypeTests to ConvosVault/Tests/
- ConvosCore now depends on ConvosVault package
- InboxStateMachine imports ConvosVault for codec registration
New KeychainKeyStoreProtocol and implementation for storing
KeychainIdentityKeys by arbitrary identifier with configurable
accessibility:
- afterFirstUnlockThisDeviceOnly: local keychain only (default)
- afterFirstUnlock: syncs via iCloud Keychain

This enables the Vault to store its key in both local and iCloud
keychain, while keeping KeychainIdentityStore unchanged for
conversation identity management.

Includes MockKeychainKeyStore for testing and 11 unit tests.
Simplified alternative to InboxStateMachine with 4 states
(disconnected, connecting, connected, error) instead of 10.

- connect: builds or creates XMTP client, finds or creates Vault
  group (identified by conversationType='vault' in appData)
- disconnect: cancels stream, clears state
- send: sends typed content with codec to Vault group
- members/addMember/removeMember: group membership management
- Message streaming dispatches to VaultClientDelegate for
  DeviceKeyBundle, DeviceKeyShare, and DeviceRemoved content types
- Uses OSAllocatedUnfairLock for thread-safe state access
Sits on top of VaultClient and VaultIdentityStoreProtocol:
- shareKey: sends a single key share to all Vault devices
- shareAllKeys: bundles all local identities into a DeviceKeyBundle
- listDevices: enumerates Vault group members
- removeDevice: sends DeviceRemoved message then removes from group
- Incoming key shares/bundles are auto-imported to identity store
  with duplicate detection (existing keys are not overwritten)

Supporting types:
- VaultIdentityKeys: raw key pair for vault identity generation
- VaultIdentityEntry: conversation-level identity (id, inbox, keys)
- VaultDevice: device info model for UI display
- VaultIdentityStoreProtocol: abstraction for key persistence
- VaultManagerDelegate: callbacks for import/removal/errors

8 unit tests covering import, dedup, error cases, and models.
Wraps two KeychainKeyStore instances (local + iCloud) to provide
automatic redundancy for the Vault key:
- save: writes to local (ThisDeviceOnly) and iCloud (AfterFirstUnlock)
- load: reads from local first, falls back to iCloud (and caches locally)
- exists: checks both stores
- delete: removes from both

This enables Vault key recovery when restoring to a new device
with the same Apple ID, while keeping a local copy for performance
and offline access.

9 unit tests covering dual-store save, load priority, iCloud
fallback with local caching, missing key errors, and deletion.
Phase 3 + 4 foundations:

PairingCoordinator:
- 6-digit confirmation code generation and validation
- Pairing state machine (idle, waitingForScan, waitingForConfirmation,
  sharingKeys, completed, failed, expired)
- PairingError types for all failure modes
- Input cleaning (strips spaces/dashes from confirmation codes)

VaultKeyShareNotifier:
- Protocol for auto-sharing keys on conversation creation
- ConversationKeyInfo carries identity data for sharing
- VaultManager conforms and auto-sends DeviceKeyShare when
  connected and a new conversation key is created

14 pairing tests + 47 total ConvosVault tests passing.
Provides the interface SessionManager will use to start/stop the
Vault and notify it of new conversation key creation:
- startVault: connects with signing key and XMTP options
- stopVault: disconnects cleanly
- notifyConversationCreated: triggers auto key share

VaultManager conforms, bridging to connect/disconnect/conversationKeyCreated.
KeychainKeyStore duplicated keychain CRUD from KeychainIdentityStore.
Instead, add configurable service + accessibility params to
KeychainIdentityStore's init (defaulting to existing behavior).

VaultKeyStore now uses two KeychainIdentityStore instances:
- Local: service='vault', accessibility=ThisDeviceOnly
- iCloud: service='vault-icloud', accessibility=AfterFirstUnlock

Removes 249 lines of duplication. 340 ConvosCore tests passing.
The Vault identity is a proper KeychainIdentity with its own
inboxId and clientId (assigned when the XMTP client is created),
enabling push notification registration and notification extension
processing — same as any conversation identity.

Changes:
- save/load/delete all take inboxId/clientId params
- loadFromAnyStore: finds the Vault identity from either store
  without needing the inboxId upfront (for first-time recovery)
- deleteAll: clears both stores entirely

This also prepares for device backups — when enabled, all
conversation keys from the main KeychainIdentityStore will be
mirrored to an iCloud-accessible store using the same pattern.

13 unit tests covering all paths.
Each device gets its own Vault key via explicit pairing (QR code).
No iCloud Keychain sync — avoids the complexity of one inboxId
with multiple installations vs multiple members.

One device = one member = one inboxId. Simple.

Changes:
- VaultKeyStore: single store (no local+iCloud dual store)
- KeychainIdentityStore: revert accessibility param, keep service param
- VaultManager: track member count, only share keys when > 1 member
- conversationKeyCreated skips if solo device (nobody to share with)
- refreshMemberCount on connect and after removeDevice

55 tests passing (47 ConvosVault + 8 VaultKeyStore).
VaultClient now handles background/foreground transitions:
- pause: cancels message stream, drops local DB connection
- resume: reconnects DB, syncs conversations + group, restarts stream
- Lifecycle observation via NotificationCenter (vaultDidEnterBackground,
  vaultWillEnterForeground) — started on connect, stopped on disconnect

The app posts these notifications from its scene delegate, and
VaultClient responds automatically. On resume, member count is
also refreshed in case devices were added/removed while backgrounded.

VaultSessionIntegration exposes pauseVault/resumeVault for
SessionManager to call directly if needed.
@claude
Copy link
Copy Markdown

claude Bot commented Mar 5, 2026

Claude finished @yewreeka's task —— View job


Code Review Complete

I've reviewed this comprehensive Convos Vault implementation. This is well-architected foundational work for multi-device key synchronization. Here's my detailed feedback:


🎯 Overall Assessment

Strong implementation with solid architecture, good test coverage, and proper security practices. The phased approach (content types → client → manager → integration) is logical and reviewable.

Key strengths:

  • Clean package separation (ConvosVault independent of ConvosCore)
  • Thread-safe implementations using OSAllocatedUnfairLock and actors
  • Comprehensive test suite (55 tests passing)
  • Good use of protocols for testability
  • Proper lifecycle handling (pause/resume for backgrounding)

Areas for improvement below ⬇️


🔒 Security Concerns

1. Private key logging exposure (High Priority)

VaultClient.swift:199 filters out messages from self using clientInboxId, but doesn't validate message authenticity beyond checking inbox IDs. Consider:

  • Verifying message signatures before processing key material
  • Adding rate limiting to prevent key flooding attacks

2. Missing input validation (Medium Priority)

PairingCoordinator.swift:55-58 generates confirmation codes, but:

public static func generateConfirmationCode() -> String {
    let code = (0 ..< 6).map { _ in String(Int.random(in: 0 ... 9)) }.joined()
    return code
}
  • No entropy check for weak codes (e.g., "000000", "123456")
  • Consider rejecting sequential or repeated digits

3. Keychain accessibility concern

VaultKeyStore.swift uses KeychainIdentityStore with ThisDeviceOnly accessibility, but the plan document mentions iCloud sync was removed. Good decision for security, but ensure this is clearly documented in docs/plans/convos-vault-implementation.md (currently still references iCloud Keychain recovery at line 135).


🐛 Potential Bugs

4. Race condition in member count (Medium Priority)

VaultManager.swift:73, 183-186:

private let memberCountLock: OSAllocatedUnfairLock<Int> = .init(initialState: 1)

private func refreshMemberCount() async {
    let count = (try? await vaultClient.members().count) ?? 1
    memberCountLock.withLock { $0 = count }
}
  • refreshMemberCount() is async and could be called concurrently
  • If members() throws, it defaults to 1 (could hide real errors)
  • hasMultipleDevices might return stale data between connect/resume
  • Suggestion: Track last refresh timestamp and/or expose refresh errors to delegate

5. Deduplication logic issue

VaultManager.swift:197-198, 218-219:

let alreadyExists = await identityStore.hasIdentity(for: share.inboxId)
guard !alreadyExists else { return }
  • Checks existence by inboxId, but a conversation could have multiple keys with different conversationId
  • The plan (line 244) says dedup by (inboxId, conversationId) pair
  • Suggestion: Add hasIdentity(for:conversationId:) to VaultIdentityStoreProtocol

6. Unhandled error in stream

VaultClient.swift:196-208 catches errors in message stream but continues silently:

} catch {
    if !Task.isCancelled {
        self.delegate?.vaultClient(self, didEncounterError: error)
        self.updateState(.error(error))
    }
}
  • Stream terminates permanently on error (no retry)
  • State becomes .error, but no automatic recovery
  • Suggestion: Add exponential backoff retry for transient network errors

⚡ Performance Considerations

7. Potential memory leak in stream

VaultClient.swift:194-208:

streamTask = Task { [weak self] in
    guard let self else { return }
    do {
        for try await message in group.streamMessages() {
            guard !Task.isCancelled else { break }
            guard message.senderInboxId != clientInboxId else { continue }
            self.handleMessage(message)
        }
    } catch { ... }
}
  • [weak self] is good, but streamMessages() might hold references
  • Suggestion: Verify XMTP SDK properly releases stream on task cancellation

8. Unnecessary async/await in hot path

VaultManager.swift:197-205:

let alreadyExists = await identityStore.hasIdentity(for: share.inboxId)
guard !alreadyExists else { return }

do {
    try await identityStore.save(entry: entry)
    delegate?.vaultManager(self, didImportKey: entry)
} catch { ... }
  • Two round trips to actor for check-then-save
  • Suggestion: Add saveIfNotExists to protocol for atomic check-and-save

📚 Code Quality & Best Practices

9. Inconsistent error handling patterns

  • VaultClient.swift:108 silently ignores dropLocalDatabaseConnection() error
  • VaultManager.swift:184 silently defaults member count to 1 on error
  • Suggestion: Log errors with context using Logger.error() per project conventions

10. Missing protocol documentation

VaultSessionIntegration.swift:4-10 has no doc comments:

public protocol VaultSessionIntegration: Sendable {
    func startVault(signingKey: SigningKey, options: ClientOptions) async throws
    func stopVault()
    func pauseVault()
    func resumeVault() async
    func notifyConversationCreated(_ keyInfo: ConversationKeyInfo)
}
  • Suggestion: Add doc comments explaining when each method should be called (e.g., "Called by SessionManager on app launch")

11. Weak confirmation code validation

PairingCoordinator.swift:60-63:

public func validateConfirmationCode(_ input: String, expected: String) -> Bool {
    let cleaned = input.filter(\.isNumber)
    return cleaned == expected && cleaned.count == 6
}
  • Filters to numbers but doesn't validate length before cleaning (could accept "abc123def")
  • Suggestion: Check length after cleaning, or return validation error enum

12. Protobuf field naming inconsistency

conversation_custom_metadata.proto:21:

optional string conversationType = 6;  // e.g. "vault" for multi-device key sync
  • Comment says "vault" but no validation enforces known types
  • Suggestion: Consider enum or constant definition in Swift for type safety

✅ Test Coverage Assessment

Good coverage overall (55 tests), but gaps exist:

13. Missing test scenarios:

  • VaultClient: No tests for pause/resume lifecycle
  • VaultManager: No test for shareAllKeys() with empty store
  • VaultKeyStore: No test for concurrent save/load operations
  • PairingCoordinator: No tests for timeout behavior (field exists but unused)
  • Content codecs: No tests for very large key bundles (1000+ keys)

14. Mock limitations:

VaultManagerTests.swift:6-31 has MockVaultIdentityStore but no failure scenarios:

  • No test for save() throwing errors
  • No test for hasIdentity() race conditions
  • Suggestion: Add error injection to mock for negative test cases

📏 Adherence to Project Conventions

15. Follows most conventions well:

✅ Uses actors and protocols for dependency injection
✅ No force unwrapping or implicitly unwrapped optionals
✅ Proper use of OSAllocatedUnfairLock for thread safety
✅ Guard statements for early returns
✅ No comments unless necessary (good!)

16. Minor style issues:

VaultClient.swift:4 uses @preconcurrency import XMTPiOS - this is correct but worth documenting why XMTP isn't Sendable-ready yet.

VaultManager.swift:69 mixes @unchecked Sendable with lock-based synchronization:

public final class VaultManager: @unchecked Sendable {
    private let vaultClient: VaultClient
    private let memberCountLock: OSAllocatedUnfairLock<Int> = .init(initialState: 1)
  • VaultClient is marked @unchecked Sendable but uses locks internally
  • Suggestion: Document thread-safety model in doc comment

🔄 Architecture & Design

17. Strong separation of concerns:

  • ✅ VaultClient: Low-level XMTP operations
  • ✅ VaultManager: Business logic orchestration
  • ✅ VaultKeyStore: Storage abstraction
  • ✅ Content codecs: Protocol-specific encoding

18. Protocol design consideration:

VaultIdentityStoreProtocol (VaultManager.swift:18-23) is very similar to KeychainIdentityStoreProtocol - consider if they should share a common base protocol for key storage operations.

19. Lifecycle handling is solid:

VaultClient.swift:226-257 properly observes notification center for background/foreground:

private func startLifecycleObservation() {
    lifecycleTask = Task { [weak self] in
        await withTaskGroup(of: Void.self) { group in
            group.addTask { ... }
            group.addTask { ... }
            await group.waitForAll()
        }
    }
}

✅ Good use of withTaskGroup for structured concurrency


📝 Documentation & Plan Consistency

20. Implementation matches plan:

The code follows docs/plans/convos-vault-implementation.md closely, with one exception:

Removed iCloud Keychain sync (per PR description) but plan still references it (lines 102-105, 133-137).

  • Suggestion: Update plan document to reflect local-only decision

21. Missing integration points:

Plan mentions (line 158) hooking into ConversationStateMachine for auto key sharing, but no code shows this integration yet.

  • Is this deferred to a future PR in the stack?
  • Suggestion: Add TODO comment or update plan to clarify

🎨 Suggestions for Polish

22. Add convenience initializers:

VaultDevice.swift:6-16 could add:

public static func current(inboxId: String, name: String) -> VaultDevice {
    VaultDevice(inboxId: inboxId, name: name, isCurrentDevice: true)
}

23. Consider Result type for delegate methods:

Instead of separate didImportKey and didEncounterError, use:

func vaultManager(_ manager: VaultManager, didCompleteImport result: Result<VaultIdentityEntry, Error>)

24. Add metrics:

Consider tracking:

  • Number of keys synced
  • Time to sync
  • Stream reconnection count
    For debugging multi-device issues in production

🚀 Final Recommendations

Before merging:

  1. Fix deduplication logic (issue Add PostHog for Analytics #5) - could cause data inconsistency
  2. Update plan document to remove iCloud references (issue Storage #20)
  3. Add retry logic to stream error handling (issue Privy Integration #6)
  4. Improve confirmation code generation (issue Contact Card Creation UI #2)

Future PRs:
5. Add lifecycle tests for pause/resume scenarios
6. Document thread-safety model in VaultManager
7. Add integration tests with live XMTP local node
8. Consider adding telemetry for production debugging


Summary

This is high-quality foundation work for the Vault feature. The architecture is sound, tests provide good coverage, and security practices are generally solid. The issues identified are mostly refinements rather than fundamental problems.

Recommended action: Approve with minor revisions (fix issues #2, #5, #6, #20).


Copy link
Copy Markdown
Contributor Author

yewreeka commented Mar 5, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

updateState(.connecting)

let client: Client
do {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Low ConvosVault/VaultClient.swift:63

The catch block on line 69 catches all errors from Client.build and unconditionally falls back to Client.create. If build fails due to database corruption, invalid options, or other non-recoverable errors, the code creates a fresh client anyway — masking the underlying failure and potentially leaving the system in an unexpected state with orphaned or duplicate client data. Consider checking the error type and only falling back to create when the error indicates the client does not exist.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file ConvosVault/Sources/ConvosVault/VaultClient.swift around line 63:

The `catch` block on line 69 catches all errors from `Client.build` and unconditionally falls back to `Client.create`. If `build` fails due to database corruption, invalid options, or other non-recoverable errors, the code creates a fresh client anyway — masking the underlying failure and potentially leaving the system in an unexpected state with orphaned or duplicate client data. Consider checking the error type and only falling back to `create` when the error indicates the client does not exist.

Evidence trail:
ConvosVault/Sources/ConvosVault/VaultClient.swift lines 55-80 at REVIEWED_COMMIT - The `connect` function has a do-catch block where `Client.build` is called in the try block (lines 63-67), and the catch block (lines 68-72) catches all errors without any error type checking and unconditionally calls `Client.create` as fallback.

@yewreeka yewreeka force-pushed the jarod/join-request-content-type branch from 16b87f5 to 3acd5b7 Compare March 6, 2026 06:00
@yewreeka yewreeka changed the base branch from jarod/join-request-content-type to graphite-base/558 March 6, 2026 06:01
@yewreeka yewreeka closed this Mar 6, 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.

1 participant