Skip to content

drop declined social bindings via source-of-truth reconcile#1068

Open
Bekiboo wants to merge 2 commits into
mainfrom
fix/eid-wallet-social-binding-decline-reconcile
Open

drop declined social bindings via source-of-truth reconcile#1068
Bekiboo wants to merge 2 commits into
mainfrom
fix/eid-wallet-social-binding-decline-reconcile

Conversation

@Bekiboo

@Bekiboo Bekiboo commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Description of change

Social bindings kept showing as successful on the scanner's device even after the requester declined (#990). The scanner writes a single-signature mirror into its own vault on acceptance; the requester's decline only deletes the primary doc from its own vault (vault isolation blocks cross-vault deletion) and the scanner never re-checked, so its mirror lingered forever. This reconciles scanner-initiated (sent) mirrors against the counterparty's vault (the source of truth) on every list load: counter-signed → confirmed, gone → dropped + orphan mirror deleted, still pending/offline → kept as Awaiting confirmation.

Issue Number

Closes #990

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

  • pnpm --filter eid-wallet check → svelte-check 0 errors, Biome clean.
  • Cross-vault read path verified against the local stack: registry resolve (@… → vault URI) + GraphQL read of social_connection docs returned HTTP 200 with no auth/scoping error, confirming the reconcile actually executes rather than silently no-op'ing.
  • Cursor pagination is schema-validated (typedefs → resolver → findBindingDocuments → the same paginated DB method exercised by the metaEnvelopes pagination spec); a live paginated round-trip was blocked by an unrelated local-stack outage (all bindingDocuments queries 500'd, including a previously-passing one).
  • Full 2-device / 2-vault end-to-end reproduction (accept → decline → binding disappears on the scanner; plus confirm and pending paths) delegated to QA — test spec provided.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Design decisions

  • Pull / self-healing, not push — the decliner does not try to delete the scanner's remote mirror (cross-vault write is fragile and can fail silently); each device self-heals on its next list load.
  • Never delete on a transient failure — a mirror is removed only on a confirmed declined; an unreachable vault keeps it pending.
  • Reconciliation pages through the counterparty's full doc setdeclined is concluded only after every page has been checked (via after/pageInfo cursor pagination), so a doc merely absent from the first page can't trigger a wrongful mirror deletion.
  • Honest unconfirmed state — the scanner success screen now reads "Request sent" and unconfirmed bindings are labelled "Awaiting confirmation" in the list and details sheet, so a request no longer looks successful before the counterparty acts.

A detailed high-level summary could not be generated for this review. Here is an overview derived from the analyzed file changes:

  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts: ## AI-generated summary of changes
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte: ## AI-generated summary of changes
  • infrastructure/eid-wallet/src/routes/(app)/main/components/SocialBindingAccordion.svelte: ## AI-generated summary of changes
  • infrastructure/eid-wallet/src/routes/(app)/main/components/SocialBindingDetailsSheet.svelte: ## AI-generated summary of changes
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/SocialBindingDrawer.svelte: ## AI-generated summary of changes
  • infrastructure/eid-wallet/src/routes/(app)/social-bindings/+page.svelte: ## AI-generated summary of changes

@Bekiboo Bekiboo self-assigned this Jul 2, 2026
@Bekiboo Bekiboo requested a review from coodos as a code owner July 2, 2026 07:29
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c926c5a-9e2a-4e56-b082-539b4820fef3

📥 Commits

Reviewing files that changed from the base of the PR and between f94b5eb and 6f204b6.

📒 Files selected for processing (1)
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
👮 Files not reviewed due to content moderation or server errors (1)
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts

📝 Walkthrough
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: reconciling and dropping declined social binding mirrors.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description follows the template and includes the change summary, issue number, type, testing, checklist, and extra design notes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/eid-wallet-social-binding-decline-reconcile

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

423-436: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Throttle social-binding reconciliation on passive refreshes. refreshBindings() calls loadBindingDocuments() on mount, every 30s, and on visibility changes, so fetchReconciledSocialBindings() now fans out to remote vault lookups repeatedly for each unconfirmed sent binding. Cache or skip recent reconciliation runs so foreground polling doesn’t turn into repeated cross-vault traffic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
423 - 436, Throttle the social-binding reconciliation in loadSocialBindings so
refreshBindings does not repeatedly trigger cross-vault fetches on mount,
interval polling, and visibility changes. Add a recent-run cache or
debounce/skip guard around fetchReconciledSocialBindings in
loadSocialBindings/loadBindingDocuments so unconfirmed sent bindings are not
reconciled multiple times in quick succession. Use the existing
loadSocialBindings and refreshBindings entry points to centralize the cooldown
logic and prevent repeated remote vault traffic.
🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts (1)

707-718: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Deduplicate remote reads per counterparty.

summaries.map(...) fires one fetchSentBindingStatus per sent mirror, and each call does a resolveVaultUri fetch plus a GraphQL request. When the same counterparty appears in multiple sent docs (the grouping in both consuming pages assumes this happens), you repeatedly resolve and query the identical vault. Consider caching the status by counterpartyEname for the duration of one reconcile.

♻️ Sketch: memoize status per counterparty
 const summaries = await fetchSocialBindings(ownGqlUrl, callerEname);
+const statusCache = new Map<string, Promise<SentBindingStatus>>();
+const getStatus = (counterparty: string) => {
+    let p = statusCache.get(counterparty);
+    if (!p) {
+        p = fetchSentBindingStatus(callerEname, counterparty);
+        statusCache.set(counterparty, p);
+    }
+    return p;
+};
 
 const reconciled = await Promise.all(
     summaries.map(async (summary) => {
         if (summary.role !== "sent" || summary.mutuallySigned) {
             return summary;
         }
         try {
-            const status = await fetchSentBindingStatus(
-                callerEname,
-                summary.counterpartyEname,
-            );
+            const status = await getStatus(summary.counterpartyEname);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infrastructure/eid-wallet/src/lib/utils/socialBinding.ts` around lines 707 -
718, The reconcile flow in socialBinding.ts is redundantly calling
fetchSentBindingStatus for multiple sent summaries with the same
counterpartyEname, causing repeated resolveVaultUri and GraphQL reads. Update
the Promise.all mapping in the reconcile logic to memoize the remote status per
counterpartyEname for the duration of one run, so repeated sent mirrors reuse
the same in-flight or cached result instead of re-querying. Keep the fix local
to the reconcile path and preserve the existing behavior for non-sent or
mutuallySigned summaries.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

456-490: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pending-group computation duplicated across pages.

The hasSent/hasReceived/pending grouping logic here mirrors what the stack outline describes for social-bindings/+page.svelte (same cohort, "computes pending per group"). Duplicating this logic in two Svelte pages risks the two views drifting out of sync if reconciliation semantics change later.

Consider extracting a shared helper (e.g. groupSocialBindingsByContact / buildSocialBindingDisplay) into $lib/utils/socialBinding.ts and reusing it from both main/+page.svelte and social-bindings/+page.svelte.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
456 - 490, The pending-group and role calculation is duplicated in this page and
in the social-bindings page, so extract the shared grouping/display logic into a
reusable helper such as groupSocialBindingsByContact or
buildSocialBindingDisplay in $lib/utils/socialBinding.ts. Move the hasSent,
hasReceived, role, and pending computation out of the inline groupedSlice.map
callback in main/+page.svelte and reuse the same helper from
social-bindings/+page.svelte to keep both views consistent.
infrastructure/eid-wallet/src/routes/(app)/social-bindings/+page.svelte (1)

68-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the group-aggregation logic to a shared helper.

The role-derivation/pending-computation/name-resolution logic that builds SocialBindingDisplay here appears to be duplicated in main/+page.svelte within the same PR stack (per the stack outline, that page also "computes a pending flag per counterparty group"). Since both pages need identical grouping semantics against SocialBindingSummary, extracting this into a shared utility (e.g. alongside fetchReconciledSocialBindings in socialBinding.ts) would prevent the two implementations from silently diverging over time.

The pending/role computation itself is correct here and matches the documented SocialBindingDisplay contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infrastructure/eid-wallet/src/routes/`(app)/social-bindings/+page.svelte
around lines 68 - 104, The `SocialBindingDisplay` group-building logic in the
`rows` aggregation is duplicated with the matching counterparty-group handling
in main/+page.svelte and should be centralized. Extract the shared
role/pending/name-resolution behavior into a reusable helper near
`fetchReconciledSocialBindings` in `socialBinding.ts`, then have both pages call
that helper so the `role`, `pending`, and name lookup semantics stay consistent
and don’t diverge.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@infrastructure/eid-wallet/src/lib/utils/socialBinding.ts`:
- Around line 670-686: The reconciliation logic in fetchReconciledSocialBindings
is treating an empty first page from bindingDocuments as a real “declined”
state. Update the social connection lookup in socialBinding.ts to page through
all bindingDocuments results instead of relying on the hard first: 50 cap, and
only return "declined" after all pages have been checked and no matching
BindingDocParsed entry is found.

---

Outside diff comments:
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 423-436: Throttle the social-binding reconciliation in
loadSocialBindings so refreshBindings does not repeatedly trigger cross-vault
fetches on mount, interval polling, and visibility changes. Add a recent-run
cache or debounce/skip guard around fetchReconciledSocialBindings in
loadSocialBindings/loadBindingDocuments so unconfirmed sent bindings are not
reconciled multiple times in quick succession. Use the existing
loadSocialBindings and refreshBindings entry points to centralize the cooldown
logic and prevent repeated remote vault traffic.

---

Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/socialBinding.ts`:
- Around line 707-718: The reconcile flow in socialBinding.ts is redundantly
calling fetchSentBindingStatus for multiple sent summaries with the same
counterpartyEname, causing repeated resolveVaultUri and GraphQL reads. Update
the Promise.all mapping in the reconcile logic to memoize the remote status per
counterpartyEname for the duration of one run, so repeated sent mirrors reuse
the same in-flight or cached result instead of re-querying. Keep the fix local
to the reconcile path and preserve the existing behavior for non-sent or
mutuallySigned summaries.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 456-490: The pending-group and role calculation is duplicated in
this page and in the social-bindings page, so extract the shared
grouping/display logic into a reusable helper such as
groupSocialBindingsByContact or buildSocialBindingDisplay in
$lib/utils/socialBinding.ts. Move the hasSent, hasReceived, role, and pending
computation out of the inline groupedSlice.map callback in main/+page.svelte and
reuse the same helper from social-bindings/+page.svelte to keep both views
consistent.

In `@infrastructure/eid-wallet/src/routes/`(app)/social-bindings/+page.svelte:
- Around line 68-104: The `SocialBindingDisplay` group-building logic in the
`rows` aggregation is duplicated with the matching counterparty-group handling
in main/+page.svelte and should be centralized. Extract the shared
role/pending/name-resolution behavior into a reusable helper near
`fetchReconciledSocialBindings` in `socialBinding.ts`, then have both pages call
that helper so the `role`, `pending`, and name lookup semantics stay consistent
and don’t diverge.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba62ab24-2725-45d0-8f5a-f6c04d5fb1e7

📥 Commits

Reviewing files that changed from the base of the PR and between c6f8c3e and f94b5eb.

📒 Files selected for processing (6)
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/main/components/SocialBindingAccordion.svelte
  • infrastructure/eid-wallet/src/routes/(app)/main/components/SocialBindingDetailsSheet.svelte
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/SocialBindingDrawer.svelte
  • infrastructure/eid-wallet/src/routes/(app)/social-bindings/+page.svelte

Comment thread infrastructure/eid-wallet/src/lib/utils/socialBinding.ts Outdated
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.

[Bug] - User addition succeeds on Devices despite a declined binding request from Device 2

1 participant