play data-safety: PSL_SUPPORT_DATA_DELETION_BY_USER as Play's single choice (partialDeletion true | false | 'auto90'), drop the removed PSL_DATA_COLLECTION_USER_REQUEST_DELETE (deletionRequest deprecated, check notes it); ship Google's empty form (cli/data/play-data-safety-form.csv, exported 2026-10-12), take every Answer requirement from it and report pairs not in it (formPairIssues) in check, declarations and play data-safety. Pitfall 21
This commit is contained in:
parent
dfc28c15f8
commit
b5b8f2da9d
35 changed files with 1824 additions and 859 deletions
|
|
@ -57,7 +57,7 @@ export const DECLARATIONS: Record<string, DeclarationsResult> = {
|
|||
'p-prism': {
|
||||
profile: 'game', stores: { apple: true, play: true },
|
||||
dataSafety: {
|
||||
collectsData: true, encryptedInTransit: true, deletionRequest: false, accountCreation: ['PSL_ACM_NONE'], extra: 0, csv: PRISM_CSV, rows: 431,
|
||||
collectsData: true, encryptedInTransit: true, accountCreation: ['PSL_ACM_NONE'], partialDeletion: null, extra: 0, csv: PRISM_CSV, rows: 431,
|
||||
types: [
|
||||
{ id: 'PSL_DEVICE_ID', category: 'identifiers', shared: true, ephemeral: false, optional: false, collect: ['advertising', 'analytics'], share: ['advertising'] },
|
||||
{ id: 'PSL_APPROX_LOCATION', category: 'location', shared: true, ephemeral: false, optional: false, collect: ['advertising'], share: ['advertising'] },
|
||||
|
|
@ -89,7 +89,7 @@ export const DECLARATIONS: Record<string, DeclarationsResult> = {
|
|||
'p-tidy': {
|
||||
profile: 'app', stores: { apple: true, play: true },
|
||||
dataSafety: {
|
||||
collectsData: true, encryptedInTransit: true, deletionRequest: true, accountCreation: ['PSL_ACM_USER_ID_PASSWORD'], extra: 1, csv: PRISM_CSV, rows: 432,
|
||||
collectsData: true, encryptedInTransit: true, accountCreation: ['PSL_ACM_USER_ID_PASSWORD'], partialDeletion: false, extra: 1, csv: PRISM_CSV, rows: 432,
|
||||
types: [
|
||||
{ id: 'PSL_EMAIL', category: 'personal', shared: false, ephemeral: false, optional: false, collect: ['functionality', 'account'], share: [] },
|
||||
{ id: 'PSL_USER_ACCOUNT', category: 'personal', shared: false, ephemeral: false, optional: false, collect: ['functionality', 'account'], share: [] },
|
||||
|
|
|
|||
|
|
@ -1153,7 +1153,8 @@ export const en = {
|
|||
'ai.dsNoneText': 'Add dataSafety to storebound.config.ts (store-declarations skill): what the app and every SDK collect, why, and whether it is shared.',
|
||||
'ai.dsCollects': 'Collects or shares data',
|
||||
'ai.dsEncrypted': 'Encrypted in transit',
|
||||
'ai.dsDeletion': 'Users can ask for deletion',
|
||||
'ai.dsDeletion': 'Data deletion without deleting the account',
|
||||
'ai.dsAuto90': 'No, deleted automatically within 90 days',
|
||||
'ai.dsAccounts': 'Account creation',
|
||||
'ai.dsNever': 'Play cannot read this form back. Once you upload from here, this tab shows what changed since.',
|
||||
'ai.dsSame': 'Same as the last upload ({when}).',
|
||||
|
|
|
|||
|
|
@ -1155,7 +1155,8 @@ export const tr: Record<keyof typeof en, string> = {
|
|||
'ai.dsNoneText': 'storebound.config.ts içine dataSafety ekleyin (store-declarations becerisi): uygulama ve her SDK neyi, neden topluyor ve paylaşıyor mu.',
|
||||
'ai.dsCollects': 'Veri topluyor veya paylaşıyor',
|
||||
'ai.dsEncrypted': 'Aktarımda şifreli',
|
||||
'ai.dsDeletion': 'Kullanıcılar silme isteyebilir',
|
||||
'ai.dsDeletion': 'Hesabı silmeden veri silme',
|
||||
'ai.dsAuto90': 'Hayır, 90 gün içinde otomatik silinir',
|
||||
'ai.dsAccounts': 'Hesap oluşturma',
|
||||
'ai.dsNever': 'Play bu formu geri okuyamaz. Buradan bir kez yükledikten sonra bu sekme o zamandan beri neyin değiştiğini gösterir.',
|
||||
'ai.dsSame': 'Son yüklemeyle aynı ({when}).',
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export function declarationsMd(app: string, d: DeclarationsResult): string {
|
|||
const yes = (b: boolean | null | undefined) => (b === null || b === undefined ? 'not answered' : b ? 'yes' : 'no');
|
||||
const out = [`# ${app}: store declarations`, '', 'From storebound.config.ts and .storebound/ (contact and demo account masked).', ''];
|
||||
if (d.ask.length) out.push('## Ask the owner', '', mdTable(['area', 'question', 'how to answer'], d.ask.map(a => [a.area, a.text, a.fix])));
|
||||
if (d.dataSafety) out.push('## Google Play Data safety', '', `Collects data: ${yes(d.dataSafety.collectsData)} · encrypted in transit: ${yes(d.dataSafety.encryptedInTransit)} · deletion request: ${yes(d.dataSafety.deletionRequest)}`, '',
|
||||
if (d.dataSafety) out.push('## Google Play Data safety', '', `Collects data: ${yes(d.dataSafety.collectsData)} · encrypted in transit: ${yes(d.dataSafety.encryptedInTransit)}${d.dataSafety.accountCreation.includes('PSL_ACM_NONE') ? '' : ` · data deletion without deleting the account: ${d.dataSafety.partialDeletion === 'auto90' ? 'no, deleted automatically within 90 days' : yes(d.dataSafety.partialDeletion)}`}`, '',
|
||||
mdTable(['data type', 'category', 'collected for', 'shared for', 'optional', 'ephemeral'], d.dataSafety.types.map(x => [x.id, x.category, x.collect.join(', '), x.shared ? x.share.join(', ') : 'not shared', yes(x.optional), yes(x.ephemeral)])));
|
||||
if (d.appPrivacy) out.push('## App Store App Privacy (enter in App Store Connect)', '', mdTable(['data type', 'category', 'purposes', 'third party', 'from'], d.appPrivacy.items.map(x => [x.apple, x.category, x.purposes.join(', '), yes(x.thirdParty), x.from.join(', ')])));
|
||||
if (d.appInfo) out.push('## App Store app info', '', `Category: ${d.appInfo.categories ? [d.appInfo.categories.primary, d.appInfo.categories.primarySubOne, d.appInfo.categories.primarySubTwo].filter(Boolean).join(' / ') : 'not set'}`,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function DataSafety({ project, d, onDone }: { project: Project; d: DeclarationsR
|
|||
<dl className="kv">
|
||||
<dt>{t('ai.dsCollects')}</dt><dd>{yes(ds.collectsData)}</dd>
|
||||
<dt>{t('ai.dsEncrypted')}</dt><dd>{yes(ds.encryptedInTransit)}</dd>
|
||||
<dt>{t('ai.dsDeletion')}</dt><dd>{yes(ds.deletionRequest)}</dd>
|
||||
{!ds.accountCreation.includes('PSL_ACM_NONE') && <><dt>{t('ai.dsDeletion')}</dt><dd>{ds.partialDeletion === 'auto90' ? t('ai.dsAuto90') : yes(ds.partialDeletion)}</dd></>}
|
||||
<dt>{t('ai.dsAccounts')}</dt><dd className="small">{ds.accountCreation.map(x => typeLabel(x.replace('ACM_', ''))).join(', ')}</dd>
|
||||
</dl>
|
||||
{up === null ? <Notice tone="info" icon="info">{t('ai.dsNever')}</Notice>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue