feat(attribution): per-file provenance for the forked theme's bundled art #213

Merged
peterswimm merged 1 commit from chore/third-party-asset-attribution into main 2026-10-16 19:50:37 +00:00
Owner

Follow-on to #212. That PR deleted the archived copies of this art; this one records whose the live copies are.

udg/products/wp-themes/toilville-fse/ is a fork of MotoPress's Prime FSE. Its art is third-party GPL work sitting in the live tree under ordinary names — check.svg, quote.svg, feature-1.svg — and nothing in the repo said whose it was except upstream's own readme.txt. That is how it came to be mistaken for unattributed mystery assets recovered from an archived design system (#207). Each audit had to rediscover it. This records it by path, so the next one measures instead of guessing.

Scope

In scope: bundled media provenance for the forked WordPress theme. Six files, +576/−0.

udg/config/attribution.yml — the canon. Authored, reviewed in PRs, in git. 57 bundled files across five works:

Work Files Holder Licence
prime-fse-ui-art 33 MotoPress GPL-2.0-or-later
prime-fse-icon-font 4 MotoPress GPL-2.0-or-later
prime-fse-screenshot 1 MotoPress GPL-2.0-or-later
sora-font 4 The Sora Project Authors OFL-1.1
prime-fse-demo-photos 15 various photographers CC0-1.0 (nine source URLs)

These are upstream's own claims, transcribed from readme.txt, and the file says so. We record who asserts what and where — not an independent verification of MotoPress's authorship. The generator enforces only that every bundled media file is claimed by exactly one work.

udg/products/wp-themes/toilville-fse/ATTRIBUTION.soon — the generated projection. Emitted by udg/generators/attribution.py, wired into the just build and just check recipes.

To be precise about where the gate bites in CI: no workflow runs just check wholesale (udg-dist-check.yml runs three named generator checks), so the enforcement path in CI is the pytest lane — Test Generator / test runs PYTHONPATH=. pytest from the root, which collects generator/tests/unit/test_attribution.py, and its coverage and staleness tests fail the build. Verified: 19 passed under that exact invocation. I deliberately did not add a step to .forgejo/workflows/ — the lint-workflows lane runs --strict, which exits non-zero on pre-existing soft warnings elsewhere in the repo, so any workflow-touching PR goes red through no fault of its own. Worth adding a named gate there once that is scoped to changed files.

Per ADR-0002, SOON's only legitimate role in this stack is a regenerated, never-authored projection — so provenance (a human claim) is authored in YAML and only the index is generated, and check fails if the .soon was hand-edited. It holds exactly one SOON table and no comment lines, because soon::Table::decode reads every non-empty line as a row; a # header would decode as data. The human-readable notice stays upstream's readme.txt, which ships with the theme — this is the machine-readable index on top of it.

attribution[57]{path,work,holder,license,source,declared_in}:
assets/images/check.svg,prime-fse-ui-art,MotoPress,GPL-2.0-or-later,"https://motopress.com/products/prime-fse/",readme.txt
assets/images/image-2.jpg,prime-fse-demo-photos,"various, released CC0 by the photographers",CC0-1.0,"https://www.pexels.com/photo/silver-imac-on-white-table-7058",readme.txt

udg/generators/_soon.py ports the canonical encoder (bridges/crates/soon, Python home bin/forge_soon.py) because the generators are stdlib-only and cannot import across repos — the same way _miniyaml.py ports a YAML subset instead of taking PyYAML.

Out of scope: includes/tgmpa/ is a bundled third-party PHP library (TGM Plugin Activation) that also arrived with the fork. It is code rather than artwork, so it is not claimed here — worth its own pass if you want bundled code attribution too. No live asset, token, theme or pattern changes: this adds an index and a gate, and touches no art.

Testing

generator/tests/unit/test_attribution.py — 19 new tests: coverage, staleness, two failing controls, no double-claims, upstream recorded, encoder parity vectors, table framing, format-version pin, single-decodable-table.

Verified against failing controls, not just the happy path:

Control Result
clean tree ok — 57 bundled files carry provenance, exit 0
unattributed asset added exit 1, names the file
.soon hand-edited exit 1, says to run just build
claim for a file not on disk exit 1, names the claim

Encoder parity: verified byte-identical to bin/forge_soon.py over the real 57 rows and over the spec's quoting vectors, with matching SOON_FORMAT_VERSION (0.5.0-soon.1). The test pins those vectors and the version, so a drifted port fails instead of emitting a different dialect under the same version. Worth noting: every URL contains a colon and is therefore always quoted — the detail a hand-written SOON file gets wrong.

Full suite: generator/tests/unit → 194 passed, 18 skipped, 3 failed. The 3 are pre-existing test_doctor.py sandbox failures, identical to a clean origin/main baseline run in a separate checkout. No new failures. test_archive_inert.py still passes 5/5 — the canon deliberately avoids naming a dated archive folder, since a build input that does fails that test, and it says so inline.

Impact Statement

Risk: low. Nothing renders from this, and no existing generator, token, theme, pattern or asset is touched. The new .soon file ships inside the theme directory alongside upstream's readme.txt; it is inert data that no build step reads back.

What changes for contributors: just build gains one step, and just check gains one gate. From now on, adding a media file to the forked theme without a provenance entry fails the test lane. That is the intended cost — it is what stops this art going unattributed again. The failure message names the file and points at udg/config/attribution.yml.

One rule it must enforce: Toilville-authored art added to this theme needs its own work entry. Filing it under an upstream work to quiet the gate would re-create exactly the confusion this fixes, so a test asserts no bundled asset is attributed to Toilville LLC.

Licence posture: unchanged and already compliant. MotoPress's attribution has been intact in the theme's readme.txt since the fork landed; this does not fix a compliance gap, it makes the existing position legible per path. Resolves the open provenance question from #207 for the live copies, as #212 did for the archived ones.

Follow-on to #212. That PR deleted the archived copies of this art; this one records whose the live copies are. `udg/products/wp-themes/toilville-fse/` is a fork of MotoPress's Prime FSE. Its art is third-party GPL work sitting in the live tree under ordinary names — `check.svg`, `quote.svg`, `feature-1.svg` — and nothing in the repo said whose it was except upstream's own `readme.txt`. That is how it came to be mistaken for unattributed mystery assets recovered from an archived design system (#207). Each audit had to rediscover it. This records it by path, so the next one measures instead of guessing. ## Scope **In scope: bundled media provenance for the forked WordPress theme.** Six files, +576/−0. **`udg/config/attribution.yml` — the canon.** Authored, reviewed in PRs, in git. 57 bundled files across five works: | Work | Files | Holder | Licence | |---|---|---|---| | `prime-fse-ui-art` | 33 | MotoPress | GPL-2.0-or-later | | `prime-fse-icon-font` | 4 | MotoPress | GPL-2.0-or-later | | `prime-fse-screenshot` | 1 | MotoPress | GPL-2.0-or-later | | `sora-font` | 4 | The Sora Project Authors | OFL-1.1 | | `prime-fse-demo-photos` | 15 | various photographers | CC0-1.0 (nine source URLs) | These are **upstream's own claims**, transcribed from `readme.txt`, and the file says so. We record who asserts what and where — not an independent verification of MotoPress's authorship. The generator enforces only that every bundled media file is claimed by exactly one work. **`udg/products/wp-themes/toilville-fse/ATTRIBUTION.soon` — the generated projection.** Emitted by `udg/generators/attribution.py`, wired into the `just build` and `just check` recipes. To be precise about where the gate bites in CI: no workflow runs `just check` wholesale (`udg-dist-check.yml` runs three named generator checks), so **the enforcement path in CI is the pytest lane** — `Test Generator / test` runs `PYTHONPATH=. pytest` from the root, which collects `generator/tests/unit/test_attribution.py`, and its coverage and staleness tests fail the build. Verified: 19 passed under that exact invocation. I deliberately did not add a step to `.forgejo/workflows/` — the `lint-workflows` lane runs `--strict`, which exits non-zero on pre-existing soft warnings elsewhere in the repo, so any workflow-touching PR goes red through no fault of its own. Worth adding a named gate there once that is scoped to changed files. Per [ADR-0002](https://git.toilville.dev/Toilville/spel/src/branch/main/docs/adr/0002-pin-soon-format-version.md), SOON's only legitimate role in this stack is a regenerated, never-authored projection — so provenance (a human claim) is authored in YAML and only the index is generated, and `check` fails if the `.soon` was hand-edited. It holds exactly one SOON table and no comment lines, because `soon::Table::decode` reads every non-empty line as a row; a `#` header would decode as data. The human-readable notice stays upstream's `readme.txt`, which ships with the theme — this is the machine-readable index on top of it. ``` attribution[57]{path,work,holder,license,source,declared_in}: assets/images/check.svg,prime-fse-ui-art,MotoPress,GPL-2.0-or-later,"https://motopress.com/products/prime-fse/",readme.txt assets/images/image-2.jpg,prime-fse-demo-photos,"various, released CC0 by the photographers",CC0-1.0,"https://www.pexels.com/photo/silver-imac-on-white-table-7058",readme.txt ``` **`udg/generators/_soon.py`** ports the canonical encoder (`bridges/crates/soon`, Python home `bin/forge_soon.py`) because the generators are stdlib-only and cannot import across repos — the same way `_miniyaml.py` ports a YAML subset instead of taking PyYAML. **Out of scope:** `includes/tgmpa/` is a bundled third-party PHP library (TGM Plugin Activation) that also arrived with the fork. It is code rather than artwork, so it is not claimed here — worth its own pass if you want bundled *code* attribution too. No live asset, token, theme or pattern changes: this adds an index and a gate, and touches no art. ## Testing `generator/tests/unit/test_attribution.py` — 19 new tests: coverage, staleness, two failing controls, no double-claims, upstream recorded, encoder parity vectors, table framing, format-version pin, single-decodable-table. **Verified against failing controls, not just the happy path:** | Control | Result | |---|---| | clean tree | `ok — 57 bundled files carry provenance`, exit 0 | | unattributed asset added | exit 1, names the file | | `.soon` hand-edited | exit 1, says to run `just build` | | claim for a file not on disk | exit 1, names the claim | **Encoder parity:** verified byte-identical to `bin/forge_soon.py` over the real 57 rows and over the spec's quoting vectors, with matching `SOON_FORMAT_VERSION` (`0.5.0-soon.1`). The test pins those vectors and the version, so a drifted port fails instead of emitting a different dialect under the same version. Worth noting: every URL contains a colon and is therefore always quoted — the detail a hand-written SOON file gets wrong. **Full suite:** `generator/tests/unit` → **194 passed**, 18 skipped, 3 failed. The 3 are pre-existing `test_doctor.py` sandbox failures, identical to a clean `origin/main` baseline run in a separate checkout. No new failures. `test_archive_inert.py` still passes 5/5 — the canon deliberately avoids naming a dated archive folder, since a build input that does fails that test, and it says so inline. ## Impact Statement **Risk: low.** Nothing renders from this, and no existing generator, token, theme, pattern or asset is touched. The new `.soon` file ships inside the theme directory alongside upstream's `readme.txt`; it is inert data that no build step reads back. **What changes for contributors:** `just build` gains one step, and `just check` gains one gate. From now on, adding a media file to the forked theme without a provenance entry **fails the test lane**. That is the intended cost — it is what stops this art going unattributed again. The failure message names the file and points at `udg/config/attribution.yml`. **One rule it must enforce:** Toilville-authored art added to this theme needs its own work entry. Filing it under an upstream work to quiet the gate would re-create exactly the confusion this fixes, so a test asserts no bundled asset is attributed to Toilville LLC. **Licence posture: unchanged and already compliant.** MotoPress's attribution has been intact in the theme's `readme.txt` since the fork landed; this does not fix a compliance gap, it makes the existing position legible per path. Resolves the open provenance question from #207 for the live copies, as #212 did for the archived ones.
feat(attribution): per-file provenance for the forked theme's bundled art
Some checks failed
Security Scans / Trivy filesystem scan (pull_request) Successful in 59s
Block Validation / validate (pull_request) Successful in 1m49s
Security Scans / Gitleaks secret scan (pull_request) Successful in 54s
Test Generator / lint-workflows (pull_request) Successful in 25s
Test Generator / lint-prose (pull_request) Successful in 30s
Test Generator / test (pull_request) Failing after 1m29s
UDG dist drift gate / dist/ matches dtcg + udg sources (pull_request) Successful in 26s
Copy coverage / Design copy reaching rendered HTML (pull_request) Successful in 55s
PR policy check / policy-gate (pull_request) Successful in 0s
86b05b7999
udg/products/wp-themes/toilville-fse/ is a fork of MotoPress's Prime FSE.
Its art is third-party GPL work sitting in the live tree under ordinary
names -- check.svg, quote.svg, feature-1.svg -- which is how it came to be
mistaken for unattributed mystery assets recovered from an archived design
system (#207, #212). Nothing in the repo said whose it was except upstream's
own readme.txt, so each audit had to rediscover it.

This records it by path, so the next one measures instead of guessing.

udg/config/attribution.yml is the canon: authored, reviewed in PRs, in git.
It transcribes upstream's own claims and says so -- we record who asserts
what and where, not an independent verification of MotoPress's authorship.
57 bundled files across five works: MotoPress's UI art (33), their icon font
(4), their screenshot, Sora under OFL-1.1 (4), and 15 CC0 demo photographs
with the photographer URLs upstream lists.

ATTRIBUTION.soon beside the theme is the generated projection, emitted by
udg/generators/attribution.py into `just build` / `just check`. Per ADR-0002
SOON is only ever a regenerated projection, never authored, so `check` fails
if it was hand-edited. It holds exactly one SOON table and no comment lines,
because soon::Table::decode reads every non-empty line as a row. The human
notice stays upstream's readme.txt, which ships with the theme; this is the
machine-readable index on top of it.

The coverage gate is why it belongs in CI: every bundled media file must be
claimed by exactly one work, so an asset added with no provenance entry
fails the build rather than passing quietly. Toilville-authored art added
here needs its own work entry -- filing it under an upstream work to quiet
the gate is the one thing this must not allow.

udg/generators/_soon.py ports the canonical encoder (bridges/crates/soon,
Python home bin/forge_soon.py) because the generators are stdlib-only and
cannot import across repos, the same way _miniyaml.py ports a YAML subset.
Verified byte-identical to bin/forge_soon.py over the real rows and the
spec's quoting vectors; the test pins those vectors and the format version
so a drifted port fails instead of emitting a different dialect under the
same version. Note every URL contains a colon and is therefore always
quoted -- the detail a hand-written SOON file gets wrong.
peterswimm deleted branch chore/third-party-asset-attribution 2026-10-16 19:50:37 +00:00
Sign in to join this conversation.
No description provided.