A reloaded pair finds itself again: stable endpoint identity, a peer directory, and resume wiring - #99
Merged
Merged
Conversation
… key, and the devices map learns where its devices live iroh-bind minted a fresh identity every call, so a device's transport address died with its page session and a rebooted peer was unreachable forever — the enrollment's recorded endpoint id pointed at a key that no longer existed anywhere. The device-identity seam grows endpoint-key-pair: a SECOND persisted pair, deliberately not the signing pair (no cross-protocol reuse between keyhive's signatures and iroh's handshake; transport identity stays separably rotatable). The worker answers it from the namespace beside the signing key; iroh-bind adopts it through identity-from-keys and falls back to generate exactly as before when the embedder holds nothing. The same device now binds the same endpoint id every boot — solo-persistence carries the proof across a real reload. The us-doc devices map becomes the peer directory: entries carry endpoint (the adder records the joiner's transport-observed id at enrollment; user-create records the founder's own) and enrolled-by (empty = founding device), both additive. us-device-endpoint-put lets a device upsert its own — a no-op chunk-wise when unchanged, and it never creates an absent entry (two devices racing a fresh map under one key is an automerge conflict whose loser's fields vanish).
…atience over attempts, and a rebind when the endpoint died
The sync wiring existed only in the ceremony session; any reload on
either side ended it, and the resumed branch parked a pointerless
account on a sentence about a permanent state ('no todo list yet') that
was actually temporary.
resumeWire runs on every account:resumed boot: post the acceptor first,
find own entry in the devices directory, dial the device that enrolled
this one (reader dials — the ceremony's own direction, per edge), and
subscribe the todo list for every device this one enrolled. The loop is
patient (5s ticks for the life of the page, one status line) because the
thing being waited for is a person opening a browser. A Closed error
names this page's own dead endpoint — home-relay death is sticky — and
the identity is stable now, so the loop rebinds (same endpoint id),
re-posts the acceptor, and keeps going: the backgrounded-Android case
heals itself. The pointerless resume waits ten minutes for the pointer,
then adopts, reconciles the profile, and mounts.
Found en route: the driving hook drained us-events and threw the batch
away, starving the 1s poll of the profile-changed events it existed to
apply; both callers now share one drainAndAdopt.
Written down, not worked around: one-sided reload of the ACCEPTING
device is not recoverable from this file — conn-status reports the
handshake once and is never invalidated, so the surviving reader has no
evidence its handle is stale. The honest fix is engine-side liveness.
solo-resume-sync (e2e): pair, converge, reload BOTH pages, and a todo
plus a rename must still cross — plus the endpoint id equal across the
reload, the premise the wiring rests on.
…the marks cross into the trust table The display name synced and stopped there. The user's own glyph never left the device — the settings write-through sent name and hue and dropped the icon on the floor — and the account's marks (the user's word and glyph for a component) were fetched, cached, and never materialized: the visor's trust table stayed empty on every paired device, so the app mounted unmarked no matter what the account knew. Outbound, the write-through now carries the glyph as UTF-8 bytes (opaque to the engine, us-profile.icon). Inbound, applyProfile decodes and adopts it only when it decodes cleanly AND names a glyph in the visor's own vocabulary — the visor never adopts a word it doesn't speak, and an account with no icon must not undress a device that has one. Marks ride reconcileFromDriver's new onMarks (optional; existing callers unchanged), at the same three moments the profile established: join beat, resumed boots, drained events (the drain now also reconciles on mark-added/-changed/-conflict-repaired). Only WHOLE marks are seeded — petname and icon non-empty, needsReconfirm false — because the repaired view hands a conflict's loser out reconfirm-flagged and icon-cleared, and such a record is the ceremony's to re-confirm, not the table's to adopt (PAIRING.md §5). The mounted surface refreshes in the same breath, the shape onNamed already uses. Mark deletions still don't cross — there is no mark-forgotten event to drain; noted at the applyMarks site, needs an engine event. solo-pairing now picks a glyph on A through the real settings sheet and names the app through the real ceremony; B must wear both. The resume-sync scenario renames the app after the double reload and reads it on B — the drain trigger over the re-established wire.
Collaborator
Author
|
Added a third commit: the rest of the user follows the name — closes the two remaining account→device sync gaps you hit on the phone.
Gates: |
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.
Closes the last structural gap from the solo pairing work: sync existed only in the ceremony's page session, and a reload on either side ended it — permanently, because
iroh-bindminted a fresh transport identity every boot, so the enrollment's recorded endpoint id pointed at a key that no longer existed anywhere.Commit 1 — engine: the endpoint identity becomes the device's own
device-identitygrowsendpoint-key-pair: a second persisted Ed25519 pair, deliberately not the signing pair — no cross-protocol key reuse between keyhive's signatures and iroh's handshake, and transport identity stays separably rotatable. The worker answers it from the device namespace beside the signing key;iroh-bindadopts it viaidentity-from-keys(already in polymorph-iroh) and falls back togeneratewhen the embedder holds nothing.endpoint(adder records the joiner's transport-observed id at enrollment;user-createrecords the founder's) andenrolled-by(empty = founder). Additive.us-device-endpoint-putupserts a device's own endpoint — chunk-wise no-op when unchanged, and never creates an absent entry (a racing fresh-map insert is an automerge conflict whose loser's fields vanish).undefinedfails exactly there).Commit 2 — page: resume wiring by the directory
resumeWireon everyaccount:resumedboot: acceptor first, then dial the device that enrolled this one (reader dials — the ceremony's direction, per edge), subscribe the todo list for every device this one enrolled. Patient by design: 5s ticks for the life of the page, one status line — the wait is for a person opening a browser.Closedon a dial names this page's own dead endpoint (home-relay death is sticky). With a stable identity a rebind mints the same endpoint id, so the loop rebinds, re-posts the acceptor, and continues — the backgrounded-Android failure now heals itself.us-eventsand discarded the batch, starving the 1s poll ofprofile-changed; both callers now share onedrainAndAdopt.Known limit (documented in-code, not worked around)
One-sided reload of the accepting device is not recoverable from the page:
conn-statusreports the handshake once and is never invalidated, so the surviving reader has no evidence its handle is stale — and the only page-side "fix" would be double-dialling. The honest fix is engine-side connection liveness; the both-sides case (the ordinary one) recovers.Gates (re-run post-rebase onto #98)
cd engine && just compose— passjust e2e-check,just check— passjust e2e solo-resume-sync solo-pairing solo-erase solo-persistence device-pairing solo-account-storage— 6/6 in real ChromiumresumeWirefails solo-resume-sync at exactly the post-reload claimUnexercised by gates (documented): the rebind-on-Closed path (needs a killed home relay) and the pointerless-resume branch (needs a mid-sync reload). Both are reasoned from cited sources in comments.