WI-072: The obligation, before the content
WI-072: The obligation, before the content
Wikidata is CC0 and obliges nothing. Wikipedia is CC BY-SA, which obliges us to name the source and the licence wherever the material appears — a rendering requirement, not a footnote, and the reason ADR-0016 made provenance per field rather than per record. One game row can legitimately mix a CC0 designer list with a CC BY-SA description, and only one of those puts a line on the page.
None of that content exists yet, and this ships anyway. An obligation added after the content is one somebody has to remember. Added before, it is a thing the content has to satisfy.
Three guards
| The database | A CHECK refuses any CC-BY-SA-4.0 row with no source_url. A row we may not render is not a row we may hold — and the loader is only one of several ways a row arrives. |
| The API | requiredCredits throws rather than returning an empty list. Returning nothing there is the dangerous answer: the page renders the material with no credit and nothing looks wrong. |
| The screen | Credits render as links naming the source and the licence. A bare link is not attribution, and neither is a mention without one. |
Credits are grouped per (source, url), not per field: three fields from one Wikipedia article is one credit, and a page repeating the same link three times reads like a bug.
Where the prose will come from is not decided
/api/ and /w/ are both Disallowed on en.wikipedia.org — verified verbatim, not inferred — so the REST API is out under the same reading that produced ADR-0023. Unlike WDQS there is a genuine sanctioned alternative, so a second exemption would be a convenience rather than a necessity, and it is not taken.
/wiki/{Title} is permitted, and ADR-0016 discourages scraping rendered pages. dumps.wikimedia.org has no robots.txt at all, but the abstract dumps have been retired and the remaining prose artefact is pages-articles at roughly 22GB. That is a real cost on somebody’s connection, so it is a decision rather than a default.
Definition of done
- A CC BY-SA row with no source URL cannot be stored.
- The same row is accepted once it can be credited.
- CC0 is unaffected.
- The game route returns credits, present and empty rather than absent.
- The screen names the source and the licence, and links to it.
- Every credit renders, not just the first.
- Each check proven able to fail.
- Gate green.
Verification
pnpm --filter @tabletop/db testpnpm --filter @tabletop/catalogue testpnpm --filter @tabletop/mobile testpnpm gateSeeded failures
| Seed | Bit |
|---|---|
| Unattributable material returns no credit instead of refusing | 1 |
| CC0 treated as requiring attribution | 4 |
| One credit per field rather than per source | 2 |
| Credits collected but never rendered | 3 |
| The licence dropped from the credit line | 1 |
| Only the first credit rendered | 1 |
| The credit is not a link | 1 |
| The database constraint dropped | 1 |
| A NUL byte in a source file | 1 |
A NUL byte compiled clean
While editing attribution.ts, a script replaced one space in a template literal with \0. TypeScript compiled it, eslint passed it, vitest ran it, and every test went green. grep was the only thing that noticed, by refusing to search a “binary file”.
Nothing in the gate looked at bytes. scripts/check-source-bytes.mjs now runs first and does.
Two migrations, one of which did nothing
The first attempt hand-wrote 0007_attribution_constraint.sql. Drizzle reads migrations/meta/_journal.json, not the directory — so migrate skipped it entirely and reported success. The constraint was absent and a probe inserted an unattributable row without complaint.
Generated from schema.ts instead, which keeps the journal and the snapshot in step, and the drift probe able to see it (ADR-0020).
Not done here
Any actual Wikipedia prose. The three guards are in place and there is nothing yet for them to guard.