export type KeychainStartupState = { portable: boolean; appVersion: string; approvedVersion: string; accessGranted: boolean; promptDismissed: boolean; }; export type KeychainStartupDecision = { deferKeychainHydration: boolean; showKeychainPrompt: boolean; }; // The semantic app version can remain unchanged across release-candidate and // packaging rebuilds. Use the build identity so an updated binary cannot skip // Firelink's explanation and invoke the OS prompt directly. The policy epoch // remains only as a safe fallback for builds created outside the Git checkout. const KEYCHAIN_CONSENT_POLICY_VERSION = '2'; const buildId = typeof import.meta.env.VITE_BUILD_ID === 'string' ? import.meta.env.VITE_BUILD_ID.trim() : ''; export const getKeychainConsentVersion = (appVersion: string): string => { const normalizedVersion = appVersion.trim(); const consentIdentity = buildId && buildId !== 'unknown' ? `build-${buildId}` : `keychain-policy-${KEYCHAIN_CONSENT_POLICY_VERSION}`; return normalizedVersion ? `${normalizedVersion}|${consentIdentity}` : ''; }; export const getKeychainStartupDecision = ({ portable, appVersion, approvedVersion, accessGranted, promptDismissed }: KeychainStartupState): KeychainStartupDecision => { if (portable) { return { deferKeychainHydration: false, showKeychainPrompt: false }; } const versionKnown = Boolean(appVersion.trim()); const versionChanged = versionKnown && approvedVersion !== appVersion; const mustDeferAccess = !accessGranted || !versionKnown || versionChanged; return { deferKeychainHydration: mustDeferAccess, showKeychainPrompt: versionChanged || (!accessGranted && !promptDismissed) }; };