play data-safety: write the conditional required rows (PSL_ACCOUNT_DELETION_URL from accounts.deletionUrl, PSL_SUPPORT_DATA_DELETION_BY_USER from dataSafety.partialDeletion, PSL_DATA_DELETION_URL); a rule table checks every conditional row in check, declarations and --dry before Play answers 400

This commit is contained in:
CRTkafa 2026-10-12 08:25:14 +08:00
parent c9a7f095c4
commit 97ab76e9c8
14 changed files with 295 additions and 24 deletions

View file

@ -37,10 +37,13 @@ deletion requirements" (https://support.google.com/googleplay/android-developer/
authentication" (`PSL_ACM_USER_ID_OTHER_AUTH`) with User ID as the only account data; `username-password` and
`email-password` are "Username and password" (`PSL_ACM_USER_ID_PASSWORD`); `email-link` and `phone` are
`PSL_ACM_USER_ID_OTHER_AUTH`; `oauth` is `PSL_ACM_OAUTH`; `other` is `PSL_ACM_OTHER`. An account alone never means
an email address: declare Email only when the app asks for one. Google does not publish the question ids for the delete-account URL: export the form once
(Play Console > App content > Data safety > Export to CSV), copy the id, and add it as
`dataSafety.extra: [{ question: '<id>', value: '<deletionUrl>' }]`; otherwise enter the URL in the Console after
`storebound play data-safety`.
an email address: declare Email only when the app asks for one. With accounts Play requires more rows, and storebound
writes them: the delete-account URL (`PSL_ACCOUNT_DELETION_URL` = `accounts.deletionUrl`) and "can people delete
some or all of their data without deleting the account?" (`PSL_SUPPORT_DATA_DELETION_BY_USER` =
`dataSafety.partialDeletion`; ask the owner, never guess), plus its web page when the answer is yes
(`PSL_DATA_DELETION_URL` = `dataSafety.partialDeletionUrl`, default `accounts.deletionUrl`). `storebound check` and
`play data-safety --dry` say `missing: <id> (... set <field>)` for any of them; without them Play answers 400
"Response missing for PSL_ACCOUNT_DELETION_URL".
5. Privacy policy: a "Deleting your account" section with both paths (`privacy-policy` skill).
6. Test it on a fresh account before every submission; the reviewer will.

View file

@ -73,6 +73,16 @@ CLI: `storebound` (or `bunx storebound` in a project that has it, or `bun <toolc
- The Data safety CSV import **replaces** the whole form.
- `PSL_SUPPORTED_ACCOUNT_CREATION_METHODS` (e.g. `PSL_ACM_NONE`) is required even though Google's older sample CSV
omits it: without it the import fails with "Response missing". storebound always writes it.
- Conditional required rows: some questions become required by another answer, and Play refuses the import with
"Response missing for <id>" (400). Account creation not `PSL_ACM_NONE` -> `PSL_ACCOUNT_DELETION_URL` (from
`accounts.deletionUrl`) and `PSL_SUPPORT_DATA_DELETION_BY_USER` ("delete some or all data without deleting the
account?", `dataSafety.partialDeletion: true | false`, ask the owner); that answer true -> `PSL_DATA_DELETION_URL`
(`dataSafety.partialDeletionUrl`, default `accounts.deletionUrl`); `PSL_HAS_OUTSIDE_APP_ACCOUNTS` is refused next
to sign-up methods. A selected data type needs collected / shared, ephemeral, required / optional and a purpose
(a sharing purpose when shared). `storebound check` and `play data-safety --dry` name each missing row as
`missing: <id> (<when>; set <config field>)` and the real run stops before sending. Rules:
`DATA_SAFETY_RULES` / `typeRules` in `cli/lib/data-safety.ts`. A `dataSafety.extra` row with a generated
question id replaces that question (use it when your Play Console export shows other ids).
- "Optional" data is only allowed when the user can really choose for that data; otherwise required.
- Ads SDKs: read each network's own Play data safety guidance; adding a network can add data types.
- Target audience under 13 pulls the app into the Families policy (certified ad networks, no ad id): only if true.

View file

@ -230,6 +230,23 @@ Each entry: **symptom** → cause → what to do → what guards it now. The ful
- **Guard:** `console plan` / `console verify` (CLI and the read-only MCP tools `console_plan`, `console_verify`), the
console-autofill skill puts them first; `tests/console-plan.test.ts`.
## 21. Play Data safety: conditional required rows
- **Symptom:** `play data-safety` answers 400 "Invalid safety labels declaration: Response missing for
PSL_ACCOUNT_DELETION_URL" (or another `PSL_...` id) although `--dry` looked fine.
- **Cause:** some questions are required only after another answer. Account creation not `PSL_ACM_NONE` requires the
delete-account URL (`PSL_ACCOUNT_DELETION_URL`) and the data-only deletion question
(`PSL_SUPPORT_DATA_DELETION_BY_USER`; when yes, `PSL_DATA_DELETION_URL`), and Play then refuses
`PSL_HAS_OUTSIDE_APP_ACCOUNTS`. A selected data type needs its whole usage block.
- **Do:** set `accounts.deletionUrl`, ask the owner for `dataSafety.partialDeletion: true | false` (and
`partialDeletionUrl` when true and it is another page), run `storebound check` / `play data-safety --dry` until no
`missing: <id>` is left. If Play names an id storebound does not know, export the form (Play Console > App content >
Data safety > Export to CSV) and add the row in `dataSafety.extra` (same id = replaces the generated row).
- **Guard:** `DATA_SAFETY_RULES` / `typeRules` / `dataSafetyIssues` in `cli/lib/data-safety.ts`; `check`,
`project.declarations` and `play data-safety` (dry and real, before the POST) report them.
- **Sources:** https://support.google.com/googleplay/android-developer/answer/10787469 (Data safety form, CSV import),
https://support.google.com/googleplay/android-developer/answer/13327111 (account deletion: delete-account link;
deleting other data is a separate question).
## Adding a lesson
Every bug found in real use gets an entry in `docs/pitfalls.md` and here, and, where possible, a check or a test
that fails without the fix (see CONTRIBUTING.md).