wolfhece._plugin_trust ====================== .. py:module:: wolfhece._plugin_trust .. autoapi-nested-parse:: Plugin trust store — persists per-plugin approval decisions. Trust levels ------------ ``BUILTIN`` The plugin ships inside ``wolfhece/_builtin_plugins/``. Always trusted, no user action required. ``APPROVED`` The user has explicitly approved the plugin *in its current state*: the SHA-256 digest of ``companion.py`` + ``plugin.toml`` matches the stored value. ``CHANGED`` The user previously approved the plugin, but at least one of the two tracked files has changed since then. Re-approval is required. ``UNKNOWN`` The plugin has never been seen; the user has not yet been asked. Usage example ------------- :: from wolfhece._plugin_trust import get_default_store, TrustStatus store = get_default_store() status = store.get_status(plugin_info) if status in (TrustStatus.UNKNOWN, TrustStatus.CHANGED): # --- show approval dialog to the user --- store.approve(plugin_info) The trust file is a human-readable JSON document stored in the OS user-config directory (no extra dependencies required): * **Windows** — ``%APPDATA%\wolfhece\trusted_plugins.json`` * **Linux / macOS** — ``$XDG_CONFIG_HOME/wolfhece/trusted_plugins.json`` (defaults to ``~/.config/wolfhece/``) Module Contents --------------- .. py:data:: _logger .. py:data:: BUILTIN_PLUGINS_DIR :type: pathlib.Path .. py:class:: TrustStatus(*args, **kwds) Bases: :py:obj:`enum.Enum` .. autoapi-inheritance-diagram:: wolfhece._plugin_trust.TrustStatus :parts: 1 :private-bases: Approval status of a discovered plugin. .. py:attribute:: BUILTIN .. py:attribute:: APPROVED .. py:attribute:: CHANGED .. py:attribute:: UNKNOWN .. py:function:: compute_plugin_hash(plugin_dir: pathlib.Path) -> str Return a SHA-256 hex digest that covers ``companion.py`` + ``plugin.toml``. Both file names are included in the digest so that renaming a file without changing its content still invalidates the hash. The files are processed in a deterministic order so the result is stable across platforms. :param plugin_dir: Root directory of the plugin (must contain both files). :return: 64-character lowercase hex string. .. py:function:: _user_config_dir() -> pathlib.Path Return the wolfhece user-config directory without importing extras. .. py:class:: TrustStore(trust_file: pathlib.Path | None = None) Persist per-plugin approval decisions in a local JSON file. Each entry maps a plugin *slug* (``PluginInfo.name``) to a record with: * ``hash`` — SHA-256 digest at the time of approval * ``approved_at`` — ISO-8601 UTC timestamp * ``display_name`` — for human readability only * ``version`` — version string at the time of approval :param trust_file: Path to the JSON trust file. Defaults to ``/wolfhece/trusted_plugins.json``. .. py:attribute:: _trust_file :type: pathlib.Path :value: None .. py:attribute:: _data :type: dict[str, dict] .. py:method:: _load() -> dict[str, dict] .. py:method:: _save() -> None .. py:method:: get_status(info: wolfhece._plugin_loader.PluginInfo) -> TrustStatus Return the :class:`TrustStatus` for *info*. The check is performed in this order: 1. If the plugin path is inside :data:`BUILTIN_PLUGINS_DIR` → :attr:`~TrustStatus.BUILTIN`. 2. If no record exists for ``info.name`` → :attr:`~TrustStatus.UNKNOWN`. 3. If the stored hash matches the current digest → :attr:`~TrustStatus.APPROVED`. 4. Otherwise → :attr:`~TrustStatus.CHANGED`. .. py:method:: approve(info: wolfhece._plugin_loader.PluginInfo) -> None Record user approval for the *current* state of *info*. Computes the hash of ``companion.py`` + ``plugin.toml`` and stores it alongside a timestamp and human-readable metadata. .. py:method:: revoke(name: str) -> None Remove any stored approval for the plugin *name*. After revocation :meth:`get_status` returns :attr:`~TrustStatus.UNKNOWN` for that slug. .. py:method:: get_approved_at(name: str) -> str | None Return the ISO-8601 timestamp of the last approval, or ``None``. .. py:property:: trust_file :type: pathlib.Path Path to the backing JSON file. .. py:data:: _default_store :type: TrustStore | None :value: None .. py:function:: get_default_store() -> TrustStore Return the process-wide :class:`TrustStore` (created on first call). .. py:function:: reset_default_store(trust_file: pathlib.Path | None = None) -> TrustStore Replace the default store — useful for testing. :param trust_file: Custom path; ``None`` resets to the system default. :return: The new :class:`TrustStore` instance.