From 5f3bebc1d406b1ff00ba56c6da6e85ceeb7c4a0d Mon Sep 17 00:00:00 2001 From: gavinbee <29419542+gavinbee@users.noreply.github.com> Date: Thu, 6 Aug 2026 01:17:13 -0400 Subject: [PATCH] Make Faker the documented standard for fixture pseudonymization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deck-eval-parser has been doing this properly for a while — seeded Faker, locale en_CA, constrained vocabularies for the domain terms Faker doesn't know, generator script beside the fixture it writes. Nothing said so, so the next repo to need fake people had no reason to land in the same place, and officials-admin nearly grew a second scheme for its rules-oracle fixtures. Records the convention and the reasoning behind each rule, because the rules look arbitrary until you know what breaks without them: unseeded fixtures make diffs meaningless and failures unreproducible; mapping a real value to more than one fake value silently destroys the referential integrity that is often the thing under test; and the real-to-fake mapping is a re-identification table, so it belongs with local secrets rather than in git. Two points that are easy to get backwards. Prefer generating synthetic data outright — pseudonymizing real data is only warranted when its real structure is the point, such as reproducing a legacy system's behaviour. And when that does apply, read only the columns you need: personal data never fetched cannot leak, and it reduces review to checking a column list rather than inspecting a diff. Also states plainly that pseudonymized is not anonymous, since dates and category combinations re-identify people inside a club of a few hundred. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 18 ++++++++++++++++++ CONTRIBUTING.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 6d255f8..adf03a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -96,6 +96,24 @@ commits rather than amending already-pushed ones. Treat such CSV/PDF as local sample data and gitignore it, or scrub before commit. - Report any exposure privately via the repo's Security tab, not a public issue. +**Test fixtures use [Faker](https://faker.readthedocs.io/)** (`faker>=30.0`, locale `en_CA`). +This is the standard — don't hand-roll a substitution scheme. + +- **Seed it** (`Faker.seed(n)`) and record the seed, so the same seed and Faker version + reproduce the fixture byte for byte. +- **Same real value → same fake value**, keyed on a stable identifier, so references between + rows still line up. Keep any real→fake mapping **out of git**. +- **Faker doesn't know swimming.** Roles, club codes and credential names come from a + constrained vocabulary in the generator, not from Faker. +- **Put the generator beside the fixture** it produces, so regenerating is one documented + command. +- **Prefer generating synthetic data outright.** Pseudonymize real data only when its real + structure is the point — a correctness oracle, say — and then read only the columns you + need, so personal data is never fetched in the first place. + +Reference implementation: +[`deck-eval-parser/tests/fixtures/form_field/make_synthetic_fixture.py`](https://github.com/swimblocks/deck-eval-parser/blob/main/tests/fixtures/form_field/make_synthetic_fixture.py). + ## 8. Creating a new repo Use [`scripts/create-repo.sh`](scripts/create-repo.sh) — **never** the GitHub UI. It applies diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bce045f..ebcef00 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,6 +120,40 @@ Rationale and the cross-repo rollout are in - Never commit real personal data (officials' names/emails, club contact lists). Treat any such CSV/PDF as local sample data and gitignore it, or scrub before committing. +### Test fixtures: Faker is the standard + +Our data is about real volunteers and real minors, so tests can't just use a trimmed-down +copy of production. Every repo needing realistic-looking people in fixtures uses +[Faker](https://faker.readthedocs.io/) — `faker>=30.0`, locale `en_CA`. Don't invent a +per-repo substitution scheme; a scheme nobody else recognises is a scheme nobody else audits. + +The rules, and why each exists: + +- **Seed Faker and record the seed.** Unseeded fixtures change every run, so a diff tells you + nothing and a failure can't be reproduced. Same seed plus same Faker version gives identical + output; if a Faker major bump shifts it, regenerate deliberately and say so in the commit. +- **Map each real value to one fake value, consistently**, keyed on something stable — a + registry ID, not a name. Otherwise rows that referenced each other in the real data stop + doing so in the fixture, silently destroying the thing under test. +- **Keep any real→fake mapping out of git.** It's useful for debugging a fixture row, and it is + a re-identification table, so it belongs with your local secrets. +- **Faker doesn't know swimming.** Roles, club codes, credential and meet names come from a + constrained vocabulary in the generator. Faker names a person; it does not name an + `Inspector of Turns Evaluation #2`. +- **Keep the generator beside the fixture it writes**, with a `--help`, so regenerating is one + command a reviewer can run rather than prose they have to trust. + +**Prefer fully synthetic data** — generate the shape you need and there's no privacy problem +at all. Pseudonymize real data only when its real structure is the point, such as reproducing +a legacy system's behaviour where real dates and real credential combinations are exactly +what's under test. Then read only the columns you need: personal data you never fetched is +personal data you cannot leak, and it turns the reviewer's job into checking a column list +rather than inspecting a diff. + +Dates and category combinations can still re-identify someone inside a small club even with +names removed. Say so in the fixture's README rather than implying pseudonymization made the +data anonymous. + ## Repo settings (org-wide policy) Every SwimBlocks repo's GitHub settings are governed by