Skip to content

Couple-and-merge trigger (Phase 4): server-mediated two-sided authorisation of the standalone→coupled identity merge #699

Description

@helmerzNL

Part of the standalone client-direct contribution track. Cross-repo trigger work: DiscVault (backend, mediator) + DiscVaultApp (iOS, initiator). Decomposed from App-Guidance change spec projects/discvault/changes/2026-08-16-ios-contributions.md §2b.1. Tracked by cross-repo epic https://github.com/Flux76HQ/App-Guidance/issues/236.

Context

When a standalone iOS install holding a client-side identity I_ios (Phase 3, epic #236 / iOS slice) gains a DiscVault serverAPIConfiguration.configure(serverURL:token:username:role:) succeeds for the first time on a device that already has a MovieVaultKeychainStore identity — its contributions must stop forking onto a second, orphaned contributor. This slice wires the trigger that fires MovieVault's merge operation (that operation is MovieVault-v2 #255, and its semantics are DECIDED — this issue does not re-decide them).

The merge is a privileged cross-identity operation and cannot be authorised by the client alone: a device proving possession of I_ios is not proof it may fold I_ios into the server identity I_srv. Two-sided authorisation is required — which is why the DiscVault backend is an implementer, not just iOS.

What must change

iOS (DiscVaultApp) — initiator (§2b.1):

  • Observe the couple event on APIConfiguration.configure(...) (first successful configure on a device that already holds a keychain identity).
  • Produce a proof over I_ios: a signature by I_ios's key over I_srv's instance id (mirroring the existing key-rotation proof shape, contributions/routes.py L337–346), and hand it to the DiscVault server. iOS never holds I_srv's key.
  • Show a one-line consent before the merge fires (D3): "your standalone contributions will move to this server's contributor identity." The merge is never a silent side-effect of sign-in; it fires only on the first successful, user-confirmed couple.
  • Retain the last known surviving instance_id as a small persistent breadcrumb (for D4 chain-forward), which also survives reinstall via the ThisDeviceOnly keychain.
  • Decouple (§2b.4): when APIConfiguration.isConfigured returns to false and the user next contributes standalone, revert to the retained I_ios keychain key (no new identity minted). Re-couple re-fires this same trigger on since-decouple contributions.

Backend (DiscVault) — mediator (§2b.1):

  • Accept iOS's proof-over-I_ios, add its own proof of possession of I_srv (it holds I_srv's credentials), and call MovieVault's merge operation (feat: add bol provider and amazon asin-aware shop detection #255) with both halves. The server never holds I_ios's key; each side contributes the half it can prove.
  • Mediate the re-couple merge exactly as the first couple.
  • No sync-contract change (contributions are not in the normative sync contract).

Dependencies (§9 step 8)

Blocked on step 6 (iOS Phase 3 — the iOS slice above) AND step 7 (MovieVault merge operation, #255). The trigger has nothing to fire until both the standalone identity exists client-side and the merge capability exists server-side.

Backwards-compatibility (§5)

  • iOS must not retire I_ios locally until MovieVault confirms the merge. A merge that never happens leaves the two identities co-existing — worse than one, far better than a deleted history.
  • Against a MovieVault without the merge operation, or a couple event on a device with no standalone identity: no-op-safe, leave both identities intact, never strand or drop contributions.

Spec ref

§2b.1 (trigger, two-sided auth), §2b.3 D3/D4 (idempotent, consent, chain-forward — DECIDED), §2b.4 (decouple/revert — DECIDED). Code: APIConfiguration.swift L63; proof shape contributions/routes.py L337–346.

Acceptance

  • Merge fires only on the first successful, user-confirmed couple, after a one-line consent; never as a silent side-effect of sign-in.
  • Two-sided authorisation: iOS proves I_ios, backend proves I_srv; neither holds the other's key.
  • Backend calls MovieVault feat: add bol provider and amazon asin-aware shop detection #255's merge with both proofs; I_ios is soft-retired (dormant), not deleted (verified via feat: add bol provider and amazon asin-aware shop detection #255).
  • iOS retains the last surviving instance_id breadcrumb; survives reinstall.
  • Decouple reverts to the retained I_ios key (no new mint); re-couple re-fires idempotently on since-decouple contributions.
  • No-op-safe against a MovieVault lacking the merge, and on a device with no standalone identity; no history stranded or dropped.
  • No sync-contract change.

Draft prepared from the change spec §2b.1; filed as part of the standalone-contribution handoff (epic #236). Scheduling owner assigns when this slice starts.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions