Internal plugins are the administrator's to switch: hide them on Settings → Plugins by default, behind a check mark, and take the person's switch away #200

Closed
opened 2026-09-13 08:24:50 +00:00 by tiagoagueda · 0 comments
Owner

The change

Plugins of provenance internal — the ones shipped inside Postulo — are the
administrator's to switch on and off, not the person's. A regular user cannot enable or
disable them. On Settings → Plugins they are hidden by default, behind a check mark
that shows them; with the mark off, the page lists the plugins that were installed on
the instance — official and custom — and nothing else.

What happens today

policy.decide (plugins/policy.py:109-149) treats every governed plugin the same way:
instance switch, then the administrator's row for this person, then the administrator's
row for everybody, then the person's own Profile.plugins_off. A plugin with no
administrator row is theirs — Decision(theirs=True) — whatever its provenance, and
set_choice (:165) writes the person's choice into plugins_off. So Several
telephone numbers
, Several websites, the email notifier, the local store: every
built-in feature and service is a checkbox a person can untick on their own settings
page today.

policy.overview (:184) lists every plugin of a governed kind that is offered, with
its provenance read once from installing.status() (internal, official, custom,
uploaded — plugins/provenance.py:37-47), and settings/plugins.html draws one row
per plugin with a checkbox, disabled where the row is not theirs. Nothing on the page
filters by provenance, and nothing in the policy knows provenance at all.

The administrator's side already exists: Server settings → People → Plugins
(PersonPluginsView, server_views.py:1000) writes PluginPolicy rows — available,
forced on, forced off, unavailable — per person or for everybody. That is the mechanism
the change leans on; the person's own choice is what it takes away for internal plugins.

What a fix has to settle

  • Where provenance meets the decision. decide needs the plugin's provenance —
    provenance.of_builtin for a built-in, of_record for an installed one — and, for an
    internal plugin, must never reach _their_choices: theirs=False, on decided by
    the administrator's rows or the default, decided_by saying which. set_choice then
    refuses by itself, which is what stops a hand-written POST.
  • The plugins_off entries that already name an internal plugin. Anybody who has
    unticked Several telephone numbers has "phone-numbers" in plugins_off today. Once
    the person's choice no longer counts for internal plugins, that entry is inert; either a
    data migration drops those names, or the reader ignores them and they linger. Inert
    data that looks like a choice is worse than none.
  • The page. Rows of provenance internal are left out unless the mark is on. The
    mark is a decision of its own: a query parameter (?internal=1, bookmarkable, not
    remembered) or a stored preference (remembered, one more field). Internal rows, when
    shown, carry no checkbox — the reason beside them says whose decision it is, in the
    existing why slot.
  • uploaded is neither official nor custom. A wheel somebody uploaded that matches
    nothing a repository signed (provenance.py:44-47) is still a plugin installed on the
    instance and still a person's to switch. The change says official and custom; the
    page should list uploaded ones too, or the distinction is by evidence the person cannot
    see. Say which.
  • Seven test files switch a feature off through plugins_off —
    test_phone_numbers.py, test_phones.py, test_web_links.py,
    test_profile_sections.py, test_mail_transport.py, test_plugin_policy.py,
    test_settings_plugins.py — and every one of them does it to an internal plugin. They
    move to the administrator's route, a PluginPolicy row in FORCED_OFF, which
    test_recovery_number.py:190 already does. test_plugin_policy.py and
    test_settings_plugins.py also assert the person can choose, and change to assert
    the new rule.
  • The wiki says the opposite in three places. Tracking applications (:455) says a
    feature can be switched off "for yourself under Settings → Plugins, or for the whole
    instance by an administrator"; Writing a plugin says a feature is "switched on and off
    through the same page, the same policy and the same explanation as everything else"
    (:995) and that off "is offered … the person is told how many are kept back" (:1036);
    the plugin descriptions themselves ("Switched off, Postulo shows and uses the primary
    number only") are written to the person. The pages and the descriptions say the new
    truth: an administrator's switch, not yours.
  • The kept-back sentences stay right. "Some websites are kept and not shown, because
    Several websites is switched off" is still true when an administrator did the
    switching; the who is already in the decision, and the page can say it.

Classification

Breaking change: a person who switched a built-in feature off for themselves loses that
switch, and the state they chose either migrates to nothing or lingers. Say so in the
changelog.

## The change Plugins of provenance **internal** — the ones shipped inside Postulo — are the administrator's to switch on and off, not the person's. A regular user cannot enable or disable them. On *Settings → Plugins* they are **hidden by default**, behind a check mark that shows them; with the mark off, the page lists the plugins that were installed on the instance — official and custom — and nothing else. ## What happens today `policy.decide` (`plugins/policy.py:109-149`) treats every governed plugin the same way: instance switch, then the administrator's row for this person, then the administrator's row for everybody, then the person's own `Profile.plugins_off`. A plugin with no administrator row is *theirs* — `Decision(theirs=True)` — whatever its provenance, and `set_choice` (`:165`) writes the person's choice into `plugins_off`. So *Several telephone numbers*, *Several websites*, the email notifier, the local store: every built-in feature and service is a checkbox a person can untick on their own settings page today. `policy.overview` (`:184`) lists every plugin of a governed kind that is *offered*, with its provenance read once from `installing.status()` (`internal`, `official`, `custom`, `uploaded` — `plugins/provenance.py:37-47`), and `settings/plugins.html` draws one row per plugin with a checkbox, disabled where the row is not theirs. Nothing on the page filters by provenance, and nothing in the policy knows provenance at all. The administrator's side already exists: *Server settings → People → Plugins* (`PersonPluginsView`, `server_views.py:1000`) writes `PluginPolicy` rows — available, forced on, forced off, unavailable — per person or for everybody. That is the mechanism the change leans on; the person's own choice is what it takes away for internal plugins. ## What a fix has to settle - **Where provenance meets the decision.** `decide` needs the plugin's provenance — `provenance.of_builtin` for a built-in, `of_record` for an installed one — and, for an internal plugin, must never reach `_their_choices`: `theirs=False`, `on` decided by the administrator's rows or the default, `decided_by` saying which. `set_choice` then refuses by itself, which is what stops a hand-written POST. - **The `plugins_off` entries that already name an internal plugin.** Anybody who has unticked *Several telephone numbers* has `"phone-numbers"` in `plugins_off` today. Once the person's choice no longer counts for internal plugins, that entry is inert; either a data migration drops those names, or the reader ignores them and they linger. Inert data that looks like a choice is worse than none. - **The page.** Rows of provenance `internal` are left out unless the mark is on. The mark is a decision of its own: a query parameter (`?internal=1`, bookmarkable, not remembered) or a stored preference (remembered, one more field). Internal rows, when shown, carry no checkbox — the reason beside them says whose decision it is, in the existing *why* slot. - **`uploaded` is neither official nor custom.** A wheel somebody uploaded that matches nothing a repository signed (`provenance.py:44-47`) is still a plugin installed on the instance and still a person's to switch. The change says *official and custom*; the page should list uploaded ones too, or the distinction is by evidence the person cannot see. Say which. - **Seven test files switch a feature off through `plugins_off`** — `test_phone_numbers.py`, `test_phones.py`, `test_web_links.py`, `test_profile_sections.py`, `test_mail_transport.py`, `test_plugin_policy.py`, `test_settings_plugins.py` — and every one of them does it to an internal plugin. They move to the administrator's route, a `PluginPolicy` row in `FORCED_OFF`, which `test_recovery_number.py:190` already does. `test_plugin_policy.py` and `test_settings_plugins.py` also assert the person *can* choose, and change to assert the new rule. - **The wiki says the opposite in three places.** *Tracking applications* (`:455`) says a feature can be switched off "for yourself under *Settings → Plugins*, or for the whole instance by an administrator"; *Writing a plugin* says a feature is "switched on and off through the same page, the same policy and the same explanation as everything else" (`:995`) and that off "is offered … the person is told how many are kept back" (`:1036`); the plugin descriptions themselves ("Switched off, Postulo shows and uses the primary number only") are written to the person. The pages and the descriptions say the new truth: an administrator's switch, not yours. - **The kept-back sentences stay right.** "Some websites are kept and not shown, because *Several websites* is switched off" is still true when an administrator did the switching; the *who* is already in the decision, and the page can say it. ## Classification Breaking change: a person who switched a built-in feature off for themselves loses that switch, and the state they chose either migrates to nothing or lingers. Say so in the changelog.
tiagoagueda added this to the 0.3.0 milestone 2026-09-13 08:24:50 +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#200
No description provided.