{"id":50158,"plugin_id":"plugin_asdk_app_6abf2061a1248191a70d61197a20542a","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-24T13:42:16.666Z","digest":"c99bd14a7d6f8b14ace9136eadf3c200c8795453bf546ff6089cb19e8b5ca03b","against":null,"payload":{"description":"Links a provider account to the signed-in Hive Axyl player in Unity. Use for guest-to-provider linking, acquisition, ownership conflicts and post-link reconciliation while preserving the current account. Does not implement provider login or account merging.","included_files":[{"relative_path":"references/apple.md","size_in_bytes":905},{"relative_path":"references/credential-manager.md","size_in_bytes":1150},{"relative_path":"references/gpg.md","size_in_bytes":1597},{"relative_path":"references/steam.md","size_in_bytes":1122},{"relative_path":"references/webauth.md","size_in_bytes":10845}],"name":"axyl-link-provider","skill_md_contents":"---\nname: axyl-link-provider\ndescription: \"Links a provider account to the signed-in Hive Axyl player in Unity. Use for guest-to-provider linking, acquisition, ownership conflicts and post-link reconciliation while preserving the current account. Does not implement provider login or account merging.\"\nlicense: Apache-2.0\nmetadata:\n  author: Com2uS Platform Corporation\n  sdk-version: 1.0.0\n  engine: Unity 6000.0+\n---\n\n# Link a provider to the current player\n\nAdds a provider account to the signed-in player without changing the session. The\nInstalled SDK, SDK results and Login metadata sections below apply to every step.\n\n**Inputs:** a live session and the chosen route's configuration, or an\nalready-acquired token and user ID with their `Provider` value.\n**App owns:** UI, storage, conflict choices and result types.\n\n## Flow\n\n1. Resolve `IAuthService` and `ISessionManager` and require a live session before\n   opening provider UI; check cancellation.\n2. Capture the originating player and session. The recipe keeps `session.AccessToken`\n   and aborts if it changed after acquisition, a refresh included; the app may tell a\n   same-player refresh from an account change, but never links whichever account is\n   current.\n3. Acquire the credential with the chosen route below, or validate the supplied one.\n   Dismissal, cancellation, an unknown result or a failure stops here.\n4. Recheck the player, session and cancellation, then call\n   `auth.LinkProviderAsync(new ProviderLinkRequest { ProviderId = provider,\n   ProviderUserId = providerUserId, ProviderToken = providerToken },\n   new ApiCallContext { Token = ct })`, coordinated so an account change cannot\n   retarget it. Linking never calls `LoginProviderAsync`, exchanges an Axyl code or\n   calls `SetSession`.\n5. On `AuthLinkProviderResult.Success`, require `Data.PlayerId` and\n   `Data.ProviderUserId` and check them against the intended player and credential.\n   The first provider link invalidates that player's guest token: retire a saved\n   guest credential only when its player ID matches, and keep other players'.\n\n| Route | `Provider` | Read |\n|---|---|---|\n| Sign in with Apple | `SigninApple` | [Apple](references/apple.md) |\n| Google Credential Manager (Android) | `Google` | [Credential Manager](references/credential-manager.md) |\n| Google Play Games (Android) | `GooglePlayGames` | [Play Games](references/gpg.md) |\n| Native Steam ticket | `Steam` | [Steam](references/steam.md) |\n| Browser Google/X OAuth, Apple or Steam OpenID | `Google`, `X`, `SigninApple`, `Steam` | [WebAuth](references/webauth.md) |\n\nEach route stops at the token and user ID. Register its addon (for example\n`.AddAppleSignIn()`) in the app's single `HiveBootstrap.Initialize` builder next to\n`AddAuth()`, never at link time. The addon namespaces are `Hive.Axyl.Auth.Addon.Apple`,\n`.CredentialManager`, `.GPG`, `.Steam` and `.WebAuth`; a missing port or\nconfiguration ends that route. Use the route's real `Provider`; never cast another\nenum or default an unknown provider to `X`. A native Steam ticket stays alive\nuntil the link settles, then its owner releases it.\n\n## Conflicts\n\n| `AuthLinkProviderResult` | Handling |\n|---|---|\n| `ProviderOwnedByOther` | Another player owns it: ask for the app's account choice; never switch or merge automatically |\n| `ProviderTypeAlreadyExists` | This player already has that provider type: do not overwrite or retry |\n| `ProviderAlreadyConnected` | This pair is already linked: reconcile instead of repeating the link |\n| Anything else | Keep its meaning, the session and recovery data; it is not an ownership conflict |\n\nUsername linking has its own SDK contract and no recipe here; never send a password\nas `ProviderToken`.\n\n## Uncertain link\n\nAfter a cancellation once dispatched, an unknown result or an `IUntypedProblem`, the\nserver may already have linked. A timeout has no dedicated result or error code: it\nis one of these technical failures. Keep the guest credential and recovery data, and\ndo not retry blindly.\n\n- `GetProviderListAsync` is the provider catalog for the app and country, not this\n  player's links, so it cannot confirm the link. There is no linked-account read.\n- A fresh successful login for the same player (for example\n  `LoginWithAccessTokenAsync`) can confirm the exact `ProviderId` and\n  `ProviderUserId` in its `ProviderList`. Never reauthenticate or install a session\n  automatically; if the app chooses to, use its login flow and account guards. A\n  guest re-login may fail once the link has invalidated the guest token.\n- A different player, a match on provider type alone, a cached pre-link list or a\n  cancelled wait confirms nothing and authorizes neither a retry nor a deletion.\n  Report the gap when no confirmation is available.\n\n## Verify\n\n- [ ] No live session stops before provider UI; an account change during acquisition\n  aborts the link.\n- [ ] `LinkProviderAsync` receives the route's `Provider`, token and user ID; no\n  login, code exchange or `SetSession` runs.\n- [ ] The three conflicts stay distinct, with no automatic account switch.\n- [ ] An uncertain result keeps the guest credential and is not reconciled from\n  `GetProviderListAsync`; success retires only the matching player's guest credential.\n- [ ] Steam tickets are released only after the link settles, when that route is used.\n\n<!-- axyl-shared: core/installed-sdk.md -->\n\n## Installed SDK\n\n- Read `Packages/manifest.json`, the lock file and each resolved `package.json`.\n  All `com.com2usplatform.hiveaxyl.*` packages must be the same release (UPM does\n  not enforce this); `com.hive.axyl.*` packages are a release before 1.0.0-rc.9.\n- The installed SDK wins for APIs, types and contracts. If its release differs\n  from this skill's `sdk-version`, apply a step only after its fields, result\n  variants, platform support and failure behaviour match; otherwise stop at that\n  step and report what is missing. Never change the installed version.\n- The app owns UI, storage, the device key and orchestration. Sample and recipe\n  classes are not SDK APIs.\n\n<!-- /axyl-shared -->\n\n<!-- axyl-shared: core/results.md -->\n\n## SDK results\n\nMatch each call's concrete result and read `Success.Data` only after checking the\nfields the next step needs. The markers live in `Hive.Axyl.Contracts.Result`.\n\n| Result | Handling |\n|---|---|\n| `IUserCanceledOutcome` | The user dismissed OS or provider UI: stop this attempt quietly |\n| `IUnknownOutcome` | Keep `Code` and `RawJson`; never treat it as a known refusal or a success |\n| `IUntypedProblem` | Technical failure in `Problem` (`HiveError`): network, timeout or caller `Cancelled` |\n| Other typed variant | Only that call's documented meaning |\n\nPass `new ApiCallContext { Token = ct }` and check cancellation before each\nfollow-up call; a request cancelled in flight may still have taken effect. Never\nbranch on HTTP status, exception text or raw bodies, and never log tokens or\ncredentials.\n\n<!-- /axyl-shared -->\n\n<!-- axyl-shared: core/auth-account.md -->\n\n## Login metadata\n\n- `LoginResponseData` carries `PlayerId`, `CreatedAt`, `AuthorizationCode`,\n  `ProviderList` (`ProviderId`, `ProviderUserId`, `ProviderIndex`) and `IsBlock`.\n  Other responses carry only their own fields. Account creation\n  (`GuestCreateResponseData`, `UsernameCreateResponseData`) returns `PlayerId`,\n  `CreatedAt` and `AuthorizationCode` (guest also `GuestToken`), with no\n  `ProviderList` or `IsBlock`. Token refresh returns tokens only, so earlier\n  metadata stays stale and block status unknown. Never invent missing fields.\n- Return the supplied fields in an app-owned result tied to that player and\n  attempt; the SDK session keeps only tokens, player ID and expiry. The app decides\n  what to persist; authorization codes stay transient.\n- `ProviderList` lists the links at that response; it is not the provider catalog\n  from `GetProviderListAsync`. `Guest` appears only for a pure guest. An empty list\n  or a default value is not proof of no links.\n- `CreatedAt` is not a new-player flag. `IsBlock` is account status for the app to\n  handle, not a transport failure.\n\n<!-- /axyl-shared -->\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}