Skip to content

fix(team-vault): reconcile vault-key distribution for keyless members (#41)#49

Open
kipavy wants to merge 1 commit into
devfrom
fix/41-team-vault-key-reconciliation
Open

fix(team-vault): reconcile vault-key distribution for keyless members (#41)#49
kipavy wants to merge 1 commit into
devfrom
fix/41-team-vault-key-reconciliation

Conversation

@kipavy

@kipavy kipavy commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #41.

Problem

Team vault-key distribution was purely event-driven — the key was wrapped for members only at team creation and via the live team_members SSE diff. Two normal flows left a member permanently keyless and silently locked out (client polling GET vault-key → 404 until the inviter next came online):

  • Async invite acceptance: invitee accepts while the only key-holder is offline → the membership event is never delivered to a key-holder.
  • First invite / adder-is-only-key-holder: addMemberById loads members before the adder's own team_members event fires, so the local diff sees zero newcomers and skips distribution.

There was no reconciliation for members who already exist but lack a key.

Fix

Reconciliation (reconcileTeamVaultKeys). Compares team_members against the server's key-holder list and wraps the key for anyone missing it. Runs:

  • on a key-holder's login (onTeamLogin) — self-heals the async-acceptance lockout;
  • on every membership change (team_members SSE handler) — replacing the old local-diff logic, which also fixes the adder-is-only-key-holder case.

Non-holders no-op (they can't unwrap the key). Only genuinely keyless members trigger a PUT, so repeated runs don't churn or re-notify existing holders. The missing set is computed precisely via a new server endpoint (getVaultKeyHoldersGET /v1/teams/:id/vault-key/holders).

Invitee UX. not_found now renders a dedicated "Waiting for access" screen. Previously it was bucketed with generic errors — and never even shown, since not_found was missing from the state-screen gate in MainPanel.

Tests

  • reconcile distributes only to the missing set / no-ops when all hold a key / no-ops for non-holders
  • getVaultKeyHolders transport shape
  • Full client suite: 850 passed, tsc clean

Companion

Server endpoint: VoltiusApp/server#2 (must merge for reconciliation to function; the client degrades gracefully — reconcile catches the failure and no-ops — if the endpoint is absent).

🤖 Generated with Claude Code

https://claude.ai/code/session_01RnVt37SuhKfm94kpkh8rez

…#41)

Team vault-key distribution was purely event-driven: the key was wrapped
for members only at team creation and via the live team_members SSE diff.
A member who accepted an invite while the only key-holder was offline —
or the very first invitee, whom the adder's own membership event never
counts as "new" — was left with no key and silently locked out, polling
GET vault-key → 404 until the inviter happened to reconnect.

Add a reconciliation pass. reconcileTeamVaultKeys() compares team_members
against the server's key-holder list and wraps the key for anyone missing
it. It runs on a key-holder's login (onTeamLogin) and on every membership
change (team_members SSE handler, replacing the old local-diff logic that
missed the adder-is-only-key-holder case). Non-holders no-op; only
genuinely keyless members trigger a PUT, so repeated runs don't churn or
re-notify existing holders.

Detection uses a new GET /v1/teams/:id/vault-key/holders endpoint
(returns user_ids with a key) via teamService.getVaultKeyHolders(), so
the client distributes to the precise missing set instead of blindly
re-wrapping for everyone.

Invitee UX: the not_found state now renders a dedicated "Waiting for
access" screen (previously bucketed with generic errors, and never even
shown because not_found was missing from the state-screen gate) so the
member sees a clear waiting state instead of a blank/failed vault.

Tests: reconcile distributes only to the missing set / no-ops when all
hold a key / no-ops for non-holders; getVaultKeyHolders transport shape.
Server endpoint tested separately in VoltiusApp/server.
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