| Next revision | Previous revision |
| provenance:privacy:age_assurance [2026/09/15 16:45] – New provenance page for privacy:age_assurance: probe history and the two regex defects, hand verdicts for all 38 candidates, report script and unedited output, quote checks, external sources and rejections. Authored by Claude karel.kubicek.claude | provenance:privacy:age_assurance [2026/09/15 17:21] (current) – Second-round review log, the de-hyphenation-as-someone-else's-defect mistake, three stale figures corrected, and the completed neighbour backlinks. Authored by Claude karel.kubicek.claude |
|---|
| |
| ^ Queued claim ^ What derivation found ^ | ^ Queued claim ^ What derivation found ^ |
| | 11 candidates from the title-and-summary probe in ''scripts/gap_probe_roadmap.mjs'' | The title probe's 11 have **1** paper in the derived population — //Easy As Child's Play// — and **9.1% precision** by the page's inclusion rule. A full-text probe finds **38** candidates and **5** population papers, so the title probe has **20% recall** against this page's own population. | | | 11 candidates from the title-and-summary probe in ''scripts/gap_probe_roadmap.mjs'' | The 11 are really **10** — the probe has no word boundary before ''age'' (below) — and **3** of them are in the derived population: //Easy As Child's Play//, //Tales from the Porn// and the CCS 2022 kids'-apps poster. **27.3% precision** against the 11. A full-text probe finds **38** candidates and **5** population papers, so the title probe's **recall is 3 of 5 (60%)**: it misses {[west2024_picture]} and {[alomar2022_developers]}, whose titles say nothing about age. | |
| | "most are children's-privacy and COPPA-compliance work" | Confirmed, and sized: **36** papers name COPPA in ''legal[]'', against **5** in the age-assurance population, and exactly **1** paper is in both. | | | "most are children's-privacy and COPPA-compliance work" | Confirmed, and sized: **36** papers name COPPA in ''legal[]'', against **5** in the age-assurance population, and exactly **1** paper is in both. | |
| | "the one squarely on it is //Easy As Child's Play//" | Confirmed. It is the only ''OBJECT'' verdict in the audit, and it has 194 phrase matches against the runner-up's 22. | | | "the one squarely on it is //Easy As Child's Play//" | Confirmed. It is the only ''OBJECT'' verdict in the audit, and it dominates the probe: the matched-form counts in the script output below are driven by it. | |
| | "the 2019 IMC porn-ecosystem paper is adjacent and useful for the web case" | Confirmed, and it turned out to be the **only** web-platform measurement of age gates in the corpus, and the source of the page's vantage-point argument. | | | "the 2019 IMC porn-ecosystem paper is adjacent and useful for the web case" | Confirmed, and it turned out to be the **only** web-platform measurement of age gates in the corpus, and the source of the page's vantage-point argument. | |
| | "the regulatory surface is moving years ahead of the published measurement" | Confirmed with nine dated primary sources, 2025-01-16 to 2026-09-11. | | | "the regulatory surface is moving years ahead of the published measurement" | Confirmed with nine dated primary sources, 2025-01-16 to 2026-09-11. | |
| |
| One thing the queue did not anticipate and the page now leads on: **six papers hit age assurance as an obstacle to a measurement about something else**, and that is the most common way it appears in the corpus. Four of those six are from 2025–2026. | One thing the queue did not anticipate and the page now leads on: **six papers hit age assurance as an obstacle to a measurement about something else** — more than measure it, though fewer than the 20 that merely mention it. Five of those six are from 2025–2026. |
| |
| ===== The population ===== | ===== The population ===== |
| | ''SECTION'' | a section of the paper measures age assurance in the wild | 4 | | | ''SECTION'' | a section of the paper measures age assurance in the wild | 4 | |
| | ''OBSTACLE'' | age assurance is a constraint on, or a treatment in, the method | 6 | | | ''OBSTACLE'' | age assurance is a constraint on, or a treatment in, the method | 6 | |
| | ''MENTION'' | background, related work, regulation text, or a category label | 19 | | | ''MENTION'' | background, related work, regulation text, a category label, or //age estimation// as the name of an unrelated ML task | 20 | |
| | ''ARTEFACT'' | a de-columning artefact, or a cited title in the bibliography only | 8 | | | ''ARTEFACT'' | a de-columning artefact (''voltage signal''), or a cited title in the bibliography only | 7 | |
| |
| ''OBJECT'' + ''SECTION'' = the **5**-paper population the page uses. **13.2% precision** against the candidate set. | ''OBJECT'' + ''SECTION'' = the **5**-paper population the page uses. **13.2% precision** against the candidate set. |
| | 2025 | NDSS | The Kids Are All Right: YouTube Giveaway Scams | no | scam susceptibility by age group, not age assurance | | | 2025 | NDSS | The Kids Are All Right: YouTube Giveaway Scams | no | scam susceptibility by age group, not age assurance | |
| |
| **1 of 11 in the population; 4 of 11 reached by the full-text probe.** Conversely the full-text probe's 38 include **34** the title probe never saw, and 4 of the 5 population papers are among them. A title-and-summary probe is the wrong instrument for this topic because the measurement is almost always one section of a paper about something else. | **3 of 11 in the population; 4 of 11 reached by the full-text probe.** Conversely the full-text probe's 38 include **34** the title probe never saw, and **2** of the 5 population papers are among them. A title-and-summary probe is a poor instrument for this topic because the measurement is often one section of a paper whose title says nothing about age — //A Picture is Worth 500 Labels// and //Developers Say the Darnedest Things// are both invisible to it. |
| | |
| | <WRAP important> |
| | **The first draft of this page said 1 of 11, 9.1% precision and 20% recall.** That was wrong and was contradicted by the table three rows above it, whose verdict column already read ''SECTION'' for two of the three. A reviewer re-ran ''gap_probe_roadmap.mjs'' and intersected the keys rather than reading the prose. The corrected figures are 3, 27.3% and 60%, and the same wrong numbers were published for twenty minutes on [[:roadmap]] before being fixed there too. |
| | </WRAP> |
| |
| ===== Report script and output ===== | ===== Report script and output ===== |
| |
| // PDF line breaks inside a phrase otherwise silently undercount. | // PDF line breaks inside a phrase otherwise silently undercount. |
| const norm = (s) => s.replace(//g, '').replace(/-\n/g, '').replace(/\s+/g, ' '); | // Ligatures: pypdf hands back U+FB01 for 'fi', so 'verification' in the PDF |
| | // is not the 'verification' in a needle typed on a keyboard, and a true quote |
| | // scores as a fabrication. |
| | const LIGATURES = [[/\ufb00/g, 'ff'], [/\ufb01/g, 'fi'], [/\ufb02/g, 'fl'], [/\ufb03/g, 'ffi'], [/\ufb04/g, 'ffl'], |
| | // U+2011 NON-BREAKING HYPHEN is not U+002D. Ofcom's PDF writes "third\u2011party" |
| | // and a needle typed with a plain hyphen misses it, for the same reason the |
| | // ligatures do. Fold the whole dash block to a plain hyphen. |
| | [/[\u2010-\u2015]/g, '-']]; |
| | const delig = (s) => LIGATURES.reduce((t, [re, r]) => t.replace(re, r), s); |
| | const norm = (s) => delig(s).replace(//g, '').replace(/-\n/g, '').replace(/\s+/g, ' '); |
| const colsPath = (k) => { | const colsPath = (k) => { |
| const [venue, year, slug] = k.split('/'); | const [venue, year, slug] = k.split('/'); |
| 'USENIX/2023/glitchhiker-uncovering-vulnerabilities-of-image-signal-transmission-with-iemi': 'ARTEFACT', | 'USENIX/2023/glitchhiker-uncovering-vulnerabilities-of-image-signal-transmission-with-iemi': 'ARTEFACT', |
| 'NDSS/2026/peering-inside-the-black-box-long-range-and-scalable-model-architecture-snooping-via-gpu-electromagnetic-side-channel': 'ARTEFACT', | 'NDSS/2026/peering-inside-the-black-box-long-range-and-scalable-model-architecture-snooping-via-gpu-electromagnetic-side-channel': 'ARTEFACT', |
| // No paper.cols.txt in the mount, so this one was NOT reachable by the probe; | // Its five matches are all "age estimation" as the name of a downstream |
| // it is here because the title probe on the roadmap reached it. Judged from | // face-attribute ML task ("deep learning tasks such as age estimation, |
| // title and summary only — say so rather than pretend it was read. | // attribute recognition, expression analysis"), not an age check. A first |
| 'PETS/2026/gan-invert-unveiling-vulnerabilities-in-privacy-preserving-facial-transformation': 'ARTEFACT', | // draft filed this as ARTEFACT and claimed the paper had no full text in the |
| | // mount, on the strength of a mistyped slug; the file is there and the probe |
| | // did read it. MENTION is the honest bucket. |
| | 'PETS/2026/gan-invert-unveiling-vulnerabilities-in-privacy-preserving-facial-transformation': 'MENTION', |
| })); | })); |
| |
| console.log(table(['year', 'candidates', 'measures it', 'obstructed by it'], yearRows)); | console.log(table(['year', 'candidates', 'measures it', 'obstructed by it'], yearRows)); |
| console.log('* 2025-2026 are provisional venue-years — see literature:corpus.'); | console.log('* 2025-2026 are provisional venue-years — see literature:corpus.'); |
| | // A share of candidates means nothing without the corpus's own share for the |
| | // same years: the corpus grew, so "more mentions lately" is partly arithmetic. |
| | const recentCand = rows.filter((r) => r.p.year >= 2025).length; |
| | const recentCorpus = P.filter((p) => p.year >= 2025).length; |
| | console.log(`\n2025-2026: ${recentCand} of ${tightKeys.size} candidates (${pct(recentCand, tightKeys.size)})` + |
| | ` against ${recentCorpus} of ${P.length} papers in the corpus (${pct(recentCorpus, P.length)})`); |
| | console.log(` of those ${recentCand} candidates, ${rows.filter((r) => r.p.year >= 2025 && (r.v === 'OBJECT' || r.v === 'SECTION')).length} measure age assurance` + |
| | ` and ${rows.filter((r) => r.p.year >= 2025 && r.v === 'OBSTACLE').length} were obstructed by it`); |
| const venueRows = [...new Set(P.map((p) => p.venue))] | const venueRows = [...new Set(P.map((p) => p.venue))] |
| .map((v) => [v, rows.filter((r) => r.p.venue === v).length, | .map((v) => [v, rows.filter((r) => r.p.venue === v).length, |
| for (const p of coppa) coppaVenues[p.venue] = (coppaVenues[p.venue] ?? 0) + 1; | for (const p of coppa) coppaVenues[p.venue] = (coppaVenues[p.venue] ?? 0) + 1; |
| console.log('by venue: ' + Object.entries(coppaVenues).sort((a, b) => b[1] - a[1]).map(([v, c]) => `${v}:${c}`).join(' ')); | console.log('by venue: ' + Object.entries(coppaVenues).sort((a, b) => b[1] - a[1]).map(([v, c]) => `${v}:${c}`).join(' ')); |
| | const coppaMobile = coppa.filter((p) => p.platforms.includes('mobile')); |
| | const coppaWeb = coppa.filter((p) => p.platforms.includes('web')); |
| | console.log(` ... carrying the \`mobile\` platform tag ${coppaMobile.length} (${pct(coppaMobile.length, coppa.length)})`); |
| | console.log(` ... carrying the \`web\` platform tag ${coppaWeb.length} (${pct(coppaWeb.length, coppa.length)})`); |
| const overlap = coppa.filter((p) => POPULATION.some((r) => r.k === key(p))); | const overlap = coppa.filter((p) => POPULATION.some((r) => r.k === key(p))); |
| console.log(`papers in BOTH the COPPA set and the age-assurance population: ${overlap.length}` + | console.log(`papers in BOTH the COPPA set and the age-assurance population: ${overlap.length}` + |
| den: 'the 1,165 apps that implement age verification', | den: 'the 1,165 apps that implement age verification', |
| val: 'age gate 31.84%, biometric verification 8.48%', | val: 'age gate 31.84%, biometric verification 8.48%', |
| needle: 'are the most widely implemented method' }, | // The digits must be inside the needle: a needle that stops before them |
| | // substantiates the sentence and not the number the page prints. |
| | needle: 'are the most widely implemented method (31.84%)' }, |
| | { k: 'USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and', |
| | what: 'the strongest method is the least used', |
| | den: 'the same 1,165', |
| | val: 'biometric verification 8.48%', |
| | needle: 'Biometric verification is the least utilized (8.48%)' }, |
| | { k: 'USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and', |
| | what: "the detector's own error rate, on a sample drawn from its own output", |
| | den: '100 apps sampled from each side of GUARD\'s classification, hand-verified', |
| | val: '3 false positives, 2 false negatives', |
| | needle: 'We randomly sampled 100 apps categorized as having age verification mechanisms and 100 apps without any verification methods' }, |
| { k: 'USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and', | { k: 'USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and', |
| what: 'the declared minimum age does not match the store rating', | what: 'the declared minimum age does not match the store rating', |
| val: '152 apps enforce 21, 309 apps enforce 16', | val: '152 apps enforce 21, 309 apps enforce 16', |
| needle: 'Despite being rated as 17+, 152 apps actually enforce an age limit of 21 years' }, | needle: 'Despite being rated as 17+, 152 apps actually enforce an age limit of 21 years' }, |
| | // THE DENOMINATOR TRAP ON THIS PAGE. 6,843 (and the 6,346 OpenWPM crawled) is |
| | // the paper's corpus for its tracker and privacy-policy sections. The |
| | // age-verification section is a MANUAL check of at most fifty sites in four |
| | // countries, because the authors judged their own keyword detector too |
| | // false-positive-prone. A first draft of this page published 20% against |
| | // 6,843 and "six vantage points"; a reviewer caught it. |
| { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', | { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', |
| what: 'pornographic websites showing any age-verification mechanism, by vantage point', | what: 'the age-verification section is manual, and is NOT over the 6,843-site corpus', |
| den: '6,843 pornographic websites (6,346 crawled successfully by OpenWPM)', | den: 'stated by the paper itself', |
| | val: 'a subset of the top-50 most popular pornographic websites, manually, in 4 countries', |
| | needle: 'we only investigate a subset of the top-50 most popular pornographic websites manually. We perform this manual analysis in 4 countries' }, |
| | { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', |
| | what: 'pornographic websites showing any age-verification mechanism, by country', |
| | den: 'a subset of the top-50 most popular pornographic websites, hand-checked, per country', |
| val: '20% from the USA, UK and Spain; 14% from Russia', | val: '20% from the USA, UK and Spain; 14% from Russia', |
| needle: 'the same set of 20% of the pornographic websites implement and show to the end user the same age verification mechanism' }, | needle: 'the same set of 20% of the pornographic websites implement and show to the end user the same age verification mechanism' }, |
| { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', | { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', |
| what: 'the same site behaves differently depending on where the crawl appears to come from', | what: 'the same site behaves differently depending on where the visit appears to come from', |
| den: 'the same 6,843', | den: 'the same hand-checked subset', |
| val: '8% verify only in Russia; 12% verify everywhere except Russia', | val: '8% verify only in Russia; 12% verify everywhere except Russia', |
| needle: '8% of the websites that do not verify users\' age for the rest of countries do so in Russia' }, | needle: '8% of the websites that do not verify users\' age for the rest of countries do so in Russia' }, |
| | { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', |
| | what: 'why the authors abandoned the automated detector for this section', |
| | den: 'the paper\'s own account of its method', |
| | val: 'keyword matching was judged too false-positive-prone', |
| | needle: 'it is prone to introduce false positives, specially so in age-related keywords that appear often in the content of the websites' }, |
| { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', | { k: 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', |
| what: 'what the mechanism actually was', | what: 'what the mechanism actually was', |
| den: '137 apps analysed, from 150 selected out of a 470-app candidate set', | den: '137 apps analysed, from 150 selected out of a 470-app candidate set', |
| val: '13 distinct methods; year of birth in 12 apps', | val: '13 distinct methods; year of birth in 12 apps', |
| | // A vendor name from the adjacent column ("Moat") is spliced into this |
| | // sentence in paper.cols.txt, so the needle stops before the splice; the |
| | // "(12 apps)" half is checked by the next entry. |
| needle: 'Apps used a total of 13 different methods for' }, | needle: 'Apps used a total of 13 different methods for' }, |
| | { k: 'CCS/2022/poster-an-analysis-of-privacy-features-in-expert-approved-kids-apps', |
| | what: 'the most common of those 13 methods', |
| | den: 'the same 137 apps', |
| | val: 'asking for the year of birth, 12 apps', |
| | needle: 'asking for the year of' }, |
| { k: 'PETS/2022/developers-say-the-darnedest-things-privacy-compliance-processes-followed-by-dev', | { k: 'PETS/2022/developers-say-the-darnedest-things-privacy-compliance-processes-followed-by-dev', |
| what: 'child-directed app developers who say they use an age gate for parental consent', | what: 'child-directed app developers who say they use an age gate for parental consent', |
| verdict papers | verdict papers |
| -------- ------ | -------- ------ |
| MENTION 19 | MENTION 20 |
| ARTEFACT 8 | ARTEFACT 7 |
| OBSTACLE 6 | OBSTACLE 6 |
| SECTION 4 | SECTION 4 |
| MENTION 2026 IEEE-SP zkFuzz: Foundation and Framework for Effective Fuzzing of Zero-Knowledge Circuits. | MENTION 2026 IEEE-SP zkFuzz: Foundation and Framework for Effective Fuzzing of Zero-Knowledge Circuits. |
| MENTION 2026 PETS Chatbot Confessions:~Large-Scale Analysis of Private Data Disclosure in Shared AI Chatbo | MENTION 2026 PETS Chatbot Confessions:~Large-Scale Analysis of Private Data Disclosure in Shared AI Chatbo |
| | MENTION 2026 PETS GAN-Invert: Unveiling Vulnerabilities in Privacy-Preserving Facial Transformations |
| ARTEFACT 2019 WWW Demographic Inference and Representative Population Estimates from Multilingual Social M | ARTEFACT 2019 WWW Demographic Inference and Representative Population Estimates from Multilingual Social M |
| ARTEFACT 2020 PETS Illuminating the Dark or how to recover what should not be seen in FE-based classifiers | ARTEFACT 2020 PETS Illuminating the Dark or how to recover what should not be seen in FE-based classifiers |
| ARTEFACT 2025 NDSS SongBsAb: A Dual Prevention Approach against Singing Voice Conversion based Illegal Song | ARTEFACT 2025 NDSS SongBsAb: A Dual Prevention Approach against Singing Voice Conversion based Illegal Song |
| ARTEFACT 2026 NDSS Peering Inside the Black-Box: Long-Range and Scalable Model Architecture Snooping via GP | ARTEFACT 2026 NDSS Peering Inside the Black-Box: Long-Range and Scalable Model Architecture Snooping via GP |
| ARTEFACT 2026 PETS GAN-Invert: Unveiling Vulnerabilities in Privacy-Preserving Facial Transformations | |
| |
| POPULATION (measures age assurance) = 5 papers of the 38 candidates = 13.2% precision | POPULATION (measures age assurance) = 5 papers of the 38 candidates = 13.2% precision |
| 2026 * 6 0 1 | 2026 * 6 0 1 |
| * 2025-2026 are provisional venue-years — see literature:corpus. | * 2025-2026 are provisional venue-years — see literature:corpus. |
| | |
| | 2025-2026: 17 of 38 candidates (44.7%) against 1185 of 5859 papers in the corpus (20.2%) |
| | of those 17 candidates, 1 measure age assurance and 5 were obstructed by it |
| |
| venue candidates measures it | venue candidates measures it |
| by year: 2013:1 2014:1 2016:1 2017:1 2018:1 2019:3 2020:4 2021:4 2022:5 2023:3 2024:6 2025:5 2026:1 | by year: 2013:1 2014:1 2016:1 2017:1 2018:1 2019:3 2020:4 2021:4 2022:5 2023:3 2024:6 2025:5 2026:1 |
| by venue: PETS:15 USENIX:6 CCS:4 IMC:3 NDSS:3 IEEE-SP:3 WWW:2 | by venue: PETS:15 USENIX:6 CCS:4 IMC:3 NDSS:3 IEEE-SP:3 WWW:2 |
| | ... carrying the `mobile` platform tag 25 (69.4%) |
| | ... carrying the `web` platform tag 9 (25.0%) |
| papers in BOTH the COPPA set and the age-assurance population: 1 — PETS/2022/developers-say-the-darnedest-things-privacy-compliance-processes-followed-by-dev | papers in BOTH the COPPA set and the age-assurance population: 1 — PETS/2022/developers-say-the-darnedest-things-privacy-compliance-processes-followed-by-dev |
| |
| DENOMINATOR: the 1,165 apps that implement age verification | DENOMINATOR: the 1,165 apps that implement age verification |
| value: age gate 31.84%, biometric verification 8.48% | value: age gate 31.84%, biometric verification 8.48% |
| needle: "are the most widely implemented method" | needle: "are the most widely implemented method (31.84%)" |
| found in: paper.cols.txt | found in: paper.pdf (pypdf) |
| | |
| | [ ok ] USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and |
| | what: the strongest method is the least used |
| | DENOMINATOR: the same 1,165 |
| | value: biometric verification 8.48% |
| | needle: "Biometric verification is the least utilized (8.48%)" |
| | found in: paper.pdf (pypdf) |
| | |
| | [ ok ] USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and |
| | what: the detector's own error rate, on a sample drawn from its own output |
| | DENOMINATOR: 100 apps sampled from each side of GUARD's classification, hand-verified |
| | value: 3 false positives, 2 false negatives |
| | needle: "We randomly sampled 100 apps categorized as having age verification mechanisms and 100 apps without any verification methods" |
| | found in: paper.pdf (pypdf) |
| |
| [ ok ] USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and | [ ok ] USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and |
| |
| [ ok ] IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem | [ ok ] IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem |
| what: pornographic websites showing any age-verification mechanism, by vantage point | what: the age-verification section is manual, and is NOT over the 6,843-site corpus |
| DENOMINATOR: 6,843 pornographic websites (6,346 crawled successfully by OpenWPM) | DENOMINATOR: stated by the paper itself |
| | value: a subset of the top-50 most popular pornographic websites, manually, in 4 countries |
| | needle: "we only investigate a subset of the top-50 most popular pornographic websites manually. We perform this manual analysis in 4 countries" |
| | found in: paper.cols.txt |
| | |
| | [ ok ] IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem |
| | what: pornographic websites showing any age-verification mechanism, by country |
| | DENOMINATOR: a subset of the top-50 most popular pornographic websites, hand-checked, per country |
| value: 20% from the USA, UK and Spain; 14% from Russia | value: 20% from the USA, UK and Spain; 14% from Russia |
| needle: "the same set of 20% of the pornographic websites implement and show to the end user the same age verification mechanism" | needle: "the same set of 20% of the pornographic websites implement and show to the end user the same age verification mechanism" |
| |
| [ ok ] IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem | [ ok ] IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem |
| what: the same site behaves differently depending on where the crawl appears to come from | what: the same site behaves differently depending on where the visit appears to come from |
| DENOMINATOR: the same 6,843 | DENOMINATOR: the same hand-checked subset |
| value: 8% verify only in Russia; 12% verify everywhere except Russia | value: 8% verify only in Russia; 12% verify everywhere except Russia |
| needle: "8% of the websites that do not verify users' age for the rest of countries do so in Russia" | needle: "8% of the websites that do not verify users' age for the rest of countries do so in Russia" |
| | found in: paper.cols.txt |
| | |
| | [ ok ] IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem |
| | what: why the authors abandoned the automated detector for this section |
| | DENOMINATOR: the paper's own account of its method |
| | value: keyword matching was judged too false-positive-prone |
| | needle: "it is prone to introduce false positives, specially so in age-related keywords that appear often in the content of the websites" |
| found in: paper.cols.txt | found in: paper.cols.txt |
| |
| value: 13 distinct methods; year of birth in 12 apps | value: 13 distinct methods; year of birth in 12 apps |
| needle: "Apps used a total of 13 different methods for" | needle: "Apps used a total of 13 different methods for" |
| | found in: paper.cols.txt |
| | |
| | [ ok ] CCS/2022/poster-an-analysis-of-privacy-features-in-expert-approved-kids-apps |
| | what: the most common of those 13 methods |
| | DENOMINATOR: the same 137 apps |
| | value: asking for the year of birth, 12 apps |
| | needle: "asking for the year of" |
| found in: paper.cols.txt | found in: paper.cols.txt |
| |
| found in: paper.cols.txt | found in: paper.cols.txt |
| |
| located in: paper.cols.txt 19, paper.pdf (pypdf) 1 | located in: paper.cols.txt 21, paper.pdf (pypdf) 4 |
| quote check: 20/20 located verbatim | quote check: 25/25 located verbatim |
| control: a fabricated needle is correctly NOT located in USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and | control: a fabricated needle is correctly NOT located in USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and |
| |
| </file> | </file> |
| |
| The PDF fallback the quote checker calls when no .txt rendering has the needle. | The PDF fallback both guards call when no .txt rendering has the needle. |
| |
| <file python pdftext.py> | <file python pdftext.py> |
| text = text.replace("", "").replace("-\n", "") | text = text.replace("", "").replace("-\n", "") |
| sys.stdout.write(re.sub(r"\s+", " ", text)) | sys.stdout.write(re.sub(r"\s+", " ", text)) |
| | </file> |
| | |
| | The second guard: every quoted span on the page, pulled out of the page itself. |
| | |
| | <file javascript aa_quotespans.mjs> |
| | // Guard for privacy:age_assurance: every //"…"// span on the page must be |
| | // accounted for. |
| | // |
| | // node scripts/aa_quotespans.mjs aa/page.txt |
| | // |
| | // A curated quote list drifts from its page, so the list is not the input: the |
| | // page is. Every span is pulled out of the page source and must either be |
| | // located verbatim in the paper it is attributed to, or be on the NOT_A_QUOTE |
| | // list below (rhetorical phrases the page puts in quotation marks that are not |
| | // attributed to anyone) or the EXTERNAL list (checked against a fetched |
| | // primary source, cached under aa/). An unlisted, unlocated span is an error. |
| | // |
| | // Renderings are tried in order: paper.cols.txt, paper.norm.txt, paper.txt, |
| | // then pypdf. .cols repairs two-column reading order but still splices some |
| | // sentences at a column boundary, and a spliced true quote would otherwise be |
| | // scored as a fabrication. |
| | |
| | import fs from 'node:fs'; |
| | import path from 'node:path'; |
| | import { execFileSync } from 'node:child_process'; |
| | import { dataRoot } from './lib.mjs'; |
| | |
| | const ROOT = path.join(dataRoot(), 'fulltext'); |
| | // Ligatures: pypdf hands back U+FB01 for 'fi', so 'verification' in the PDF |
| | // is not the 'verification' in a needle typed on a keyboard, and a true quote |
| | // scores as a fabrication. |
| | const LIGATURES = [[/\ufb00/g, 'ff'], [/\ufb01/g, 'fi'], [/\ufb02/g, 'fl'], [/\ufb03/g, 'ffi'], [/\ufb04/g, 'ffl'], |
| | // U+2011 NON-BREAKING HYPHEN is not U+002D. Ofcom's PDF writes "third\u2011party" |
| | // and a needle typed with a plain hyphen misses it, for the same reason the |
| | // ligatures do. Fold the whole dash block to a plain hyphen. |
| | [/[\u2010-\u2015]/g, '-']]; |
| | const delig = (s) => LIGATURES.reduce((t, [re, r]) => t.replace(re, r), s); |
| | const norm = (s) => delig(s).replace(//g, '').replace(/-\n/g, '').replace(/\s+/g, ' ') |
| | .replace(/[‘’]/g, "'").replace(/[“”]/g, '"'); |
| | |
| | // Spans the page puts in quotation marks that are NOT attributed to a source: |
| | // the page's own scare quotes, and UI strings it is describing rather than |
| | // quoting. Each must be justified here, not merely listed. |
| | const NOT_A_QUOTE = new Map(Object.entries({ |
| | 'X% of sites have an age gate': "the page's own example of a meaningless quantity", |
| | 'Sites with an age gate': 'same', |
| | 'I am over 18': 'the page describing what a button says, not quoting a paper', |
| | 'Yes': 'a keyword from the 2019 detector, listed individually below as a set', |
| | 'Enter': 'same', 'Agree': 'same', 'Continue': 'same', 'Accept': 'same', |
| | })); |
| | |
| | // Spans attributed to a paper. Key = the citekey used on the page next to it. |
| | const PAPERS = { |
| | 'yao2025_easy': 'USENIX/2025/easy-as-childs-play-an-empirical-study-on-age-verification-of-adult-oriented-and', |
| | 'vallina2019_porn': 'IMC/2019/tales-from-the-porn-a-comprehensive-privacy-analysis-of-the-web-porn-ecosystem', |
| | 'west2024_picture': 'IEEE-SP/2024/a-picture-is-worth-500-labels-a-case-study-of-demographic-disparities-in-local-m', |
| | 'alomar2022_developers': 'PETS/2022/developers-say-the-darnedest-things-privacy-compliance-processes-followed-by-dev', |
| | 'ekambaranathan2022_poster': 'CCS/2022/poster-an-analysis-of-privacy-features-in-expert-approved-kids-apps', |
| | 'woodruff2026_risk': 'PETS/2026/a-risk-assessment-framework-for-digital-identification-systems', |
| | 'breuer2026_ad': 'PETS/2026/ad-personalization-and-transparency-in-mobile-ecosystems-a-comparative-analysis', |
| | 'ardi2023_prevalence': 'IMC/2023/the-prevalence-of-single-sign-on-on-the-web-towards-the-next-generation-of-web-c', |
| | 'moti2025_whispertest': 'CCS/2025/whispertest-a-voice-control-based-library-for-ios-ui-automation', |
| | 'mai2025_more': 'PETS/2025/more-and-scammier-ads-the-perils-of-youtubes-ad-privacy-settings', |
| | 'gibson2025_analyzing': 'USENIX/2025/analyzing-the-ai-nudification-application-ecosystem', |
| | 'cui2025_privacy': 'PETS/2025/understanding-privacy-norms-through-web-forms', |
| | 'zhao2023_mobile': 'WWW/2023/are-mobile-advertisements-in-compliance-with-apps-age-group', |
| | 'chen2013_this': 'WWW/2013/is-this-app-safe-for-children-a-comparison-study-of-maturity-ratings-on-android', |
| | }; |
| | |
| | // Spans quoted from an external primary source. Value = the cached file the |
| | // quote must appear in, so the check does not depend on the network. |
| | const EXTERNAL = new Map(Object.entries({ |
| | 'open banking, photo ID matching, facial age estimation, mobile network operator age checks, credit card checks, digital identity services and email-based age estimation': 'aa/ofcom.txt', |
| | "methods including self-declaration of age and online payments which don't require a person to be 18 are not highly effective": 'aa/ofcom.txt', |
| | 'host or permit content that directs or encourages users to attempt to circumvent an age assurance process': 'aa/ofcom.txt', |
| | 'started returning age signals for users in Brazil': 'aa/android_age_signals.txt', |
| | 'triggers, and survives, review under intermediate scrutiny because it only incidentally burdens the protected speech of adults': 'aa/paxton.txt', |
| | 'a 23-fold increase on the previous six months': 'aa/ofcom2026.txt', |
| | 'with a further 10 geo-blocking UK users': 'aa/ofcom2026.txt', |
| | 'Circumvention appears low, although some activity may not be captured due to reporting bias.': 'aa/ofcom2026.txt', |
| | "serious doubts about the efficacy of some age inference models, which analyse a user's activity and behaviour on a platform to estimate whether they are a child or an adult": 'aa/ofcom2026.txt', |
| | 'when creating an account, minors below 13 can enter a false birth date that makes them at least 13 years old, with no effective controls in place to check the correctness of the self-declared date of birth': 'aa/ec_meta.txt', |
| | 'risk-based, flexible, tech-neutral and future-proof': 'aa/ico_joint.txt', |
| | 'laid before the end of the year, and the changes should be implemented in Spring 2027': 'aa/gov_factsheet.txt', |
| | 'should be implemented in Spring 2027': 'aa/gov_factsheet.txt', |
| | 'serious doubts': 'aa/ofcom2026.txt', |
| | 'Almost all analysed pornography services relied exclusively on third-party vendors, with only one analysed pornography service using an in-house solution': 'aa/ofcom2026.txt', |
| | 'receive minimal outcome signals from third-party age assurance providers': 'aa/ofcom2026.txt', |
| | 'facial age estimation and photo ID matching the most commonly deployed': 'aa/ofcom2026.txt', |
| | })); |
| | |
| | const TEXTCACHE = new Map(); |
| | function renderings(key) { |
| | if (TEXTCACHE.has(key)) return TEXTCACHE.get(key); |
| | const [venue, year, slug] = key.split('/'); |
| | const dir = path.join(ROOT, year, venue, slug); |
| | const out = []; |
| | for (const r of ['paper.cols.txt', 'paper.norm.txt', 'paper.txt']) { |
| | const f = path.join(dir, r); |
| | if (fs.existsSync(f)) out.push([r, norm(fs.readFileSync(f, 'utf8'))]); |
| | } |
| | if (fs.existsSync(path.join(dir, 'paper.pdf'))) { |
| | try { |
| | out.push(['paper.pdf (pypdf)', norm(execFileSync('python3', |
| | [path.join('scripts', 'pdftext.py'), key], |
| | { encoding: 'utf8', maxBuffer: 1 << 28, stdio: ['ignore', 'pipe', 'ignore'] }))]); |
| | } catch { /* no pdf text; the other renderings still stand */ } |
| | } |
| | TEXTCACHE.set(key, out); |
| | return out; |
| | } |
| | |
| | const src = fs.readFileSync(process.argv[2] ?? 'aa/page.txt', 'utf8'); |
| | // Pull each span together with the nearest citekey that follows it on the page, |
| | // so the check is "this quote is in THAT paper", not "in some paper". |
| | const spans = []; |
| | const re = /\/\/"(.*?)"\/\//gs; |
| | let m; |
| | while ((m = re.exec(src))) { |
| | // The citekey can sit on either side of the quote — a bullet often opens |
| | // with it and a table row always does. Look at the enclosing block first |
| | // (bullets and table rows are one line each; paragraphs are one line), then |
| | // fall back to the nearest key after, then the nearest before. |
| | const lineStart = src.lastIndexOf('\n', m.index) + 1; |
| | let lineEnd = src.indexOf('\n', m.index + m[0].length); |
| | if (lineEnd < 0) lineEnd = src.length; |
| | // Order the block's citekeys by DISTANCE from the quote, not by position in |
| | // the line. "first key in the block" searches the wrong paper first whenever a |
| | // bullet cites two papers and the quote belongs to the second one, and it |
| | // would report ok against the wrong paper if that paper's text happened to |
| | // contain a colliding phrase. |
| | const near = (s, offset) => |
| | [...s.matchAll(/\{\[([a-z0-9_]+)\]\}/gi)] |
| | .map((x) => ({ key: x[1], d: Math.abs(offset + x.index - m.index) })); |
| | const cands = [ |
| | ...near(src.slice(lineStart, lineEnd), lineStart), |
| | ...near(src.slice(Math.max(0, m.index - 900), m.index + 900), Math.max(0, m.index - 900)), |
| | ].sort((a, b) => a.d - b.d); |
| | const block = [...new Set(cands.map((c) => c.key))]; |
| | spans.push({ text: norm(m[1]), cite: block[0] ?? null, block }); |
| | } |
| | |
| | // One resolver, used by the main loop AND by the control below. A control that |
| | // is a separate hand-written assertion cannot see a regression in the code it is |
| | // supposed to be guarding: mutating the matcher to always succeed left the old |
| | // control still printing "correctly not located" while the run passed with a |
| | // quote attributed to the wrong paper. |
| | function resolve(span) { |
| | const tryKeys = [...new Set([span.cite, ...span.block])].filter((k) => PAPERS[k]); |
| | for (const k of tryKeys) { |
| | const r = renderings(PAPERS[k]).find(([, t]) => t.includes(span.text)); |
| | if (r) return { hit: r, hitKey: k, tryKeys }; |
| | } |
| | return { hit: null, hitKey: null, tryKeys }; |
| | } |
| | |
| | let fail = 0; |
| | let warn = 0; |
| | const tally = new Map(); |
| | for (const s of spans) { |
| | if (NOT_A_QUOTE.has(s.text)) { |
| | tally.set('not-a-quote', (tally.get('not-a-quote') ?? 0) + 1); |
| | console.log(`[ n/a] ${s.text.slice(0, 70)} — ${NOT_A_QUOTE.get(s.text)}`); |
| | continue; |
| | } |
| | if (EXTERNAL.has(s.text)) { |
| | const f = EXTERNAL.get(s.text); |
| | const ok = fs.existsSync(f) && norm(fs.readFileSync(f, 'utf8')).includes(s.text); |
| | if (!ok) fail += 1; |
| | tally.set('external', (tally.get('external') ?? 0) + 1); |
| | console.log(`[${ok ? ' ok ' : 'FAIL'}] ${s.text.slice(0, 70)} — ${f}`); |
| | continue; |
| | } |
| | // Every citekey in the block is tried, nearest first, so a bullet citing two |
| | // papers is not a false failure — but the paper the quote was actually found |
| | // in is printed, so a misattribution stays visible. |
| | const { hit, hitKey, tryKeys } = resolve(s); |
| | if (tryKeys.length === 0) { |
| | fail += 1; |
| | console.log(`[FAIL] ${s.text.slice(0, 90)} — no mapped citekey near it and it is on no list`); |
| | continue; |
| | } |
| | if (!hit) fail += 1; |
| | tally.set('paper', (tally.get('paper') ?? 0) + 1); |
| | // A quote found in a paper OTHER than the nearest cited one is not an error, |
| | // but it is the shape in which a misattribution would hide, so it is flagged |
| | // rather than passing silently. |
| | const fellBack = hit && hitKey !== tryKeys[0]; |
| | if (fellBack) warn += 1; |
| | console.log(`[${hit ? (fellBack ? 'WARN' : ' ok ') : 'FAIL'}] ${s.text.slice(0, 70)} — ${hitKey ?? tryKeys.join('/')}, ${hit ? hit[0] : 'NOT IN ANY RENDERING'}` + |
| | (fellBack ? ` [nearest citekey was ${tryKeys[0]}; check the attribution]` : '')); |
| | } |
| | |
| | console.log(`\n${spans.length} spans: ` + |
| | [...tally.entries()].map(([k, v]) => `${v} ${k}`).join(', ') + |
| | `; ${fail} failed, ${warn} located in a paper other than the nearest citekey`); |
| | |
| | // Controls. Both are pushed through resolve(), the same function the main loop |
| | // uses, so a regression in the matcher fails them. |
| | // (a) a fabricated sentence must NOT resolve at all; |
| | // (b) a real sentence from paper A, placed in a block that cites B then A, |
| | // must resolve to A — which is what catches a resolver that returns the |
| | // first citekey's paper regardless of the text. |
| | const CONTROLS = [ |
| | { label: 'a fabricated span is not located', |
| | span: { text: norm('age verification is implemented by ninety-nine percent of all adult-only apps'), |
| | cite: 'yao2025_easy', block: ['yao2025_easy'] }, |
| | expect: null }, |
| | { label: 'a real span resolves to its own paper, not to the other key in the block', |
| | span: { text: norm('Despite being rated as 17+, 152 apps actually enforce an age limit of 21 years'), |
| | cite: 'vallina2019_porn', block: ['vallina2019_porn', 'yao2025_easy'] }, |
| | expect: 'yao2025_easy' }, |
| | ]; |
| | for (const c of CONTROLS) { |
| | const got = resolve(c.span).hitKey; |
| | if (got !== c.expect) |
| | throw new Error(`control failed (${c.label}): expected ${c.expect}, got ${got} — the quote checker is broken`); |
| | console.log(`control ok: ${c.label}`); |
| | } |
| | if (fail) throw new Error(`${fail} quoted spans on the page are unaccounted for`); |
| | </file> |
| | |
| | Its unedited output over the published page. |
| | |
| | <file aa_quotespans-output.txt> |
| | [ n/a] X% of sites have an age gate — the page's own example of a meaningless quantity |
| | [ n/a] I am over 18 — the page describing what a button says, not quoting a paper |
| | [ ok ] facial age estimation and photo ID matching the most commonly deployed — aa/ofcom2026.txt |
| | [ ok ] open banking, photo ID matching, facial age estimation, mobile network — aa/ofcom.txt |
| | [ ok ] methods including self-declaration of age and online payments which do — aa/ofcom.txt |
| | [ ok ] started returning age signals for users in Brazil — aa/android_age_signals.txt |
| | [ ok ] An age verification app generates one-time use credentials so that a u — woodruff2026_risk, paper.cols.txt |
| | [ ok ] the most widely implemented method (31.84%) — yao2025_easy, paper.pdf (pypdf) |
| | [ ok ] the least utilized (8.48%) — yao2025_easy, paper.pdf (pypdf) |
| | [WARN] Despite being rated as 17+, 152 apps actually enforce an age limit of — yao2025_easy, paper.cols.txt [nearest citekey was vallina2019_porn; check the attribution] |
| | [ ok ] specially so in age-related keywords that appear often in the content — vallina2019_porn, paper.cols.txt |
| | [ ok ] only investigate a subset of the top-50 most popular pornographic webs — vallina2019_porn, paper.cols.txt |
| | [ ok ] if our automatic crawler manages to bypass the mechanism, a child coul — vallina2019_porn, paper.cols.txt |
| | [ ok ] we did not find any instance of AgeID being deployed during our study — vallina2019_porn, paper.cols.txt |
| | [ ok ] we evaluate the effectiveness of age verification. We find that age ve — west2024_picture, paper.pdf (pypdf) |
| | [ ok ] trivially bypassed by providing a birth year — alomar2022_developers, paper.cols.txt |
| | [ ok ] did not observe any mechanisms for obtaining verifiable parental conse — alomar2022_developers, paper.cols.txt |
| | [ ok ] a total of 13 different methods — ekambaranathan2022_poster, paper.cols.txt |
| | [ ok ] Circumvention appears low, although some activity may not be captured — aa/ofcom2026.txt |
| | [ ok ] a 23-fold increase on the previous six months — aa/ofcom2026.txt |
| | [ ok ] with a further 10 geo-blocking UK users — aa/ofcom2026.txt |
| | [ ok ] serious doubts about the efficacy of some age inference models, which — aa/ofcom2026.txt |
| | [ ok ] some potentially interesting app categories, such as dating apps, requ — breuer2026_ad, paper.cols.txt |
| | [ ok ] These include age-verification prompts from adult websites — ardi2023_prevalence, paper.cols.txt |
| | [ ok ] which often must be dismissed before any other interaction is possible — ardi2023_prevalence, paper.cols.txt |
| | [WARN] seven out of 20 apps, we observed at least one ad, typically after byp — moti2025_whispertest, paper.cols.txt [nearest citekey was ardi2023_prevalence; check the attribution] |
| | [ ok ] we used a mobile VPN and selfie verification in Australia, Ireland, an — mai2025_more, paper.cols.txt |
| | [ ok ] a region in the U.S. that does not have an age verification law for ex — gibson2025_analyzing, paper.pdf (pypdf) |
| | [ ok ] Many websites use standalone age verification forms that only ask for — cui2025_privacy, paper.cols.txt |
| | [ n/a] Yes — a keyword from the 2019 detector, listed individually below as a set |
| | [ n/a] Enter — same |
| | [ n/a] Agree — same |
| | [ n/a] Continue — same |
| | [ n/a] Accept — same |
| | [ ok ] in 8 languages — vallina2019_porn, paper.cols.txt |
| | [ ok ] heavily relies on taint analysis, which might not be perfect — yao2025_easy, paper.cols.txt |
| | [ ok ] Almost all analysed pornography services relied exclusively on third-p — aa/ofcom2026.txt |
| | [ ok ] receive minimal outcome signals from third-party age assurance provide — aa/ofcom2026.txt |
| | [ ok ] the most common default languages in our list of pornographic websites — vallina2019_porn, paper.cols.txt |
| | [ n/a] Sites with an age gate — same |
| | [ ok ] with roughly half of the app promotions not in compliance with host ap — zhao2023_mobile, paper.cols.txt |
| | [ ok ] host or permit content that directs or encourages users to attempt to — aa/ofcom.txt |
| | [ ok ] while we analyze the age estimation of computer vision models, we do n — west2024_picture, paper.cols.txt |
| | [ ok ] triggers, and survives, review under intermediate scrutiny because it — aa/paxton.txt |
| | [ ok ] risk-based, flexible, tech-neutral and future-proof — aa/ico_joint.txt |
| | [ ok ] when creating an account, minors below 13 can enter a false birth date — aa/ec_meta.txt |
| | [ ok ] laid before the end of the year, and the changes should be implemented — aa/gov_factsheet.txt |
| | [ ok ] should be implemented in Spring 2027 — aa/gov_factsheet.txt |
| | [ ok ] serious doubts — aa/ofcom2026.txt |
| | |
| | 49 spans: 8 not-a-quote, 17 external, 24 paper; 0 failed, 2 located in a paper other than the nearest citekey |
| | control ok: a fabricated span is not located |
| | control ok: a real span resolves to its own paper, not to the other key in the block |
| </file> | </file> |
| |
| * **The checker must be able to fail.** A fabricated needle (''reveals that only 9,999 (99.99%) implement age verification'') is run against the same paper on every execution and the script throws if it is located. Without that control the check asserts nothing. | * **The checker must be able to fail.** A fabricated needle (''reveals that only 9,999 (99.99%) implement age verification'') is run against the same paper on every execution and the script throws if it is located. Without that control the check asserts nothing. |
| |
| **20 of 20 needles located.** 19 in ''paper.cols.txt''; **1 only in the PDF**. | **25 of 25 needles located.** 21 in ''paper.cols.txt''; **4 only in the PDF**. A second guard, ''scripts/aa_quotespans.mjs'', takes the opposite direction: it pulls **every** ''//"…"//'' span out of the page source — **42** of them, of which 21 are located in a cited paper, 13 in a cached copy of an external primary source, and 8 are on a short list of the page's own scare quotes with a written justification for each. A curated needle list drifts from its page; this one cannot, because the page is its input. |
| | |
| | Its two controls both run through the **same resolver the main loop uses**, which is the part an earlier version got wrong: a fabricated span must not resolve at all, and a real sentence placed in a block that cites two papers must resolve to //its own// paper. Mutating the matcher to always succeed fails the first; mutating the resolver to credit the first citekey regardless of the text fails the second. Citekeys are tried in order of character distance from the quote, and any span located in a paper other than the nearest citekey prints ''WARN'' rather than passing silently — two do, both correctly. |
| |
| ^ Needle ^ Paper ^ Where it was found ^ | ^ Needle ^ Paper ^ Where it was found ^ |
| | ''we evaluate the effectiveness of age verification. We find that age verification, if done using the model deployed by TikTok, is less effective for younger demographics'' | {[west2024_picture]} | **none of ''paper.cols.txt'', ''paper.norm.txt'' or ''paper.txt''** — the sentence is interleaved with the adjacent column (''"…the effectiveness of age Java, apps create links to JNI calls through the native verification…"''). Re-extracting ''paper.pdf'' with ''pypdf'' finds it verbatim. | | | ''we evaluate the effectiveness of age verification. We find that age verification, if done using the model deployed by TikTok, is less effective for younger demographics'' | {[west2024_picture]} | **none of ''paper.cols.txt'', ''paper.norm.txt'' or ''paper.txt''** — the sentence is interleaved with the adjacent column (''"…the effectiveness of age Java, apps create links to JNI calls through the native verification…"''). Re-extracting ''paper.pdf'' with ''pypdf'' finds it verbatim. | |
| |
| ''scripts/pdftext.py'' exists for exactly this and is called by the report script as a last resort. The lesson is the one already recorded for this corpus: a quote-check keyed on ''paper.cols.txt'' alone can score a faithful quote as a fabrication. | ''scripts/pdftext.py'' exists for exactly this and is called by both guards as a last resort. The lesson is the one already recorded for this corpus: a quote-check keyed on ''paper.cols.txt'' alone can score a faithful quote as a fabrication. |
| | |
| | A second cause of the same false failure turned up while tightening the needles: **pypdf returns U+FB01 for the ''fi'' ligature**, so //"Biometric verification is the least utilized (8.48%)"// in the PDF is not the //"Biometric verification…"// of a needle typed on a keyboard. Both guards now fold the five Latin ligatures before comparing. Two true quotes had failed on this. |
| |
| One needle is **truncated on purpose**: ''Apps used a total of 13 different methods for'' {[ekambaranathan2022_poster]}. The next words in the file are ''"Moat age assurance"'' — a vendor name from the adjacent column has been spliced into the sentence. The needle stops before the splice. | One needle is **truncated on purpose**: ''Apps used a total of 13 different methods for'' {[ekambaranathan2022_poster]}. The next words in the file are ''"Moat age assurance"'' — a vendor name from the adjacent column has been spliced into the sentence. The needle stops before the splice. |
| | |
| | ==== The needle that proved nothing ==== |
| | |
| | Two needles in the first draft stopped short of the digits the page prints — ''"are the most widely implemented method"'' and ''"Apps used a total of 13 different methods for"''. Both located, and neither substantiated **31.84%**, **8.48%** or **12 apps**, which is the whole reason a quote is attached to a figure. A reviewer flagged it. The first was extended to include ''(31.84%)'', a separate needle was added for ''"Biometric verification is the least utilized (8.48%)"'', and a second needle covers the ''(12 apps)'' half of the poster row, which cannot be joined to the first because a vendor name from the adjacent column is spliced into the sentence between them. |
| |
| ===== Folding ===== | ===== Folding ===== |
| | Apple ''DeclaredAgeRange'' availability | ''developer.apple.com/documentation/declaredagerange'' | The HTML page requires JavaScript and renders empty in a headless browser. Fetched the documentation JSON at ''/tutorials/data/documentation/declaredagerange.json'' instead: ''platforms'' gives iOS/iPadOS/Mac Catalyst/macOS ''introducedAt 26.0'', ''beta: false''. | | | Apple ''DeclaredAgeRange'' availability | ''developer.apple.com/documentation/declaredagerange'' | The HTML page requires JavaScript and renders empty in a headless browser. Fetched the documentation JSON at ''/tutorials/data/documentation/declaredagerange.json'' instead: ''platforms'' gives iOS/iPadOS/Mac Catalyst/macOS ''introducedAt 26.0'', ''beta: false''. | |
| | Play Age Signals dates for Brazil and Texas | ''developer.android.com/google/play/age-signals'' | Fetched with curl; the two dates are in the page's own banner. The API is marked **beta** and the page says so. | | | Play Age Signals dates for Brazil and Texas | ''developer.android.com/google/play/age-signals'' | Fetched with curl; the two dates are in the page's own banner. The API is marked **beta** and the page says so. | |
| | Digital Credentials API status | chromestatus API, features 5166035265650688 and 5099333963874304 | Queried the JSON API rather than the HTML. Presentation: ''Origin trial'', milestone 141. Issuance: ''Proposed'', milestone 155. Stable channel at the time of writing is **153.0.8010.36, released 2026-09-08**, from ''chromiumdash.appspot.com/fetch_releases''. | | | Digital Credentials API status | chromestatus API, features 5166035265650688 and 5099333963874304 | Queried the JSON API rather than the HTML, then the **per-feature** endpoint, because the list endpoint's summary ''status'' field is wrong: it reports ''{"text":"Origin trial","milestone_str":"141"}'' for presentation, while the feature's own ''stages'' array gives stage 150 (origin trial) at desktop 134 / Android 128 and **stage 160 (ship) at desktop 141 / Android 141**. Presentation therefore //shipped// at 141; the trial had already closed. The first draft of the page said "in origin trial since Chrome 141" on the strength of the summary field, and a reviewer caught it by reading the stages. Issuance is stage 160 at desktop 155, not yet reached. Stable channel **153.0.8010.36, released 2026-09-08**, from ''chromiumdash.appspot.com/fetch_releases''. | |
| | | Ofcom's statutory report on the use of age assurance — 69 million checks, 32 services, 25%→43%, top-10 and 64-of-100, "serious doubts" about age inference | Ofcom, //Report on the use of age assurance//, presented to Parliament under s157 Online Safety Act 2023, published 16 July 2026, 92 pp | ofcom.org.uk 403s this sandbox, but the report is mirrored on the UK government's asset host: ''assets.publishing.service.gov.uk/media/6a56377e2f6185941a9a6493/Report_on_the_use_of_age_assurance.pdf''. Fetched (HTTP 200, 1.59 MB), text extracted with ''pypdf'', and every quoted phrase checked verbatim by ''aa_quotespans.mjs'' against the cached extraction. **Found by a reviewer, not by me** — the first draft of the page had no 2026 UK material at all and dated Ofcom at January 2025. | |
| | | European Commission preliminary finding against Meta under DSA Article 28 | ''digital-strategy.ec.europa.eu'' press release, published 29 April 2026 | Fetched with curl and a browser user-agent; HTTP 200; the quoted sentence about the false birth date is verbatim from the article body. Also found by a reviewer. | |
| | | Ofcom/ICO joint statement on age assurance, 25 March 2026 | //Age Assurance: A Joint Statement by Ofcom and the Information Commissioner's Office//, 14 pp | **''ico.org.uk'' is not blocked even though ''ofcom.org.uk'' is** — the same joint document is published by both regulators, and the ICO's copy fetches HTTP 200 at ''ico.org.uk/media2/5ybpmabf/ofcom-ico-joint-statement.pdf'' (733,881 bytes). A reviewer found the host; the PDF was then fetched and extracted here. | |
| | | UK ban on certain social media for under-16s, Spring 2027 | //Fact sheet: New rules to protect children online//, DCMS and DSIT, updated 17 July 2026 | ''gov.uk'' is not blocked. Fetched HTTP 200 and the timeline read from the body. This row was **rejected on the first review pass and un-rejected on the second**, when a reviewer produced the government's own fact sheet in place of the law-firm posts it had originally been proposed from. | |
| | Australian minimum-age Act, day-of-effect instrument, and the 2026 enforcement amendment | Federal Register of Legislation | Queried ''api.prod.legislation.gov.au/v1/titles'' with an OData filter. Three records: C2024A00127 (Act, 10 Dec 2024), F2025N00628 (day-of-effect instrument, 29 Jul 2025), C2026A00083 (enforcement amendment, 11 Sep 2026). | | | Australian minimum-age Act, day-of-effect instrument, and the 2026 enforcement amendment | Federal Register of Legislation | Queried ''api.prod.legislation.gov.au/v1/titles'' with an OData filter. Three records: C2024A00127 (Act, 10 Dec 2024), F2025N00628 (day-of-effect instrument, 29 Jul 2025), C2026A00083 (enforcement amendment, 11 Sep 2026). | |
| |
| | Australian eSafety Commissioner's minimum-age industry page | ''esafety.gov.au'' returned ''ERR_HTTP2_PROTOCOL_ERROR''. The legislative register was used instead, which is the primary source anyway. | | | Australian eSafety Commissioner's minimum-age industry page | ''esafety.gov.au'' returned ''ERR_HTTP2_PROTOCOL_ERROR''. The legislative register was used instead, which is the primary source anyway. | |
| | News coverage of the EU app's April 2026 "technically ready" status | Replaced by the Commission's own 29 April 2026 recommendation page. | | | News coverage of the EU app's April 2026 "technically ready" status | Replaced by the Commission's own 29 April 2026 recommendation page. | |
| | | Secondary coverage of the Ofcom 2026 report (Lewis Silkin, Verifymy, Xident, biometricupdate) | These are how the report was //found//, and none of their figures were used. The report's own PDF was obtained and every figure taken from it. | |
| | | Wikipedia and law-firm posts giving 10 December 2025 as the Australian day of effect | Consistent across four secondary sources, and still not a primary one. The page continues not to assert the day. | |
| |
| ===== What could not be established ===== | ===== What could not be established ===== |
| |
| * **The calendar day of effect of the Australian minimum-age rule.** The instrument F2025N00628 is registered and named, but its text would not extract — ''legislation.gov.au'' returns HTML from every ''/text'' and ''/downloadPdf'' route tried, and the API has no document endpoint for a notifiable instrument. The page names the Act and the instrument and **does not assert the day**. | * **The calendar day of effect of the Australian minimum-age rule.** The instrument F2025N00628 is registered and named, but its text would not extract — ''legislation.gov.au'' returns HTML from every ''/text'' and ''/downloadPdf'' route tried, and the API has no document endpoint for a notifiable instrument. Four secondary sources (Wikipedia and three law firms) agree on **10 December 2025**, and a reviewer proposed adding it. Not added: the whole point of that table is that each row names a primary source read on the day. The page names the Act and the instrument and **does not assert the day**. |
| * **How many US states have an age-verification statute.** No non-advocacy tracker was found. Not claimed. | * **How many US states have an age-verification statute.** No non-advocacy tracker was found. Not claimed. |
| * **Any web-side prevalence figure after 2019.** This is the single biggest hole. //Tales from the Porn// is seven years old, predates the UK duty, the EU blueprint and every US state statute, and its 20% is a figure for click-through interstitials — a category the current UK rules explicitly exclude. There is no more recent web measurement in these seven venues. The page says this rather than interpolating. | * **Ofcom's promised "rapid assessment" of age assurance for an under-16 threshold**, reported as due to Parliament by the end of October 2026. Only law-firm sources say so; the government's own fact sheet does not carry that deadline, and Ofcom's site is unreachable. Not on the page. |
| * **Recall of either published detector.** Neither {[vallina2019_porn]} nor {[yao2025_easy]} reports detector recall against an independent hand-labelled sample. The page lists this as the first thing a new measurement should do. | * **Any web-side prevalence figure after 2019 //from the literature//.** //Tales from the Porn// is seven years old, predates the UK duty, the EU blueprint and every US state statute, its 20% is a figure for click-through interstitials — a category the current UK rules explicitly exclude — and its denominator is at most fifty hand-checked sites. There is no more recent web measurement in these seven venues. There //is// a more recent deployment measurement: Ofcom's, obtained by statutory compulsion over 32 services and not reproducible by anyone else. The page now leads on that contrast rather than on the absence. |
| * **One candidate was judged from title and summary only.** ''PETS/2026/gan-invert-unveiling-vulnerabilities-in-privacy-preserving-facial-transformation'' has no ''paper.cols.txt'' in the mount (one of four such papers in the whole corpus), so the full-text probe never reached it; it is in the verdict map as ''ARTEFACT'' on the basis of its title and abstract, which are about inverting privacy-preserving face transformations, not about age. If that judgement is wrong the population is 5, not 6, by at most one. | * **Recall of either published detector against an independently drawn sample.** {[yao2025_easy]} does report an error rate — 100 apps sampled from each side of GUARD's own output, hand-verified, 3 false positives and 2 false negatives — but a sample drawn from the classifier's own positives bounds precision, not recall over the population. {[vallina2019_porn]} reports no evaluation of its age-verification detector at all and abandoned it for manual checking. The page says this rather than the flat "neither reports recall" an earlier draft carried. |
| | * //(An earlier draft had a fourth entry here, claiming one candidate was judged from title and summary only because its full text was missing. That was false — see the mistakes list below.)// |
| |
| ===== Judgement calls ===== | ===== Judgement calls ===== |
| | Corpus | ''data/extract/run1'', 5,859 papers, 5,855 with ''paper.cols.txt'', seven venues, 2010–2026 | | | Corpus | ''data/extract/run1'', 5,859 papers, 5,855 with ''paper.cols.txt'', seven venues, 2010–2026 | |
| | Model | Claude Opus 5 for the derivation, drafting and external fetches | | | Model | Claude Opus 5 for the derivation, drafting and external fetches | |
| | Scripts committed | ''scripts/report_age_assurance.mjs'', ''scripts/pdftext.py'', ''scripts/build_provenance_age_assurance.py'' | | | Scripts committed | ''scripts/report_age_assurance.mjs'', ''scripts/aa_quotespans.mjs'', ''scripts/pdftext.py'', ''scripts/build_provenance_age_assurance.py'' | |
| | Bibliography | 11 new entries; 0 duplicate keys, 0 duplicate DOIs against the live file; ''scripts/bib_dedup_scan.py'' reports 0 definite duplicate pairs over the merged 1,036 entries | | | Bibliography | 11 new entries; 0 duplicate keys, 0 duplicate DOIs against the live file; ''scripts/bib_dedup_scan.py'' reports 0 definite duplicate pairs over the merged 1,036 entries | |
| | Authors filled by hand | four PoPETs records whose landing pages ''fetch_authors.py'' could not parse (2018-0021, 2022-0108, 2025-0094, 2026-0046), read from ''petsymposium.org'' and written to ''out/authors.json'' | | | Authors filled by hand | four PoPETs records whose landing pages ''fetch_authors.py'' could not parse (2018-0021, 2022-0108, 2025-0094, 2026-0046), read from ''petsymposium.org'' and written to ''out/authors.json'' | |
| - **The roadmap's own committed probe has the same defect** and is the reason //F-BLEAU: Fast Black-Box Leakage Estimation// is one of its 11 age-assurance candidates. | - **The roadmap's own committed probe has the same defect** and is the reason //F-BLEAU: Fast Black-Box Leakage Estimation// is one of its 11 age-assurance candidates. |
| - **The first quote check failed on a true quote.** {[west2024_picture]}'s contribution sentence is spliced across a column boundary in all three ''.txt'' renderings; ''pypdf'' has it verbatim. The checker was rewritten to try four renderings and to print which one located each needle, and a fabricated-needle control was added so the check cannot pass vacuously. | - **The first quote check failed on a true quote.** {[west2024_picture]}'s contribution sentence is spliced across a column boundary in all three ''.txt'' renderings; ''pypdf'' has it verbatim. The checker was rewritten to try four renderings and to print which one located each needle, and a fabricated-needle control was added so the check cannot pass vacuously. |
| - **The bibliography cache served a stale parse.** After appending 11 entries and saving the page, 11 of 20 references rendered as allocated-but-empty numbers while both sources looked perfect. Purging ''literature/bibliography?purge=true'' and then the page fixed it; the rendered reference count was then checked against the distinct marker count (20 = 20, 42 markers, 42 ''bibtex_citekey'' spans). | - **The bibliography cache served a stale parse.** After appending 11 entries and saving the page, 11 of 20 references rendered as allocated-but-empty numbers while both sources looked perfect. Purging ''literature/bibliography?purge=true'' and then the page fixed it. The check that matters is **distinct citekeys against rendered references**: 20 = 20. The marker-to-span ratio is not a check — the plugin emits **two** ''bibtex_citekey'' spans per marker, so the page's 55 markers render as 110 spans, and an earlier note here that read "42 markers, 42 spans" was counting one of them with a regex that only matched the opening span. |
| - **ofcom.org.uk is unreachable from this sandbox** (403 to curl, to WebFetch, and to a full headless-Chromium context). The page's two Ofcom quotes come from an Internet Archive capture, and the page says so in the footnote rather than implying a direct read. | - **ofcom.org.uk is unreachable from this sandbox** (403 to curl, to WebFetch, and to a full headless-Chromium context). The January 2025 quotes come from an Internet Archive capture; the July 2026 report was obtained from the UK government's asset host, which is not blocked. The page says so in both footnotes rather than implying a direct read. |
| | - **A de-hyphenation artefact was published as a defect in someone else's paper.** The artefacts table asserted that the repository URL printed in {[moti2024_targeted]} 404s. It does not. The paper prints ''https://github.com/targeted-and-troublesome/'', broken across a line at ''targeted-and-''; ''paper.cols.txt'', ''paper.norm.txt'' and the extraction's ''artifacts.codeUrl'' all rejoin it as ''targeted-andtroublesome'', which is what 404s. Only ''paper.txt'', which preserves the line break, shows the truth. The generic reviewer found it by reading all three renderings. **The tooling's own artefact was published as their error**, which is the second time in this run that an ENOENT or a mangled string was read as a fact about someone else's work. |
| | - **A mistyped slug produced a published falsehood about the corpus itself.** An early context pull used ''…privacy-preserving-facial-transformation**s**'' where the directory is singular. The ENOENT was read as "this paper has no full text", and that became an ''ARTEFACT'' verdict, a script comment, a provenance bullet and a limitation on the content page saying one candidate had been judged from its title alone. The file is there, 102 KB; the probe read it; its five matches are //age estimation// as the name of a face-attribute ML task. The generic reviewer found it by checking the claim against the script's **own printed list** of the four papers without full text — GAN-Invert is not on it. The verdict is now ''MENTION'' (20/7) and the limitation is deleted. Nothing about the population changed, but four separate places had repeated the same unchecked inference. |
| | - **The page's most important figures were the ones a first draft got wrong, and neither was caught by a guard.** The 2019 age-verification percentages were published against the wrong denominator — the paper's 6,843-site corpus and "six vantage points", where the section itself is a hand check of at most fifty sites in four countries — because the quote-check located the sentence and nothing checked what the sentence was a share //of//. And the whole 2026 UK deployment picture was missing, because the page was written from the corpus and the corpus stops at seven academic venues. Both came from reviewers. |
| |
| ==== Review log ==== | ==== Review log ==== |
| |
| Four reviewers, each told explicitly that the author's context may not be exhaustive, and each handed the page text, the report script, its output and this provenance draft. | Four reviewers, each told explicitly that the author's context may not be exhaustive, and each handed the page text, the report script, its output and this provenance draft. The three focused passes ran in parallel on the pre-review draft; their findings were applied in one pass, and all three were then re-run against the corrected pages. The generic pass ran last — and its first finding was a process one: **the page was republished twice while it was reading**, so it reviewed a moving target and had to mark which of its findings the in-flight edits already fixed. That is a real cost and it is recorded rather than tidied away; the next page should freeze the source while the generic pass runs. |
| | |
| | ^ # ^ Pass ^ Finding ^ Disposition ^ |
| | | 1 | figures vs script (''sonnet'') | The provenance page said the title probe had **1 of 11** in the population, 9.1% precision and 20% recall, contradicted by its own table three rows above, which scored two more as ''SECTION''. Real figures: **3 of 11**, 27.3% precision, 3 of 5 (60%) recall. Re-derived by re-running ''gap_probe_roadmap.mjs'' and intersecting extraction keys. | **Accepted.** Fixed here and on [[:roadmap]], where the wrong numbers had already been published. | |
| | | 1 | figures vs script | Re-ran the report script: output reproduces the committed file byte-for-byte. Every page figure matches. Mutation-tested both published guards — narrowing ''LOOSE'' made the containment assertion throw; stubbing ''locate()'' made the fabricated-needle control throw. Neither passes vacuously. | **Accepted as a pass.** No change. | |
| | | 1 | figures vs script | Two needles stopped before the digits the page prints, so the quote check did not substantiate 31.84%, 8.48% or 12 apps. | **Accepted.** Needles extended; two more added. | |
| | | 2 | citations and quotes (''sonnet'') | **The 20%/14%/8%/12% figures are not over 6,843 sites from six vantage points.** §7.2 is a manual check of //"a subset of the top-50 most popular pornographic websites"// in **4** countries, because the authors judged their own keyword detector too false-positive-prone. | **Accepted — the most serious finding of the run.** Corrected in the population table, both figure bullets, the denominator section and the report script, and the paper's own reason for going manual is now on the page, because it is a methodological point in its own right. | |
| | | 2 | citations and quotes | The page called {[west2024_picture]} an age-verification accuracy study; age is one of its **two** case studies and the paper is framed as an on-device-ML fairness audit. | **Accepted.** Qualified in the table, the figure bullet and the reading list. | |
| | | 2 | citations and quotes | ''zhao2023_mobile'' BibTeX has //Grundy, John C.//; the paper's title page says //John Grundy//. | **Accepted.** Corrected in [[:literature:bibliography]]. | |
| | | 2 | citations and quotes | All 20 citekeys resolve; no duplicate keys, DOIs or titles; 10 of 11 new entries verified against the paper's own title page including author order; every regulatory footnote checked against its primary source. | **Accepted as a pass.** | |
| | | 3 | external currency (''sonnet'') | **Ofcom published a statutory report on 16 July 2026** — 69 million age checks over 32 services, 25%→43% child exposure, all of the UK top-10 and 64 of the top-100 pornography services — and the page dated UK regulation at January 2025. | **Accepted, and it changed the page's thesis.** The report's own PDF was fetched from the government asset host and every figure taken from it; the page now has a section on it, and the contrast between a regulator with information-notice powers and a literature with fifty hand-checked sites is the argument the page had been missing. | |
| | | 3 | external currency | **The Commission preliminarily found Meta in breach of the DSA on 29 April 2026** over self-declared birth dates on Instagram and Facebook. | **Accepted.** Primary press release fetched; a row added to the regulatory table. | |
| | | 3 | external currency | **Chrome's Digital Credentials API presentation support //shipped// in 141**; it did not enter origin trial then. The chromestatus list endpoint's summary ''status'' field says otherwise, and disagrees with the same feature's ''stages'' array. | **Accepted.** Verified against the per-feature endpoint. Footnote rewritten and the gotcha recorded above. | |
| | | 3 | external currency | The Australian day of effect is 10 December 2025 per Wikipedia and three law firms. | **Rejected.** Consistent secondary sourcing is not a primary source, and every other row in that table names one. The page still does not assert the day. | |
| | | 3 | external currency | An Ofcom/ICO joint statement (25 Mar 2026) and a UK plan for an under-16 social-media threshold (June 2026) are also missing. | **Rejected on the first pass, and the rejection was wrong** — see the re-review rows below. Both were surfaced only through law-firm posts and neither could be read from a primary source //at that point//; the reviewer then found primary sources for both and they were added. | |
| | | 3 | external currency | Apple, Google Play, the EU blueprint pages, the SCOTUS opinion, the Australian register ids and all footnote URLs re-fetched and confirmed; no dead links. | **Accepted as a pass.** | |
| | |
| | All three focused passes were then **re-run against the corrected pages**, because their findings had been acted on: |
| | |
| | ^ # ^ Pass ^ Finding on re-review ^ Disposition ^ |
| | | 1 | figures vs script, re-run | **''aa_quotespans.mjs''' fabricated-span control did not exercise the code it guarded.** It was a separate hand-written assertion against one hardcoded paper/string pair, so mutating the matcher to always succeed left it still printing "correctly not located" while a quote was silently reattributed to the wrong paper. | **Accepted.** The resolver is now one function used by both the main loop and the controls, and there are two controls: a fabricated span must not resolve, and a real span in a two-citekey block must resolve to **its own** paper. Both were mutation-tested: an always-matching matcher fails the first, a resolver that credits the first key fails the second. | |
| | | 1 | figures vs script, re-run | The page said §2.2 of {[yao2025_easy]} is titled //Age Verification Methods//; that is Table 2's caption. The section is //Age Verification in Adult-Oriented Apps//. | **Accepted.** Corrected, and the reference now points at the table explicitly. | |
| | | 1 | figures vs script, re-run | Script output reproduces the committed file byte-for-byte; the 3-of-11 / 27.3% / 60% figures, the corrected 2019 denominator, the Ofcom figures and the new 25-of-36 COPPA platform split all verified independently. | **Accepted as a pass.** | |
| | | 2 | citations and quotes, re-run | **''aa_quotespans.mjs'' picked the //first// citekey in a block, not the nearest one.** On the one bullet that cites two papers it tried {[chen2013_this]} first and only fell through to the right paper by luck; a colliding phrase in the wrong paper would have passed as ''ok''. | **Accepted.** Citekeys are now ordered by character distance from the quote, and a quote located in any paper other than the nearest one prints ''WARN'' with both keys. Two spans currently warn, both correctly. | |
| | | 2 | citations and quotes, re-run | Every Ofcom 2026 figure and quote, the Meta press-release quote, the rewritten Chrome footnote, the GUARD FP/FN entry and the corrected 2019 denominator all verified against their primary sources. The claim that {[vallina2019_porn]} reports no evaluation of its detector was independently confirmed. | **Accepted as a pass.** | |
| | | 3 | external currency, re-run | **Two of the first pass's rejections were wrong, and the reviewer proved it by finding the primary sources.** ''ico.org.uk'' publishes the same Ofcom/ICO joint statement that ''ofcom.org.uk'' blocks, and the UK under-16 plan has a government fact sheet on ''gov.uk''. | **Accepted, both rejections reversed.** Two rows added to the regulatory table, and the under-16 timeline changed the page's closing argument: every figure on the page is about pornography and adult-only apps, and from Spring 2027 the UK duty is about ordinary social media. | |
| | | 3 | external currency, re-run | The Australian day of effect is still unreachable: ''/text'', ''/downloadPdf'' and ''/contentDocuments'' all 404, the public page is a client-rendered SPA with an empty body, and ''esafety.gov.au'' and ''infrastructure.gov.au'' fail at the network layer. | **Rejection stands.** Still not asserted. | |
| | | 3 | external currency, re-run | Nothing supersedes the Ofcom report or the Meta preliminary finding; the Chrome stage reading and all page figures re-confirmed against the primary sources. | **Accepted as a pass.** | |
| | |
| | And the generic pass, which read the page after all of the above: |
| | |
| | ^ Finding ^ Disposition ^ |
| | | **The "one candidate judged from title only" limitation is false** and the script's own output contradicts it. | **Accepted**, and it is the worst defect of the run — see the mistakes list above. | |
| | | The probe is described on the page as **five phrases**; it is fourteen forms, and six of the 38 candidates enter only through the extra ones. A reader reproducing "the five phrases" gets about 31. | **Accepted.** The page now says fourteen and points at the pattern here. | |
| | | **The 2019 detector is not English-only** — it searches its five keywords //"in 8 languages"//, chosen as the most common default languages in that corpus. | **Accepted.** A flat factual error, corrected in the detection table and the language bullet. The paper's claim to //"manually validate the accuracy of our method in Section 7.2"// is also now stated precisely: §7.2 replaces the detector with a manual check and reports no error rate. | |
| | | The page said the CCS poster is //the only mechanism taxonomy derived from observation// while also crediting GUARD's six categories with six of the eight rows in its own mechanism table. | **Accepted.** Scoped to children's apps. | |
| | | //"The first three are the ones that exist at scale"// is contradicted by the page's own Ofcom section (facial age estimation and photo ID matching most commonly deployed). | **Accepted.** Both halves now name their population and the sentence no longer asserts a general ranking. | |
| | | Six unsupported negatives, including //"the field's most-cited web figure"//, //"the corpus contains no general guidance"// on adult-content crawls (there is: {[vallina2019_porn]} §8), and //"the ordering below is the one the field and the regulators both use"//. | **Accepted, all six.** Each is now bounded to what was actually checked. | |
| | | //"the mentions are growing and the measurements are not"// generalises past //n// = 1–2 per year, and the 17-of-38 has no base rate beside it. | **Accepted.** 44.7% of candidates against 20.2% of the corpus is now printed, and the second half is labelled a small-//n// observation. | |
| | | **The page names no artefacts.** GUARD's code, the 500-labels repository, the 2,004-site child-directed list and DiffAudit's release all exist; a methods page that says "do not rebuild one" should list them. | **Accepted, and it turned up a live defect in a paper.** A new section gives all four with their HTTP status, and checking them found that the URL printed in {[moti2024_targeted]} — ''github.com/targeted-andtroublesome/'' — **404s**: the org is ''targeted-and-troublesome'' and the list is ''urls/kids_websites.csv'' in ''targeted-and-troublesome-crawler'', 2,004 distinct URLs, which matches the paper exactly. The same section records that {[vallina2019_porn]}'s population construction cannot be repeated because Alexa's Adult category was retired in 2022. | |
| | | //"the rule that separates a gate from a cookie banner, a paywall and a login wall"// is demanded and never sketched; and //"all four are dismissable overlays"// is wrong about paywalls and login walls. | **Accepted.** The features to build such a rule from are now listed, with the honest note that nobody has published one. | |
| | | The page claims //"a verbatim-quote check of every per-paper number"//; eleven numbers sit outside every needle and a mutation of 14% to 24% passed both guards. The reviewer hand-verified all eleven and they are correct. | **Accepted.** The methodology section now names the eleven figures that are hand-checked rather than guarded. | |
| | | Missing practical point in the ethics section: verification vendors expose sandbox modes. | **Rejected for now.** No primary source was reached for which vendors do, and the page does not name vendors at all; asserting it would be exactly the vendor-marketing claim the source policy on this page rejects. Recorded here so the next run can close it. | |
| | | No neighbouring page links back to this one. | **Accepted and done**, as a separate edit after the page was frozen for the re-review: one line each in [[:design:blocking_and_geodifference]], [[:design:crawling_location]], [[:privacy:consent]] and [[:practices:ethics]]. No figures on those pages were touched. | |
| | | Several phrases repeat (//at most fifty// five times, //one of its two case studies// three times). | **Partly accepted.** Two instances trimmed; the rest carry the caveat in places a reader may arrive at directly. | |
| | | Nits: {[west2024_picture]} uses Frida rather than the camera; the 5,855-apps / 5,855-papers coincidence; an unsupported causal claim about the six obstacle papers; the unused //"we did not find any instance of AgeID being deployed"//. | **All four accepted.** | |
| | |
| | Both passes were re-run once more against the frozen pages. What they found the second time: |
| | |
| | ^ Finding ^ Disposition ^ |
| | | **The artefacts section published a defect in someone else's paper that does not exist.** {[moti2024_targeted]} prints its repository URL correctly; ''paper.cols.txt'', ''paper.norm.txt'' and the extraction all de-hyphenate it across a line break into a URL that 404s. | **Accepted — the worst finding of the second round**, and recorded in the mistakes list above. The row now states the correct URL, its HTTP 200, and the artefact that caused the confusion. | |
| | | The Zenodo record for GUARD is titled //Drexel-SePAL/AgeScope: v1.0.1-1//, so searching for "GUARD" will not find it. | **Accepted.** The row now says so. | |
| | | Three unbounded negatives introduced by the new artefacts section — //"there is no dataset"//, //"nobody has published one"//, and an ordering sentence attributing a sort to Ofcom that Ofcom does not perform. | **Accepted, all three.** | |
| | | The closing argument said a crawl against adult sites would measure //the smaller half//, which nothing on the page sizes, and asserted the Spring 2027 date the fact sheet hedges. | **Accepted.** Both softened to what the sources support. | |
| | | The intro's verdict clause attached //"the name of an unrelated ML task"// to the 7 ''ARTEFACT'' papers, where it describes the 20th ''MENTION''. | **Accepted.** Reworded. | |
| | | The methodology section's list of eleven hand-checked figures is incomplete — the artefact file sizes, Ofcom's 25%-to-43% and 64-of-100, the survey base of 50 and several paraphrased denominators are in the same position. | **Accepted, and the enumeration was abandoned.** It was incomplete twice; the page now states the class instead, which is both shorter and true. | |
| | | Three stale numbers in this provenance page: the "most common way it appears in the corpus" phrasing, an unsourced "194 phrase matches", and a marker-to-span identity that was never consistent. | **Accepted, all three.** The last one is worth keeping in mind: the bibtex plugin emits **two** spans per marker, so marker count and span count are not a check. | |
| | | Scripts reproduce their committed outputs byte-for-byte; the verdict split, 44.7%/20.2%, the four artefact URLs, 55,481 bytes, 2,004 distinct URLs and the 2025-02-04 push date all independently verified. | **Accepted as a pass.** | |
| | | The two ''WARN'' spans are correctly attributed; the log did not name them. | **Accepted.** They are //"Despite being rated as 17+…"// (nearest key ''vallina2019_porn'', found in ''yao2025_easy'') and //"seven out of 20 apps…"// (nearest key ''ardi2023_prevalence'', found in ''moti2025_whispertest''). In both, the previous bullet's trailing citekey is closer in characters than the bullet's own key at its end. A third ''WARN'' would be a new thing to look at. | |
| |
| |