Console pages as absolute deep links: cli/lib/console-links.ts builds the URL of every App Store Connect / Play Console page from apple.appId, play.developerId and play.appId (app list / developer chooser when unknown); every plain 'App Store Connect > ...' / 'Play Console > ...' direction in cli/ and templates/ now carries it, validate / scan / declarations / release plan JSON carry url, the desktop shows an Open button; tests/console-links.test.ts guards it; AGENTS rule 11, pitfall 71
This commit is contained in:
parent
f898d642d0
commit
e500e88e40
50 changed files with 381 additions and 92 deletions
|
|
@ -52,6 +52,7 @@
|
|||
// - Impersonation: answer/9888374 (don't imply a relation with someone you are not, e.g. in the title or developer
|
||||
// name). The brand list below is storebound's own short list of well-known app brands, reported as low "check".
|
||||
// Play Integrity / anti-tamper is not a policy: no rule.
|
||||
import { consoleLink } from '../console-links';
|
||||
import { PLAY_AGE_GROUPS } from '../config';
|
||||
import { allSubscriptions } from '../subscriptions';
|
||||
import { grep, type Hit, type ProjectFacts } from './project';
|
||||
|
|
@ -164,7 +165,7 @@ export function playRules(inp: ScanInput, x: PlayCtx): void {
|
|||
if (bg)
|
||||
add({ rule: 'play-background-location', guideline: 'Play: Location permissions', severity: 'high', title: 'ACCESS_BACKGROUND_LOCATION needs a core feature that uses location in the background, the Location permissions declaration and a video',
|
||||
evidence: [ev(bg, bg.name)], ref: PLAY_REFS.backgroundLocation, skill: 'store-declarations',
|
||||
fix: 'Keep it only for one core, user-visible feature (never for ads or analytics alone); fill App content > Sensitive app permissions > Location permissions (one feature, a video of 30 s or less showing the prominent disclosure, the runtime prompt and the feature working in the background), and name the feature in the store description. Otherwise remove it (tools:node="remove" if a library adds it).' });
|
||||
fix: 'Keep it only for one core, user-visible feature (never for ads or analytics alone); fill ' + consoleLink('play.sensitive-permissions', '> Location permissions') + ' (one feature, a video of 30 s or less showing the prominent disclosure, the runtime prompt and the feature working in the background), and name the feature in the store description. Otherwise remove it (tools:node="remove" if a library adds it).' });
|
||||
const media = ['READ_MEDIA_IMAGES', 'READ_MEDIA_VIDEO'].map(n => perms.get(n)).filter(q => !!q);
|
||||
if (media.length)
|
||||
add({ rule: 'play-photo-video', guideline: 'Play: Photo and Video Permissions', severity: 'high', title: `${media.map(q => permName(q.name)).join(' / ')}: only for apps whose core purpose needs broad photo / video access; others use the photo picker`,
|
||||
|
|
@ -193,12 +194,12 @@ export function playRules(inp: ScanInput, x: PlayCtx): void {
|
|||
if (health.length)
|
||||
add({ rule: 'play-health-connect', guideline: 'Play: Health Connect permissions', severity: 'high', title: `Health Connect permissions (${health.slice(0, 3).map(q => permName(q.name).replace(/^health\./, '')).join(', ')}${health.length > 3 ? ', ...' : ''}) need a justification for each one and a permitted use case`,
|
||||
evidence: health.slice(0, 3).map(q => ev(q, q.name)), ref: PLAY_REFS.healthConnect, skill: 'store-declarations',
|
||||
fix: 'Request only the data types a feature uses, justify each permission in the Health Connect declaration, show the privacy policy in the app, and answer App content > Health apps (answer/14738291). Ads, data sales and child-only apps are not allowed to use it.' });
|
||||
fix: 'Request only the data types a feature uses, justify each permission in the Health Connect declaration, show the privacy policy in the app, and answer ' + consoleLink('play.health-apps') + ' (answer/14738291). Ads, data sales and child-only apps are not allowed to use it.' });
|
||||
const fsi = perms.get('USE_FULL_SCREEN_INTENT');
|
||||
if (fsi && (target === null || target >= 34))
|
||||
add({ rule: 'play-full-screen-intent', guideline: 'Play: Full-screen intent', severity: 'high', title: 'USE_FULL_SCREEN_INTENT: on Android 14+ only calling and alarm apps get it by default, and every app declares it in Play Console',
|
||||
evidence: [ev(fsi, fsi.name)], ref: PLAY_REFS.fullScreenIntent, skill: 'store-declarations',
|
||||
fix: 'Remove it unless the core function is alarms or phone / video calls (an SDK may add it: check the merged manifest); otherwise fill App content > Full-screen intent, ask the user with ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT and handle a denial. Never for ads.' });
|
||||
fix: 'Remove it unless the core function is alarms or phone / video calls (an SDK may add it: check the merged manifest); otherwise fill ' + consoleLink('play.full-screen-intent') + ', ask the user with ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT and handle a denial. Never for ads.' });
|
||||
const vpn = a.components.filter(q => q.kind === 'service' && (q.permission === 'android.permission.BIND_VPN_SERVICE' || q.actions.includes('android.net.VpnService')));
|
||||
if (vpn.length)
|
||||
add({ rule: 'play-vpn', guideline: 'Play: VpnService', severity: 'high', title: `VpnService ${vpn[0]!.name}: only for VPN apps and the listed exceptions, with a disclosure, consent and the VpnService declaration`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue