feedBack/schema/plugin.schema.json
barlind 04db4a233b
chore: address plugin manifest review
Signed-off-by: barlind <tobias@barlind.se>
2026-06-18 00:41:56 -07:00

262 lines
12 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/slopsmith/slopsmith/blob/main/schema/plugin.schema.json",
"title": "Slopsmith plugin manifest",
"description": "Schema for plugins/<id>/plugin.json. See docs/plugin-manifest.md for prose. Contributions must use AGPL-3.0-only. Capability metadata follows capability-pipelines.v1 and is intentionally additive.",
"type": "object",
"additionalProperties": true,
"required": ["id", "name"],
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z0-9_.-]+$",
"description": "Plugin identifier. Snake_case is preferred for bundled plugins. Used to namespace localStorage, the plugin screen id (plugin-<id>), the backend logger (slopsmith.plugin.<id>), and the diagnostics bundle directory. Reverse-DNS form (com.example.foo) is supported via the load_sibling encoding."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name shown in UI surfaces."
},
"version": {
"type": ["string", "null"],
"description": "Plain semver string. Advisory only; the plugin loader does not consume this."
},
"private": {
"type": "boolean",
"description": "Advisory metadata for plugin authors. Not consumed by the loader."
},
"standards": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"uniqueItems": true,
"description": "Versioned standards the plugin participates in. Use capability-pipelines.v1 for native capability metadata and plugin-runtime-idempotent.v1 only when repeated hydration cannot duplicate runtime work."
},
"capability_api": {
"type": "object",
"properties": {
"standard": { "const": "capability-pipelines.v1" },
"version": { "const": 1 }
},
"additionalProperties": false,
"description": "Optional explicit capability API version marker. The standards array is the preferred compact form."
},
"bundled": {
"type": "boolean",
"description": "True for plugins shipped in-tree with Slopsmith. Used by tooling that distinguishes built-in plugins from user-installed ones."
},
"type": {
"type": "string",
"enum": ["visualization"],
"description": "Role hint (slopsmith#36). 'visualization' makes the plugin eligible for the viz picker; must pair with a window.slopsmithViz_<id> factory."
},
"license": {
"type": "string",
"description": "SPDX identifier. Contributions must use AGPL-3.0-only.",
"enum": ["AGPL-3.0-only"]
},
"nav": {
"type": "object",
"additionalProperties": false,
"required": ["label", "screen"],
"properties": {
"label": { "type": "string", "minLength": 1 },
"screen": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*$" }
},
"description": "Optional navigation metadata. Plugin UI declares stable ui contributions for attribution."
},
"screen": {
"$ref": "#/$defs/pluginRelpath",
"description": "Relative path to HTML mounted at #plugin-<id>."
},
"script": {
"$ref": "#/$defs/pluginRelpath",
"description": "Relative path to JS loaded in global scope on page load. Wrap your code in an IIFE."
},
"styles": {
"type": "string",
"minLength": 1,
"pattern": "^assets/(?!.*\\.\\.)[^\\\\?#]+$",
"description": "Plugin-root-relative path under assets/ (e.g. 'assets/plugin.css') to a compiled, preflight-off Tailwind stylesheet the frontend injects as a <link>. Must stay under assets/ with no '..', backslash, query, or fragment. See docs/plugin-styles.md."
},
"routes": {
"$ref": "#/$defs/pluginRelpath",
"description": "Relative path to Python module exporting setup(app, context). See docs/plugin-manifest.md."
},
"tour": {
"$ref": "#/$defs/pluginRelpath",
"description": "Relative path to tour JSON for the in-app onboarding tour."
},
"settings": {
"type": "object",
"additionalProperties": false,
"properties": {
"html": {
"$ref": "#/$defs/pluginRelpath",
"description": "Relative path to settings-panel HTML."
},
"server_files": {
"type": "array",
"items": {
"type": "string",
"not": { "pattern": "^/|^[a-zA-Z]:|\\\\|(^|/)\\.\\.(/|$)|//|(^|/)\\.(/|$)|^\\." },
"description": "Relpath under context['config_dir']. No abs paths, no '..', no '//', no './', no leading dotfiles, no backslashes. Mirrors the runtime _validate_relpath rules in plugins/__init__.py so a schema-valid manifest is also load-time-valid. Trailing '/' denotes a directory (recurse)."
},
"uniqueItems": true,
"description": "Opt-in for Settings export/import (slopsmith#113). Files included in user-triggered backups. See docs/plugin-manifest.md."
}
}
},
"diagnostics": {
"type": "object",
"additionalProperties": false,
"properties": {
"server_files": {
"type": "array",
"items": {
"type": "string",
"not": { "pattern": "^/|^[a-zA-Z]:|\\\\|(^|/)\\.\\.(/|$)|//|(^|/)\\.(/|$)|^\\." }
},
"uniqueItems": true,
"description": "Files copied verbatim into plugins/<id>/<relpath> inside the diagnostics bundle. Path rules match the settings.server_files pattern (no abs paths, no '..', no '//', no './', no leading dotfiles, no backslashes)."
},
"callable": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]*:[A-Za-z_][A-Za-z0-9_]*$",
"description": "<module>:<function> resolved via load_sibling at export time. Called with {'plugin_id', 'config_dir'} dict; return dict/list/bytes/str."
}
},
"description": "Opt-in for the Export Diagnostics bundle (slopsmith#166). See docs/plugin-diagnostics.md."
},
"settings_schema": {
"type": "object",
"description": "Redaction-safe settings metadata used by capability diagnostics and backup/import tooling. Do not store actual settings values here."
},
"capabilities": {
"type": "object",
"propertyNames": { "$ref": "#/$defs/domainName" },
"additionalProperties": { "$ref": "#/$defs/capabilityDeclaration" },
"description": "Native capability-pipelines.v1 declarations keyed by capability domain."
},
"ui_contributions": {
"type": "object",
"propertyNames": { "$ref": "#/$defs/domainName" },
"additionalProperties": { "$ref": "#/$defs/contributionList" },
"description": "Native UI contribution declarations keyed by UI domain."
},
"ui": {
"type": "object",
"propertyNames": { "$ref": "#/$defs/domainName" },
"additionalProperties": { "$ref": "#/$defs/contributionList" },
"description": "Alias for UI contribution declarations used by current first-party plugin manifests."
},
"runtime_domains": {
"type": "object",
"propertyNames": { "$ref": "#/$defs/domainName" },
"additionalProperties": { "$ref": "#/$defs/domainDeclaration" },
"description": "Reserved runtime-domain metadata. New native declarations use capabilities."
},
"domains": {
"type": "object",
"propertyNames": { "$ref": "#/$defs/domainName" },
"additionalProperties": { "$ref": "#/$defs/domainDeclaration" },
"description": "Runtime-domain declaration alias. New native declarations use capabilities."
},
"privileged_capabilities": {
"type": "array",
"items": { "type": "object" },
"description": "Manifest-derived privileged workflow summaries. The privileged-capabilities host validates detailed semantics at runtime."
},
"privileged_compatibility_bridges": {
"type": "array",
"items": { "type": "object" },
"description": "Diagnostics-only bridge declarations for privileged compatibility surfaces."
}
},
"allOf": [
{
"$comment": "type=visualization requires a script (the window.slopsmithViz_<id> factory)",
"if": { "properties": { "type": { "const": "visualization" } }, "required": ["type"] },
"then": { "required": ["script"] }
}
],
"$defs": {
"pluginRelpath": {
"type": "string",
"minLength": 1,
"not": { "pattern": "^/|^[a-zA-Z]:|\\\\|(^|/)\\.\\.(/|$)|//|(^|/)\\.(/|$)|^\\.|[?#]" }
},
"domainName": {
"type": "string",
"minLength": 1,
"pattern": "^[A-Za-z0-9_.:-]+$"
},
"capabilityDeclaration": {
"type": "object",
"properties": {
"roles": {
"type": "array",
"items": {
"enum": ["owner", "coordinator", "provider", "observer", "requester", "transformer", "handler", "validator", "short-circuiter", "contributor"]
},
"uniqueItems": true
},
"commands": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
"operations": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
"requests": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
"observes": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
"emits": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
"events": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
"kind": { "enum": ["command", "provider-coordinator", "event", "diagnostic", "privileged"] },
"mode": { "enum": ["active", "optional", "legacy-shim", "disabled"] },
"compatibility": { "enum": ["none", "shim-allowed", "degrade-noop", "required", "legacy-window-shim"] },
"ownership": { "enum": ["exclusive-owner", "multi-provider", "observer-only", "requester-only", "privileged", "diagnostic-only"] },
"safety": { "enum": ["safe", "privileged", "sensitive", "diagnostic-only"] },
"order": {
"type": "object",
"properties": {
"fixed": { "type": "boolean" },
"before": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
"after": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }
},
"additionalProperties": false
},
"provider_policy": { "type": "object" },
"description": { "type": "string" },
"summary": { "type": "string" },
"version": { "const": 1 }
},
"additionalProperties": false
},
"domainDeclaration": {
"oneOf": [
{
"type": "object",
"properties": {
"role": { "type": "string", "minLength": 1 },
"roles": { "type": "array", "items": { "type": "string", "minLength": 1 } },
"ownership": { "enum": ["exclusive-owner", "multi-provider", "observer-only", "requester-only", "privileged", "diagnostic-only"] },
"safety": { "enum": ["safe", "privileged", "sensitive", "diagnostic-only"] },
"legacy_source": { "type": "string", "minLength": 1 }
},
"additionalProperties": true
},
{ "type": "array" }
]
},
"contributionList": {
"type": "array",
"items": {
"type": "object",
"required": ["id"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"region": { "type": "string", "minLength": 1 },
"label": { "type": "string" },
"order": { "type": ["number", "integer", "string"] }
},
"additionalProperties": true
}
}
}
}