Appearance
API: https://github.com/snag-tcg/tcg-collector-backend/pull/489. Mobile: https://github.com/snag-tcg/poc-tcg-collector/pull/684 (issue 301). API merged into staging on 2026-10-16 after all six CI checks passed. Mobile merged into staging on 2026-10-17 after refreshed green CI and explicit approval for the internal Snag team rollout without waiting for iOS validation.
Contract
Existing user IDs and collection rows never change. The additive 0043 migration adds access expiry and hashed rotating refresh sessions. Access lasts fifteen minutes; refresh expires after ninety inactive days. The previous refresh hash has five minutes of retry grace. Suspension and credential/session revocation apply to private routes, claims and quotas.
Old installations still authenticate with their existing non-expiring legacy proof. New installations store that proof and their session in device-only Keychain/Keystore, never AsyncStorage. The retained proof recovers interrupted refreshes and long offline sessions for the same owner. This compatibility exception means backend issue 242 is not globally finished: retiring perpetual legacy credentials requires its own approved migration/recovery plan.
Native storage writes the inactive slot, verifies it, and then publishes a non-secret pointer. Slot revisions survive pointer loss on iOS. Storage failures never mean "new installation". Sign-out revokes the installation's server proof before removing both native copies; local holdings are untouched.
mermaid
sequenceDiagram
participant App
participant Secure as Keychain/Keystore
participant API
participant DB as PostgreSQL
App->>Secure: Verify legacy migration before plaintext removal
App->>API: Exchange existing legacy proof
API->>DB: Same owner, add session and expiring access
API-->>App: Access and refresh credentials
App->>Secure: Write inactive slot, verify, publish pointer
App->>API: Private call using access credential
Note over App,API: After expiry
App->>API: Rotate refresh (serialized)
API-->>App: New access and refresh credentials
App->>Secure: Persist before use
Note over App,DB: No collection migration or deletionEvidence And Limits
- API unit/handler suite: 1,167 passed; ingest: 553 passed; strict and JS-layer typecheck passed.
- Real PostgreSQL/API CI journey passed: existing holdings, old-client access, expiry, refresh, retry, long-offline recovery and revocation with exact holdings equality.
- Mobile tests cover verified migration, native replacement interruption, pointer loss, account adoption races, concurrent refresh, network failure, cross-owner response rejection and ledger preservation.
- Mobile: 2,516 tests passed, 11 pre-existing skips; lint and typecheck passed.
- Email claims retain the secure legacy proof. An invalid OTP is not retried as a session failure and cannot consume two verification attempts per tap.
- Android Keychain native Kotlin compilation passed on JDK 17. This does not prove a real installation upgrade or an iOS runtime flow.
- Maestro CLI is unavailable locally. The restart smoke was added but not run.
- No iOS native build/runtime validation on this Windows host.
- The original implementation performed no VPS deployment. On 2026-10-17, the approved rollout backed up users, credentials and collection tables, validated the backup archive, applied additive migration 0043 transactionally, and updated only the API container. Runtime environment, ports and mounts were preserved. The collection fingerprint was identical before/after migration; readiness and public session-route checks passed. A rollback API image remains.
Staging Rollout On 2026-10-17
- Annotated mobile tag
pre-pr-684preserves staging commit1a54876e6e6129f24be387b25c8375ae9a408a2bbefore the native storage change. It can be used as the source for an old-version Expo/EAS build; no such cloud build was triggered during this rollout. - Mobile merge:
a7619c45a767a74dc311813b68f133a2e2c4d2c7. - Conflicts were limited to the changelog; both histories were retained. Recent beta account header, catalogue logos and character sorting remain present.
- Local lint/typecheck and all 2592 mobile tests passed (11 existing skips). Refreshed PR CI passed. Fifty backend authentication/session-focused tests passed.
- A full stopped-emulator disk backup was taken before changing the installed app. Personal APKs, runtime credentials, QA fixtures and backups are not committed.
- iOS remains unverified and external beta distribution is not approved by these staging checks.
Native Android Upgrade Evidence
A temporary, uncommitted QA entry point was built against the merged staging sources and real native Keychain module. It was installed over the existing app with adb install -r; no uninstall or application-data reset occurred. Before calling the real account reader it retained the existing legacy owner's ID and an exact snapshot of non-account AsyncStorage values. It displayed only boolean results, never credentials. The initial QA request used an incorrect collection path and returned 404; the probe was corrected to /collection/items. This was a QA harness correction, not an application/API change.
Both the corrected launch and a subsequent force-stop/relaunch confirmed:
- An existing legacy account was present before migration.
- The owner was identical and the local ledger values were unchanged.
- The plaintext account entry was removed after verified native storage.
- The secure journal pointer was valid and credentials survived relaunch.
- An authenticated collection read returned 200.
- A forced renewal rotated the real refresh token successfully for that owner.
The temporary baseline was removed through the QA screen. The standard index.js bundle was then regenerated and the normal release APK rebuilt. Neither the QA screen, personal APKs nor backup files are tracked. This does not claim iOS, email-OTP, sign-out or long-offline native validation; those remain distinct tests.
Rollout
Back up PostgreSQL, apply only the reviewed additive migration, deploy the API, then distribute a native mobile rebuild. Keep the schema on API rollback. Upgraded clients retain credentials and fail closed if the session API disappears. Do not downgrade an upgraded phone to an old APK; old clients cannot read its native secure credential store. An anonymous account still needs a linked email to recover after loss of all device credentials. Do not promise anonymous recovery after uninstall/factory reset.