Federated IBD matching: a front door, and a memory - #43
Merged
Conversation
The IBD engine has been complete for months — detection, the X3DH/AES-GCM exchange channel, signed attestations, the chromosome browser — but a matching conversation had nowhere to live. Outbound requests were held in a UI HashMap, so a restart forgot that we had asked anyone anything; only *completed* exchanges persisted. Discovery, consent, and results were three disjoint cards buried in one subject's tab, even though a conversation is keyed by our DID and the broker's request URI and belongs to no subject in particular. Adds the ledger the flow was missing (`ibd_request`, migration 0041) and `App::refresh_matching` to reconcile it against the broker in one pass. The reconciler adopts what `/exchange/incoming` reports with `insert_if_absent`, so re-polling can never walk back a decision made locally, and a failing pass degrades the view instead of emptying it. Two AppView endpoints that existed but were never called are now wired. `/ibd/dismiss` gives dismissal an actual button. `/ibd/attest` is the one that mattered: without it a completed comparison stayed private forever and the discovery graph could not grow from its own results. Attest is gated on both parties agreeing on the summary — filing a one-sided figure from a comparison our own run disputes would put a claim on the graph we do not believe. Status is deliberately honest about what this edge can know. The broker is symmetric-blind, so a partner's refusal is indistinguishable from silence; both stay "waiting on them", and DECLINED means *we* declined. Consent is a modal, not a row button, because accepting reveals our DID and puts our IBD-panel dosages on the wire. It says so, in three headings: what you send, what they learn, what never leaves the device. The subject tab keeps its results card and gains a link across. The subject picker is a filter plus a virtualized list rather than a ComboBox — a workspace can hold 10k subjects, and a ComboBox builds a widget per entry per frame. Companion AppView change (decodingus): `/ibd/suggestions` now returns the caller's own `target_sample_guid`. Without it `owns_sample` could never be satisfied from the edge and attest was unreachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The remaining flat `ComboBox` over every other subject in the workspace, left behind when the Matching tab's picker was rebuilt. Two costs, both proportional to workspace size and both paid per frame: the `others` vec cloned every subject's `donor_identifier` whether or not the popup was open, and the popup itself built one widget per entry. At 10k subjects that is a stall on a card that is merely *visible*. Same shape as the Matching picker now — current choice plus a Change reveal, then a filter over a virtualized `show_rows` list, so the cost is the rows on screen. The selected label is a lookup rather than a scan of a copied roster, and the "no other subjects" check is a short-circuiting `any` instead of building the vec to ask whether it is empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backlog item 1.6. The IBD engine has been complete for months — detection, identity math, the X3DH/AES-GCM exchange channel, signed attestations, the pairwise chromosome browser. What it never had was a coherent surface, or any memory of a conversation in progress.
What was wrong
HashMap, so a restart forgot that we had asked anyone anything. Only completed exchanges persisted (ibd_exchange_result)./ibd/dismisshad no button./ibd/attestwas worse: a completed comparison stayed private forever, so the discovery graph could not grow from its own results.urn:ibd:…and a purpose string — and nothing else.What this adds
M1 — the ledger. Migration
0041_ibd_request+navigator-store/src/ibd_request.rs: one durable row per conversation, keyed by the brokerrequest_uri.App::refresh_matchingreconciles it in three passes (/exchange/incoming→ adopt,/exchange/pending→ advance non-terminal, stored results → EXCHANGED).insert_if_absentis the primitive that matters — re-polling can never walk back a decision made locally. A failing pass degrades the view rather than emptying it.M2 — the surface. Top-level
Nav::Matching(🔗) with Suggestions / Requests / Results, replacing the per-subject discovery cards. The subject tab keeps its results card and gains a link across.M5 — both endpoints wired.
ibd_dismissandibd_attest, with canonical signing strings mirroringdu_db::ibd::messagesbyte for byte (the cM figure is signed at{:.1}to match).Judgement calls worth reviewing
Requested, andDeclinedmeans we declined. There is deliberately no "they said no" state, because we are never told.show_rows, notComboBox— a workspace can hold 10k subjects and aComboBoxbuilds a widget per entry per frame. The second commit converts the pre-existing one inconsensus_ibd_section, which also rebuilt the whole roster every frame whether or not its popup was open.Depends on
JamesKane/decodingus#29 —
/ibd/suggestionsmust return the caller's owntarget_sample_guid, orowns_samplecan never be satisfied and attest is unreachable. Navigator parses it asOption, so this branch degrades cleanly without it: everything works except attesting.Testing
cargo clippy --all-targetsclean ·cargo test --workspacegreen (0 failures) · en/es locales at parity · the new nav glyph 🔗 added to theicon_glyph_testslist so it is actually checked (🧬 shipped as tofu once).New offline coverage: ledger round-trip and the consent-preservation invariant (
navigator-store), the failure/forget lifecycle, and that attest is skipped without handles or agreement (navigator-app).Note for the reviewer
cargo fmt --allreformats ~124 files — the repo is already rustfmt-drifted atmain(stable rustfmt disagrees withmaintoo, so it is not a toolchain artifact). I reverted all of that and re-applied my edits by hand, so this diff carries no formatting noise. The drift is untouched and still wants its own commit.Deferred
Background polling + an unread badge for inbound consent requests (the Community 🔔 pattern); a Settings discoverability opt-in; a UI path for the direct
exchange_request(partner_did, …)initiator, still test-only; the segment ideogram for persisted exchange results.🤖 Generated with Claude Code