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

@ -82,15 +82,23 @@ export interface DataUsage {
export interface DataSafety {
collectsData: boolean;
encryptedInTransit: boolean;
deletionRequest: boolean;
/**
* @deprecated Not written any more: Play removed "Do you provide a way for users to request that their data is
* deleted?" (PSL_DATA_COLLECTION_USER_REQUEST_DELETE) from the Data safety form. `check` notes it; remove it. With
* accounts, Play asks partialDeletion instead.
*/
deletionRequest?: boolean;
/** Response ids for PSL_SUPPORTED_ACCOUNT_CREATION_METHODS. Default ["PSL_ACM_NONE"] (no accounts). */
accountCreation?: string[];
/**
* Play "Do you provide a way for users to request that some or all of their data is deleted, without requiring them
* to delete their account?" (PSL_SUPPORT_DATA_DELETION_BY_USER). Play requires it once accountCreation is not
* PSL_ACM_NONE. Only the owner knows the answer: leave it out and `check` / `play data-safety --dry` say it is missing.
* Play "Do you provide a way for users to request that their data is deleted?" (PSL_SUPPORT_DATA_DELETION_BY_USER, a
* single choice Play asks once accountCreation is not PSL_ACM_NONE): true = yes, people can request that some or all
* of their data is deleted without deleting the account (DATA_DELETION_YES, plus partialDeletionUrl); false = no
* (DATA_DELETION_NO); 'auto90' = no, but user data is automatically deleted within 90 days
* (DATA_DELETION_NO_AUTO_DELETED). Only the owner knows the answer: leave it out and `check` /
* `play data-safety --dry` say it is missing.
*/
partialDeletion?: boolean;
partialDeletion?: boolean | 'auto90';
/** Web page for that data-only deletion (PSL_DATA_DELETION_URL, required when partialDeletion is true). Default:
* accounts.deletionUrl, when that page also lets people delete data without deleting the account. */
partialDeletionUrl?: string;
@ -101,7 +109,8 @@ export interface DataSafety {
* a generated row replaces that generated question (every response row of it). Google does not publish every
* question id: export the form from Play Console (App content > Data safety > Export to CSV), copy the exact ids
* from it, e.g. { question: '<id from the export>', value: '...' }. The delete-account URL is generated from
* accounts.deletionUrl (PSL_ACCOUNT_DELETION_URL).
* accounts.deletionUrl (PSL_ACCOUNT_DELETION_URL). Every (question, response) pair must be in Play's current form
* (cli/data/play-data-safety-form.csv); `check` and `play data-safety --dry` report one that is not.
*/
extra?: { question: string; response?: string; value: string; label?: string }[];
}
@ -901,7 +910,7 @@ export function validateConfig(c: unknown): string[] {
if (!u.collect?.length) e.push(`dataSafety.data.${t}: at least one collection purpose`);
if (u.shared && !u.share?.length) e.push(`dataSafety.data.${t}: shared, so it needs sharing purposes`);
}
if (cfg.dataSafety.partialDeletion !== undefined && typeof cfg.dataSafety.partialDeletion !== 'boolean') e.push('dataSafety.partialDeletion: true | false');
if (cfg.dataSafety.partialDeletion !== undefined && typeof cfg.dataSafety.partialDeletion !== 'boolean' && cfg.dataSafety.partialDeletion !== 'auto90') e.push("dataSafety.partialDeletion: true | false | 'auto90'");
if (cfg.dataSafety.partialDeletionUrl !== undefined && !URL_RE.test(cfg.dataSafety.partialDeletionUrl)) e.push('dataSafety.partialDeletionUrl: must be an https URL');
for (const [i, x] of (cfg.dataSafety.extra ?? []).entries()) if (!/^[A-Z][A-Z0-9_:]+$/.test(x?.question ?? '') || typeof x.value !== 'string')
e.push(`dataSafety.extra[${i}]: { question: "<PSL_... id from a Play Console export>", value: "..." }`);