$ storebound console plan play-content-rating Play Console: Content rating (IARC) start: https://play.google.com/console then: Pick the developer account if asked (set play.developerId for a deep link) then: the app (com.example.prismpuddle) in the app list then: Policy and programs > App content then: Content rating > Start (or Manage) Steps: 1. Open App content > Content ratings (/app-content/content-rating-overview), then "Start questionnaire": it opens /app-content/content-rating-iarc-questionnaire. [click: "Start questionnaire"] 2. Page 1: type the email address and pick the category (material radio: JS click on the inner [role=radio]). The IARC Terms of Use checkbox is an agreement: never tick it; the owner ticks it (it is in the batch). Then Next. [click: "Next"] 3. The questionnaire is ONE long page of sections (Violence, Fear, Sexuality, Gambling, Language, Controlled Substance, Crude Humor, Digital Purchases, Miscellaneous). Answer each section's Yes / No as the sheet says; each Yes reveals follow-ups right below it, so answer top to bottom and re-read the page after every Yes. Run it as one in-page job (plan.inPageLoop) and poll window.__log. 4. Violence follow-ups (after Yes): the humans / non-humans checkboxes, then setting, childlike style, reactions, presentation checkboxes, blood level, historical war setting, innocent characters, fierce sounds. 5. Fear, Sexuality, Gambling, Language, Controlled Substance, Crude Humor follow-ups (after Yes): answer the questions shown as the sheet says (details.
.other). 6. Digital Purchases follow-ups (after "purchases of digital goods" Yes): the random items (loot box) question, then trading between players. 7. Miscellaneous: user interaction (after Yes: block, report, moderation, friends only), location sharing, Nazi symbols, Korea history, terrorism, crimes. 8. Press Save (stores the draft; nothing goes to IARC yet), then Next: the Summary shows the rating per authority (PEGI, ESRB, USK, ClassInd, Google Play ...). [click: "Save", "Next"] 9. Summary: stop. Do NOT press its Save (the final button here is "Save", not "Submit"): tell the owner the ratings shown and let them press Save (it applies the IARC ratings). 10. Verify: read the Summary page as text and pipe it to `storebound console verify play-content-rating --from-text -`. Answer sheet: [ok] Email address: s***@example.com -> type "Email address" @ Email address and category [ask] Category: (you decide) -> pick "Category" @ Email address and category Pick the category; set console.contentRating.category to have it filled. [you] IARC Terms of Use: the owner ticks it (an agreement) -> untick "IARC Terms of Use" @ Email address and category An agreement on page 1 (a checkbox): only the owner ticks it, never the agent or storebound. [ask] Violence: (you decide) -> pick "Does the app contain (any )violen" @ Questionnaire (Does the app contain (any )violen) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.violence.humans, violence.nonHumans, violence.setting, violence.childlike, violence.reactions, violence.presentation, violence.blood, violence.historicalWar, violence.innocents, violence.fierceSounds too. [ask] Fear / horror: (you decide) -> pick "Does the app contain …scary" @ Questionnaire (Does the app contain …scary) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.fear.other too. [ask] Sexuality or nudity: (you decide) -> pick "Does the app contain …sexual" @ Questionnaire (Does the app contain …sexual) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.sexuality.other too. [ask] Language (profanity): (you decide) -> pick "Does the app contain …profan" @ Questionnaire (Does the app contain …profan) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.language.other too. [ask] Controlled substances (drugs, alcohol, tobacco): (you decide) -> pick "Does the app contain …drug" @ Questionnaire (Does the app contain …drug) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.controlledSubstances.other too. [ask] Crude humour: (you decide) -> pick "crude humour" @ Questionnaire (crude humour) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.crudeHumor.other too. [ask] Gambling with real money: (you decide) -> pick "Does the app allow …gambl…real money" @ Questionnaire (Does the app allow …gambl…real money) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.gambling.other too. [ask] Simulated gambling: (you decide) -> pick "simulated gambling" @ Questionnaire (simulated gambling) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.gambling.other too. [ok] Random paid items (loot boxes): No -> pick "No" @ Questionnaire (random (paid )items) [ask] Users can interact or exchange content: (you decide) -> pick "users …interact" @ Questionnaire (users …interact) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.userInteraction.block, userInteraction.report, userInteraction.moderation, userInteraction.friendsOnly too. [ask] Shares the user's location with other users: (you decide) -> pick "share …(physical )location" @ Questionnaire (share …(physical )location) Answer from the app's real content; set console.contentRating.answers to have it filled. [ask] Digital purchases: (you decide) -> pick "digital goods" @ Questionnaire (digital goods) Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.digitalPurchases.playerTrading too. [ask] Unrestricted internet access (browser): (you decide) -> pick "unrestricted internet" @ Questionnaire (unrestricted internet) Answer from the app's real content; set console.contentRating.answers to have it filled. [ask] Miscellaneous: Nazi symbols or references?: (you decide) -> pick "nazi" @ Questionnaire: Miscellaneous follow-ups (nazi) Answer from the app's real content; set console.contentRating.details.miscellaneous.naziSymbols to have it filled. [ask] Miscellaneous: content about Korean history that may be sensitive?: (you decide) -> pick "korea" @ Questionnaire: Miscellaneous follow-ups (korea) Answer from the app's real content; set console.contentRating.details.miscellaneous.koreaHistory to have it filled. [ask] Miscellaneous: terrorism or extremist content?: (you decide) -> pick "terror" @ Questionnaire: Miscellaneous follow-ups (terror) Answer from the app's real content; set console.contentRating.details.miscellaneous.terrorism to have it filled. [ask] Miscellaneous: depicts or encourages crimes?: (you decide) -> pick "crime" @ Questionnaire: Miscellaneous follow-ups (crime) Answer from the app's real content; set console.contentRating.details.miscellaneous.crimes to have it filled. Never press: "IARC Terms of Use box", "Save (Summary page)", "Publish", "Submit", "Create", "Create app", "Send", "Release", "Definitions and Examples", "?", "More information" Final button (the owner's): Save (Summary page) Rule: Press only navigation (Next, Back, Get Started, Edit, Set Up, Start / Manage links) and draft saves (Save draft / Save (questionnaire page)). Never press Save (Summary page) or anything that publishes, submits, creates, sends or accepts; stop and tell the owner. UI facts: - Verified 2026-10-12 (an agent with a browser tool on a real game): - App content > Content ratings is /app-content/content-rating-overview; "Start questionnaire" opens /app-content/content-rating-iarc-questionnaire. - Page 1: email address, the category radio, and an IARC Terms of Use checkbox: an agreement, the owner ticks it, never the agent. - The questionnaire is one long page of sections; each Yes reveals follow-ups (Violence: humans / non-humans checkboxes, then setting, childlike style, reactions, presentation checkboxes, blood level, historical war setting, innocent characters, fierce sounds; Digital Purchases: digital goods, then loot boxes, then player trading; Miscellaneous: user interaction then block / report / moderation / friends-only, location sharing, Nazi symbols, Korea history, terrorism, crimes). Follow-up wording here is storebound's: match by topic and the console's own labels. - Save on the questionnaire stores it; Next goes to the Summary with per-authority ratings. The Summary's final button is "Save" (not "Submit"): the owner's. - Example result (the game of that run): PEGI 7, ESRB E10+, USK 12, ClassInd 18 (paid random items alone made it 18), Google Play 7+. - The email address field gets the full app.contactEmail from the config (printed masked here). - Play Console radios and checkboxes are / : computer-tool clicks and setting the native input (form_input) can leave the Angular model unchanged. Click the inner [role=radio] / [role=checkbox] (or the itself) with a JS el.click(), then read aria-checked (or input.checked). Their labels sit in sibling nodes, not in an aria-label: walk up from the control to the nearest parent whose innerText is not empty and match by "starts with" (verified 2026-10-12). - An app's Play Console home is /console/u/0/developers//app//app-dashboard (verified 2026-10-12); with play.appId (or the id storebound remembered) the plan starts there. Driving: - Use your own browser tool (Claude in Chrome, a built-in browser, Playwright). Read the page as text or an accessibility tree first; click by visible text; adapt when the page differs from these steps (the console changes without notice), but never press anything in buttons.neverPress. - Long flows run INSIDE the page: one Runtime.evaluate / javascript call times out at about 45 s on big Play pages (the IARC questionnaire, App content), so never loop over many controls in one call. Paste plan.inPageLoop once per page (one short call; it defines window.__sb), start the job with window.__sb.run([{ role: "radio", label: "No", within: "" }, { role: "checkbox", label: "", want: true }, ...]) (it returns at once), then poll window.__log.slice(-20) and window.__done in short calls every 2-5 s until the log says done. The helper only picks radios and ticks boxes (never agreements); press Next / Save yourself afterwards, by their visible text. - Wait for each step to render (poll for the next heading or input, up to ~10 s) before acting; never act on a step you have not read. - An answer with status ask-owner: ask the owner in chat before filling it (AGENTS.md rule 5), or leave it empty and tell the owner. Never guess. - Stop before the final button (buttons.final) and tell the owner exactly what to press; then run the verify command. - Never sign in, type a password, accept an agreement or tick a declaration: the owner does that in the browser. In-page helper (plan.inPageLoop: paste it once per page in one short call, then window.__sb.run([...]) and poll window.__log): (() => { const norm = (t) => (t || "").replace(/\s+/g, " ").trim(); const log = (m) => { window.__log.push(new Date().toISOString().slice(11, 19) + " " + m); }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const labelOf = (el) => { const own = norm(el.getAttribute("aria-label")); if (own) return own; for (let a = el; a && a !== document.body; a = a.parentElement) { const t = norm(a.innerText); if (t) return t; } return ""; }; const NEVER = /terms of use|terms of service|i agree|agree to|declaration|developer program polic|export laws/i; const CONTROLS = "[role=radio], [role=checkbox], input[type=radio], input[type=checkbox]"; const scopeOf = (text) => { const want = norm(text).toLowerCase(); let best = null; for (const el of document.querySelectorAll("section, fieldset, div, [role=radiogroup], [role=group]")) if (norm(el.innerText).toLowerCase().includes(want) && el.querySelector(CONTROLS) && (!best || best.contains(el))) best = el; return best; }; const find = (role, label, scope) => { const want = norm(label).toLowerCase(); const hits = [...(scope || document).querySelectorAll("[role=" + role + "], input[type=" + role + "]")] .filter((el) => el.getClientRects().length && labelOf(el).toLowerCase().startsWith(want)); return hits.length === 1 ? hits[0] : null; }; const set = async (s) => { const scope = s.within ? scopeOf(s.within) : null; if (s.within && !scope) return "no block with \"" + s.within + "\""; const el = find(s.role, s.label, scope); if (!el) return "not found or ambiguous"; if (NEVER.test(labelOf(el))) return "refused: an agreement (the owner ticks it)"; const host = el.closest("material-radio, material-checkbox"); const inner = el.matches("[role=radio], [role=checkbox]") ? el : (host && host.querySelector("[role=radio], [role=checkbox]")) || el; const state = () => (inner.hasAttribute("aria-checked") ? inner.getAttribute("aria-checked") === "true" : !!inner.checked); const want = s.want !== false; if (state() !== want) inner.click(); await sleep(150); return state() === want ? "ok" : "did not change (read the control, try the itself)"; }; window.__log = []; window.__done = false; window.__sb = { log, sleep, labelOf, scopeOf, find, set }; window.__sb.run = (steps) => { window.__done = false; window.__run = (async () => { for (let i = 0; i < steps.length; i++) { const s = steps[i], tag = (i + 1) + "/" + steps.length + " " + s.role + " \"" + s.label + "\"" + (s.within ? " in \"" + s.within + "\"" : ""); try { log(tag + ": " + await set(s)); } catch (e) { log(tag + ": error " + e.message); } } log("done"); window.__done = true; })(); return "started " + steps.length + " steps: poll window.__log"; }; return "storebound helper ready: window.__sb.run([...]), then poll window.__log.slice(-20) and window.__done"; })() Verify: storebound console verify play-content-rating --from-text - - The Summary shows each question with the Yes / No of the answer sheet, and a rating per authority. Ask the owner first: - Category: ask the owner (Pick the category; set console.contentRating.category to have it filled.) - IARC Terms of Use: an agreement, the owner ticks it - Violence: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.violence.humans, violence.nonHumans, violence.setting, violence.childlike, violence.reactions, violence.presentation, violence.blood, violence.historicalWar, violence.innocents, violence.fierceSounds too.) - Fear / horror: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.fear.other too.) - Sexuality or nudity: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.sexuality.other too.) - Language (profanity): ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.language.other too.) - Controlled substances (drugs, alcohol, tobacco): ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.controlledSubstances.other too.) - Crude humour: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.crudeHumor.other too.) - Gambling with real money: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.gambling.other too.) - Simulated gambling: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.gambling.other too.) - Users can interact or exchange content: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.userInteraction.block, userInteraction.report, userInteraction.moderation, userInteraction.friendsOnly too.) - Shares the user's location with other users: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled.) - Digital purchases: ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled. If Yes, the page asks follow-ups: set console.contentRating.details.digitalPurchases.playerTrading too.) - Unrestricted internet access (browser): ask the owner (Answer from the app's real content; set console.contentRating.answers to have it filled.) - Miscellaneous: Nazi symbols or references?: ask the owner (Answer from the app's real content; set console.contentRating.details.miscellaneous.naziSymbols to have it filled.) - Miscellaneous: content about Korean history that may be sensitive?: ask the owner (Answer from the app's real content; set console.contentRating.details.miscellaneous.koreaHistory to have it filled.) - Miscellaneous: terrorism or extremist content?: ask the owner (Answer from the app's real content; set console.contentRating.details.miscellaneous.terrorism to have it filled.) - Miscellaneous: depicts or encourages crimes?: ask the owner (Answer from the app's real content; set console.contentRating.details.miscellaneous.crimes to have it filled.)