scan, privacy answers and credentials from a second game's first release (lessons 35-37): review-prompt needs rating context, not a bare "rate"; account signals skip import lines and module paths, config-only accounts say "from config"; accounts.inApp without deletion code stays a medium 5.1.1(v) finding; privacy-manifest --propose builds NSPrivacyCollectedDataTypes from appPrivacy.types and check warns on an empty list; appPrivacy.types is authoritative in the App Privacy sheet (unlisted = not collected), ad / support / AR types default to not collected without tracking or ad SDKs; Play advertising ID from SDKs, the AD_ID permission, app-ads.txt or the flag, never from a Device ID alone; scan.ignore[].evidence and warnings for entries that match nothing; empty credentials template, placeholder values count as missing (check, doctor)

This commit is contained in:
CRTkafa 2026-10-13 05:39:09 +08:00
parent 86cd79dd45
commit 1366e7e231
34 changed files with 881 additions and 71 deletions

View file

@ -470,3 +470,44 @@ that fails without the fix (see CONTRIBUTING.md).
- **Guard:** `tools/desktop-release.ts` (`archProblems` before signing: every Mach-O must contain arm64, no Intel-only
file, no Info.plist that forces Intel; `sliceProblems`: every Mach-O in the bundle must carry arm64 and x86_64,
nothing x86_64 is run); `tests/desktop-release.test.ts`.
## 35. Scan findings from a bare word, a module path or a config claim
- **Symptom:** `scan` reports "A reward for rating or reviewing the app" on `const rate = F.genRate(lv, bonus.gen(r));`;
account findings cite `import './buildings/register';`; 5.1.1(v) disappears once `accounts.inApp` is set although
the app has no delete-account code.
- **Cause:** the signals matched "rate" next to "bonus", and "/register'" anywhere on a line; the config's word was
taken as proof.
- **Do:** read every finding's evidence before acting on it. A finding whose evidence is not real code is a scan bug:
report it (and accept it with `scan.ignore` and the owner's reason meanwhile). For 5.1.1(v), show the deletion code
path or screen; a config line alone is a claim, not evidence.
- **Guard:** the incentive signal needs a rate-us / review phrase next to a reward, a reward "for rating", or a reward
next to a system review prompt call; sign-in / sign-up / deletion signals skip import and require lines and accept
only absolute auth paths or URLs; config-only accounts read "from config (accounts.enabled)"; deletion claimed only
by the config stays a medium finding ("claimed by config, not found in code"). `tests/game-report.test.ts`.
## 36. Privacy answers derived from the wrong source
- **Symptom:** `privacy-manifest --propose` prints an empty NSPrivacyCollectedDataTypes while `appPrivacy.types` lists
collected types; the App Privacy plan adds "Other Usage Data" you never listed; the Play plan answers "uses
advertising ID: Yes" because Data safety declares a Device ID.
- **Cause:** the proposal only kept the manifest's own entries; the App Privacy sheet mixed in types derived from
dataSafety; the advertising ID answer read PSL_DEVICE_ID (a random install id is not the advertising ID).
- **Do:** treat `appPrivacy.types` as the App Privacy answer: a type it does not list is "not collected". Answer the
advertising ID from the SDKs and the merged manifest (`com.google.android.gms.permission.AD_ID`), never from Data
safety alone; set `console.appContent.advertisingId` when the signals disagree.
- **Guard:** `--propose` builds the list from `appPrivacy.types` (linked, tracking, purposes; NSPrivacyTracking true
only with app-level domains, lesson 29) and `check` warns on an empty list; `console plan` scans the project
(ad / attribution SDKs, AD_ID, support and AR code) and answers No with no signal, asks on a conflict.
`tests/game-report.test.ts`.
## 37. `scan.ignore` that matches nothing; placeholder credentials
- **Symptom:** a `scan.ignore` entry does not accept the finding and nothing says why; commands treat
`YOUR_KEY_ID` or `/absolute/path/to/upload.p12` in `.storebound/credentials.json` as a credential.
- **Cause:** `match` is compared with the finding TITLE, not the evidence; the 0.7.1 credentials template held
example-looking values.
- **Do:** `scan.ignore: [{ rule, reason, match?, evidence?, store? }]`: `match` = text in the finding title (one check
of a rule with several), `evidence` = text in one evidence line as the report prints it (`src/main.ts:12` or a code
fragment); both together narrow further. Never add an entry without the owner's reason. Fill credentials with real
values or leave them empty.
- **Guard:** `scan` (notes) and `check` (warnings) name every entry that accepts no finding; the credentials template
is empty strings; "your-…", `<…>`, "xxx", "/absolute/path/to/…" and "example" values count as missing everywhere
(`check` warns, `doctor` lists them). `tests/game-report.test.ts`.

View file

@ -17,7 +17,7 @@ the stores; run them freely.
Privacy / ATT / privacy manifest proposal, each item "detected (file:line)" or "ask owner", as a diff against the
config. Exit 1 on an open blocker. `--write` adds only the detected answers to the config (show the diff and ask
the owner first; MCP never writes). `--store apple|play`, `--json`. A finding the owner accepts goes into
`scan.ignore: [{ rule, reason, match? }]` (`match` = text in the finding title, to accept one check of a rule with several); never add one without the owner's reason. The Play rules also read
`scan.ignore: [{ rule, reason, match?, evidence? }]` (`match` = text in the finding title, to accept one check of a rule with several; `evidence` = text in one evidence line, e.g. `src/main.ts:12`); never add one without the owner's reason. `scan` and `check` name an entry that matches no finding. The Play rules also read
`play.targetAges` (Families), `play.accountType` / `play.accountCreated` (closed-testing requirement) and
`play.billingPrograms` (payments): ask the owner for them, never guess.
1. `storebound check`: config, both listing files (limits are checked locally: title 30, subtitle 30, keywords 100