Files
shopdb-flask/shopdb/plugins/manifest_schema.json
cproudlock d178726687 ADR-013 Phase 0: plugin lifecycle groundwork
Additive, zero-risk-to-running-sites prep for the plugin catalog. No
distribution or lean-build behavior yet; fixes latent bugs and adds the
declarative + validate tooling later phases build on.

Fixes:
- upgrade_all_plugins iterates registry.get_all(); only adopted plugins are
  migrated. Removes the phantom hasattr(registry, 'list_installed') probe
  that always fell through to migrating every folder on disk (unadopted DDL
  ran with full DB rights on every deploy).
- Reverse-dependency checks on uninstall/disable read dependencies from the
  manifest on disk via _installed_dependents, so an installed-but-unloaded or
  disabled dependent is counted. Uninstall blocks on any installed dependent;
  disable blocks on an enabled dependent.
- _sort_by_dependencies detects a dependency cycle (back edge in the DFS) and
  raises PluginDependencyError instead of looping or dropping a plugin.

New:
- flask plugin validate <name>: manifest loads + name match, manifest-schema
  check, core_version admits the framework contract, declared dependencies
  exist on disk. No new dependency (lightweight checker); schema ships in the
  package at shopdb/plugins/manifest_schema.json (docs/ is stripped on
  publish). The check caught that provides is an object, not an array.
- flask plugin apply-profile <file>: declarative install AND enable of a
  chosen plugin set plus its hard-dependency closure, in dependency order,
  idempotent. Replaces the hand-ordered runbook sequences that could enable a
  plugin that was never installed. deploy/site-profile.example.json template.
- Dockerfile header corrected (all 13 catalog plugins, not "eleven core").

10 new lifecycle tests (reverse-deps from disk, cycle detection, upgrade-all
scope, profile closure, schema, all 13 manifests match schema). 1018 pass,
naming green.
2026-07-18 18:08:34 -04:00

67 lines
2.6 KiB
JSON

{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://shopdb-flask/plugin-manifest.schema.json",
"title": "shopdb-flask plugin manifest",
"description": "Shape of a plugin's manifest.json (ADR-002, ADR-013). Known fields are typed; additionalProperties is permitted so existing manifests and future additive fields validate unchanged.",
"type": "object",
"additionalProperties": true,
"required": ["name", "version", "description"],
"properties": {
"name": {
"type": "string",
"description": "Machine name. Must equal the plugin directory name. Lowercase, concatenated.",
"pattern": "^[a-z][a-z0-9]*$"
},
"version": {
"type": "string",
"description": "Plugin version (semver-ish string)."
},
"description": {
"type": "string",
"description": "One-line human description."
},
"display_name": {
"type": "string",
"description": "Human label for UIs. Falls back to a title-cased name."
},
"author": {
"type": "string"
},
"dependencies": {
"type": "array",
"description": "Hard dependencies: plugin names that must be installed and enabled first. A name may carry an optional PEP440 range (e.g. 'employees>=1.1'); the runtime loader uses name-only semantics.",
"items": { "type": "string" }
},
"optional_dependencies": {
"type": "array",
"description": "Soft couplings: plugins that unlock extra behavior when present, but do not block install/enable. Loader-ignored; adopt/list only WARN when unmet.",
"items": { "type": "string" }
},
"tier": {
"type": "string",
"description": "core = mandatory, lifecycle refuses to uninstall/disable it; optional = catalog plugin (default).",
"enum": ["core", "optional"]
},
"core_version": {
"type": "string",
"description": "PEP440 specifier set the framework contract version must satisfy, e.g. '>=0.12.0,<1.0.0'."
},
"api_prefix": {
"type": "string",
"description": "URL prefix for the plugin blueprint, e.g. '/api/printedparts'. Must be unique across enabled plugins."
},
"default_enabled": {
"type": "boolean",
"description": "Whether install leaves the plugin enabled. Plugins that provision extra tables ship false so a site opts in."
},
"provides": {
"type": "object",
"description": "Capabilities this plugin advertises (e.g. asset_type, a features list). Free-form object read by catalog/UI, not the loader."
},
"settings": {
"type": "object",
"description": "Plugin-scoped default settings metadata."
}
}
}