You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 server — APIConfiguration.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 retainedI_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.
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.
Part of the standalone client-direct contribution track. Cross-repo trigger work:
DiscVault(backend, mediator) +DiscVaultApp(iOS, initiator). Decomposed from App-Guidance change specprojects/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 server —APIConfiguration.configure(serverURL:token:username:role:)succeeds for the first time on a device that already has aMovieVaultKeychainStoreidentity — 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_iosis not proof it may foldI_iosinto the server identityI_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):APIConfiguration.configure(...)(first successful configure on a device that already holds a keychain identity).I_ios: a signature byI_ios's key overI_srv's instance id (mirroring the existing key-rotation proof shape,contributions/routes.pyL337–346), and hand it to the DiscVault server. iOS never holdsI_srv's key.instance_idas a small persistent breadcrumb (for D4 chain-forward), which also survives reinstall via the ThisDeviceOnly keychain.APIConfiguration.isConfiguredreturns to false and the user next contributes standalone, revert to the retainedI_ioskeychain key (no new identity minted). Re-couple re-fires this same trigger on since-decouple contributions.Backend (
DiscVault) — mediator (§2b.1):I_ios, add its own proof of possession ofI_srv(it holdsI_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 holdsI_ios's key; each side contributes the half it can prove.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)
I_ioslocally 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.Spec ref
§2b.1 (trigger, two-sided auth), §2b.3 D3/D4 (idempotent, consent, chain-forward — DECIDED), §2b.4 (decouple/revert — DECIDED). Code:
APIConfiguration.swiftL63; proof shapecontributions/routes.pyL337–346.Acceptance
I_ios, backend provesI_srv; neither holds the other's key.I_iosis soft-retired (dormant), not deleted (verified via feat: add bol provider and amazon asin-aware shop detection #255).instance_idbreadcrumb; survives reinstall.I_ioskey (no new mint); re-couple re-fires idempotently on since-decouple contributions.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.