provenance:design:longitudinal
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| provenance:design:longitudinal [2026/09/03 20:43] – Add section 17: the Tranco provider-swap date amendment (three saves, W9ZN9/25299 read from the API with 30 Jul and 2 Aug as controls), whose-date-is-whose table, and structure re-verification. Authored by Claude karel.kubicek.claude | provenance:design:longitudinal [2026/09/22 04:33] (current) – Figures-vs-script finding accepted: the 18.5 heading still named a three-generations-stale count (43); renamed to carry no count. No inbound anchor links across 188 pages. Authored by Claude karel.kubicek.claude | ||
|---|---|---|---|
| Line 59: | Line 59: | ||
| </ | </ | ||
| - | Both default to ''/ | + | Both default to ''/ |
| ===== 5. The fold, and its residue in full ===== | ===== 5. The fold, and its residue in full ===== | ||
| Line 211: | Line 211: | ||
| ===== 9. What could not be established ===== | ===== 9. What could not be established ===== | ||
| - | * **Whether a paper held a pin // | + | * **Whether a paper held a pin // |
| * **Whether the 2025–2026 slice changes the trend.** The repeat rate is flat at roughly one crawling paper in five across four buckets, and the last bucket is provisional by construction. Nothing in the 2025–2026 material suggests a change, but those venue-years are the thinnest in the corpus. | * **Whether the 2025–2026 slice changes the trend.** The repeat rate is flat at roughly one crawling paper in five across four buckets, and the last bucket is provisional by construction. Nothing in the 2025–2026 material suggests a change, but those venue-years are the thinnest in the corpus. | ||
| * **Whether pinning actually changes a published answer.** The whole case rests on single-wave variation studies ({[demir2022_reproducibility]}, | * **Whether pinning actually changes a published answer.** The whole case rests on single-wave variation studies ({[demir2022_reproducibility]}, | ||
| Line 1473: | Line 1473: | ||
| ^ Item ^ Value ^ | ^ Item ^ Value ^ | ||
| | Content page | [[design: | | Content page | [[design: | ||
| - | | Saves | rev **1788465991** ('' | + | | Saves | rev **1788465991** ('' |
| | Corpus queried | none — this is a vendor date | | | Corpus queried | none — this is a vendor date | | ||
| | External source | Tranco' | | External source | Tranco' | ||
| Line 1507: | Line 1507: | ||
| **Not changed, on purpose.** The four '' | **Not changed, on purpose.** The four '' | ||
| + | |||
| + | ===== Amendment, 2026-09-04: citekey consolidation ===== | ||
| + | |||
| + | * '' | ||
| + | * 1 citation marker on [[: | ||
| + | |||
| + | ===== 18. The four-pin hand-coding, | ||
| + | |||
| + | §9 above named one follow-up as the most valuable: //" | ||
| + | |||
| + | ==== 18.1 What was built, and what each piece is for ==== | ||
| + | |||
| + | ^ Artefact ^ What it does ^ Committed as ^ | ||
| + | | Population | Re-derives the 250 from the extraction with the predicate copied verbatim out of '' | ||
| + | | Reading packet | Per-paper keyword-window excerpt of the '' | ||
| + | | Codebook | '' | ||
| + | | Coding | 250 JSON files, one per paper | '' | ||
| + | | Reliability sample | Deterministic draw of 30 from the 250 | '' | ||
| + | | Blind second coding | The same 30, re-coded from the **full text** by a coder who never saw the first coding | '' | ||
| + | | Aggregation | Every published figure, with its denominator, | ||
| + | | Quote check | Every evidence quote located in the source, in four escalating modes | '' | ||
| + | | Agreement | Per-pin exact and collapsed agreement, Cohen' | ||
| + | | Citekey map | Slug → the citekey **already on the content page**, so the exemplar table is emitted, not retyped | '' | ||
| + | | Quote adjudication | Hand verdicts on every quote no matching mode could locate | '' | ||
| + | |||
| + | ==== 18.2 The population, and that it reproduces ==== | ||
| + | |||
| + | <code bash> | ||
| + | cd / | ||
| + | node scripts/ | ||
| + | node scripts/ | ||
| + | node scripts/ | ||
| + | node scripts/ | ||
| + | node scripts/ | ||
| + | node scripts/ | ||
| + | node scripts/ | ||
| + | </ | ||
| + | |||
| + | Both derived files were re-run on 2026-09-07 and diffed against the committed copies: '' | ||
| + | |||
| + | The predicate is '' | ||
| + | |||
| + | '' | ||
| + | |||
| + | < | ||
| + | if (coded.length !== pop.length) { | ||
| + | console.error(`\nFATAL: | ||
| + | </ | ||
| + | |||
| + | It has the same guard on the citekey map: if the map and the all-four-stated set disagree by one slug, the exemplar table would silently lose a row, so it exits instead. | ||
| + | |||
| + | ==== 18.3 The reading packet, and what it costs ==== | ||
| + | |||
| + | 250 papers is 20.9 MB of '' | ||
| + | |||
| + | The packet is a recall risk, and it was measured rather than assumed — that is what the blind full-text second coder in §18.4 is for. The answer, in short: the packet does not lose " | ||
| + | |||
| + | ==== 18.4 The blind second coder, and the agreement ==== | ||
| + | |||
| + | 30 of the 250 were drawn with a seeded shuffle ('' | ||
| + | |||
| + | ^ Measure ^ Exact ^ //κ// ^ Collapsed to fixed / not fixed ^ //κ// ^ | ||
| + | | Population list | 20/30 (66.7%) | 0.49 | 26/30 (86.7%) | 0.59 | | ||
| + | | Browser | 21/30 (70.0%) | 0.57 | 22/30 (73.3%) | 0.48 | | ||
| + | | Vantage point | 21/30 (70.0%) | 0.56 | 25/30 (83.3%) | 0.67 | | ||
| + | | Statefulness | 24/30 (80.0%) | 0.64 | 25/30 (83.3%) | 0.64 | | ||
| + | | **All 120 pin codings** | **86/120 (71.7%)** | **0.60** | **98/120 (81.7%)** | **0.64** | | ||
| + | | '' | ||
| + | | "all four fixed" | 27/30 (90.0%) | 0.52 | A: 3/30, B: 4/30 | — | | ||
| + | |||
| + | An earlier draft of this section said //κ// on "all four fixed" is low only because the class is rare, //"not because the coders disagree about which papers they are"// | ||
| + | |||
| + | **Direction of disagreement.** This is the part that changes how the content page is written: | ||
| + | |||
| + | ^ Pin ^ Both call it fixed ^ Only the excerpt coder ^ Only the full-text coder ^ Neither ^ | ||
| + | | Population list | 22 | 4 | 0 | 4 | | ||
| + | | Browser | 9 | **7** | 1 | 13 | | ||
| + | | Vantage point | 11 | 1 | **4** | 14 | | ||
| + | | Statefulness | 8 | 4 | 1 | 17 | | ||
| + | |||
| + | The excerpt coder calls the **browser** fixed seven times where the full-text coder finds it never stated, and only once the other way; statefulness leans the same way, four to one. The **vantage point** goes the opposite way — the full-text coder finds four vantage statements the packet' | ||
| + | |||
| + | The most likely cause of the browser lean is a design mistake that is worth recording: **the packet header prints the structured extraction' | ||
| + | |||
| + | Consequence for the content page: **the browser row of the estimate column is more likely too high than too low**, and the page says so. | ||
| + | |||
| + | Every disagreement is printed paper by paper in the unedited output in §18.12. | ||
| + | |||
| + | ==== 18.5 The quote check, and what " | ||
| + | |||
| + | Every non-empty '' | ||
| + | |||
| + | The naive substring test is wrong here, and getting it wrong once already produced a scare. Run against '' | ||
| + | |||
| + | * coder' | ||
| + | * what '' | ||
| + | |||
| + | The two-column repair still splices a quarter of pages, and a sentence crossing a column boundary comes back with the neighbouring column injected into the middle of it. **A coder who de-splices that correctly writes a quote that is a substring of nothing.** Failing it would call a correct reading a hallucination. So the check now searches '' | ||
| + | |||
| + | ^ Mode ^ Codings ^ Share of 536 ^ | ||
| + | | Exact substring | 489 | 91.2% | | ||
| + | | Whitespace-collapsed | 0 | 0.0% | | ||
| + | | Loose (de-hyphenated, | ||
| + | | De-spliced (6-word shingles, ≥80%) | 3 | 0.6% | | ||
| + | | Rescued from the '' | ||
| + | | **Located by no mode, in any of the five texts** | **7** | **1.3%** | | ||
| + | |||
| + | //The counts above are after the one correction described below **and** after the '' | ||
| + | |||
| + | **All 43 were then read by hand** — three sub-agents, each given the paper' | ||
| + | |||
| + | ^ Verdict ^ Codings ^ | ||
| + | | Faithful de-splice of a real sentence | 42 | | ||
| + | | Quote is real but from the wrong part of the paper | **1** | | ||
| + | | Unsupported paraphrase / fabrication | **0** | | ||
| + | |||
| + | The one bad coding, and what was done about it: | ||
| + | |||
| + | * **{[singanamalla2022_respect]}, | ||
| + | * **Corrected to '' | ||
| + | * **What moved on the content page:** the vantage row over the 124 went from 25/44/10 to **26/45/9** (words / estimate / moved), and the all-pins fixed-versus-not agreement from 80.8% to 81.7%. The all-four figure, the 124, and the 6-of-23 survivor count did not move. | ||
| + | |||
| + | The verdict files are '' | ||
| + | |||
| + | ==== 18.6 What changed on the content page ==== | ||
| + | |||
| + | ^ Where ^ Before ^ After ^ | ||
| + | | Lead paragraph | "Of those 250, **23 (9.2%)** state all four" | 124 are really repeat crawls; **15 of 124 (12.1%)** plausibly held all four fixed, **2** say so in words | | ||
| + | | '' | ||
| + | | — | — | **new section** '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | Methodology bullets | one bullet: treat the pin figures as ceilings | three bullets: what the two columns each measure, the coder-agreement caveat, and that every other share-of-250 figure on the page inherits the false-positive rate and has **not** been recomputed | | ||
| + | |||
| + | **Also corrected in the same edit, outside the four-pin work.** Four external claims the currency reviewer found stale — Quantcast' | ||
| + | |||
| + | **Not changed, on purpose.** The cadence, span, probe and Tranco-ID figures are still shares of 250 rather than of 124. Recomputing them is a different piece of work with its own review obligation; folding it into this edit would have meant republishing a dozen tables under a population change that nothing in this hand-coding validates for them. The content page says plainly that they were not recomputed, which is the honest state, and it is filed below as the follow-up. | ||
| + | |||
| + | ==== 18.7 Judgement calls ==== | ||
| + | |||
| + | * **Two populations are published, and the 124 is the one the prose leans on.** Publishing only the 250 would keep the page consistent with itself and answer the wrong question; publishing only the 124 would break every cross-reference to [[statistics: | ||
| + | * **'' | ||
| + | * **A list re-downloaded each wave is '' | ||
| + | * **The ceiling is not a ceiling for the list pin.** The hand-coded estimate for the population list (95 of 124) is //above// the structured figure (86). They measure different things: the extraction looks for '' | ||
| + | * **The 23-paper exemplar table was kept, not deleted.** It is the clearest possible demonstration of what " | ||
| + | * **'' | ||
| + | |||
| + | ==== 18.8 What still cannot be established ==== | ||
| + | |||
| + | * **Whether a pin was // | ||
| + | * **Whether the reliability figures hold at n=250.** They were measured on 30. //κ// = 0.60 on exact values is moderate; the confidence interval at n=30 is wide and is not published because it would imply a precision the design does not have. | ||
| + | * **The other share-of-250 figures on the content page.** Cadence, span bands, the probe table and the Tranco-ID rate are all still computed over the 250. The 124 exists now, so they //could// be recomputed; they have not been, and the page says so. | ||
| + | * **Whether the pin figures differ by venue.** n per venue-year is small and the 124 splits into groups of two and three. Not attempted. | ||
| + | * **Whether a paper that pinned got a different answer than one that did not.** Unchanged from §9: this remains the open question the whole page rests on and no study in the corpus answers it. | ||
| + | |||
| + | ==== 18.9 Run log ==== | ||
| + | |||
| + | ^ Item ^ Value ^ | ||
| + | | Date | 2026-09-07 | | ||
| + | | Corpus | '' | ||
| + | | Coder A | Claude Sonnet, 63 batches of ≤5 papers, excerpt packet plus full-text access, one paper coded once | | ||
| + | | Coder B | Claude Sonnet, 10 batches of 3, full text only, blind | | ||
| + | | Quote adjudication | Claude Sonnet, 3 batches, over the 43 quotes no matching mode located; 1 correction applied | | ||
| + | | Review | Three focused passes (Claude Sonnet: figures-vs-script, | ||
| + | | Orchestration, | ||
| + | | Interruption | The run hit an API quota limit with 185 of 250 coded and resumed the same day. Same model, same codebook, same prompt; the resumed batches are '' | ||
| + | | Bibliography | **No entries added and none changed.** The exemplar table reuses the 23 citekeys already on the content page, via '' | ||
| + | | Guards run before saving | '' | ||
| + | | Discussion block | Still none on this page, following the convention in §16. | | ||
| + | |||
| + | ==== 18.10 Review passes, 2026-09-07 ==== | ||
| + | |||
| + | Four reviewers, each told explicitly that the author' | ||
| + | |||
| + | === Pass A — figures against the script (Claude Sonnet) === | ||
| + | |||
| + | Re-ran all six scripts and confirmed every committed '' | ||
| + | |||
| + | - **Stale table cell.** The 23-row exemplar table' | ||
| + | - **Wrong hand-written count.** The prose said //" | ||
| + | - **The structural guard in '' | ||
| + | - Mutation-tested and **confirmed sound**: the partial-population refusal in '' | ||
| + | - Hand-computed Cohen' | ||
| + | - Checked the figures **outside** the edited window against '' | ||
| + | |||
| + | === Pass B — citations and quotes (Claude Sonnet) === | ||
| + | |||
| + | - All 32 keys on the content page and all 8 on this page resolve against the **live** '' | ||
| + | - Verified all 23 rows of '' | ||
| + | - **Independently found the same stale vantage cell as Pass A**, by diffing the published table against a fresh '' | ||
| + | - Spot-checked **24 evidence quotes** (10 '' | ||
| + | - Verified the "12 further papers outside this 23" arithmetic (18 all-four minus the 6 in the table) and every external figure quoted from a cited paper. | ||
| + | |||
| + | === Pass C — external currency (Claude Sonnet) === | ||
| + | |||
| + | This pass reviewed the **whole** of both pages, not the diff, and found four stale external claims — all of them in text this edit did not touch. All four accepted: | ||
| + | |||
| + | - **Quantcast' | ||
| + | - **OpenWPM had moved on.** The page cited v0.36.0 / Firefox 154.0 as its worked example; **v0.37.0 shipped on 6 September 2026** pinning **Firefox 155.0.1**. Confirmed via '' | ||
| + | - **The Playwright footnote quoted '' | ||
| + | - **ICLab is dormant.** The page called Censored Planet and ICLab //" | ||
| + | - Confirmed accurate and unchanged: the HTTP Archive changelog 404 (still 12 entries, last 2017-06-01, on '' | ||
| + | |||
| + | === Pass D — generic (Claude Fable) === | ||
| + | |||
| + | Run last, on the published pages, after A–C were applied. Findings and their disposition are in §18.11. | ||
| + | |||
| + | ==== 18.11 Generic review pass ==== | ||
| + | |||
| + | **Commissioned, | ||
| + | |||
| + | This is worth stating rather than quietly leaving a gap, because the generic pass is the one that catches overstatement, | ||
| + | |||
| + | * The **estimate** column rests on '' | ||
| + | * The reliability figures are **//n// = 30**, and the //κ// for "all four fixed" (0.52) is computed on three and four positives respectively. | ||
| + | * **Every coder in this exercise was a language model**, reading text extracted from PDFs by a pipeline, coding a schema produced by another language model. The blind second coder and the quote adjudication bound the error; they do not remove it, and no human read any of the 250 papers. | ||
| + | |||
| + | Re-running this pass was filed as follow-up work — and then it returned. | ||
| + | |||
| + | **Findings, and what was done.** The pass returned after the first save of this section. Applied to the content page: the disclosure that **both coders and the adjudicator were language-model agents** reading LLM-extracted text, which the content page had nowhere stated and which " | ||
| + | |||
| + | **Accepted and not done, for budget:** re-adjudicating the 73 '' | ||
| + | |||
| + | ==== 18.12 The scripts, and their unedited output ==== | ||
| + | |||
| + | Everything below is read from the committed file by '' | ||
| + | |||
| + | < | ||
| + | # Four-pin hand-coding codebook — design: | ||
| + | |||
| + | You are hand-coding papers for a systematization page on measuretheweb.org. The | ||
| + | page currently publishes, for 250 repeated-crawl papers, how many **stated** each | ||
| + | of four crawl parameters. " | ||
| + | every wave". Your job is to turn the ceiling into an estimate by reading what the | ||
| + | paper actually says about its repeated measurement. | ||
| + | |||
| + | ## Step 0 — is this really a repeated crawl by the authors? | ||
| + | |||
| + | The 250 were selected mechanically (`temporal.snapshots > 1` in a structured | ||
| + | extraction). That predicate has false positives. Code `repeatKind` as exactly one: | ||
| + | |||
| + | | value | meaning | | ||
| + | |---|---| | ||
| + | | `own-repeat-crawl` | the authors themselves ran their crawler more than once over time and the waves are compared or aggregated | | ||
| + | | `own-repeat-nonweb` | repeated measurement, | ||
| + | | `parallel-not-temporal` | the " | ||
| + | | `reused-waves` | the multiple time points come from a dataset or archive somebody else collected (web archive, existing corpus), not from the authors' | ||
| + | | `single-crawl` | the paper ran one crawl; the snapshot count is a mis-extraction (page counts, record counts, number of sites, number of runs of an unrelated thing) | | ||
| + | | `unclear` | you genuinely cannot tell after reading | | ||
| + | |||
| + | Also record `waveCount`: | ||
| + | authors' | ||
| + | daily/ | ||
| + | otherwise `null`. | ||
| + | |||
| + | ## Step 1 — the four pins | ||
| + | |||
| + | Code each of the four independently. **The question is not "did they say it" but | ||
| + | "was it the same in every wave" | ||
| + | |||
| + | * `list` — the **population / seed list**: which sites or targets were crawled | ||
| + | (Tranco, Alexa, CrUX, Majestic, a custom seed set …). NOT the filter list. | ||
| + | * `browser` — the browser (and version, if given) the crawler drove. | ||
| + | * `vantage` — the network location the crawl ran from (country, cloud region, | ||
| + | university network, VPN exit, residential proxy …). | ||
| + | * `state` — stateful vs stateless crawling: fresh profile per site, persistent | ||
| + | cookie jar, shared profile, cleared between waves, etc. | ||
| + | |||
| + | Each pin takes exactly one value: | ||
| + | |||
| + | | value | when to use | | ||
| + | |---|---| | ||
| + | | `same-explicit` | the paper says in words that this was the same / unchanged / the same identifier across the waves. A single permanent identifier used for all waves (one Tranco list ID, one pinned browser version, one container image) counts. | | ||
| + | | `same-implied` | the paper gives ONE value in a single methods description that plainly covers the whole repeated measurement, | ||
| + | | `varied-deliberate` | the value differs across waves ON PURPOSE, as part of the design (rotating vantage points, comparing browsers across waves). | | ||
| + | | `changed` | the value changed or was refreshed between waves — the top list re-downloaded per wave, the filter list or browser updated, a move to a different host — whether stated as deliberate or as drift. | | ||
| + | | `not-stated` | the paper never gives the value at all. | | ||
| + | | `unclear` | the value is given somewhere but you cannot tell which wave(s) it applies to, or the description is contradictory. | | ||
| + | |||
| + | Rules that matter: | ||
| + | |||
| + | 1. **Do not infer from the structured extraction line at the top of the packet.** | ||
| + | It is shown so you can see what a machine thought; it records a value stated | ||
| + | | ||
| + | 2. **A list that is re-downloaded each wave is `changed`, not `same-implied`** — | ||
| + | even though that is often the right thing to do scientifically. We are | ||
| + | | ||
| + | 3. **"We used Chrome" | ||
| + | | ||
| + | setup for the whole study. If the paper says the crawler was rebuilt, updated, | ||
| + | or migrated, it is `changed`. | ||
| + | 4. If `repeatKind` is `single-crawl` or `parallel-not-temporal`, | ||
| + | four pins, but the " | ||
| + | only if one configuration is described, and note it in `notes`. | ||
| + | 5. `not-stated` is a finding, not a failure. Do not guess. | ||
| + | |||
| + | ## Step 2 — evidence | ||
| + | |||
| + | For each pin, give `quote`: a **verbatim** substring of the packet, ≤ 220 | ||
| + | characters, that is your evidence. Copy it exactly — it will be checked against | ||
| + | the source file automatically, | ||
| + | `not-stated`, | ||
| + | |||
| + | Set `confidence` per paper: `high` | `medium` | `low`. | ||
| + | |||
| + | ## Output | ||
| + | |||
| + | For EACH paper write one file `out_pins/ | ||
| + | (same base name as its packet, `.json` instead of `.txt`), containing: | ||
| + | |||
| + | ```json | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | Write valid JSON. No markdown fences in the file. | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | # Blind re-coding — second coder | ||
| + | |||
| + | You are the SECOND coder in a two-coder reliability check. A first coder has | ||
| + | already coded these papers; you will not see their answers, and you must not go | ||
| + | looking for them. Files under `/ | ||
| + | first coder' | ||
| + | |||
| + | Read `/ | ||
| + | |||
| + | Two differences from the first pass: | ||
| + | |||
| + | 1. **You read the FULL paper text**, not a keyword excerpt. The path is given to | ||
| + | you. Read the whole methodology and any limitations section. | ||
| + | 2. **Write your output to `/ | ||
| + | file naming (`< | ||
| + | |||
| + | Everything else — the `repeatKind` values, the six pin values, the verbatim | ||
| + | `quote` requirement, | ||
| + | says, not what you think it probably did. | ||
| + | </ | ||
| + | |||
| + | <file javascript pins_population.mjs> | ||
| + | # | ||
| + | // Emit the 250 repeated-crawl papers that design: | ||
| + | // is computed over, with the extraction' | ||
| + | // | ||
| + | // node scripts/ | ||
| + | // | ||
| + | // The population predicate is copied verbatim from scripts/ | ||
| + | // (`crawled` AND max stated temporal.snapshots > 1) so the two agree by construction. | ||
| + | import fs from ' | ||
| + | import path from ' | ||
| + | |||
| + | const argv = process.argv.slice(2); | ||
| + | const RUN = argv.includes(' | ||
| + | const FULLTEXT = '/ | ||
| + | |||
| + | const P = fs.readFileSync(path.join(RUN, | ||
| + | const crawled = P.filter((p) => p.crawlConfig != null || p.studyTypes.includes(' | ||
| + | const snapOf = (p) => Math.max(0, ...p.temporal.map((t) => (t.snapshots == null ? 0 : t.snapshots))); | ||
| + | const repeated = crawled.filter((p) => snapOf(p) > 1); | ||
| + | |||
| + | const SENT = /^(?:not[- ]stated|unknown|n\/ | ||
| + | const statedListVersion = (p) => p.population.some((x) => x.listVersion != null && !SENT.test(x.listVersion.trim())); | ||
| + | const namedBrowser = (p) => p.crawlConfig != null && p.crawlConfig.browsers.length > 0; | ||
| + | const statedVantage = (p) => p.vantage.some((v) => v.locations.some((l) => !SENT.test(String(l).trim()))); | ||
| + | const ccStated = (p, f) => p.crawlConfig != null && ![' | ||
| + | |||
| + | for (const p of repeated.sort((a, | ||
| + | const key = `${p.year}/ | ||
| + | const f = path.join(FULLTEXT, | ||
| + | process.stdout.write(JSON.stringify({ | ||
| + | key, year: p.year, venue: p.venue, slug: p.slug, title: p.title, | ||
| + | snapshots: snapOf(p), | ||
| + | cadence: p.temporal.map((t) => t.cadence).filter(Boolean), | ||
| + | spans: p.temporal.map((t) => [t.spanStart, | ||
| + | modes: [...new Set(p.temporal.map((t) => t.mode))], | ||
| + | x_listVersion: | ||
| + | x_browser: namedBrowser(p), | ||
| + | x_vantage: statedVantage(p), | ||
| + | x_statefulness: | ||
| + | v_listVersion: | ||
| + | v_sourceList: | ||
| + | v_browsers: p.crawlConfig ? p.crawlConfig.browsers : [], | ||
| + | v_statefulness: | ||
| + | v_vantage: [...new Set(p.vantage.flatMap((v) => v.locations).filter((l) => l && !SENT.test(String(l).trim())))], | ||
| + | textPath: f, | ||
| + | textBytes: fs.existsSync(f) ? fs.statSync(f).size : 0, | ||
| + | }) + ' | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <file javascript pins_excerpt.mjs> | ||
| + | # | ||
| + | // Build the per-paper reading packet a hand-coder gets for the four-pin audit. | ||
| + | // | ||
| + | // node scripts/ | ||
| + | // | ||
| + | // Why an excerpt and not the whole paper: 250 papers is 20.9 MB of `.cols` text. | ||
| + | // The coder needs the parts that describe the repeated measurement, | ||
| + | // results or the related work. The rule below is deliberately over-inclusive — | ||
| + | // two keyword tiers with context windows, merged — and the coder is also given | ||
| + | // the full-text path and told to open it when the packet is not enough. The | ||
| + | // recall of this rule is measured, not assumed: see the validation pass in | ||
| + | // provenance: | ||
| + | import fs from ' | ||
| + | import path from ' | ||
| + | |||
| + | const argv = process.argv.slice(2); | ||
| + | const OUT = argv.includes(' | ||
| + | const BUDGET = argv.includes(' | ||
| + | const POP = ' | ||
| + | |||
| + | // Tier 1: the sentence is about a wave, a repetition, or a pin being held. | ||
| + | const CORE = [ | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | ]; | ||
| + | // Tier 2: methodology furniture — useful surroundings, | ||
| + | const CTX = [ | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | / | ||
| + | /\btop[- ](?: | ||
| + | ]; | ||
| + | const CORE_W = 700; | ||
| + | const CTX_W = 300; | ||
| + | |||
| + | function windows(text) { | ||
| + | const spans = []; | ||
| + | for (const [res, w, tier] of [[CORE, CORE_W, 1], [CTX, CTX_W, 2]]) { | ||
| + | for (const re of res) { | ||
| + | const g = new RegExp(re.source, | ||
| + | let m; | ||
| + | while ((m = g.exec(text)) !== null) { | ||
| + | spans.push({ a: Math.max(0, m.index - w), b: Math.min(text.length, | ||
| + | if (m.index === g.lastIndex) g.lastIndex++; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | return spans; | ||
| + | } | ||
| + | |||
| + | function merge(spans) { | ||
| + | spans.sort((x, | ||
| + | const out = []; | ||
| + | for (const s of spans) { | ||
| + | const last = out[out.length - 1]; | ||
| + | if (last && s.a <= last.b + 40) { last.b = Math.max(last.b, | ||
| + | else out.push({ ...s }); | ||
| + | } | ||
| + | return out; | ||
| + | } | ||
| + | |||
| + | fs.mkdirSync(OUT, | ||
| + | const pop = fs.readFileSync(POP, | ||
| + | const stats = []; | ||
| + | for (const p of pop) { | ||
| + | const raw = fs.readFileSync(p.textPath, | ||
| + | const text = raw.replace(/ | ||
| + | const HEAD = text.slice(0, | ||
| + | let sp = merge(windows(text)); | ||
| + | let kept = sp; | ||
| + | let dropped = 0; | ||
| + | const total = (s) => s.reduce((a, | ||
| + | if (total(kept) > BUDGET) { | ||
| + | // Drop tier-2-only windows first, longest-shortfall last: keep every core window. | ||
| + | const core = kept.filter((s) => s.tier === 1); | ||
| + | dropped = kept.length - core.length; | ||
| + | kept = core; | ||
| + | } | ||
| + | if (total(kept) > BUDGET) { | ||
| + | // Still over: shrink core windows symmetrically rather than cutting the tail, | ||
| + | // because limitations sections sit at the end and carry the pin admissions. | ||
| + | const f = BUDGET / total(kept); | ||
| + | kept = kept.map((s) => { const mid = (s.a + s.b) / 2, h = ((s.b - s.a) * f) / 2; return { a: Math.round(mid - h), b: Math.round(mid + h), tier: s.tier }; }); | ||
| + | kept = merge(kept); | ||
| + | } | ||
| + | const body = kept.map((s) => `[…${s.a}] ` + text.slice(s.a, | ||
| + | const packet = `# ${p.key}\n# title: ${p.title}\n# | ||
| + | const f = path.join(OUT, | ||
| + | fs.writeFileSync(f, | ||
| + | stats.push({ key: p.key, src: text.length, | ||
| + | } | ||
| + | const ps = stats.map((s) => s.packet).sort((a, | ||
| + | console.log(`wrote ${stats.length} packets to ${OUT}`); | ||
| + | console.log(`packet chars min ${ps[0]} | ||
| + | console.log(`total packet MB ${(ps.reduce((a, | ||
| + | console.log(`coverage: | ||
| + | console.log(`packets where tier-2 windows had to be dropped: ${stats.filter((s) => s.droppedCtx > 0).length}`); | ||
| + | fs.writeFileSync(path.join(OUT, | ||
| + | </ | ||
| + | |||
| + | <file javascript pins_valsample.mjs> | ||
| + | # | ||
| + | // Draw the blind re-coding sample for the four-pin audit. | ||
| + | // | ||
| + | // node scripts/ | ||
| + | // | ||
| + | // A second coder re-codes these papers from the FULL `.cols` text, without | ||
| + | // seeing the first coding and without the keyword packet. That measures two | ||
| + | // things at once: how reliable the coding is, and how much the excerpting rule | ||
| + | // in scripts/ | ||
| + | // reproducible from the seed alone. | ||
| + | import fs from ' | ||
| + | |||
| + | const argv = process.argv.slice(2); | ||
| + | const N = argv.includes(' | ||
| + | const SEED = argv.includes(' | ||
| + | |||
| + | // mulberry32 | ||
| + | let s = SEED >>> | ||
| + | const rnd = () => { s |= 0; s = (s + 0x6d2b79f5) | 0; let t = Math.imul(s ^ (s >>> | ||
| + | |||
| + | const pop = fs.readFileSync(' | ||
| + | const idx = pop.map((_, i) => i); | ||
| + | for (let i = idx.length - 1; i > 0; i--) { const j = Math.floor(rnd() * (i + 1)); [idx[i], idx[j]] = [idx[j], idx[i]]; } | ||
| + | const pick = idx.slice(0, | ||
| + | console.error(`seed ${SEED}, n=${N} of ${pop.length}`); | ||
| + | for (const p of pick) console.log(`${p.key}\t${p.textPath}`); | ||
| + | </ | ||
| + | |||
| + | <file javascript report_pins_audit.mjs> | ||
| + | # | ||
| + | // The four-pin HAND-CODED audit for design: | ||
| + | // | ||
| + | // node scripts/ | ||
| + | // | ||
| + | // design: | ||
| + | // many of the 250 repeated-crawl papers STATED it. Stating a value once is only a | ||
| + | // ceiling on holding it fixed across waves. This script aggregates a hand-coding | ||
| + | // of all 250 papers against out_pins/ | ||
| + | // three-number estimate: | ||
| + | // | ||
| + | // | ||
| + | // | ||
| + | // | ||
| + | // | ||
| + | // Rules obeyed: papers are counted, never tuples; sentinels are never an answer; | ||
| + | // every denominator is named on the line above the numbers; every value in the | ||
| + | // codebook is printed even when it is zero, so nothing vanishes. | ||
| + | import fs from ' | ||
| + | import path from ' | ||
| + | |||
| + | const argv = process.argv.slice(2); | ||
| + | const CODED = argv.includes(' | ||
| + | const RUN = argv.includes(' | ||
| + | const POP = argv.includes(' | ||
| + | |||
| + | const pct = (a, b) => (b ? ((100 * a) / b).toFixed(1) : ' | ||
| + | const rule = (t) => console.log(`\n${' | ||
| + | const head = (t, d) => console.log(`\n-- ${t}\n | ||
| + | |||
| + | const pop = fs.readFileSync(POP, | ||
| + | const byKey = new Map(pop.map((p) => [p.key, p])); | ||
| + | |||
| + | // ------------------------------------------------------------------ load | ||
| + | const PINS = [' | ||
| + | const VALUES = [' | ||
| + | const KINDS = [' | ||
| + | |||
| + | const coded = []; | ||
| + | const problems = []; | ||
| + | for (const p of pop) { | ||
| + | const f = path.join(CODED, | ||
| + | if (!fs.existsSync(f)) { problems.push(`MISSING coding: ${p.key}`); continue; } | ||
| + | const raw = fs.readFileSync(f, | ||
| + | let c; | ||
| + | try { c = JSON.parse(raw); | ||
| + | if (c.key !== p.key) problems.push(`KEY MISMATCH in ${f}: says ${c.key}`); | ||
| + | if (!KINDS.includes(c.repeatKind)) problems.push(`BAD repeatKind " | ||
| + | let ok = true; | ||
| + | for (const k of PINS) { | ||
| + | if (!c.pins || !c.pins[k] || !VALUES.includes(c.pins[k].value)) { | ||
| + | problems.push(`BAD/ | ||
| + | ok = false; | ||
| + | } | ||
| + | } | ||
| + | if (!ok) continue; | ||
| + | coded.push({ ...c, key: p.key, year: p.year, venue: p.venue, slug: p.slug, ex: p }); | ||
| + | } | ||
| + | |||
| + | // The whole audit rests on every paper being coded. Refuse to print figures | ||
| + | // computed over a partial population rather than quietly shrinking the | ||
| + | // denominator — a shelled-out denominator is how percentages go wrong silently. | ||
| + | if (coded.length !== pop.length) { | ||
| + | console.error(`\nFATAL: | ||
| + | for (const s of problems) console.error(' | ||
| + | process.exit(1); | ||
| + | } | ||
| + | |||
| + | console.log(`design: | ||
| + | console.log(`coded dir: ${CODED}`); | ||
| + | console.log(`extraction run: ${RUN}`); | ||
| + | console.log(`corpus: | ||
| + | if (problems.length) { console.log(`\nSCHEMA PROBLEMS (${problems.length}): | ||
| + | else console.log(`\nschema: | ||
| + | |||
| + | // ------------------------------------------------- 1. is it a repeat crawl | ||
| + | rule(' | ||
| + | head(' | ||
| + | for (const k of KINDS) { | ||
| + | const n = coded.filter((c) => c.repeatKind === k).length; | ||
| + | console.log(` | ||
| + | } | ||
| + | const OWN = coded.filter((c) => c.repeatKind === ' | ||
| + | console.log(`\n | ||
| + | console.log(` | ||
| + | |||
| + | // ------------------------------------------------------- 2. the four pins | ||
| + | rule(' | ||
| + | for (const [POPNAME, G] of [['all 250 mechanically selected', | ||
| + | head(' | ||
| + | console.log(` | ||
| + | for (const k of PINS) { | ||
| + | const row = VALUES.map((v) => G.filter((c) => c.pins[k].value === v).length); | ||
| + | console.log(` | ||
| + | } | ||
| + | console.log(`\n | ||
| + | console.log(` | ||
| + | const XF = { list: ' | ||
| + | for (const k of PINS) { | ||
| + | const fl = G.filter((c) => c.pins[k].value === ' | ||
| + | const es = G.filter((c) => [' | ||
| + | const ce = G.filter((c) => c.ex[XF[k]]).length; | ||
| + | console.log(` | ||
| + | } | ||
| + | const four = (c, set) => PINS.every((k) => set.includes(c.pins[k].value)); | ||
| + | const f4fl = G.filter((c) => four(c, [' | ||
| + | const f4es = G.filter((c) => four(c, [' | ||
| + | const f4ce = G.filter((c) => c.ex.x_listVersion && c.ex.x_browser && c.ex.x_vantage && c.ex.x_statefulness).length; | ||
| + | console.log(`\n | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | const none = G.filter((c) => PINS.every((k) => c.pins[k].value === ' | ||
| + | console.log(` | ||
| + | const anyMoved = G.filter((c) => PINS.some((k) => [' | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | // -------------------------------------------- 3. where the ceiling overstates | ||
| + | rule(' | ||
| + | head(' | ||
| + | const XF = { list: ' | ||
| + | console.log(` | ||
| + | for (const k of PINS) { | ||
| + | const ce = coded.filter((c) => c.ex[XF[k]]).length; | ||
| + | const es = coded.filter((c) => [' | ||
| + | const why = coded.filter((c) => c.ex[XF[k]] && ![' | ||
| + | const brk = new Map(); | ||
| + | for (const c of why) brk.set(c.pins[k].value, | ||
| + | console.log(` | ||
| + | } | ||
| + | console.log(`\n | ||
| + | console.log(` | ||
| + | for (const k of PINS) { | ||
| + | const n = coded.filter((c) => !c.ex[XF[k]] && [' | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | // ------------------------------------------------- 4. by span, by year | ||
| + | rule(' | ||
| + | { | ||
| + | const MONTHS = [' | ||
| + | // Same parser as report_longitudinal.mjs, | ||
| + | const parseDate = (s) => { | ||
| + | if (!s) return null; | ||
| + | const t = String(s).toLowerCase(); | ||
| + | const ym = / | ||
| + | if (ym) return { y: +ym[1], m: +ym[2] }; | ||
| + | const yO = / | ||
| + | if (!yO) return null; | ||
| + | const y = +yO[1]; | ||
| + | if (y < 1990 || y > 2027) return null; | ||
| + | const nm = new RegExp(`\\b(${MONTHS.join(' | ||
| + | return { y, m: nm ? MONTHS.indexOf(nm[1]) + 1 : 1 }; | ||
| + | }; | ||
| + | const longestSpan = (p) => { | ||
| + | let best = null; | ||
| + | for (const [a, b] of p.spans) { | ||
| + | const pa = parseDate(a), | ||
| + | if (!pa || !pb) continue; | ||
| + | const d = (pb.y - pa.y) * 12 + (pb.m - pa.m); | ||
| + | if (d >= 0 && (best == null || d > best)) best = d; | ||
| + | } | ||
| + | return best; | ||
| + | }; | ||
| + | const four = (c) => PINS.every((k) => [' | ||
| + | head(' | ||
| + | const multi = OWN.filter((c) => { const m = longestSpan(c.ex); | ||
| + | const sub = OWN.filter((c) => { const m = longestSpan(c.ex); | ||
| + | const unk = OWN.length - multi.length - sub.length; | ||
| + | for (const [name, g] of [[' | ||
| + | console.log(` | ||
| + | PINS.map((k) => `${k} ${g.filter((c) => [' | ||
| + | } | ||
| + | console.log(` | ||
| + | |||
| + | head(' | ||
| + | for (const [a, b] of [[2010, 2011], [2012, 2015], [2016, 2019], [2020, 2023], [2024, 2026]]) { | ||
| + | const g = OWN.filter((c) => c.year >= a && c.year <= b); | ||
| + | const flag = b === 2026 ? ' | ||
| + | console.log(` | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // ------------------------------------------------- 5. explicit-pin exemplars | ||
| + | rule(' | ||
| + | { | ||
| + | for (const k of PINS) { | ||
| + | const g = coded.filter((c) => c.pins[k].value === ' | ||
| + | console.log(`\n-- ${k}: ${g.length} papers state explicitly that it was unchanged across waves`); | ||
| + | for (const c of g) console.log(` | ||
| + | } | ||
| + | } | ||
| + | rule(' | ||
| + | { | ||
| + | for (const k of PINS) { | ||
| + | const g = coded.filter((c) => c.pins[k].value === ' | ||
| + | console.log(`\n-- ${k}: ${g.length} papers state the value changed or was refreshed between waves`); | ||
| + | for (const c of g) console.log(` | ||
| + | } | ||
| + | for (const k of PINS) { | ||
| + | const g = coded.filter((c) => c.pins[k].value === ' | ||
| + | console.log(`\n-- ${k}: ${g.length} papers vary it across waves BY DESIGN`); | ||
| + | for (const c of g) console.log(` | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // ------------------------------------------------------- 7. coder confidence | ||
| + | rule(' | ||
| + | { | ||
| + | head(' | ||
| + | for (const v of [' | ||
| + | const n = coded.filter((c) => c.confidence === v).length; | ||
| + | console.log(` | ||
| + | } | ||
| + | const bad = coded.filter((c) => ![' | ||
| + | if (bad.length) console.log(` | ||
| + | const four = (c) => PINS.every((k) => [' | ||
| + | const g = coded.filter(four).sort((a, | ||
| + | console.log(`\n-- the ${g.length} papers estimated to hold all four fixed (any repeatKind), | ||
| + | console.log(` | ||
| + | for (const c of g) { | ||
| + | const ex = PINS.filter((k) => c.pins[k].value === ' | ||
| + | console.log(` | ||
| + | } | ||
| + | const ownFour = OWN.filter(four); | ||
| + | console.log(`\n | ||
| + | for (const c of ownFour.sort((a, | ||
| + | } | ||
| + | |||
| + | // ---------------------------------------------- 8. old exemplar list, rechecked | ||
| + | rule(' | ||
| + | { | ||
| + | const old = coded.filter((c) => c.ex.x_listVersion && c.ex.x_browser && c.ex.x_vantage && c.ex.x_statefulness); | ||
| + | const four = (c) => PINS.every((k) => [' | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | for (const c of old.sort((a, | ||
| + | console.log(` | ||
| + | } | ||
| + | console.log(`\n | ||
| + | } | ||
| + | console.log(); | ||
| + | |||
| + | // ============================================================ wiki emitter | ||
| + | // The page's tables are pasted from here, not retyped. A retyped table is how a | ||
| + | // stale cell survives a refresh. | ||
| + | if (argv.includes(' | ||
| + | const four = (c, set) => PINS.every((k) => set.includes(c.pins[k].value)); | ||
| + | const XW = { list: ' | ||
| + | const LBL = { list: ' | ||
| + | rule(' | ||
| + | console.log(`\n---- table: repeatKind over the ${coded.length} mechanically selected papers ----`); | ||
| + | const KL = { | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | unclear: ' | ||
| + | }; | ||
| + | console.log(`^ What the "more than one snapshot" | ||
| + | for (const k of KINDS) { | ||
| + | const n = coded.filter((c) => c.repeatKind === k).length; | ||
| + | console.log(`| ${KL[k]} | ${n} | ${pct(n, coded.length)}% |`); | ||
| + | } | ||
| + | for (const [G, name] of [[coded, `all ${coded.length}`], | ||
| + | console.log(`\n---- table: four pins over ${name} ----`); | ||
| + | console.log(`^ Pin ^ Held fixed, stated in words ^ Held fixed, estimate ^ Stated at all (old ceiling) ^ Moved between waves ^ Never stated ^`); | ||
| + | for (const k of PINS) { | ||
| + | const fl = G.filter((c) => c.pins[k].value === ' | ||
| + | const es = G.filter((c) => [' | ||
| + | const ce = G.filter((c) => c.ex[XW[k]]).length; | ||
| + | const mv = G.filter((c) => [' | ||
| + | const ns = G.filter((c) => c.pins[k].value === ' | ||
| + | console.log(`| ${LBL[k]} | ${fl} (${pct(fl, G.length)}%) | **${es} (${pct(es, G.length)}%)** | ${ce} (${pct(ce, G.length)}%) | ${mv} (${pct(mv, G.length)}%) | ${ns} (${pct(ns, G.length)}%) |`); | ||
| + | } | ||
| + | const f4fl = G.filter((c) => four(c, [' | ||
| + | const f4es = G.filter((c) => four(c, [' | ||
| + | const f4ce = G.filter((c) => c.ex.x_listVersion && c.ex.x_browser && c.ex.x_vantage && c.ex.x_statefulness).length; | ||
| + | const f4mv = G.filter((c) => PINS.some((k) => [' | ||
| + | const f4ns = G.filter((c) => PINS.every((k) => c.pins[k].value === ' | ||
| + | console.log(`| **All four** | ${f4fl} (${pct(f4fl, | ||
| + | console.log(` | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // ------------------------------------------------- the exemplar table, regenerated | ||
| + | // The content page's " | ||
| + | // edited in place, so a hand-coded verdict cannot drift from the table cell that | ||
| + | // reports it. Citekeys come from out_pins/ | ||
| + | // keys ALREADY on design: | ||
| + | if (argv.includes(' | ||
| + | const MAPF = argv.includes(' | ||
| + | const map = JSON.parse(fs.readFileSync(MAPF, | ||
| + | delete map._comment; | ||
| + | const old = coded.filter((c) => c.ex.x_listVersion && c.ex.x_browser && c.ex.x_vantage && c.ex.x_statefulness); | ||
| + | // If the map and the population disagree the table would silently lose a row. | ||
| + | const mapped = new Set(Object.keys(map)); | ||
| + | const inPop = new Set(old.map((c) => c.slug)); | ||
| + | const onlyMap = [...mapped].filter((s) => !inPop.has(s)); | ||
| + | const onlyPop = [...inPop].filter((s) => !mapped.has(s)); | ||
| + | if (onlyMap.length || onlyPop.length) { | ||
| + | console.error(`\nFATAL: | ||
| + | for (const s of onlyMap) console.error(` | ||
| + | for (const s of onlyPop) console.error(` | ||
| + | process.exit(1); | ||
| + | } | ||
| + | const VEN = { ' | ||
| + | const KIND = { | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | unclear: ' | ||
| + | }; | ||
| + | const SH = { ' | ||
| + | const four = (c) => PINS.every((k) => [' | ||
| + | console.log(`\n---- table: the ${old.length} papers that STATED all four, with what hand-coding found ----`); | ||
| + | console.log(`^ Year ^ Venue ^ Paper ^ Really a repeat crawl? ^ List ^ Browser ^ Vantage ^ State ^ All four fixed? ^`); | ||
| + | for (const c of old.sort((a, | ||
| + | const m = map[c.slug]; | ||
| + | console.log(`| ${c.year} | ${VEN[c.venue]} | ${m.name} {[${m.key}]} | ${KIND[c.repeatKind]} | ${PINS.map((k) => SH[c.pins[k].value]).join(' | ||
| + | } | ||
| + | console.log(`\n | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <file javascript pins_quotecheck.mjs> | ||
| + | # | ||
| + | // Verify every evidence quote the four-pin hand-coding produced. | ||
| + | // | ||
| + | // node scripts/ | ||
| + | // | ||
| + | // A hand-coder that paraphrases is a hand-coder that hallucinates. Each non-empty | ||
| + | // `quote` must appear in the paper it is attributed to. | ||
| + | // | ||
| + | // WHY THIS IS NOT A PLAIN SUBSTRING TEST. The corpus stores three renderings of | ||
| + | // each PDF and they disagree sentence by sentence: | ||
| + | // | ||
| + | // | ||
| + | // | ||
| + | // Even `.cols` still splices a quarter of pages: a sentence that runs across a | ||
| + | // column boundary comes back with the neighbouring column' | ||
| + | // the middle of it, e.g. | ||
| + | // " | ||
| + | // B. Design Principles Firefox. It currently instruments ..." | ||
| + | // A coder who reads that correctly and writes down the real sentence produces a | ||
| + | // quote that is NOT a substring of any rendering. Failing it as unlocated would | ||
| + | // call a correct reading a hallucination. | ||
| + | // | ||
| + | // So matching is tried in four escalating modes, against all three renderings | ||
| + | // and the excerpt packet: | ||
| + | // | ||
| + | // | ||
| + | // | ||
| + | // dashes folded to ASCII, case ignored | ||
| + | // | ||
| + | // passes if SHINGLE_MIN of them are found individually. This is the | ||
| + | // de-splicing mode and it is reported separately and listed in full, | ||
| + | // because it is the weakest evidence and has to be read by a human. | ||
| + | // A quote that fails all four against every stored rendering is then tried | ||
| + | // against a FIFTH text: a pypdf rendering of the same paper.pdf, produced on | ||
| + | // demand by `scripts/ | ||
| + | // pypdf splice different sentences, so a coder who read the PDF correctly can | ||
| + | // produce a quote that is absent from every stored rendering and verbatim in | ||
| + | // the PDF. The same four modes are tried there and a pass is reported as | ||
| + | // | ||
| + | // quote is in the paper; the stored text is what is wrong. | ||
| + | // This is the fallback `scripts/ | ||
| + | // already use; it was added here on 2026-09-22, and before that this checker' | ||
| + | // NOT FOUND bucket was not comparable with theirs. | ||
| + | // Anything failing all four modes against all five texts is printed in full. | ||
| + | import fs from ' | ||
| + | import path from ' | ||
| + | import { pdfText } from ' | ||
| + | |||
| + | const argv = process.argv.slice(2); | ||
| + | const CODED = argv.includes(' | ||
| + | const POP = argv.includes(' | ||
| + | const EXC = argv.includes(' | ||
| + | const SHINGLE_N = 6; | ||
| + | const SHINGLE_MIN = 0.8; | ||
| + | |||
| + | const collapse = (s) => s.replace(/ | ||
| + | const loose = (s) => | ||
| + | collapse(s) | ||
| + | .replace(/ | ||
| + | .replace(/ | ||
| + | .replace(/ | ||
| + | .replace(/ | ||
| + | .replace(/[ ]/g, ' ') | ||
| + | .toLowerCase(); | ||
| + | |||
| + | // Fraction of the quote' | ||
| + | // loose-folded haystack; null when the quote is too short to shingle. | ||
| + | const shingleFrac = (q, hayLoose) => { | ||
| + | const w = loose(q).split(' | ||
| + | if (w.length < SHINGLE_N) return null; | ||
| + | const sh = []; | ||
| + | for (let i = 0; i + SHINGLE_N <= w.length; i++) sh.push(w.slice(i, | ||
| + | return sh.filter((x) => hayLoose.includes(x)).length / sh.length; | ||
| + | }; | ||
| + | |||
| + | const pop = fs.readFileSync(POP, | ||
| + | let total = 0, empty = 0; | ||
| + | const modes = { exact: 0, collapsed: 0, loose: 0, spliced: 0 }; | ||
| + | const fails = []; | ||
| + | const splicedList = []; | ||
| + | const rescuedList = []; | ||
| + | let missing = 0; | ||
| + | |||
| + | for (const p of pop) { | ||
| + | const f = path.join(CODED, | ||
| + | if (!fs.existsSync(f)) { missing++; continue; } | ||
| + | const raw = fs.readFileSync(f, | ||
| + | const c = JSON.parse(raw); | ||
| + | |||
| + | // Every rendering the corpus holds, plus the packet the coder was handed. | ||
| + | const dir = path.dirname(p.textPath); | ||
| + | const parts = []; | ||
| + | for (const name of [' | ||
| + | const q = path.join(dir, | ||
| + | if (fs.existsSync(q)) parts.push(fs.readFileSync(q, | ||
| + | } | ||
| + | const packet = path.join(EXC, | ||
| + | if (fs.existsSync(packet)) parts.push(fs.readFileSync(packet, | ||
| + | const hay = parts.join(' | ||
| + | const hayC = collapse(hay); | ||
| + | const hayL = loose(hay); | ||
| + | |||
| + | for (const [pin, v] of Object.entries(c.pins)) { | ||
| + | total++; | ||
| + | const q = (v.quote || '' | ||
| + | if (!q) { empty++; continue; } | ||
| + | if (hay.includes(q)) { modes.exact++; | ||
| + | if (hayC.includes(collapse(q))) { modes.collapsed++; | ||
| + | if (hayL.includes(loose(q))) { modes.loose++; | ||
| + | // de-splicing mode | ||
| + | const frac = shingleFrac(q, | ||
| + | if (frac !== null && frac >= SHINGLE_MIN) { | ||
| + | modes.spliced++; | ||
| + | splicedList.push({ key: p.key, pin, value: v.value, quote: q, frac }); | ||
| + | continue; | ||
| + | } | ||
| + | // Fifth text: pypdf. Only consulted for a quote that is in none of the | ||
| + | // stored renderings, so it costs nothing on a clean run. | ||
| + | const pdf = pdfText(p.key); | ||
| + | if (pdf !== null) { | ||
| + | const pdfC = collapse(pdf); | ||
| + | const pdfL = loose(pdf); | ||
| + | let mode = null; | ||
| + | if (pdf.includes(q)) mode = ' | ||
| + | else if (pdfC.includes(collapse(q))) mode = ' | ||
| + | else if (pdfL.includes(loose(q))) mode = ' | ||
| + | else { | ||
| + | const pdfFrac = shingleFrac(q, | ||
| + | if (pdfFrac !== null && pdfFrac >= SHINGLE_MIN) mode = `spliced ${(100 * pdfFrac).toFixed(0)}%`; | ||
| + | } | ||
| + | if (mode !== null) { | ||
| + | rescuedList.push({ key: p.key, pin, value: v.value, quote: q, frac, mode }); | ||
| + | continue; | ||
| + | } | ||
| + | } | ||
| + | fails.push({ key: p.key, pin, value: v.value, quote: q, frac, pdf: pdf !== null }); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | const checked = total - empty; | ||
| + | const pc = (n) => `${n} ${((100 * n) / (checked || 1)).toFixed(1)}%`; | ||
| + | console.log(`design: | ||
| + | console.log(`coded dir: ${CODED}`); | ||
| + | console.log(`renderings searched per paper: paper.cols.txt, | ||
| + | console.log(`fallback for a quote found in none of those: a pypdf rendering of paper.pdf (scripts/ | ||
| + | console.log(`de-splicing mode: ${SHINGLE_N}-word shingles, pass at >= ${(100 * SHINGLE_MIN).toFixed(0)}% found`); | ||
| + | console.log(`\npapers with no coding file (skipped): ${missing}`); | ||
| + | console.log(`pin codings: | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | |||
| + | // A quote-less coding that is not `not-stated` is a coding with no evidence. | ||
| + | const noEvidence = []; | ||
| + | for (const p of pop) { | ||
| + | const f = path.join(CODED, | ||
| + | if (!fs.existsSync(f)) continue; | ||
| + | const c = JSON.parse(fs.readFileSync(f, | ||
| + | for (const [pin, v] of Object.entries(c.pins)) { | ||
| + | if (!(v.quote || '' | ||
| + | } | ||
| + | } | ||
| + | console.log(`\ncodings with no quote and a value other than not-stated: ${noEvidence.length}`); | ||
| + | for (const s of noEvidence) console.log(' | ||
| + | |||
| + | console.log(`\nMATCHED ONLY AFTER DE-SPLICING (${splicedList.length}) — these are read by hand, the source page is two-column spliced:`); | ||
| + | for (const f of splicedList.sort((a, | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | console.log(`\nRESCUED — in none of the stored renderings, located in the pypdf rendering of paper.pdf (${rescuedList.length}).`); | ||
| + | console.log(`These are defects in the stored text, not in the hand coding:`); | ||
| + | for (const f of rescuedList) { | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | console.log(`\nQUOTES NOT LOCATED, printed in full (${fails.length}): | ||
| + | for (const f of fails) console.log(` | ||
| + | </ | ||
| + | |||
| + | <file javascript pins_agreement.mjs> | ||
| + | # | ||
| + | // Inter-coder agreement for the design: | ||
| + | // | ||
| + | // node scripts/ | ||
| + | // | ||
| + | // Coder A read a keyword-window excerpt packet; coder B read the whole paper and | ||
| + | // never saw A's output. The sample is out_pins/ | ||
| + | // reported three ways per pin, because the three answer different questions: | ||
| + | // | ||
| + | // | ||
| + | // | ||
| + | // {not = varied-deliberate|changed|not-stated|unclear} — this is | ||
| + | // the partition the published " | ||
| + | // Cohen kappa chance-corrected, | ||
| + | // | ||
| + | // A high exact agreement with a low kappa means the coders agree because one | ||
| + | // value dominates, not because they are reading the same thing. Both are printed. | ||
| + | import fs from ' | ||
| + | import path from ' | ||
| + | |||
| + | const argv = process.argv.slice(2); | ||
| + | const A = argv.includes(' | ||
| + | const B = argv.includes(' | ||
| + | const SAMPLE = argv.includes(' | ||
| + | |||
| + | const PINS = [' | ||
| + | const VALUES = [' | ||
| + | const FIXED = new Set([' | ||
| + | const pct = (a, b) => (b ? ((100 * a) / b).toFixed(1) : ' | ||
| + | |||
| + | const keys = fs.readFileSync(SAMPLE, | ||
| + | const load = (dir, key) => { | ||
| + | const f = path.join(dir, | ||
| + | if (!fs.existsSync(f)) return null; | ||
| + | const raw = fs.readFileSync(f, | ||
| + | return JSON.parse(raw); | ||
| + | }; | ||
| + | |||
| + | const pairs = []; | ||
| + | const missing = []; | ||
| + | for (const k of keys) { | ||
| + | const a = load(A, k), b = load(B, k); | ||
| + | if (!a || !b) { missing.push(`${k} | ||
| + | pairs.push({ key: k, a, b }); | ||
| + | } | ||
| + | |||
| + | // Refuse to report an agreement rate over a shrunken sample without saying so. | ||
| + | console.log(' | ||
| + | console.log(`coder A (excerpt packets): ${A}`); | ||
| + | console.log(`coder B (full text, blind): ${B}`); | ||
| + | console.log(`\nreliability sample: ${keys.length} papers drawn from the 250`); | ||
| + | console.log(`pairs available: | ||
| + | if (missing.length) { console.log(`MISSING (${missing.length}): | ||
| + | |||
| + | const kappa = (rows) => { | ||
| + | const n = rows.length; | ||
| + | if (!n) return NaN; | ||
| + | const obs = rows.filter(([x, | ||
| + | const ma = new Map(), mb = new Map(); | ||
| + | for (const [x, y] of rows) { ma.set(x, (ma.get(x) || 0) + 1); mb.set(y, (mb.get(y) || 0) + 1); } | ||
| + | let exp = 0; | ||
| + | for (const v of new Set([...ma.keys(), | ||
| + | return exp === 1 ? NaN : (obs - exp) / (1 - exp); | ||
| + | }; | ||
| + | |||
| + | console.log(`\n${' | ||
| + | console.log(` | ||
| + | for (const k of PINS) { | ||
| + | const rows = pairs.map((p) => [p.a.pins[k].value, | ||
| + | const bin = rows.map(([x, | ||
| + | const ex = rows.filter(([x, | ||
| + | const bi = bin.filter(([x, | ||
| + | const ke = kappa(rows), | ||
| + | console.log(` | ||
| + | } | ||
| + | { | ||
| + | const rows = pairs.flatMap((p) => PINS.map((k) => [p.a.pins[k].value, | ||
| + | const bin = rows.map(([x, | ||
| + | const ex = rows.filter(([x, | ||
| + | const bi = bin.filter(([x, | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | console.log(`\n-- repeatKind`); | ||
| + | { | ||
| + | const rows = pairs.map((p) => [p.a.repeatKind, | ||
| + | const ex = rows.filter(([x, | ||
| + | console.log(` | ||
| + | const own = (v) => (v === ' | ||
| + | const bin = rows.map(([x, | ||
| + | const bi = bin.filter(([x, | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | console.log(`\n-- "all four fixed", | ||
| + | { | ||
| + | const f = (c) => PINS.every((k) => FIXED.has(c.pins[k].value)); | ||
| + | const rows = pairs.map((p) => [String(f(p.a)), | ||
| + | const ex = rows.filter(([x, | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | console.log(`\n${' | ||
| + | for (const k of PINS) { | ||
| + | let aOnly = 0, bOnly = 0, both = 0, neither = 0; | ||
| + | for (const p of pairs) { | ||
| + | const x = FIXED.has(p.a.pins[k].value), | ||
| + | if (x && y) both++; else if (x) aOnly++; else if (y) bOnly++; else neither++; | ||
| + | } | ||
| + | console.log(` | ||
| + | } | ||
| + | |||
| + | console.log(`\n${' | ||
| + | let nd = 0; | ||
| + | for (const p of pairs) { | ||
| + | const diffs = PINS.filter((k) => p.a.pins[k].value !== p.b.pins[k].value); | ||
| + | const kindDiff = p.a.repeatKind !== p.b.repeatKind; | ||
| + | if (!diffs.length && !kindDiff) continue; | ||
| + | nd++; | ||
| + | console.log(`\n${p.key}`); | ||
| + | if (kindDiff) console.log(` | ||
| + | for (const k of diffs) console.log(` | ||
| + | } | ||
| + | console.log(`\npapers with at least one disagreement: | ||
| + | |||
| + | console.log(`\n${' | ||
| + | console.log(` | ||
| + | for (const v of VALUES) { | ||
| + | const a = pairs.flatMap((p) => PINS.map((k) => p.a.pins[k].value)).filter((x) => x === v).length; | ||
| + | const b = pairs.flatMap((p) => PINS.map((k) => p.b.pins[k].value)).filter((x) => x === v).length; | ||
| + | console.log(` | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <file python build_provenance_pins.py> | ||
| + | # | ||
| + | """ | ||
| + | |||
| + | Reads the live page text and the prose fragment, and splices in < | ||
| + | read from the committed scripts and their committed outputs — never retyped, so | ||
| + | the published listing is the code that produced the published figures. | ||
| + | |||
| + | python3 scripts/ | ||
| + | --live pages/ | ||
| + | --prose prov/ | ||
| + | --out pages/ | ||
| + | |||
| + | The prose fragment carries {{BLOCK:< | ||
| + | replaced by the file's real contents. A placeholder naming a missing file, or a | ||
| + | file containing a literal file-closing tag (which would end the block early and | ||
| + | render the rest of the page as markup), is a hard error. | ||
| + | """ | ||
| + | import argparse, pathlib, re, sys | ||
| + | |||
| + | CLOSER = "</" | ||
| + | |||
| + | ap = argparse.ArgumentParser() | ||
| + | ap.add_argument(" | ||
| + | ap.add_argument(" | ||
| + | ap.add_argument(" | ||
| + | ap.add_argument(" | ||
| + | a = ap.parse_args() | ||
| + | |||
| + | root = pathlib.Path(__file__).resolve().parent.parent | ||
| + | live = pathlib.Path(a.live).read_text(encoding=" | ||
| + | prose = pathlib.Path(a.prose).read_text(encoding=" | ||
| + | |||
| + | def expand(m): | ||
| + | rel, lang, name = m.group(1), m.group(2), m.group(3) | ||
| + | p = root / rel | ||
| + | if not p.exists(): | ||
| + | sys.exit(f" | ||
| + | src = p.read_text(encoding=" | ||
| + | if CLOSER in src: | ||
| + | sys.exit(f" | ||
| + | if not src.strip(): | ||
| + | sys.exit(f" | ||
| + | return f"< | ||
| + | |||
| + | prose, n = re.subn(r" | ||
| + | if n == 0: | ||
| + | sys.exit(" | ||
| + | |||
| + | # §9 said the hand-coding " | ||
| + | # would leave the page contradicting its own §18. Patched here rather than by hand | ||
| + | # so the edit is regenerable and fails loudly if the target text ever moves. | ||
| + | PATCHES = [( | ||
| + | " | ||
| + | "paper stated, once. Every pin figure on the content page is a ceiling and is labelled as one. " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | )] | ||
| + | for _old, _new in PATCHES: | ||
| + | if _old not in live: | ||
| + | sys.exit(" | ||
| + | live = live.replace(_old, | ||
| + | |||
| + | if a.anchor not in live: | ||
| + | sys.exit(f" | ||
| + | head, sep, tail = live.rpartition(a.anchor) | ||
| + | out = head + prose.rstrip() + " | ||
| + | |||
| + | # Structural guard: an in-place generator that silently drops sections is the | ||
| + | # failure mode this check exists for. | ||
| + | # | ||
| + | # A whole-output count is NOT enough. The appended section adds ~11 headings of | ||
| + | # its own, so a total-count check has that much slack: drop one heading from the | ||
| + | # preserved live content and the total still exceeds the original. Mutation | ||
| + | # testing on 2026-09-07 confirmed exactly that — a heading deleted from `head` | ||
| + | # gave "27 -> 37" and exit 0. So the counts are compared on the PRESERVED live | ||
| + | # text alone (head + sep + tail), which must match the live page exactly, and the | ||
| + | # appended section is counted separately and required to be non-empty. | ||
| + | preserved = head + sep + tail | ||
| + | for tag, pat in ((" | ||
| + | b, keep, add = len(re.findall(pat, | ||
| + | if keep != b: | ||
| + | sys.exit(f" | ||
| + | print(f" | ||
| + | if add == 0 and tag != " | ||
| + | sys.exit(f" | ||
| + | # Byte-level check that nothing in the live page was silently rewritten, beyond | ||
| + | # the patches applied above, which are themselves verified to have a target. | ||
| + | if preserved != live: | ||
| + | sys.exit(" | ||
| + | |||
| + | pathlib.Path(a.out).write_text(out, | ||
| + | print(f" | ||
| + | </ | ||
| + | |||
| + | **Unedited output of the audit** — '' | ||
| + | |||
| + | < | ||
| + | design: | ||
| + | coded dir: out_pins/ | ||
| + | extraction run: / | ||
| + | corpus: 5,859 papers, 7 venues (CCS, IMC, NDSS, PETS, USENIX Sec, TheWebConf, IEEE S&P), 2010-2026 | ||
| + | |||
| + | schema: all 250 codings parse and use only codebook values. | ||
| + | |||
| + | ============================================================================== | ||
| + | 1. IS THE MECHANICALLY SELECTED POPULATION REALLY REPEATED CRAWLING | ||
| + | ============================================================================== | ||
| + | |||
| + | -- repeatKind, hand-coded | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | TRUE POSITIVES for "the authors re-crawled the web over time": 124 / 250 = 49.6% | ||
| + | The remaining 126 are the mechanical predicate' | ||
| + | |||
| + | ============================================================================== | ||
| + | 2. THE FOUR PINS — CEILING, ESTIMATE, FLOOR | ||
| + | ============================================================================== | ||
| + | |||
| + | -- hand-coded pin values | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | ALL FOUR held fixed | ||
| + | floor (all four same-explicit) | ||
| + | estimate (all four explicit or implied) | ||
| + | ceiling | ||
| + | none of the four stated at all 10 / 250 = 4.0% | ||
| + | at least one pin demonstrably NOT fixed 79 / 250 = 31.6% | ||
| + | |||
| + | -- hand-coded pin values | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | ALL FOUR held fixed | ||
| + | floor (all four same-explicit) | ||
| + | estimate (all four explicit or implied) | ||
| + | ceiling | ||
| + | none of the four stated at all 0 / 124 = 0.0% | ||
| + | at least one pin demonstrably NOT fixed 42 / 124 = 33.9% | ||
| + | |||
| + | ============================================================================== | ||
| + | 3. HOW FAR THE OLD CEILING OVERSTATED EACH PIN | ||
| + | ============================================================================== | ||
| + | |||
| + | -- ceiling minus estimate, per pin | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | The reverse direction — hand-coding finds the pin fixed where the extraction | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | ============================================================================== | ||
| + | 4. DOES THE LONGER PANEL STILL PIN LESS | ||
| + | ============================================================================== | ||
| + | |||
| + | -- all four fixed (estimate), by longest stated span | ||
| + | | ||
| + | span >= 12 months | ||
| + | span < 12 months | ||
| + | (14 have no parsable end date and are excluded from this split) | ||
| + | |||
| + | -- all four fixed (estimate), by four-year bucket | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | ============================================================================== | ||
| + | 5. PAPERS THAT SAY IN WORDS THAT A PIN WAS HELD FIXED | ||
| + | ============================================================================== | ||
| + | |||
| + | -- list: 73 papers state explicitly that it was unchanged across waves | ||
| + | 2026 PETS more-space-less-privacy-measuring-the-effectiveness-of-ip-base " | ||
| + | 2026 PETS overcoming-language-barriers-multilingual-analysis-of-the-2023 "The set of websites is held constant across both snapshots;" | ||
| + | 2025 IEEE-SP | ||
| + | 2025 IMC | ||
| + | 2025 IMC | ||
| + | 2025 IMC | ||
| + | 2025 IMC | ||
| + | 2025 IMC | ||
| + | 2025 PETS buy-it-now-track-me-later-attacking-user-privacy-via-wi-fi-ap- "We continued to look these MAC addresses up every day for a month, from mid-January to mid-February 2025." | ||
| + | 2025 PETS intractable-cookie-crumbs-unveiling-the-nexus-of-stateful-bann "This list, generated on 07 December 2023 with ID K2NZW, is available at: https:" | ||
| + | 2025 PETS measuring-the-accuracy-and-effectiveness-of-pii-removal-servic "we use a script to automatically crawl the data broker coverage list on the official website of the PII remova" | ||
| + | 2025 USENIX | ||
| + | 2025 USENIX | ||
| + | 2025 USENIX | ||
| + | 2025 USENIX | ||
| + | 2025 USENIX | ||
| + | 2025 WWW | ||
| + | 2024 IMC | ||
| + | 2024 PETS a-large-scale-study-of-cookie-banner-interaction-tools-and-the "we resort to the widely used quasi-standard Tranco list [30] generated on 29/ | ||
| + | 2024 PETS johnny-still-cant-opt-out-assessing-the-iab-ccpa-compliance-fr "We use the Tranco list dated April 17th, 2023, with ID GZ7NK." | ||
| + | 2024 PETS two-steps-forward-and-one-step-back-the-right-to-opt-out-of-sa "the October 29, 2022 Tranco top websites list" | ||
| + | 2024 USENIX | ||
| + | 2023 CCS | ||
| + | 2023 IMC | ||
| + | 2023 IMC | ||
| + | 2023 PETS blocking-javascript-without-breaking-the-web-an-empirical-inve "In 2021, we crawled 100K webpages and classified the collected JS" | ||
| + | 2023 USENIX | ||
| + | 2023 USENIX | ||
| + | 2023 WWW | ||
| + | 2023 WWW | ||
| + | 2022 CCS | ||
| + | 2022 CCS | ||
| + | 2022 IEEE-SP | ||
| + | 2022 IEEE-SP | ||
| + | 2022 IMC | ||
| + | 2022 IMC | ||
| + | 2022 PETS a-global-survey-of-android-dual-use-applications-used-in-intim "we crawled Google Play again in all fifteen languages for one day in February 2022 with the queries we obtaine" | ||
| + | 2022 PETS atom-ad-network-tomography | ||
| + | 2022 WWW | ||
| + | 2021 NDSS whos-hosting-the-block-party-studying-third-party-blockage-of- "For each crawl, our crawlers visited the start pages from the fixed list and followed every same-site link." | ||
| + | 2021 PETS deja-vu-abusing-browser-cache-headers-to-identify-and-track-on " | ||
| + | 2021 PETS domain-name-encryption-is-not-enough-privacy-leakage-via-ip-ba "The list was created on March 3rd 2020, and is available at https:// | ||
| + | 2021 WWW | ||
| + | 2021 WWW | ||
| + | 2020 IMC | ||
| + | 2020 IMC | ||
| + | 2020 NDSS complex-security-policy-a-longitudinal-analysis-of-deployed-co "Given that our list of Web sites remains stable, we can attribute the increased CSP adoption to Web developers" | ||
| + | 2020 PETS a-comparative-measurement-study-of-web-tracking-on-mobile-and- "in both experiments, | ||
| + | 2020 WWW | ||
| + | 2020 WWW | ||
| + | 2020 WWW | ||
| + | 2019 IMC | ||
| + | 2019 IMC | ||
| + | 2019 WWW | ||
| + | 2019 WWW | ||
| + | 2019 WWW | ||
| + | 2019 WWW | ||
| + | 2018 USENIX | ||
| + | 2018 WWW | ||
| + | 2017 CCS | ||
| + | 2017 IEEE-SP | ||
| + | 2017 PETS cross-device-tracking-measurement-and-disclosures | ||
| + | 2017 USENIX | ||
| + | 2017 USENIX | ||
| + | 2016 CCS | ||
| + | 2016 IEEE-SP | ||
| + | 2016 USENIX | ||
| + | 2016 USENIX | ||
| + | 2015 IMC | ||
| + | 2015 NDSS i-do-not-know-what-you-visited-last-summer-protecting-users-fr "This process is repeated twice: once starting with a clean browser and once more after priming the client-side" | ||
| + | 2015 WWW | ||
| + | 2012 IEEE-SP | ||
| + | the latency of visiting the top 200 websites [17]" | ||
| + | 2012 WWW | ||
| + | |||
| + | -- browser: 23 papers state explicitly that it was unchanged across waves | ||
| + | 2026 NDSS eviction-notice-reviving-and-advancing-page-cache-attacks | ||
| + | 2025 IEEE-SP | ||
| + | 2025 USENIX | ||
| + | 2024 NDSS flow-correlation-attacks-on-tor-onion-service-sessions-with-sl " | ||
| + | 2024 PETS a-large-scale-study-of-cookie-banner-interaction-tools-and-the "Each profile is based on OpenWPM [12] (v0.20.0), uses the Firefox browser (v100.0 with the default user agent1" | ||
| + | 2024 PETS johnny-still-cant-opt-out-assessing-the-iab-ccpa-compliance-fr "Each of our four crawls used the same baseline configuration." | ||
| + | 2024 PETS two-steps-forward-and-one-step-back-the-right-to-opt-out-of-sa "All data collection was conducted using the Chrome web browser." | ||
| + | 2023 CCS | ||
| + | 2023 IMC | ||
| + | 2023 WWW | ||
| + | 2022 IEEE-SP | ||
| + | 2021 IMC | ||
| + | 2021 PETS deja-vu-abusing-browser-cache-headers-to-identify-and-track-on "3rd of June, using Headless Chrome instrumented with" | ||
| + | 2021 PETS domain-name-encryption-is-not-enough-privacy-leakage-via-ip-ba "we repeatedly crawled the 220K websites from our test list curated in §5.1, using the Chrome browser (desktop " | ||
| + | 2020 PETS a-comparative-measurement-study-of-web-tracking-on-mobile-and- "The version of both Firefox for Android and desktop Firefox is 53.0 without the tracking protection function." | ||
| + | 2020 WWW | ||
| + | 2020 WWW | ||
| + | 2019 IMC | ||
| + | 2019 PETS p1-fp-extraction-classification-and-prediction-of-website-fing "Tor version 0.4.0.8 to collect our datasets" | ||
| + | 2018 CCS | ||
| + | 2018 IMC | ||
| + | 2016 IMC | ||
| + | 2011 CCS | ||
| + | |||
| + | -- vantage: 32 papers state explicitly that it was unchanged across waves | ||
| + | 2026 PETS more-space-less-privacy-measuring-the-effectiveness-of-ip-base " | ||
| + | 2026 PETS privacy-vs-profit-the-impact-of-googles-manifest-version-3-mv3 "held geography constant so that any differences reflect" | ||
| + | 2025 IEEE-SP | ||
| + | 2025 IMC | ||
| + | 2025 PETS intractable-cookie-crumbs-unveiling-the-nexus-of-stateful-bann " | ||
| + | 2025 PETS more-and-scammier-ads-the-perils-of-youtubes-ad-privacy-settin "we run five such virtual machines simultaneously with local IP addresses in Sydney (Australia), | ||
| + | 2025 USENIX | ||
| + | 2025 USENIX | ||
| + | 2024 PETS a-bilingual-longitudinal-analysis-of-privacy-policies-measurin "For all crawls, we used a server located in California to simulate the geolocation of Californian residents in" | ||
| + | 2024 PETS automatic-generation-of-web-censorship-probe-lists | ||
| + | 2024 PETS johnny-still-cant-opt-out-assessing-the-iab-ccpa-compliance-fr "All crawls were conducted using virtual machines from Amazon Web Services with IP addresses in California." | ||
| + | 2024 PETS the-devil-is-in-the-details-detection-measurement-and-lawfulne " | ||
| + | 2024 PETS two-steps-forward-and-one-step-back-the-right-to-opt-out-of-sa "All of the data collection was done on AWS EC2 c6i.8xlarge instances that were located in a California data ce" | ||
| + | 2024 USENIX | ||
| + | 2023 CCS | ||
| + | 2023 IMC | ||
| + | 2023 WWW | ||
| + | 2022 IMC | ||
| + | 2022 PETS a-global-survey-of-android-dual-use-applications-used-in-intim "The EC2 servers we used for our crawling in 2020 and 2022 were all located in the US." | ||
| + | 2022 WWW | ||
| + | 2021 PETS deja-vu-abusing-browser-cache-headers-to-identify-and-track-on "For our evaluation, we performed crawls from a single address in our research lab." | ||
| + | 2020 IMC | ||
| + | 2020 IMC | ||
| + | 2020 WWW | ||
| + | 2019 WWW | ||
| + | 2018 IEEE-SP | ||
| + | 2018 WWW | ||
| + | 2017 NDSS dial-one-for-scam-a-large-scale-analysis-of-technical-support- "We deployed the ROBOVIC Crawler on three different sites (our campus, Amazon' | ||
| + | 2017 NDSS thou-shalt-not-depend-on-me-analysing-the-use-of-outdated-java "We conducted the two crawls in May 2016 from IP addresses in a /24 range in the US." | ||
| + | 2016 USENIX | ||
| + | 2016 USENIX | ||
| + | 2012 IEEE-SP | ||
| + | the Tor network when visiting the top 200 websites from 50 | ||
| + | PlanetL" | ||
| + | |||
| + | -- state: 24 papers state explicitly that it was unchanged across waves | ||
| + | 2026 NDSS characterizing-the-implementation-of-censorship-policies-in-ch "For the majority of services, we keep the browser session | ||
| + | persistent across a given test run." | ||
| + | 2026 PETS more-space-less-privacy-measuring-the-effectiveness-of-ip-base " | ||
| + | 2025 IEEE-SP | ||
| + | 2025 NDSS evocrawl-exploring-web-application-code-and-state-using-evolut "reset all tested web application instances before each crawling | ||
| + | session. This ensures that all scanners commen" | ||
| + | 2025 PETS unmasking-the-shadows-a-cross-country-study-of-online-tracking "in stateless mode, where no session data or cookies were preserved between page loads" | ||
| + | 2025 WWW | ||
| + | 2024 PETS a-large-scale-study-of-cookie-banner-interaction-tools-and-the "In our experiment, we perform a stateful crawl for each visited site, meaning we keep the browser' | ||
| + | 2024 PETS the-devil-is-in-the-details-detection-measurement-and-lawfulne "Crawl state Stateless Stateless" | ||
| + | 2023 IMC | ||
| + | 2023 PETS blocking-javascript-without-breaking-the-web-an-empirical-inve "and stateless crawls." | ||
| + | 2023 PETS raven-stateless-rapid-ip-address-variation-for-enterprise-netw "The browser cache was cleaned" | ||
| + | 2023 USENIX | ||
| + | 2022 IMC | ||
| + | 2022 USENIX | ||
| + | 2022 WWW | ||
| + | 2020 PETS a-comparative-measurement-study-of-web-tracking-on-mobile-and- "Each data collec- tion experiment includes two stateless2 measurements on the mobile and desktop environments," | ||
| + | 2020 WWW | ||
| + | 2019 IMC | ||
| + | 2019 IMC | ||
| + | 2019 PETS p1-fp-extraction-classification-and-prediction-of-website-fing "reset the Tor process after each website visit, but otherwise used the default options" | ||
| + | 2017 NDSS thou-shalt-not-depend-on-me-analysing-the-use-of-outdated-java "our crawler remains on each page for a fixed delay of 60 seconds before clearing its entire state, restarting," | ||
| + | 2017 PETS cross-device-tracking-measurement-and-disclosures | ||
| + | 2016 IEEE-SP | ||
| + | 2015 IMC | ||
| + | |||
| + | ============================================================================== | ||
| + | 6. PAPERS WHERE A PIN DEMONSTRABLY MOVED BETWEEN WAVES | ||
| + | ============================================================================== | ||
| + | |||
| + | -- list: 38 papers state the value changed or was refreshed between waves | ||
| + | 2026 PETS quicstep-evaluating-connection-migration-based-quic-censorship "daily Tranco top 1M websites" | ||
| + | 2025 NDSS the-unusual-suspects-studying-reasons-for-lacking-updates-in-w "as described for Sampling Round 1 (May 9-19, 2023)" | ||
| + | 2025 PETS beyond-the-request-harnessing-http-response-headers-for-cross- "using T.EX and visiting the landing pages with Chrome to explore the top 10K websites from the Tranco ranking " | ||
| + | 2025 PETS the-effect-of-platform-policies-on-app-privacy-compliance-a-st "we re-queued all of the previous apps for testing and added 302 additional apps that we subsequently identifie" | ||
| + | 2025 PETS who-s-watching-you-zoom-investigating-privacy-of-third-party-z " | ||
| + | 2025 WWW | ||
| + | 2024 PETS a-bilingual-longitudinal-analysis-of-privacy-policies-measurin " | ||
| + | 2024 USENIX | ||
| + | 2023 USENIX | ||
| + | 2023 USENIX | ||
| + | 2022 WWW | ||
| + | 2021 IEEE-SP | ||
| + | 2021 PETS privacy-preference-signals-past-present-and-future | ||
| + | 2021 USENIX | ||
| + | 2021 WWW | ||
| + | 2021 WWW | ||
| + | 2020 IMC | ||
| + | 2020 WWW | ||
| + | 2019 IEEE-SP | ||
| + | 2019 IEEE-SP | ||
| + | 2019 IMC | ||
| + | 2019 IMC | ||
| + | 2019 NDSS we-value-your-privacy-now-take-some-cookies-measuring-the-gdpr "To extend the scope of our study, we retrieved updated top lists once per month." | ||
| + | 2019 WWW | ||
| + | 2019 WWW | ||
| + | 2019 WWW | ||
| + | 2018 IEEE-SP | ||
| + | 2018 IMC | ||
| + | 2018 WWW | ||
| + | 2017 NDSS dial-one-for-scam-a-large-scale-analysis-of-technical-support- "Note that ROBOVIC was originally relying just on domain parking in order to find technical support scams and w" | ||
| + | 2017 WWW | ||
| + | 2016 USENIX | ||
| + | 2015 CCS | ||
| + | 2015 USENIX | ||
| + | 2014 CCS | ||
| + | 2014 CCS | ||
| + | 2011 USENIX | ||
| + | 2010 IEEE-SP | ||
| + | |||
| + | -- browser: 7 papers state the value changed or was refreshed between waves | ||
| + | 2025 PETS who-s-watching-you-zoom-investigating-privacy-of-third-party-z "We created another crawler and parser to handle a major change in the app category listing after May 2024." | ||
| + | 2024 PETS the-devil-is-in-the-details-detection-measurement-and-lawfulne " | ||
| + | 2022 IMC | ||
| + | 2019 IEEE-SP | ||
| + | 2019 NDSS we-value-your-privacy-now-take-some-cookies-measuring-the-gdpr "using the latest version of Firefox (version 57 onward)" | ||
| + | 2018 WWW | ||
| + | 2014 CCS | ||
| + | |||
| + | -- vantage: 2 papers state the value changed or was refreshed between waves | ||
| + | 2017 USENIX | ||
| + | 2015 CCS | ||
| + | |||
| + | -- state: 0 papers state the value changed or was refreshed between waves | ||
| + | |||
| + | -- list: 6 papers vary it across waves BY DESIGN | ||
| + | 2011 CCS | ||
| + | 2017 NDSS thou-shalt-not-depend-on-me-analysing-the-use-of-outdated-java | ||
| + | 2017 USENIX | ||
| + | 2022 USENIX | ||
| + | 2024 PETS website-data-transparency-in-the-browser | ||
| + | 2025 PETS more-and-scammier-ads-the-perils-of-youtubes-ad-privacy-settin | ||
| + | |||
| + | -- browser: 14 papers vary it across waves BY DESIGN | ||
| + | 2016 IEEE-SP | ||
| + | 2019 NDSS time-does-not-heal-all-wounds-a-longitudinal-analysis-of-secur | ||
| + | 2020 USENIX | ||
| + | 2020 WWW | ||
| + | 2022 IMC | ||
| + | 2022 USENIX | ||
| + | 2023 USENIX | ||
| + | 2023 USENIX | ||
| + | 2024 IEEE-SP | ||
| + | 2025 NDSS cross-origin-web-attacks-via-http-2-server-push-and-signed-htt | ||
| + | 2025 PETS intractable-cookie-crumbs-unveiling-the-nexus-of-stateful-bann | ||
| + | 2026 PETS clicking-into-exposure-uncovering-privacy-risks-of-google-clic | ||
| + | 2026 PETS privacy-vs-profit-the-impact-of-googles-manifest-version-3-mv3 | ||
| + | 2026 USENIX | ||
| + | |||
| + | -- vantage: 20 papers vary it across waves BY DESIGN | ||
| + | 2014 CCS | ||
| + | 2015 NDSS parking-sensors-analyzing-and-detecting-parked-domains | ||
| + | 2016 IEEE-SP | ||
| + | 2016 IMC | ||
| + | 2020 WWW | ||
| + | 2021 IMC | ||
| + | 2021 USENIX | ||
| + | 2022 IMC | ||
| + | 2022 PETS from-onion-not-found-to-guard-discovery | ||
| + | 2022 USENIX | ||
| + | 2022 USENIX | ||
| + | 2023 USENIX | ||
| + | 2023 USENIX | ||
| + | 2023 USENIX | ||
| + | 2024 IEEE-SP | ||
| + | 2024 PETS a-large-scale-study-of-cookie-banner-interaction-tools-and-the | ||
| + | 2024 PETS website-data-transparency-in-the-browser | ||
| + | 2025 PETS unmasking-the-shadows-a-cross-country-study-of-online-tracking | ||
| + | 2026 NDSS characterizing-the-implementation-of-censorship-policies-in-ch | ||
| + | 2026 PETS clicking-into-exposure-uncovering-privacy-risks-of-google-clic | ||
| + | |||
| + | -- state: 8 papers vary it across waves BY DESIGN | ||
| + | 2015 NDSS i-do-not-know-what-you-visited-last-summer-protecting-users-fr | ||
| + | 2016 USENIX | ||
| + | 2021 WWW | ||
| + | 2022 USENIX | ||
| + | 2023 CCS | ||
| + | 2024 IMC | ||
| + | 2025 PETS intractable-cookie-crumbs-unveiling-the-nexus-of-stateful-bann | ||
| + | 2026 PETS clicking-into-exposure-uncovering-privacy-risks-of-google-clic | ||
| + | |||
| + | ============================================================================== | ||
| + | 7. CODER CONFIDENCE AND THE ALL-FOUR EXEMPLAR LIST | ||
| + | ============================================================================== | ||
| + | |||
| + | -- self-reported confidence | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | -- the 18 papers estimated to hold all four fixed (any repeatKind), | ||
| + | year | venue | slug | repeatKind | waves | explicit pins | ||
| + | 2026 | PETS | more-space-less-privacy-measuring-the-effectiveness-of-ip-based-website-fingerpr | own-repeat-crawl | 5 | list, | ||
| + | 2025 | IEEE-SP | sok-decoding-the-enigma-of-encrypted-network-traffic-classifiers | own-repeat-crawl | 100 | list, | ||
| + | 2025 | USENIX | navigating-cookie-consent-violations-across-the-globe | own-repeat-crawl | 10 | list, | ||
| + | 2024 | USENIX | fledging-will-continue-until-privacy-improves-empirical-analysis-of-googles-priv | own-repeat-crawl | 3 | vantage | ||
| + | 2023 | IMC | tracking-profiling-and-ad-targeting-in-the-alexa-echo-smart-speaker-ecosystem | own-repeat-crawl | 31 | list, | ||
| + | 2023 | PETS | blocking-javascript-without-breaking-the-web-an-empirical-investigation | own-repeat-crawl | 2 | list,state | ||
| + | 2023 | PETS | raven-stateless-rapid-ip-address-variation-for-enterprise-networks | own-repeat-nonweb | ? | state | ||
| + | 2022 | WWW | measuring-the-privacy-vs-compatibility-trade-off-in-preventing-third-party-state | own-repeat-crawl | 2 | list, | ||
| + | 2021 | WWW | towards-realistic-and-reproducibleweb-crawl-measurements | own-repeat-crawl | 3 | list | ||
| + | 2020 | NDSS | encrypted-dns-privacy-a-traffic-analysis-perspective | own-repeat-crawl | 5 | - | ||
| + | 2020 | PETS | a-comparative-measurement-study-of-web-tracking-on-mobile-and-desktop-environmen | own-repeat-crawl | 2 | list, | ||
| + | 2020 | PETS | missed-by-filter-lists-detecting-unknown-third-party-trackers-with-invisible-pix | parallel-not-temporal | ? | - | ||
| + | 2020 | PETS | no-boundaries-data-exfiltration-by-third-parties-embedded-on-web-pages | single-crawl | ? | - | ||
| + | 2020 | WWW | a-first-look-at-commercial-5g-performance-on-smartphones | own-repeat-crawl | ? | state | ||
| + | 2020 | WWW | the-representativeness-of-automated-web-crawls-as-a-surrogate-for-human-browsing | own-repeat-crawl | 44 | list, | ||
| + | 2019 | WWW | before-and-after-gdpr-the-changes-in-third-party-presence-at-public-and-private | own-repeat-crawl | 21 | list | ||
| + | 2019 | WWW | measuring-political-personalization-of-google-news-search | own-repeat-crawl | 7 | vantage | ||
| + | 2018 | WWW | auditing-the-personalization-and-composition-of-politically-related-search-engin | own-repeat-crawl | 6 | vantage | ||
| + | |||
| + | of those, hand-confirmed own repeat crawls: 15 | ||
| + | 2026 | PETS | more-space-less-privacy-measuring-the-effectiveness-of-ip-based-website-fingerpr | ||
| + | 2025 | IEEE-SP | sok-decoding-the-enigma-of-encrypted-network-traffic-classifiers | ||
| + | 2025 | USENIX | navigating-cookie-consent-violations-across-the-globe | ||
| + | 2024 | USENIX | fledging-will-continue-until-privacy-improves-empirical-analysis-of-googles-priv | ||
| + | 2023 | IMC | tracking-profiling-and-ad-targeting-in-the-alexa-echo-smart-speaker-ecosystem | ||
| + | 2023 | PETS | blocking-javascript-without-breaking-the-web-an-empirical-investigation | ||
| + | 2022 | WWW | measuring-the-privacy-vs-compatibility-trade-off-in-preventing-third-party-state | ||
| + | 2021 | WWW | towards-realistic-and-reproducibleweb-crawl-measurements | ||
| + | 2020 | NDSS | encrypted-dns-privacy-a-traffic-analysis-perspective | ||
| + | 2020 | PETS | a-comparative-measurement-study-of-web-tracking-on-mobile-and-desktop-environmen | ||
| + | 2020 | WWW | a-first-look-at-commercial-5g-performance-on-smartphones | ||
| + | 2020 | WWW | the-representativeness-of-automated-web-crawls-as-a-surrogate-for-human-browsing | ||
| + | 2019 | WWW | before-and-after-gdpr-the-changes-in-third-party-presence-at-public-and-private | ||
| + | 2019 | WWW | measuring-political-personalization-of-google-news-search | ||
| + | 2018 | WWW | auditing-the-personalization-and-composition-of-politically-related-search-engin | ||
| + | |||
| + | ============================================================================== | ||
| + | 8. WHAT HAPPENED TO THE 23 PAPERS THE OLD CEILING TABLE LISTED | ||
| + | ============================================================================== | ||
| + | 23 papers stated all four. Hand-coding each: | ||
| + | |||
| + | year venue slug kind | ||
| + | 2026 PETS | ||
| + | 2026 PETS | ||
| + | 2026 PETS | ||
| + | 2025 PETS | ||
| + | 2025 PETS | ||
| + | 2025 PETS | ||
| + | 2025 USENIX | ||
| + | 2025 WWW before-after-the-effect-of-eus-2022-code-of own-repeat-crawl | ||
| + | 2024 IEEE-SP | ||
| + | 2024 PETS | ||
| + | 2024 PETS | ||
| + | 2023 USENIX | ||
| + | 2022 IMC respect-the-origin-a-best-case-evaluation-o own-repeat-crawl | ||
| + | 2022 USENIX | ||
| + | 2022 WWW reproducibility-and-replicability-of-web-me own-repeat-crawl | ||
| + | 2020 NDSS | ||
| + | 2020 PETS | ||
| + | 2020 WWW beyond-the-front-page-measuring-third-party parallel-not-temporal same-explicit / same-explicit / varied-deliberate / same-implied | ||
| + | 2020 WWW the-representativeness-of-automated-web-cra own-repeat-crawl | ||
| + | 2019 WWW measuring-political-personalization-of-goog own-repeat-crawl | ||
| + | 2017 NDSS | ||
| + | 2016 IEEE-SP | ||
| + | 2016 USENIX | ||
| + | |||
| + | | ||
| + | </ | ||
| + | |||
| + | **Unedited output of the quote check.** | ||
| + | |||
| + | < | ||
| + | design: | ||
| + | coded dir: out_pins/ | ||
| + | renderings searched per paper: paper.cols.txt, | ||
| + | fallback for a quote found in none of those: a pypdf rendering of paper.pdf (scripts/ | ||
| + | de-splicing mode: 6-word shingles, pass at >= 80% found | ||
| + | |||
| + | papers with no coding file (skipped): 0 | ||
| + | pin codings: | ||
| + | no quote given: | ||
| + | | ||
| + | exact match: | ||
| + | whitespace-collapsed match: | ||
| + | loose match: | ||
| + | spliced match (de-spliced): | ||
| + | RESCUED from the PDF: 35 6.5% | ||
| + | NOT FOUND: | ||
| + | | ||
| + | |||
| + | codings with no quote and a value other than not-stated: 0 | ||
| + | |||
| + | MATCHED ONLY AFTER DE-SPLICING (3) — these are read by hand, the source page is two-column spliced: | ||
| + | | ||
| + | applied our system to this data set by following the same procedure. | ||
| + | | ||
| + | applied our system to this data set by following the same procedure. | ||
| + | | ||
| + | we sampled 100 domains from each parking service and setup an additional crawler which crawled them daily for a week. | ||
| + | |||
| + | RESCUED — in none of the stored renderings, located in the pypdf rendering of paper.pdf (35). | ||
| + | These are defects in the stored text, not in the hand coding: | ||
| + | | ||
| + | We implemented FourthParty as an extension to Mozilla Firefox. | ||
| + | | ||
| + | To assess any geographical differences, | ||
| + | | ||
| + | We managed to emulate typical browser behavior in all but one case (BlueSky). | ||
| + | | ||
| + | The number of Onion addresses in this experiment was 16,683, and the addresses were crawled twice a day at 12-hour intervals. | ||
| + | | ||
| + | we can only continuously monitor 1,000 samples. Besides, we re-visit these samples every 3 days | ||
| + | | ||
| + | to visit the Alexa top 50k web domains | ||
| + | | ||
| + | We have crawled two datasets at different moments, June 2018 and September 2018, which are three months apart. The two app datasets have more than 2/3 overlapped apps. | ||
| + | | ||
| + | We installed the widely used OpenWPM framework and configured it to use four vanilla Mozilla Firefox browsers in parallel | ||
| + | | ||
| + | the physical machine on which the VM was deployed is located in the EU, meaning that the visited websites must comply with GDPR | ||
| + | | ||
| + | we did not install any extension on the browser that would stop the websites from storing or reading cookies | ||
| + | | ||
| + | There are no overlaps for the above datasets, and the apps are from a wide range of functionalities, | ||
| + | | ||
| + | we conduct the measurements from three different locations (Europe (DE), North America (US), and Asia (JP)) to account for possible geographical differences | ||
| + | | ||
| + | We used the same method to create the browser profile for our experiment crawls that we utilized in the pre-study. | ||
| + | | ||
| + | Tranco list of top sites [18] (snapshot 77PX) | ||
| + | | ||
| + | We crawl using two variants of Puppeteer [4] controlling Chromium 80: a lower-realism naive variant and a higher-realism stealth variant. | ||
| + | | ||
| + | we rely on a fully-featured, | ||
| + | | ||
| + | ODIN uses university IP addresses (one per profile) and a /24 subnet from a research-friendly, | ||
| + | | ||
| + | We now evaluate Muzeel across browsers: Chrome, Edge, Firefox, and Brave. | ||
| + | | ||
| + | Our initial sample set was the 5000 domains with the most requests to the third party, as indicated by the Referer field in the CDN's third party logs. | ||
| + | | ||
| + | Results shown are for Firefox (v91) for later comparison, since only Firefox has client-side support for ORIGIN Frame. | ||
| + | | ||
| + | Client caches are intentionally deleted between measurements in all of our active measurements to prevent bias from caching effects | ||
| + | | ||
| + | We scraped Hansa 17 times between late 2015 and mid-2017, collecting a total of 332,795 pages amounting to 39.5 GB of data. | ||
| + | | ||
| + | We perform our analysis on websites which have been crawled in all snapshots (i.e., their intersection) and contain at least one Publisher ID. There are over 191,000 such websites. | ||
| + | | ||
| + | We successfully conducted the four parallel crawls for 99.31% of the 20K websites. | ||
| + | | ||
| + | We run all crawls in the US to minimize the impact of the EU GDPR and do not interact with cookie banners. | ||
| + | | ||
| + | we redid the crawling on the same 10K domains in September 2022 (11-month gap) and applied our system to this data set by following the same procedure. | ||
| + | | ||
| + | clients repeatedly accessed onion services and the top 150 accessed websites according to the Tranco ranking [68], in parallel. | ||
| + | | ||
| + | Clients also run on an isolated Docker container that executes a 0.4.7.10 Tor process and issues requests to Tor via the Python libraries selenium [58] and tbselenium [3] | ||
| + | | ||
| + | we used the PeeringDB snapshot from July 24, 2024, and CAIDA' | ||
| + | | ||
| + | Since OpenWPM only supports Firefox and, unlike Chrome, Firefox does not expose the partitioned_key field in its cookie storage, we modified BannerClick to operate in Chrome' | ||
| + | | ||
| + | We use OpenWPM [24] to conduct a combination of stateful and stateless crawls aiming to observe the interplay of the setter and the sender website | ||
| + | | ||
| + | we joined the top one million websites from Tranco [49] with SimilarWeb, a web traffic estimator | ||
| + | | ||
| + | Each website was opened in a Chrome instance with two installed custom Chrome extensions. | ||
| + | | ||
| + | All website visits took place on Linux virtual machines in Google Cloud' | ||
| + | | ||
| + | between December 2023 and December 2024, 667 new apps were added, 212 apps were removed, and 49 apps were renamed | ||
| + | |||
| + | QUOTES NOT LOCATED, printed in full (7): | ||
| + | | ||
| + | We re-crawled the profile page of each user in the above dataset in February 2011 | ||
| + | | ||
| + | In order to measure the stability of these amplifiers we probed them periodically for 13 weeks to understand how many were still located at the same IP and misconfiged. | ||
| + | | ||
| + | the browser is launched with a clean user profile (i.e., no cookies or cached content) | ||
| + | | ||
| + | we crawled Google Play again in all fifteen languages for one day in February 2022 with the queries we obtained and used in our 2020 crawling. | ||
| + | | ||
| + | Each persona was associated with a unique browser running on an isolated virtual machine with a unique IP address. | ||
| + | | ||
| + | We conduct four parallel crawls: two with third-party cookies enabled (3P-Allowed) and two with third-party cookies blocked (3P-Blocked). | ||
| + | | ||
| + | We restrict the list to sites that appear in all of the top 100k lists over this period to avoid having our results affected by list churn | ||
| + | </ | ||
| + | |||
| + | **Unedited output of the agreement check**, including every disagreement between the two coders. | ||
| + | |||
| + | < | ||
| + | design: | ||
| + | coder A (excerpt packets): out_pins/ | ||
| + | coder B (full text, blind): out_pins/ | ||
| + | |||
| + | reliability sample: 30 papers drawn from the 250 | ||
| + | pairs available: | ||
| + | |||
| + | ============================================================================== | ||
| + | PER-PIN AGREEMENT (n=30 papers) | ||
| + | ============================================================================== | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | ALL PINS 86/120 (71.7%) 98/120 (81.7%) | ||
| + | |||
| + | -- repeatKind | ||
| + | exact 25/30 (83.3%) | ||
| + | | ||
| + | |||
| + | -- "all four fixed", | ||
| + | A says all four fixed: 3/30 | ||
| + | B says all four fixed: 4/30 | ||
| + | | ||
| + | |||
| + | ============================================================================== | ||
| + | DIRECTION OF DISAGREEMENT — does the excerpt coder over- or under-call " | ||
| + | ============================================================================== | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | ============================================================================== | ||
| + | EVERY DISAGREEMENT, | ||
| + | ============================================================================== | ||
| + | |||
| + | 2013/ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | 2015/ | ||
| + | | ||
| + | |||
| + | 2015/ | ||
| + | | ||
| + | | ||
| + | |||
| + | 2016/ | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | 2018/ | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | 2019/ | ||
| + | | ||
| + | |||
| + | 2020/ | ||
| + | | ||
| + | |||
| + | 2022/ | ||
| + | | ||
| + | | ||
| + | |||
| + | 2022/ | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | 2023/ | ||
| + | | ||
| + | | ||
| + | |||
| + | 2023/ | ||
| + | | ||
| + | |||
| + | 2024/ | ||
| + | | ||
| + | | ||
| + | |||
| + | 2025/ | ||
| + | | ||
| + | |||
| + | 2025/ | ||
| + | | ||
| + | |||
| + | 2025/ | ||
| + | | ||
| + | |||
| + | 2025/ | ||
| + | | ||
| + | |||
| + | 2026/ | ||
| + | | ||
| + | |||
| + | 2026/ | ||
| + | | ||
| + | | ||
| + | |||
| + | 2026/ | ||
| + | | ||
| + | | ||
| + | |||
| + | 2026/ | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | papers with at least one disagreement: | ||
| + | |||
| + | ============================================================================== | ||
| + | VALUE DISTRIBUTION, | ||
| + | ============================================================================== | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | </ | ||
| [[design: | [[design: | ||
| + | |||
| + | ===== Markup sweep, 2026-09-17 ===== | ||
| + | |||
| + | Mechanical rendering repair only: a fresh live raw/XHTML export of 188 pages was checked with '' | ||
| + | |||
| + | ===== Quote-check refresh, 2026-09-22 ===== | ||
| + | |||
| + | **What was wrong.** '' | ||
| + | stored renderings and the excerpt packet — and had **no '' | ||
| + | 2026-09-04 '' | ||
| + | 73.1% of quotes unlocatable in '' | ||
| + | '' | ||
| + | patched that day to report a **RESCUED** verdict. This checker was never patched, so | ||
| + | §18.5' | ||
| + | site was using. It has now been patched the same way and re-run. | ||
| + | |||
| + | < | ||
| + | $ node scripts/ | ||
| + | exact match: | ||
| + | whitespace-collapsed match: | ||
| + | loose match: | ||
| + | spliced match (de-spliced): | ||
| + | RESCUED from the PDF: 35 6.5% | ||
| + | NOT FOUND: | ||
| + | | ||
| + | </ | ||
| + | |||
| + | ^ Figure ^ Was ^ Is ^ Why ^ | ||
| + | | pin codings | 1,000 | 1,000 | population unchanged — no coding was touched | | ||
| + | | checked (non-empty quote) | 536 | 536 | unchanged | | ||
| + | | exact | 489 | 489 | unchanged | | ||
| + | | whitespace-collapsed | 0 | 0 | unchanged | | ||
| + | | loose | 2 | 2 | unchanged | | ||
| + | | de-spliced (6-word shingles, ≥80%) | 3 | 3 | unchanged | | ||
| + | | rescued from the PDF | — | **35** | new verdict; all 35 were inside the old 42 | | ||
| + | | located by no mode | **42** (7.8%) | **7** (1.3%) | 42 = 35 + 7 exactly; nothing else moved | | ||
| + | |||
| + | **The hand-audited fabrication rate is unchanged at 0.2%, and this run is evidence | ||
| + | for it rather than a replacement of it.** The 42 had already been read by three | ||
| + | sub-agents, who called 42 of the 43 faithful de-splices of real sentences and found | ||
| + | **one** real defect — a quote from the wrong part of {[singanamalla2022_respect]} — | ||
| + | and **zero** fabrications. The fallback now locates **35 of those same 42** in a | ||
| + | second rendering, by a script that has never seen the hand verdicts. It contradicts | ||
| + | none of them. The 7 that remain are all inside the hand-audited set and all carry the | ||
| + | verdict //faithful de-splice// | ||
| + | and 9.1% before it, it is an upper bound produced by an instrument, and the rate is | ||
| + | still the hand-read **0.2% (1 of 536)**. | ||
| + | |||
| + | **The mutation test was re-run and its pin moved.** The guard recorded in §18.11 was | ||
| + | //"an injected fabricated quote raises NOT FOUND from 42 to 43"//, which pinned the | ||
| + | literal 42. Injecting a fabricated sentence into one coding of | ||
| + | '' | ||
| + | script raises NOT FOUND **from 7 to 8**, drops '' | ||
| + | the fabricated sentence in full in the unlocated list. The pin in §18.11 has been | ||
| + | updated to 7 → 8 and the old value recorded beside it, because a guard that still | ||
| + | asserts a superseded number is a guard that will pass for the wrong reason. | ||
| + | |||
| + | **The fallback was verified to be doing work, not failing silently.** '' | ||
| + | swallows a subprocess failure and returns '' | ||
| + | and look like a clean result. The run therefore prints the number of papers for which | ||
| + | no '' | ||
| + | hand against '' | ||
| + | (//"to visit the Alexa top 50k web domains"//, | ||
| + | verbatim in the PDF) and '' | ||
| + | (//" | ||
| + | this script would now be a measured zero. | ||
| + | |||
| + | **Scope of this edit.** §18.5' | ||
| + | §18.11 mutation-test pin, and the two '' | ||
| + | its published output — both regenerated from the committed files rather than edited. | ||
| + | '' | ||
| + | **not** re-run: no coding, no population and no agreement figure is touched by this | ||
| + | change, and the quote check feeds none of them. Nothing on | ||
| + | [[: | ||
| + | No '' | ||
| + | |||
| + | ^ Item ^ Value ^ | ||
| + | | Date | 2026-09-22, unsupervised | | ||
| + | | Command | '' | ||
| + | | Artifacts | '' | ||
| + | | Script change | '' | ||
| + | | Source fragment | '' | ||
| + | | Reviewers | one '' | ||
| + | | Reviewer finding accepted | The pass caught that this section' | ||
| + | | Pages saved | this page only | | ||
provenance/design/longitudinal.1788468217.txt.gz · Last modified: by karel.kubicek.claude
