From 6a862a16419834e6d4fb6257aebb097e1243239b Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 17 Apr 2026 23:27:17 -0500 Subject: [PATCH] feat(docs): add initial drafts for SEP v1 manifest schema, example manifests, and protocol documentation --- docs/sep_manifest.android.example.json | 45 ++++ docs/sep_manifest.example.json | 50 ++++ docs/sep_manifest.schema.json | 286 +++++++++++++++++++++++ docs/sidecar_extension_protocol_draft.md | 193 +++++++++++++++ 4 files changed, 574 insertions(+) create mode 100644 docs/sep_manifest.android.example.json create mode 100644 docs/sep_manifest.example.json create mode 100644 docs/sep_manifest.schema.json create mode 100644 docs/sidecar_extension_protocol_draft.md diff --git a/docs/sep_manifest.android.example.json b/docs/sep_manifest.android.example.json new file mode 100644 index 0000000..073197a --- /dev/null +++ b/docs/sep_manifest.android.example.json @@ -0,0 +1,45 @@ +{ + "schemaVersion": "1.0.0", + "id": "io.meshchatx.extensions.sample-android-tool", + "name": "Sample Android Tool Extension", + "description": "Reference Android profile manifest draft for SEP v1 planning", + "version": "0.1.0", + "apiVersion": "1.0.0", + "trustLevel": "first_party", + "platformProfile": "android", + "entrypoint": { + "command": "meshchatx-android-extension-runtime", + "args": ["sample_android_tool"], + "workingDirectory": ".", + "environment": {} + }, + "permissions": ["events.subscribe", "messages.read", "notifications.create", "tools.register"], + "events": { + "subscribe": ["message.received", "peer.online"] + }, + "health": { + "startupTimeoutMs": 12000, + "heartbeatIntervalMs": 20000, + "unhealthyAfterMissedHeartbeats": 2 + }, + "limits": { + "maxRequestBytes": 32768, + "maxRequestsPerSecond": 30, + "maxConcurrentRequests": 8 + }, + "android": { + "runtimeMode": "packaged_module", + "allowBackgroundExecution": false, + "maxStartupTimeMs": 12000, + "maxMemoryMb": 256 + }, + "compatibility": { + "meshchatx": ">=4.5.0", + "platforms": ["android-arm64"] + }, + "signing": { + "required": true, + "keyId": "meshchatx-release-key", + "signature": "" + } +} diff --git a/docs/sep_manifest.example.json b/docs/sep_manifest.example.json new file mode 100644 index 0000000..9821d39 --- /dev/null +++ b/docs/sep_manifest.example.json @@ -0,0 +1,50 @@ +{ + "schemaVersion": "1.0.0", + "id": "io.meshchatx.extensions.sample-tool", + "name": "Sample Tool Extension", + "description": "Reference manifest draft for SEP v1 planning", + "version": "0.1.0", + "apiVersion": "1.0.0", + "trustLevel": "trusted_third_party", + "platformProfile": "desktop", + "entrypoint": { + "command": "python3", + "args": ["main.py"], + "workingDirectory": ".", + "environment": { + "PYTHONUNBUFFERED": "1" + } + }, + "permissions": ["events.subscribe", "messages.read", "messages.send", "notifications.create", "tools.register"], + "events": { + "subscribe": ["message.received", "message.sent", "peer.online"] + }, + "health": { + "startupTimeoutMs": 10000, + "heartbeatIntervalMs": 15000, + "unhealthyAfterMissedHeartbeats": 3 + }, + "limits": { + "maxRequestBytes": 65536, + "maxRequestsPerSecond": 50, + "maxConcurrentRequests": 16 + }, + "ui": { + "toolPanels": [ + { + "id": "sample-tool-panel", + "title": "Sample Tool", + "slot": "tools.page.primary" + } + ] + }, + "compatibility": { + "meshchatx": ">=4.5.0", + "platforms": ["linux-x64", "linux-arm64", "windows-x64"] + }, + "signing": { + "required": false, + "keyId": "", + "signature": "" + } +} diff --git a/docs/sep_manifest.schema.json b/docs/sep_manifest.schema.json new file mode 100644 index 0000000..b80a7e2 --- /dev/null +++ b/docs/sep_manifest.schema.json @@ -0,0 +1,286 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://meshchatx.com/schemas/sep_manifest.schema.json", + "title": "MeshChatX Sidecar Extension Protocol Manifest", + "description": "Draft schema for SEP v1 extension manifest files.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "id", + "name", + "version", + "apiVersion", + "trustLevel", + "entrypoint", + "permissions", + "health", + "limits" + ], + "properties": { + "schemaVersion": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "id": { + "type": "string", + "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)+$", + "minLength": 3, + "maxLength": 128 + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 80 + }, + "description": { + "type": "string", + "maxLength": 512 + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+][A-Za-z0-9._-]+)?$" + }, + "apiVersion": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "trustLevel": { + "type": "string", + "enum": ["first_party", "trusted_third_party", "untrusted_local"] + }, + "platformProfile": { + "type": "string", + "enum": ["desktop", "android"] + }, + "entrypoint": { + "type": "object", + "additionalProperties": false, + "required": ["command", "args"], + "properties": { + "command": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "args": { + "type": "array", + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64 + }, + "workingDirectory": { + "type": "string", + "maxLength": 512 + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string", + "pattern": "^[A-Z_][A-Z0-9_]*$" + }, + "additionalProperties": { + "type": "string", + "maxLength": 4096 + }, + "maxProperties": 64 + } + } + }, + "permissions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "messages.read", + "messages.send", + "contacts.read", + "events.subscribe", + "tools.register", + "notifications.create" + ] + }, + "minItems": 0, + "maxItems": 64, + "uniqueItems": true + }, + "events": { + "type": "object", + "additionalProperties": false, + "properties": { + "subscribe": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9]+(?:\\.[a-z0-9]+)+$", + "maxLength": 128 + }, + "maxItems": 256, + "uniqueItems": true + } + } + }, + "health": { + "type": "object", + "additionalProperties": false, + "required": ["startupTimeoutMs", "heartbeatIntervalMs", "unhealthyAfterMissedHeartbeats"], + "properties": { + "startupTimeoutMs": { + "type": "integer", + "minimum": 1000, + "maximum": 120000 + }, + "heartbeatIntervalMs": { + "type": "integer", + "minimum": 1000, + "maximum": 120000 + }, + "unhealthyAfterMissedHeartbeats": { + "type": "integer", + "minimum": 1, + "maximum": 20 + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "required": ["maxRequestBytes", "maxRequestsPerSecond", "maxConcurrentRequests"], + "properties": { + "maxRequestBytes": { + "type": "integer", + "minimum": 1024, + "maximum": 4194304 + }, + "maxRequestsPerSecond": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "maxConcurrentRequests": { + "type": "integer", + "minimum": 1, + "maximum": 1024 + } + } + }, + "ui": { + "type": "object", + "additionalProperties": false, + "properties": { + "toolPanels": { + "type": "array", + "maxItems": 32, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "title", "slot"], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*$", + "maxLength": 128 + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 80 + }, + "slot": { + "type": "string", + "enum": ["tools.page.primary", "tools.page.secondary", "tools.page.advanced"] + } + } + } + } + } + }, + "compatibility": { + "type": "object", + "additionalProperties": false, + "properties": { + "meshchatx": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "linux-x64", + "linux-arm64", + "windows-x64", + "windows-arm64", + "macos-x64", + "macos-arm64", + "android-arm64" + ] + }, + "uniqueItems": true, + "maxItems": 16 + } + } + }, + "android": { + "type": "object", + "additionalProperties": false, + "required": ["runtimeMode", "allowBackgroundExecution"], + "properties": { + "runtimeMode": { + "type": "string", + "enum": ["packaged_module", "approved_runtime"] + }, + "allowBackgroundExecution": { + "type": "boolean" + }, + "maxStartupTimeMs": { + "type": "integer", + "minimum": 1000, + "maximum": 120000 + }, + "maxMemoryMb": { + "type": "integer", + "minimum": 16, + "maximum": 2048 + } + } + }, + "signing": { + "type": "object", + "additionalProperties": false, + "required": ["required", "keyId", "signature"], + "properties": { + "required": { + "type": "boolean" + }, + "keyId": { + "type": "string", + "maxLength": 256 + }, + "signature": { + "type": "string", + "maxLength": 8192 + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "platformProfile": { + "const": "android" + } + }, + "required": ["platformProfile"] + }, + "then": { + "required": ["android"] + } + } + ] +} diff --git a/docs/sidecar_extension_protocol_draft.md b/docs/sidecar_extension_protocol_draft.md new file mode 100644 index 0000000..4f864b6 --- /dev/null +++ b/docs/sidecar_extension_protocol_draft.md @@ -0,0 +1,193 @@ +# Sidecar Extension Protocol Draft (SEP v1) + +This is a draft design for extension support in MeshChatX. + +## Goals + +- Keep extension failures isolated from core messaging runtime. +- Provide a stable, versioned API for trusted and power-user automation. +- Minimize direct access to Reticulum internals, local DB internals, and process memory. +- Support repeatable testing, contract validation, and fuzzing from day one. + +## Non-Goals + +- No in-process plugin execution in v1. +- No arbitrary UI code execution inside the main frontend runtime in v1. +- No remote extension installation pipeline in v1. + +## Runtime Model + +- MeshChatX host discovers extension manifests from a local configured directory. +- Each extension is started as a separate local process. +- Host and extension communicate over a local JSON-RPC channel. +- Host enforces capability checks on every privileged request. +- Host applies lifecycle controls: startup timeout, health checks, backoff restart policy, and hard stop. + +## Platform Profiles + +SEP v1 uses a shared manifest with platform-specific runtime profiles. + +### Desktop Profile (Linux, Windows, macOS) + +- Sidecar process execution is allowed via manifest entrypoint. +- Local IPC transport defaults to Unix domain socket or localhost loopback transport. +- Full `entrypoint` command and args model applies. + +### Android Profile + +- Android support is planned as a constrained profile, not a full desktop-equivalent runtime. +- Host may reject arbitrary executable launch and require approved runtimes or packaged modules. +- Lifecycle contract must tolerate app pause/resume, background restrictions, and process reclaim. +- Extensions should assume tighter CPU/memory quotas and more aggressive timeout enforcement. +- Long-running background behavior should be explicit and policy-gated by host configuration. + +## Trust Model + +Three trust levels are defined in the manifest: + +- `first_party`: shipped and maintained with MeshChatX. +- `trusted_third_party`: explicitly approved by operator policy. +- `untrusted_local`: local experiments with restrictive permissions and stricter limits. + +Even for trusted extensions, API permissions are explicit and least-privilege. + +## API Shape (Draft) + +Host to extension: + +- `sep.initialize` +- `sep.event` +- `sep.health` +- `sep.shutdown` + +Extension to host: + +- `host.get_capabilities` +- `host.messages.send` +- `host.messages.list` +- `host.contacts.list` +- `host.ui.register_tool_panel` +- `host.notifications.toast` + +All methods are version-gated by protocol version and permission checks. + +## Permission Model + +Permissions are stable string capabilities. Initial candidates: + +- `messages.read` +- `messages.send` +- `contacts.read` +- `events.subscribe` +- `tools.register` +- `notifications.create` + +Permissions should be audited in logs with extension id, method name, decision, and reason. + +## UI Integration Constraints + +v1 should avoid arbitrary extension-rendered code in core UI. + +Allowed model: + +- Extension registers metadata for approved host-owned UI slots. +- MeshChatX renders UI components and invokes extension actions through host API. + +This keeps UX consistent and lowers attack surface. + +## Draft Manifest Contract + +See `docs/sep_manifest.example.json` for an initial manifest layout and `docs/sep_manifest.schema.json` for the draft validation contract. + +Required fields: + +- `id` +- `name` +- `version` +- `apiVersion` +- `entrypoint` +- `permissions` +- `trustLevel` +- `health` +- `limits` + +Optional platform section: + +- `platformProfile` +- `android` (required when `platformProfile` is `android`) + +## Error Handling and Resilience + +- Host returns structured errors with stable codes. +- Extension failures do not crash host runtime. +- Host tracks crash loops and disables extensions after policy threshold. +- Startup and request timeouts are mandatory. + +## Observability + +- Per-extension log stream with bounded retention. +- Metrics counters: starts, exits, restarts, failed RPC calls, denied permissions. +- Optional debug tracing mode for protocol troubleshooting. + +## Testing Strategy + +### Contract Tests + +- Validate manifest schema acceptance/rejection. +- Validate capability enforcement per API method. +- Validate version negotiation and downgrade behavior. + +### Integration Tests + +- Start host with a fixture extension process and assert lifecycle transitions. +- Verify extension crash does not affect messaging flows. +- Verify timeout and restart policies. +- For Android profile, simulate pause/resume and process restart during active extension calls. + +### End-to-End Tests + +- Register tool panel metadata and validate host UI representation. +- Send and receive sample messages through extension API path. +- Validate Android profile fallback behavior when sidecar runtime is unavailable. + +### Regression Tests + +- Freeze behavior for error codes and permission-denied responses. +- Snapshot protocol payloads for compatibility checks. + +## Fuzzing Plan + +### Message-Level Fuzzing + +- Fuzz JSON-RPC payload structure, field types, nested depth, and size boundaries. +- Fuzz invalid method names, duplicate ids, malformed params, and unknown capability requests. + +### State Transition Fuzzing + +- Randomize lifecycle sequences: initialize, event storm, health checks, shutdown, restart loops. +- Inject extension process faults during active calls. + +### Boundary Fuzzing + +- Oversized payloads near configured limits. +- High-rate event bursts to verify backpressure controls. +- Invalid UTF-8 and control character edge cases in text fields. +- Android-profile fuzzing for lifecycle churn: rapid foreground/background transitions and delayed heartbeat acknowledgements. + +Fuzzing should run in CI with deterministic seeds plus rotating random seeds for nightly jobs. + +## Open Questions + +- Whether `untrusted_local` should be enabled by default. +- Whether extension signatures are mandatory in v1 or policy-configurable. +- Whether API tokens are needed for local RPC channel or if socket isolation is sufficient. +- Whether Android should permit third-party runtime execution or only first-party packaged extensions in v1. + +## Delivery Approach + +1. Finalize manifest schema and protocol error model. +2. Implement host-side schema validator and capability gate only. +3. Build one first-party reference extension for test coverage. +4. Wire CI contract tests and initial fuzz harness. +5. Validate Android profile behavior with restricted lifecycle tests. +6. Decide on public SDK timing after operational feedback.