{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.fablepool.org/v1/widget-manifest.json", "title": "FablePool Widget Manifest", "description": "Manifest for a sandboxed interactive widget. Widgets are untrusted community code: they run inside a sandboxed iframe served from an isolated origin, communicate only via the versioned postMessage API, and never receive network access. The manifest is reviewed as content; entry/assets are integrity-pinned.", "type": "object", "required": ["schema", "id", "name", "version", "entry", "api_version", "sandbox", "accessibility", "license"], "additionalProperties": false, "properties": { "schema": { "const": "fablepool.widget/1" }, "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$" }, "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?$" }, "description": { "type": "string", "maxLength": 2000 }, "entry": { "type": "string", "pattern": "^[A-Za-z0-9._/-]+\\.html$", "description": "Package-relative path to the iframe entry document." }, "api_version": { "const": 1, "description": "Version of the host<->widget postMessage protocol." }, "sandbox": { "type": "object", "required": ["network"], "additionalProperties": false, "properties": { "network": { "const": false, "description": "Widgets never get network access; this field exists to make the guarantee explicit and machine-checkable." }, "allow": { "type": "array", "uniqueItems": true, "items": { "enum": ["pointer-events", "keyboard", "audio", "fullscreen"] }, "default": [] }, "max_storage_kb": { "type": "integer", "minimum": 0, "maximum": 1024, "default": 64 } } }, "params_schema": { "type": "object", "description": "A JSON Schema (2020-12) validating the 'params' object that content documents pass to this widget.", "properties": { "type": { "const": "object" } }, "required": ["type"] }, "assets": { "type": "array", "maxItems": 200, "items": { "type": "object", "required": ["path", "sha256", "media_type"], "additionalProperties": false, "properties": { "path": { "type": "string", "pattern": "^[A-Za-z0-9._/-]+$", "maxLength": 300 }, "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, "media_type": { "type": "string", "maxLength": 100 } } } }, "accessibility": { "type": "object", "required": ["keyboard_operable", "screen_reader_summary"], "additionalProperties": false, "properties": { "keyboard_operable": { "type": "boolean" }, "screen_reader_summary": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "Text exposed to assistive technology describing what the widget does." }, "reduced_motion_supported": { "type": "boolean", "default": false } } }, "license": { "enum": ["CC-BY-SA-4.0", "CC-BY-4.0", "CC0-1.0", "MIT", "AGPL-3.0-only"] }, "authors": { "type": "array", "maxItems": 20, "items": { "type": "object", "required": ["name"], "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 120 }, "url": { "type": "string", "pattern": "^https://", "maxLength": 500 }, "username": { "type": "string", "maxLength": 64 } } } } } }