Catalogue tool: every plugin's catalogues are stamped "same license as Postulo (AGPL-3.0-or-later)", whatever the plugin's licence #417

Closed
opened 2026-10-01 10:46:33 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d and postulo-helloworld at 19f0bcc29, reading the catalogue headers of the reference plugin and then running the tool's own check against a copy of it.

Checked again on 2026-10-01 against main at 0a5cad587: still present.

postulo-messages extract writes the same second line into every catalogue it produces, in whichever project it runs: "This file is distributed under the same license as Postulo (AGPL-3.0-or-later)." The line is true for the core and, by coincidence, for the four AGPL plugins. It is false for postulo-helloworld, which is MIT precisely so that it can be copied, and for any third-party plugin whose author chose another licence. The author cannot correct it: the tool's check treats a corrected line as a stale catalogue, and the next extract writes the AGPL line back.

  • Where:
    • core/messages_tool.py:416, in dump(): the licence line is a string literal. The line above it already takes the project's name from project().describe(subject) (core/messages_tool.py:71), so the header names the plugin and then Postulo's licence.
    • core/messages_tool.py:95, in use(): pyproject.toml is read for project.name only; project.license is not looked at.
    • core/messages_tool.py:617 and :634, in cmd_extract() and _without_dates(): the check compares the whole file apart from the two date headers, so the comment block is part of what must match.
    • core/messages_tool.py:545-546 and :556, in header_for(): the same kind of literal. Every plugin's catalogue carries Project-Id-Version: Postulo, Report-Msgid-Bugs-To: https://source.tiagoagueda.com/postulo/postulo/issues and X-Generator: postulo scripts/messages.py, although a plugin's issues belong in its own repository and no plugin has a scripts/messages.py.
    • In postulo-helloworld: pyproject.toml:7 (license = "MIT"), LICENSE, README.md:90-93 ("MIT, so that copying it into your own plugin is a decision you never have to think about"), and line 2 of all 68 files matching src/postulo_helloworld/locale/*/LC_MESSAGES/django.po. README.md:49 is the step that tells an author to run the extract.
  • What happens:
    1. An author copies postulo-helloworld, keeps MIT or picks another licence, and runs uv run postulo-messages extract as the README says. All 68 catalogues state AGPL-3.0-or-later.
    2. The author corrects the line by hand in one catalogue. tool.cmd_extract(check=True) now returns 1 and lists that file as out of date, so test_the_catalogues_are_current in the copied tests/test_catalogues.py:43-44 fails. Confirmed on a copy of the snapshot with only line 2 of fr_FR changed.
    3. Running the extract again restores the AGPL line. Confirmed on the same copy.
  • Why it matters: the package contradicts itself file by file: LICENSE and the metadata say one thing, 68 shipped files say another. Anyone reusing the translations, or a packager checking licences per file, has to guess which statement holds, and the plugin's author has no way to ship a correct one short of dropping the check. The reference plugin exists to be copied, so the mistake is copied with it. The bug-report address has the same cost on a smaller scale: a translator who finds a wrong source string in a plugin is sent to the core's tracker.
  • Fix: let the header describe the project the tool is pointed at.
    • Give Project a licence read in use() from project.license (the SPDX string, or the text of the older table form), and have dump() write "the same license as the <name> package", followed by the licence in brackets when the project declares one. Keeping today's wording when project().is_postulo avoids rewriting the core's own catalogues.
    • In header_for(), take Project-Id-Version from the project's name and Report-Msgid-Bugs-To from the project's own URLs in pyproject.toml (falling back to today's value for the core), and name the generator postulo-messages.
    • Then repin and re-extract in postulo-helloworld, so its catalogues stop naming AGPL. The other plugin repositories (postulo-apprise, postulo-dav, postulo-imap, postulo-paperless) will fail their own test_the_catalogues_are_current on the next repin until they are re-extracted as well; that is the header changing, not a regression.

Done when

  • A test in tests/test_messages_tool.py points use() at a temporary project whose pyproject.toml says license = "MIT" and asserts that the comment block written by dump() names that project and MIT and does not contain "AGPL"; it fails today.
  • A test in the same file asserts that, for that project, header_for() no longer reports bugs to the core's tracker when the project declares its own URL.
  • In postulo-helloworld, tests/test_catalogues.py asserts that no catalogue under src/postulo_helloworld/locale/ names a licence other than the one in pyproject.toml, and the 68 catalogues are re-extracted so that it passes.
Found on 2026-09-29 in a read-only review of postulo at 95348908d and postulo-helloworld at 19f0bcc29, reading the catalogue headers of the reference plugin and then running the tool's own check against a copy of it. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. `postulo-messages extract` writes the same second line into every catalogue it produces, in whichever project it runs: "This file is distributed under the same license as Postulo (AGPL-3.0-or-later)." The line is true for the core and, by coincidence, for the four AGPL plugins. It is false for `postulo-helloworld`, which is MIT precisely so that it can be copied, and for any third-party plugin whose author chose another licence. The author cannot correct it: the tool's check treats a corrected line as a stale catalogue, and the next extract writes the AGPL line back. - **Where:** - `core/messages_tool.py:416`, in `dump()`: the licence line is a string literal. The line above it already takes the project's name from `project().describe(subject)` (`core/messages_tool.py:71`), so the header names the plugin and then Postulo's licence. - `core/messages_tool.py:95`, in `use()`: `pyproject.toml` is read for `project.name` only; `project.license` is not looked at. - `core/messages_tool.py:617` and `:634`, in `cmd_extract()` and `_without_dates()`: the check compares the whole file apart from the two date headers, so the comment block is part of what must match. - `core/messages_tool.py:545-546` and `:556`, in `header_for()`: the same kind of literal. Every plugin's catalogue carries `Project-Id-Version: Postulo`, `Report-Msgid-Bugs-To: https://source.tiagoagueda.com/postulo/postulo/issues` and `X-Generator: postulo scripts/messages.py`, although a plugin's issues belong in its own repository and no plugin has a `scripts/messages.py`. - In `postulo-helloworld`: `pyproject.toml:7` (`license = "MIT"`), `LICENSE`, `README.md:90-93` ("MIT, so that copying it into your own plugin is a decision you never have to think about"), and line 2 of all 68 files matching `src/postulo_helloworld/locale/*/LC_MESSAGES/django.po`. `README.md:49` is the step that tells an author to run the extract. - **What happens:** 1. An author copies `postulo-helloworld`, keeps MIT or picks another licence, and runs `uv run postulo-messages extract` as the README says. All 68 catalogues state AGPL-3.0-or-later. 2. The author corrects the line by hand in one catalogue. `tool.cmd_extract(check=True)` now returns 1 and lists that file as out of date, so `test_the_catalogues_are_current` in the copied `tests/test_catalogues.py:43-44` fails. Confirmed on a copy of the snapshot with only line 2 of `fr_FR` changed. 3. Running the extract again restores the AGPL line. Confirmed on the same copy. - **Why it matters:** the package contradicts itself file by file: `LICENSE` and the metadata say one thing, 68 shipped files say another. Anyone reusing the translations, or a packager checking licences per file, has to guess which statement holds, and the plugin's author has no way to ship a correct one short of dropping the check. The reference plugin exists to be copied, so the mistake is copied with it. The bug-report address has the same cost on a smaller scale: a translator who finds a wrong source string in a plugin is sent to the core's tracker. - **Fix:** let the header describe the project the tool is pointed at. - Give `Project` a `licence` read in `use()` from `project.license` (the SPDX string, or the `text` of the older table form), and have `dump()` write "the same license as the `<name>` package", followed by the licence in brackets when the project declares one. Keeping today's wording when `project().is_postulo` avoids rewriting the core's own catalogues. - In `header_for()`, take `Project-Id-Version` from the project's name and `Report-Msgid-Bugs-To` from the project's own URLs in `pyproject.toml` (falling back to today's value for the core), and name the generator `postulo-messages`. - Then repin and re-extract in `postulo-helloworld`, so its catalogues stop naming AGPL. The other plugin repositories (`postulo-apprise`, `postulo-dav`, `postulo-imap`, `postulo-paperless`) will fail their own `test_the_catalogues_are_current` on the next repin until they are re-extracted as well; that is the header changing, not a regression. ## Done when - A test in `tests/test_messages_tool.py` points `use()` at a temporary project whose `pyproject.toml` says `license = "MIT"` and asserts that the comment block written by `dump()` names that project and MIT and does not contain "AGPL"; it fails today. - A test in the same file asserts that, for that project, `header_for()` no longer reports bugs to the core's tracker when the project declares its own URL. - In `postulo-helloworld`, `tests/test_catalogues.py` asserts that no catalogue under `src/postulo_helloworld/locale/` names a licence other than the one in `pyproject.toml`, and the 68 catalogues are re-extracted so that it passes.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:46:33 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#417
No description provided.