Writing a plugin: three notifier events of seven, a logo URL the code refuses, stale counts and docstrings #405
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#405
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908dand postulo.wiki at de1f52467, reading the plugin author's documentation againstpostulo.pluginsandnotifications/base.py. Related to #106 and #264 (both closed), which made a plugin's logo a file inside its package, served as sanitised SVG or a re-encoded PNG; this issue lists the documentation and docstrings those two left describing the earlier design, and the other places where the same wiki page has fallen behind the code.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The consent example inCONTRIBUTING.mdis now at:560(the import) and:584(postulo.plugins.consent.access_token).The page a plugin author works from, Writing a plugin, disagrees with the code in six places, and the docstrings beside the logo code disagree with it in four more. Two of the wiki's errors produce a plugin that does the wrong thing without any error the author sees: a notifier written from the event list, and a manifest copied from the example.
1. The notifier events
Writing-a-plugin.md:404-405saysEVENTS"is every one of them:reminder_due,capture_received,went_quiet".notifications/base.py:29-37defines seven: those three plusposting_closing,status_changed,interview_scheduledandoffer_recorded. A notifier that switches onnotification.eventfrom the documented list drops four kinds.2. The logo in the manifest example
Writing-a-plugin.md:1045giveslogo="https://example.org/your-plugin/logo.png", and:1062says "logonames an image to show beside your plugin's name. Nothing renders it yet." The page's own A logo, if you have one (:881onwards) says the opposite: a file name inside the package, served by Postulo, and "There is no way to give a URL". The code agrees with that section:raw_bytes(plugins/logos.py:105-106) refuses any name holding a separator or starting with a dot ("a logo is a file name, not a path"),logo_forturns the refusal into the initials tile, and the only trace is a warning in the administrator's log. An author who copies the example gets no logo and no explanation.3. The logo's size limit
Writing-a-plugin.md:906says "Under 5 MB in, under 1 MB stored."MAX_BYTES(plugins/logos.py:45) is2 * 1024 * 1024, checked inraw_bytes(plugins/logos.py:115-116) before anything is decoded, and an SVG is further held toMAX_SVG_BYTES, 512 KiB (core/pictures.py:244,:297). A 3 MB raster file, allowed by the page, is refused the same silent way as the URL above. Only "under 1 MB stored" matches (MAX_STORED_BYTES,plugins/logos.py:54).4. The logo docstrings still describe the design before #106 and #264
plugins/base.py:174-176(Manifest): "Nothing renders it yet; the field exists so that shipping one (#106) is a matter of pointing at an image". It is rendered, and it is a file name in the package.plugins/logos.py:21-25(module docstring): "Raster only … The bytes are decoded and written out again as PNG"._render(plugins/logos.py:145-169) keeps an SVG as a sanitised SVG and says SVG "is the preferred form".plugins/logos.py:47: "What a resolved logo is: the PNG bytes, and the plugin they belong to." The comment directly under it (:48-50) says the cache holds bytes and a content type since #264; the first line was left behind.plugins/logos.py:121(logo_for): "The logo as a square PNG". It returns(bytes, content type), the type may beimage/svg+xml, and_rendercallspictures.as_storedwithoutsquare=True, so a raster mark keeps its own shape.5. The shipped plugins, counted three ways
plugins/holds seventeen plugin packages, and each callsshipped().Writing-a-plugin.md:842says "Fourteen of them across eight kinds", and its tree (:846-859) omitsbrowser/,gdpr/andwebhook/.:1097says of the plugins that useshipped(): "There are six of them".docs/PLAN.md:181says "fifteen", and its tree (:184-186) omitsgdpr/andwebhook/.6. Installing from the interface
Writing-a-plugin.md:1536-1537says "Installing from the interface — an uploaded package, a catalogue — is a later step on the roadmap", while:1357(From the interface) describes exactly that on Server settings → Plugins.7. The consent example imports past the surface
CONTRIBUTING.md:558hasfrom postulo.plugins.base import Consent, FieldSpec, and:582sends the reader topostulo.plugins.consent.access_token(connection). Official plugins may import onlypostulo.plugins.api, which exports all three names (plugins/api.py:152,:159,:184);test_a_shipped_plugin_imports_the_surface_or_something_written_downintests/test_plugin_surface.pyfails a shipped plugin that follows the example, and the published check inpostulo.plugins.testingdoes the same for a plugin written elsewhere.postulo.wikiWriting-a-plugin.md:404-405,:842-860,:906,:1045,:1062,:1097,:1536-1537;docs/PLAN.md:181-186;CONTRIBUTING.md:558,:582;plugins/base.py:174-176;plugins/logos.py:21-25,:47,:121.logos.pythat SVG is refused when it is the preferred form.EVENTSrather than enumerating it, or list all seven with their labels.logo="logo.svg"and replace the sentence at:1062with a pointer to A logo, if you have one.:906say 2 MB for a raster file and 512 KB for an SVG (or raiseMAX_BYTESto match the page, deliberately).logos.pymodule docstring, the comment at:47and thelogo_fordocstring to describe a sanitised SVG or a re-encoded PNG at its own shape, and theManifestdocstring to say the logo is a file name in the package, served by Postulo.:1536-1537.CONTRIBUTING.md:from postulo.plugins.api import Consent, FieldSpec, access_token, and callaccess_token(connection).Done when
tests/test_wiki_surface.pychecks that every key ofnotifications.base.EVENTSappears on Writing a plugin, that every plugin package underplugins/appears in its tree, and that nologo=on the page holds a separator; all three fail today.MAX_BYTESandMAX_SVG_BYTES.test_a_logo_is_a_file_name_and_never_a_pathintests/test_plugin_logos.pyincludes a URL among its names, so the refusal the wiki documents stays true.plugins/base.pyorplugins/logos.pysays "Nothing renders it yet", "Raster only" or "square PNG", andCONTRIBUTING.mdimportsConsent,FieldSpecandaccess_tokenfrompostulo.plugins.api.Fixed in
54aeedaf9on main.