--- import Base from '../layouts/Base.astro'; import PageHeader from '../components/PageHeader.astro'; import { assertScopeNonEmpty, collectedIn } from '../data/collection.mjs'; import { legal } from '../data/legal.mjs'; import { brand } from '../lib/brand.mjs'; /** * `/privacy` — PLAN.md §9, built in phase 6. The URL given to Google Play. * * --------------------------------------------------------------------------------------- * THREE SCOPES, NEVER MERGED * --------------------------------------------------------------------------------------- * §9's structure is the substance of the page rather than its layout. Runic Gateway is * self-hosted software, so "we" means three different parties depending on which sentence * you are reading, and a policy that blurred them would be wrong in both directions at * once: it would claim responsibility for data we cannot see, and it would let a player * believe this page governs the community site they actually use. * * So the page is three separately-scoped sections with the boundary stated in each, and * the rows come from `src/data/collection.mjs` — the same array `scripts/playDataSafety.mjs` * answers the console form from (D33). A published policy and a Play declaration that * disagree is the failure this repository already builds machinery against elsewhere. * * --------------------------------------------------------------------------------------- * NO ADDRESS IN THIS FILE * --------------------------------------------------------------------------------------- * The contact route is `brand.contactEmail`, read from the mounted `brand.json`. D13 * publishes a personal address on the promise that replacing it with `privacy@` later * costs one file copy, and `checkFacts.mjs` fails the build if an address is typed into any * source file. This is the page most likely to want to — a privacy policy is where an * address belongs — which is exactly why the rule has to hold here. */ const title = 'Privacy'; const description = 'What this site collects, what the Android app holds on your device, and what a ' + 'self-hosted deployment is responsible for.'; /* A section with nothing under it reads as a claim rather than an omission. */ for (const scope of ['site', 'app', 'deployment']) assertScopeNonEmpty(scope); const sections = [ { id: 'this-site', number: 1, heading: 'This website', controller: 'We are responsible for this section.', lede: 'There are no cookies, no analytics, no tracking scripts and no third-party ' + 'requests of any kind — not as a policy we promise to keep, but as a description of ' + 'what the pages load. The only thing this site ever asks you for is an email ' + 'address for the Android beta, and only if you choose to give one.', rows: collectedIn('site'), }, { id: 'the-app', number: 2, heading: 'The Android app', controller: 'We operate no server the app talks to.', lede: 'This is the part that makes the app unusual, and it is worth reading rather than ' + 'skimming. The app ships pointed at nothing: on first run it asks for the address ' + 'of a Runic Gateway site and nothing else in the app works until one is entered and ' + 'validated. That site is run by whoever runs that community. Everything you do in ' + 'the app happens between your phone and their server, and there is no account with ' + 'us, no service of ours in the middle, and no copy of anything on our side — because ' + 'we do not operate one.', rows: collectedIn('app'), }, { id: 'deployments', number: 3, heading: 'Self-hosted deployments', controller: 'The operator of that deployment is responsible, not us.', lede: 'Runic Gateway is software people install on their own machines. If you play on a ' + 'community that runs it, your account lives on their server, under their control ' + 'and their policy — this page is not it. What follows is an inventory of what the ' + 'software collects, so that an operator can see plainly what they are taking on, ' + 'and a player can see what to ask their operator about.', rows: collectedIn('deployment'), }, ]; ---

Written from what the code does rather than from a template — every entry below was read out of the file that implements it, and the file is named. It is deliberately specific in the places a policy is usually vague, because the vague places are the ones that matter.

Three sections, because there are three different answers to “who has this”. Read the one that applies to you; the boundaries between them are real.

{ sections.map((section) => ( )) }