diff --git a/.cursor/rules/storebound.mdc b/.cursor/rules/storebound.mdc index 5b8121d..12a3401 100644 --- a/.cursor/rules/storebound.mdc +++ b/.cursor/rules/storebound.mdc @@ -68,6 +68,9 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify
`. Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. `storebound console fill` is the optional scripted fast path; it never blocks you. +11. **Console pages: always the absolute deep link.** When you send the owner to a console page, give the absolute + deep link (storebound prints it; `storebound console plan` has start URLs), never just "go to App Store Connect" + or "App Store Connect > App Information" alone. ## Tools diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7f734b8..68c0e72 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -68,6 +68,9 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. `storebound console fill` is the optional scripted fast path; it never blocks you. +11. **Console pages: always the absolute deep link.** When you send the owner to a console page, give the absolute + deep link (storebound prints it; `storebound console plan` has start URLs), never just "go to App Store Connect" + or "App Store Connect > App Information" alone. ## Tools diff --git a/AGENTS.md b/AGENTS.md index 9cea3a8..4920362 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,6 +69,9 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. `storebound console fill` is the optional scripted fast path; it never blocks you. +11. **Console pages: always the absolute deep link.** When you send the owner to a console page, give the absolute + deep link (storebound prints it; `storebound console plan` has start URLs), never just "go to App Store Connect" + or "App Store Connect > App Information" alone. ## Tools diff --git a/CHANGELOG.md b/CHANGELOG.md index a228836..b984278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- **Console pages as absolute deep links.** Every finding, fix hint, error and plan step that points to an App Store + Connect or Play Console page now prints the readable path plus the absolute URL of that page for the app + (`cli/lib/console-links.ts`; apple.appId, play.developerId and play.appId pick the app, the app list otherwise). + validate, scan, declarations and release plan JSON carry `url`, and the desktop shows it as an Open button. A test + fails on a plain console direction without a URL; AGENTS.md rule 11 and pitfall 71 tell agents the same. + ## 0.8.7 · 2026-10-15 - **First in-app purchases left out of a review are caught.** Apple reviews the first in-app purchase of a type (and diff --git a/GEMINI.md b/GEMINI.md index 6399c67..0f6362c 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -67,6 +67,9 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. `storebound console fill` is the optional scripted fast path; it never blocks you. +11. **Console pages: always the absolute deep link.** When you send the owner to a console page, give the absolute + deep link (storebound prints it; `storebound console plan` has start URLs), never just "go to App Store Connect" + or "App Store Connect > App Information" alone. ## Tools diff --git a/apps/desktop/src/dev/fixtures-more.ts b/apps/desktop/src/dev/fixtures-more.ts index 0bf6c3e..edecbec 100644 --- a/apps/desktop/src/dev/fixtures-more.ts +++ b/apps/desktop/src/dev/fixtures-more.ts @@ -83,7 +83,7 @@ export const DECLARATIONS: Record = { review: { signInRequired: false, accounts: false, contact: 's***@example.com', demoAccount: null, notes: 'Offline puzzle game, no sign-in. Remove Ads is restored from Settings > Restore purchases. The daily drop unlocks at midnight local time.', notesBytes: 148, notesMax: 4000 }, ask: [ { area: 'appInfo', id: 'ai-age', text: 'No age rating answers: storebound would answer every content question "none".', fix: 'Confirm that is true (ads, user-generated content, chat, loot boxes, web access count), or set apple.info.ageRating overrides.' }, - { area: 'appPrivacy', id: 'ap-linked', text: 'App Privacy asks per data type whether it is linked to the user\'s identity and whether it is used for tracking; the config cannot prove either.', fix: 'Answer both in App Store Connect > App Privacy for each type below (tracking also needs App Tracking Transparency in the app).' }, + { area: 'appPrivacy', id: 'ap-linked', text: 'App Privacy asks per data type whether it is linked to the user\'s identity and whether it is used for tracking; the config cannot prove either.', fix: 'Answer both in App Store Connect > App Privacy: https://appstoreconnect.apple.com/apps/1000000001/distribution/privacy for each type below (tracking also needs App Tracking Transparency in the app).', url: 'https://appstoreconnect.apple.com/apps/1000000001/distribution/privacy' }, ], }, 'p-tidy': { diff --git a/apps/desktop/src/dev/fixtures.ts b/apps/desktop/src/dev/fixtures.ts index acc6a3c..ecf2f05 100644 --- a/apps/desktop/src/dev/fixtures.ts +++ b/apps/desktop/src/dev/fixtures.ts @@ -92,7 +92,7 @@ let overrides: Overrides = { }; if (import.meta.env.VITE_FIXTURES === 'empty') overrides = { pairs: [], unpairs: [], links: [], appStudio: [], ignored: [] }; let settings: Settings = { ...DEFAULT_SETTINGS, refresh: { ...DEFAULT_SETTINGS.refresh, autoMinutes: 60 } }; -const PLAY_INVITE = (who: string) => `Play Console > Users and permissions > Invite new users: add ${who}. Account permissions: "View app information and download bulk reports (read-only)"; per app, as needed: "Manage store presence", "Manage testing tracks", "Release to production, exclude devices and use Play App Signing" (rollouts), "Reply to reviews", "Manage orders and subscriptions". One service account can be invited to several developer accounts.`; +const PLAY_INVITE = (who: string) => `Play Console > Users and permissions: https://play.google.com/console > Invite new users: add ${who}. Account permissions: "View app information and download bulk reports (read-only)"; per app, as needed: "Manage store presence", "Manage testing tracks", "Release to production, exclude devices and use Play App Signing" (rollouts), "Reply to reviews", "Manage orders and subscriptions". One service account can be invited to several developer accounts.`; const CHECKS: Record = { [P1]: { store: 'android', status: 'ok', principal: 'play-bot@example.com', apps: 8, message: 'The service account can see 8 apps.', fix: null, docs: null }, [P2]: { store: 'android', status: 'permission', principal: 'sample-labs-bot@example.com', apps: 0, message: 'The key works, but no developer account has invited this service account (it sees no apps).', fix: PLAY_INVITE('sample-labs-bot@example.com'), docs: 'https://play.google.com/console/developers/users-and-permissions' }, @@ -344,7 +344,7 @@ const VALIDATE: Record = { { store: 'apple', level: 'warn', code: 'screenshots-ipad', msg: 'ar-SA has no 13" iPad screenshots', fix: 'storebound shots render --lang ar, then storebound apple screenshots --only ar-SA' }, { store: 'apple', level: 'warn', code: 'whats-new', msg: '2.4.0 has no "What\'s New" for ja', fix: 'storebound whats-new --since v2.3.0' }, // a long unbreakable path (a merged manifest of a local build) must wrap inside the card - { store: 'play', level: 'warn', code: 'scan', msg: 'Play: Foreground service types: FOREGROUND_SERVICE is requested but no declares android:foregroundServiceType (apps/native/android/app/build/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml:74)', fix: 'Give each foreground service a type and its FOREGROUND_SERVICE_ permission.' }, + { store: 'play', level: 'warn', code: 'scan', msg: 'Play: Foreground service types: FOREGROUND_SERVICE is requested but no declares android:foregroundServiceType (apps/native/android/app/build/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml:74)', fix: 'Give each foreground service a type and its FOREGROUND_SERVICE_ permission, then fill Play Console > App content > Foreground service permissions: https://play.google.com/console/u/0/developers/5550000000000000001/app/4970000000000000002/app-content/foreground-services', url: 'https://play.google.com/console/u/0/developers/5550000000000000001/app/4970000000000000002/app-content/foreground-services' }, ], }, 'p-tidy': { blockers: 0, findings: [] }, diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index 64283b9..730adf0 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -1470,6 +1470,8 @@ export const en = { 'cp.failed': 'Could not make the plan for {form}', 'cp.agentFirst': 'Agent-first: your AI agent drives the page with this plan; storebound never clicks the final button', 'cp.openPage': 'Open the page', + 'link.open': 'Open', + 'link.openFailed': 'Could not open the page', 'cp.openFailed': 'Could not open the page', 'cp.steps': '{count} steps', 'cp.steps_one': '1 step', diff --git a/apps/desktop/src/i18n/tr.ts b/apps/desktop/src/i18n/tr.ts index fd814f4..aca173a 100644 --- a/apps/desktop/src/i18n/tr.ts +++ b/apps/desktop/src/i18n/tr.ts @@ -1472,6 +1472,8 @@ export const tr: Record = { 'cp.failed': '{form} için plan oluşturulamadı', 'cp.agentFirst': 'Önce ajan: yapay zekâ aracınız sayfayı bu planla yönetir; storebound son düğmeye asla basmaz', 'cp.openPage': 'Sayfayı aç', + 'link.open': 'Aç', + 'link.openFailed': 'Sayfa açılamadı', 'cp.openFailed': 'Sayfa açılamadı', 'cp.steps': '{count} adım', 'cp.steps_one': '1 adım', diff --git a/apps/desktop/src/lib/model.ts b/apps/desktop/src/lib/model.ts index 6efd7ef..e1979c0 100644 --- a/apps/desktop/src/lib/model.ts +++ b/apps/desktop/src/lib/model.ts @@ -41,13 +41,13 @@ export interface ParityRow { id: string; region: string; currency: string; apple export interface CatalogDiff { errors: number; rows: CatalogRow[]; parity: ParityRow[] } /** A finding's machine-readable fix (cli/lib/fix-actions.ts): the write the Fix button runs (dry run, dialog, apply). */ export interface FixAction { method: string; args: Record } -export interface Finding { store: 'apple' | 'play'; level: 'blocker' | 'warn' | 'ok'; code: string; msg: string; fix?: string; action?: FixAction } +export interface Finding { store: 'apple' | 'play'; level: 'blocker' | 'warn' | 'ok'; code: string; msg: string; fix?: string; url?: string; action?: FixAction } export interface ValidateResult { blockers: number; findings: Finding[] } /** `storebound scan --json` (offline rejection-risk scan of the project files), the parts the Checks tab shows. */ export type ScanSeverity = 'blocker' | 'high' | 'medium' | 'low'; export interface ScanFinding { rule: string; guideline: string; store: 'apple' | 'play'; severity: ScanSeverity; title: string; - evidence: { file: string; line: number; text?: string }[]; fix: string; skill: string; ref: string; accepted?: string; action?: FixAction; aiFix?: boolean; + evidence: { file: string; line: number; text?: string }[]; fix: string; skill: string; ref: string; url?: string; accepted?: string; action?: FixAction; aiFix?: boolean; } export interface ScanResult { stacks: string[]; diff --git a/apps/desktop/src/screens/tabs/AppInfo.tsx b/apps/desktop/src/screens/tabs/AppInfo.tsx index f97ea67..dc3f73e 100644 --- a/apps/desktop/src/screens/tabs/AppInfo.tsx +++ b/apps/desktop/src/screens/tabs/AppInfo.tsx @@ -4,6 +4,7 @@ // (no API: the answers to enter in App Store Connect), app info, export compliance and the App Review details. // The forms without an API (App Privacy, age rating, the Play questionnaires, a missing App Store app record) have a // "Fill in console" button (ui/ConsoleFill.tsx): storebound fills them in a visible browser, the owner saves. +import { OpenLink } from '../../ui/OpenLink'; import { useState } from 'react'; import { api, type ConsoleForm, type DeclarationsResult, type DeclAsk, type Project } from '../../api'; import { type Key, t } from '../../i18n'; @@ -38,6 +39,7 @@ function AskCard({ ask }: { ask: DeclAsk[] }) {
{a.text}{a.fix}
+ {t(AREA[a.area])}
))} diff --git a/apps/desktop/src/screens/tabs/Checks.tsx b/apps/desktop/src/screens/tabs/Checks.tsx index 80177ea..903880d 100644 --- a/apps/desktop/src/screens/tabs/Checks.tsx +++ b/apps/desktop/src/screens/tabs/Checks.tsx @@ -10,6 +10,7 @@ import { AiFix, aiFinding } from '../../ui/AiFix'; import { api } from '../../api'; import { useEffect, useState } from 'react'; import { Icon } from '../../ui/icons'; +import { OpenLink } from '../../ui/OpenLink'; import { Card, Chip, Empty, ErrorNotice, ReadControl, Skeleton } from '../../ui/kit'; import { exportName, validateMd } from '../../lib/exports'; import { useExports } from '../../ui/Exports'; @@ -70,6 +71,7 @@ function ScanCard({ project }: { project: Project }) { {ev.slice(0, 2).map((e, j) => {e.file}:{e.line}{e.text ? ` ${e.text}` : ''})} {f.accepted ? {t('checks.accepted', { reason: f.accepted })} : {f.fix} {f.skill}} + {!f.accepted && } {!f.accepted && f.aiFix && !fixable(f.action) && { s.refresh(); }} stillThere={findings.some(x => same(x, f))} onActive={on => setAiActive(on ? f : null)} />} @@ -131,6 +133,7 @@ export function ChecksTab({ project }: { project: Project }) { {f.msg} {f.fix && {f.fix}} + {f.level !== 'ok' && fixable(f.action) && } {f.store === 'apple' ? 'App Store' : 'Google Play'} diff --git a/apps/desktop/src/ui/OpenLink.tsx b/apps/desktop/src/ui/OpenLink.tsx new file mode 100644 index 0000000..d6384eb --- /dev/null +++ b/apps/desktop/src/ui/OpenLink.tsx @@ -0,0 +1,17 @@ +// The "Open" button for a finding, notice or ask that carries an absolute console deep link (`url` from the CLI's +// console-links module, pitfall 71): opens it in the browser through the open-external command. +import { api, errorText } from '../api'; +import { t } from '../i18n'; +import { useData } from '../store'; +import { Icon } from './icons'; + +export function OpenLink({ url }: { url?: string | null }) { + const d = useData(); + if (!url || !/^https:\/\//.test(url)) return null; + return ( + + ); +} diff --git a/cli/analytics.ts b/cli/analytics.ts index be3adb4..f6f3f83 100644 --- a/cli/analytics.ts +++ b/cli/analytics.ts @@ -8,6 +8,7 @@ // Download reports > Statistics > Copy Cloud Storage URI) are read with the service account (store listing visitors, // acquisitions, daily user installs). compare lines the snapshots up with the git history of the store files (listing // text, screenshots, config): the days before vs after each change. Read-only on both stores except `setup`. +import { consoleLink } from './lib/console-links'; import { storeReadError } from './lib/google-errors'; import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; @@ -71,7 +72,7 @@ async function appleSnapshot(ctx: Ctx, start: string, end: string): Promise<{ da async function playSnapshot(ctx: Ctx, start: string, end: string): Promise<{ daily: PlayDay[] }> { const { config, root } = ctx.loaded; const bucket = config.play?.reportsBucket; - if (!bucket) throw new Error('play.reportsBucket is not set (Play Console > Download reports > Statistics > Copy Cloud Storage URI)'); + if (!bucket) throw new Error(`play.reportsBucket is not set (${consoleLink('play.reports-statistics', '> Copy Cloud Storage URI')})`); const g = googleClient(root, STORAGE_READ_SCOPE); const sp: Record[] = [], inst: Record[] = []; for (const month of monthsBetween(start, end)) { diff --git a/cli/apple/bundle.ts b/cli/apple/bundle.ts index 60c1d54..50c828a 100644 --- a/cli/apple/bundle.ts +++ b/cli/apple/bundle.ts @@ -2,6 +2,7 @@ // storebound apple bundle [--name "My App"] [--dry] // The App Store Connect app record itself can NOT be created through the API: do that in the web UI afterwards // (Apps > + > New App, pick this bundle id), see the appstore-first-release skill. +import { consoleLink } from '../lib/console-links'; import type { Ctx } from '../lib/ctx'; import { need } from '../lib/ctx'; import { done, log } from '../lib/log'; @@ -25,6 +26,6 @@ export async function run(ctx: Ctx): Promise { } const apps = (await c.asc('GET', `/v1/apps?filter[bundleId]=${encodeURIComponent(bundleId)}`)).data; if (apps.length) log('apple', `app record: ${apps[0].attributes.name} (Apple ID ${apps[0].id}); put it in apple.appId`); - else log('apple', 'no app record yet: App Store Connect > Apps > + > New App (UI only), then re-run `apple status`'); + else log('apple', `no app record yet: ${consoleLink('asc.new-app')} (UI only), then re-run \`apple status\``); done('apple', 'bundle done'); } diff --git a/cli/apple/encryption.ts b/cli/apple/encryption.ts index b7708ad..32f037d 100644 --- a/cli/apple/encryption.ts +++ b/cli/apple/encryption.ts @@ -7,6 +7,7 @@ // with documents where required). This command lists the declarations and links the approved one to the builds; it // never creates a declaration or claims an exemption for you (export-compliance skill). // Not set: refuses. storebound never guesses this answer. +import { consoleLink } from '../lib/console-links'; import type { Ctx } from '../lib/ctx'; import { need } from '../lib/ctx'; import { done, log, warn } from '../lib/log'; @@ -39,7 +40,7 @@ export async function run(ctx: Ctx): Promise { for (const d of decls) log('apple', `declaration ${d.id}: ${d.attributes.appEncryptionDeclarationState} exempt=${d.attributes.exempt} code=${d.attributes.codeValue ?? '-'}`); const approved = decls.find(d => d.attributes.appEncryptionDeclarationState === 'APPROVED'); if (!approved) { - warn('apple', 'no APPROVED encryption declaration: create it in App Store Connect > App Information > App Encryption Documentation (export-compliance skill), then re-run'); + warn('apple', `no APPROVED encryption declaration: create it in ${consoleLink('asc.encryption')} (export-compliance skill), then re-run`); process.exitCode = 1; return; } diff --git a/cli/apple/ppo.ts b/cli/apple/ppo.ts index ec45baa..b184650 100644 --- a/cli/apple/ppo.ts +++ b/cli/apple/ppo.ts @@ -9,6 +9,7 @@ // screenshot sets, MD5-idempotent like `apple screenshots`. start / stop change what users see (stop is final, a test // cannot be restarted) and ask first; they are not MCP tools. Google Play store listing experiments have no API: they // stay in Play Console (store-screenshots skill). Pure planners: cli/lib/ppo.ts. +import { consoleLink } from '../lib/console-links'; import { existsSync } from 'node:fs'; import { join } from 'node:path'; import type { Ctx } from '../lib/ctx'; @@ -66,7 +67,7 @@ export async function run(ctx: Ctx): Promise { log(TAG, `"${x.name}" treatment "${t.attributes.name}": ${locs.length} locale(s), ${shots} screenshot(s)`); } } - log(TAG, 'results (conversion per treatment) are in App Store Connect > Product Page Optimization; no API returns them'); + log(TAG, `results (conversion per treatment) are in ${consoleLink('asc.ppo')}; no API returns them`); return; } diff --git a/cli/apple/status.ts b/cli/apple/status.ts index fc25d57..c4808c1 100644 --- a/cli/apple/status.ts +++ b/cli/apple/status.ts @@ -11,6 +11,7 @@ // exit codes: 0 approved, 1 rejected, 7 timeout (2 = nothing in review) // Also compares the App ID's capabilities, apple.signing.capabilities and the iOS project's entitlements and warns, // naming what is missing where. +import { consoleLink } from '../lib/console-links'; import type { Ctx } from '../lib/ctx'; import { confirm, need } from '../lib/ctx'; import { log, printJson, warn } from '../lib/log'; @@ -37,7 +38,7 @@ async function waitReview(ctx: Ctx, c: Asc, appId: string): Promise { const outcome = classifyReviewState(st); if (st !== last) { log('apple', `${v.attributes.versionString}: ${st}`); last = st; } if (outcome === 'approved') { log('apple', 'approved (release it yourself, or it goes out automatically if the release type says so)'); process.exit(WAIT_EXIT.approved); } - if (outcome === 'rejected') { log('apple', 'rejected: read the message in App Store Connect > App Review, then the review-rejections skill'); process.exit(WAIT_EXIT.rejected); } + if (outcome === 'rejected') { log('apple', `rejected: read the message in ${consoleLink('asc.app-review')}, then the review-rejections skill`); process.exit(WAIT_EXIT.rejected); } if (outcome === 'not-submitted') { log('apple', 'nothing is waiting for review'); process.exit(2); } delay = nextPollDelay(delay); if (Date.now() + delay > deadline) { log('apple', 'timeout, still in review'); process.exit(WAIT_EXIT.timeout); } diff --git a/cli/lib/aso-audit.ts b/cli/lib/aso-audit.ts index c64a6dc..edd7cd3 100644 --- a/cli/lib/aso-audit.ts +++ b/cli/lib/aso-audit.ts @@ -5,6 +5,7 @@ // the App Store analytics snapshots (.storebound/analytics, impressions / downloads / conversion) and the screenshot // variants of the config. Output: the report, experiment ideas (suggestions only: nothing is started) and the next 3 // concrete actions. No network, no writes. +import { consoleLink } from './console-links'; import type { AppleDay } from './analytics'; import type { AsoFinding } from './aso'; import { words } from './aso'; @@ -155,7 +156,7 @@ export function experimentIdeas(a: AsoAudit, i: AuditInput): Experiment[] { for (const p of a.play.slice(0, 1)) { out.push({ store: 'play', what: `short description variant (${p.lang})`, why: p.shortRoom > 30 ? `the short description uses ${p.shortChars}/80 characters` : !p.titleWordsInShort ? 'the short description shares no word with the title' : 'the short description is the first text on the listing', - how: 'Play Console > Grow users > Store listing experiments (no API: storebound only prepares the text; the store-screenshots skill has the steps)' }); + how: `${consoleLink('play.listing-experiments')} (no API: storebound only prepares the text; the store-screenshots skill has the steps)` }); if (i.variants.length) out.push({ store: 'play', what: `screenshot variant "${i.variants[0]}"`, why: 'graphics experiments need no new version', how: `storebound shots render --variant ${i.variants[0]} --size play, then upload it as the experiment's variant in Play Console` }); } diff --git a/cli/lib/config.ts b/cli/lib/config.ts index 5107a4c..5c5536f 100644 --- a/cli/lib/config.ts +++ b/cli/lib/config.ts @@ -1,5 +1,6 @@ // Per-project config (storebound.config.ts at the project root). Everything app-specific lives here; credentials never // do (see credentials.ts). The file default-exports a plain object; `import type { ShipConfig }` is optional. +import { consoleIdsFromConfig, consoleLink, setConsoleIds } from './console-links'; import { existsSync } from 'node:fs'; import { dirname, isAbsolute, join, resolve } from 'node:path'; import * as rt from './rt'; @@ -1074,7 +1075,7 @@ export function validateConfig(c: unknown): string[] { e.push('play.video: a plain YouTube video URL (https://www.youtube.com/watch?v=... or https://youtu.be/...), no playlist, channel or extra parameters'); e.push(...validatePlayBuild(cfg.play?.build), ...validateOriginality(cfg.originality)); if (cfg.play?.reportsBucket && !/^(gs:\/\/)?pubsite_prod(_rev)?_\d+\/?$/.test(cfg.play.reportsBucket)) - e.push('play.reportsBucket: the Play reports bucket, e.g. "pubsite_prod_rev_" (Play Console > Download reports > Copy Cloud Storage URI)'); + e.push(`play.reportsBucket: the Play reports bucket, e.g. "pubsite_prod_rev_" (${consoleLink('play.reports-statistics', '> Copy Cloud Storage URI')})`); if (cfg.play?.targetAges !== undefined && !(Array.isArray(cfg.play.targetAges) && cfg.play.targetAges.length && cfg.play.targetAges.every(a => PLAY_AGE_GROUPS.includes(a)))) e.push(`play.targetAges: a list of ${PLAY_AGE_GROUPS.join(' | ')}`); if (cfg.play?.category !== undefined) { @@ -1096,7 +1097,7 @@ export function validateConfig(c: unknown): string[] { const an = cfg.apple.name; if (an !== undefined && (typeof an !== 'string' || !an.trim())) e.push('apple.name: the App Store name (text), or leave it out to use app.name'); else if (an !== undefined && [...an].length > APPLE_NAME_MAX) e.push(`apple.name: ${[...an].length} characters > ${APPLE_NAME_MAX} (App Store limit)`); - if (cfg.apple.appId && !/^\d+$/.test(cfg.apple.appId)) e.push('apple.appId: numeric Apple ID of the app (App Store Connect > App Information)'); + if (cfg.apple.appId && !/^\d+$/.test(cfg.apple.appId)) e.push(`apple.appId: numeric Apple ID of the app (${consoleLink('asc.app-info')})`); const enc = cfg.apple.usesNonExemptEncryption; if (enc !== undefined && typeof enc !== 'boolean') e.push('apple.usesNonExemptEncryption: true | false'); if (cfg.apple.screenshotTypes && !cfg.apple.screenshotTypes.every(t => /^APP_[A-Z0-9_]+$/.test(t))) e.push('apple.screenshotTypes: display types like APP_IPHONE_67'); @@ -1230,6 +1231,7 @@ export async function loadConfig(explicit?: string, start = process.cwd()): Prom if (errors.length) throw new Error(`${path}:\n ` + errors.join('\n ')); const root = dirname(path); setConfigAccount(root, config.account); + setConsoleIds(consoleIdsFromConfig(config)); return { config, root, path, resolve: (p: string) => (isAbsolute(p) ? p : join(root, p)) }; } diff --git a/cli/lib/console-answers.ts b/cli/lib/console-answers.ts index cbb97cb..b670d68 100644 --- a/cli/lib/console-answers.ts +++ b/cli/lib/console-answers.ts @@ -15,6 +15,7 @@ // https://support.google.com/googleplay/android-developer/answer/9859152 (create and set up your app) // https://support.google.com/googleplay/android-developer/answer/9859655 (content ratings) // https://support.google.com/googleplay/android-developer/answer/9867159 (target audience and content) +import { consoleLink } from './console-links'; import { AGE_RATING_NONE } from '../apple/appinfo'; import { type AgeGroup, AGE_GROUPS, appleName, IARC_DETAIL_KEYS, IARC_DETAIL_SECTIONS, IARC_QUESTIONS, type IarcDetailSection, type IarcQuestion, profileOf, type ShipConfig } from './config'; import { APPLE_PRIVACY, applePurpose } from './declarations'; @@ -91,11 +92,11 @@ export function formMeta(id: FormId, config: ShipConfig): FormMeta { 'The Developer Program Policies and US export laws boxes are agreements: storebound never ticks them. Read and tick them yourself, then press Create app.', ] }; case 'play-content-rating': return { id, store: 'play', title: 'Play Console: Content rating (IARC)', url: PLAY, stopBefore: 'Send changes for review', fillable: true, - open: [pickApp, 'Policy > App content > Content ratings > Start (app-content/content-rating-overview > Start questionnaire)'], + open: [pickApp, `${consoleLink('play.content-rating', '> Start questionnaire')}`], notes: ['The IARC Terms of Use box on page 1 is an agreement: storebound never ticks it. Tick it before the run (page 1, under the email address and the category): Next does nothing until it is ticked (verified 2026-10-13).', 'The Summary\'s button is "Save", not "Submit" (verified 2026-10-12). storebound presses it: it stores the rating declaration, which Google gets only with the next "Send changes for review" in Publishing overview (yours).'] }; case 'play-target-audience': return { id, store: 'play', title: 'Play Console: Target audience and content', url: PLAY, stopBefore: 'Send changes for review', fillable: true, - open: [pickApp, 'Policy > App content > Target audience and content > Start (or Manage); the Ads declaration is App content > Ads'], + open: [pickApp, `${consoleLink('play.target-audience', '> Start (or Manage)')}; the Ads declaration is ${consoleLink('play.ads')}`], notes: ['storebound presses the last page\'s Save: it stores the declaration, which Google gets only with the next "Send changes for review" in Publishing overview (yours).'] }; } } diff --git a/cli/lib/console-forms.ts b/cli/lib/console-forms.ts index 9b6eb6b..4f61e98 100644 --- a/cli/lib/console-forms.ts +++ b/cli/lib/console-forms.ts @@ -6,6 +6,7 @@ // The names are modeled on Apple's and Google's public help pages (sources in console-answers.ts), not recorded from a // signed-in console: the exact App Privacy dialog titles, the IARC question wording and the Play category names could // not be verified without an account, which is why every field has candidates and the engine falls back on a miss. +import { consoleLink } from './console-links'; import { AGE_GROUPS, appleName, IARC_DETAIL_SECTIONS, type ShipConfig } from './config'; import { AGE_LABELS, type Answer, APPLE_PURPOSES, type FormId, iarcSectionName } from './console-answers'; @@ -483,7 +484,7 @@ export const FORM_BUTTONS: Record = { { button: 'Create app (form)', class: 'final-submit', where: 'the Create app form: creates the app', auto: false }, ], 'play-content-rating': [ - { button: 'Start / Start questionnaire', class: 'navigation', where: 'App content > Content ratings (content-rating-overview): opens the questionnaire', auto: true }, + { button: 'Start / Start questionnaire', class: 'navigation', where: `${consoleLink('play.content-rating')}: opens the questionnaire`, auto: true }, { button: 'Next', class: 'navigation', where: 'after the category, and from the questions to the Summary', auto: true }, { button: 'Save draft / Save (questionnaire page)', class: 'draft-save', where: 'questionnaire: stores the answers as the one draft of the app; nothing goes to IARC before the Summary\'s Save', auto: true }, { button: 'IARC Terms of Use box', class: 'final-submit', where: 'page 1: an agreement, never ticked by the agent or storebound (the owner ticks it before the run: Next does nothing until it is ticked, verified 2026-10-13)', auto: false }, diff --git a/cli/lib/console-links.ts b/cli/lib/console-links.ts new file mode 100644 index 0000000..748874c --- /dev/null +++ b/cli/lib/console-links.ts @@ -0,0 +1,116 @@ +// Absolute deep links to App Store Connect and Play Console pages. Every message that sends the owner (or an agent) to a +// console page goes through here: "App Information > Age Rating: https://appstoreconnect.apple.com/apps//...", +// never plain directions alone (pitfall 71). With unknown ids the link falls back to the best page that exists (the +// app list, the developer chooser), so a message always carries an absolute https URL. +// +// Ids: apple.appId, play.developerId and play.appId from the config (loadConfig calls setConsoleIds), or explicit ids. +// URL shapes reused from console-forms / console-plan (app-dashboard, app-content/*, create-new-app, app-list) are +// verified; the others follow the consoles' current routes (see UNVERIFIED in the tests' page list). + +export const ASC = 'https://appstoreconnect.apple.com'; +export const PLAY_CONSOLE = 'https://play.google.com/console'; + +export interface ConsoleIds { appleAppId?: string | null; playDeveloperId?: string | null; playAppId?: string | null } + +let current: ConsoleIds = {}; +/** Remember the loaded config's ids so every later message deep-links to this app. */ +export function setConsoleIds(ids: ConsoleIds): void { current = { ...ids }; } +export function consoleIdsFromConfig(c: { apple?: { appId?: string }; play?: { developerId?: string; appId?: string } } | null | undefined): ConsoleIds { + return { appleAppId: c?.apple?.appId, playDeveloperId: c?.play?.developerId, playAppId: c?.play?.appId }; +} + +type AppleScope = 'app' | 'account'; +type PlayScope = 'app' | 'developer'; +interface ApplePage { store: 'apple'; label: string; scope: AppleScope; path: string } +interface PlayPage { store: 'play'; label: string; scope: PlayScope; path: string } + +/** Every console page storebound points to: readable path + URL path under the app (or account). */ +export const CONSOLE_PAGES = { + // App Store Connect, per app (/apps//...) + 'asc.apps': { store: 'apple', label: 'App Store Connect > Apps', scope: 'account', path: '/apps' }, + 'asc.new-app': { store: 'apple', label: 'App Store Connect > Apps > + > New App', scope: 'account', path: '/apps' }, + 'asc.app-info': { store: 'apple', label: 'App Store Connect > App Information', scope: 'app', path: '/distribution/info' }, + 'asc.age-rating': { store: 'apple', label: 'App Store Connect > App Information > Age Rating', scope: 'app', path: '/distribution/info' }, + 'asc.encryption': { store: 'apple', label: 'App Store Connect > App Information > App Encryption Documentation', scope: 'app', path: '/distribution/info' }, + 'asc.app-privacy': { store: 'apple', label: 'App Store Connect > App Privacy', scope: 'app', path: '/distribution/privacy' }, + 'asc.pricing': { store: 'apple', label: 'App Store Connect > Pricing and Availability', scope: 'app', path: '/distribution/pricing' }, + 'asc.version': { store: 'apple', label: 'App Store Connect > the iOS version page', scope: 'app', path: '/distribution/ios/version/inflight' }, + 'asc.app-review': { store: 'apple', label: 'App Store Connect > App Review', scope: 'app', path: '/distribution/reviewsubmissions' }, + 'asc.testflight': { store: 'apple', label: 'App Store Connect > TestFlight', scope: 'app', path: '/testflight/ios' }, + 'asc.iaps': { store: 'apple', label: 'App Store Connect > In-App Purchases', scope: 'app', path: '/distribution/iaps' }, + 'asc.subscriptions': { store: 'apple', label: 'App Store Connect > Subscriptions', scope: 'app', path: '/distribution/subscriptions' }, + 'asc.ppo': { store: 'apple', label: 'App Store Connect > Product Page Optimization', scope: 'app', path: '/distribution/product-page-optimization' }, + // App Store Connect, account + 'asc.payments': { store: 'apple', label: 'App Store Connect > Payments and Financial Reports', scope: 'account', path: '/itc/payments_and_financial_reports' }, + 'asc.users': { store: 'apple', label: 'App Store Connect > Users and Access', scope: 'account', path: '/access/users' }, + 'asc.keys': { store: 'apple', label: 'App Store Connect > Users and Access > Integrations > App Store Connect API', scope: 'account', path: '/access/integrations/api' }, + // Play Console, per app (/u/0/developers//app//...) + 'play.dashboard': { store: 'play', label: 'Play Console > Dashboard', scope: 'app', path: 'app-dashboard' }, + 'play.app-content': { store: 'play', label: 'Play Console > Policy and programs > App content', scope: 'app', path: 'app-content/overview' }, + 'play.data-safety': { store: 'play', label: 'Play Console > App content > Data safety', scope: 'app', path: 'app-content/data-privacy-security' }, + 'play.content-rating': { store: 'play', label: 'Play Console > App content > Content ratings', scope: 'app', path: 'app-content/content-rating-overview' }, + 'play.target-audience': { store: 'play', label: 'Play Console > App content > Target audience and content', scope: 'app', path: 'app-content/target-audience-content' }, + 'play.ads': { store: 'play', label: 'Play Console > App content > Ads', scope: 'app', path: 'app-content/ads-declaration' }, + 'play.app-access': { store: 'play', label: 'Play Console > App content > App access', scope: 'app', path: 'app-content/testing-credentials' }, + 'play.foreground-service': { store: 'play', label: 'Play Console > App content > Foreground service permissions', scope: 'app', path: 'app-content/foreground-services' }, + 'play.sensitive-permissions': { store: 'play', label: 'Play Console > App content > Sensitive app permissions', scope: 'app', path: 'app-content/overview' }, + 'play.health-apps': { store: 'play', label: 'Play Console > App content > Health apps', scope: 'app', path: 'app-content/overview' }, + 'play.full-screen-intent': { store: 'play', label: 'Play Console > App content > Full-screen intent', scope: 'app', path: 'app-content/overview' }, + 'play.store-listing': { store: 'play', label: 'Play Console > Store presence > Main store listing', scope: 'app', path: 'main-store-listing' }, + 'play.store-settings': { store: 'play', label: 'Play Console > Store presence > Store settings', scope: 'app', path: 'store-settings' }, + 'play.listing-experiments': { store: 'play', label: 'Play Console > Grow users > Store listing experiments', scope: 'app', path: 'store-listing-experiments' }, + 'play.releases': { store: 'play', label: 'Play Console > Test and release', scope: 'app', path: 'releases/overview' }, + 'play.production': { store: 'play', label: 'Play Console > Test and release > Production', scope: 'app', path: 'tracks/production' }, + 'play.internal-testing': { store: 'play', label: 'Play Console > Test and release > Internal testing', scope: 'app', path: 'tracks/internal-testing' }, + 'play.publishing': { store: 'play', label: 'Play Console > Publishing overview', scope: 'app', path: 'publishing' }, + 'play.app-signing': { store: 'play', label: 'Play Console > Test and release > App integrity > Play app signing', scope: 'app', path: 'keymanagement' }, + 'play.bundle-explorer': { store: 'play', label: 'Play Console > Test and release > App bundle explorer', scope: 'app', path: 'bundle-explorer-selector' }, + 'play.products': { store: 'play', label: 'Play Console > Monetize with Play > Products > In-app products', scope: 'app', path: 'managed-products' }, + 'play.subscriptions': { store: 'play', label: 'Play Console > Monetize with Play > Products > Subscriptions', scope: 'app', path: 'subscriptions' }, + 'play.promo-codes': { store: 'play', label: 'Play Console > Monetize with Play > Promo codes', scope: 'app', path: 'promotions' }, + 'play.vitals': { store: 'play', label: 'Play Console > Monitor and improve > Android vitals', scope: 'app', path: 'vitals/crashes' }, + 'play.policy-status': { store: 'play', label: 'Play Console > Policy and programs > Policy status', scope: 'app', path: 'policy-center/issues' }, + 'play.pre-registration': { store: 'play', label: 'Play Console > Test and release > Production > Countries / regions (pre-registration)', scope: 'app', path: 'tracks/production' }, + // Play Console, developer account (/u/0/developers//...) + 'play.app-list': { store: 'play', label: 'Play Console > Home (All apps)', scope: 'developer', path: 'app-list' }, + 'play.create-app': { store: 'play', label: 'Play Console > Home > Create app', scope: 'developer', path: 'create-new-app' }, + 'play.reports-statistics': { store: 'play', label: 'Play Console > Download reports > Statistics', scope: 'developer', path: 'download-reports/statistics' }, + 'play.reports-financial': { store: 'play', label: 'Play Console > Download reports > Financial', scope: 'developer', path: 'download-reports/financial' }, + 'play.users': { store: 'play', label: 'Play Console > Users and permissions', scope: 'developer', path: 'users-and-permissions' }, + 'play.api-access': { store: 'play', label: 'Play Console > Setup > API access', scope: 'developer', path: 'api-access' }, + 'play.internal-sharing': { store: 'play', label: 'Play Console > Internal app sharing', scope: 'developer', path: 'internal-app-sharing' }, +} as const satisfies Record; + +export type ConsolePage = keyof typeof CONSOLE_PAGES; + +/** The absolute URL of a console page. Unknown ids: the best page that exists (app list / developer chooser). */ +export function consoleUrl(page: ConsolePage, ids: ConsoleIds = current): string { + const p: ApplePage | PlayPage = CONSOLE_PAGES[page]; + const id = (v: string | null | undefined) => v && /^\d+$/.test(v) ? v : null; + if (p.store === 'apple') { + if (p.scope === 'account') return `${ASC}${p.path}`; + const app = id(ids.appleAppId ?? current.appleAppId); + return app ? `${ASC}/apps/${app}${p.path}` : `${ASC}/apps`; + } + const dev = id(ids.playDeveloperId ?? current.playDeveloperId), app = id(ids.playAppId ?? current.playAppId); + if (!dev) return PLAY_CONSOLE; + const base = `${PLAY_CONSOLE}/u/0/developers/${dev}`; + if (p.scope === 'developer') return `${base}/${p.path}`; + return app ? `${base}/app/${app}/${p.path}` : `${base}/app-list`; +} + +/** True when the URL is the page itself (ids known), false when it is a fallback (app list, chooser). */ +export function consoleUrlExact(page: ConsolePage, ids: ConsoleIds = current): boolean { + const u = consoleUrl(page, ids); + return u !== PLAY_CONSOLE && !(CONSOLE_PAGES[page].scope === 'app' && (u === `${ASC}/apps` || u.endsWith('/app-list'))); +} + +/** "Readable > path: https://..." (the path suffix, e.g. "> Copy Cloud Storage URI", is appended to the label). */ +export function consoleLink(page: ConsolePage, suffix = '', ids: ConsoleIds = current): string { + return `${CONSOLE_PAGES[page].label}${suffix ? ` ${suffix}` : ''}: ${consoleUrl(page, ids)}`; +} + +/** Play track page: production / internal have their own page; other tracks open Test and release. */ +export function playTrackPage(track: string | undefined): ConsolePage { + return track === 'production' ? 'play.production' : track === 'internal' ? 'play.internal-testing' : 'play.releases'; +} diff --git a/cli/lib/console-plan.ts b/cli/lib/console-plan.ts index bd1e159..84eb872 100644 --- a/cli/lib/console-plan.ts +++ b/cli/lib/console-plan.ts @@ -5,6 +5,7 @@ // The scripted engine (`console fill`) uses the same answers and click rules and stays an optional fast path. Pure. // UI facts marked "verified" were read on a real console (App Privacy, 2026-10-12, /apps//distribution/privacy); // everything else is modeled on Apple's and Google's help pages (sources in console-answers.ts): the agent adapts. +import { consoleLink, consoleUrl } from './console-links'; import { playCategoryOf, type ShipConfig } from './config'; import type { ProjectSignals } from './project-signals'; import { type Answer, type AnswerValue, consoleAnswers, type FormId, formMeta, isPlanOnly, type PlanFormId, type Store, valueText } from './console-answers'; @@ -258,7 +259,7 @@ function appPrivacyPlan(answers: PlanAnswer[], appId: string | null | undefined) const collected = types.filter(a => a.value === true).map(a => a.id.slice(5)); const steps: PlanStep[] = []; const add = (s: Omit) => steps.push({ n: steps.length + 1, ...s }); - add({ text: appId ? 'Open the App Privacy page of the app (deep link).' : 'Open App Store Connect > Apps > the app > App Privacy (sidebar).' }); + add({ text: appId ? 'Open the App Privacy page of the app (deep link).' : `Open App Store Connect > Apps > the app > App Privacy (sidebar): ${consoleUrl('asc.apps')}` }); add({ text: 'First time: click "Get Started" and answer the Data Collection question; then Next.', click: ['Get Started'], answers: ['collects'] }); add({ text: 'Data types: when the Data Types section does not list exactly the collected types below, click the "Edit" next to the "Data Types" heading (never the Privacy Policy Edit), tick exactly these types and untick the rest, then Save (a draft: nothing is public before Publish). Console labels: Purchase History shows as "Purchases", Other Data Types as "Other Data".', click: ['Edit', 'Save'], answers: ids(answers, a => a.id.startsWith('type:')) }); for (const t of collected) { @@ -328,9 +329,10 @@ function otherPlan(form: FormId, config: ShipConfig, answers: PlanAnswer[], p: P const steps: PlanStep[] = []; const add = (s: Omit) => steps.push({ n: steps.length + 1, ...s }); const all = (pred: (a: PlanAnswer) => boolean) => ids(answers, pred); + const planIds = { playDeveloperId: p.playDeveloperId ?? config.play?.developerId, playAppId: p.playAppId ?? config.play?.appId }; switch (form) { case 'apple-app-record': - add({ text: 'Open App Store Connect > Apps.' }); + add({ text: `Open ${consoleLink('asc.apps')}.` }); add({ text: 'Click the add button (+, labelled "Add Apps") at the top left, then "New App" in the menu. The New App dialog opens.', click: ['+', 'New App'] }); add({ text: 'Fill the New App dialog: tick iOS, type the Name, choose the Primary Language and the Bundle ID, type the SKU, pick User Access. Name and SKU: click the field and TYPE the text (your browser tool\'s type action, key by key); never set the value programmatically (form_input, el.value = …): the dialog then keeps an empty name / SKU (verified 2026-10-13). Read both fields back.', answers: all(() => true) }); add({ text: 'Stop. Do NOT press Create: tell the owner to review and press Create (it creates the app record for good; the bundle id and SKU cannot change).' }); @@ -377,7 +379,7 @@ function otherPlan(form: FormId, config: ShipConfig, answers: PlanAnswer[], p: P }; case 'play-content-rating': { const section = (sec: string) => all(a => a.id.startsWith(`d:${sec}:`)); - add({ text: 'Open App content > Content ratings (/app-content/content-rating-overview), then "Start questionnaire": it opens /app-content/content-rating-iarc-questionnaire.', click: ['Start questionnaire'] }); + add({ text: `Open ${consoleLink('play.content-rating', '', planIds)}, then "Start questionnaire": it opens /app-content/content-rating-iarc-questionnaire.`, click: ['Start questionnaire'] }); add({ text: 'Page 1: type the email address and pick the category (material radio: JS click on the inner [role=radio]). The IARC Terms of Use checkbox is an agreement: never tick it. The owner ticked it before the run (owner batch); read its aria-checked. Still unticked: Next does nothing (verified 2026-10-13), so do not press it again and again: say once that the box is waiting for the owner and poll aria-checked every 5 s (up to 15 min). Then Next.', click: ['Next'], answers: ['email', 'category', 'agree:iarcTerms'] }); add({ text: 'The questionnaire is ONE long page of sections (Violence, Fear, Sexuality, Gambling, Language, Controlled Substance, Crude Humor, Digital Purchases, Miscellaneous). Answer each section\'s Yes / No as the sheet says; each Yes reveals follow-ups right below it, so answer top to bottom and re-read the page after every Yes. Run it as one in-page job (plan.inPageLoop) and poll window.__log.', answers: all(a => a.id.startsWith('q:')) }); add({ text: 'Violence follow-ups (after Yes): the humans / non-humans checkboxes, then setting, childlike style, reactions, presentation checkboxes, blood level, historical war setting, innocent characters, fierce sounds.', answers: section('violence') }); @@ -391,7 +393,7 @@ function otherPlan(form: FormId, config: ShipConfig, answers: PlanAnswer[], p: P start: playStart(form, config, p), steps, uiFacts: [ 'Verified 2026-10-12 (an agent with a browser tool on a real game):', - 'App content > Content ratings is /app-content/content-rating-overview; "Start questionnaire" opens /app-content/content-rating-iarc-questionnaire.', + `App content > Content ratings is /app-content/content-rating-overview (${consoleUrl('play.content-rating', planIds)}); "Start questionnaire" opens /app-content/content-rating-iarc-questionnaire.`, 'Page 1: email address, the category radio, and an IARC Terms of Use checkbox: an agreement, the owner ticks it, never the agent.', 'Verified 2026-10-13: page 1\'s Next does nothing until the IARC Terms of Use box is ticked. So the box is in the ONE owner batch before the run (the owner ticks it on page 1 first), never a stop in the middle.', 'The questionnaire is one long page of sections; each Yes reveals follow-ups (Violence: humans / non-humans checkboxes, then setting, childlike style, reactions, presentation checkboxes, blood level, historical war setting, innocent characters, fierce sounds; Digital Purchases: digital goods, then loot boxes, then player trading; Miscellaneous: user interaction then block / report / moderation / friends-only, location sharing, Nazi symbols, Korea history, terrorism, crimes). Follow-up wording here is storebound\'s: match by topic and the console\'s own labels.', @@ -405,10 +407,10 @@ function otherPlan(form: FormId, config: ShipConfig, answers: PlanAnswer[], p: P } case 'play-target-audience': { const teens = answers.filter(a => a.id.startsWith('age:')).every(a => a.status === 'certain' && (a.value !== true || ['age:13-15', 'age:16-17', 'age:18-over'].includes(a.id))); - add({ text: 'Open App content > Target audience and content (/app-content/target-audience-content) > Start (or Manage).', click: ['Start'] }); + add({ text: `Open ${consoleLink('play.target-audience', '', planIds)} > Start (or Manage).`, click: ['Start'] }); add({ text: 'Target age: tick exactly the age groups of the sheet (material checkboxes: JS click on the inner [role=checkbox]; read aria-checked back). Next.', click: ['Next'], answers: all(a => a.id.startsWith('age:')) }); if (answers.some(a => a.id === 'appeal')) add({ text: `Appeal to children: pick the answer, then Next.${teens ? ' With 13+ age groups only the page skipped this question and went straight to the Summary (verified 2026-10-12): answer it only if it shows.' : ''}`, click: ['Next'], answers: ['appeal'] }); - add({ text: 'Ads (also App content > Ads, /app-content/ads-declaration): pick whether the app contains ads, if this section shows it.', answers: ['ads'] }); + add({ text: `Ads (also ${consoleLink('play.ads', '', planIds)}): pick whether the app contains ads, if this section shows it.`, answers: ['ads'] }); add({ text: `Summary: press Save (the agent's button, a declaration save: Google gets it only with the next "${SEND_FOR_REVIEW}" in Publishing overview, the owner's). Read the summary back.`, click: ['Save'] }); add({ text: 'Verify: read the section\'s summary as text and pipe it to `storebound console verify play-target-audience --from-text -`.' }); return { @@ -433,7 +435,7 @@ const IARC_TERMS = 'Tick the IARC Terms of Use box'; /** Where the IARC Terms box is (verified 2026-10-12 / 2026-10-13). */ function iarcWhere(cfg: ShipConfig, p: PlanIds): string { const dev = p.playDeveloperId ?? cfg.play?.developerId, app = p.playAppId ?? cfg.play?.appId; - const url = dev && app ? playAppPage(dev, app, 'app-content/content-rating-iarc-questionnaire') : '/app-content/content-rating-iarc-questionnaire'; + const url = dev && app ? playAppPage(dev, app, 'app-content/content-rating-iarc-questionnaire') : `${consoleUrl('play.content-rating', { playDeveloperId: dev, playAppId: app })} (then /app-content/content-rating-iarc-questionnaire)`; return `Play Console > the app > Policy and programs > App content > Content ratings > "Start questionnaire" (${url}), page 1: the IARC Terms of Use checkbox under the Email address and the Category. Tick it, leave the tab open; the agent fills the rest of page 1 and presses Next (Next does nothing until it is ticked).`; } @@ -442,7 +444,7 @@ function ownerBatchOf(form: PlanFormId, cfg: ShipConfig, p: PlanIds, answers: Pl const out: Omit[] = []; const open = answers.filter(a => (a.status === 'ask-owner' || a.status === 'owner-check') && !a.id.endsWith('credentials')); if (open.length) out.push({ when: 'before', kind: 'answers', text: `Answer ${open.length} question(s) in ONE batch (askOwner), then write the answers into storebound.config.ts and rerun the plan: ${open.map(a => a.question).join('; ')}` }); - if (answers.some(a => a.status === 'ask-owner' && a.id.endsWith('credentials'))) out.push({ when: 'before', kind: 'credentials', text: 'Put the App Review demo account into .storebound/credentials.json (reviewDemoAccount: username, password) so the agent types it without printing it; or type the username and password into the Sign in details card yourself', where: 'App content > Sign in details > Add instructions' }); + if (answers.some(a => a.status === 'ask-owner' && a.id.endsWith('credentials'))) out.push({ when: 'before', kind: 'credentials', text: 'Put the App Review demo account into .storebound/credentials.json (reviewDemoAccount: username, password) so the agent types it without printing it; or type the username and password into the Sign in details card yourself', where: `${consoleLink('play.app-access', '> Add instructions')}` }); const iarc = answers.some(a => a.status === 'agreement' && /iarcTerms$/.test(a.id)); if (iarc) out.push({ when: 'before', kind: 'agreement', text: IARC_TERMS, where: iarcWhere(cfg, p) }); if (form === 'play-app-record') { diff --git a/cli/lib/console-play-plans.ts b/cli/lib/console-play-plans.ts index 069577b..b851e61 100644 --- a/cli/lib/console-play-plans.ts +++ b/cli/lib/console-play-plans.ts @@ -166,7 +166,7 @@ function privacyPolicy(config: ShipConfig, p: PlanIds): Part { title: 'Play Console: Privacy policy (App content)', start: start(config, p, 'app-content/privacy-policy', appContentReach('Privacy policy')), steps: steps([ - { text: 'Open App content > Privacy policy (/app-content/privacy-policy).' }, + { text: `Open App content > Privacy policy: ${start(config, p, 'app-content/privacy-policy', []).url}` }, { text: 'Type the privacy policy URL into the Privacy policy URL field (replace what is there).', answers: ['url'] }, { text: DECLARATION_SAVE }, { text: 'Verify: read the page text and pipe it to `storebound console verify play-privacy-policy --from-text -`.' }, @@ -176,7 +176,7 @@ function privacyPolicy(config: ShipConfig, p: PlanIds): Part { ...declarationButtons('Privacy policy'), uiFacts: ['Verified 2026-10-12: the card\'s page is /app-content/privacy-policy (one URL field).', 'The URL must be public (no PDF, no sign-in): `storebound validate` checks it.', PLAY_MATERIAL_FACT], verify: pageVerify('play-privacy-policy', ['The field shows app.privacyPolicyUrl.']), - guidance: ['Open: Policy and programs > App content > Privacy policy'], + guidance: [`Open: Policy and programs > App content > Privacy policy: ${start(config, p, 'app-content/privacy-policy', []).url}`], }; } @@ -195,7 +195,7 @@ function adsDeclaration(config: ShipConfig, p: PlanIds): Part { title: 'Play Console: Ads (App content)', start: start(config, p, 'app-content/ads-declaration', appContentReach('Ads')), steps: steps([ - { text: 'Open App content > Ads (/app-content/ads-declaration).' }, + { text: `Open App content > Ads: ${start(config, p, 'app-content/ads-declaration', []).url}` }, { text: 'Pick the radio (JS click on the inner [role=radio]); read aria-checked back.', answers: ['ads'] }, { text: DECLARATION_SAVE }, { text: 'Verify: read the page text and pipe it to `storebound console verify play-ads --from-text -`.' }, @@ -205,7 +205,7 @@ function adsDeclaration(config: ShipConfig, p: PlanIds): Part { ...declarationButtons('Ads'), uiFacts: ['Verified 2026-10-12: /app-content/ads-declaration.', 'Rewarded ads count as ads.', PLAY_MATERIAL_FACT], verify: pageVerify('play-ads', ['The selected radio matches the sheet.']), - guidance: ['Open: Policy and programs > App content > Ads'], + guidance: [`Open: Policy and programs > App content > Ads: ${start(config, p, 'app-content/ads-declaration', []).url}`], }; } @@ -232,7 +232,7 @@ function signInDetails(config: ShipConfig, p: PlanIds): Part { title: 'Play Console: Sign in details (App access)', start: start(config, p, 'app-content/testing-credentials', appContentReach('Sign in details')), steps: steps([ - { text: 'Open App content > Sign in details (/app-content/testing-credentials).' }, + { text: `Open App content > Sign in details: ${start(config, p, 'app-content/testing-credentials', []).url}` }, { text: 'Pick whether any functionality is restricted (material radio: JS click on the inner [role=radio]).', answers: ['access'] }, ra?.signInRequired && { text: p.demoAccount ? 'Restricted: press "Add instructions", type the instructions, then type the Username and Password from .storebound/credentials.json (reviewDemoAccount; click each field and type: never echo them in chat or logs).' @@ -244,7 +244,7 @@ function signInDetails(config: ShipConfig, p: PlanIds): Part { ...declarationButtons('Sign in details', [{ button: 'Add instructions', class: 'navigation', where: 'Sign in details: opens the instructions dialog' }]), uiFacts: ['Verified 2026-10-12: the Sign in details card (App access) is /app-content/testing-credentials.', 'Credentials are never in the config, the plan or chat: the agent types them from .storebound/credentials.json (reviewDemoAccount) without printing them; without that entry the owner types them.', PLAY_MATERIAL_FACT], verify: pageVerify('play-sign-in-details', ['The selected radio matches reviewAccess.signInRequired; with a sign-in, one set of instructions is listed.']), - guidance: ['Open: Policy and programs > App content > Sign in details (App access)'], + guidance: [`Open: Policy and programs > App content > Sign in details (App access): ${start(config, p, 'app-content/testing-credentials', []).url}`], }; } @@ -292,7 +292,7 @@ function advertisingId(config: ShipConfig, p: PlanIds): Part { title: 'Play Console: Advertising ID (App content)', start: start(config, p, 'app-content/ad-id-declaration', appContentReach('Advertising ID')), steps: steps([ - { text: 'Open App content > Advertising ID (/app-content/ad-id-declaration).' }, + { text: `Open App content > Advertising ID: ${start(config, p, 'app-content/ad-id-declaration', []).url}` }, { text: 'Pick Yes or No (material radio: JS click on the inner [role=radio]).', answers: ['uses'] }, uses.v === true && { text: 'Tick exactly the purposes of the sheet (material checkboxes: JS click on the inner [role=checkbox]; read aria-checked back).', answers: out.filter(a => a.id.startsWith('purpose')).map(a => a.id) }, { text: DECLARATION_SAVE }, @@ -302,7 +302,7 @@ function advertisingId(config: ShipConfig, p: PlanIds): Part { ...declarationButtons('Advertising ID'), uiFacts: ['Verified 2026-10-12: /app-content/ad-id-declaration.', 'Yes needs the com.google.android.gms.permission.AD_ID permission in the merged manifest (ad SDKs usually add it); `storebound scan` reports the SDKs.', PLAY_MATERIAL_FACT], verify: pageVerify('play-advertising-id', ['The selected radio and the ticked purposes match the sheet.']), - guidance: ['Open: Policy and programs > App content > Advertising ID'], + guidance: [`Open: Policy and programs > App content > Advertising ID: ${start(config, p, 'app-content/ad-id-declaration', []).url}`], }; } @@ -312,7 +312,7 @@ function government(config: ShipConfig, p: PlanIds): Part { title: 'Play Console: Government apps (App content)', start: start(config, p, 'app-content/government-apps', appContentReach('Government apps')), steps: steps([ - { text: 'Open App content > Government apps (/app-content/government-apps).' }, + { text: `Open App content > Government apps: ${start(config, p, 'app-content/government-apps', []).url}` }, { text: 'Pick whether the app is developed by or on behalf of a government (material radio).', answers: ['government'] }, { text: DECLARATION_SAVE }, { text: 'Verify: read the page text and pipe it to `storebound console verify play-government-apps --from-text -`.' }, @@ -322,7 +322,7 @@ function government(config: ShipConfig, p: PlanIds): Part { ...declarationButtons('Government apps'), uiFacts: ['Verified 2026-10-12: /app-content/government-apps.', PLAY_MATERIAL_FACT], verify: pageVerify('play-government-apps', ['The selected answer matches the sheet.']), - guidance: ['Open: Policy and programs > App content > Government apps'], + guidance: [`Open: Policy and programs > App content > Government apps: ${start(config, p, 'app-content/government-apps', []).url}`], }; } @@ -343,7 +343,7 @@ function featureCard(config: ShipConfig, p: PlanIds, kind: 'finance' | 'health') title: `Play Console: ${name} (App content)`, start: start(config, p, `app-content/${kind}`, appContentReach(name)), steps: steps([ - { text: `Open App content > ${name} (/app-content/${kind}).` }, + { text: `Open App content > ${name}: ${start(config, p, `app-content/${kind}`, []).url}` }, { text: 'Step 1: read it and press Next.', click: ['Next'] }, { text: `Last step: tick exactly the boxes of the sheet (material checkboxes: JS click on the inner [role=checkbox]; read aria-checked back)${fin ? '; no financial features: "My app doesn\'t provide any financial features"' : '; no health features: "Other" (as on the verified run) or the none option the page offers'}. Then Next if the page shows one.`, click: ['Next'], answers: out.map(a => a.id) }, { text: DECLARATION_SAVE }, @@ -353,7 +353,7 @@ function featureCard(config: ShipConfig, p: PlanIds, kind: 'finance' | 'health') ...declarationButtons(name, [{ button: 'Next', class: 'navigation', where: `${name}: between the two steps` }]), uiFacts: [`Verified 2026-10-12: /app-content/${kind}, two steps; the last step holds ${fin ? '"My app doesn\'t provide any financial features"' : 'the health feature boxes, "Other" among them'}; then Next and Save.`, PLAY_MATERIAL_FACT], verify: pageVerify(form, ['The ticked boxes match the sheet.']), - guidance: [`Open: Policy and programs > App content > ${name}`], + guidance: [`Open: Policy and programs > App content > ${name}: ${start(config, p, `app-content/${kind}`, []).url}`], }; } diff --git a/cli/lib/declarations.ts b/cli/lib/declarations.ts index 93dcacc..347111b 100644 --- a/cli/lib/declarations.ts +++ b/cli/lib/declarations.ts @@ -4,6 +4,7 @@ // (App Privacy has no API at all: the owner enters it in App Store Connect), app info (categories, age rating, // content rights), the export compliance answer and the App Review details (presence only, masked). Every answer the // config does not prove is an "ask the owner" item, never a guess (AGENTS.md rule 5). Pure. +import { consoleLink, consoleUrl } from './console-links'; import { parseCsv } from './csv'; import { type AccountSignUp, type DataSafety, type PlayPurpose, profileOf, type ShipConfig } from './config'; import { CATEGORIES, DATA_DELETION_RESPONSE, dataDeletionResponse, dataSafetyCsv, dataSafetyIssues, dataSafetyRows } from './data-safety'; @@ -163,8 +164,8 @@ export function declarations(config: ShipConfig, inp: DeclarationInputs): Declar fromDataSafety: !!ds, } : null; if (apple) { - if (!ds) ask.push({ area: 'appPrivacy', id: 'ap-unknown', text: 'App Privacy (App Store Connect > App Privacy) cannot be suggested without dataSafety answers in the config.', fix: 'Answer the data collection once in dataSafety; storebound then lists the matching App Privacy answers to enter.' }); - else ask.push({ area: 'appPrivacy', id: 'ap-linked', text: 'App Privacy asks per data type whether it is linked to the user\'s identity and whether it is used for tracking; the config cannot prove either.', fix: 'Answer both in App Store Connect > App Privacy for each type below (tracking also needs App Tracking Transparency in the app).' }); + if (!ds) ask.push({ area: 'appPrivacy', id: 'ap-unknown', text: `App Privacy (${consoleLink('asc.app-privacy')}) cannot be suggested without dataSafety answers in the config.`, url: consoleUrl('asc.app-privacy'), fix: 'Answer the data collection once in dataSafety; storebound then lists the matching App Privacy answers to enter.' }); + else ask.push({ area: 'appPrivacy', id: 'ap-linked', text: 'App Privacy asks per data type whether it is linked to the user\'s identity and whether it is used for tracking; the config cannot prove either.', fix: `Answer both in ${consoleLink('asc.app-privacy')} for each type below (tracking also needs App Tracking Transparency in the app).`, url: consoleUrl('asc.app-privacy') }); } const info = config.apple?.info; diff --git a/cli/lib/desktop-protocol.ts b/cli/lib/desktop-protocol.ts index 873d00c..41d5baa 100644 --- a/cli/lib/desktop-protocol.ts +++ b/cli/lib/desktop-protocol.ts @@ -324,7 +324,7 @@ export interface PricesCurrent { csv: string; products: string[]; stores: ('appl export interface PriceDiffRow { product: string; store: 'apple' | 'play'; region: string; startDate?: string; currency: string; from: number | null; to: number; change: 'new' | 'change' | 'same' } export interface PricesDiff { csv: string; diff: PriceDiffRow[]; changed: string[] } /** One "ask the owner" item: the config does not prove the answer (AGENTS.md rule 5). */ -export interface DeclAsk { area: 'dataSafety' | 'appPrivacy' | 'appInfo' | 'encryption' | 'review'; id: string; text: string; fix: string } +export interface DeclAsk { area: 'dataSafety' | 'appPrivacy' | 'appInfo' | 'encryption' | 'review'; id: string; text: string; fix: string; url?: string } /** project.declarations (`inspect --declarations --json`): store declarations from the config and local files. */ export interface DeclarationsResult { profile: string; diff --git a/cli/lib/embedded.ts b/cli/lib/embedded.ts index 9ccef13..40b0cff 100644 --- a/cli/lib/embedded.ts +++ b/cli/lib/embedded.ts @@ -4,25 +4,25 @@ /** Every file under templates/, by its path there (forward slashes). */ export const TEMPLATE_TEXT: Record = { - "app-privacy.md": "# Example Game · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** at the top of the App Privacy page (saving alone does not publish it, and review checks the published one).\nKeep it consistent with the privacy policy and the Play Data safety answers (`dataSafety` in `storebound.config.ts`).\n\nTypical free-to-play game: analytics SDK + ad mediation (with ATT) + optional server backup, no accounts.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: Yes, if any ad network uses the IDFA after the App Tracking Transparency prompt (third-party advertising).\nThen `NSUserTrackingUsageDescription` must be in Info.plist and the prompt must come before the ad SDK starts.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Identifiers > Device ID (IDFA, analytics random id) | yes | yes (IDFA, with ATT consent) | no | Third-Party Advertising, Analytics, App Functionality |\n| Location > Coarse Location (country from IP) | yes | yes | no | Third-Party Advertising, Analytics |\n| Usage Data > Advertising Data (ads shown, ad taps) | yes | yes | no | Third-Party Advertising, Analytics |\n| Usage Data > Product Interaction (sessions, level events) | yes | no | no | Analytics |\n| User Content > Gameplay Content (save backup, scores) | only with a server backup | no | no | App Functionality |\n| Diagnostics > Other Diagnostic Data (device model, OS version) | yes | no | no | Analytics, Third-Party Advertising |\n| Purchases > Purchase History | only if your server stores receipts | no | yes if tied to an account | App Functionality |\n\n\"Linked to you\" is **No** when there are no accounts and nothing is tied to a name, email or phone number.\nNot collected (typical): Contact Info, Health and Fitness, Financial Info, Precise Location, Sensitive Info, Contacts,\nEmails or Text Messages, Photos or Videos, Audio, Browsing History, Search History, Crash Data (unless you ship a crash\nreporter).\n\nPurposes follow Apple's definitions (https://developer.apple.com/app-store/app-privacy-details/): ads shown by an ad\nSDK are **Third-Party Advertising** only. **Developer's Advertising or Marketing** is your own ads or marketing to your\nusers; tick it only when that is true.\n\nPut the answers into `storebound.config.ts` so `storebound console fill apple-app-privacy` fills them and\n`storebound check` compares them with Info.plist and PrivacyInfo.xcprivacy (all type ids: storebound's\ncli/lib/app-privacy.ts):\n\n```ts\nappPrivacy: { tracking: true, types: {\n DEVICE_ID: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS', 'APP_FUNCTIONALITY'] },\n COARSE_LOCATION: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS'] },\n ADVERTISING_DATA: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS'] },\n PRODUCT_INTERACTION: { collected: true, linked: false, tracking: false, purposes: ['ANALYTICS'] },\n GAMEPLAY_CONTENT: { collected: false },\n} },\n```\n\nPer data type the UI asks, in order: purposes (checkboxes) > \"Is this data linked to the user's identity?\" >\n\"Do you or your third-party partners use this data for tracking purposes?\".\n\nPrivacy policy URL: App Information > Privacy Policy URL (set by `storebound apple listing`). Privacy choices URL:\noptional, leave empty unless you have a page for it.\n", + "app-privacy.md": "# Example Game · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy (https://appstoreconnect.apple.com/apps, then the app; `storebound console plan apple-app-privacy` gives the deep link). **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** at the top of the App Privacy page (saving alone does not publish it, and review checks the published one).\nKeep it consistent with the privacy policy and the Play Data safety answers (`dataSafety` in `storebound.config.ts`).\n\nTypical free-to-play game: analytics SDK + ad mediation (with ATT) + optional server backup, no accounts.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: Yes, if any ad network uses the IDFA after the App Tracking Transparency prompt (third-party advertising).\nThen `NSUserTrackingUsageDescription` must be in Info.plist and the prompt must come before the ad SDK starts.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Identifiers > Device ID (IDFA, analytics random id) | yes | yes (IDFA, with ATT consent) | no | Third-Party Advertising, Analytics, App Functionality |\n| Location > Coarse Location (country from IP) | yes | yes | no | Third-Party Advertising, Analytics |\n| Usage Data > Advertising Data (ads shown, ad taps) | yes | yes | no | Third-Party Advertising, Analytics |\n| Usage Data > Product Interaction (sessions, level events) | yes | no | no | Analytics |\n| User Content > Gameplay Content (save backup, scores) | only with a server backup | no | no | App Functionality |\n| Diagnostics > Other Diagnostic Data (device model, OS version) | yes | no | no | Analytics, Third-Party Advertising |\n| Purchases > Purchase History | only if your server stores receipts | no | yes if tied to an account | App Functionality |\n\n\"Linked to you\" is **No** when there are no accounts and nothing is tied to a name, email or phone number.\nNot collected (typical): Contact Info, Health and Fitness, Financial Info, Precise Location, Sensitive Info, Contacts,\nEmails or Text Messages, Photos or Videos, Audio, Browsing History, Search History, Crash Data (unless you ship a crash\nreporter).\n\nPurposes follow Apple's definitions (https://developer.apple.com/app-store/app-privacy-details/): ads shown by an ad\nSDK are **Third-Party Advertising** only. **Developer's Advertising or Marketing** is your own ads or marketing to your\nusers; tick it only when that is true.\n\nPut the answers into `storebound.config.ts` so `storebound console fill apple-app-privacy` fills them and\n`storebound check` compares them with Info.plist and PrivacyInfo.xcprivacy (all type ids: storebound's\ncli/lib/app-privacy.ts):\n\n```ts\nappPrivacy: { tracking: true, types: {\n DEVICE_ID: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS', 'APP_FUNCTIONALITY'] },\n COARSE_LOCATION: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS'] },\n ADVERTISING_DATA: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS'] },\n PRODUCT_INTERACTION: { collected: true, linked: false, tracking: false, purposes: ['ANALYTICS'] },\n GAMEPLAY_CONTENT: { collected: false },\n} },\n```\n\nPer data type the UI asks, in order: purposes (checkboxes) > \"Is this data linked to the user's identity?\" >\n\"Do you or your third-party partners use this data for tracking purposes?\".\n\nPrivacy policy URL: App Information > Privacy Policy URL (set by `storebound apple listing`). Privacy choices URL:\noptional, leave empty unless you have a page for it.\n", "appstore.md": "# Example Game · App Store listing copy\n\nSource for `storebound apple listing`. One `## ` section per locale (the first word is the locale code:\n`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### `, field names exactly:\n`name` (optional, defaults to `apple.name`, else `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 characters, comma\nseparated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed\non the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.\n\n---\n\n## en-US · English (U.S.)\n\n### subtitle\n```\nShort hook under the name\n```\n\n### promotionalText\n```\nOne or two sentences you can change any time without a new version.\n```\n\n### keywords\n```\npuzzle,casual,relaxing,offline,brain,match,levels\n```\n\n### description\n```\nFirst paragraph: what the player does and why it feels good.\n\nFEATURES\n• One-thumb controls\n• 100+ levels\n• Play offline, no account needed\n\nFree to play. Contains ads and offers optional in-app purchases.\n```\n\n## tr · Türkçe\n\n### subtitle\n```\nİsmin altındaki kısa kanca\n```\n\n### promotionalText\n```\nYeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.\n```\n\n### keywords\n```\nbulmaca,rahatlatıcı,internetsiz,zeka,eşleştirme,bölüm\n```\n\n### description\n```\nİlk paragraf: oyuncu ne yapıyor ve neden keyifli.\n\nÖZELLİKLER\n• Tek parmakla kontrol\n• 100+ bölüm\n• İnternetsiz oyna, hesap gerekmez\n\nÜcretsiz oyun. Reklam içerir ve isteğe bağlı uygulama içi satın alımlar sunar.\n```\n", "credentials.example.json": "{\n \"PLAY_SERVICE_ACCOUNT\": \"\",\n \"ASC_KEY_ID\": \"\",\n \"ASC_ISSUER_ID\": \"\",\n \"ASC_P8\": \"\",\n \"APPLE_ADS_CLIENT_ID\": \"\",\n \"APPLE_ADS_TEAM_ID\": \"\",\n \"APPLE_ADS_KEY_ID\": \"\",\n \"APPLE_ADS_PRIVATE_KEY\": \"\",\n \"APPLE_ADS_ACCOUNT_ID\": \"\",\n \"REVENUECAT_SECRET_KEY\": \"\",\n \"ANDROID_KEYSTORE\": \"\",\n \"ANDROID_KEYSTORE_PASSWORD\": \"\",\n \"ANDROID_KEY_ALIAS\": \"\",\n \"ANDROID_KEY_PASSWORD\": \"\",\n \"ASC_VENDOR_NUMBER\": \"\",\n \"reviewContact\": { \"firstName\": \"\", \"lastName\": \"\", \"phone\": \"\", \"email\": \"\" },\n \"reviewDemoAccount\": { \"username\": \"\", \"password\": \"\" }\n}\n", "data-safety.csv": "Question ID (machine readable),Response ID (machine readable),Response value,Answer requirement,Human-friendly question label\nPSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA,,TRUE,REQUIRED,Does your app collect or share any of the required user data types?\nPSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT,,TRUE,MAYBE_REQUIRED,Is all of the user data collected by your app encrypted in transit?\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_NONE,TRUE,MULTIPLE_CHOICE,Which of the following methods of account creation does your app support? Select all that apply / My app does not allow users to create an account\nPSL_DATA_TYPES_PERSONAL,PSL_NAME,,MULTIPLE_CHOICE,Personal info / Name\nPSL_DATA_TYPES_PERSONAL,PSL_EMAIL,,MULTIPLE_CHOICE,Personal info / Email address\nPSL_DATA_TYPES_PERSONAL,PSL_USER_ACCOUNT,,MULTIPLE_CHOICE,Personal info / User IDs\nPSL_DATA_TYPES_PERSONAL,PSL_ADDRESS,,MULTIPLE_CHOICE,Personal info / Address\nPSL_DATA_TYPES_PERSONAL,PSL_PHONE,,MULTIPLE_CHOICE,Personal info / Phone number\nPSL_DATA_TYPES_PERSONAL,PSL_RACE_ETHNICITY,,MULTIPLE_CHOICE,Personal info / Race and ethnicity\nPSL_DATA_TYPES_PERSONAL,PSL_POLITICAL_RELIGIOUS,,MULTIPLE_CHOICE,Personal info / Political or religious beliefs\nPSL_DATA_TYPES_PERSONAL,PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY,,MULTIPLE_CHOICE,Personal info / Sexual orientation\nPSL_DATA_TYPES_PERSONAL,PSL_OTHER_PERSONAL,,MULTIPLE_CHOICE,Personal info / Other info\nPSL_DATA_TYPES_FINANCIAL,PSL_PURCHASE_HISTORY,TRUE,MULTIPLE_CHOICE,Financial info / Purchase history\nPSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_SCORE,,MULTIPLE_CHOICE,Financial info / Credit score\nPSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER,,MULTIPLE_CHOICE,Financial info / User payment info\nPSL_DATA_TYPES_FINANCIAL,PSL_OTHER,,MULTIPLE_CHOICE,Financial info / Other financial info\nPSL_DATA_TYPES_LOCATION,PSL_APPROX_LOCATION,TRUE,MULTIPLE_CHOICE,Location / Approximate location\nPSL_DATA_TYPES_LOCATION,PSL_PRECISE_LOCATION,,MULTIPLE_CHOICE,Location / Precise location\nPSL_DATA_TYPES_SEARCH_AND_BROWSING,PSL_WEB_BROWSING_HISTORY,,MULTIPLE_CHOICE,Web browsing / Web browsing history\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_EMAILS,,MULTIPLE_CHOICE,Messages / Emails\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_SMS_CALL_LOG,,MULTIPLE_CHOICE,Messages / SMS or MMS\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_OTHER_MESSAGES,,MULTIPLE_CHOICE,Messages / Other in-app messages\nPSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_PHOTOS,,MULTIPLE_CHOICE,Photos and videos / Photos\nPSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_VIDEOS,,MULTIPLE_CHOICE,Photos and videos / Videos\nPSL_DATA_TYPES_AUDIO,PSL_AUDIO,,MULTIPLE_CHOICE,Audio files / Voice or sound recordings\nPSL_DATA_TYPES_AUDIO,PSL_MUSIC,,MULTIPLE_CHOICE,Audio files / Music files\nPSL_DATA_TYPES_AUDIO,PSL_OTHER_AUDIO,,MULTIPLE_CHOICE,Audio files / Other audio files\nPSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_HEALTH,,MULTIPLE_CHOICE,Health and fitness / Health info\nPSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_FITNESS,,MULTIPLE_CHOICE,Health and fitness / Fitness info\nPSL_DATA_TYPES_CONTACTS,PSL_CONTACTS,,MULTIPLE_CHOICE,Contacts / Contacts\nPSL_DATA_TYPES_CALENDAR,PSL_CALENDAR,,MULTIPLE_CHOICE,Calendar / Calendar events\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_CRASH_LOGS,,MULTIPLE_CHOICE,App info and performance / Crash logs\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_PERFORMANCE_DIAGNOSTICS,TRUE,MULTIPLE_CHOICE,App info and performance / Diagnostics\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_OTHER_PERFORMANCE,,MULTIPLE_CHOICE,App info and performance / Other app performance data\nPSL_DATA_TYPES_FILES_AND_DOCS,PSL_FILES_AND_DOCS,,MULTIPLE_CHOICE,Files and docs / Files and docs\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_INTERACTION,TRUE,MULTIPLE_CHOICE,App activity / App interactions\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_IN_APP_SEARCH_HISTORY,,MULTIPLE_CHOICE,App activity / In-app search history\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_APPS_ON_DEVICE,,MULTIPLE_CHOICE,App activity / Installed apps\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_GENERATED_CONTENT,,MULTIPLE_CHOICE,App activity / Other user-generated content\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_OTHER_APP_ACTIVITY,TRUE,MULTIPLE_CHOICE,App activity / Other actions\nPSL_DATA_TYPES_IDENTIFIERS,PSL_DEVICE_ID,TRUE,MULTIPLE_CHOICE,Device or other IDs / Device or other IDs\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Name) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Name) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Name) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Email address) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Email address) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Email address) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (User IDs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (User IDs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (User IDs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Address) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Address) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Address) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Phone number) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Phone number) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Phone number) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Race and ethnicity) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Political or religious beliefs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Sexual orientation) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Purchase history) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Credit score) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Credit score) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Credit score) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (User payment info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (User payment info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (User payment info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other financial info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Approximate location) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Precise location) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Precise location) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Precise location) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Web browsing history) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Emails) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Emails) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Emails) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (SMS or MMS) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other in-app messages) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Photos) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Photos) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Photos) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Videos) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Videos) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Videos) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Voice or sound recordings) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Music files) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Music files) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Music files) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other audio files) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Health info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Health info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Health info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Fitness info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Contacts) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Contacts) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Contacts) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Calendar events) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Crash logs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Diagnostics) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other app performance data) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Files and docs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (App interactions) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (App interactions) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (App interactions) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (In-app search history) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Installed apps) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other user-generated content) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Other actions) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other actions) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Other actions) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Device or other IDs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Account management\n", - "declarations.md": "# Example Game · store declarations (answers to copy into the consoles)\n\nAnswers for a typical single-player free-to-play game with ads (mediation) and in-app purchases, no accounts, no chat,\nno user-generated content, general audience 13+. Change what does not match your game. Everything marked **UI** has no\nAPI: Play Console > your app > Policy and programs > **App content** (or Dashboard > \"Set up your app\"), and App Store\nConnect for Apple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | All functionality is available without special access (no login) |\n| Ads | Yes, my app contains ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 13-15, 16-17, 18 and over; \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| COVID-19 contact tracing / status | No |\n| Data safety | `storebound play data-safety` (then check the summary in the Console) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | Yes, uses it; purposes: Advertising or marketing, Analytics, Fraud prevention / security / compliance |\n| Sign-in details (App access) | No sign-in needed |\n| Store settings | App category Game > (Casual, Puzzle, Arcade ...); tags; contact email + website (website serves app-ads.txt) |\n\nFamilies / Designed for Families: only if you really target children; it changes the ad SDK setup (certified networks,\nno ad id). If you leave children out, the policy and the ad mediation must say \"not directed at children\" too.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: Game. Typical answers for a non-violent casual game:\n\n| topic | answer |\n|---|---|\n| Violence (realistic / cartoon / fantasy) | No |\n| Blood / gore | No |\n| Fear / horror | No |\n| Sexuality / nudity | No |\n| Language (profanity, crude humour) | No |\n| Controlled substances | No |\n| Gambling: simulated / real money | No / No |\n| Random paid items (loot boxes) | No (fixed contents only; answer Yes if a paid item has random contents) |\n| Users interact or exchange content | No (no chat; OS share sheet does not count) |\n| Shares location with other users | No |\n| Digital purchases | Yes (in-app purchases) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, USK 0, IARC 3+. Apple age rating 4+ (all content questions None; ads are declared as\n\"advertising: true\", which does not change 4+).\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App (platform, name, primary language, bundle id, SKU). No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` in Info.plist only if true (HTTPS / OS encryption only) so\n every build skips the question.\n- First in-app purchases: from the IAP page \"Add for Review\" into the draft submission with the version (see the\n store-iap-setup skill).\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app** (Settings > Privacy).\n\n## Ads\n\n- app-ads.txt at the root of the developer website named in both listings (`https://example.com/app-ads.txt`),\n with the mediation's DIRECT line plus each network's lines.\n- iOS: SKAdNetwork ids of every network in Info.plist, ATT prompt before the ad SDK starts.\n- EU / EEA / UK / CH: consent (CMP) or non-personalized ads.\n", + "declarations.md": "# Example Game · store declarations (answers to copy into the consoles)\n\nAnswers for a typical single-player free-to-play game with ads (mediation) and in-app purchases, no accounts, no chat,\nno user-generated content, general audience 13+. Change what does not match your game. Everything marked **UI** has no\nAPI: Play Console > your app > Policy and programs > **App content** (https://play.google.com/console, then the app; `storebound console plan` gives deep links) (or Dashboard > \"Set up your app\"), and App Store\nConnect for Apple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | All functionality is available without special access (no login) |\n| Ads | Yes, my app contains ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 13-15, 16-17, 18 and over; \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| COVID-19 contact tracing / status | No |\n| Data safety | `storebound play data-safety` (then check the summary in the Console) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | Yes, uses it; purposes: Advertising or marketing, Analytics, Fraud prevention / security / compliance |\n| Sign-in details (App access) | No sign-in needed |\n| Store settings | App category Game > (Casual, Puzzle, Arcade ...); tags; contact email + website (website serves app-ads.txt) |\n\nFamilies / Designed for Families: only if you really target children; it changes the ad SDK setup (certified networks,\nno ad id). If you leave children out, the policy and the ad mediation must say \"not directed at children\" too.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: Game. Typical answers for a non-violent casual game:\n\n| topic | answer |\n|---|---|\n| Violence (realistic / cartoon / fantasy) | No |\n| Blood / gore | No |\n| Fear / horror | No |\n| Sexuality / nudity | No |\n| Language (profanity, crude humour) | No |\n| Controlled substances | No |\n| Gambling: simulated / real money | No / No |\n| Random paid items (loot boxes) | No (fixed contents only; answer Yes if a paid item has random contents) |\n| Users interact or exchange content | No (no chat; OS share sheet does not count) |\n| Shares location with other users | No |\n| Digital purchases | Yes (in-app purchases) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, USK 0, IARC 3+. Apple age rating 4+ (all content questions None; ads are declared as\n\"advertising: true\", which does not change 4+).\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App (https://appstoreconnect.apple.com/apps) (platform, name, primary language, bundle id, SKU). No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` in Info.plist only if true (HTTPS / OS encryption only) so\n every build skips the question.\n- First in-app purchases: from the IAP page \"Add for Review\" into the draft submission with the version (see the\n store-iap-setup skill).\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app** (Settings > Privacy).\n\n## Ads\n\n- app-ads.txt at the root of the developer website named in both listings (`https://example.com/app-ads.txt`),\n with the mediation's DIRECT line plus each network's lines.\n- iOS: SKAdNetwork ids of every network in Info.plist, ATT prompt before the ad SDK starts.\n- EU / EEA / UK / CH: consent (CMP) or non-personalized ads.\n", "listing.md": "# Example Game · Google Play listing copy\n\nSource for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).\nRules the parser follows:\n- every `## ...` section with a `` line is a listing; one section can serve several Play codes\n (``)\n- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and\n **what's new**, each followed by one fenced block\n- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters\nOnly claim what is really in the game. No prices, no \"best / #1\", no other games' names.\n\n---\n\n## English (en-US)\n\n\n### Play short description (max 80)\n```\nShort, punchy promise of the core loop in one line\n```\n\n### Play full description (max 4000)\n```\nFirst paragraph: what the player does and why it feels good, in two or three sentences.\n\nFEATURES\n• One-thumb controls\n• 100+ levels and new ones every update\n• Play offline, no account needed\n\nFree to play. Contains ads and offers optional in-app purchases.\n```\n\n### What's new (max 500)\n```\nFirst release. Have fun!\n```\n\n## Türkçe (tr-TR)\n\n\n### Play kısa açıklama · short (max 80)\n```\nOyunun özünü anlatan kısa ve vurucu bir cümle\n```\n\n### Play tam açıklama · full (max 4000)\n```\nİlk paragraf: oyuncu ne yapıyor ve neden keyifli, iki üç cümlede.\n\nÖZELLİKLER\n• Tek parmakla kontrol\n• 100+ bölüm, her güncellemede yenileri\n• İnternetsiz oyna, hesap gerekmez\n\nÜcretsiz oyun. Reklam içerir ve isteğe bağlı uygulama içi satın alımlar sunar.\n```\n\n### Yenilikler · what's new (max 500)\n```\nİlk sürüm. İyi eğlenceler!\n```\n", "privacy-policy.html": "\n\n\n\n\nPrivacy Policy · Example Studio\n\n\n\n\n
\n \n\n
\n

Privacy Policy

\n

Example Studio · applies to all our games · last updated 2026-01-01

\n\n

We make small games. We collect as little as we can, never sell personal data, and none of our games needs an\n account. This page explains what each game collects, why, and what you can do about it.

\n\n

What our games use

\n
\n \n \n \n \n
GameServiceWhat and whyYour choice
Example GameAnalytics (e.g. GameAnalytics)Random player id, sessions, level events, device model, OS version, country derived from IP: to improve the game.Settings > Analytics off
Example GameAds (e.g. a mediation SDK and its networks)Advertising id (if you allow it), IP, device info, ad interactions: to show and measure ads.iOS: tracking prompt; Android: OS ad settings; buy \"Remove Ads\"
Example GameApp storesPurchases are handled by Google Play / the App Store; we never see payment details.-
\n\n

Children

\n

Our games are for a general audience and are not directed at children under 13. We do not knowingly collect\n personal data from children. If you believe a child sent us data, contact us and we will delete it.

\n\n

Retention and your rights

\n

Analytics data is kept for up to 24 months. You can ask us for a copy of your data or to delete it by writing\n to privacy@example.com; we answer within 30 days. Include the game name and,\n if you can, the player id shown in Settings.

\n\n

Security

\n

All network traffic of our games uses HTTPS.

\n\n

Changes

\n

When a game starts collecting something new we update this page and its date first.

\n\n

Contact

\n

Example Studio · privacy@example.com

\n
\n\n
\n

Gizlilik Politikası

\n

Example Studio · tüm oyunlarımız için geçerlidir · son güncelleme 2026-01-01

\n

Küçük oyunlar yapıyoruz. Olabildiğince az veri topluyoruz, kişisel verileri asla satmıyoruz ve hiçbir oyunumuz\n hesap istemiyor. Yukarıdaki tablo her oyunun neyi neden topladığını gösterir.

\n

Haklarınız

\n

Verilerinizin bir kopyasını veya silinmesini privacy@example.com adresine\n yazarak isteyebilirsiniz; 30 gün içinde yanıtlıyoruz.

\n
\n
\n\n\n", - "profiles/app/app-privacy.md": "# Example App · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety\nanswers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**\n\nTypical utility / productivity app: optional account (email), content synced to your backend, an analytics SDK\nacting as your processor, no ads, no tracking.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that\nchanges, add the App Tracking Transparency prompt first.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Contact Info > Email Address | yes (account) | no | yes | App Functionality |\n| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |\n| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |\n| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |\n| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |\n| Diagnostics > Performance Data | yes | no | no | Analytics |\n\n\"Linked to you\" is **Yes** for what you store under the account. Not collected (typical): Location, Health and\nFitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,\nPurchases (until you sell something), Sensitive Info.\n\nPer data type the UI asks, in order: purposes > \"Is this data linked to the user's identity?\" > \"Do you or your\nthird-party partners use this data for tracking purposes?\".\n", + "profiles/app/app-privacy.md": "# Example App · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy (https://appstoreconnect.apple.com/apps, then the app; `storebound console plan apple-app-privacy` gives the deep link). **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety\nanswers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**\n\nTypical utility / productivity app: optional account (email), content synced to your backend, an analytics SDK\nacting as your processor, no ads, no tracking.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that\nchanges, add the App Tracking Transparency prompt first.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Contact Info > Email Address | yes (account) | no | yes | App Functionality |\n| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |\n| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |\n| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |\n| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |\n| Diagnostics > Performance Data | yes | no | no | Analytics |\n\n\"Linked to you\" is **Yes** for what you store under the account. Not collected (typical): Location, Health and\nFitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,\nPurchases (until you sell something), Sensitive Info.\n\nPer data type the UI asks, in order: purposes > \"Is this data linked to the user's identity?\" > \"Do you or your\nthird-party partners use this data for tracking purposes?\".\n", "profiles/app/appstore.md": "# Example App · App Store listing copy\n\nSource for `storebound apple listing`. One `## ` section per locale (the first word is the locale code:\n`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### `, field names exactly:\n`name` (optional, defaults to `apple.name`, else `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 characters, comma\nseparated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed\non the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.\n\n---\n\n## en-US · English (U.S.)\n\n### subtitle\n```\nLists and notes, in sync\n```\n\n### promotionalText\n```\nOne or two sentences you can change any time without a new version.\n```\n\n### keywords\n```\ntodo,checklist,groceries,planner,notes,shared,family,organizer,tasks,sync\n```\n\n### description\n```\nFirst paragraph: the job the app does and why it is easier with it.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use. An account is optional and only needed for sync and sharing.\n```\n\n## tr · Türkçe\n\n### subtitle\n```\nListeler ve notlar, eşitli\n```\n\n### promotionalText\n```\nYeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.\n```\n\n### keywords\n```\nyapılacaklar,kontrol,market,planlayıcı,not,paylaşım,aile,düzen,görev\n```\n\n### description\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\nÜcretsiz. Hesap isteğe bağlıdır; yalnızca eşitleme ve paylaşım için gerekir.\n```\n", - "profiles/app/declarations.md": "# Example App · store declarations (answers to copy into the consoles)\n\nStarting answers for a typical **utility / productivity app**: an optional account (email + password, or sign in\nwith a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with\nstrangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything\nmarked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for\nApple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | \"All or some functionality is restricted\": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |\n| Ads | No, my app does not contain ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 18 and over (or 13+ if teens use it); \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |\n| Store settings | App, category (Productivity, Tools, ...); contact email + website |\n\n## Account deletion (account-deletion skill)\n\n- In the app: Settings > Account > Delete account deletes the account and its data (not only \"deactivate\").\n- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.\n- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with\nothers). Typical answers for a lists / notes app:\n\n| topic | answer |\n|---|---|\n| Violence, sexuality, language, controlled substances | No |\n| Gambling (simulated / real money) | No |\n| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |\n| Shares location with other users | No |\n| Digital purchases | No (Yes once you sell IAPs or subscriptions) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share\ncontent publicly.\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App. No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- App Review information: `storebound apple review-details` sends the contact, the demo account (from\n `.storebound/credentials.json`, masked in the log) and the notes.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion\n inside the app.\n", + "profiles/app/declarations.md": "# Example App · store declarations (answers to copy into the consoles)\n\nStarting answers for a typical **utility / productivity app**: an optional account (email + password, or sign in\nwith a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with\nstrangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything\nmarked **UI** has no API: Play Console > your app > Policy and programs > **App content** (https://play.google.com/console), and App Store Connect for\nApple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | \"All or some functionality is restricted\": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |\n| Ads | No, my app does not contain ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 18 and over (or 13+ if teens use it); \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |\n| Store settings | App, category (Productivity, Tools, ...); contact email + website |\n\n## Account deletion (account-deletion skill)\n\n- In the app: Settings > Account > Delete account deletes the account and its data (not only \"deactivate\").\n- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.\n- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with\nothers). Typical answers for a lists / notes app:\n\n| topic | answer |\n|---|---|\n| Violence, sexuality, language, controlled substances | No |\n| Gambling (simulated / real money) | No |\n| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |\n| Shares location with other users | No |\n| Digital purchases | No (Yes once you sell IAPs or subscriptions) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share\ncontent publicly.\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App (https://appstoreconnect.apple.com/apps). No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- App Review information: `storebound apple review-details` sends the contact, the demo account (from\n `.storebound/credentials.json`, masked in the log) and the notes.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion\n inside the app.\n", "profiles/app/listing.md": "# Example App · Google Play listing copy\n\nSource for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).\nRules the parser follows:\n- every `## ...` section with a `` line is a listing; one section can serve several Play codes\n- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and\n **what's new**, each followed by one fenced block\n- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters\nOnly claim what the app really does. No prices, no \"best / #1\", no other apps' names.\n\n---\n\n## English (en-US)\n\n\n### Play short description (max 80)\n```\nWhat the app does for people, in one plain line\n```\n\n### Play full description (max 4000)\n```\nFirst paragraph: the job the app does and why it is easier with it, in two or three sentences.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use. An account is optional and only needed for sync and sharing.\n```\n\n### What's new (max 500)\n```\nFirst release. Thanks for trying it!\n```\n\n## Türkçe (tr-TR)\n\n\n### Play kısa açıklama · short (max 80)\n```\nUygulamanın insanlar için ne yaptığını anlatan sade bir cümle\n```\n\n### Play tam açıklama · full (max 4000)\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu, iki üç cümlede.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\nÜcretsiz. Hesap isteğe bağlıdır; yalnızca eşitleme ve paylaşım için gerekir.\n```\n\n### Yenilikler · what's new (max 500)\n```\nİlk sürüm. Denediğin için teşekkürler!\n```\n", "profiles/app/privacy-policy.html": "\n\n\n\n\nPrivacy Policy · Example Studio\n\n\n\n\n
\n \n\n
\n

Privacy Policy

\n

Example Studio · applies to all our apps · last updated 2026-01-01

\n\n

We collect what an app needs to work for you, never sell personal data and show no ads. This page explains what\n each app collects, why, and what you can do about it.

\n\n

What our apps use

\n
\n \n \n \n \n \n
AppServiceWhat and whyYour choice
Example AppAccount (our servers)Email address, account id and the lists and notes you create: to sign you in and sync your content between your devices.The account is optional; export or delete it in Settings > Account
Example AppAnalytics (e.g. a privacy-friendly analytics SDK)Random install id, screens used, device model, OS version: to improve the app.Settings > Analytics off
Example AppCrash reportingCrash logs and device model: to fix crashes.-
Example AppApp storesPurchases and subscriptions are handled by Google Play / the App Store; we never see payment details.-
\n\n

Deleting your account

\n

In the app: Settings > Account > Delete account. Without the app: example.com/delete-account.\n We delete your account and its content within 30 days; backups roll over within 90 days. A store subscription is\n not cancelled by deleting the account: cancel it in your Google Play or App Store subscriptions.

\n\n

Children

\n

Our apps are not directed at children under 13. We do not knowingly collect personal data from children. If you\n believe a child sent us data, contact us and we will delete it.

\n\n

Retention and your rights

\n

We keep account data while the account exists and analytics data for up to 24 months. You can ask for a copy or\n the deletion of your data by writing to privacy@example.com; we answer\n within 30 days.

\n\n

Security

\n

All network traffic of our apps uses HTTPS; passwords are stored only as salted hashes.

\n\n

Changes

\n

When an app starts collecting something new we update this page and its date first.

\n\n

Contact

\n

Example Studio · privacy@example.com

\n
\n\n
\n

Gizlilik Politikası

\n

Example Studio · tüm uygulamalarımız için geçerlidir · son güncelleme 2026-01-01

\n

Yalnızca uygulamanın sizin için çalışması gereken verileri topluyoruz, kişisel verileri asla satmıyoruz ve reklam\n göstermiyoruz. Yukarıdaki tablo her uygulamanın neyi neden topladığını gösterir.

\n

Hesabınızı silmek

\n

Uygulamada: Ayarlar > Hesap > Hesabı sil. Uygulama olmadan: example.com/delete-account.\n Mağaza aboneliği hesap silinince iptal olmaz; Google Play veya App Store aboneliklerinizden iptal edin.

\n

Haklarınız

\n

Verilerinizin bir kopyasını veya silinmesini privacy@example.com adresine\n yazarak isteyebilirsiniz; 30 gün içinde yanıtlıyoruz.

\n
\n
\n\n\n", "profiles/app/shots-copy.json": "{\n \"en\": {\n \"home\": { \"eyebrow\": \"Example App\", \"title\": [\"Everything in order,\", \"in one calm place\"], \"sub\": \"Lists and notes, synced on all your devices.\" },\n \"detail\": { \"eyebrow\": \"Get it done\", \"title\": [\"See what is left\", \"at a glance\"], \"sub\": \"Tap to tick, swipe to reorder.\" },\n \"share\": { \"eyebrow\": \"Together\", \"title\": [\"Share a list,\", \"plan together\"], \"sub\": \"Invite people by link and edit live.\" },\n \"privacy\": { \"eyebrow\": \"Your data\", \"title\": [\"Private by default,\", \"yours to export\"], \"sub\": \"Export your data or delete your account in Settings.\" }\n },\n \"tr\": {\n \"home\": { \"eyebrow\": \"Example App\", \"title\": [\"Her şey düzenli,\", \"tek sakin bir yerde\"], \"sub\": \"Listeler ve notlar, tüm cihazlarında eşitlenir.\" },\n \"detail\": { \"eyebrow\": \"Bitir\", \"title\": [\"Kalanları\", \"bir bakışta gör\"], \"sub\": \"Dokun işaretle, sürükle sırala.\" },\n \"share\": { \"eyebrow\": \"Birlikte\", \"title\": [\"Listeyi paylaş,\", \"birlikte planla\"], \"sub\": \"Bağlantıyla davet et, canlı düzenleyin.\" },\n \"privacy\": { \"eyebrow\": \"Verilerin\", \"title\": [\"Varsayılan olarak gizli,\", \"dışa aktarması senin\"], \"sub\": \"Verilerini dışa aktar ya da hesabını Ayarlar'dan sil.\" }\n }\n}\n", "profiles/app/storebound.config.ts": "// storebound config for one app (profile \"app\": a utility / productivity app with an optional account and an\n// analytics SDK, no ads). Every answer below is a starting point: edit it to match your app. Credentials NEVER go\n// here (env vars or .storebound/credentials.json, gitignored). Paths are relative to this file.\n// The type import is optional: it only works when storebound is installed in the project (bun add -d storebound).\nimport type { ShipConfig } from 'storebound';\n\nconst config: ShipConfig = {\n // Every compliance answer below is the TEMPLATE's, not yours. While an area is listed here, check / validate block\n // and nothing sends it to a store. Derive the answers from the code (storebound scan --write; for an app that is\n // already live: storebound onboard --declarations), answer the rest, then remove the area from this list.\n unanswered: ['dataSafety', 'appPrivacy', 'reviewNotes', 'ageRating', 'accounts', 'reviewAccess'],\n\n app: {\n name: 'Example App',\n profile: 'app',\n copyright: '2026 Example Studio',\n privacyPolicyUrl: 'https://example.com/privacy',\n // termsUrl: 'https://example.com/terms', // Terms of Use; required once you sell subscriptions\n supportUrl: 'https://example.com/support',\n marketingUrl: 'https://example.com',\n website: 'https://example.com',\n contactEmail: 'support@example.com',\n },\n\n // Accounts (account-deletion skill). Remove this block if the app has no sign-up / sign-in at all.\n // Apple 5.1.1(v): deletion must start inside the app. Google Play: in the app AND on a public web page.\n accounts: {\n enabled: true,\n deletionUrl: 'https://example.com/delete-account', // public page, no login needed to request deletion\n inApp: 'Settings > Account > Delete account', // goes into the App Review notes\n // How people sign up: 'username' (a name picked in the app, no password, no email), 'username-password',\n // 'email-password', 'email-link', 'phone', 'oauth', 'other'. `storebound scan` proposes the account data and\n // dataSafety.accountCreation from it; only the 'email-*' methods mean an email address.\n signUp: ['email-password'],\n },\n\n // Sign-in for store reviewers (review-access skill). The demo account itself lives ONLY in\n // .storebound/credentials.json (\"reviewDemoAccount\") or REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD.\n reviewAccess: {\n signInRequired: true, // false if every feature works without signing in\n instructions: 'Sign in with the demo account. Sample data is already there; nothing needs a second device.',\n },\n\n // id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale\n languages: [\n { id: 'en', play: 'en-US', apple: 'en-US' },\n { id: 'tr', play: 'tr-TR', apple: 'tr' },\n ],\n\n play: {\n packageName: 'com.example.app',\n defaultLanguage: 'en-US',\n listing: 'store/listing.md',\n icon: 'store/icons/play-512.png',\n },\n\n apple: {\n bundleId: 'com.example.app',\n // name: 'Example: Short Tagline', // App Store name (max 30) when app.name is taken there (\"already being used\"); default app.name\n listing: 'store/appstore.md',\n // Export compliance (export-compliance skill): decide it, then uncomment. false ONLY if the app uses no encryption\n // beyond the OS (HTTPS through the system, standard sign-in).\n // usesNonExemptEncryption: false,\n // ipad: true,\n info: {\n categories: { primary: 'PRODUCTIVITY', secondary: 'UTILITIES' },\n ageRating: {}, // every content question NONE / false; change what applies (user-generated content, web access ...)\n contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT',\n free: true,\n reviewNotes: [\n 'Example App keeps lists and notes in sync across devices. An account is optional on the first screen.',\n 'Privacy policy and Terms: Settings > About.',\n ].join('\\n\\n'),\n },\n },\n\n // In-app purchases (optional): a one-time \"Pro\" unlock, for example. Subscriptions: see the subscription-app profile\n // (storebound init --profile subscription-app) or the store-subscriptions skill.\n // products: [{ id: 'com.example.app.pro', type: 'non_consumable', usd: 4.99,\n // listings: { en: { title: 'Pro', description: 'Unlimited lists and themes.' } }, reviewScreenshot: 'store/iap-review/pro.png' }],\n\n // Google Play Data safety. Types not listed = not collected. Typical app with accounts + an analytics SDK acting as\n // your processor (collected, not shared). Edit to match what your app and its SDKs really do.\n dataSafety: {\n collectsData: true,\n encryptedInTransit: true,\n // Sign-up methods. Google does not publish these ids: compare with Play Console > App content > Data safety >\n // Export to CSV before the first import. The delete-account URL (PSL_ACCOUNT_DELETION_URL) comes from\n // accounts.deletionUrl; `storebound check` names any other row Play requires.\n accountCreation: ['PSL_ACM_USER_ID_PASSWORD'],\n // Can people delete their data without deleting the account (a data-only page)? true needs partialDeletionUrl.\n // Play's \"can users request deletion?\" is answered yes by accounts.deletionUrl. Ask the owner.\n partialDeletion: false,\n data: {\n PSL_EMAIL: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_ACCOUNT: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_GENERATED_CONTENT: { shared: false, collect: ['functionality'] },\n PSL_USER_INTERACTION: { shared: false, collect: ['analytics'] },\n PSL_CRASH_LOGS: { shared: false, collect: ['analytics'] },\n PSL_PERFORMANCE_DIAGNOSTICS: { shared: false, collect: ['analytics'] },\n PSL_DEVICE_ID: { shared: false, collect: ['analytics'] },\n },\n },\n\n renders: 'store/renders',\n\n shots: {\n style: 'app', // calm gradient, device frame, headline + subline (game: sparkles and badges)\n copy: 'store/shots/copy.json',\n frames: 'store/shots/frames', // /phone/.png, /tablet/.png\n out: 'store/renders',\n // fonts: { title: 'Inter', body: 'Inter' }, // the app style default\n scenes: [\n { id: 'home', palette: 'mist', frames: [{ src: 'home.png' }] },\n { id: 'detail', palette: 'mint', frames: [{ src: 'detail.png' }] },\n { id: 'share', palette: 'sky', frames: [{ src: 'share.png' }] },\n { id: 'privacy', palette: 'slate', frames: [{ src: 'settings.png' }] },\n ],\n feature: { frame: 'home.png', palette: 'mist' },\n // Raw frames from a web build, the iOS Simulator (ios-sim) or an Android emulator (android-emu): sign in to a demo\n // account with realistic sample data, never real user data (store-screenshots skill).\n capture: {\n baseUrl: 'http://127.0.0.1:5173',\n query: 'lang={lang}&demo=1',\n shots: {\n 'home': { path: '/', delayMs: 1000, device: 'both' },\n 'detail': { path: '/lists/1', delayMs: 1000, device: 'both' },\n 'share': { path: '/lists/1/share', delayMs: 1000, device: 'both' },\n 'settings': { path: '/settings', delayMs: 1000, device: 'both' },\n },\n },\n },\n\n icons: {\n composite: 'store/icon/icon.png',\n foreground: 'store/icon/foreground.png',\n background: 'store/icon/background.png',\n out: 'store/icons',\n },\n};\n\nexport default config;\n", "profiles/app/workflows/beta.yml": "# storebound flow \"beta\": a TestFlight / internal testing build for your testers.\n# storebound flow run beta VERSION=1.2.0 [--apply]\ndescription: Next build to TestFlight and Play internal testing\nparams:\n VERSION: \"1.0.0\"\n GROUP: \"Internal\"\nsteps:\n - id: check\n run: check\n - id: next-build\n if: apple\n run: apple next-build\n capture: BUILD\n - id: build\n if: apple\n run: apple build --version ${VERSION} --build-number ${BUILD}\n - id: upload\n if: apple\n run: apple upload --no-wait --build ${BUILD} --allow-existing\n - id: wait\n if: apple\n run: apple testflight wait --build ${BUILD} --version ${VERSION}\n - id: what-to-test\n if: apple\n run: apple testflight what-to-test --build ${BUILD}\n - id: distribute\n if: apple\n run: apple testflight distribute --build ${BUILD} --group ${GROUP}\n - id: play-build\n if: play\n run: play build --version-name ${VERSION}\n - id: play-upload\n if: play\n run: play upload --track internal\n - id: external\n owner: \"External testers: storebound apple testflight distribute --build ${BUILD} --group --submit-review (Beta App Review is your call)\"\n", "profiles/app/workflows/release.yml": "# storebound flow \"release\": one version of the app on both stores, up to your Submit click.\n# storebound flow run release VERSION=1.2.0 reads run, writes run as dry runs (nothing changes)\n# storebound flow run release VERSION=1.2.0 --apply writes run for real; each command still asks you first\n# storebound flow run release --resume --apply continue after fixing a failed step\n# Steps are storebound commands (no \"storebound\" prefix). `if: apple` / `if: play` skip a store the config does not\n# have; `owner:` steps are never run, they list what you click. Edit freely: this file is yours.\ndescription: Build, upload and check one version on both stores, then your Submit click\nparams:\n VERSION: \"1.0.0\"\nsteps:\n - id: next-build\n if: apple\n run: apple next-build\n capture: BUILD\n - id: build\n if: apple\n run: apple build --version ${VERSION} --build-number ${BUILD}\n - id: upload\n if: apple\n run: apple upload --no-wait --build ${BUILD} --allow-existing\n - id: wait\n if: apple\n run: apple testflight wait --build ${BUILD} --version ${VERSION}\n - id: play-build\n if: play\n run: play build --version-name ${VERSION}\n - id: play-upload\n if: play\n run: play upload --track internal\n - id: whats-new\n if: exists store/whats-new\n run: whats-new apply\n - id: listing\n if: apple\n run: apple listing --version ${VERSION} --build ${BUILD}\n - id: validate\n run: validate\n - id: scan\n run: scan\n - id: plan\n run: release plan ${VERSION}\n - id: submit\n owner: \"App Store Connect: Submit for Review (version ${VERSION}, build ${BUILD}). Play Console: promote the internal release and Send changes for review\"\n", - "profiles/subscription-app/app-privacy.md": "# Example App · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety\nanswers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**\n\nTypical subscription app: account (email), content synced to your backend, auto-renewable subscriptions checked by\nyour server, an analytics SDK acting as your processor, no ads, no tracking.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that\nchanges, add the App Tracking Transparency prompt first.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Contact Info > Email Address | yes (account) | no | yes | App Functionality |\n| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |\n| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |\n| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |\n| Purchases > Purchase History (subscription status) | yes | no | yes | App Functionality |\n| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |\n| Diagnostics > Performance Data | yes | no | no | Analytics |\n\n\"Linked to you\" is **Yes** for what you store under the account. Not collected (typical): Location, Health and\nFitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,\nSensitive Info.\n\nPer data type the UI asks, in order: purposes > \"Is this data linked to the user's identity?\" > \"Do you or your\nthird-party partners use this data for tracking purposes?\".\n", + "profiles/subscription-app/app-privacy.md": "# Example App · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy (https://appstoreconnect.apple.com/apps, then the app; `storebound console plan apple-app-privacy` gives the deep link). **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety\nanswers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**\n\nTypical subscription app: account (email), content synced to your backend, auto-renewable subscriptions checked by\nyour server, an analytics SDK acting as your processor, no ads, no tracking.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that\nchanges, add the App Tracking Transparency prompt first.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Contact Info > Email Address | yes (account) | no | yes | App Functionality |\n| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |\n| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |\n| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |\n| Purchases > Purchase History (subscription status) | yes | no | yes | App Functionality |\n| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |\n| Diagnostics > Performance Data | yes | no | no | Analytics |\n\n\"Linked to you\" is **Yes** for what you store under the account. Not collected (typical): Location, Health and\nFitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,\nSensitive Info.\n\nPer data type the UI asks, in order: purposes > \"Is this data linked to the user's identity?\" > \"Do you or your\nthird-party partners use this data for tracking purposes?\".\n", "profiles/subscription-app/appstore.md": "# Example App · App Store listing copy\n\nSource for `storebound apple listing`. One `## ` section per locale (the first word is the locale code:\n`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### `, field names exactly:\n`name` (optional, defaults to `apple.name`, else `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 characters, comma\nseparated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed\non the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.\n\n---\n\n## en-US · English (U.S.)\n\n### subtitle\n```\nLists and notes, in sync\n```\n\n### promotionalText\n```\nOne or two sentences you can change any time without a new version.\n```\n\n### keywords\n```\ntodo,checklist,groceries,planner,notes,shared,family,organizer,tasks,sync\n```\n\n### description\n```\nFirst paragraph: the job the app does and why it is easier with it.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use with up to 3 lists. Example Pro unlocks unlimited lists, sharing and themes: yearly with a 1-week free trial, or monthly. Payment is charged to your store account; the subscription renews automatically unless cancelled at least 24 hours before the end of the period, and you can manage or cancel it in your account settings.\n\nTerms of Use: https://example.com/terms\nPrivacy Policy: https://example.com/privacy\n```\n\n## tr · Türkçe\n\n### subtitle\n```\nListeler ve notlar, eşitli\n```\n\n### promotionalText\n```\nYeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.\n```\n\n### keywords\n```\nyapılacaklar,kontrol,market,planlayıcı,not,paylaşım,aile,düzen,görev\n```\n\n### description\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\n3 listeye kadar ücretsiz. Example Pro sınırsız liste, paylaşım ve tema açar: 1 hafta ücretsiz denemeli yıllık ya da aylık. Ödeme mağaza hesabınızdan alınır; abonelik, dönem bitmeden en az 24 saat önce iptal edilmezse otomatik yenilenir; hesap ayarlarınızdan yönetebilir ya da iptal edebilirsiniz.\n\nKullanım Koşulları: https://example.com/terms\nGizlilik Politikası: https://example.com/privacy\n```\n", - "profiles/subscription-app/declarations.md": "# Example App · store declarations (answers to copy into the consoles)\n\nStarting answers for a typical **subscription app**: auto-renewable subscriptions (free trial on the yearly plan), an account (email + password, or sign in\nwith a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with\nstrangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything\nmarked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for\nApple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | \"All or some functionality is restricted\": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |\n| Ads | No, my app does not contain ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 18 and over (or 13+ if teens use it); \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |\n| Store settings | App, category (Productivity, Tools, ...); contact email + website |\n\n## Account deletion (account-deletion skill)\n\n- In the app: Settings > Account > Delete account deletes the account and its data (not only \"deactivate\").\n- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.\n- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with\nothers). Typical answers for a lists / notes app:\n\n| topic | answer |\n|---|---|\n| Violence, sexuality, language, controlled substances | No |\n| Gambling (simulated / real money) | No |\n| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |\n| Shares location with other users | No |\n| Digital purchases | Yes (subscriptions) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share\ncontent publicly.\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App. No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- App Review information: `storebound apple review-details` sends the contact, the demo account (from\n `.storebound/credentials.json`, masked in the log) and the notes.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion\n inside the app.\n\n## Subscriptions (store-subscriptions skill)\n\n- Purchase screen (both stores): plan name, length, the full renewal price per period (most prominent), what is\n included, free trial length and what is charged after it, how to cancel, Restore Purchases, links to the Terms of Use\n and the Privacy Policy. Apple 3.1.2; Google Play subscriptions policy.\n- Metadata: the App Store description (or a custom EULA in App Information > License Agreement) links the Terms of\n Use; the Privacy Policy URL field is set by `storebound apple listing`.\n- First subscriptions are submitted together with an app version (App Store Connect: add them to the version on its\n page before Submit for Review), like first in-app purchases.\n- App Review notes say where the paywall is and that sandbox purchases work with the demo account.\n- Play: base plans and offers must be active before people can buy; new subscribers see the converted regional prices.\n", + "profiles/subscription-app/declarations.md": "# Example App · store declarations (answers to copy into the consoles)\n\nStarting answers for a typical **subscription app**: auto-renewable subscriptions (free trial on the yearly plan), an account (email + password, or sign in\nwith a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with\nstrangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything\nmarked **UI** has no API: Play Console > your app > Policy and programs > **App content** (https://play.google.com/console), and App Store Connect for\nApple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | \"All or some functionality is restricted\": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |\n| Ads | No, my app does not contain ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 18 and over (or 13+ if teens use it); \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |\n| Store settings | App, category (Productivity, Tools, ...); contact email + website |\n\n## Account deletion (account-deletion skill)\n\n- In the app: Settings > Account > Delete account deletes the account and its data (not only \"deactivate\").\n- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.\n- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with\nothers). Typical answers for a lists / notes app:\n\n| topic | answer |\n|---|---|\n| Violence, sexuality, language, controlled substances | No |\n| Gambling (simulated / real money) | No |\n| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |\n| Shares location with other users | No |\n| Digital purchases | Yes (subscriptions) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share\ncontent publicly.\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App (https://appstoreconnect.apple.com/apps). No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- App Review information: `storebound apple review-details` sends the contact, the demo account (from\n `.storebound/credentials.json`, masked in the log) and the notes.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion\n inside the app.\n\n## Subscriptions (store-subscriptions skill)\n\n- Purchase screen (both stores): plan name, length, the full renewal price per period (most prominent), what is\n included, free trial length and what is charged after it, how to cancel, Restore Purchases, links to the Terms of Use\n and the Privacy Policy. Apple 3.1.2; Google Play subscriptions policy.\n- Metadata: the App Store description (or a custom EULA in App Information > License Agreement) links the Terms of\n Use; the Privacy Policy URL field is set by `storebound apple listing`.\n- First subscriptions are submitted together with an app version (App Store Connect: add them to the version on its\n page before Submit for Review), like first in-app purchases.\n- App Review notes say where the paywall is and that sandbox purchases work with the demo account.\n- Play: base plans and offers must be active before people can buy; new subscribers see the converted regional prices.\n", "profiles/subscription-app/listing.md": "# Example App · Google Play listing copy\n\nSource for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).\nRules the parser follows:\n- every `## ...` section with a `` line is a listing; one section can serve several Play codes\n- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and\n **what's new**, each followed by one fenced block\n- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters\nOnly claim what the app really does. No prices, no \"best / #1\", no other apps' names.\n\n---\n\n## English (en-US)\n\n\n### Play short description (max 80)\n```\nWhat the app does for people, in one plain line\n```\n\n### Play full description (max 4000)\n```\nFirst paragraph: the job the app does and why it is easier with it, in two or three sentences.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use with up to 3 lists. Example Pro unlocks unlimited lists, sharing and themes: yearly with a 1-week free trial, or monthly. Payment is charged to your store account; the subscription renews automatically unless cancelled at least 24 hours before the end of the period, and you can manage or cancel it in your account settings.\n\nTerms of Use: https://example.com/terms\nPrivacy Policy: https://example.com/privacy\n```\n\n### What's new (max 500)\n```\nFirst release. Thanks for trying it!\n```\n\n## Türkçe (tr-TR)\n\n\n### Play kısa açıklama · short (max 80)\n```\nUygulamanın insanlar için ne yaptığını anlatan sade bir cümle\n```\n\n### Play tam açıklama · full (max 4000)\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu, iki üç cümlede.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\n3 listeye kadar ücretsiz. Example Pro sınırsız liste, paylaşım ve tema açar: 1 hafta ücretsiz denemeli yıllık ya da aylık. Ödeme mağaza hesabınızdan alınır; abonelik, dönem bitmeden en az 24 saat önce iptal edilmezse otomatik yenilenir; hesap ayarlarınızdan yönetebilir ya da iptal edebilirsiniz.\n\nKullanım Koşulları: https://example.com/terms\nGizlilik Politikası: https://example.com/privacy\n```\n\n### Yenilikler · what's new (max 500)\n```\nİlk sürüm. Denediğin için teşekkürler!\n```\n", "profiles/subscription-app/storebound.config.ts": "// storebound config for one app (profile \"subscription-app\": accounts plus auto-renewable subscriptions, an\n// analytics SDK, no ads). Every answer below is a starting point: edit it to match your app. Credentials NEVER go\n// here (env vars or .storebound/credentials.json, gitignored). Paths are relative to this file.\n// The type import is optional: it only works when storebound is installed in the project (bun add -d storebound).\nimport type { ShipConfig } from 'storebound';\n\nconst config: ShipConfig = {\n // Every compliance answer below is the TEMPLATE's, not yours. While an area is listed here, check / validate block\n // and nothing sends it to a store. Derive the answers from the code (storebound scan --write; for an app that is\n // already live: storebound onboard --declarations), answer the rest, then remove the area from this list.\n unanswered: ['dataSafety', 'appPrivacy', 'reviewNotes', 'ageRating', 'accounts', 'reviewAccess'],\n\n app: {\n name: 'Example App',\n profile: 'subscription-app',\n copyright: '2026 Example Studio',\n privacyPolicyUrl: 'https://example.com/privacy',\n termsUrl: 'https://example.com/terms', // Terms of Use (EULA): in the app and the metadata (Apple 3.1.2)\n supportUrl: 'https://example.com/support',\n marketingUrl: 'https://example.com',\n website: 'https://example.com',\n contactEmail: 'support@example.com',\n },\n\n // Accounts (account-deletion skill). Remove this block if the app has no sign-up / sign-in at all.\n // Apple 5.1.1(v): deletion must start inside the app. Google Play: in the app AND on a public web page.\n accounts: {\n enabled: true,\n deletionUrl: 'https://example.com/delete-account', // public page, no login needed to request deletion\n inApp: 'Settings > Account > Delete account', // goes into the App Review notes\n // How people sign up: 'username' (a name picked in the app, no password, no email), 'username-password',\n // 'email-password', 'email-link', 'phone', 'oauth', 'other'. `storebound scan` proposes the account data and\n // dataSafety.accountCreation from it; only the 'email-*' methods mean an email address.\n signUp: ['email-password'],\n },\n\n // Sign-in for store reviewers (review-access skill). The demo account itself lives ONLY in\n // .storebound/credentials.json (\"reviewDemoAccount\") or REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD.\n reviewAccess: {\n signInRequired: true, // false if every feature works without signing in\n instructions: 'Sign in with the demo account. Sample data is already there; nothing needs a second device.',\n },\n\n // id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale\n languages: [\n { id: 'en', play: 'en-US', apple: 'en-US' },\n { id: 'tr', play: 'tr-TR', apple: 'tr' },\n ],\n\n play: {\n packageName: 'com.example.app',\n defaultLanguage: 'en-US',\n listing: 'store/listing.md',\n icon: 'store/icons/play-512.png',\n },\n\n apple: {\n bundleId: 'com.example.app',\n // name: 'Example: Short Tagline', // App Store name (max 30) when app.name is taken there (\"already being used\"); default app.name\n listing: 'store/appstore.md',\n // Export compliance (export-compliance skill): decide it, then uncomment. false ONLY if the app uses no encryption\n // beyond the OS (HTTPS through the system, standard sign-in).\n // usesNonExemptEncryption: false,\n // ipad: true,\n info: {\n categories: { primary: 'PRODUCTIVITY', secondary: 'UTILITIES' },\n ageRating: {}, // every content question NONE / false; change what applies (user-generated content, web access ...)\n contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT',\n free: true,\n reviewNotes: [\n 'Example App keeps lists and notes in sync across devices. Pro (yearly with a 1-week free trial, or monthly) unlocks unlimited lists, sharing and themes.',\n 'Subscriptions: Settings > Pro (or the paywall after the third list). Restore Purchases is on the paywall and in Settings > Pro.',\n 'Privacy policy and Terms of Use: on the paywall and in Settings > About.',\n ].join('\\n\\n'),\n },\n },\n\n // Auto-renewable subscriptions (store-subscriptions skill): `storebound apple subscriptions`, `storebound play\n // subscriptions`. Apple: one group, levels rank the plans (1 = highest). Play: one subscription per entry with one\n // auto-renewing base plan. Prices: USD base, Apple equalizes from the USA, Play converts per region; overrides set a\n // region by hand. The disclosure (price per period, renewal, how to cancel, Terms + Privacy links) belongs on the\n // purchase screen and in the descriptions.\n subscriptions: [\n {\n name: 'Example Pro',\n listings: { en: { name: 'Example Pro' }, tr: { name: 'Example Pro' } },\n subscriptions: [\n {\n id: 'com.example.app.pro_yearly', period: 'P1Y', level: 1, usd: 29.99,\n listings: {\n en: { title: 'Pro yearly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] },\n tr: { title: 'Pro yıllık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] },\n },\n intro: { type: 'free', duration: 'P1W' }, // 1 week free trial for new subscribers\n reviewScreenshot: 'store/iap-review/paywall.png',\n },\n {\n id: 'com.example.app.pro_monthly', period: 'P1M', level: 2, usd: 3.99,\n overrides: [{ region: 'TR', currency: 'TRY', price: 99.99 }],\n listings: {\n en: { title: 'Pro monthly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] },\n tr: { title: 'Pro aylık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] },\n },\n reviewScreenshot: 'store/iap-review/paywall.png',\n },\n ],\n },\n ],\n\n // Google Play Data safety. Types not listed = not collected. Typical app with accounts + an analytics SDK acting as\n // your processor (collected, not shared). Edit to match what your app and its SDKs really do.\n dataSafety: {\n collectsData: true,\n encryptedInTransit: true,\n // Sign-up methods. Google does not publish these ids: compare with Play Console > App content > Data safety >\n // Export to CSV before the first import. The delete-account URL (PSL_ACCOUNT_DELETION_URL) comes from\n // accounts.deletionUrl; `storebound check` names any other row Play requires.\n accountCreation: ['PSL_ACM_USER_ID_PASSWORD'],\n // Can people delete their data without deleting the account (a data-only page)? true needs partialDeletionUrl.\n // Play's \"can users request deletion?\" is answered yes by accounts.deletionUrl. Ask the owner.\n partialDeletion: false,\n data: {\n PSL_EMAIL: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_ACCOUNT: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_GENERATED_CONTENT: { shared: false, collect: ['functionality'] },\n PSL_USER_INTERACTION: { shared: false, collect: ['analytics'] },\n PSL_CRASH_LOGS: { shared: false, collect: ['analytics'] },\n PSL_PERFORMANCE_DIAGNOSTICS: { shared: false, collect: ['analytics'] },\n PSL_DEVICE_ID: { shared: false, collect: ['analytics'] },\n PSL_PURCHASE_HISTORY: { shared: false, collect: ['functionality'] }, // subscription status tied to the account\n },\n },\n\n renders: 'store/renders',\n\n shots: {\n style: 'app', // calm gradient, device frame, headline + subline (game: sparkles and badges)\n copy: 'store/shots/copy.json',\n frames: 'store/shots/frames', // /phone/.png, /tablet/.png\n out: 'store/renders',\n // fonts: { title: 'Inter', body: 'Inter' }, // the app style default\n scenes: [\n { id: 'home', palette: 'mist', frames: [{ src: 'home.png' }] },\n { id: 'detail', palette: 'mint', frames: [{ src: 'detail.png' }] },\n { id: 'share', palette: 'sky', frames: [{ src: 'share.png' }] },\n { id: 'privacy', palette: 'slate', frames: [{ src: 'settings.png' }] },\n ],\n feature: { frame: 'home.png', palette: 'mist' },\n // Raw frames from a web build, the iOS Simulator (ios-sim) or an Android emulator (android-emu): sign in to a demo\n // account with realistic sample data, never real user data (store-screenshots skill).\n capture: {\n baseUrl: 'http://127.0.0.1:5173',\n query: 'lang={lang}&demo=1',\n shots: {\n 'home': { path: '/', delayMs: 1000, device: 'both' },\n 'detail': { path: '/lists/1', delayMs: 1000, device: 'both' },\n 'share': { path: '/lists/1/share', delayMs: 1000, device: 'both' },\n 'settings': { path: '/settings', delayMs: 1000, device: 'both' },\n },\n },\n },\n\n icons: {\n composite: 'store/icon/icon.png',\n foreground: 'store/icon/foreground.png',\n background: 'store/icon/background.png',\n out: 'store/icons',\n },\n};\n\nexport default config;\n", "shots-copy.json": "{\n \"en\": {\n \"hero\": { \"title\": [\"Tap. Match.\", \"Repeat!\"], \"badges\": [[\"100+ levels\", \"and more every update\"], [\"Play offline\", \"No account needed\"]] },\n \"play\": { \"eyebrow\": \"Play\", \"title\": [\"Easy to learn,\", \"hard to put down\"], \"sub\": \"One thumb, short rounds, big satisfying moments.\", \"badges\": [[\"Quick rounds\", \"Two minutes each\"], [\"Great!\"]] },\n \"levels\": { \"eyebrow\": \"Worlds\", \"title\": [\"Explore new\", \"worlds\"], \"sub\": \"A fresh look and new rules every ten levels.\", \"badges\": [[\"10 worlds\", \"A new one every 10 levels\"]] },\n \"stars\": { \"eyebrow\": \"Stars\", \"title\": [\"Play smart,\", \"grab 3 stars\"], \"sub\": \"Finish with moves to spare and collect every star.\", \"badges\": [[\"3 stars\"]] }\n },\n \"tr\": {\n \"hero\": { \"title\": [\"Dokun. Eşleştir.\", \"Tekrarla!\"], \"badges\": [[\"100+ bölüm\", \"her güncellemede yenileri\"], [\"İnternetsiz\", \"hesap gerekmez\"]] },\n \"play\": { \"eyebrow\": \"Oyna\", \"title\": [\"Öğrenmesi kolay,\", \"bırakması zor\"], \"sub\": \"Tek parmak, kısa turlar, büyük keyifli anlar.\", \"badges\": [[\"Kısa turlar\", \"Her biri iki dakika\"], [\"Harika!\"]] },\n \"levels\": { \"eyebrow\": \"Dünyalar\", \"title\": [\"Yeni dünyaları\", \"keşfet\"], \"sub\": \"Her on bölümde yeni bir görünüm ve yeni kurallar.\", \"badges\": [[\"10 dünya\", \"Her 10 bölümde yeni biri\"]] },\n \"stars\": { \"eyebrow\": \"Yıldızlar\", \"title\": [\"Akıllı oyna,\", \"3 yıldızı kap\"], \"sub\": \"Hamle artırarak bitir, tüm yıldızları topla.\", \"badges\": [[\"3 yıldız\"]] }\n },\n \"es\": {\n \"hero\": { \"title\": [\"Toca. Combina.\", \"¡Repite!\"], \"badges\": [[\"100+ niveles\", \"y más en cada versión\"], [\"Sin conexión\", \"Sin cuenta\"]] },\n \"play\": { \"eyebrow\": \"Juega\", \"title\": [\"Fácil de aprender,\", \"difícil de soltar\"], \"sub\": \"Un pulgar, rondas cortas, grandes momentos.\", \"badges\": [[\"Rondas cortas\", \"Dos minutos cada una\"], [\"¡Genial!\"]] },\n \"levels\": { \"eyebrow\": \"Mundos\", \"title\": [\"Explora\", \"nuevos mundos\"], \"sub\": \"Un aspecto nuevo y nuevas reglas cada diez niveles.\", \"badges\": [[\"10 mundos\", \"Uno nuevo cada 10 niveles\"]] },\n \"stars\": { \"eyebrow\": \"Estrellas\", \"title\": [\"Juega con cabeza,\", \"logra 3 estrellas\"], \"sub\": \"Termina con movimientos de sobra y consigue todas las estrellas.\", \"badges\": [[\"3 estrellas\"]] }\n },\n \"ja\": {\n \"hero\": { \"title\": [\"タップして、そろえて、\", \"もう一回!\"], \"badges\": [[\"100以上のステージ\", \"アップデートで追加\"], [\"オフラインで遊べる\", \"アカウント不要\"]] },\n \"play\": { \"eyebrow\": \"プレイ\", \"title\": [\"かんたんなのに\", \"やめられない\"], \"sub\": \"片手で遊べる短いラウンド、気持ちいい瞬間がたくさん。\", \"badges\": [[\"短いラウンド\", \"1回2分\"], [\"すごい!\"]] },\n \"levels\": { \"eyebrow\": \"ワールド\", \"title\": [\"新しいワールドを\", \"冒険しよう\"], \"sub\": \"10ステージごとに新しい見た目と新しいルール。\", \"badges\": [[\"10のワールド\", \"10ステージごとに追加\"]] },\n \"stars\": { \"eyebrow\": \"スター\", \"title\": [\"かしこく遊んで\", \"星を3つゲット\"], \"sub\": \"手数を残してクリアし、すべての星を集めよう。\", \"badges\": [[\"星3つ\"]] }\n },\n \"ar\": {\n \"hero\": { \"title\": [\"انقر. طابق.\", \"كرّر!\"], \"badges\": [[\"أكثر من 100 مرحلة\", \"والمزيد مع كل تحديث\"], [\"العب دون اتصال\", \"دون حساب\"]] },\n \"play\": { \"eyebrow\": \"العب\", \"title\": [\"سهلة التعلّم،\", \"صعبة التوقف\"], \"sub\": \"إبهام واحد، جولات قصيرة، ولحظات ممتعة.\", \"badges\": [[\"جولات قصيرة\", \"دقيقتان لكل جولة\"], [\"رائع!\"]] },\n \"levels\": { \"eyebrow\": \"العوالم\", \"title\": [\"استكشف\", \"عوالم جديدة\"], \"sub\": \"شكل جديد وقواعد جديدة كل عشر مراحل.\", \"badges\": [[\"10 عوالم\", \"عالم جديد كل 10 مراحل\"]] },\n \"stars\": { \"eyebrow\": \"النجوم\", \"title\": [\"العب بذكاء،\", \"واجمع 3 نجوم\"], \"sub\": \"أنهِ المرحلة بحركات متبقية واجمع كل النجوم.\", \"badges\": [[\"3 نجوم\"]] }\n }\n}\n", diff --git a/cli/lib/fastlane.ts b/cli/lib/fastlane.ts index 292c9d4..36a04ab 100644 --- a/cli/lib/fastlane.ts +++ b/cli/lib/fastlane.ts @@ -4,6 +4,7 @@ // run, only plain `key("value")` lines are taken, everything else is reported as "set by hand". // Secrets and personal data never enter the plan's files: key files are named as references to move into .storebound/, // App Review contact and demo account files are reported (the owner copies them), team / app ids become placeholders. +import { consoleLink } from './console-links'; import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; import { basename, dirname, extname, join, relative, resolve, sep } from 'node:path'; import { imageInfo } from '../shots/spec'; @@ -426,7 +427,7 @@ export function planImport(scan: FastlaneScan, o: PlanOptions): ImportPlan { else notMapped.push({ what: `${f}: "${raw}"`, where: 'apple.info.categories in storebound.config.ts (App Store Connect ids such as GAMES, PRODUCTIVITY, GAMES_PUZZLE)' }); } for (const f of ['secondary_first_sub_category', 'secondary_second_sub_category']) - if (scan.deliver.root[f]) notMapped.push({ what: `${f}.txt`, where: 'set in App Store Connect > App Information (storebound sets primary sub-categories only)' }); + if (scan.deliver.root[f]) notMapped.push({ what: `${f}.txt`, where: `set in ${consoleLink('asc.app-info')} (storebound sets primary sub-categories only)` }); const profile: AppProfile = o.profile ?? (cats.primary === 'GAMES' ? 'game' : 'app'); const style: 'game' | 'app' = profile === 'game' ? 'game' : 'app'; @@ -440,7 +441,7 @@ export function planImport(scan: FastlaneScan, o: PlanOptions): ImportPlan { if (demo.length) secrets.push({ what: `review_information/${demo.join(', ')} (App Review demo account)`, where: 'not copied: the owner puts it in .storebound/credentials.json "reviewDemoAccount": { username, password } (review-access skill); reviewAccess.signInRequired was set to true' }); const signIn = demo.includes('demo_user.txt'); if (scan.deliver.metadata && isDir(join(scan.deliver.metadata, 'trade_representative_contact_information'))) - notMapped.push({ what: 'trade_representative_contact_information/ (Korea)', where: 'App Store Connect > App Information (personal data, not imported)' }); + notMapped.push({ what: 'trade_representative_contact_information/ (Korea)', where: `${consoleLink('asc.app-info')} (personal data, not imported)` }); // --- App Store listing const appleSections: string[] = []; @@ -535,8 +536,8 @@ ${secs.join('\n\n')} const id = idOfPlay(l.code)!; const fg = l.images.featureGraphic; if (fg) { const p = `store/renders/${id}/feature/1024x500${extname(fg).toLowerCase().replace('.jpeg', '.jpg')}`; files.push({ path: p, from: fg, src: rel(fg), kind: 'feature' }); mapped.push({ from: rel(fg), to: p }); } - for (const k of ['promoGraphic', 'tvBanner']) if (l.images[k]) notMapped.push({ what: rel(l.images[k]!), where: 'Play Console > Store listing (storebound uploads icon, feature graphic and phone / tablet screenshots)' }); - for (const k of ['tv', 'wear'] as const) if (l.shots[k].length) notMapped.push({ what: `metadata/android/${l.folder}/images/${k}Screenshots (${l.shots[k].length})`, where: `Play Console > Store listing (${k === 'tv' ? 'Android TV' : 'Wear OS'} screenshots)` }); + for (const k of ['promoGraphic', 'tvBanner']) if (l.images[k]) notMapped.push({ what: rel(l.images[k]!), where: `${consoleLink('play.store-listing')} (storebound uploads icon, feature graphic and phone / tablet screenshots)` }); + for (const k of ['tv', 'wear'] as const) if (l.shots[k].length) notMapped.push({ what: `metadata/android/${l.folder}/images/${k}Screenshots (${l.shots[k].length})`, where: `${consoleLink('play.store-listing')} (${k === 'tv' ? 'Android TV' : 'Wear OS'} screenshots)` }); } // screenshots -> frames: ///NN.png; iOS shots win over Play shots for the same language diff --git a/cli/lib/health.ts b/cli/lib/health.ts index c4fe084..3c935a9 100644 --- a/cli/lib/health.ts +++ b/cli/lib/health.ts @@ -16,6 +16,7 @@ // https://developer.apple.com/documentation/appstoreconnectapi/power-and-performance-metrics-and-logs // GET /v1/apps/{id}/perfPowerMetrics (application/vnd.apple.xcode-metrics+json), GET /v1/builds/{id}/diagnosticSignatures // (HANGS, DISK_WRITES, LAUNCHES), GET /v1/apps/{id}/betaFeedbackCrashSubmissions (TestFlight crash reports, API 4.0+). +import { CONSOLE_PAGES, consoleLink, consoleUrl } from './console-links'; import { type CrashHint, crashHint } from './crash-hints'; import type { Finding, Level } from './validate-rules'; import { mdTable, pct, textTable } from './table'; @@ -265,7 +266,7 @@ export function healthFindings(h: HealthSnapshot, o: { unreadable?: Level; princ f.push(g?.kind === 'api-disabled' || REPORTING_OFF.test(raw) ? { store: 'play', level: o.unreadable ?? 'warn', code: 'vitals', msg: `vitals not readable: the ${g?.kind === 'api-disabled' ? g.serviceTitle : 'Google Play Developer Reporting API'} is off in the service account's Google Cloud project${(g?.kind === 'api-disabled' ? g.projectId : on.project) ? ` ${g?.kind === 'api-disabled' ? g.projectId : on.project}` : ''}`, fix: `enable "${g?.kind === 'api-disabled' ? g.serviceTitle : 'Google Play Developer Reporting API'}" for that project: ${g?.kind === 'api-disabled' ? g.activationUrl : on.url} (Enable), wait a few minutes (store-health skill)` } - : { store: 'play', level: o.unreadable ?? 'warn', code: 'vitals', msg: `vitals not readable: ${g ? findingText(g) : plainError(raw).slice(0, 200)}`, fix: g?.kind === 'no-app-access' ? `Play Console > Users and permissions: ${g.usersUrl}` : 'store-health skill (Reporting API access)' }); + : { store: 'play', level: o.unreadable ?? 'warn', code: 'vitals', msg: `vitals not readable: ${g ? findingText(g) : plainError(raw).slice(0, 200)}`, fix: g?.kind === 'no-app-access' ? `${CONSOLE_PAGES['play.users'].label}: ${g.usersUrl}` : 'store-health skill (Reporting API access)' }); } else if (play) { const crash = lastValue(play.crash, 'userPerceivedCrashRate28dUserWeighted') ?? weightedAverage(play.crash, 'userPerceivedCrashRate'); @@ -276,7 +277,7 @@ export function healthFindings(h: HealthSnapshot, o: { unreadable?: Level; princ f.push({ store: 'play', level: levelFor(crash, PLAY_THRESHOLDS.crash), code: 'vitals-crash', msg: `user-perceived crash rate (28 d) ${pct(crash)} (bad behavior threshold ${pct(PLAY_THRESHOLDS.crash)})`, fix }); f.push({ store: 'play', level: levelFor(anr, PLAY_THRESHOLDS.anr), code: 'vitals-anr', msg: `user-perceived ANR rate (28 d) ${pct(anr)} (bad behavior threshold ${pct(PLAY_THRESHOLDS.anr)})`, fix }); } - if (play.issuesError) f.push({ store: 'play', level: 'warn', code: 'vitals-issues', msg: `top crash / ANR issues not read: ${play.issuesError.slice(0, 200)}`, fix: 'Play Console > Android vitals > Crashes and ANRs' }); + if (play.issuesError) f.push({ store: 'play', level: 'warn', code: 'vitals-issues', msg: `top crash / ANR issues not read: ${play.issuesError.slice(0, 200)}`, fix: consoleLink('play.vitals', '> Crashes and ANRs'), url: consoleUrl('play.vitals') }); for (const d of play.devices ?? []) { const worst = Math.max(d.crash ?? 0, d.anr ?? 0); const lv = levelFor(worst, PLAY_THRESHOLDS.perDevice); diff --git a/cli/lib/registry.ts b/cli/lib/registry.ts index c7f87b0..e16a6ee 100644 --- a/cli/lib/registry.ts +++ b/cli/lib/registry.ts @@ -10,6 +10,7 @@ // --app-id --account `). The desktop shows them read-only (status, reviews, analytics) under that // profile's account, noted "no local project"; they carry no path. Absent when there are none (older readers ignore it). // Written atomically (temp file, rename); a missing or unreadable file reads as empty (an unreadable one is kept). +import { consoleLink } from './console-links'; import { createHash } from 'node:crypto'; import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'; import { homedir } from 'node:os'; @@ -93,7 +94,7 @@ export function withFolder(r: Registry, path: string, name: string | undefined, /** Adds a store-only app (the same store + app id again updates its account, name and bundle id). Pure. */ export function withStoreApp(r: Registry, a: { store: 'apple' | 'play'; appId: string; account?: string | null; name?: string; bundleId?: string | null }, now: string): { registry: Registry; app: StoreOnlyApp; added: boolean } { const appId = a.appId.trim(); - if (!(a.store === 'apple' ? APPLE_APP_ID : PACKAGE_NAME).test(appId)) throw new Error(a.store === 'apple' ? `--app-id ${appId}: the numeric Apple ID of the app (App Store Connect > App Information)` : `--app-id ${appId}: the Play package name (com.example.app)`); + if (!(a.store === 'apple' ? APPLE_APP_ID : PACKAGE_NAME).test(appId)) throw new Error(a.store === 'apple' ? `--app-id ${appId}: the numeric Apple ID of the app (${consoleLink('asc.app-info')})` : `--app-id ${appId}: the Play package name (com.example.app)`); const list = r.storeApps ?? []; const have = list.find(x => x.store === a.store && x.appId === appId); const app: StoreOnlyApp = { id: storeAppId(a.store, appId), store: a.store, appId, account: a.account ?? have?.account ?? null, name: a.name?.trim() || have?.name || appId, diff --git a/cli/lib/scan/play-rules.ts b/cli/lib/scan/play-rules.ts index 4030799..7cda53e 100644 --- a/cli/lib/scan/play-rules.ts +++ b/cli/lib/scan/play-rules.ts @@ -52,6 +52,7 @@ // - Impersonation: answer/9888374 (don't imply a relation with someone you are not, e.g. in the title or developer // name). The brand list below is storebound's own short list of well-known app brands, reported as low "check". // Play Integrity / anti-tamper is not a policy: no rule. +import { consoleLink } from '../console-links'; import { PLAY_AGE_GROUPS } from '../config'; import { allSubscriptions } from '../subscriptions'; import { grep, type Hit, type ProjectFacts } from './project'; @@ -164,7 +165,7 @@ export function playRules(inp: ScanInput, x: PlayCtx): void { if (bg) add({ rule: 'play-background-location', guideline: 'Play: Location permissions', severity: 'high', title: 'ACCESS_BACKGROUND_LOCATION needs a core feature that uses location in the background, the Location permissions declaration and a video', evidence: [ev(bg, bg.name)], ref: PLAY_REFS.backgroundLocation, skill: 'store-declarations', - fix: 'Keep it only for one core, user-visible feature (never for ads or analytics alone); fill App content > Sensitive app permissions > Location permissions (one feature, a video of 30 s or less showing the prominent disclosure, the runtime prompt and the feature working in the background), and name the feature in the store description. Otherwise remove it (tools:node="remove" if a library adds it).' }); + fix: 'Keep it only for one core, user-visible feature (never for ads or analytics alone); fill ' + consoleLink('play.sensitive-permissions', '> Location permissions') + ' (one feature, a video of 30 s or less showing the prominent disclosure, the runtime prompt and the feature working in the background), and name the feature in the store description. Otherwise remove it (tools:node="remove" if a library adds it).' }); const media = ['READ_MEDIA_IMAGES', 'READ_MEDIA_VIDEO'].map(n => perms.get(n)).filter(q => !!q); if (media.length) add({ rule: 'play-photo-video', guideline: 'Play: Photo and Video Permissions', severity: 'high', title: `${media.map(q => permName(q.name)).join(' / ')}: only for apps whose core purpose needs broad photo / video access; others use the photo picker`, @@ -193,12 +194,12 @@ export function playRules(inp: ScanInput, x: PlayCtx): void { if (health.length) add({ rule: 'play-health-connect', guideline: 'Play: Health Connect permissions', severity: 'high', title: `Health Connect permissions (${health.slice(0, 3).map(q => permName(q.name).replace(/^health\./, '')).join(', ')}${health.length > 3 ? ', ...' : ''}) need a justification for each one and a permitted use case`, evidence: health.slice(0, 3).map(q => ev(q, q.name)), ref: PLAY_REFS.healthConnect, skill: 'store-declarations', - fix: 'Request only the data types a feature uses, justify each permission in the Health Connect declaration, show the privacy policy in the app, and answer App content > Health apps (answer/14738291). Ads, data sales and child-only apps are not allowed to use it.' }); + fix: 'Request only the data types a feature uses, justify each permission in the Health Connect declaration, show the privacy policy in the app, and answer ' + consoleLink('play.health-apps') + ' (answer/14738291). Ads, data sales and child-only apps are not allowed to use it.' }); const fsi = perms.get('USE_FULL_SCREEN_INTENT'); if (fsi && (target === null || target >= 34)) add({ rule: 'play-full-screen-intent', guideline: 'Play: Full-screen intent', severity: 'high', title: 'USE_FULL_SCREEN_INTENT: on Android 14+ only calling and alarm apps get it by default, and every app declares it in Play Console', evidence: [ev(fsi, fsi.name)], ref: PLAY_REFS.fullScreenIntent, skill: 'store-declarations', - fix: 'Remove it unless the core function is alarms or phone / video calls (an SDK may add it: check the merged manifest); otherwise fill App content > Full-screen intent, ask the user with ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT and handle a denial. Never for ads.' }); + fix: 'Remove it unless the core function is alarms or phone / video calls (an SDK may add it: check the merged manifest); otherwise fill ' + consoleLink('play.full-screen-intent') + ', ask the user with ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT and handle a denial. Never for ads.' }); const vpn = a.components.filter(q => q.kind === 'service' && (q.permission === 'android.permission.BIND_VPN_SERVICE' || q.actions.includes('android.net.VpnService'))); if (vpn.length) add({ rule: 'play-vpn', guideline: 'Play: VpnService', severity: 'high', title: `VpnService ${vpn[0]!.name}: only for VPN apps and the listed exceptions, with a disclosure, consent and the VpnService declaration`, diff --git a/cli/lib/scan/rules.ts b/cli/lib/scan/rules.ts index 67dc7b8..9b83b21 100644 --- a/cli/lib/scan/rules.ts +++ b/cli/lib/scan/rules.ts @@ -33,6 +33,7 @@ // error of Play Console (no policy page; developer.android.com/build/configure-app-module on choosing the id). // The other Google Play rules (sensitive permissions, prominent disclosure, Families, advertising ID, 16 KB pages, // testing for personal accounts, Play metadata, ads, payments, impersonation) are in play-rules.ts with their sources. +import { consoleLink, consoleUrl } from '../console-links'; import type { FixAction } from '../fix-actions'; import { templateBlockers } from '../template-answers'; import { BETA_WORDS, competitorRe, OTHER_PLATFORMS, PLACEHOLDER_MARKERS, PLACEHOLDER_TEXT, PRICE_CLAIMS } from '../listing-rules'; @@ -57,6 +58,8 @@ export interface ScanFinding { severity: Severity; title: string; evidence: Hit[]; + /** Absolute deep link to the console page the message points to (desktop / MCP render it as an Open link). */ + url?: string; fix: string; skill: string; /** Policy page. */ @@ -222,7 +225,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] { } if (play && ra?.signInRequired && !inp.demoAccount) add({ rule: 'play-app-access', guideline: 'Play: App access', store: 'play', severity: 'high', title: 'Sign-in is required but there are no reviewer credentials for Play App access', evidence: ev, - fix: 'Play Console > App content > App access: add the demo account and instructions (keep the credentials in .storebound/credentials.json only).', skill: 'review-access', ref: `${PLAY}9859455` }); + fix: `${consoleLink('play.app-access')}: add the demo account and instructions (keep the credentials in .storebound/credentials.json only).`, url: consoleUrl('play.app-access'), skill: 'review-access', ref: `${PLAY}9859455` }); } if (creates.length) { if (apple && !deletionCode.length && deletionClaim.length) @@ -264,7 +267,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] { const termsUrl = c?.app.termsUrl; if (!(termsUrl && all.includes(termsUrl)) && !/terms of use|terms of service|\beula\b|license agreement/i.test(all)) add({ rule: 'subscription-info', guideline: '3.1.2(c)', store: 'apple', severity: 'high', title: 'App Store description has no Terms of Use (EULA) link', evidence: desc.slice(0, 1), - fix: 'Add a line "Terms of Use: " (and the privacy policy) to the description of every locale, or set a custom EULA in App Store Connect > App Information.', skill: 'store-subscriptions' }); + fix: 'Add a line "Terms of Use: " (and the privacy policy) to the description of every locale, or set a custom EULA in ' + consoleLink('asc.app-info') + '.', url: consoleUrl('asc.app-info'), skill: 'store-subscriptions' }); if (!/renew|cancel/i.test(all)) add({ rule: 'subscription-info', guideline: '3.1.2(c)', store: 'apple', severity: 'medium', title: 'App Store description does not say the subscription renews automatically or how to cancel', evidence: desc.slice(0, 1), fix: 'Describe what the subscription includes, the length and price per period, that it renews automatically unless cancelled at least 24 hours before the end of the period, and where to manage it.', skill: 'store-subscriptions' }); @@ -469,7 +472,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] { ref: 'https://developer.android.com/google/play/requirements/target-sdk' }); else if (!a.targetSdk && a.targetSdkFrom) add({ rule: 'play-target-sdk', guideline: 'Play: Target API level', store: 'play', severity: 'low', title: `targetSdk comes from ${a.targetSdkFrom}: check that the built AAB targets ${need} or higher`, evidence: a.manifests.slice(0, 1).map(f => ({ file: f, line: 1, text: 'AndroidManifest.xml' })), - fix: `Check with \`aapt2 dump badging app.aab\` or Play Console > App bundle explorer; Play refuses uploads below ${need}.`, skill: 'play-first-release', ref: 'https://developer.android.com/google/play/requirements/target-sdk' }); + fix: `Check with \`aapt2 dump badging app.aab\` or ${consoleLink('play.bundle-explorer')}; Play refuses uploads below ${need}.`, skill: 'play-first-release', ref: 'https://developer.android.com/google/play/requirements/target-sdk' }); const perms = new Map(a.permissions.map(x => [x.name.replace(/^android\.permission\./, ''), x] as const)); const fgs = perms.get('FOREGROUND_SERVICE'); if (fgs && (!a.targetSdk || a.targetSdk.value >= 34)) { @@ -477,7 +480,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] { const untyped = a.services.filter(s => !s.fgsType); if (untyped.length && !typed.length) add({ rule: 'play-fgs', guideline: 'Play: Foreground service types', store: 'play', severity: 'high', title: 'FOREGROUND_SERVICE is requested but no declares android:foregroundServiceType (required when targeting Android 14+)', evidence: [{ ...fgs.at, text: 'FOREGROUND_SERVICE' }, ...untyped.slice(0, 2).map(s => ({ ...s.at, text: `service ${s.name}` }))], - fix: 'Give each foreground service a type and its FOREGROUND_SERVICE_ permission, then fill App content > Foreground service permissions (description, user impact, video).', skill: 'play-first-release', ref: `${PLAY}13392821` }); + fix: 'Give each foreground service a type and its FOREGROUND_SERVICE_ permission, then fill ' + consoleLink('play.foreground-service') + ' (description, user impact, video).', url: consoleUrl('play.foreground-service'), skill: 'play-first-release', ref: `${PLAY}13392821` }); for (const s of typed) { const types = s.fgsType!.split('|').map(x => x.trim()).filter(Boolean); const missing = types.filter(t => FGS_PERMISSION[t] && !perms.has(`FOREGROUND_SERVICE_${FGS_PERMISSION[t]}`)); @@ -487,7 +490,7 @@ export function scanRules(inp: ScanInput): ScanFinding[] { } if (typed.length) add({ rule: 'play-fgs', guideline: 'Play: Foreground service types', store: 'play', severity: 'medium', title: `Foreground service types need a Play Console declaration: ${[...new Set(typed.flatMap(s => s.fgsType!.split('|')))].join(', ')}`, evidence: typed.slice(0, 3).map(s => ({ ...s.at, text: `${s.name}: ${s.fgsType}` })), - fix: 'App content > Foreground service permissions: describe each use, the user impact if it is deferred, and add a short video; shortService needs none.', skill: 'play-first-release', ref: `${PLAY}13392821` }); + fix: consoleLink('play.foreground-service') + ': describe each use, the user impact if it is deferred, and add a short video; shortService needs none.', skill: 'play-first-release', ref: `${PLAY}13392821` }); } const exact = perms.get('USE_EXACT_ALARM'); if (exact) diff --git a/cli/lib/store-apps.ts b/cli/lib/store-apps.ts index 9966457..38d8466 100644 --- a/cli/lib/store-apps.ts +++ b/cli/lib/store-apps.ts @@ -10,6 +10,7 @@ // Google Play: the Play Developer Reporting API `apps:search` lists every app the service account can see, across all // developer accounts it was invited to. // https://developers.google.com/play/developer/reporting/reference/rest/v1beta1/apps/search +import { consoleLink } from './console-links'; import type { AccountCheck, StoreApp } from './desktop-protocol'; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -175,7 +176,7 @@ export function localPlayApps(known: { packageName: string; name: string; source /** Play service account health from the token exchange and an `apps:search` outcome. */ export function playCheck(principal: string | null, outcome: { apps: number } | { error: unknown }): AccountCheck { const who = principal ?? 'the service account'; - const invite = `Play Console > Users and permissions > Invite new users: add ${who}. Account permissions: "View app information and download bulk reports (read-only)"; per app, as needed: "Manage store presence", "Manage testing tracks", "Release to production, exclude devices and use Play App Signing" (rollouts), "Reply to reviews", "Manage orders and subscriptions". One service account can be invited to several developer accounts.`; + const invite = `${consoleLink('play.users')} > Invite new users: add ${who}. Account permissions: "View app information and download bulk reports (read-only)"; per app, as needed: "Manage store presence", "Manage testing tracks", "Release to production, exclude devices and use Play App Signing" (rollouts), "Reply to reviews", "Manage orders and subscriptions". One service account can be invited to several developer accounts.`; if ('apps' in outcome) { return outcome.apps > 0 ? { store: 'android', status: 'ok', principal, apps: outcome.apps, message: `The service account can see ${outcome.apps} app${outcome.apps === 1 ? '' : 's'}.`, fix: null, docs: null } diff --git a/cli/lib/train.ts b/cli/lib/train.ts index 7ed5bae..1cdb660 100644 --- a/cli/lib/train.ts +++ b/cli/lib/train.ts @@ -3,6 +3,7 @@ // re-exports these) and `storebound release status | plan` (cli/release.ts) share this file, so both show the same // stage for the same store state. Pure; the store reads are `apple status --json` / `play status --json`, turned into // StoreRelease rows by cli/lib/portfolio.ts. +import { CONSOLE_PAGES, consoleLink, consoleUrl, playTrackPage } from './console-links'; import type { AppStatus, ReleaseTrain, StoreRelease } from './desktop-protocol'; import { cmpVersion, displayVersion, PHASE_RANK, playReleaseLabel, REACHING, releaseTrains, sameVersion, versionKey } from './portfolio'; import type { Finding } from './validate-rules'; @@ -151,6 +152,8 @@ export type PlanKind = 'config' | 'store' | 'blocker' | 'owner' | 'wait' | 'warn export interface PlanStep { kind: PlanKind; text: string; + /** Absolute deep link to the console page the message points to (desktop / MCP render it as an Open link). */ + url?: string; /** The command or console place that does it. Owner steps name what the owner runs; storebound never does them. */ fix?: string; code?: string; @@ -217,18 +220,18 @@ function storeSteps(store: 'ios' | 'android', v: string, r: StoreRelease | null, break; case 'draft': steps.push(store === 'ios' - ? { kind: 'owner', text: `submit ${v} for App Review (owner)`, fix: 'App Store Connect > the version > Add for Review' } + ? { kind: 'owner', text: `submit ${v} for App Review (owner)`, fix: consoleLink('asc.version', '> Add for Review'), url: consoleUrl('asc.version') } : r!.track && r!.track !== 'production' - ? { kind: 'owner', text: `roll out the ${r!.track} draft of ${v} to its testers (owner)`, fix: `Play Console > Test and release > ${r!.track}` } - : { kind: 'owner', text: `send the production draft of ${v} for review (owner)`, fix: 'Play Console > Publishing overview > Send for review' }); + ? { kind: 'owner', text: `roll out the ${r!.track} draft of ${v} to its testers (owner)`, fix: `${CONSOLE_PAGES[playTrackPage(r!.track)].label} (${r!.track}): ${consoleUrl(playTrackPage(r!.track))}`, url: consoleUrl(playTrackPage(r!.track)) } + : { kind: 'owner', text: `send the production draft of ${v} for review (owner)`, fix: consoleLink('play.publishing', '> Send for review'), url: consoleUrl('play.publishing') }); break; case 'testing': steps.push(store === 'ios' ? { kind: 'store', text: `${v} is in TestFlight (build ${r!.build ?? '?'}) but has no App Store version yet: create it with that build and its texts`, fix: `storebound apple listing --version ${v} --build ${r!.build ?? ''} --dry` } - : { kind: 'owner', text: `promote ${v} from ${r!.track ?? 'testing'} to production (owner)`, fix: 'Play Console > Test and release > Production, or `storebound play release --production draft` in a terminal' }); + : { kind: 'owner', text: `promote ${v} from ${r!.track ?? 'testing'} to production (owner)`, fix: `${consoleLink('play.production')}, or \`storebound play release --production draft\` in a terminal`, url: consoleUrl('play.production') }); break; case 'rejected': - steps.push({ kind: 'owner', text: `resubmit ${v} after the fix (owner)`, fix: store === 'ios' ? 'App Store Connect > App Review > reply / resubmit' : 'Play Console > Publishing overview' }); + steps.push({ kind: 'owner', text: `resubmit ${v} after the fix (owner)`, fix: store === 'ios' ? consoleLink('asc.app-review', '> reply / resubmit') : consoleLink('play.publishing'), url: consoleUrl(store === 'ios' ? 'asc.app-review' : 'play.publishing') }); break; case 'sent': case 'released': @@ -236,13 +239,13 @@ function storeSteps(store: 'ios' | 'android', v: string, r: StoreRelease | null, break; case 'review': if (store === 'android' && r!.track === 'production') { - steps.push({ kind: r!.managed ? 'owner' : 'wait', text: `${v}: ${playReleaseLabel(r!)}`, fix: r!.managed ? 'Play Console > Publishing overview (publish once approved)' : 'storebound play status' }); + steps.push({ kind: r!.managed ? 'owner' : 'wait', text: `${v}: ${playReleaseLabel(r!)}`, fix: r!.managed ? `${consoleLink('play.publishing')} (publish once approved)` : 'storebound play status', ...(r!.managed ? { url: consoleUrl('play.publishing') } : {}) }); break; } steps.push({ kind: 'wait', text: `${v} is in review (${r!.state}); nothing to do until the store answers`, fix: store === 'ios' ? 'storebound apple status --wait-review' : 'storebound play status' }); break; case 'approved': - steps.push({ kind: 'owner', text: `${v} is approved (${r!.state}): release it (owner)`, fix: store === 'ios' ? 'App Store Connect > Release this version, or `storebound apple release --manual-release` in a terminal' : 'Play Console > Publishing overview' }); + steps.push({ kind: 'owner', text: `${v} is approved (${r!.state}): release it (owner)`, fix: store === 'ios' ? `${consoleLink('asc.version', '> Release this version')}, or \`storebound apple release --manual-release\` in a terminal` : consoleLink('play.publishing'), url: consoleUrl(store === 'ios' ? 'asc.version' : 'play.publishing') }); break; case 'rollout': steps.push({ kind: 'owner', text: `${v} reaches ${pctText(r!.fraction) || 'part'} of users: raise or complete the rollout when health is good (owner)`, diff --git a/cli/lib/validate-rules.ts b/cli/lib/validate-rules.ts index 1f6d47e..4c3b14b 100644 --- a/cli/lib/validate-rules.ts +++ b/cli/lib/validate-rules.ts @@ -1,6 +1,7 @@ // Store readiness rules for `storebound validate`. Pure: the command collects a snapshot from both APIs (read-only), // these functions turn it into an ordered list of findings. Order = the order you would fix things in. +import { consoleLink, consoleUrl } from './console-links'; import { type FixAction, fixAppleListing, fixPlayListing, fixReviewDetails } from './fix-actions'; export type Level = 'blocker' | 'warn' | 'ok'; @@ -16,6 +17,8 @@ export const ACCEPTED_INSTEAD: Record = { APP_IPAD_PRO_129: ['APP_IPAD_PRO_3GEN_129'], }; export interface Finding { store: 'apple' | 'play'; level: Level; code: string; msg: string; fix?: string; + /** Absolute deep link to the console page the message points to (desktop / MCP render it as an Open link). */ + url?: string; /** The desktop's Fix button: a write fully determined by an existing command (lib/fix-actions.ts). */ action?: FixAction } @@ -181,7 +184,7 @@ export function copyrightRules(s: Pick f.push({ store: 'apple', level, code, msg, fix, ...(action ? { action } : {}) }); + const add = (level: Level, code: string, msg: string, fix?: string, action?: FixAction, url?: string) => f.push({ store: 'apple', level, code, msg, fix, ...(action ? { action } : {}), ...(url ? { url } : {}) }); const listing = s.version && EDITABLE.includes(s.version.state) ? fixAppleListing(s.version.versionString) : undefined; if (!s.version) add('blocker', 'version', 'no App Store version being prepared', 'storebound apple listing --version x.y.z'); @@ -230,11 +233,11 @@ export function appleRules(s: AppleSnapshot): Finding[] { const { open, soft } = ageOpen(s.ageRating); if (open.length) add('blocker', 'age-rating', `age rating unanswered: ${open.join(', ')}`, 'storebound apple appinfo'); else add('ok', 'age-rating', 'age rating answered'); - if (soft.length) add('warn', 'age-rating', `age rating question not answered yet: ${soft.join(', ')} (App Store Connect > App Information > Age Rating)`); + if (soft.length) add('warn', 'age-rating', `age rating question not answered yet: ${soft.join(', ')} (${consoleLink('asc.age-rating')})`, undefined, undefined, consoleUrl('asc.age-rating')); } add(s.priceSchedule ? 'ok' : 'blocker', 'price', s.priceSchedule ? 'app price set' : 'no app price schedule', 'storebound apple appinfo'); add(s.availability ? 'ok' : 'blocker', 'availability', s.availability ? 'availability set' : 'no territory availability', - 'storebound apple appinfo; if the API refuses the first availability record, set it once in App Store Connect > Pricing and Availability'); + 'storebound apple appinfo; if the API refuses the first availability record, set it once in ' + consoleLink('asc.pricing')); const iaps = new Map(s.iaps.map(i => [i.productId, i] as const)); for (const id of s.expectedProducts) if (!iaps.has(id)) add('blocker', 'iap', `${id}: not on App Store Connect`, 'storebound apple iap'); @@ -380,7 +383,7 @@ export function appleSubscriptionRules(s: Pick f.push({ store, level, code, msg, fix }); + const add = (level: Level, code: string, msg: string, fix?: string, _action?: undefined, url?: string) => f.push({ store, level, code, msg, fix, ...(url ? { url } : {}) }); if (p.accounts) { if (store === 'play') { if (!p.deletionUrl) add('blocker', 'account-deletion', 'the app has accounts but accounts.deletionUrl is not set (Play Data safety asks for a delete-account URL)', 'account-deletion skill'); @@ -393,7 +396,7 @@ export function profileRules(store: 'apple' | 'play', p: ProfileFacts, o: { revi if (!p.demoAccountLocal) add('blocker', 'demo-account', 'sign-in required for review but no demo account in .storebound/credentials.json (reviewDemoAccount) or REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD', 'review-access skill'); if (store === 'apple' && o.reviewDetail && !(o.reviewDetail.demoAccountRequired && o.reviewDetail.demoAccountName)) add('blocker', 'demo-account', 'App Review information has no demo account although the app needs a sign-in (Guideline 2.1)', 'storebound apple review-details'); - if (store === 'play') add('warn', 'app-access', 'sign-in required: Play Console > App content > App access must list the demo credentials and steps (no API)', 'review-access skill'); + if (store === 'play') add('warn', 'app-access', `sign-in required: ${consoleLink('play.app-access')} must list the demo credentials and steps (no API)`, 'review-access skill', undefined, consoleUrl('play.app-access')); if (!f.some(x => x.code === 'demo-account')) add('ok', 'demo-account', 'demo account for review present'); } if (p.subscriptions) { diff --git a/cli/lib/web.ts b/cli/lib/web.ts index 65f5237..990b1fd 100644 --- a/cli/lib/web.ts +++ b/cli/lib/web.ts @@ -16,6 +16,7 @@ // sha256_cert_fingerprints ["AB:CD:..."] }; https:///.well-known/assetlinks.json, content-type // application/json, HTTPS, no redirects; relation_extensions.dynamic_app_link_components on Android 15+) // https://developer.android.com/training/app-links/verify-android-applinks (autoVerify, per-host lookup) +import { consoleLink, consoleUrl } from './console-links'; import type { ShipConfig } from './config'; export const AASA_PATH = '/.well-known/apple-app-site-association'; @@ -25,7 +26,7 @@ export const HANDLE_ALL_URLS = 'delegate_permission/common.handle_all_urls'; export const AASA_MAX_BYTES = 128 * 1024; export type Level = 'ok' | 'problem' | 'info'; -export interface Finding { level: Level; message: string; fix?: string } +export interface Finding { level: Level; message: string; fix?: string; url?: string } type Cfg = Pick; @@ -187,7 +188,7 @@ export function validateAssetLinks(json: unknown, ids: { packageName?: string; f f.push({ level: 'ok', message: `${ids.packageName} has ${HANDLE_ALL_URLS}` }); const live = new Set(linked.flatMap(s => ((s.target as Record).sha256_cert_fingerprints as string[] | undefined) ?? []).map(x => normalizeFingerprint(x) ?? x)); const want = (ids.fingerprints ?? []).map(x => normalizeFingerprint(x) ?? x); - if (!want.length) { f.push({ level: 'info', message: 'web.androidSha256 not in the config: fingerprints not compared', fix: 'copy the SHA-256 from Play Console > App integrity > Play app signing' }); return f; } + if (!want.length) { f.push({ level: 'info', message: 'web.androidSha256 not in the config: fingerprints not compared', fix: `copy the SHA-256 from ${consoleLink('play.app-signing')}`, url: consoleUrl('play.app-signing') }); return f; } const missing = want.filter(x => !live.has(x)), extra = [...live].filter(x => !want.includes(x)); for (const m of missing) bad(`fingerprint ${m.slice(0, 11)}... from web.androidSha256 is missing: app links do not verify for builds signed with it`); if (!missing.length) f.push({ level: 'ok', message: `all ${want.length} fingerprint(s) from web.androidSha256 are listed` }); @@ -277,10 +278,10 @@ export function planWeb(c: Cfg, o: { landing: boolean; template?: string; marks? const fps = o.fingerprints ?? c.web?.androidSha256 ?? []; const links = assetLinksFor(c, fps); if (links) files.push({ path: '.well-known/assetlinks.json', content: JSON.stringify(links, null, 2) + '\n' }); - else if (c.play) skipped.push('.well-known/assetlinks.json: no app signing fingerprint: copy the SHA-256 from Play Console > Test and release > App integrity > Play app signing into web.androidSha256, or run `storebound web init --version-code ` to read it from the Play Developer API'); + else if (c.play) skipped.push('.well-known/assetlinks.json: no app signing fingerprint: copy the SHA-256 from ' + consoleLink('play.app-signing') + ' into web.androidSha256, or run `storebound web init --version-code ` to read it from the Play Developer API'); const tag = smartBannerTag(c); if (tag) files.push({ path: 'smart-app-banner.html', content: `\n${tag}\n` }); - else if (c.apple) skipped.push('smart-app-banner.html: apple.appId is not set (App Store Connect > App Information > Apple ID)'); + else if (c.apple) skipped.push(`smart-app-banner.html: apple.appId is not set (${consoleLink('asc.app-info', '> Apple ID')})`); if (o.landing) { if (!o.template) skipped.push('index.html: landing template not found'); else { diff --git a/cli/play/share.ts b/cli/play/share.ts index 39d0b6d..f6f090f 100644 --- a/cli/play/share.ts +++ b/cli/play/share.ts @@ -8,6 +8,7 @@ // or a place on the Console's uploader list. See skills/adhoc-distribution/SKILL.md. // Sources (checked October 2026): https://developers.google.com/android-publisher/api-ref/rest/v3/internalappsharingartifacts // and https://support.google.com/googleplay/android-developer/answer/9844679 (details in cli/lib/play-share.ts). +import { consoleLink } from '../lib/console-links'; import { createHash } from 'node:crypto'; import { createReadStream, existsSync, readFileSync, statSync } from 'node:fs'; import type { Ctx } from '../lib/ctx'; @@ -61,7 +62,7 @@ export async function run(ctx: Ctx): Promise { res = await play.call('POST', shareUrl(pkg, kind), new Uint8Array(readFileSync(file)), { ...SHARE_HEADERS }); } catch (e) { const m = (e as Error).message; - if (/-> 403/.test(m)) throw new Error(`${m}\n\nThe service account needs "Release apps to testing tracks" for this app, or a place on the internal app sharing uploader list (Play Console > Internal app sharing).`); + if (/-> 403/.test(m)) throw new Error(`${m}\n\nThe service account needs "Release apps to testing tracks" for this app, or a place on the internal app sharing uploader list (${consoleLink('play.internal-sharing')}).`); throw e; } finally { clearInterval(beat); } const art = parseShareArtifact(res); diff --git a/cli/reports.ts b/cli/reports.ts index e643454..1032910 100644 --- a/cli/reports.ts +++ b/cli/reports.ts @@ -9,6 +9,7 @@ // fetched and fetches nothing. Apple needs a team API key (not an individual key) with access to reports and the vendor // number (ASC_VENDOR_NUMBER in env or .storebound/credentials.json; --vendor). Play needs play.reportsBucket and the // service account with "View financial data". Read-only on both stores. Pure parts and sources: cli/lib/reports.ts. +import { consoleLink } from './lib/console-links'; import { mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { ascClient } from './apple/asc'; @@ -51,7 +52,7 @@ function save(dir: string, name: string, raw: string | Uint8Array, rows: Summary /** What a failed Sales and Trends / finance download usually means. Seen live: a wrong vendor number gets a 500 * (after the retries) from salesReports and a 400 PARAMETER_ERROR.INVALID from financeReports. Pure. */ export function appleReportHint(msg: string): string | null { - if (/salesReports -> 500|financeReports -> 400 .*vendor number/i.test(msg)) return 'check the vendor number (App Store Connect > Payments and Financial Reports, the number next to the legal entity name); reports also need a team key with Sales or Finance access'; + if (/salesReports -> 500|financeReports -> 400 .*vendor number/i.test(msg)) return `check the vendor number (${consoleLink('asc.payments')}, the number next to the legal entity name); reports also need a team key with Sales or Finance access`; if (/-> 403/.test(msg)) return 'the API key may not read reports: it needs the Admin, Finance or Sales role (Users and Access > Integrations)'; return null; } @@ -67,7 +68,7 @@ export async function run(ctx: Ctx): Promise { if (sub === 'apple-sales' || sub === 'apple-finance') { const vendor = a.get('vendor') ?? ascVendorNumber(root); - if (!vendor || !/^\d{5,12}$/.test(vendor)) throw new Error('vendor number missing: ASC_VENDOR_NUMBER in env, .storebound/credentials.json or the credential profile (--account), or --vendor (App Store Connect > Payments and Financial Reports)'); + if (!vendor || !/^\d{5,12}$/.test(vendor)) throw new Error(`vendor number missing: ASC_VENDOR_NUMBER in env, .storebound/credentials.json or the credential profile (--account), or --vendor (${consoleLink('asc.payments')})`); let path: string, name: string; if (sub === 'apple-sales') { const type = a.str('type', 'SALES').toUpperCase(); @@ -100,7 +101,7 @@ export async function run(ctx: Ctx): Promise { if (sub === 'play-earnings' || sub === 'play-sales') { const bucket = config.play?.reportsBucket; - if (!bucket) throw new Error('play.reportsBucket is not set (Play Console > Download reports > Financial > Copy Cloud Storage URI)'); + if (!bucket) throw new Error(`play.reportsBucket is not set (${consoleLink('play.reports-financial', '> Copy Cloud Storage URI')})`); const month = need(a.get('month'), '--month YYYY-MM'); const kind = sub === 'play-earnings' ? 'earnings' : 'sales'; const prefix = playReportPrefix(kind, month); diff --git a/docs/agents-core.md b/docs/agents-core.md index 51dcb7b..de13e68 100644 --- a/docs/agents-core.md +++ b/docs/agents-core.md @@ -63,3 +63,6 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. `storebound console fill` is the optional scripted fast path; it never blocks you. +11. **Console pages: always the absolute deep link.** When you send the owner to a console page, give the absolute + deep link (storebound prints it; `storebound console plan` has start URLs), never just "go to App Store Connect" + or "App Store Connect > App Information" alone. diff --git a/docs/pitfalls.md b/docs/pitfalls.md index bb85840..529538b 100644 --- a/docs/pitfalls.md +++ b/docs/pitfalls.md @@ -9,6 +9,7 @@ fails without the fix. | # | date | lesson | guard | |---|---|---|---| +| 71 | 2026-10 | Messages sent the owner to console pages as plain text paths ("App Store Connect > App Information > Age Rating", "Play Console > App content") without a link; the owner had to find the app and the page by hand. | `cli/lib/console-links.ts`: every message carries the absolute deep link (app list / developer chooser when ids are unknown), `url` in finding JSON, an Open button in the desktop; `tests/console-links.test.ts` fails on a console path in cli/ or templates/ without an https URL; agent rule 11 in AGENTS.md. | | 70 | 2026-10 | A game: the app's first in-app purchase (a starter pack) was not in the 1.0.0 review submission (its items held only the appStoreVersion); it stayed READY_TO_SUBMIT and was noticed after two days in WAITING_FOR_REVIEW. Apple reviews the first product of a type only with an app version; approved without it, it is not sellable. | `firstProductGaps` (`cli/lib/review-submit.ts`, read by `cli/apple/first-products.ts`): WARN in `apple status` (text and --json), `release status` and the desktop checks; blocker in `validate` / `release plan` and the `apple submit` plan; `iap-review` says "ready, not submitted yet". `tests/first-products.test.ts`. | | 69 | 2026-10 | A web app: `bun add -d storebound` plus the scaffolded `storebound.config.ts` broke `next build`: the package exported only its TypeScript source and shipped no declarations; Bun imports .ts, so every storebound test passed, but tsc never takes a package's .ts source as its types. | The package ships declarations for ES module (`dist/types`) and CommonJS projects (`dist/types-cjs`) and a Node entry (`dist/config.js`); Bun keeps the source (`buildEntry`, `pruneDeclarations` in `tools/build-node.ts`, run by prepack). `tests/package-types.test.ts` type-checks the template against the packed entry under bundler / node16 / nodenext / node10 in both project types with no @types/node and skipLibCheck off, and loads the package with `import()` and `require()` under Node. `check` fails on the template's own placeholders (Example Studio, bare example.com). | | 68 | 2026-10 | A headless Chrome started by storebound's console automation (a test run's `--user-data-dir=$TMPDIR/storebound-profile-apple-…`, `--remote-debugging-port=0 --headless=new`) outlived its parent for ~20 hours as an orphan (parent PID 1). macOS LaunchServices took it for the running "Google Chrome", so the Dock's Chrome only activated the windowless instance and the owner's own Chrome would not open. A Chrome started with a DevTools port does not notice its parent dying. | `cli/console/chrome-guard.ts`: every Chrome storebound starts itself is ended on process exit, closed gracefully on SIGINT / SIGTERM / SIGHUP, and ended by a detached watchdog when the parent dies (SIGKILL included; a reused pid is never killed); every start first ends headless orphans on storebound's profiles and names orphaned windows; headless runs on macOS prefer Chromium / Chrome for Testing over Google Chrome.app; `console fill` closes the window on an error too (`tests/chrome-guard.test.ts`). | diff --git a/media/teaser/script.js b/media/teaser/script.js index 920a2e6..a3c5166 100644 --- a/media/teaser/script.js +++ b/media/teaser/script.js @@ -134,7 +134,7 @@ export const PRIVACY_ANSWERS = [ ' [ok] Data type: Location > Precise Location: No -> untick "Precise Location" @ the Data Collection dialog (Edit next to Data Types): the data type list', ]; export const PRIVACY_STEPS = [ - ' 1. Open App Store Connect > Apps > the app > App Privacy (sidebar).', + ' 1. Open App Store Connect > Apps > the app > App Privacy (sidebar): https://appstoreconnect.apple.com/apps', ' 2. First time: click "Get Started" and answer the Data Collection question; then Next. [click: "Get Started"]', ' 5. "Email Address" step 1, purposes: tick and untick the 6 checkboxes as answered, then Next. [click: "Next"]', ' 6. "Email Address" step 2, linked to the user: pick the radio, then Next. [click: "Next"]', @@ -264,7 +264,7 @@ export const CONSOLE_CMD = 'storebound console plan apple-app-privacy'; export const CONSOLE_LINES = [ { at: 0.12, text: 'App Store Connect: App Privacy', head: true }, { at: 0.22, text: 'Steps:', head: true }, - { at: 0.3, text: ' 1. Open App Store Connect > Apps > the app > App Privacy (sidebar).' }, + { at: 0.3, text: ' 1. Open App Store Connect > Apps > the app > App Privacy (sidebar): https://appstoreconnect.apple.com/apps' }, { at: 0.38, text: ' 2. First time: click "Get Started" and answer the Data Collection question; then Next. [click: "Get Started"]' }, { at: 0.5, text: 'Answer sheet:', head: true }, { at: 0.58, text: ' [ok] Data type: Contact Info > Email Address: Yes -> tick "Email Address" @ the Data Collection dialog (Edit next to Data Types): the data type list' }, diff --git a/skills/store-pitfalls/SKILL.md b/skills/store-pitfalls/SKILL.md index 136bf65..90e73d1 100644 --- a/skills/store-pitfalls/SKILL.md +++ b/skills/store-pitfalls/SKILL.md @@ -822,3 +822,15 @@ that fails without the fix (see CONTRIBUTING.md). and `firstProductGaps` in --json), `release status` and the desktop checks (`appleFirstProductWarnings`), `validate` / `release plan` (blocker), the `apple submit` plan (blocker); `iap-review` says "ready, not submitted yet"; `tests/first-products.test.ts`. + +## 71. The owner was sent to a console page with plain directions +- **Symptom:** a message said "App Store Connect > App Information > Age Rating" or "go to Play Console" and the owner + had to find the app, the page and the card by hand. +- **Cause:** storebound's findings, fix hints and errors named console pages as text paths (40+ in cli/), without the + app's URL. +- **Do:** when you send the owner to a console page, give the absolute deep link (storebound prints it; `storebound + console plan` has start URLs), never just "go to App Store Connect". Set apple.appId, play.developerId and + play.appId so links open the app's own page instead of the app list. +- **Guard:** `cli/lib/console-links.ts` (every page key, absolute URL with and without ids; `url` in validate / scan / + declarations / release plan JSON, an Open button in the desktop) and `tests/console-links.test.ts`, which fails on a + console path in cli/ or templates/ without an https URL. diff --git a/templates/app-privacy.md b/templates/app-privacy.md index fef05ea..0e7295e 100644 --- a/templates/app-privacy.md +++ b/templates/app-privacy.md @@ -1,6 +1,6 @@ # Example Game · App Store "App Privacy" answers -App Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press +App Store Connect > your app > App Privacy (https://appstoreconnect.apple.com/apps, then the app; `storebound console plan apple-app-privacy` gives the deep link). **Web UI only, there is no API.** Fill it from this table, then press **Publish** at the top of the App Privacy page (saving alone does not publish it, and review checks the published one). Keep it consistent with the privacy policy and the Play Data safety answers (`dataSafety` in `storebound.config.ts`). diff --git a/templates/declarations.md b/templates/declarations.md index 1299987..4d8e95d 100644 --- a/templates/declarations.md +++ b/templates/declarations.md @@ -2,7 +2,7 @@ Answers for a typical single-player free-to-play game with ads (mediation) and in-app purchases, no accounts, no chat, no user-generated content, general audience 13+. Change what does not match your game. Everything marked **UI** has no -API: Play Console > your app > Policy and programs > **App content** (or Dashboard > "Set up your app"), and App Store +API: Play Console > your app > Policy and programs > **App content** (https://play.google.com/console, then the app; `storebound console plan` gives deep links) (or Dashboard > "Set up your app"), and App Store Connect for Apple. Data safety (Play) has an API: `storebound play data-safety`. ## Google Play · App content (UI) @@ -51,7 +51,7 @@ Expected: PEGI 3, ESRB Everyone, USK 0, IARC 3+. Apple age rating 4+ (all conten ## App Store (UI-only parts) -- App record: App Store Connect > Apps > + > New App (platform, name, primary language, bundle id, SKU). No API. +- App record: App Store Connect > Apps > + > New App (https://appstoreconnect.apple.com/apps) (platform, name, primary language, bundle id, SKU). No API. - App Privacy: `app-privacy.md`, then **Publish**. - Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` in Info.plist only if true (HTTPS / OS encryption only) so every build skips the question. diff --git a/templates/profiles/app/app-privacy.md b/templates/profiles/app/app-privacy.md index ccce3e5..08d7f74 100644 --- a/templates/profiles/app/app-privacy.md +++ b/templates/profiles/app/app-privacy.md @@ -1,6 +1,6 @@ # Example App · App Store "App Privacy" answers -App Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press +App Store Connect > your app > App Privacy (https://appstoreconnect.apple.com/apps, then the app; `storebound console plan apple-app-privacy` gives the deep link). **Web UI only, there is no API.** Fill it from this table, then press **Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety answers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.** diff --git a/templates/profiles/app/declarations.md b/templates/profiles/app/declarations.md index 5ce1aa9..96e65a6 100644 --- a/templates/profiles/app/declarations.md +++ b/templates/profiles/app/declarations.md @@ -3,7 +3,7 @@ Starting answers for a typical **utility / productivity app**: an optional account (email + password, or sign in with a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with strangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything -marked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for +marked **UI** has no API: Play Console > your app > Policy and programs > **App content** (https://play.google.com/console), and App Store Connect for Apple. Data safety (Play) has an API: `storebound play data-safety`. ## Google Play · App content (UI) @@ -48,7 +48,7 @@ content publicly. ## App Store (UI-only parts) -- App record: App Store Connect > Apps > + > New App. No API. +- App record: App Store Connect > Apps > + > New App (https://appstoreconnect.apple.com/apps). No API. - App Privacy: `app-privacy.md`, then **Publish**. - App Review information: `storebound apple review-details` sends the contact, the demo account (from `.storebound/credentials.json`, masked in the log) and the notes. diff --git a/templates/profiles/subscription-app/app-privacy.md b/templates/profiles/subscription-app/app-privacy.md index 94f8851..00d9f54 100644 --- a/templates/profiles/subscription-app/app-privacy.md +++ b/templates/profiles/subscription-app/app-privacy.md @@ -1,6 +1,6 @@ # Example App · App Store "App Privacy" answers -App Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press +App Store Connect > your app > App Privacy (https://appstoreconnect.apple.com/apps, then the app; `storebound console plan apple-app-privacy` gives the deep link). **Web UI only, there is no API.** Fill it from this table, then press **Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety answers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.** diff --git a/templates/profiles/subscription-app/declarations.md b/templates/profiles/subscription-app/declarations.md index 6d786de..3e137ee 100644 --- a/templates/profiles/subscription-app/declarations.md +++ b/templates/profiles/subscription-app/declarations.md @@ -3,7 +3,7 @@ Starting answers for a typical **subscription app**: auto-renewable subscriptions (free trial on the yearly plan), an account (email + password, or sign in with a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with strangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything -marked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for +marked **UI** has no API: Play Console > your app > Policy and programs > **App content** (https://play.google.com/console), and App Store Connect for Apple. Data safety (Play) has an API: `storebound play data-safety`. ## Google Play · App content (UI) @@ -48,7 +48,7 @@ content publicly. ## App Store (UI-only parts) -- App record: App Store Connect > Apps > + > New App. No API. +- App record: App Store Connect > Apps > + > New App (https://appstoreconnect.apple.com/apps). No API. - App Privacy: `app-privacy.md`, then **Publish**. - App Review information: `storebound apple review-details` sends the contact, the demo account (from `.storebound/credentials.json`, masked in the log) and the notes. diff --git a/tests/console-links.test.ts b/tests/console-links.test.ts new file mode 100644 index 0000000..2182ce7 --- /dev/null +++ b/tests/console-links.test.ts @@ -0,0 +1,83 @@ +// Console deep links (pitfall 71): every page key builds an absolute https URL with and without ids, and no message in +// cli/ sends the owner to "App Store Connect > ..." / "Play Console > ..." / "App content > ..." without a URL. +import { afterEach, describe, expect, test } from 'bun:test'; +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join } from 'node:path'; +import { CONSOLE_PAGES, type ConsolePage, consoleLink, consoleUrl, consoleUrlExact, playTrackPage, setConsoleIds } from '../cli/lib/console-links'; + +const IDS = { appleAppId: '1000000001', playDeveloperId: '5550000000000000001', playAppId: '4970000000000000002' }; +const PAGES = Object.keys(CONSOLE_PAGES) as ConsolePage[]; + +afterEach(() => setConsoleIds({})); + +describe('console-links', () => { + test('every page: absolute https URL without ids', () => { + for (const p of PAGES) { + const u = consoleUrl(p, {}); + expect(u).toMatch(/^https:\/\/(appstoreconnect\.apple\.com|play\.google\.com\/console)/); + expect(() => new URL(u)).not.toThrow(); + expect(consoleLink(p, '', {})).toContain(`: ${u}`); + } + }); + + test('every page: id-specific URL with ids', () => { + for (const p of PAGES) { + const u = consoleUrl(p, IDS); + expect(u.startsWith('https://')).toBe(true); + expect(consoleUrlExact(p, IDS)).toBe(true); + const page = CONSOLE_PAGES[p]; + if (page.scope === 'app') expect(u).toContain(page.store === 'apple' ? `/apps/${IDS.appleAppId}/` : `/developers/${IDS.playDeveloperId}/app/${IDS.playAppId}/`); + if (page.store === 'play' && page.scope === 'developer') expect(u).toContain(`/developers/${IDS.playDeveloperId}/`); + } + }); + + test('fallbacks: app list / developer chooser; reused verified shapes', () => { + expect(consoleUrl('asc.age-rating', {})).toBe('https://appstoreconnect.apple.com/apps'); + expect(consoleUrl('play.data-safety', {})).toBe('https://play.google.com/console'); + expect(consoleUrl('play.data-safety', { playDeveloperId: IDS.playDeveloperId })).toBe(`https://play.google.com/console/u/0/developers/${IDS.playDeveloperId}/app-list`); + expect(consoleUrl('play.dashboard', IDS)).toBe(`https://play.google.com/console/u/0/developers/${IDS.playDeveloperId}/app/${IDS.playAppId}/app-dashboard`); + expect(consoleUrl('play.content-rating', IDS)).toEndWith('/app-content/content-rating-overview'); + expect(consoleUrl('play.create-app', IDS)).toEndWith(`/developers/${IDS.playDeveloperId}/create-new-app`); + expect(consoleUrl('asc.keys', {})).toBe('https://appstoreconnect.apple.com/access/integrations/api'); + expect(consoleUrlExact('asc.app-privacy', {})).toBe(false); + expect(consoleUrl('asc.app-info', { appleAppId: 'not-a-number' })).toBe('https://appstoreconnect.apple.com/apps'); + }); + + test('ids remembered from the loaded config', () => { + setConsoleIds(IDS); + expect(consoleUrl('asc.app-privacy')).toBe(`https://appstoreconnect.apple.com/apps/${IDS.appleAppId}/distribution/privacy`); + expect(consoleLink('play.app-access')).toContain(`/app/${IDS.playAppId}/app-content/testing-credentials`); + expect(playTrackPage('production')).toBe('play.production'); + expect(playTrackPage('beta')).toBe('play.releases'); + }); +}); + +describe('guard: no plain console directions in cli/', () => { + const PLAIN = /App Store Connect >|Play Console >|App content >/; + const LINKED = /https:\/\/|consoleLink\(|consoleUrl\(|\.url\b|\$\{\w*[Uu]rl\}/; // ${url}: a variable that holds the absolute link + const SKIP = new Set(['console-links.ts', 'embedded.ts']); // embedded.ts is generated from templates/ (checked below) + const files = (dir: string): string[] => readdirSync(dir).flatMap(n => { + const p = join(dir, n); + return statSync(p).isDirectory() ? files(p) : p.endsWith('.ts') && !p.endsWith('.test.ts') && !SKIP.has(n) ? [p] : []; + }); + + test('every message line with a console path carries an https URL or goes through console-links', () => { + const bad: string[] = []; + for (const f of files('cli')) { + readFileSync(f, 'utf8').split('\n').forEach((line, i) => { + const t = line.trim(); + if (t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) return; // comments are not messages + if (PLAIN.test(line) && !LINKED.test(line)) bad.push(`${f}:${i + 1}: ${t.slice(0, 120)}`); + }); + } + expect(bad).toEqual([]); + }); + + test('templates: console directions in the generated docs carry a URL', () => { + const bad: string[] = []; + const walk = (dir: string): string[] => readdirSync(dir).flatMap(n => { const p = join(dir, n); return statSync(p).isDirectory() ? walk(p) : p.endsWith('.md') ? [p] : []; }); + for (const f of walk('templates')) + readFileSync(f, 'utf8').split('\n').forEach((line, i) => { if (/App Store Connect >|Play Console >/.test(line) && !/https:\/\//.test(line)) bad.push(`${f}:${i + 1}`); }); + expect(bad).toEqual([]); + }); +});