plugin.install wrote the module and its grant to the Plug-ins folder,
attached the plug-in's settings and cache and turned a disabled plug-in
on before the registry's last approval check. When another session
revoked the grant, or recorded one for other bytes, after the command
recorded its own grant and before that check, the install was refused
but left the new module and grant in the folder for the next launch.
A plug-in another session installed under the same id between the
command's checks and the install took no part in the decision, so the
install replaced it without `replace: true`.
- registry::install_checked records the grant (or forgets it) and
installs the plug-in as one step, after a caller check that sees the
plug-in installed under the id and its grant; a refusal changes
nothing.
- plugin.install commits through it before any store write, refusing
with the existing messages when another plug-in now needs `replace`
or the grant its approval came from changed. The store writes, the
put-back on a failed write and the disabled-list update now run after
the commit; their logic is unchanged.
- The grant call-site audit also counts registry::install_checked.
Docs: docs/plugins.md, docs/target-app-parity.md.
registry::grant takes a GrantToken, which the engine makes after a
person's approval and when it loads grants.json. registry::install,
install_bytes and folder loads refuse a module that lists permissions no
recorded grant gives this very module (Error::NeedsApproval), and leave
the plug-in under its id and the revision as they were. A folder load's
approve callback only narrows what installs. A module without
permissions, or with an empty list, installs as before.
A folder load in a session with a plug-in store records the store's
grant for each module it approves, so those modules still load.
docs/plugins.md says what the approval does not protect against: native
code linked into the app, and another program running as the same user,
which can edit the Plug-ins folder and its unsigned grants.json.