# ADR-003: Plugin distribution model - **Status:** ACCEPTED - **Date:** 2026-05-08 - **Deciders:** cproudlock ## Context Sister sites adopting shopdb-flask need a way to: 1. Install the framework 2. Pick which plugins they want 3. Build their own plugins for site-specific equipment 4. Receive updates to both framework and plugins Today, every plugin lives in the `plugins/` directory of the framework repo. There is no separation between framework code and plugin code, no install / uninstall, and no way for a site to develop a plugin without forking the whole repo. Three viable distribution models: | Model | How a site installs a plugin | |---|---| | **In-tree only** | Fork the framework repo, add plugin under `plugins/`, run their own deploy. No separation. | | **Pip-installable plugins** | Each plugin published as a Python package. Site does `pip install shopdb-printers shopdb-network` etc. Discovery via Python entry points. Framework loads any installed plugin that registers itself. | | **Git-based plugins** | Each plugin lives in its own git repo. Site clones / submodules into `plugins//`. Loader picks them up from the directory. | ## Decision **PROPOSED:** Use a **hybrid model** with two clearly-labeled paths. 1. **Bundled plugins**: a small set of plugins ships with the framework, in-tree at `plugins/`. These are the reference implementations and the default install (printers, computers, network, equipment, usb, notifications). A site that wants only what's bundled needs no extra work. 2. **External plugins**: sister sites or third parties build plugins in their own git repos. The site running the framework drops the plugin into `plugins//` (clone, submodule, or symlink) and runs `flask plugin install `. No pip packaging required for v1. Pip-installable plugins (Python entry-point discovery) are deferred to v2. The complexity is not justified until at least two sites are running their own plugins. ## Consequences ### Positive - v1 is simple: filesystem-based discovery (already implemented in `shopdb/plugins/loader.py`), works for both bundled and external plugins. - Sites can develop plugins without changing the framework repo. - The `plugins/` directory is already the canonical location, so no architectural change is needed. ### Negative / cost - No automatic update path for external plugins. Sites must `git pull` in each plugin directory manually. Acceptable for v1; revisit when plugin count grows. - Multiple plugin authors writing in parallel can collide on namespace (e.g., two plugins both registering an `AssetType` named "Equipment"). Need a naming policy: plugin names and asset-type names should be prefixed with the site or org if not in the bundled set. ### Neutral - The existing in-tree pattern keeps working. This decision just formalizes it and clarifies the path for outside-the-tree plugins. ## Alternatives considered 1. **Pip-installable from day one.** Cleaner for the long term but adds packaging, entry-point registration, and CI steps. Premature for current scale (one site running, no sister-site plugins yet). 2. **In-tree only forever.** Forces every site to fork. Doesn't scale beyond two or three sites. 3. **Submodules only.** Forces git-submodule discipline on every adopting site. Submodules are notoriously fiddly. Rejected. ## Open questions - For external plugins, should there be a manifest field (`source_url`) declaring where the plugin can be cloned from, so `flask plugin install` could pull it for the site? Defer; manual clone is fine for v1. - Naming convention for non-bundled plugin directory names: prefix with site? (`gea-wjsf-shipping`)? Adopt if and when we hit a name collision. ## References - `shopdb/plugins/loader.py` (filesystem discovery) - `shopdb/plugins/cli.py` (plugin install / uninstall command) - ADR-001 (defines what plugins target) - ADR-002 (defines plugin version compatibility)