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:
CRTkafa 2026-10-12 09:33:56 +08:00
parent dfc28c15f8
commit b5b8f2da9d
35 changed files with 1824 additions and 859 deletions

View file

@ -127,7 +127,7 @@ export function declarations(config: ShipConfig, inp: DeclarationInputs): Declar
ask.push({ area: 'dataSafety', id: `ds-row-${i.question}`, text: i.msg, fix: i.msg.replace(/^.*; set /, 'Set ').replace(/\)$/, '.') });
const types = Object.entries(ds.data).map(([id, u]) => ({ id, category: categoryOf(id), shared: !!u.shared, ephemeral: !!u.ephemeral, optional: !!u.optional, collect: [...u.collect], share: [...(u.shared ? u.share ?? [] : [])] }));
dataSafety = {
collectsData: ds.collectsData, encryptedInTransit: ds.encryptedInTransit, deletionRequest: ds.deletionRequest, accountCreation: ds.accountCreation ?? ['PSL_ACM_NONE'],
collectsData: ds.collectsData, encryptedInTransit: ds.encryptedInTransit, partialDeletion: ds.partialDeletion ?? null, accountCreation: ds.accountCreation ?? ['PSL_ACM_NONE'],
types, extra: ds.extra?.length ?? 0, csv, rows: csv ? csv.trim().split('\n').length - 1 : 0,
uploaded: inp.uploaded && csv ? { at: inp.uploaded.at, changes: dataSafetyChanges(inp.uploaded.csv, csv) } : null,
};
@ -135,7 +135,6 @@ export function declarations(config: ShipConfig, inp: DeclarationInputs): Declar
if (!ds.collectsData && types.length) ask.push({ area: 'dataSafety', id: 'ds-contradiction', text: 'collectsData is false but data types are listed.', fix: 'Decide which is true; Play rejects a form that says both.' });
if (profile === 'game' && !ds.data.PSL_DEVICE_ID) ask.push({ area: 'dataSafety', id: 'ds-ads-id', text: 'A game profile usually ships an ads or analytics SDK that reads the advertising id, but PSL_DEVICE_ID is not declared.', fix: 'Check every SDK\'s data disclosure; declare PSL_DEVICE_ID (advertising, analytics) if any reads it.' });
if (config.accounts?.enabled && (ds.accountCreation ?? ['PSL_ACM_NONE']).includes('PSL_ACM_NONE')) ask.push({ area: 'dataSafety', id: 'ds-accounts', text: 'The app has accounts, but the form says no account creation.', fix: `Set dataSafety.accountCreation to the methods the app offers${accountCreationOf(config.accounts.signUp) ? ` (accounts.signUp means ${JSON.stringify(accountCreationOf(config.accounts.signUp))})` : ' (or set accounts.signUp and run storebound scan)'}, and the delete-account URL in dataSafety.extra.` });
if (!ds.deletionRequest && config.accounts?.enabled) ask.push({ area: 'dataSafety', id: 'ds-deletion', text: 'Users can create accounts but deletionRequest is false.', fix: 'Both stores want account deletion (account-deletion skill); then set deletionRequest: true.' });
}
// App Privacy: suggested from the Data safety answers; linked / tracking are questions only the owner can answer