feat(legal): phase 6 — the privacy policy and the terms
All checks were successful
PR checks / checks (pull_request) Successful in 55s
All checks were successful
PR checks / checks (pull_request) Successful in 55s
PLAN.md §9. Builds /privacy and /terms, links them from the footer on every page,
and generates the Play Data Safety notes from the same inventory the policy renders.
Four decisions taken by the org lead before either page was written, recorded in
§9 under "How phase 6 built the legal pages":
D30 DNS-only records, so the reverse proxy on the host keeps the only access
log. Described qualitatively — the retention belongs to the proxy, and a
policy that quotes a number the deployment does not enforce is worse than
one that does not.
D31 Eighteen or older. Above the children's-consent threshold everywhere in the
EEA, so consent works with no parental-consent machinery this form could not
honestly operate. Four surfaces render it from src/data/legal.mjs, and every
one says plainly that nothing verifies it.
D32 No governing-law clause. Nothing of value is contracted for here.
D33 PLAY_DATA_SAFETY.md is generated from src/data/collection.mjs and checked in
CI, so the published policy and the answers given to Google cannot drift.
/privacy is three separately-scoped sections because "we" means three different
parties: this site (one form, no cookies, no third-party requests), the Android app
(we operate no server it talks to — the rows are what the DEVICE holds), and a
self-hosted deployment (the operator is the controller, not us). Every row names the
file it was read out of, because a policy is the document most likely to be written
from a template and least likely to be re-read against the software.
/terms governs only what we run: this site, the beta list, and the APK we publish.
The software is governed by its licence, and a community's deployment by that
community — a terms page claiming authority over every install of a GPL program is
the thing a generated template gets wrong.
Also here:
- the age clause changed CONSENT_TEXT, so CONSENT_VERSION gained a suffix; rows
written from now on carry the new sentence and older rows keep theirs
- PLANNED_ROUTES is now empty — these were its last two entries, and its reverse
check is what forced the deletion; the list stays for phases 7 and 8
- test/legal.test.mjs asserts the structural promises no build check can see,
including that every mapped Play row still answers "not collected, not shared"
- --check normalises line endings: the repo has no .gitattributes and Windows
checkouts are CRLF, so a byte comparison would fail for every Windows developer
while passing in CI
Verified: npm run verify green end to end (tokens, brand, data safety, astro check,
36 tests, build, 214 links, 19 facts), both pages walked in a browser, and neither
overflows at 390px. One defect the checks could not see and a look could: the
retention line was being pushed to the foot of the tallest card in its row, opening
a void in the middle of the short ones.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
61
PLAN.md
61
PLAN.md
@@ -236,7 +236,7 @@ Taken by the org lead (Colby Whitlock) on 2026-08-19. Recorded so they are not r
|
||||
|
||||
**Decisions after D13 are recorded where they were taken**, in the section describing the phase that
|
||||
raised them, rather than appended here — a decision is only re-litigated when its reasoning is
|
||||
somewhere other than the thing it decided. The count of record is **twenty-five**:
|
||||
somewhere other than the thing it decided. The count of record is **thirty-three**:
|
||||
|
||||
| # | Where | What it settled |
|
||||
|---|---|---|
|
||||
@@ -244,6 +244,7 @@ somewhere other than the thing it decided. The count of record is **twenty-five*
|
||||
| D17–D19 | §10, "How phase 3 built the homepage" | The data-path diagram, all five groups on the homepage, the emblem-led hero |
|
||||
| D20–D25 | §10, "How phase 4 built the marketing pages" | `/features/` as the same list with detail, `/architecture/` as reasons not reference, the absences as data, the two absorbed scope items, `needsModule`, the demo deep links |
|
||||
| D26–D29 | §8, "How phase 5 built the app and the beta" | The screenshot slot reserved for phase 9, the demo as the tester target, `/beta` handling its own POST, equal billing for the APK and the beta |
|
||||
| D30–D33 | §9, "How phase 6 built the legal pages" | One logging hop and no edge provider, eighteen or older, no governing-law clause, the Data Safety notes as a generated document |
|
||||
|
||||
---
|
||||
|
||||
@@ -593,6 +594,64 @@ because it cannot.
|
||||
|
||||
Both pages are linked from the footer on every page, and `/privacy` is the URL given to Play.
|
||||
|
||||
### How phase 6 built the legal pages
|
||||
|
||||
Four decisions taken before either page was written (org lead, 2026-08-24).
|
||||
|
||||
**D30 — One hop in front of the site, and the page says so.** §9 requires `/privacy` to state the
|
||||
access logs and their retention, which needed a fact rather than a guess. The domain's DNS is on
|
||||
Cloudflare but the records are **DNS-only**: no edge provider terminates the connection, so the
|
||||
reverse proxy on the org lead's own host keeps the only access log there is — IP, path, user agent,
|
||||
timestamp — read when something is broken or being attacked, rotated on the proxy's own schedule.
|
||||
The page describes it qualitatively rather than quoting a retention number, because the number
|
||||
belongs to the proxy's configuration and a policy that states one the deployment does not enforce is
|
||||
worse than one that does not. **If the record is ever proxied, this section is wrong and has to be
|
||||
rewritten** — an edge provider that terminates TLS is a processor, and D9's "no third-party
|
||||
requests" would still be true of the browser while ceasing to be the whole story.
|
||||
|
||||
**D31 — Eighteen or older.** Play asks, and the answer decides whether consent alone is a lawful
|
||||
basis in the EEA. Eighteen was chosen over thirteen (Google's own account minimum, but below the
|
||||
children's-consent threshold in several EEA states, so a 13–15 year old's consent would need a
|
||||
parent's — which this form cannot obtain) and over sixteen (sufficient, but no simpler to state).
|
||||
The number lives in `src/data/legal.mjs` because four surfaces render it: `/terms`, `/privacy`, the
|
||||
eligibility list on `/beta`, and the consent sentence itself. **Nothing verifies it and no surface
|
||||
implies otherwise** — the pages say in as many words that ticking the box is the whole of it, which
|
||||
is both accurate and the only claim the code supports.
|
||||
|
||||
Adding the clause changed `CONSENT_TEXT`, which is stored per row rather than versioned — so rows
|
||||
written from now on carry the new sentence and older ones keep theirs. `CONSENT_VERSION` gained a
|
||||
suffix rather than a new date, because the change landed on the day the original wording was
|
||||
written and two different sentences must not share the label an operator groups a CSV by.
|
||||
|
||||
**D32 — No governing-law clause.** Nothing of value is contracted for on this site: it sells
|
||||
nothing, the software is free under a licence that carries its own terms, and the beta is a list of
|
||||
addresses people asked to be on. A jurisdiction clause here would be decoration, and §9's standard
|
||||
for these pages is that accurate and specific beats boilerplate. It stays available: adding one
|
||||
later is a clause, not a rewrite.
|
||||
|
||||
**D33 — The Play Data Safety notes are a generated repository document.** §9 says the declaration is
|
||||
"filled from section 2, and section 2 is written knowing that is what it is for" — so the two are
|
||||
one array, `src/data/collection.mjs`, rendered by `/privacy` as prose and by
|
||||
`scripts/playDataSafety.mjs` as the console's own questions into a committed
|
||||
`PLAY_DATA_SAFETY.md`. `--check` regenerates and fails if the committed copy differs, and CI runs
|
||||
it, so a hand edit is a red build that names the data file to edit instead. The document is
|
||||
operator-facing rather than published: it is a form's worth of console vocabulary no visitor is
|
||||
looking for, and `/privacy` already says the same things in prose.
|
||||
|
||||
Two properties of that file are worth keeping. **It does not pretend to know Play's current
|
||||
definitions** — there is no API to read them from and the requirements have changed more than once
|
||||
(the same reason `playPolicy` carries a `verifiedOn` date), so it holds the facts arranged as the
|
||||
console arranges its questions, with the answer each fact supports and why; a person reads the
|
||||
console's wording against them. And **a test asserts that every mapped row answers "not collected,
|
||||
not shared"**, failing with the reason rather than a diff: "we operate no server the app talks to"
|
||||
is the premise of the whole section, and a telemetry endpoint added later must not be able to
|
||||
produce a row that quietly contradicts the lede three inches above it.
|
||||
|
||||
**What phase 6 also closed.** `/privacy/` and `/terms/` were the last two entries in
|
||||
`checkLinks.mjs`'s `PLANNED_ROUTES`; building them emptied the list, and its reverse check is what
|
||||
forced the deletion. The list itself stays, because §10's documentation routes land in phases 7 and 8
|
||||
under the same convention.
|
||||
|
||||
---
|
||||
|
||||
## 10. Information architecture
|
||||
|
||||
Reference in New Issue
Block a user