openvibers/openvibe.contracts Commits

Capability schemas (WS-C task 4) for AI and Events: ai provider and model records, provider-manage request/result, usage result, definition-version request/result (templates, workflows, routes); events publish result, pull read request/result (pages, one event, checkpoints), subscription record/request/result, delivery-admin request/result. 11 capabilities complete; gaps 144 -> 133 (unreleased)

OP OpenVibers committed 25 Sept 2026 · 36e0e5825a · 72 files, +6226 −2094


Changes

ddiff --git a/compatibility/capability-schema-gaps.json b/compatibility/capability-schema-gaps.json
iindex e4497e54..f34145e3 100644
---- a/compatibility/capability-schema-gaps.json
++++ b/compatibility/capability-schema-gaps.json
@@@ -1,33 +1,6 @@
{
"_about": "Active capabilities still without an input or output schema (roadmap WS-C task 4). test/capability-schemas.test.js fails on any active capability missing a schema that is not listed here, and on a listed one that has since gained it (remove it from this list). The list only shrinks: fill a capability's schemas, then delete its row. A request with no body names common.no-body@1; a response of bytes names common.binary@1.",
"gaps": [
-- {
-- "id": "ai.provider.manage",
-- "missing": [
-- "inputSchema",
-- "outputSchema"
-- ]
-- },
-- {
-- "id": "ai.run.read",
-- "missing": [
-- "inputSchema"
-- ]
-- },
-- {
-- "id": "ai.usage.read",
-- "missing": [
-- "inputSchema",
-- "outputSchema"
-- ]
-- },
-- {
-- "id": "ai.workflow.manage",
-- "missing": [
-- "inputSchema",
-- "outputSchema"
-- ]
-- },
{
"id": "blog.blog.configure",
"missing": [
@@@ -250,52 +223,6 @@
"outputSchema"
]
},
-- {
-- "id": "events.app.publish",
-- "missing": [
-- "outputSchema"
-- ]
-- },
-- {
-- "id": "events.app.read",
-- "missing": [
-- "inputSchema",
-- "outputSchema"
-- ]
-- },
-- {
-- "id": "events.app.subscribe",
-- "missing": [
-- "inputSchema",
-- "outputSchema"
-- ]
-- },
-- {
-- "id": "events.delivery.admin",
-- "missing": [
-- "inputSchema",
-- "outputSchema"
-- ]
-- },
-- {
-- "id": "events.event.publish",
-- "missing": [
-- "outputSchema"
-- ]
-- },
-- {
-- "id": "events.event.read",
-- "missing": [
-- "inputSchema"
-- ]
-- },
-- {
-- "id": "events.subscription.manage",
-- "missing": [
-- "inputSchema",
-- "outputSchema"
-- ]
-- },
{
"id": "games.mod.manage",
"missing": [
ddiff --git a/contracts/ai/definition-version-request.v1.json b/contracts/ai/definition-version-request.v1.json
nnew file mode 100644
iindex 00000000..f439012e
---- /dev/null
++++ b/contracts/ai/definition-version-request.v1.json
@@@ -0,0 +1,216 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/definition-version-request.v1.json",
++ "title": "AiDefinitionVersionRequest",
++ "description": "ai.definition-version-request@1: bodies of ai.workflow.manage on OpenVibe.AI. An edit is always a new version (the previous one stays readable); fields left out are taken from the newest existing version. POST /api/v1/templates/:key/versions: a prompt template (input_schema and output_schema must compile as JSON Schema; visibility public, first-party or internal). POST /api/v1/workflows/:key/versions (key <namespace>.<name>): steps (at least one; kind llm (naming an existing template), passthrough, transcribe or embed), schemas, default route, cache mode. POST /api/v1/routes/:key/versions: primary { provider, model } and fallbacks (every provider must exist), or alias_of another route. POST …/:key/versions/:version/status: { status } (templates and workflows draft|active|deprecated|archived; routes active|disabled). Refusals are 422 ai.invalid.",
++ "anyOf": [
++ {
++ "type": "object",
++ "properties": {
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "input_schema": {
++ "type": "object",
++ "description": "A JSON Schema the run input must match."
++ },
++ "output_schema": {
++ "type": "object",
++ "description": "A JSON Schema the output must match."
++ },
++ "system_prompt": {
++ "type": "string"
++ },
++ "user_prompt": {
++ "type": "string"
++ },
++ "default_route": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "owner": {
++ "type": "string"
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "first-party",
++ "internal"
++ ]
++ },
++ "status": {
++ "enum": [
++ "draft",
++ "active",
++ "deprecated",
++ "archived"
++ ]
++ },
++ "metadata": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "properties": {
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "namespace": {
++ "type": "string"
++ },
++ "input_schema": {
++ "type": "object",
++ "description": "A JSON Schema."
++ },
++ "output_schema": {
++ "type": "object",
++ "description": "A JSON Schema."
++ },
++ "steps": {
++ "type": "array",
++ "minItems": 1,
++ "items": {
++ "type": "object",
++ "required": [
++ "kind"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "llm",
++ "passthrough",
++ "transcribe",
++ "embed"
++ ]
++ },
++ "template": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "default_route": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "cache_mode": {
++ "enum": [
++ "none",
++ "private",
++ "service"
++ ]
++ },
++ "cache_ttl_sec": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "status": {
++ "enum": [
++ "draft",
++ "active",
++ "deprecated",
++ "archived"
++ ]
++ },
++ "metadata": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "properties": {
++ "primary": {
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "properties": {
++ "provider": {
++ "type": "string"
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "fallbacks": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "properties": {
++ "provider": {
++ "type": "string"
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ },
++ "options": {
++ "type": "object"
++ },
++ "max_output_tokens": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "response_format": {
++ "enum": [
++ "text",
++ "json"
++ ]
++ },
++ "timeout_ms": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "alias_of": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/ai/definition-version-result.v1.json b/contracts/ai/definition-version-result.v1.json
nnew file mode 100644
iindex 00000000..3fae355c
---- /dev/null
++++ b/contracts/ai/definition-version-result.v1.json
@@@ -0,0 +1,92 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/definition-version-result.v1.json",
++ "title": "AiDefinitionVersionResult",
++ "description": "ai.definition-version-result@1: answers of ai.workflow.manage on OpenVibe.AI. POST …/:key/versions → 201 { template | workflow | route } (the new version, whole: prompts and schemas for a template, steps for a workflow, primary/fallbacks for a route); POST …/versions/:version/status → { template | workflow | route: { key, version, status } }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "template"
++ ],
++ "properties": {
++ "template": {
++ "type": "object",
++ "required": [
++ "key",
++ "version",
++ "status"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "status": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "workflow"
++ ],
++ "properties": {
++ "workflow": {
++ "type": "object",
++ "required": [
++ "key",
++ "version",
++ "status"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "status": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "route"
++ ],
++ "properties": {
++ "route": {
++ "type": "object",
++ "required": [
++ "key",
++ "version",
++ "status"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "status": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/ai/model.v1.json b/contracts/ai/model.v1.json
nnew file mode 100644
iindex 00000000..61d8368f
---- /dev/null
++++ b/contracts/ai/model.v1.json
@@@ -0,0 +1,111 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/model.v1.json",
++ "title": "AiModel",
++ "description": "ai.model@1: one model of a provider in OpenVibe.AI's registry (server/registry.js decodeModel): type, status, limits, per-million-token costs and what it supports.",
++ "type": "object",
++ "required": [
++ "provider_key",
++ "model_key",
++ "type",
++ "status",
++ "cost",
++ "supports"
++ ],
++ "properties": {
++ "provider_key": {
++ "type": "string"
++ },
++ "model_key": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "type": {
++ "enum": [
++ "chat",
++ "vision",
++ "embedding",
++ "stt"
++ ]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
++ },
++ "context_window": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "max_output": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "cost": {
++ "type": "object",
++ "properties": {
++ "in_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "out_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "cached_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ }
++ }
++ },
++ "supports": {
++ "type": "object",
++ "properties": {
++ "json": {
++ "type": "boolean"
++ },
++ "tools": {
++ "type": "boolean"
++ },
++ "streaming": {
++ "type": "boolean"
++ },
++ "vision": {
++ "type": "boolean"
++ }
++ }
++ },
++ "metadata": {
++ "type": "object"
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++}
ddiff --git a/contracts/ai/provider-manage-request.v1.json b/contracts/ai/provider-manage-request.v1.json
nnew file mode 100644
iindex 00000000..9b0cb921
---- /dev/null
++++ b/contracts/ai/provider-manage-request.v1.json
@@@ -0,0 +1,262 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/provider-manage-request.v1.json",
++ "title": "AiProviderManageRequest",
++ "description": "ai.provider-manage-request@1: bodies of ai.provider.manage on OpenVibe.AI. POST /api/v1/providers creates or replaces a provider (key required) and PATCH /api/v1/providers/:key changes one (fields left out keep their value); a body carrying api_key or secret is refused (422: send secret_ref env:NAME, never a secret value). POST /api/v1/models (provider_key and model_key required) and PATCH /api/v1/models/:provider/:model upsert a model. POST /api/v1/quotas upserts a quota (scope_type and window required; scope_id too unless global; the same scope, window and workflow_prefix is one quota). POST /api/v1/providers/:key/disable|enable|reset and DELETE /api/v1/cache[?workflow=] take no body. Unknown fields are ignored.",
++ "anyOf": [
++ {
++ "type": "object",
++ "not": {
++ "anyOf": [
++ {
++ "required": [
++ "api_key"
++ ]
++ },
++ {
++ "required": [
++ "secret"
++ ]
++ }
++ ]
++ },
++ "properties": {
++ "key": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)*$"
++ },
++ "display_name": {
++ "type": "string"
++ },
++ "kind": {
++ "enum": [
++ "stub",
++ "openai",
++ "anthropic",
++ "http",
++ "whisper"
++ ]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
++ },
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^https?://\\S+$"
++ },
++ "auth_mode": {
++ "enum": [
++ "none",
++ "bearer",
++ "x-api-key"
++ ]
++ },
++ "secret_ref": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^(env:[A-Z][A-Z0-9_]{0,63})?$"
++ },
++ "default_model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "capabilities": {
++ "type": "array",
++ "items": {
++ "enum": [
++ "chat",
++ "generate",
++ "summarize",
++ "classify",
++ "extract",
++ "enrich",
++ "embed",
++ "vision",
++ "json",
++ "transcribe"
++ ]
++ }
++ },
++ "timeout_ms": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "Clamped to 1000..600000."
++ },
++ "priority": {
++ "type": [
++ "number",
++ "string"
++ ]
++ },
++ "metadata": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "properties": {
++ "provider_key": {
++ "type": "string"
++ },
++ "model_key": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "display_name": {
++ "type": "string"
++ },
++ "type": {
++ "enum": [
++ "chat",
++ "vision",
++ "embedding",
++ "stt"
++ ]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
++ },
++ "context_window": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "max_output": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "cost": {
++ "type": "object",
++ "properties": {
++ "in_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "out_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "cached_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ }
++ }
++ },
++ "supports": {
++ "type": "object",
++ "properties": {
++ "json": {
++ "type": "boolean"
++ },
++ "tools": {
++ "type": "boolean"
++ },
++ "streaming": {
++ "type": "boolean"
++ },
++ "vision": {
++ "type": "boolean"
++ }
++ }
++ },
++ "metadata": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "scope_type",
++ "window"
++ ],
++ "properties": {
++ "scope_type": {
++ "enum": [
++ "global",
++ "service",
++ "actor",
++ "attribution"
++ ]
++ },
++ "scope_id": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "* on a non-global scope = each one separately."
++ },
++ "window": {
++ "enum": [
++ "minute",
++ "hour",
++ "day"
++ ]
++ },
++ "max_requests": {
++ "type": [
++ "number",
++ "string",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "max_tokens": {
++ "type": [
++ "number",
++ "string",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "max_cost_usd": {
++ "type": [
++ "number",
++ "string",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "workflow_prefix": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++}
ddiff --git a/contracts/ai/provider-manage-result.v1.json b/contracts/ai/provider-manage-result.v1.json
nnew file mode 100644
iindex 00000000..ece76882
---- /dev/null
++++ b/contracts/ai/provider-manage-result.v1.json
@@@ -0,0 +1,130 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/provider-manage-result.v1.json",
++ "title": "AiProviderManageResult",
++ "description": "ai.provider-manage-result@1: answers of ai.provider.manage on OpenVibe.AI. Provider create/update/disable/enable → { provider } (201 on POST); POST …/reset → { health } (the circuit breaker, closed again); model upserts → { model }; POST /api/v1/quotas → 201 { quota } (the stored row); DELETE /api/v1/cache → { removed } (entries purged). Every change is audited.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "properties": {
++ "provider": {
++ "$ref": "provider.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "health"
++ ],
++ "properties": {
++ "health": {
++ "type": "object",
++ "required": [
++ "state"
++ ],
++ "properties": {
++ "provider_key": {
++ "type": "string"
++ },
++ "state": {
++ "enum": [
++ "closed",
++ "open",
++ "half_open"
++ ]
++ },
++ "consecutive_failures": {
++ "type": "integer"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "model"
++ ],
++ "properties": {
++ "model": {
++ "$ref": "model.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "quota"
++ ],
++ "properties": {
++ "quota": {
++ "type": "object",
++ "required": [
++ "id",
++ "scope_type",
++ "scope_id",
++ "window",
++ "status"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "scope_type": {
++ "type": "string"
++ },
++ "scope_id": {
++ "type": "string"
++ },
++ "window": {
++ "type": "string"
++ },
++ "max_requests": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "max_tokens": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "max_cost_usd": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "workflow_prefix": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "status": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "removed"
++ ],
++ "properties": {
++ "removed": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/ai/provider.v1.json b/contracts/ai/provider.v1.json
nnew file mode 100644
iindex 00000000..10b98771
---- /dev/null
++++ b/contracts/ai/provider.v1.json
@@@ -0,0 +1,146 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/provider.v1.json",
++ "title": "AiProvider",
++ "description": "ai.provider@1: one model provider as OpenVibe.AI's registry API shows it (server/registry.js publicProvider): never a secret value, only the secret reference name (env:NAME) and whether it resolves (credentials), plus the circuit-breaker health.",
++ "type": "object",
++ "required": [
++ "key",
++ "kind",
++ "status",
++ "auth_mode",
++ "capabilities",
++ "credentials"
++ ],
++ "properties": {
++ "key": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)*$"
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "kind": {
++ "enum": [
++ "stub",
++ "openai",
++ "anthropic",
++ "http",
++ "whisper"
++ ]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
++ },
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "auth_mode": {
++ "enum": [
++ "none",
++ "bearer",
++ "x-api-key"
++ ]
++ },
++ "secret_ref": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "env:NAME, a reference; never the secret."
++ },
++ "default_model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "capabilities": {
++ "type": "array",
++ "items": {
++ "enum": [
++ "chat",
++ "generate",
++ "summarize",
++ "classify",
++ "extract",
++ "enrich",
++ "embed",
++ "vision",
++ "json",
++ "transcribe"
++ ]
++ }
++ },
++ "timeout_ms": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "priority": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "metadata": {
++ "type": "object"
++ },
++ "origin": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "credentials": {
++ "enum": [
++ "not_required",
++ "configured",
++ "missing"
++ ]
++ },
++ "health": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "provider_key": {
++ "type": "string"
++ },
++ "state": {
++ "enum": [
++ "closed",
++ "open",
++ "half_open"
++ ]
++ },
++ "consecutive_failures": {
++ "type": "integer"
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/ai/usage-result.v1.json b/contracts/ai/usage-result.v1.json
nnew file mode 100644
iindex 00000000..911d2ec3
---- /dev/null
++++ b/contracts/ai/usage-result.v1.json
@@@ -0,0 +1,223 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/usage-result.v1.json",
++ "title": "AiUsageResult",
++ "description": "ai.usage-result@1: answers of ai.usage.read on OpenVibe.AI (all GETs, parameters in the query). GET /api/v1/status → { providers, runs, today, workflows, templates, routes, inflight, quotas }; GET /api/v1/usage?from=&to=&requester= → { usage } (daily rows); GET /api/v1/quotas → { quotas, current }; GET /api/v1/requests?provider=&status=&run=&fallback=&limit= → { requests } (request-log metadata: hashes, tokens, cost, latency; raw debug fields only for provider managers who ask); GET /api/v1/audit → { audit }; GET /api/v1/cache → { cache }; GET /api/v1/providers → { providers }; GET /api/v1/models?provider= → { models }. Never raw prompts, never secrets.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "providers",
++ "runs",
++ "today"
++ ],
++ "properties": {
++ "providers": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "key"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "kind": {
++ "type": "string"
++ },
++ "status": {
++ "type": "string"
++ },
++ "credentials": {
++ "type": "string"
++ },
++ "health": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "runs": {
++ "type": "object",
++ "additionalProperties": {
++ "type": "integer"
++ },
++ "description": "Run counts by status."
++ },
++ "today": {
++ "type": "object",
++ "required": [
++ "day"
++ ],
++ "properties": {
++ "day": {
++ "type": "string"
++ },
++ "requests": {
++ "type": "number"
++ },
++ "cost_usd": {
++ "type": "number"
++ }
++ }
++ },
++ "workflows": {
++ "type": "integer"
++ },
++ "templates": {
++ "type": "integer"
++ },
++ "routes": {
++ "type": "integer"
++ },
++ "inflight": {
++ "type": "integer"
++ },
++ "quotas": {
++ "type": "array"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "providers"
++ ],
++ "properties": {
++ "providers": {
++ "type": "array",
++ "items": {
++ "$ref": "provider.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "models"
++ ],
++ "properties": {
++ "models": {
++ "type": "array",
++ "items": {
++ "$ref": "model.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "quotas",
++ "current"
++ ],
++ "properties": {
++ "quotas": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ },
++ "current": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "properties": {
++ "quota": {
++ "type": "object"
++ },
++ "scope_id": {
++ "type": "string"
++ },
++ "window_start": {
++ "type": "string"
++ },
++ "resets_in_seconds": {
++ "type": "number"
++ },
++ "used": {
++ "type": "object"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "usage"
++ ],
++ "properties": {
++ "usage": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "requests"
++ ],
++ "properties": {
++ "requests": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "audit"
++ ],
++ "properties": {
++ "audit": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "cache"
++ ],
++ "properties": {
++ "cache": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "properties": {
++ "workflow_key": {
++ "type": "string"
++ },
++ "privacy": {
++ "type": "string"
++ },
++ "entries": {
++ "type": "integer"
++ },
++ "hits": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 0ef5cf57..6abca6b4 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -270,6 +270,78 @@
"status": "active",
"adr": "ADR-026"
},
++ {
++ "id": "events.delivery-admin-request",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/delivery-admin-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "events.delivery-admin-result",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/delivery-admin-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "events.publish-result",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/publish-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "events.read-request",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/read-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "events.read-result",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "events.subscription-request",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/subscription-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "events.subscription-result",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/subscription-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "events.subscription",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/subscription.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "chat.message.deleted",
"version": "1.0.0",
@@@ -627,6 +699,69 @@
"status": "planned",
"adr": "ADR-015"
},
++ {
++ "id": "ai.definition-version-request",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/definition-version-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.definition-version-result",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/definition-version-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.model",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/model.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.provider-manage-request",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/provider-manage-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.provider-manage-result",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/provider-manage-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.provider",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/provider.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.usage-result",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/usage-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "tips.interaction.ready",
"version": "1.0.0",
ddiff --git a/contracts/events/delivery-admin-request.v1.json b/contracts/events/delivery-admin-request.v1.json
nnew file mode 100644
iindex 00000000..491b46eb
---- /dev/null
++++ b/contracts/events/delivery-admin-request.v1.json
@@@ -0,0 +1,45 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/delivery-admin-request.v1.json",
++ "title": "EventsDeliveryAdminRequest",
++ "description": "events.delivery-admin-request@1: the body of POST /api/v1/deliveries/replay on OpenVibe.Events (events.delivery.admin, operators): { subscription_id } and exactly one of from_seq (requeue everything the subscription matches from that seq) or event_ids (at most 1000). GET /api/v1/deliveries?status=&subscription_id=&after_seq=&limit= takes no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "subscription_id"
++ ],
++ "oneOf": [
++ {
++ "required": [
++ "from_seq"
++ ]
++ },
++ {
++ "required": [
++ "event_ids"
++ ]
++ }
++ ],
++ "properties": {
++ "subscription_id": {
++ "type": "string"
++ },
++ "from_seq": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "event_ids": {
++ "type": "array",
++ "maxItems": 1000,
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++}
ddiff --git a/contracts/events/delivery-admin-result.v1.json b/contracts/events/delivery-admin-result.v1.json
nnew file mode 100644
iindex 00000000..b5e79f92
---- /dev/null
++++ b/contracts/events/delivery-admin-result.v1.json
@@@ -0,0 +1,118 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/delivery-admin-result.v1.json",
++ "title": "EventsDeliveryAdminResult",
++ "description": "events.delivery-admin-result@1: answers of events.delivery.admin on OpenVibe.Events. GET /api/v1/deliveries → { deliveries, counts } (per delivery: event, subscription, seq, status pending|delivered|failed|dead, attempt, next_attempt_at, the last HTTP status and error, delivered_at; counts by status); POST /api/v1/deliveries/replay → { subscription_id, queued }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "deliveries",
++ "counts"
++ ],
++ "properties": {
++ "deliveries": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "event_id",
++ "subscription_id",
++ "seq",
++ "status",
++ "attempt"
++ ],
++ "properties": {
++ "event_id": {
++ "type": "string"
++ },
++ "subscription_id": {
++ "type": "string"
++ },
++ "seq": {
++ "type": "integer"
++ },
++ "status": {
++ "enum": [
++ "pending",
++ "delivered",
++ "failed",
++ "dead"
++ ]
++ },
++ "attempt": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "next_attempt_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "last_status": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "last_error": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "delivered_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "counts": {
++ "type": "object",
++ "required": [
++ "pending",
++ "delivered",
++ "failed",
++ "dead"
++ ],
++ "properties": {
++ "pending": {
++ "type": "integer"
++ },
++ "delivered": {
++ "type": "integer"
++ },
++ "failed": {
++ "type": "integer"
++ },
++ "dead": {
++ "type": "integer"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "subscription_id",
++ "queued"
++ ],
++ "properties": {
++ "subscription_id": {
++ "type": "string"
++ },
++ "queued": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/publish-result.v1.json b/contracts/events/publish-result.v1.json
nnew file mode 100644
iindex 00000000..a00d792d
---- /dev/null
++++ b/contracts/events/publish-result.v1.json
@@@ -0,0 +1,65 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/publish-result.v1.json",
++ "title": "EventsPublishResult",
++ "description": "events.publish-result@1: the answer of POST /api/v1/events on OpenVibe.Events (events.event.publish for services, events.app.publish for developer apps). One envelope → { event_id, seq, duplicate } (201 when stored, 200 when every event was a repeat); a batch { events: [...] } (at most 100, atomic) → { results } in the same order. Refusals are problem+json: 422 events.invalid_envelope / events.invalid_redaction, 403 events.source_mismatch / events.type_not_allowed / events.actor_mismatch, 413 events.payload_too_large / events.batch_too_large, 429 events.quota_exceeded (apps).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "event_id",
++ "seq",
++ "duplicate"
++ ],
++ "properties": {
++ "event_id": {
++ "type": "string",
++ "pattern": "^evt_"
++ },
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "duplicate": {
++ "type": "boolean",
++ "description": "true: this event_id was stored before; the stored seq is returned and nothing is stored twice."
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "results"
++ ],
++ "properties": {
++ "results": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "event_id",
++ "seq",
++ "duplicate"
++ ],
++ "properties": {
++ "event_id": {
++ "type": "string",
++ "pattern": "^evt_"
++ },
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "duplicate": {
++ "type": "boolean",
++ "description": "true: this event_id was stored before; the stored seq is returned and nothing is stored twice."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/read-request.v1.json b/contracts/events/read-request.v1.json
nnew file mode 100644
iindex 00000000..72b49975
---- /dev/null
++++ b/contracts/events/read-request.v1.json
@@@ -0,0 +1,31 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/read-request.v1.json",
++ "title": "EventsReadRequest",
++ "description": "events.read-request@1: request bodies of the pull API on OpenVibe.Events (events.event.read, events.app.read). PUT /api/v1/checkpoints: { topic, cursor } stores the calling consumer's cursor for a topic pattern (an app's patterns must start with a literal segment and app.* must name its own project_key). GET /api/v1/events?topic=&after_seq=&limit=, GET /api/v1/events/:event_id, GET /api/v1/checkpoints?topic= and GET /realtime/stream?topics= take no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "topic",
++ "cursor"
++ ],
++ "properties": {
++ "topic": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^(\\*|[a-z0-9_]+)(\\.(\\*|[a-z0-9_]+))*$",
++ "description": "Dot-separated segments of [a-z0-9_] or *, where * stands for one or more whole segments (*.* is refused)."
++ },
++ "cursor": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "A seq (next_after_seq of the last page handled)."
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++}
ddiff --git a/contracts/events/read-result.v1.json b/contracts/events/read-result.v1.json
nnew file mode 100644
iindex 00000000..83094ffc
---- /dev/null
++++ b/contracts/events/read-result.v1.json
@@@ -0,0 +1,105 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/read-result.v1.json",
++ "title": "EventsReadResult",
++ "description": "events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). An app sees only its project's events in its token's environment plus public first-party events.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "events",
++ "next_after_seq",
++ "latest_seq"
++ ],
++ "properties": {
++ "events": {
++ "type": "array",
++ "maxItems": 1000,
++ "items": {
++ "type": "object",
++ "required": [
++ "seq",
++ "event"
++ ],
++ "properties": {
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "event": {
++ "$ref": "event-envelope.v1.json"
++ }
++ }
++ }
++ },
++ "next_after_seq": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "latest_seq": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "gap": {
++ "type": "object",
++ "required": [
++ "from_seq",
++ "to_seq"
++ ],
++ "properties": {
++ "from_seq": {
++ "type": "integer"
++ },
++ "to_seq": {
++ "type": "integer"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "seq",
++ "event"
++ ],
++ "properties": {
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "event": {
++ "$ref": "event-envelope.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "consumer",
++ "topic",
++ "cursor",
++ "updated_at"
++ ],
++ "properties": {
++ "consumer": {
++ "type": "string"
++ },
++ "topic": {
++ "type": "string"
++ },
++ "cursor": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/subscription-request.v1.json b/contracts/events/subscription-request.v1.json
nnew file mode 100644
iindex 00000000..dbc05d3e
---- /dev/null
++++ b/contracts/events/subscription-request.v1.json
@@@ -0,0 +1,76 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/subscription-request.v1.json",
++ "title": "EventsSubscriptionRequest",
++ "description": "events.subscription-request@1: the body of POST /api/v1/subscriptions on OpenVibe.Events (events.subscription.manage for services, events.app.subscribe for developer apps; the consumer is always the caller). topic_pattern (topic is accepted for it) and endpoint are required: a service's endpoint must be http(s) on the allow-list (127.0.0.1, *.openvibe.*), an app's must be https resolving only to public addresses. secret, when given, is 32 to 256 characters (else one is generated); retry_policy accepts max_attempts (1..20) and backoff_ms (1..20 delays of 0..86400000 ms) and nothing else. The same pattern and endpoint twice is 409 events.subscription_exists. GET /api/v1/subscriptions[/:id] and POST /api/v1/subscriptions/:id/disable|enable take no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "endpoint"
++ ],
++ "anyOf": [
++ {
++ "required": [
++ "topic_pattern"
++ ]
++ },
++ {
++ "required": [
++ "topic"
++ ]
++ }
++ ],
++ "properties": {
++ "topic_pattern": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^(\\*|[a-z0-9_]+)(\\.(\\*|[a-z0-9_]+))*$",
++ "description": "Dot-separated segments of [a-z0-9_] or *, where * stands for one or more whole segments (*.* is refused)."
++ },
++ "topic": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^(\\*|[a-z0-9_]+)(\\.(\\*|[a-z0-9_]+))*$",
++ "description": "Dot-separated segments of [a-z0-9_] or *, where * stands for one or more whole segments (*.* is refused)."
++ },
++ "endpoint": {
++ "type": "string",
++ "pattern": "^https?://"
++ },
++ "secret": {
++ "type": "string",
++ "minLength": 32,
++ "maxLength": 256
++ },
++ "retry_policy": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "max_attempts": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 20
++ },
++ "backoff_ms": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 20,
++ "items": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 86400000
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++}
ddiff --git a/contracts/events/subscription-result.v1.json b/contracts/events/subscription-result.v1.json
nnew file mode 100644
iindex 00000000..00586e78
---- /dev/null
++++ b/contracts/events/subscription-result.v1.json
@@@ -0,0 +1,25 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/subscription-result.v1.json",
++ "title": "EventsSubscriptionResult",
++ "description": "events.subscription-result@1: answers of the subscription routes on OpenVibe.Events. POST /api/v1/subscriptions → 201 events.subscription@1 with its secret (shown once); GET /api/v1/subscriptions → { subscriptions } (the caller's own, no secrets); GET /api/v1/subscriptions/:id and POST …/disable|enable → events.subscription@1. Another consumer's subscription is 404.",
++ "anyOf": [
++ {
++ "$ref": "subscription.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "subscriptions"
++ ],
++ "properties": {
++ "subscriptions": {
++ "type": "array",
++ "items": {
++ "$ref": "subscription.v1.json"
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/subscription.v1.json b/contracts/events/subscription.v1.json
nnew file mode 100644
iindex 00000000..09abc426
---- /dev/null
++++ b/contracts/events/subscription.v1.json
@@@ -0,0 +1,71 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/subscription.v1.json",
++ "title": "EventsSubscription",
++ "description": "events.subscription@1: one push subscription as OpenVibe.Events shows it to its consumer (server/store.js subscriptionView). The signing secret appears only in the answer that creates it (and in a rotation's). A developer app's subscription also names its project and environment.",
++ "type": "object",
++ "required": [
++ "id",
++ "consumer",
++ "topic_pattern",
++ "endpoint",
++ "enabled",
++ "retry_policy",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^sub_"
++ },
++ "consumer": {
++ "type": "string",
++ "description": "The calling service, or app:app_<ULID>."
++ },
++ "topic_pattern": {
++ "type": "string"
++ },
++ "endpoint": {
++ "type": "string"
++ },
++ "enabled": {
++ "type": "boolean"
++ },
++ "retry_policy": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "max_attempts": {
++ "type": "integer"
++ },
++ "backoff_ms": {
++ "type": "array",
++ "items": {
++ "type": "integer"
++ }
++ }
++ }
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "project_id": {
++ "type": "string"
++ },
++ "env": {
++ "type": "string"
++ },
++ "secret": {
++ "type": "string",
++ "description": "Only when the subscription is created: shown once."
++ }
++ }
++}
ddiff --git a/fixtures/ai.definition-version-request/invalid/unknown-status.json b/fixtures/ai.definition-version-request/invalid/unknown-status.json
nnew file mode 100644
iindex 00000000..44402095
---- /dev/null
++++ b/fixtures/ai.definition-version-request/invalid/unknown-status.json
@@@ -0,0 +1 @@
++{ "status": "live" }
ddiff --git a/fixtures/ai.definition-version-request/valid/route.json b/fixtures/ai.definition-version-request/valid/route.json
nnew file mode 100644
iindex 00000000..e1af057c
---- /dev/null
++++ b/fixtures/ai.definition-version-request/valid/route.json
@@@ -0,0 +1 @@
++{ "primary": { "provider": "anthropic", "model": "claude-sonnet-4-5" }, "fallbacks": [ { "provider": "openai", "model": "gpt-4.1-mini" } ], "response_format": "json" }
ddiff --git a/fixtures/ai.definition-version-request/valid/template.json b/fixtures/ai.definition-version-request/valid/template.json
nnew file mode 100644
iindex 00000000..f7931bdf
---- /dev/null
++++ b/fixtures/ai.definition-version-request/valid/template.json
@@@ -0,0 +1 @@
++{ "name": "Clip title", "system_prompt": "You title stream clips.", "user_prompt": "Transcript: {{transcript}}", "input_schema": { "type": "object", "required": ["transcript"] }, "output_schema": { "type": "object", "required": ["title"] }, "visibility": "first-party", "status": "draft" }
ddiff --git a/fixtures/ai.definition-version-request/valid/workflow.json b/fixtures/ai.definition-version-request/valid/workflow.json
nnew file mode 100644
iindex 00000000..020b5c5e
---- /dev/null
++++ b/fixtures/ai.definition-version-request/valid/workflow.json
@@@ -0,0 +1 @@
++{ "steps": [ { "kind": "llm", "template": "live.clip_title" } ], "cache_mode": "service", "cache_ttl_sec": 86400 }
ddiff --git a/fixtures/ai.definition-version-result/invalid/no-version.json b/fixtures/ai.definition-version-result/invalid/no-version.json
nnew file mode 100644
iindex 00000000..f733f223
---- /dev/null
++++ b/fixtures/ai.definition-version-result/invalid/no-version.json
@@@ -0,0 +1 @@
++{ "workflow": { "key": "live.clip_title", "status": "active" } }
ddiff --git a/fixtures/ai.definition-version-result/valid/route-status.json b/fixtures/ai.definition-version-result/valid/route-status.json
nnew file mode 100644
iindex 00000000..56af75e6
---- /dev/null
++++ b/fixtures/ai.definition-version-result/valid/route-status.json
@@@ -0,0 +1 @@
++{ "route": { "key": "default.chat", "version": 3, "status": "disabled" } }
ddiff --git a/fixtures/ai.definition-version-result/valid/template.json b/fixtures/ai.definition-version-result/valid/template.json
nnew file mode 100644
iindex 00000000..b7186fd2
---- /dev/null
++++ b/fixtures/ai.definition-version-result/valid/template.json
@@@ -0,0 +1 @@
++{ "template": { "key": "live.clip_title", "version": 4, "status": "active", "name": "Clip title" } }
ddiff --git a/fixtures/ai.model/invalid/unknown-type.json b/fixtures/ai.model/invalid/unknown-type.json
nnew file mode 100644
iindex 00000000..a98a29d4
---- /dev/null
++++ b/fixtures/ai.model/invalid/unknown-type.json
@@@ -0,0 +1 @@
++{ "provider_key": "anthropic", "model_key": "x", "type": "image", "status": "active", "cost": {}, "supports": {} }
ddiff --git a/fixtures/ai.model/valid/sonnet.json b/fixtures/ai.model/valid/sonnet.json
nnew file mode 100644
iindex 00000000..a1a17d5a
---- /dev/null
++++ b/fixtures/ai.model/valid/sonnet.json
@@@ -0,0 +1 @@
++{ "provider_key": "anthropic", "model_key": "claude-sonnet-4-5", "display_name": "Claude Sonnet 4.5", "type": "chat", "status": "active", "context_window": 200000, "max_output": 64000, "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 }, "supports": { "json": true, "tools": true, "streaming": true, "vision": true }, "metadata": {}, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-20T10:00:00.000Z" }
ddiff --git a/fixtures/ai.provider-manage-request/invalid/secret-value.json b/fixtures/ai.provider-manage-request/invalid/secret-value.json
nnew file mode 100644
iindex 00000000..0cceb106
---- /dev/null
++++ b/fixtures/ai.provider-manage-request/invalid/secret-value.json
@@@ -0,0 +1 @@
++{ "key": "openai", "kind": "openai", "api_key": "sk-live-123", "status": "paused" }
ddiff --git a/fixtures/ai.provider-manage-request/valid/disable.json b/fixtures/ai.provider-manage-request/valid/disable.json
nnew file mode 100644
iindex 00000000..0967ef42
---- /dev/null
++++ b/fixtures/ai.provider-manage-request/valid/disable.json
@@@ -0,0 +1 @@
++{}
ddiff --git a/fixtures/ai.provider-manage-request/valid/provider.json b/fixtures/ai.provider-manage-request/valid/provider.json
nnew file mode 100644
iindex 00000000..306b932f
---- /dev/null
++++ b/fixtures/ai.provider-manage-request/valid/provider.json
@@@ -0,0 +1 @@
++{ "key": "openai", "kind": "openai", "auth_mode": "bearer", "secret_ref": "env:OPENAI_API_KEY", "capabilities": ["chat", "embed"], "priority": 20 }
ddiff --git a/fixtures/ai.provider-manage-request/valid/quota.json b/fixtures/ai.provider-manage-request/valid/quota.json
nnew file mode 100644
iindex 00000000..6480c3af
---- /dev/null
++++ b/fixtures/ai.provider-manage-request/valid/quota.json
@@@ -0,0 +1 @@
++{ "scope_type": "service", "scope_id": "live", "window": "day", "max_cost_usd": 25 }
ddiff --git a/fixtures/ai.provider-manage-result/invalid/negative.json b/fixtures/ai.provider-manage-result/invalid/negative.json
nnew file mode 100644
iindex 00000000..8ec6ca90
---- /dev/null
++++ b/fixtures/ai.provider-manage-result/invalid/negative.json
@@@ -0,0 +1 @@
++{ "removed": -1 }
ddiff --git a/fixtures/ai.provider-manage-result/valid/cache-purged.json b/fixtures/ai.provider-manage-result/valid/cache-purged.json
nnew file mode 100644
iindex 00000000..2fc36882
---- /dev/null
++++ b/fixtures/ai.provider-manage-result/valid/cache-purged.json
@@@ -0,0 +1 @@
++{ "removed": 42 }
ddiff --git a/fixtures/ai.provider-manage-result/valid/provider.json b/fixtures/ai.provider-manage-result/valid/provider.json
nnew file mode 100644
iindex 00000000..056b82ef
---- /dev/null
++++ b/fixtures/ai.provider-manage-result/valid/provider.json
@@@ -0,0 +1 @@
++{ "provider": { "key": "anthropic", "display_name": "Anthropic", "kind": "anthropic", "status": "active", "base_url": null, "auth_mode": "x-api-key", "secret_ref": "env:ANTHROPIC_API_KEY", "default_model": "claude-sonnet-4-5", "capabilities": ["chat", "generate", "summarize", "json"], "timeout_ms": 30000, "priority": 10, "metadata": {}, "origin": "admin", "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z", "credentials": "configured", "health": { "provider_key": "anthropic", "state": "closed", "consecutive_failures": 0, "opened_at": null, "last_error": null } } }
ddiff --git a/fixtures/ai.provider/invalid/secret-in-credentials.json b/fixtures/ai.provider/invalid/secret-in-credentials.json
nnew file mode 100644
iindex 00000000..86fe70c9
---- /dev/null
++++ b/fixtures/ai.provider/invalid/secret-in-credentials.json
@@@ -0,0 +1 @@
++{ "key": "anthropic", "kind": "anthropic", "status": "active", "auth_mode": "x-api-key", "capabilities": ["chat"], "credentials": "sk-ant-secret" }
ddiff --git a/fixtures/ai.provider/valid/anthropic.json b/fixtures/ai.provider/valid/anthropic.json
nnew file mode 100644
iindex 00000000..c4e1d2f9
---- /dev/null
++++ b/fixtures/ai.provider/valid/anthropic.json
@@@ -0,0 +1 @@
++{ "key": "anthropic", "display_name": "Anthropic", "kind": "anthropic", "status": "active", "base_url": null, "auth_mode": "x-api-key", "secret_ref": "env:ANTHROPIC_API_KEY", "default_model": "claude-sonnet-4-5", "capabilities": ["chat", "generate", "summarize", "json"], "timeout_ms": 30000, "priority": 10, "metadata": {}, "origin": "admin", "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z", "credentials": "configured", "health": { "provider_key": "anthropic", "state": "closed", "consecutive_failures": 0, "opened_at": null, "last_error": null } }
ddiff --git a/fixtures/ai.usage-result/invalid/partial-status.json b/fixtures/ai.usage-result/invalid/partial-status.json
nnew file mode 100644
iindex 00000000..512caac0
---- /dev/null
++++ b/fixtures/ai.usage-result/invalid/partial-status.json
@@@ -0,0 +1 @@
++{ "runs": { "succeeded": 1 } }
ddiff --git a/fixtures/ai.usage-result/valid/models.json b/fixtures/ai.usage-result/valid/models.json
nnew file mode 100644
iindex 00000000..62d5ae4f
---- /dev/null
++++ b/fixtures/ai.usage-result/valid/models.json
@@@ -0,0 +1 @@
++{ "models": [ { "provider_key": "anthropic", "model_key": "claude-sonnet-4-5", "display_name": "Claude Sonnet 4.5", "type": "chat", "status": "active", "context_window": 200000, "max_output": 64000, "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 }, "supports": { "json": true, "tools": true, "streaming": true, "vision": true }, "metadata": {}, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-20T10:00:00.000Z" } ] }
ddiff --git a/fixtures/ai.usage-result/valid/status.json b/fixtures/ai.usage-result/valid/status.json
nnew file mode 100644
iindex 00000000..814b2d06
---- /dev/null
++++ b/fixtures/ai.usage-result/valid/status.json
@@@ -0,0 +1 @@
++{ "providers": [ { "key": "anthropic", "kind": "anthropic", "status": "active", "credentials": "configured", "health": "closed" } ], "runs": { "succeeded": 1203, "failed": 7 }, "today": { "day": "2026-09-25", "requests": 311, "cost_usd": 1.84 }, "workflows": 23, "templates": 19, "routes": 6, "inflight": 2, "quotas": [] }
ddiff --git a/fixtures/events.delivery-admin-request/invalid/both.json b/fixtures/events.delivery-admin-request/invalid/both.json
nnew file mode 100644
iindex 00000000..b8532bb7
---- /dev/null
++++ b/fixtures/events.delivery-admin-request/invalid/both.json
@@@ -0,0 +1 @@
++{ "subscription_id": "sub_01JAB2C3D4E5F6G7H8J9K0MNPQ", "from_seq": 1, "event_ids": ["evt_01JAB2C3D4E5F6G7H8J9K0MNPQ"] }
ddiff --git a/fixtures/events.delivery-admin-request/valid/from-seq.json b/fixtures/events.delivery-admin-request/valid/from-seq.json
nnew file mode 100644
iindex 00000000..f80aae83
---- /dev/null
++++ b/fixtures/events.delivery-admin-request/valid/from-seq.json
@@@ -0,0 +1 @@
++{ "subscription_id": "sub_01JAB2C3D4E5F6G7H8J9K0MNPQ", "from_seq": 88000 }
ddiff --git a/fixtures/events.delivery-admin-result/invalid/partial-counts.json b/fixtures/events.delivery-admin-result/invalid/partial-counts.json
nnew file mode 100644
iindex 00000000..253c7d39
---- /dev/null
++++ b/fixtures/events.delivery-admin-result/invalid/partial-counts.json
@@@ -0,0 +1 @@
++{ "deliveries": [], "counts": { "pending": 0 } }
ddiff --git a/fixtures/events.delivery-admin-result/valid/dead.json b/fixtures/events.delivery-admin-result/valid/dead.json
nnew file mode 100644
iindex 00000000..bf3573c2
---- /dev/null
++++ b/fixtures/events.delivery-admin-result/valid/dead.json
@@@ -0,0 +1 @@
++{ "deliveries": [ { "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ", "subscription_id": "sub_01JAB2C3D4E5F6G7H8J9K0MNPQ", "seq": 88213, "status": "dead", "attempt": 8, "next_attempt_at": null, "last_status": 502, "last_error": "HTTP 502", "delivered_at": null } ], "counts": { "pending": 3, "delivered": 91822, "failed": 1, "dead": 1 } }
ddiff --git a/fixtures/events.delivery-admin-result/valid/replay.json b/fixtures/events.delivery-admin-result/valid/replay.json
nnew file mode 100644
iindex 00000000..209a67d6
---- /dev/null
++++ b/fixtures/events.delivery-admin-result/valid/replay.json
@@@ -0,0 +1 @@
++{ "subscription_id": "sub_01JAB2C3D4E5F6G7H8J9K0MNPQ", "queued": 213 }
ddiff --git a/fixtures/events.publish-result/invalid/no-duplicate-flag.json b/fixtures/events.publish-result/invalid/no-duplicate-flag.json
nnew file mode 100644
iindex 00000000..04c8b29a
---- /dev/null
++++ b/fixtures/events.publish-result/invalid/no-duplicate-flag.json
@@@ -0,0 +1 @@
++{ "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ", "seq": 0 }
ddiff --git a/fixtures/events.publish-result/valid/batch.json b/fixtures/events.publish-result/valid/batch.json
nnew file mode 100644
iindex 00000000..3281d02e
---- /dev/null
++++ b/fixtures/events.publish-result/valid/batch.json
@@@ -0,0 +1 @@
++{ "results": [ { "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ", "seq": 88213, "duplicate": false }, { "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPR", "seq": 70001, "duplicate": true } ] }
ddiff --git a/fixtures/events.publish-result/valid/one.json b/fixtures/events.publish-result/valid/one.json
nnew file mode 100644
iindex 00000000..b2f2344d
---- /dev/null
++++ b/fixtures/events.publish-result/valid/one.json
@@@ -0,0 +1 @@
++{ "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPQ", "seq": 88213, "duplicate": false }
ddiff --git a/fixtures/events.read-request/invalid/bad-pattern.json b/fixtures/events.read-request/invalid/bad-pattern.json
nnew file mode 100644
iindex 00000000..7436c3da
---- /dev/null
++++ b/fixtures/events.read-request/invalid/bad-pattern.json
@@@ -0,0 +1 @@
++{ "topic": "media..vod", "cursor": -1 }
ddiff --git a/fixtures/events.read-request/valid/checkpoint.json b/fixtures/events.read-request/valid/checkpoint.json
nnew file mode 100644
iindex 00000000..b5525c31
---- /dev/null
++++ b/fixtures/events.read-request/valid/checkpoint.json
@@@ -0,0 +1 @@
++{ "topic": "media.vod.*", "cursor": 88213 }
ddiff --git a/fixtures/events.read-request/valid/get.json b/fixtures/events.read-request/valid/get.json
nnew file mode 100644
iindex 00000000..0967ef42
---- /dev/null
++++ b/fixtures/events.read-request/valid/get.json
@@@ -0,0 +1 @@
++{}
ddiff --git a/fixtures/events.read-result/invalid/bad-envelope.json b/fixtures/events.read-result/invalid/bad-envelope.json
nnew file mode 100644
iindex 00000000..8e4c65f5
---- /dev/null
++++ b/fixtures/events.read-result/invalid/bad-envelope.json
@@@ -0,0 +1 @@
++{ "events": [ { "seq": 1, "event": { "event_type": "x" } } ], "next_after_seq": 1, "latest_seq": 1 }
ddiff --git a/fixtures/events.read-result/valid/no-checkpoint.json b/fixtures/events.read-result/valid/no-checkpoint.json
nnew file mode 100644
iindex 00000000..9c1e8415
---- /dev/null
++++ b/fixtures/events.read-result/valid/no-checkpoint.json
@@@ -0,0 +1 @@
++{ "consumer": "live", "topic": "media.vod.*", "cursor": 0, "updated_at": null }
ddiff --git a/fixtures/events.read-result/valid/page.json b/fixtures/events.read-result/valid/page.json
nnew file mode 100644
iindex 00000000..e6e9cfd7
---- /dev/null
++++ b/fixtures/events.read-result/valid/page.json
@@@ -0,0 +1 @@
++{ "events": [ { "seq": 88213, "event": { "event_id": "evt_01JAB2C3D4E5F6G7H8J9K0MNPR", "event_type": "app.p01jab2c3d4e5f6g7h8j9k0mnpq.order.created", "version": 1, "source": "app-01jab2c3d4e5f6g7h8j9k0mnps", "actor": { "type": "app", "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPS" }, "timestamp": "2026-09-23T12:00:00Z", "visibility": "internal", "subject": { "type": "order", "id": "42" }, "payload": { "total": 3 }} } ], "next_after_seq": 88240, "latest_seq": 88240 }
ddiff --git a/fixtures/events.subscription-request/invalid/unknown-retry-field.json b/fixtures/events.subscription-request/invalid/unknown-retry-field.json
nnew file mode 100644
iindex 00000000..38ac316e
---- /dev/null
++++ b/fixtures/events.subscription-request/invalid/unknown-retry-field.json
@@@ -0,0 +1 @@
++{ "topic_pattern": "media.vod.*", "endpoint": "https://hooks.example.com/ov", "retry_policy": { "max_attempts": 8, "jitter": true } }
ddiff --git a/fixtures/events.subscription-request/valid/service.json b/fixtures/events.subscription-request/valid/service.json
nnew file mode 100644
iindex 00000000..a47b58be
---- /dev/null
++++ b/fixtures/events.subscription-request/valid/service.json
@@@ -0,0 +1 @@
++{ "topic_pattern": "media.vod.*", "endpoint": "http://127.0.0.1:3000/internal/media-events", "retry_policy": { "max_attempts": 8, "backoff_ms": [1000, 5000, 30000] } }
ddiff --git a/fixtures/events.subscription-result/invalid/partial.json b/fixtures/events.subscription-result/invalid/partial.json
nnew file mode 100644
iindex 00000000..a9e8f950
---- /dev/null
++++ b/fixtures/events.subscription-result/invalid/partial.json
@@@ -0,0 +1 @@
++{ "subscriptions": [ { "id": "sub_1" } ] }
ddiff --git a/fixtures/events.subscription-result/valid/created.json b/fixtures/events.subscription-result/valid/created.json
nnew file mode 100644
iindex 00000000..5f8a2a28
---- /dev/null
++++ b/fixtures/events.subscription-result/valid/created.json
@@@ -0,0 +1 @@
++{"id":"sub_01JAB2C3D4E5F6G7H8J9K0MNPQ","consumer":"live","topic_pattern":"media.vod.*","endpoint":"http://127.0.0.1:3000/internal/media-events","enabled":true,"retry_policy":null,"created_at":"2026-09-25T18:00:00.000Z","updated_at":"2026-09-25T18:00:00.000Z","secret":"whsec_4f1c0b8e2a9d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b"}
ddiff --git a/fixtures/events.subscription-result/valid/list.json b/fixtures/events.subscription-result/valid/list.json
nnew file mode 100644
iindex 00000000..e2b8e522
---- /dev/null
++++ b/fixtures/events.subscription-result/valid/list.json
@@@ -0,0 +1 @@
++{ "subscriptions": [ { "id": "sub_01JAB2C3D4E5F6G7H8J9K0MNPQ", "consumer": "live", "topic_pattern": "media.vod.*", "endpoint": "http://127.0.0.1:3000/internal/media-events", "enabled": true, "retry_policy": null, "created_at": "2026-09-25T18:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" } ] }
ddiff --git a/fixtures/events.subscription/invalid/enabled-not-boolean.json b/fixtures/events.subscription/invalid/enabled-not-boolean.json
nnew file mode 100644
iindex 00000000..3ecf3332
---- /dev/null
++++ b/fixtures/events.subscription/invalid/enabled-not-boolean.json
@@@ -0,0 +1 @@
++{ "id": "sub_01JAB2C3D4E5F6G7H8J9K0MNPQ", "consumer": "live", "topic_pattern": "media.vod.*", "endpoint": "http://127.0.0.1:3000/x", "enabled": "yes", "retry_policy": null, "created_at": "2026-09-25T18:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" }
ddiff --git a/fixtures/events.subscription/valid/service.json b/fixtures/events.subscription/valid/service.json
nnew file mode 100644
iindex 00000000..2d51633a
---- /dev/null
++++ b/fixtures/events.subscription/valid/service.json
@@@ -0,0 +1 @@
++{ "id": "sub_01JAB2C3D4E5F6G7H8J9K0MNPQ", "consumer": "live", "topic_pattern": "media.vod.*", "endpoint": "http://127.0.0.1:3000/internal/media-events", "enabled": true, "retry_policy": null, "created_at": "2026-09-25T18:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 442e1e6b..e689949b 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -3347,372 +3347,1703 @@
}
}
},
++ "events.delivery-admin-request": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/delivery-admin-request.v1.json",
++ "title": "EventsDeliveryAdminRequest",
++ "description": "events.delivery-admin-request@1: the body of POST /api/v1/deliveries/replay on OpenVibe.Events (events.delivery.admin, operators): { subscription_id } and exactly one of from_seq (requeue everything the subscription matches from that seq) or event_ids (at most 1000). GET /api/v1/deliveries?status=&subscription_id=&after_seq=&limit= takes no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "subscription_id"
++ ],
++ "oneOf": [
++ {
++ "required": [
++ "from_seq"
++ ]
++ },
++ {
++ "required": [
++ "event_ids"
++ ]
++ }
++ ],
++ "properties": {
++ "subscription_id": {
++ "type": "string"
++ },
++ "from_seq": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "event_ids": {
++ "type": "array",
++ "maxItems": 1000,
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++ }
++ },
++ "events.delivery-admin-result": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/delivery-admin-result.v1.json",
++ "title": "EventsDeliveryAdminResult",
++ "description": "events.delivery-admin-result@1: answers of events.delivery.admin on OpenVibe.Events. GET /api/v1/deliveries → { deliveries, counts } (per delivery: event, subscription, seq, status pending|delivered|failed|dead, attempt, next_attempt_at, the last HTTP status and error, delivered_at; counts by status); POST /api/v1/deliveries/replay → { subscription_id, queued }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "deliveries",
++ "counts"
++ ],
++ "properties": {
++ "deliveries": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "event_id",
++ "subscription_id",
++ "seq",
++ "status",
++ "attempt"
++ ],
++ "properties": {
++ "event_id": {
++ "type": "string"
++ },
++ "subscription_id": {
++ "type": "string"
++ },
++ "seq": {
++ "type": "integer"
++ },
++ "status": {
++ "enum": [
++ "pending",
++ "delivered",
++ "failed",
++ "dead"
++ ]
++ },
++ "attempt": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "next_attempt_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "last_status": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "last_error": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "delivered_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "counts": {
++ "type": "object",
++ "required": [
++ "pending",
++ "delivered",
++ "failed",
++ "dead"
++ ],
++ "properties": {
++ "pending": {
++ "type": "integer"
++ },
++ "delivered": {
++ "type": "integer"
++ },
++ "failed": {
++ "type": "integer"
++ },
++ "dead": {
++ "type": "integer"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "subscription_id",
++ "queued"
++ ],
++ "properties": {
++ "subscription_id": {
++ "type": "string"
++ },
++ "queued": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++ }
++ },
++ "events.publish-result": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/publish-result.v1.json",
++ "title": "EventsPublishResult",
++ "description": "events.publish-result@1: the answer of POST /api/v1/events on OpenVibe.Events (events.event.publish for services, events.app.publish for developer apps). One envelope → { event_id, seq, duplicate } (201 when stored, 200 when every event was a repeat); a batch { events: [...] } (at most 100, atomic) → { results } in the same order. Refusals are problem+json: 422 events.invalid_envelope / events.invalid_redaction, 403 events.source_mismatch / events.type_not_allowed / events.actor_mismatch, 413 events.payload_too_large / events.batch_too_large, 429 events.quota_exceeded (apps).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "event_id",
++ "seq",
++ "duplicate"
++ ],
++ "properties": {
++ "event_id": {
++ "type": "string",
++ "pattern": "^evt_"
++ },
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "duplicate": {
++ "type": "boolean",
++ "description": "true: this event_id was stored before; the stored seq is returned and nothing is stored twice."
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "results"
++ ],
++ "properties": {
++ "results": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "event_id",
++ "seq",
++ "duplicate"
++ ],
++ "properties": {
++ "event_id": {
++ "type": "string",
++ "pattern": "^evt_"
++ },
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "duplicate": {
++ "type": "boolean",
++ "description": "true: this event_id was stored before; the stored seq is returned and nothing is stored twice."
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
++ }
++ },
++ "events.read-request": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/read-request.v1.json",
++ "title": "EventsReadRequest",
++ "description": "events.read-request@1: request bodies of the pull API on OpenVibe.Events (events.event.read, events.app.read). PUT /api/v1/checkpoints: { topic, cursor } stores the calling consumer's cursor for a topic pattern (an app's patterns must start with a literal segment and app.* must name its own project_key). GET /api/v1/events?topic=&after_seq=&limit=, GET /api/v1/events/:event_id, GET /api/v1/checkpoints?topic= and GET /realtime/stream?topics= take no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "topic",
++ "cursor"
++ ],
++ "properties": {
++ "topic": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^(\\*|[a-z0-9_]+)(\\.(\\*|[a-z0-9_]+))*$",
++ "description": "Dot-separated segments of [a-z0-9_] or *, where * stands for one or more whole segments (*.* is refused)."
++ },
++ "cursor": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "A seq (next_after_seq of the last page handled)."
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++ }
++ },
++ "events.read-result": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/read-result.v1.json",
++ "title": "EventsReadResult",
++ "description": "events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). An app sees only its project's events in its token's environment plus public first-party events.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "events",
++ "next_after_seq",
++ "latest_seq"
++ ],
++ "properties": {
++ "events": {
++ "type": "array",
++ "maxItems": 1000,
++ "items": {
++ "type": "object",
++ "required": [
++ "seq",
++ "event"
++ ],
++ "properties": {
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "event": {
++ "$ref": "event-envelope.v1.json"
++ }
++ }
++ }
++ },
++ "next_after_seq": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "latest_seq": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "gap": {
++ "type": "object",
++ "required": [
++ "from_seq",
++ "to_seq"
++ ],
++ "properties": {
++ "from_seq": {
++ "type": "integer"
++ },
++ "to_seq": {
++ "type": "integer"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "seq",
++ "event"
++ ],
++ "properties": {
++ "seq": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "event": {
++ "$ref": "event-envelope.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "consumer",
++ "topic",
++ "cursor",
++ "updated_at"
++ ],
++ "properties": {
++ "consumer": {
++ "type": "string"
++ },
++ "topic": {
++ "type": "string"
++ },
++ "cursor": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ }
++ ]
++ }
++ },
++ "events.subscription-request": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/subscription-request.v1.json",
++ "title": "EventsSubscriptionRequest",
++ "description": "events.subscription-request@1: the body of POST /api/v1/subscriptions on OpenVibe.Events (events.subscription.manage for services, events.app.subscribe for developer apps; the consumer is always the caller). topic_pattern (topic is accepted for it) and endpoint are required: a service's endpoint must be http(s) on the allow-list (127.0.0.1, *.openvibe.*), an app's must be https resolving only to public addresses. secret, when given, is 32 to 256 characters (else one is generated); retry_policy accepts max_attempts (1..20) and backoff_ms (1..20 delays of 0..86400000 ms) and nothing else. The same pattern and endpoint twice is 409 events.subscription_exists. GET /api/v1/subscriptions[/:id] and POST /api/v1/subscriptions/:id/disable|enable take no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "endpoint"
++ ],
++ "anyOf": [
++ {
++ "required": [
++ "topic_pattern"
++ ]
++ },
++ {
++ "required": [
++ "topic"
++ ]
++ }
++ ],
++ "properties": {
++ "topic_pattern": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^(\\*|[a-z0-9_]+)(\\.(\\*|[a-z0-9_]+))*$",
++ "description": "Dot-separated segments of [a-z0-9_] or *, where * stands for one or more whole segments (*.* is refused)."
++ },
++ "topic": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^(\\*|[a-z0-9_]+)(\\.(\\*|[a-z0-9_]+))*$",
++ "description": "Dot-separated segments of [a-z0-9_] or *, where * stands for one or more whole segments (*.* is refused)."
++ },
++ "endpoint": {
++ "type": "string",
++ "pattern": "^https?://"
++ },
++ "secret": {
++ "type": "string",
++ "minLength": 32,
++ "maxLength": 256
++ },
++ "retry_policy": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "max_attempts": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 20
++ },
++ "backoff_ms": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 20,
++ "items": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 86400000
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++ }
++ },
++ "events.subscription-result": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/subscription-result.v1.json",
++ "title": "EventsSubscriptionResult",
++ "description": "events.subscription-result@1: answers of the subscription routes on OpenVibe.Events. POST /api/v1/subscriptions → 201 events.subscription@1 with its secret (shown once); GET /api/v1/subscriptions → { subscriptions } (the caller's own, no secrets); GET /api/v1/subscriptions/:id and POST …/disable|enable → events.subscription@1. Another consumer's subscription is 404.",
++ "anyOf": [
++ {
++ "$ref": "subscription.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "subscriptions"
++ ],
++ "properties": {
++ "subscriptions": {
++ "type": "array",
++ "items": {
++ "$ref": "subscription.v1.json"
++ }
++ }
++ }
++ }
++ ]
++ }
++ },
++ "events.subscription": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/subscription.v1.json",
++ "title": "EventsSubscription",
++ "description": "events.subscription@1: one push subscription as OpenVibe.Events shows it to its consumer (server/store.js subscriptionView). The signing secret appears only in the answer that creates it (and in a rotation's). A developer app's subscription also names its project and environment.",
++ "type": "object",
++ "required": [
++ "id",
++ "consumer",
++ "topic_pattern",
++ "endpoint",
++ "enabled",
++ "retry_policy",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^sub_"
++ },
++ "consumer": {
++ "type": "string",
++ "description": "The calling service, or app:app_<ULID>."
++ },
++ "topic_pattern": {
++ "type": "string"
++ },
++ "endpoint": {
++ "type": "string"
++ },
++ "enabled": {
++ "type": "boolean"
++ },
++ "retry_policy": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "max_attempts": {
++ "type": "integer"
++ },
++ "backoff_ms": {
++ "type": "array",
++ "items": {
++ "type": "integer"
++ }
++ }
++ }
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "project_id": {
++ "type": "string"
++ },
++ "env": {
++ "type": "string"
++ },
++ "secret": {
++ "type": "string",
++ "description": "Only when the subscription is created: shown once."
++ }
++ }
++ }
++ },
"chat.message.deleted": {
"version": "1.0.0",
-- "owner": "chat",
++ "owner": "chat",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/chat.message.deleted.v1.json",
++ "title": "ChatMessageDeletedPayload",
++ "description": "chat.message.deleted v1 (OpenVibe.Chat server/db/database.js _announceDeleted). Public messages were deleted: by a moderator, through Live's bridge, the author's or an anon's history, a time-range purge or the auto-delete sweep. Envelope: subject { type: chat_message, id: <first id> }, visibility public, priority important, actor service:chat. One event per 500 ids. Carries only ids, never the text, the author or who deleted it; payload.redacts makes OpenVibe.Events tombstone the chat.message.created of each id.",
++ "type": "object",
++ "required": [
++ "message_ids",
++ "redacts"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "message_ids": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 500,
++ "uniqueItems": true,
++ "items": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "description": "Chat message ids (the id in chat.message.created)."
++ },
++ "redacts": {
++ "$ref": "../redaction-directive.v1.json",
++ "description": "{ subject_type: chat_message, subject_ids: message_ids as strings }."
++ }
++ }
++ }
++ },
++ "sources.item.created": {
++ "version": "1.0.0",
++ "owner": "sources",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/sources.item.created.v1.json",
++ "title": "SourcesItemCreatedPayload",
++ "description": "sources.item.created v1 (OpenVibe.Sources server/items.js ingest → itemEvent). A successful fetch, or a manual entry, produced an item Sources had not seen (revision 1). A summary only: consumers fetch the full sources.item@1 with sources.item.read. Envelope: subject { type: item, id: <item_id>, revision }, visibility internal, priority important, actor service:sources.",
++ "type": "object",
++ "required": [
++ "item_id",
++ "source_key",
++ "category",
++ "kind",
++ "canonical_url",
++ "title",
++ "revision",
++ "content_hash",
++ "retrieved_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "item_id": {
++ "type": "string",
++ "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "source_key": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
++ },
++ "category": {
++ "enum": [
++ "news",
++ "blog",
++ "reviews",
++ "deals",
++ "coupons",
++ "trade"
++ ]
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,39}$",
++ "description": "article | url | sitemap | product | offer | review | record … (sources.item@1 kind)."
++ },
++ "canonical_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "uri"
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "content_hash": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$"
++ },
++ "retrieved_at": {
++ "type": "string",
++ "format": "date-time",
++ "description": "Last successful fetch (or manual entry) that contained the item."
++ }
++ }
++ }
++ },
++ "sources.item.updated": {
++ "version": "1.0.0",
++ "owner": "sources",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/sources.item.updated.v1.json",
++ "title": "SourcesItemUpdatedPayload",
++ "description": "sources.item.updated v1 (OpenVibe.Sources server/items.js ingest → itemEvent). A successful fetch or manual entry changed an existing item's content hash, which made a new revision. A removed item is never updated (removal is sticky). Envelope: subject { type: item, id: <item_id>, revision }, visibility internal, priority important, actor service:sources.",
++ "type": "object",
++ "required": [
++ "item_id",
++ "source_key",
++ "category",
++ "kind",
++ "canonical_url",
++ "title",
++ "revision",
++ "content_hash",
++ "retrieved_at",
++ "previous_content_hash"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "item_id": {
++ "type": "string",
++ "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "source_key": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
++ },
++ "category": {
++ "enum": [
++ "news",
++ "blog",
++ "reviews",
++ "deals",
++ "coupons",
++ "trade"
++ ]
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,39}$",
++ "description": "article | url | sitemap | product | offer | review | record … (sources.item@1 kind)."
++ },
++ "canonical_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "uri"
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "content_hash": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$"
++ },
++ "retrieved_at": {
++ "type": "string",
++ "format": "date-time",
++ "description": "Last successful fetch (or manual entry) that contained the item."
++ },
++ "previous_content_hash": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$"
++ }
++ }
++ }
++ },
++ "sources.item.removed": {
++ "version": "1.0.0",
++ "owner": "sources",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/sources.item.removed.v1.json",
++ "title": "SourcesItemRemovedPayload",
++ "description": "sources.item.removed v1 (OpenVibe.Sources server/items.js remove → itemEvent, from DELETE /api/v1/items/:id). Staff removed an item (takedown, licence, error). Sticky: a later fetch that still lists it does not bring it back. The removal bumps the revision; content_hash is the last content's. Envelope: subject { type: item, id: <item_id>, revision }, visibility internal, priority important, actor service:sources.",
++ "type": "object",
++ "required": [
++ "item_id",
++ "source_key",
++ "category",
++ "kind",
++ "canonical_url",
++ "title",
++ "revision",
++ "content_hash",
++ "retrieved_at",
++ "reason"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "item_id": {
++ "type": "string",
++ "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "source_key": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
++ },
++ "category": {
++ "enum": [
++ "news",
++ "blog",
++ "reviews",
++ "deals",
++ "coupons",
++ "trade"
++ ]
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,39}$",
++ "description": "article | url | sitemap | product | offer | review | record … (sources.item@1 kind)."
++ },
++ "canonical_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "uri"
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "content_hash": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$"
++ },
++ "retrieved_at": {
++ "type": "string",
++ "format": "date-time",
++ "description": "Last successful fetch (or manual entry) that contained the item."
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 300
++ }
++ }
++ }
++ },
++ "sources.fetch.failed": {
++ "version": "1.0.0",
++ "owner": "sources",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/sources.fetch.failed.v1.json",
++ "title": "SourcesFetchFailedPayload",
++ "description": "sources.fetch.failed v1 (OpenVibe.Sources server/ingest.js failedEvent). One endpoint fetch of a source failed and counts as a failure: robots denied or unreachable, an HTTP error, the site's 429, a parse error or an internal error. Not sent for 304, a disabled source, a missing credential or the local rate limit. No item is created or changed. Envelope: subject { type: source, id: <source_key> }, visibility internal, priority important, actor service:sources.",
++ "type": "object",
++ "required": [
++ "source_key",
++ "category",
++ "run_id",
++ "endpoint_url",
++ "state",
++ "error_code",
++ "http_status",
++ "consecutive_failures"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "source_key": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
++ },
++ "category": {
++ "enum": [
++ "news",
++ "blog",
++ "reviews",
++ "deals",
++ "coupons",
++ "trade"
++ ]
++ },
++ "run_id": {
++ "type": "string",
++ "pattern": "^frn_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The recorded fetch run (GET /api/v1/sources/:key/runs)."
++ },
++ "endpoint_url": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 2048
++ },
++ "state": {
++ "enum": [
++ "robots_denied",
++ "http_error",
++ "rate_limited",
++ "parse_error"
++ ]
++ },
++ "error_code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,39}$",
++ "description": "robots | address_refused | port_refused | bad_url | upstream_429 | http_<status> | unreadable | adapter_error | internal."
++ },
++ "http_status": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 100,
++ "maximum": 599
++ },
++ "consecutive_failures": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The source's consecutive failed runs including this one."
++ }
++ }
++ }
++ },
++ "sources.index_document.upserted": {
++ "version": "1.0.0",
++ "owner": "sources",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/chat.message.deleted.v1.json",
-- "title": "ChatMessageDeletedPayload",
-- "description": "chat.message.deleted v1 (OpenVibe.Chat server/db/database.js _announceDeleted). Public messages were deleted: by a moderator, through Live's bridge, the author's or an anon's history, a time-range purge or the auto-delete sweep. Envelope: subject { type: chat_message, id: <first id> }, visibility public, priority important, actor service:chat. One event per 500 ids. Carries only ids, never the text, the author or who deleted it; payload.redacts makes OpenVibe.Events tombstone the chat.message.created of each id.",
++ "$id": "https://openvibe.network/contracts/events/payloads/sources.index_document.upserted.v1.json",
++ "title": "SourcesIndexDocumentUpsertedPayload",
++ "description": "sources.index_document.upserted v1 (OpenVibe.Sources server/items.js indexEvent). An item of a source with search_visibility was created or updated; the payload is its search.index-document@1 (owner sources, type item, visibility members for role:admin and role:global_mod, authorship imported, indexability noindex third_party_content). Consumed by OpenVibe.Search (*.index_document.*). Envelope: subject { type: item, id: <item id>, revision }, visibility internal, priority important, actor service:sources.",
++ "$ref": "../../search/index-document.v1.json"
++ }
++ },
++ "sources.index_document.deleted": {
++ "version": "1.0.0",
++ "owner": "sources",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/sources.index_document.deleted.v1.json",
++ "title": "SourcesIndexDocumentDeletedPayload",
++ "description": "sources.index_document.deleted v1 (OpenVibe.Sources server/items.js indexEvent). An item of a source with search_visibility was removed; OpenVibe.Search stores a tombstone at this revision. Envelope: subject { type: item, id: <item id>, revision }, visibility internal, priority important, actor service:sources.",
"type": "object",
"required": [
-- "message_ids",
-- "redacts"
++ "type",
++ "id",
++ "revision"
],
"additionalProperties": false,
"properties": {
-- "message_ids": {
-- "type": "array",
-- "minItems": 1,
-- "maxItems": 500,
-- "uniqueItems": true,
-- "items": {
-- "type": "integer",
-- "minimum": 1
-- },
-- "description": "Chat message ids (the id in chat.message.created)."
++ "type": {
++ "const": "item"
},
-- "redacts": {
-- "$ref": "../redaction-directive.v1.json",
-- "description": "{ subject_type: chat_message, subject_ids: message_ids as strings }."
++ "id": {
++ "type": "string",
++ "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
++ "search.document.indexed": {
++ "version": "1.0.0",
++ "owner": "search",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/search.document.indexed.v1.json",
++ "title": "SearchDocumentIndexedPayload",
++ "description": "search.document.indexed v1 (OpenVibe.Search server/store.js announce). An applied index document (from an owner's *.index_document.upserted or the direct API) is servable: restricted (ACL only) or public. Sent on every applied revision that stays exposed, first time or not. Envelope: subject { type: document, id: <owner>/<type>/<id>, revision }, visibility internal, priority important, actor service:search, trace_id of the request or event that caused it.",
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "id",
++ "revision",
++ "exposure",
++ "reindexed",
++ "canonical_url"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "owner": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "type": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,39}$"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:~-]{0,127}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 9007199254740991
++ },
++ "exposure": {
++ "enum": [
++ "restricted",
++ "public_unlisted",
++ "public_listed"
++ ]
++ },
++ "reindexed": {
++ "type": "boolean",
++ "description": "true when Search already held an earlier revision of the document."
++ },
++ "canonical_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Only when the document is public (public_unlisted or public_listed); null otherwise or when it has none."
++ }
++ }
++ }
++ },
++ "search.document.removed": {
++ "version": "1.0.0",
++ "owner": "search",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/search.document.removed.v1.json",
++ "title": "SearchDocumentRemovedPayload",
++ "description": "search.document.removed v1 (OpenVibe.Search server/store.js announce). A document's exposure went down: deleted, made draft or unpublished, its visibility changed, or it became noindex. Caches and sitemaps purge canonical_url. Envelope: subject { type: document, id: <owner>/<type>/<id>, revision }, visibility internal, priority important, actor service:search, trace_id of the request or event that caused it.",
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "id",
++ "revision",
++ "reason",
++ "previous_exposure",
++ "exposure",
++ "canonical_url"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "owner": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "type": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,39}$"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:~-]{0,127}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 9007199254740991
++ },
++ "reason": {
++ "enum": [
++ "deleted",
++ "draft",
++ "not_published",
++ "visibility_changed",
++ "noindex"
++ ]
++ },
++ "previous_exposure": {
++ "enum": [
++ "restricted",
++ "public_unlisted",
++ "public_listed"
++ ]
++ },
++ "exposure": {
++ "enum": [
++ "none",
++ "restricted",
++ "public_unlisted"
++ ],
++ "description": "Lower than previous_exposure. none: no longer served to anyone."
++ },
++ "canonical_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "The URL only when the document was public before (so it must be purged); null otherwise."
++ }
++ }
++ }
++ },
++ "wiki.space.updated": {
++ "version": "1.0.0",
++ "owner": "wiki",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.space.updated.v1.json",
++ "title": "WikiSpaceUpdatedPayload",
++ "description": "wiki.space.updated v1 (OpenVibe.Wiki server/wiki/service.js spaceEvent). Emitted when a space is created, renamed, changes visibility or settings, has its roles changed, or is deleted, in the transaction of the change. Envelope: subject { type: space, id: spc_… }, visibility public for a public space that is not deleted, otherwise internal, priority important, actor the person (user) or service.",
++ "type": "object",
++ "required": [
++ "slug",
++ "name",
++ "kind",
++ "visibility",
++ "deleted",
++ "change",
++ "url"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "slug": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,62}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "kind": {
++ "enum": [
++ "official",
++ "user"
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "private"
++ ]
++ },
++ "deleted": {
++ "type": "boolean"
++ },
++ "change": {
++ "enum": [
++ "created",
++ "renamed",
++ "visibility",
++ "settings",
++ "roles",
++ "deleted"
++ ],
++ "description": "What changed. renamed = a new slug."
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https?://",
++ "maxLength": 2048,
++ "description": "Canonical URL of the space."
}
}
}
},
-- "sources.item.created": {
++ "wiki.revision.created": {
"version": "1.0.0",
-- "owner": "sources",
++ "owner": "wiki",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/sources.item.created.v1.json",
-- "title": "SourcesItemCreatedPayload",
-- "description": "sources.item.created v1 (OpenVibe.Sources server/items.js ingest → itemEvent). A successful fetch, or a manual entry, produced an item Sources had not seen (revision 1). A summary only: consumers fetch the full sources.item@1 with sources.item.read. Envelope: subject { type: item, id: <item_id>, revision }, visibility internal, priority important, actor service:sources.",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.revision.created.v1.json",
++ "title": "WikiRevisionCreatedPayload",
++ "description": "wiki.revision.created v1 (OpenVibe.Wiki server/wiki/service.js revisionEvent). Emitted when a page gets a new immutable revision: an edit, a revert or an AI proposal. A new revision is a draft until it is published (then wiki.page.published|updated follows). Envelope: subject { type: page, id: pg_…, revision: <number> }, visibility internal, priority important, actor the person or service.",
"type": "object",
"required": [
-- "item_id",
-- "source_key",
-- "category",
++ "space_id",
++ "space",
++ "slug",
++ "number",
"kind",
-- "canonical_url",
-- "title",
-- "revision",
-- "content_hash",
-- "retrieved_at"
++ "parent_number",
++ "reverted_to",
++ "authorship",
++ "author",
++ "message"
],
"additionalProperties": false,
"properties": {
-- "item_id": {
++ "space_id": {
"type": "string",
-- "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
++ "pattern": "^spc_[0-9A-HJKMNP-TV-Z]{26}$"
},
-- "source_key": {
++ "space": {
"type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
++ "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
++ "description": "Space slug."
},
-- "category": {
++ "slug": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "Page slug."
++ },
++ "number": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The new revision number."
++ },
++ "kind": {
"enum": [
-- "news",
-- "blog",
-- "reviews",
-- "deals",
-- "coupons",
-- "trade"
++ "edit",
++ "revert",
++ "import"
]
},
-- "kind": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,39}$",
-- "description": "article | url | sitemap | product | offer | review | record … (sources.item@1 kind)."
++ "parent_number": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1
},
-- "canonical_url": {
++ "reverted_to": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision a revert restored; null otherwise."
++ },
++ "authorship": {
++ "enum": [
++ "human",
++ "ai_assisted",
++ "ai_generated",
++ "imported",
++ null
++ ],
++ "description": "Search's authorship vocabulary; null when the revision has no authorship record."
++ },
++ "author": {
"type": [
"string",
"null"
],
-- "format": "uri"
++ "maxLength": 200,
++ "description": "Who wrote it: a subject id (usr_…) or a service (svc:<id>)."
},
-- "title": {
++ "message": {
"type": [
"string",
"null"
-- ]
++ ],
++ "maxLength": 2000
++ },
++ "proposal_id": {
++ "type": "string",
++ "pattern": "^prp_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Present when the revision is an AI proposal awaiting a person's approval."
++ }
++ }
++ }
++ },
++ "wiki.watch.triggered": {
++ "version": "1.0.0",
++ "owner": "wiki",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.watch.triggered.v1.json",
++ "title": "WikiWatchTriggeredPayload",
++ "description": "wiki.watch.triggered v1 (OpenVibe.Wiki server/wiki/service.js notifyWatchers). One event per change to a watched page, listing the watchers who may read the page (never the actor); not emitted when nobody is left. Envelope: subject { type: page, id: pg_…, revision: <revision or 0> }, visibility internal, priority important, actor the person or service.",
++ "type": "object",
++ "required": [
++ "action",
++ "space",
++ "slug",
++ "title",
++ "revision",
++ "url",
++ "recipients"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "action": {
++ "enum": [
++ "published",
++ "updated",
++ "unpublished",
++ "deleted",
++ "revised",
++ "proposed"
++ ],
++ "description": "revised = a new draft revision; proposed = an AI proposal awaits approval; the rest follow wiki.page.*."
++ },
++ "space": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
++ "description": "Space slug."
++ },
++ "slug": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "Page slug."
++ },
++ "title": {
++ "type": "string",
++ "maxLength": 200
},
"revision": {
-- "type": "integer",
++ "type": [
++ "integer",
++ "null"
++ ],
"minimum": 1
},
-- "content_hash": {
++ "url": {
"type": "string",
-- "pattern": "^[0-9a-f]{64}$"
++ "format": "uri",
++ "pattern": "^https?://",
++ "maxLength": 2048
},
-- "retrieved_at": {
++ "recipients": {
++ "type": "array",
++ "minItems": 1,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "description": "Watchers to notify."
++ }
++ }
++ }
++ },
++ "wiki.page.published": {
++ "version": "1.0.0",
++ "owner": "wiki",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.published.v1.json",
++ "title": "WikiPagePublishedPayload",
++ "description": "wiki.page.published v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a page became published, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:wiki (scheduled and system changes).",
++ "type": "object",
++ "required": [
++ "canonical_url",
++ "publication_state",
++ "indexability",
++ "space",
++ "slug",
++ "visibility"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "canonical_url": {
"type": "string",
-- "format": "date-time",
-- "description": "Last successful fetch (or manual entry) that contained the item."
++ "format": "uri",
++ "pattern": "^https?://",
++ "maxLength": 2048
++ },
++ "publication_state": {
++ "const": "published"
++ },
++ "indexability": {
++ "type": "object",
++ "required": [
++ "decision",
++ "reasons"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "decision": {
++ "enum": [
++ "index",
++ "noindex"
++ ]
++ },
++ "reasons": {
++ "type": "array",
++ "maxItems": 20,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,63}$"
++ }
++ }
++ }
++ },
++ "space": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
++ "description": "Space slug."
++ },
++ "slug": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "Page slug inside the space."
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "private"
++ ],
++ "description": "Effective visibility (the page's, narrowed by its space's)."
}
}
}
},
-- "sources.item.updated": {
++ "wiki.page.updated": {
"version": "1.0.0",
-- "owner": "sources",
++ "owner": "wiki",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/sources.item.updated.v1.json",
-- "title": "SourcesItemUpdatedPayload",
-- "description": "sources.item.updated v1 (OpenVibe.Sources server/items.js ingest → itemEvent). A successful fetch or manual entry changed an existing item's content hash, which made a new revision. A removed item is never updated (removal is sticky). Envelope: subject { type: item, id: <item_id>, revision }, visibility internal, priority important, actor service:sources.",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.updated.v1.json",
++ "title": "WikiPageUpdatedPayload",
++ "description": "wiki.page.updated v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published page got a new published revision or visibility, or a review changed what Search holds, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:wiki (scheduled and system changes).",
"type": "object",
"required": [
-- "item_id",
-- "source_key",
-- "category",
-- "kind",
"canonical_url",
-- "title",
-- "revision",
-- "content_hash",
-- "retrieved_at",
-- "previous_content_hash"
++ "publication_state",
++ "indexability",
++ "space",
++ "slug",
++ "visibility"
],
"additionalProperties": false,
"properties": {
-- "item_id": {
-- "type": "string",
-- "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "source_key": {
++ "canonical_url": {
"type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
-- },
-- "category": {
-- "enum": [
-- "news",
-- "blog",
-- "reviews",
-- "deals",
-- "coupons",
-- "trade"
-- ]
++ "format": "uri",
++ "pattern": "^https?://",
++ "maxLength": 2048
},
-- "kind": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,39}$",
-- "description": "article | url | sitemap | product | offer | review | record … (sources.item@1 kind)."
++ "publication_state": {
++ "const": "published"
},
-- "canonical_url": {
-- "type": [
-- "string",
-- "null"
++ "indexability": {
++ "type": "object",
++ "required": [
++ "decision",
++ "reasons"
],
-- "format": "uri"
-- },
-- "title": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "revision": {
-- "type": "integer",
-- "minimum": 1
++ "additionalProperties": false,
++ "properties": {
++ "decision": {
++ "enum": [
++ "index",
++ "noindex"
++ ]
++ },
++ "reasons": {
++ "type": "array",
++ "maxItems": 20,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,63}$"
++ }
++ }
++ }
},
-- "content_hash": {
++ "space": {
"type": "string",
-- "pattern": "^[0-9a-f]{64}$"
++ "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
++ "description": "Space slug."
},
-- "retrieved_at": {
++ "slug": {
"type": "string",
-- "format": "date-time",
-- "description": "Last successful fetch (or manual entry) that contained the item."
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "Page slug inside the space."
},
-- "previous_content_hash": {
-- "type": "string",
-- "pattern": "^[0-9a-f]{64}$"
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "private"
++ ],
++ "description": "Effective visibility (the page's, narrowed by its space's)."
}
}
}
},
-- "sources.item.removed": {
++ "wiki.page.unpublished": {
"version": "1.0.0",
-- "owner": "sources",
++ "owner": "wiki",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/sources.item.removed.v1.json",
-- "title": "SourcesItemRemovedPayload",
-- "description": "sources.item.removed v1 (OpenVibe.Sources server/items.js remove → itemEvent, from DELETE /api/v1/items/:id). Staff removed an item (takedown, licence, error). Sticky: a later fetch that still lists it does not bring it back. The removal bumps the revision; content_hash is the last content's. Envelope: subject { type: item, id: <item_id>, revision }, visibility internal, priority important, actor service:sources.",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.unpublished.v1.json",
++ "title": "WikiPageUnpublishedPayload",
++ "description": "wiki.page.unpublished v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published page stopped being published, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:wiki (scheduled and system changes).",
"type": "object",
"required": [
-- "item_id",
-- "source_key",
-- "category",
-- "kind",
"canonical_url",
-- "title",
-- "revision",
-- "content_hash",
-- "retrieved_at",
-- "reason"
++ "publication_state",
++ "indexability",
++ "space",
++ "slug",
++ "visibility"
],
"additionalProperties": false,
"properties": {
-- "item_id": {
-- "type": "string",
-- "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "source_key": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
-- },
-- "category": {
-- "enum": [
-- "news",
-- "blog",
-- "reviews",
-- "deals",
-- "coupons",
-- "trade"
-- ]
-- },
-- "kind": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,39}$",
-- "description": "article | url | sitemap | product | offer | review | record … (sources.item@1 kind)."
-- },
"canonical_url": {
"type": [
"string",
"null"
],
-- "format": "uri"
++ "description": "null: the event carries a tombstone, which has no URL."
},
-- "title": {
++ "publication_state": {
++ "const": "unpublished"
++ },
++ "indexability": {
"type": [
-- "string",
++ "object",
"null"
-- ]
-- },
-- "revision": {
-- "type": "integer",
-- "minimum": 1
-- },
-- "content_hash": {
-- "type": "string",
-- "pattern": "^[0-9a-f]{64}$"
++ ],
++ "required": [
++ "decision",
++ "reasons"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "decision": {
++ "enum": [
++ "index",
++ "noindex"
++ ]
++ },
++ "reasons": {
++ "type": "array",
++ "maxItems": 20,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,63}$"
++ }
++ }
++ },
++ "description": "The last gate decision, or null when there is none."
},
-- "retrieved_at": {
++ "space": {
"type": "string",
-- "format": "date-time",
-- "description": "Last successful fetch (or manual entry) that contained the item."
++ "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
++ "description": "Space slug."
},
-- "reason": {
++ "slug": {
"type": "string",
"minLength": 1,
-- "maxLength": 300
++ "maxLength": 80,
++ "description": "Page slug inside the space."
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "private"
++ ],
++ "description": "Effective visibility (the page's, narrowed by its space's)."
}
}
}
},
-- "sources.fetch.failed": {
++ "wiki.page.deleted": {
"version": "1.0.0",
-- "owner": "sources",
++ "owner": "wiki",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/sources.fetch.failed.v1.json",
-- "title": "SourcesFetchFailedPayload",
-- "description": "sources.fetch.failed v1 (OpenVibe.Sources server/ingest.js failedEvent). One endpoint fetch of a source failed and counts as a failure: robots denied or unreachable, an HTTP error, the site's 429, a parse error or an internal error. Not sent for 304, a disabled source, a missing credential or the local rate limit. No item is created or changed. Envelope: subject { type: source, id: <source_key> }, visibility internal, priority important, actor service:sources.",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.deleted.v1.json",
++ "title": "WikiPageDeletedPayload",
++ "description": "wiki.page.deleted v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a page (or its whole space) was deleted, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:wiki (scheduled and system changes).",
"type": "object",
"required": [
-- "source_key",
-- "category",
-- "run_id",
-- "endpoint_url",
-- "state",
-- "error_code",
-- "http_status",
-- "consecutive_failures"
++ "canonical_url",
++ "publication_state",
++ "indexability",
++ "space",
++ "slug",
++ "visibility"
],
"additionalProperties": false,
"properties": {
-- "source_key": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
-- },
-- "category": {
-- "enum": [
-- "news",
-- "blog",
-- "reviews",
-- "deals",
-- "coupons",
-- "trade"
-- ]
++ "canonical_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "null: the event carries a tombstone, which has no URL."
},
-- "run_id": {
++ "publication_state": {
++ "const": "deleted"
++ },
++ "indexability": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "required": [
++ "decision",
++ "reasons"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "decision": {
++ "enum": [
++ "index",
++ "noindex"
++ ]
++ },
++ "reasons": {
++ "type": "array",
++ "maxItems": 20,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,63}$"
++ }
++ }
++ },
++ "description": "The last gate decision, or null when there is none."
++ },
++ "space": {
"type": "string",
-- "pattern": "^frn_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The recorded fetch run (GET /api/v1/sources/:key/runs)."
++ "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
++ "description": "Space slug."
},
-- "endpoint_url": {
++ "slug": {
"type": "string",
"minLength": 1,
-- "maxLength": 2048
++ "maxLength": 80,
++ "description": "Page slug inside the space."
},
-- "state": {
++ "visibility": {
"enum": [
-- "robots_denied",
-- "http_error",
-- "rate_limited",
-- "parse_error"
-- ]
-- },
-- "error_code": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,39}$",
-- "description": "robots | address_refused | port_refused | bad_url | upstream_429 | http_<status> | unreadable | adapter_error | internal."
-- },
-- "http_status": {
-- "type": [
-- "integer",
-- "null"
++ "public",
++ "members",
++ "private"
],
-- "minimum": 100,
-- "maximum": 599
-- },
-- "consecutive_failures": {
-- "type": "integer",
-- "minimum": 1,
-- "description": "The source's consecutive failed runs including this one."
++ "description": "Effective visibility (the page's, narrowed by its space's)."
}
}
}
},
-- "sources.index_document.upserted": {
++ "wiki.index_document.upserted": {
"version": "1.0.0",
-- "owner": "sources",
++ "owner": "wiki",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/sources.index_document.upserted.v1.json",
-- "title": "SourcesIndexDocumentUpsertedPayload",
-- "description": "sources.index_document.upserted v1 (OpenVibe.Sources server/items.js indexEvent). An item of a source with search_visibility was created or updated; the payload is its search.index-document@1 (owner sources, type item, visibility members for role:admin and role:global_mod, authorship imported, indexability noindex third_party_content). Consumed by OpenVibe.Search (*.index_document.*). Envelope: subject { type: item, id: <item id>, revision }, visibility internal, priority important, actor service:sources.",
-- "$ref": "../../search/index-document.v1.json"
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.index_document.upserted.v1.json",
++ "title": "WikiIndexDocumentUpsertedPayload",
++ "description": "wiki.index_document.upserted v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks indexEvent). The OpenVibe.Search document of a published, public, listable page, sent whenever what Search should hold changed (the revision grows with every change). Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: page, id, revision: <document revision> }, visibility internal, priority important, actor service:wiki.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "wiki"
++ },
++ "type": {
++ "const": "page"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^pg_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
}
},
-- "sources.index_document.deleted": {
++ "wiki.index_document.deleted": {
"version": "1.0.0",
-- "owner": "sources",
++ "owner": "wiki",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/sources.index_document.deleted.v1.json",
-- "title": "SourcesIndexDocumentDeletedPayload",
-- "description": "sources.index_document.deleted v1 (OpenVibe.Sources server/items.js indexEvent). An item of a source with search_visibility was removed; OpenVibe.Search stores a tombstone at this revision. Envelope: subject { type: item, id: <item id>, revision }, visibility internal, priority important, actor service:sources.",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.index_document.deleted.v1.json",
++ "title": "WikiIndexDocumentDeletedPayload",
++ "description": "wiki.index_document.deleted v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks indexEvent). A Search tombstone: the page is no longer published, public and listable, so Search drops it at this revision or older. Only sent for a page Search was sent before. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: page, id, revision }, visibility internal, priority important, actor service:wiki.",
"type": "object",
"required": [
"type",
@@@ -3722,406 +5053,359 @@
"additionalProperties": false,
"properties": {
"type": {
-- "const": "item"
++ "const": "page"
},
"id": {
"type": "string",
-- "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$"
++ "pattern": "^pg_[0-9A-HJKMNP-TV-Z]{26}$"
},
"revision": {
"type": "integer",
-- "minimum": 1
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
}
}
}
},
-- "search.document.indexed": {
++ "live.index_document.upserted": {
"version": "1.0.0",
-- "owner": "search",
++ "owner": "live",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/search.document.indexed.v1.json",
-- "title": "SearchDocumentIndexedPayload",
-- "description": "search.document.indexed v1 (OpenVibe.Search server/store.js announce). An applied index document (from an owner's *.index_document.upserted or the direct API) is servable: restricted (ACL only) or public. Sent on every applied revision that stays exposed, first time or not. Envelope: subject { type: document, id: <owner>/<type>/<id>, revision }, visibility internal, priority important, actor service:search, trace_id of the request or event that caused it.",
++ "$id": "https://openvibe.network/contracts/events/payloads/live.index_document.upserted.v1.json",
++ "title": "LiveIndexDocumentUpsertedPayload",
++ "description": "live.index_document.upserted v1 (OpenVibe.Live server/events/search-documents.js; roadmap WS-O task 10). The OpenVibe.Search document of one of Live's canonical pages: a streamer's channel (type channel, openvibe.live/@username: its name, bio, what it streams and whether it is live), or a public VOD or clip of a Live stream (types vod and clip, openvibe.live/vod/<id> and /clip/<id>: title, channel, AI overview and transcript; the id is the Media id; AI clips are noindex). Sent whenever what Search should hold changed; a banned channel, or a VOD or clip that is deleted, made private or failed, gets a tombstone. The revision grows with every change. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type, id, revision: <document revision> }, visibility internal, actor service:live.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "live"
++ },
++ "type": {
++ "enum": [
++ "channel",
++ "vod",
++ "clip"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[0-9]{1,15}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++ }
++ },
++ "live.index_document.deleted": {
++ "version": "1.0.0",
++ "owner": "live",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/live.index_document.deleted.v1.json",
++ "title": "LiveIndexDocumentDeletedPayload",
++ "description": "live.index_document.deleted v1 (OpenVibe.Live server/events/search-documents.js; roadmap WS-O task 10). A Search tombstone: Search drops the document at this revision or older. Only sent for a document Search was sent before. Envelope: subject { type, id, revision }, visibility internal, actor service:live.",
"type": "object",
"required": [
-- "owner",
"type",
"id",
-- "revision",
-- "exposure",
-- "reindexed",
-- "canonical_url"
++ "revision"
],
"additionalProperties": false,
"properties": {
-- "owner": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9-]{1,39}$"
-- },
"type": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,39}$"
++ "enum": [
++ "channel",
++ "vod",
++ "clip"
++ ]
},
"id": {
"type": "string",
-- "pattern": "^[A-Za-z0-9][A-Za-z0-9._:~-]{0,127}$"
++ "pattern": "^[0-9]{1,15}$"
},
"revision": {
"type": "integer",
-- "minimum": 0,
-- "maximum": 9007199254740991
-- },
-- "exposure": {
-- "enum": [
-- "restricted",
-- "public_unlisted",
-- "public_listed"
-- ]
-- },
-- "reindexed": {
-- "type": "boolean",
-- "description": "true when Search already held an earlier revision of the document."
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ }
++ }
++ }
++ },
++ "media.index_document.upserted": {
++ "version": "1.0.0",
++ "owner": "media",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/media.index_document.upserted.v1.json",
++ "title": "MediaIndexDocumentUpsertedPayload",
++ "description": "media.index_document.upserted v1 (OpenVibe.Media; roadmap WS-O task 10). The OpenVibe.Search document of a public, playable VOD or clip whose canonical page is on openvibe.media (openvibe.media/v/<id>, /c/<id>): readiness playable, visibility public, made by a person, and owned by an app other than Live (Live's own VODs and clips are live.index_document, type vod or clip, on Live's pages). Sent whenever what Search should hold changed; a VOD or clip that is deleted, made private or stops being playable gets a tombstone. The revision grows with every change. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type, id, revision: <document revision> }, visibility internal, actor service:media.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
},
-- "canonical_url": {
-- "type": [
-- "string",
-- "null"
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
],
-- "description": "Only when the document is public (public_unlisted or public_listed); null otherwise or when it has none."
++ "properties": {
++ "owner": {
++ "const": "media"
++ },
++ "type": {
++ "enum": [
++ "vod",
++ "clip"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[0-9]{1,15}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
}
-- }
++ ]
}
},
-- "search.document.removed": {
++ "media.index_document.deleted": {
"version": "1.0.0",
-- "owner": "search",
++ "owner": "media",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/search.document.removed.v1.json",
-- "title": "SearchDocumentRemovedPayload",
-- "description": "search.document.removed v1 (OpenVibe.Search server/store.js announce). A document's exposure went down: deleted, made draft or unpublished, its visibility changed, or it became noindex. Caches and sitemaps purge canonical_url. Envelope: subject { type: document, id: <owner>/<type>/<id>, revision }, visibility internal, priority important, actor service:search, trace_id of the request or event that caused it.",
++ "$id": "https://openvibe.network/contracts/events/payloads/media.index_document.deleted.v1.json",
++ "title": "MediaIndexDocumentDeletedPayload",
++ "description": "media.index_document.deleted v1 (OpenVibe.Media; roadmap WS-O task 10). A Search tombstone: Search drops the document at this revision or older. Only sent for a document Search was sent before. Envelope: subject { type, id, revision }, visibility internal, actor service:media.",
"type": "object",
"required": [
-- "owner",
"type",
"id",
-- "revision",
-- "reason",
-- "previous_exposure",
-- "exposure",
-- "canonical_url"
++ "revision"
],
"additionalProperties": false,
"properties": {
-- "owner": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9-]{1,39}$"
-- },
"type": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,39}$"
++ "enum": [
++ "vod",
++ "clip"
++ ]
},
"id": {
"type": "string",
-- "pattern": "^[A-Za-z0-9][A-Za-z0-9._:~-]{0,127}$"
++ "pattern": "^[0-9]{1,15}$"
},
"revision": {
"type": "integer",
-- "minimum": 0,
-- "maximum": 9007199254740991
-- },
-- "reason": {
-- "enum": [
-- "deleted",
-- "draft",
-- "not_published",
-- "visibility_changed",
-- "noindex"
-- ]
-- },
-- "previous_exposure": {
-- "enum": [
-- "restricted",
-- "public_unlisted",
-- "public_listed"
-- ]
-- },
-- "exposure": {
-- "enum": [
-- "none",
-- "restricted",
-- "public_unlisted"
-- ],
-- "description": "Lower than previous_exposure. none: no longer served to anyone."
-- },
-- "canonical_url": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "description": "The URL only when the document was public before (so it must be purged); null otherwise."
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
}
}
}
},
-- "wiki.space.updated": {
++ "community.index_document.upserted": {
"version": "1.0.0",
-- "owner": "wiki",
++ "owner": "community",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.space.updated.v1.json",
-- "title": "WikiSpaceUpdatedPayload",
-- "description": "wiki.space.updated v1 (OpenVibe.Wiki server/wiki/service.js spaceEvent). Emitted when a space is created, renamed, changes visibility or settings, has its roles changed, or is deleted, in the transaction of the change. Envelope: subject { type: space, id: spc_… }, visibility public for a public space that is not deleted, otherwise internal, priority important, actor the person (user) or service.",
-- "type": "object",
-- "required": [
-- "slug",
-- "name",
-- "kind",
-- "visibility",
-- "deleted",
-- "change",
-- "url"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "slug": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,62}$"
-- },
-- "name": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 200
-- },
-- "kind": {
-- "enum": [
-- "official",
-- "user"
-- ]
-- },
-- "visibility": {
-- "enum": [
-- "public",
-- "members",
-- "private"
-- ]
-- },
-- "deleted": {
-- "type": "boolean"
++ "$id": "https://openvibe.network/contracts/events/payloads/community.index_document.upserted.v1.json",
++ "title": "CommunityIndexDocumentUpsertedPayload",
++ "description": "community.index_document.upserted v1 (OpenVibe.Community; roadmap WS-O task 10). The OpenVibe.Search document of A public forum thread (in a public, open space) or a public paste on openvibe.community. Sent whenever what Search should hold changed; a thread or paste that is deleted, made private or members-only, or burns after reading gets a tombstone. The revision grows with every change. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type, id, revision: <document revision> }, visibility internal, actor service:community.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
},
-- "change": {
-- "enum": [
-- "created",
-- "renamed",
-- "visibility",
-- "settings",
-- "roles",
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
"deleted"
],
-- "description": "What changed. renamed = a new slug."
-- },
-- "url": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https?://",
-- "maxLength": 2048,
-- "description": "Canonical URL of the space."
++ "properties": {
++ "owner": {
++ "const": "community"
++ },
++ "type": {
++ "enum": [
++ "thread",
++ "paste"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_-]{1,80}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
}
-- }
++ ]
}
},
-- "wiki.revision.created": {
++ "community.index_document.deleted": {
"version": "1.0.0",
-- "owner": "wiki",
++ "owner": "community",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.revision.created.v1.json",
-- "title": "WikiRevisionCreatedPayload",
-- "description": "wiki.revision.created v1 (OpenVibe.Wiki server/wiki/service.js revisionEvent). Emitted when a page gets a new immutable revision: an edit, a revert or an AI proposal. A new revision is a draft until it is published (then wiki.page.published|updated follows). Envelope: subject { type: page, id: pg_…, revision: <number> }, visibility internal, priority important, actor the person or service.",
++ "$id": "https://openvibe.network/contracts/events/payloads/community.index_document.deleted.v1.json",
++ "title": "CommunityIndexDocumentDeletedPayload",
++ "description": "community.index_document.deleted v1 (OpenVibe.Community; roadmap WS-O task 10). A Search tombstone: Search drops the document at this revision or older. Only sent for a document Search was sent before. Envelope: subject { type, id, revision }, visibility internal, actor service:community.",
"type": "object",
"required": [
-- "space_id",
-- "space",
-- "slug",
-- "number",
-- "kind",
-- "parent_number",
-- "reverted_to",
-- "authorship",
-- "author",
-- "message"
++ "type",
++ "id",
++ "revision"
],
"additionalProperties": false,
"properties": {
-- "space_id": {
-- "type": "string",
-- "pattern": "^spc_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "space": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
-- "description": "Space slug."
-- },
-- "slug": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "Page slug."
-- },
-- "number": {
-- "type": "integer",
-- "minimum": 1,
-- "description": "The new revision number."
-- },
-- "kind": {
-- "enum": [
-- "edit",
-- "revert",
-- "import"
-- ]
-- },
-- "parent_number": {
-- "type": [
-- "integer",
-- "null"
-- ],
-- "minimum": 1
-- },
-- "reverted_to": {
-- "type": [
-- "integer",
-- "null"
-- ],
-- "minimum": 1,
-- "description": "The revision a revert restored; null otherwise."
-- },
-- "authorship": {
++ "type": {
"enum": [
-- "human",
-- "ai_assisted",
-- "ai_generated",
-- "imported",
-- null
-- ],
-- "description": "Search's authorship vocabulary; null when the revision has no authorship record."
-- },
-- "author": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "maxLength": 200,
-- "description": "Who wrote it: a subject id (usr_…) or a service (svc:<id>)."
-- },
-- "message": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "maxLength": 2000
++ "thread",
++ "paste"
++ ]
},
-- "proposal_id": {
++ "id": {
"type": "string",
-- "pattern": "^prp_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "Present when the revision is an AI proposal awaiting a person's approval."
++ "pattern": "^[A-Za-z0-9_-]{1,80}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
}
}
}
},
-- "wiki.watch.triggered": {
++ "blog.post.created": {
"version": "1.0.0",
-- "owner": "wiki",
++ "owner": "blog",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.watch.triggered.v1.json",
-- "title": "WikiWatchTriggeredPayload",
-- "description": "wiki.watch.triggered v1 (OpenVibe.Wiki server/wiki/service.js notifyWatchers). One event per change to a watched page, listing the watchers who may read the page (never the actor); not emitted when nobody is left. Envelope: subject { type: page, id: pg_…, revision: <revision or 0> }, visibility internal, priority important, actor the person or service.",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.post.created.v1.json",
++ "title": "BlogPostCreatedPayload",
++ "description": "blog.post.created v1 (OpenVibe.Blog server/domain/posts.js create). A post exists as a draft (its first revision). Envelope: subject { type: post, id: pst_…, revision: 1 }, visibility internal, priority low, actor the person or the calling service.",
"type": "object",
"required": [
-- "action",
-- "space",
-- "slug",
-- "title",
-- "revision",
-- "url",
-- "recipients"
++ "blog",
++ "state",
++ "visibility",
++ "authorship"
],
"additionalProperties": false,
"properties": {
-- "action": {
-- "enum": [
-- "published",
-- "updated",
-- "unpublished",
-- "deleted",
-- "revised",
-- "proposed"
++ "blog": {
++ "type": "object",
++ "required": [
++ "id",
++ "handle"
],
-- "description": "revised = a new draft revision; proposed = an AI proposal awaits approval; the rest follow wiki.page.*."
-- },
-- "space": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
-- "description": "Space slug."
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "handle": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
++ }
++ }
},
-- "slug": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "Page slug."
++ "state": {
++ "const": "draft"
},
-- "title": {
-- "type": "string",
-- "maxLength": 200
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "members",
++ "private"
++ ]
},
-- "revision": {
-- "type": [
-- "integer",
-- "null"
++ "authorship": {
++ "enum": [
++ "human",
++ "ai",
++ "hybrid",
++ "imported"
],
-- "minimum": 1
-- },
-- "url": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https?://",
-- "maxLength": 2048
++ "description": "openvibe-publishing authorship mode (not Search's vocabulary)."
},
-- "recipients": {
-- "type": "array",
-- "minItems": 1,
-- "uniqueItems": true,
-- "items": {
-- "type": "string",
-- "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "description": "Watchers to notify."
++ "workflow": {
++ "type": "object",
++ "required": [
++ "id",
++ "runId"
++ ],
++ "additionalProperties": false,
++ "description": "The OpenVibe.AI workflow and run behind ai and hybrid posts; absent for human posts.",
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
++ },
++ "runId": {
++ "type": "string",
++ "minLength": 1
++ },
++ "version": {
++ "type": [
++ "integer",
++ "string"
++ ]
++ },
++ "model": {
++ "type": "string"
++ }
++ }
}
}
}
},
-- "wiki.page.published": {
++ "blog.post.published": {
"version": "1.0.0",
-- "owner": "wiki",
++ "owner": "blog",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.published.v1.json",
-- "title": "WikiPagePublishedPayload",
-- "description": "wiki.page.published v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a page became published, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:wiki (scheduled and system changes).",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.post.published.v1.json",
++ "title": "BlogPostPublishedPayload",
++ "description": "blog.post.published v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a post became published (now or by its schedule), in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:blog (scheduled and system changes).",
"type": "object",
"required": [
"canonical_url",
"publication_state",
"indexability",
-- "space",
-- "slug",
++ "blog",
"visibility"
],
"additionalProperties": false,
@@@ -4160,44 +5444,51 @@
}
}
},
-- "space": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
-- "description": "Space slug."
-- },
-- "slug": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "Page slug inside the space."
++ "blog": {
++ "type": "object",
++ "required": [
++ "id",
++ "handle"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "handle": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
++ }
++ }
},
"visibility": {
"enum": [
"public",
++ "unlisted",
"members",
"private"
],
-- "description": "Effective visibility (the page's, narrowed by its space's)."
++ "description": "The post's own visibility (members = entitlement-gated)."
}
}
}
},
-- "wiki.page.updated": {
++ "blog.post.updated": {
"version": "1.0.0",
-- "owner": "wiki",
++ "owner": "blog",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.updated.v1.json",
-- "title": "WikiPageUpdatedPayload",
-- "description": "wiki.page.updated v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published page got a new published revision or visibility, or a review changed what Search holds, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:wiki (scheduled and system changes).",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.post.updated.v1.json",
++ "title": "BlogPostUpdatedPayload",
++ "description": "blog.post.updated v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published post got a new published revision, visibility or URL, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:blog (scheduled and system changes).",
"type": "object",
"required": [
"canonical_url",
"publication_state",
"indexability",
-- "space",
-- "slug",
++ "blog",
"visibility"
],
"additionalProperties": false,
@@@ -4236,44 +5527,51 @@
}
}
},
-- "space": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
-- "description": "Space slug."
-- },
-- "slug": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "Page slug inside the space."
++ "blog": {
++ "type": "object",
++ "required": [
++ "id",
++ "handle"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "handle": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
++ }
++ }
},
"visibility": {
"enum": [
"public",
++ "unlisted",
"members",
"private"
],
-- "description": "Effective visibility (the page's, narrowed by its space's)."
++ "description": "The post's own visibility (members = entitlement-gated)."
}
}
}
},
-- "wiki.page.unpublished": {
++ "blog.post.unpublished": {
"version": "1.0.0",
-- "owner": "wiki",
++ "owner": "blog",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.unpublished.v1.json",
-- "title": "WikiPageUnpublishedPayload",
-- "description": "wiki.page.unpublished v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published page stopped being published, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:wiki (scheduled and system changes).",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.post.unpublished.v1.json",
++ "title": "BlogPostUnpublishedPayload",
++ "description": "blog.post.unpublished v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published post stopped being published, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:blog (scheduled and system changes).",
"type": "object",
"required": [
"canonical_url",
"publication_state",
"indexability",
-- "space",
-- "slug",
++ "blog",
"visibility"
],
"additionalProperties": false,
@@@ -4305,56 +5603,63 @@
"noindex"
]
},
-- "reasons": {
-- "type": "array",
-- "maxItems": 20,
-- "uniqueItems": true,
-- "items": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,63}$"
-- }
++ "reasons": {
++ "type": "array",
++ "maxItems": 20,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,63}$"
++ }
++ }
++ },
++ "description": "The last gate decision, or null when there is none."
++ },
++ "blog": {
++ "type": "object",
++ "required": [
++ "id",
++ "handle"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "handle": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
}
-- },
-- "description": "The last gate decision, or null when there is none."
-- },
-- "space": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
-- "description": "Space slug."
-- },
-- "slug": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "Page slug inside the space."
++ }
},
"visibility": {
"enum": [
"public",
++ "unlisted",
"members",
"private"
],
-- "description": "Effective visibility (the page's, narrowed by its space's)."
++ "description": "The post's own visibility (members = entitlement-gated)."
}
}
}
},
-- "wiki.page.deleted": {
++ "blog.post.deleted": {
"version": "1.0.0",
-- "owner": "wiki",
++ "owner": "blog",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.page.deleted.v1.json",
-- "title": "WikiPageDeletedPayload",
-- "description": "wiki.page.deleted v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks publicationEvent). Emitted when a page (or its whole space) was deleted, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from wiki.index_document.*). Envelope: subject { type: page, id: pg_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:wiki (scheduled and system changes).",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.post.deleted.v1.json",
++ "title": "BlogPostDeletedPayload",
++ "description": "blog.post.deleted v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a post was soft-deleted, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:blog (scheduled and system changes).",
"type": "object",
"required": [
"canonical_url",
"publication_state",
"indexability",
-- "space",
-- "slug",
++ "blog",
"visibility"
],
"additionalProperties": false,
@@@ -4398,189 +5703,112 @@
},
"description": "The last gate decision, or null when there is none."
},
-- "space": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
-- "description": "Space slug."
-- },
-- "slug": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "Page slug inside the space."
-- },
-- "visibility": {
-- "enum": [
-- "public",
-- "members",
-- "private"
-- ],
-- "description": "Effective visibility (the page's, narrowed by its space's)."
-- }
-- }
-- }
-- },
-- "wiki.index_document.upserted": {
-- "version": "1.0.0",
-- "owner": "wiki",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.index_document.upserted.v1.json",
-- "title": "WikiIndexDocumentUpsertedPayload",
-- "description": "wiki.index_document.upserted v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks indexEvent). The OpenVibe.Search document of a published, public, listable page, sent whenever what Search should hold changed (the revision grows with every change). Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: page, id, revision: <document revision> }, visibility internal, priority important, actor service:wiki.",
-- "allOf": [
-- {
-- "$ref": "../../search/index-document.v1.json"
-- },
-- {
++ "blog": {
"type": "object",
"required": [
-- "owner",
-- "type",
-- "deleted"
++ "id",
++ "handle"
],
++ "additionalProperties": false,
"properties": {
-- "owner": {
-- "const": "wiki"
-- },
-- "type": {
-- "const": "page"
-- },
"id": {
"type": "string",
-- "pattern": "^pg_[0-9A-HJKMNP-TV-Z]{26}$"
++ "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
},
-- "deleted": {
-- "const": false
++ "handle": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
}
}
-- }
-- ]
-- }
-- },
-- "wiki.index_document.deleted": {
-- "version": "1.0.0",
-- "owner": "wiki",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/wiki.index_document.deleted.v1.json",
-- "title": "WikiIndexDocumentDeletedPayload",
-- "description": "wiki.index_document.deleted v1 (OpenVibe.Wiki server/wiki/service.js sync, via openvibe-publishing/index-hooks indexEvent). A Search tombstone: the page is no longer published, public and listable, so Search drops it at this revision or older. Only sent for a page Search was sent before. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: page, id, revision }, visibility internal, priority important, actor service:wiki.",
-- "type": "object",
-- "required": [
-- "type",
-- "id",
-- "revision"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "type": {
-- "const": "page"
-- },
-- "id": {
-- "type": "string",
-- "pattern": "^pg_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "revision": {
-- "type": "integer",
-- "minimum": 1,
-- "maximum": 9007199254740991,
-- "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
-- }
-- }
-- }
-- },
-- "live.index_document.upserted": {
-- "version": "1.0.0",
-- "owner": "live",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/live.index_document.upserted.v1.json",
-- "title": "LiveIndexDocumentUpsertedPayload",
-- "description": "live.index_document.upserted v1 (OpenVibe.Live server/events/search-documents.js; roadmap WS-O task 10). The OpenVibe.Search document of one of Live's canonical pages: a streamer's channel (type channel, openvibe.live/@username: its name, bio, what it streams and whether it is live), or a public VOD or clip of a Live stream (types vod and clip, openvibe.live/vod/<id> and /clip/<id>: title, channel, AI overview and transcript; the id is the Media id; AI clips are noindex). Sent whenever what Search should hold changed; a banned channel, or a VOD or clip that is deleted, made private or failed, gets a tombstone. The revision grows with every change. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type, id, revision: <document revision> }, visibility internal, actor service:live.",
-- "allOf": [
-- {
-- "$ref": "../../search/index-document.v1.json"
},
-- {
-- "type": "object",
-- "required": [
-- "owner",
-- "type",
-- "deleted"
++ "visibility": {
++ "enum": [
++ "public",
++ "unlisted",
++ "members",
++ "private"
],
-- "properties": {
-- "owner": {
-- "const": "live"
-- },
-- "type": {
-- "enum": [
-- "channel",
-- "vod",
-- "clip"
-- ]
-- },
-- "id": {
-- "type": "string",
-- "pattern": "^[0-9]{1,15}$"
-- },
-- "deleted": {
-- "const": false
-- }
-- }
++ "description": "The post's own visibility (members = entitlement-gated)."
}
-- ]
++ }
}
},
-- "live.index_document.deleted": {
++ "blog.schedule.failed": {
"version": "1.0.0",
-- "owner": "live",
++ "owner": "blog",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/live.index_document.deleted.v1.json",
-- "title": "LiveIndexDocumentDeletedPayload",
-- "description": "live.index_document.deleted v1 (OpenVibe.Live server/events/search-documents.js; roadmap WS-O task 10). A Search tombstone: Search drops the document at this revision or older. Only sent for a document Search was sent before. Envelope: subject { type, id, revision }, visibility internal, actor service:live.",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.schedule.failed.v1.json",
++ "title": "BlogScheduleFailedPayload",
++ "description": "blog.schedule.failed v1 (OpenVibe.Blog server/domain/posts.js runScheduled). A scheduled publish or unpublish gave up after its retries; a scheduled post goes back to draft. Envelope: subject { type: post, id: pst_…, revision?: <scheduled revision> }, visibility internal, priority important, actor service:blog.",
"type": "object",
"required": [
-- "type",
-- "id",
-- "revision"
++ "job_id",
++ "action",
++ "revision",
++ "run_at",
++ "attempts",
++ "error",
++ "blog_id"
],
"additionalProperties": false,
"properties": {
-- "type": {
++ "job_id": {
++ "type": "string",
++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "action": {
"enum": [
-- "channel",
-- "vod",
-- "clip"
++ "publish",
++ "unpublish"
]
},
-- "id": {
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision a publish was to make live; null for an unpublish."
++ },
++ "run_at": {
"type": "string",
-- "pattern": "^[0-9]{1,15}$"
++ "format": "date-time",
++ "description": "When the job was last due."
},
-- "revision": {
++ "attempts": {
"type": "integer",
-- "minimum": 1,
-- "maximum": 9007199254740991,
-- "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ "minimum": 0
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 1000,
++ "description": "The last error."
++ },
++ "blog_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "null when the post no longer exists."
}
}
}
},
-- "media.index_document.upserted": {
++ "blog.index_document.upserted": {
"version": "1.0.0",
-- "owner": "media",
++ "owner": "blog",
"status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/media.index_document.upserted.v1.json",
-- "title": "MediaIndexDocumentUpsertedPayload",
-- "description": "media.index_document.upserted v1 (OpenVibe.Media; roadmap WS-O task 10). The OpenVibe.Search document of a public, playable VOD or clip whose canonical page is on openvibe.media (openvibe.media/v/<id>, /c/<id>): readiness playable, visibility public, made by a person, and owned by an app other than Live (Live's own VODs and clips are live.index_document, type vod or clip, on Live's pages). Sent whenever what Search should hold changed; a VOD or clip that is deleted, made private or stops being playable gets a tombstone. The revision grows with every change. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type, id, revision: <document revision> }, visibility internal, actor service:media.",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.index_document.upserted.v1.json",
++ "title": "BlogIndexDocumentUpsertedPayload",
++ "description": "blog.index_document.upserted v1 (OpenVibe.Blog server/domain/publication.js syncIndex, via openvibe-publishing/index-hooks indexEvent). The OpenVibe.Search document of a published, public, listable post, sent whenever what Search should hold changed (the revision grows with every change). Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: post, id, revision: <document revision> }, visibility internal, priority important, actor service:blog.",
"allOf": [
{
"$ref": "../../search/index-document.v1.json"
@@@ -4594,17 +5822,14 @@
],
"properties": {
"owner": {
-- "const": "media"
++ "const": "blog"
},
"type": {
-- "enum": [
-- "vod",
-- "clip"
-- ]
++ "const": "post"
},
"id": {
"type": "string",
-- "pattern": "^[0-9]{1,15}$"
++ "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$"
},
"deleted": {
"const": false
@@@ -4614,15 +5839,15 @@
]
}
},
-- "media.index_document.deleted": {
++ "blog.index_document.deleted": {
"version": "1.0.0",
-- "owner": "media",
++ "owner": "blog",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/media.index_document.deleted.v1.json",
-- "title": "MediaIndexDocumentDeletedPayload",
-- "description": "media.index_document.deleted v1 (OpenVibe.Media; roadmap WS-O task 10). A Search tombstone: Search drops the document at this revision or older. Only sent for a document Search was sent before. Envelope: subject { type, id, revision }, visibility internal, actor service:media.",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.index_document.deleted.v1.json",
++ "title": "BlogIndexDocumentDeletedPayload",
++ "description": "blog.index_document.deleted v1 (OpenVibe.Blog server/domain/publication.js syncIndex, via openvibe-publishing/index-hooks indexEvent). A Search tombstone: the post is no longer published, public and listable, so Search drops it at this revision or older. Only sent for a post Search was sent before. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: post, id, revision }, visibility internal, priority important, actor service:blog.",
"type": "object",
"required": [
"type",
@@@ -4632,14 +5857,11 @@
"additionalProperties": false,
"properties": {
"type": {
-- "enum": [
-- "vod",
-- "clip"
-- ]
++ "const": "post"
},
"id": {
"type": "string",
-- "pattern": "^[0-9]{1,15}$"
++ "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$"
},
"revision": {
"type": "integer",
@@@ -4650,766 +5872,964 @@
}
}
},
-- "community.index_document.upserted": {
-- "version": "1.0.0",
-- "owner": "community",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/community.index_document.upserted.v1.json",
-- "title": "CommunityIndexDocumentUpsertedPayload",
-- "description": "community.index_document.upserted v1 (OpenVibe.Community; roadmap WS-O task 10). The OpenVibe.Search document of A public forum thread (in a public, open space) or a public paste on openvibe.community. Sent whenever what Search should hold changed; a thread or paste that is deleted, made private or members-only, or burns after reading gets a tombstone. The revision grows with every change. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type, id, revision: <document revision> }, visibility internal, actor service:community.",
-- "allOf": [
-- {
-- "$ref": "../../search/index-document.v1.json"
-- },
-- {
-- "type": "object",
-- "required": [
-- "owner",
-- "type",
-- "deleted"
-- ],
-- "properties": {
-- "owner": {
-- "const": "community"
-- },
-- "type": {
-- "enum": [
-- "thread",
-- "paste"
-- ]
-- },
-- "id": {
-- "type": "string",
-- "pattern": "^[A-Za-z0-9_-]{1,80}$"
-- },
-- "deleted": {
-- "const": false
-- }
-- }
-- }
-- ]
-- }
-- },
-- "community.index_document.deleted": {
++ "ai.run-request": {
"version": "1.0.0",
-- "owner": "community",
++ "owner": "ai",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/community.index_document.deleted.v1.json",
-- "title": "CommunityIndexDocumentDeletedPayload",
-- "description": "community.index_document.deleted v1 (OpenVibe.Community; roadmap WS-O task 10). A Search tombstone: Search drops the document at this revision or older. Only sent for a document Search was sent before. Envelope: subject { type, id, revision }, visibility internal, actor service:community.",
++ "$id": "https://openvibe.network/contracts/ai/run-request.v1.json",
++ "title": "AiRunRequest",
++ "description": "Body of POST /api/v1/runs on OpenVibe.AI (capability ai.run.create; ADR-015). Runs the newest active version of a workflow (or the given active or deprecated version) on input that must match that workflow version's own input schema; per-workflow input and output schemas live in AI's registry (GET /api/v1/workflows/:key), not here. The token's ns claim limits which workflow namespaces may run (403 capability.namespace_denied). ?wait=ms waits for the result: 201 finished or served from cache, 202 still queued/running (poll GET /api/v1/runs/:id), 200 an idempotent replay. Errors are problem+json: 404 workflow.not_found, 409 workflow.inactive, 409 idempotency.conflict, 413 input.too_large, 422 input.invalid, 429 quota.exceeded or queue.full with Retry-After. The direct operations POST /api/v1/{chat,generate,summarize,classify,extract,enrich,embed} take the same fields (except workflow and version) with the input fields at the top level and run workflow ai.<op>. target (EntityRef): what the output is about, part of the cache scope and a run filter. attribution (EntityRef): what the spend is attributed to for quotas and usage. on_behalf_of (SubjectRef): the person or actor the caller acts for, part of the cache scope and per-actor quotas.",
"type": "object",
"required": [
-- "type",
-- "id",
-- "revision"
++ "workflow"
],
"additionalProperties": false,
"properties": {
-- "type": {
-- "enum": [
-- "thread",
-- "paste"
-- ]
-- },
-- "id": {
++ "workflow": {
"type": "string",
-- "pattern": "^[A-Za-z0-9_-]{1,80}$"
++ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)+$",
++ "maxLength": 200,
++ "description": "Workflow key, <namespace>.<name> (live.chat_reply, news.summarize_story, network.site_copy…)."
},
-- "revision": {
++ "version": {
"type": "integer",
"minimum": 1,
-- "maximum": 9007199254740991,
-- "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ "description": "Pin a workflow version (active or deprecated). Omitted: the newest active version."
++ },
++ "input": {
++ "type": "object",
++ "description": "Workflow input; validated against the workflow version's input_schema. Default {}."
++ },
++ "target": {
++ "$ref": "../common/entity-ref.v1.json"
++ },
++ "attribution": {
++ "$ref": "../common/entity-ref.v1.json"
++ },
++ "on_behalf_of": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$",
++ "description": "Same (requester, key) returns the existing run; a different workflow or input under it is 409 idempotency.conflict. The Idempotency-Key header is used when this is absent."
++ },
++ "options": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "cache": {
++ "type": "boolean",
++ "default": true,
++ "description": "false skips the scoped cache lookup and store."
++ },
++ "debug": {
++ "type": "boolean",
++ "default": false,
++ "description": "Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on."
++ }
++ }
}
}
}
},
-- "blog.post.created": {
++ "ai.run": {
"version": "1.0.0",
-- "owner": "blog",
++ "owner": "ai",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.post.created.v1.json",
-- "title": "BlogPostCreatedPayload",
-- "description": "blog.post.created v1 (OpenVibe.Blog server/domain/posts.js create). A post exists as a draft (its first revision). Envelope: subject { type: post, id: pst_…, revision: 1 }, visibility internal, priority low, actor the person or the calling service.",
++ "$id": "https://openvibe.network/contracts/ai/run.v1.json",
++ "title": "AiRun",
++ "description": "A workflow run on OpenVibe.AI (ADR-015; server/runs.js decode): the `run` member of every run response (POST /api/v1/runs and the direct operations, GET /api/v1/runs/:id, cancel, retry) and each item of GET /api/v1/runs { runs }. Output is attributable to workflow + version + template version + route version + provider/model + run id, never to a person. A draft stays a draft: products store it with ai/ai_assisted authorship and publish nothing until a person reviews it. input keeps no inline media (replaced by data_url_sha256 and data_url_bytes) and no passthrough prompt ({ not_retained }); output is null until the run succeeded or was served from cache and then matches the workflow version's output_schema. requester is the token principal (service, app or mod). A run interrupted by a restart or shutdown ends failed with error.code run.interrupted.",
"type": "object",
"required": [
-- "blog",
-- "state",
-- "visibility",
-- "authorship"
++ "id",
++ "status",
++ "workflow",
++ "template",
++ "route",
++ "requester",
++ "on_behalf_of",
++ "attribution",
++ "source_service",
++ "target",
++ "input",
++ "output",
++ "error",
++ "synthetic",
++ "provenance",
++ "usage",
++ "citations_count",
++ "retry_of",
++ "idempotency_key",
++ "trace_id",
++ "created_at",
++ "started_at",
++ "finished_at"
],
"additionalProperties": false,
"properties": {
-- "blog": {
++ "id": {
++ "type": "string",
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "status": {
++ "enum": [
++ "queued",
++ "running",
++ "succeeded",
++ "failed",
++ "cancelled",
++ "cached"
++ ],
++ "description": "Terminal: succeeded, failed, cancelled, cached (a scoped cache hit, no provider call; provenance.cached_from names the run it reused)."
++ },
++ "workflow": {
"type": "object",
"required": [
-- "id",
-- "handle"
++ "key",
++ "version"
],
"additionalProperties": false,
"properties": {
-- "id": {
-- "type": "string",
-- "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ "key": {
++ "type": "string"
},
-- "handle": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
++ "version": {
++ "type": "integer",
++ "minimum": 1
}
}
},
-- "state": {
-- "const": "draft"
-- },
-- "visibility": {
-- "enum": [
-- "public",
-- "unlisted",
-- "members",
-- "private"
-- ]
-- },
-- "authorship": {
-- "enum": [
-- "human",
-- "ai",
-- "hybrid",
-- "imported"
++ "template": {
++ "type": [
++ "object",
++ "null"
],
-- "description": "openvibe-publishing authorship mode (not Search's vocabulary)."
++ "required": [
++ "key",
++ "version"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ }
++ },
++ "description": "The prompt template of the workflow's llm step; null for workflows without one."
},
-- "workflow": {
-- "type": "object",
++ "route": {
++ "type": [
++ "object",
++ "null"
++ ],
"required": [
-- "id",
-- "runId"
++ "key",
++ "version"
],
"additionalProperties": false,
-- "description": "The OpenVibe.AI workflow and run behind ai and hybrid posts; absent for human posts.",
"properties": {
-- "id": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
-- },
-- "runId": {
-- "type": "string",
-- "minLength": 1
++ "key": {
++ "type": "string"
},
"version": {
"type": [
"integer",
-- "string"
++ "null"
]
++ }
++ },
++ "description": "Provider route; version null while the route is unresolved or disabled."
++ },
++ "requester": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "on_behalf_of": {
++ "anyOf": [
++ {
++ "$ref": "../identity/subject-ref.v1.json"
},
-- "model": {
-- "type": "string"
++ {
++ "type": "null"
}
-- }
-- }
-- }
-- }
-- },
-- "blog.post.published": {
-- "version": "1.0.0",
-- "owner": "blog",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.post.published.v1.json",
-- "title": "BlogPostPublishedPayload",
-- "description": "blog.post.published v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a post became published (now or by its schedule), in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:blog (scheduled and system changes).",
-- "type": "object",
-- "required": [
-- "canonical_url",
-- "publication_state",
-- "indexability",
-- "blog",
-- "visibility"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "canonical_url": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https?://",
-- "maxLength": 2048
++ ]
},
-- "publication_state": {
-- "const": "published"
++ "attribution": {
++ "anyOf": [
++ {
++ "$ref": "../common/entity-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
},
-- "indexability": {
-- "type": "object",
-- "required": [
-- "decision",
-- "reasons"
++ "source_service": {
++ "type": [
++ "string",
++ "null"
],
-- "additionalProperties": false,
-- "properties": {
-- "decision": {
-- "enum": [
-- "index",
-- "noindex"
-- ]
++ "description": "The requester's service id when the requester is a service."
++ },
++ "target": {
++ "anyOf": [
++ {
++ "$ref": "../common/entity-ref.v1.json"
},
-- "reasons": {
-- "type": "array",
-- "maxItems": 20,
-- "uniqueItems": true,
-- "items": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,63}$"
-- }
++ {
++ "type": "null"
}
-- }
++ ]
},
-- "blog": {
++ "input": {
"type": "object",
++ "description": "The retained input (see the description)."
++ },
++ "output": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "Workflow output, or null."
++ },
++ "error": {
++ "type": [
++ "object",
++ "null"
++ ],
"required": [
-- "id",
-- "handle"
++ "code",
++ "detail"
],
"additionalProperties": false,
"properties": {
-- "id": {
++ "code": {
"type": "string",
-- "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
++ "description": "run.cancelled, run.interrupted, run.internal, output.invalid, output.empty, provider.unavailable, route.unavailable, source.unavailable, media.unresolvable…"
},
-- "handle": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
++ "detail": {
++ "type": [
++ "string",
++ "null"
++ ]
}
}
},
-- "visibility": {
-- "enum": [
-- "public",
-- "unlisted",
-- "members",
-- "private"
-- ],
-- "description": "The post's own visibility (members = entitlement-gated)."
-- }
-- }
-- }
-- },
-- "blog.post.updated": {
-- "version": "1.0.0",
-- "owner": "blog",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.post.updated.v1.json",
-- "title": "BlogPostUpdatedPayload",
-- "description": "blog.post.updated v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published post got a new published revision, visibility or URL, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility public when the document is public and the gate lets it be listed, otherwise internal, priority important, actor the person or service:blog (scheduled and system changes).",
-- "type": "object",
-- "required": [
-- "canonical_url",
-- "publication_state",
-- "indexability",
-- "blog",
-- "visibility"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "canonical_url": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https?://",
-- "maxLength": 2048
-- },
-- "publication_state": {
-- "const": "published"
++ "synthetic": {
++ "type": "boolean",
++ "description": "Produced by the stub provider; never served to production callers and never cached."
},
-- "indexability": {
++ "provenance": {
"type": "object",
++ "additionalProperties": false,
"required": [
-- "decision",
-- "reasons"
++ "origin",
++ "workflow",
++ "workflow_version",
++ "template_version",
++ "route",
++ "route_version",
++ "provider",
++ "model",
++ "fallback_used",
++ "run_id",
++ "cached_from",
++ "synthetic"
],
-- "additionalProperties": false,
"properties": {
-- "decision": {
-- "enum": [
-- "index",
-- "noindex"
++ "origin": {
++ "const": "ai"
++ },
++ "workflow": {
++ "type": "string"
++ },
++ "workflow_version": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "template_version": {
++ "type": [
++ "integer",
++ "null"
]
},
-- "reasons": {
-- "type": "array",
-- "maxItems": 20,
-- "uniqueItems": true,
-- "items": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,63}$"
-- }
++ "route": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "route_version": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "fallback_used": {
++ "type": "boolean",
++ "description": "The primary provider was degraded and the run fell back (ADR-015: fallbacks are recorded)."
++ },
++ "run_id": {
++ "type": "string",
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "cached_from": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "synthetic": {
++ "type": "boolean"
}
}
},
-- "blog": {
++ "usage": {
"type": "object",
"required": [
-- "id",
-- "handle"
++ "tokens_in",
++ "tokens_out",
++ "cost_usd",
++ "attempts"
],
"additionalProperties": false,
"properties": {
-- "id": {
-- "type": "string",
-- "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ "tokens_in": {
++ "type": "integer",
++ "minimum": 0
},
-- "handle": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
++ "tokens_out": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Provider calls made (skipped routes not counted)."
}
}
},
-- "visibility": {
-- "enum": [
-- "public",
-- "unlisted",
-- "members",
-- "private"
-- ],
-- "description": "The post's own visibility (members = entitlement-gated)."
-- }
-- }
-- }
-- },
-- "blog.post.unpublished": {
-- "version": "1.0.0",
-- "owner": "blog",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.post.unpublished.v1.json",
-- "title": "BlogPostUnpublishedPayload",
-- "description": "blog.post.unpublished v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a published post stopped being published, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:blog (scheduled and system changes).",
-- "type": "object",
-- "required": [
-- "canonical_url",
-- "publication_state",
-- "indexability",
-- "blog",
-- "visibility"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "canonical_url": {
++ "citations_count": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Citations stored for the run (GET /api/v1/runs/:id/citations): the sources the workflow was given, and any the requester attached later."
++ },
++ "retry_of": {
"type": [
"string",
"null"
],
-- "description": "null: the event carries a tombstone, which has no URL."
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
},
-- "publication_state": {
-- "const": "unpublished"
++ "idempotency_key": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- "indexability": {
++ "trace_id": {
"type": [
-- "object",
++ "string",
"null"
-- ],
-- "required": [
-- "decision",
-- "reasons"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "decision": {
-- "enum": [
-- "index",
-- "noindex"
-- ]
-- },
-- "reasons": {
-- "type": "array",
-- "maxItems": 20,
-- "uniqueItems": true,
-- "items": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,63}$"
-- }
-- }
-- },
-- "description": "The last gate decision, or null when there is none."
++ ]
},
-- "blog": {
-- "type": "object",
-- "required": [
-- "id",
-- "handle"
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "started_at": {
++ "type": [
++ "string",
++ "null"
],
-- "additionalProperties": false,
-- "properties": {
-- "id": {
-- "type": "string",
-- "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "handle": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
-- }
-- }
++ "format": "date-time"
},
-- "visibility": {
-- "enum": [
-- "public",
-- "unlisted",
-- "members",
-- "private"
++ "finished_at": {
++ "type": [
++ "string",
++ "null"
],
-- "description": "The post's own visibility (members = entitlement-gated)."
++ "format": "date-time"
}
}
}
},
-- "blog.post.deleted": {
++ "ai.run.queued": {
"version": "1.0.0",
-- "owner": "blog",
-- "status": "active",
++ "owner": "ai",
++ "status": "planned",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.post.deleted.v1.json",
-- "title": "BlogPostDeletedPayload",
-- "description": "blog.post.deleted v1 (OpenVibe.Blog server/domain/publication.js afterChange, via openvibe-publishing/index-hooks publicationEvent). Emitted when a post was soft-deleted, in the transaction of the change. For subscribers other than Search: canonical URL, publication state and Search-shaped indexability, never the body (Search gets that from blog.index_document.*). Envelope: subject { type: post, id: pst_…, revision: <published revision, 0 if none> }, visibility internal, priority important, actor the person or service:blog (scheduled and system changes).",
++ "$id": "https://openvibe.network/contracts/events/payloads/ai.run.queued.v1.json",
++ "title": "AiRunQueuedPayload",
++ "description": "ai.run.queued v1 (OpenVibe.AI server/runs.js create). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A run was stored queued (it then runs inline within ?wait or on the worker). Not published for a cache hit (ai.run.cached) or a create refused by quota or a full queue. A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority low, actor service:ai.",
"type": "object",
"required": [
-- "canonical_url",
-- "publication_state",
-- "indexability",
-- "blog",
-- "visibility"
++ "run_id",
++ "status",
++ "workflow",
++ "requester",
++ "on_behalf_of",
++ "target",
++ "attribution",
++ "provider",
++ "model",
++ "fallback_used",
++ "synthetic",
++ "cached_from",
++ "usage",
++ "error",
++ "citations_count",
++ "retry_of",
++ "created_at",
++ "finished_at"
],
"additionalProperties": false,
"properties": {
-- "canonical_url": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "description": "null: the event carries a tombstone, which has no URL."
++ "run_id": {
++ "type": "string",
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
},
-- "publication_state": {
-- "const": "deleted"
++ "status": {
++ "const": "queued"
},
-- "indexability": {
-- "type": [
-- "object",
-- "null"
-- ],
++ "workflow": {
++ "type": "object",
"required": [
-- "decision",
-- "reasons"
++ "key",
++ "version"
],
"additionalProperties": false,
"properties": {
-- "decision": {
-- "enum": [
-- "index",
-- "noindex"
-- ]
++ "key": {
++ "type": "string"
},
-- "reasons": {
-- "type": "array",
-- "maxItems": 20,
-- "uniqueItems": true,
-- "items": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,63}$"
-- }
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ },
++ "requester": {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ "on_behalf_of": {
++ "anyOf": [
++ {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "target": {
++ "anyOf": [
++ {
++ "$ref": "../../common/entity-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "attribution": {
++ "anyOf": [
++ {
++ "$ref": "../../common/entity-ref.v1.json"
++ },
++ {
++ "type": "null"
}
-- },
-- "description": "The last gate decision, or null when there is none."
++ ]
},
-- "blog": {
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Provider that produced the output; null before a provider call and for cached runs."
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "fallback_used": {
++ "type": "boolean"
++ },
++ "synthetic": {
++ "type": "boolean",
++ "description": "Stub provider output."
++ },
++ "cached_from": {
++ "type": "null"
++ },
++ "usage": {
"type": "object",
"required": [
-- "id",
-- "handle"
++ "tokens_in",
++ "tokens_out",
++ "cost_usd",
++ "attempts"
],
"additionalProperties": false,
"properties": {
-- "id": {
-- "type": "string",
-- "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$"
++ "tokens_in": {
++ "type": "integer",
++ "minimum": 0
},
-- "handle": {
-- "type": "string",
-- "pattern": "^[a-z0-9][a-z0-9_-]{1,39}$"
++ "tokens_out": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
}
}
},
-- "visibility": {
-- "enum": [
-- "public",
-- "unlisted",
-- "members",
-- "private"
++ "error": {
++ "type": "null"
++ },
++ "citations_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "retry_of": {
++ "type": [
++ "string",
++ "null"
],
-- "description": "The post's own visibility (members = entitlement-gated)."
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "finished_at": {
++ "type": "null"
}
}
}
},
-- "blog.schedule.failed": {
++ "ai.run.succeeded": {
"version": "1.0.0",
-- "owner": "blog",
-- "status": "active",
++ "owner": "ai",
++ "status": "planned",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.schedule.failed.v1.json",
-- "title": "BlogScheduleFailedPayload",
-- "description": "blog.schedule.failed v1 (OpenVibe.Blog server/domain/posts.js runScheduled). A scheduled publish or unpublish gave up after its retries; a scheduled post goes back to draft. Envelope: subject { type: post, id: pst_…, revision?: <scheduled revision> }, visibility internal, priority important, actor service:blog.",
++ "$id": "https://openvibe.network/contracts/events/payloads/ai.run.succeeded.v1.json",
++ "title": "AiRunSucceededPayload",
++ "description": "ai.run.succeeded v1 (OpenVibe.AI server/runs.js execute). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A run finished with output that matched the workflow output schema. The output itself is not in the event: a consumer that needs it reads GET /api/v1/runs/:id with ai.run.read (own runs only). A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority important, actor service:ai.",
"type": "object",
"required": [
-- "job_id",
-- "action",
-- "revision",
-- "run_at",
-- "attempts",
++ "run_id",
++ "status",
++ "workflow",
++ "requester",
++ "on_behalf_of",
++ "target",
++ "attribution",
++ "provider",
++ "model",
++ "fallback_used",
++ "synthetic",
++ "cached_from",
++ "usage",
"error",
-- "blog_id"
++ "citations_count",
++ "retry_of",
++ "created_at",
++ "finished_at"
],
"additionalProperties": false,
"properties": {
-- "job_id": {
++ "run_id": {
"type": "string",
-- "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
},
-- "action": {
-- "enum": [
-- "publish",
-- "unpublish"
-- ]
++ "status": {
++ "const": "succeeded"
},
-- "revision": {
-- "type": [
-- "integer",
-- "null"
++ "workflow": {
++ "type": "object",
++ "required": [
++ "key",
++ "version"
],
-- "minimum": 1,
-- "description": "The revision a publish was to make live; null for an unpublish."
++ "additionalProperties": false,
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
},
-- "run_at": {
-- "type": "string",
-- "format": "date-time",
-- "description": "When the job was last due."
++ "requester": {
++ "$ref": "../../identity/subject-ref.v1.json"
},
-- "attempts": {
-- "type": "integer",
-- "minimum": 0
++ "on_behalf_of": {
++ "anyOf": [
++ {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
},
-- "error": {
++ "target": {
++ "anyOf": [
++ {
++ "$ref": "../../common/entity-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "attribution": {
++ "anyOf": [
++ {
++ "$ref": "../../common/entity-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "provider": {
"type": [
"string",
"null"
],
-- "maxLength": 1000,
-- "description": "The last error."
++ "description": "Provider that produced the output; null before a provider call and for cached runs."
},
-- "blog_id": {
++ "model": {
"type": [
"string",
"null"
-- ],
-- "pattern": "^blg_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "null when the post no longer exists."
-- }
-- }
-- }
-- },
-- "blog.index_document.upserted": {
-- "version": "1.0.0",
-- "owner": "blog",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.index_document.upserted.v1.json",
-- "title": "BlogIndexDocumentUpsertedPayload",
-- "description": "blog.index_document.upserted v1 (OpenVibe.Blog server/domain/publication.js syncIndex, via openvibe-publishing/index-hooks indexEvent). The OpenVibe.Search document of a published, public, listable post, sent whenever what Search should hold changed (the revision grows with every change). Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: post, id, revision: <document revision> }, visibility internal, priority important, actor service:blog.",
-- "allOf": [
-- {
-- "$ref": "../../search/index-document.v1.json"
++ ]
},
-- {
++ "fallback_used": {
++ "type": "boolean"
++ },
++ "synthetic": {
++ "type": "boolean",
++ "description": "Stub provider output."
++ },
++ "cached_from": {
++ "type": "null"
++ },
++ "usage": {
"type": "object",
"required": [
-- "owner",
-- "type",
-- "deleted"
++ "tokens_in",
++ "tokens_out",
++ "cost_usd",
++ "attempts"
],
++ "additionalProperties": false,
"properties": {
-- "owner": {
-- "const": "blog"
++ "tokens_in": {
++ "type": "integer",
++ "minimum": 0
},
-- "type": {
-- "const": "post"
++ "tokens_out": {
++ "type": "integer",
++ "minimum": 0
},
-- "id": {
-- "type": "string",
-- "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$"
++ "cost_usd": {
++ "type": "number",
++ "minimum": 0
},
-- "deleted": {
-- "const": false
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
}
}
-- }
-- ]
-- }
-- },
-- "blog.index_document.deleted": {
-- "version": "1.0.0",
-- "owner": "blog",
-- "status": "active",
-- "schema": {
-- "$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/blog.index_document.deleted.v1.json",
-- "title": "BlogIndexDocumentDeletedPayload",
-- "description": "blog.index_document.deleted v1 (OpenVibe.Blog server/domain/publication.js syncIndex, via openvibe-publishing/index-hooks indexEvent). A Search tombstone: the post is no longer published, public and listable, so Search drops it at this revision or older. Only sent for a post Search was sent before. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: post, id, revision }, visibility internal, priority important, actor service:blog.",
-- "type": "object",
-- "required": [
-- "type",
-- "id",
-- "revision"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "type": {
-- "const": "post"
},
-- "id": {
-- "type": "string",
-- "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$"
++ "error": {
++ "type": "null"
},
-- "revision": {
++ "citations_count": {
"type": "integer",
-- "minimum": 1,
-- "maximum": 9007199254740991,
-- "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ "minimum": 0
++ },
++ "retry_of": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "finished_at": {
++ "type": "string",
++ "format": "date-time"
}
}
}
},
-- "ai.run-request": {
++ "ai.run.failed": {
"version": "1.0.0",
"owner": "ai",
-- "status": "active",
++ "status": "planned",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/ai/run-request.v1.json",
-- "title": "AiRunRequest",
-- "description": "Body of POST /api/v1/runs on OpenVibe.AI (capability ai.run.create; ADR-015). Runs the newest active version of a workflow (or the given active or deprecated version) on input that must match that workflow version's own input schema; per-workflow input and output schemas live in AI's registry (GET /api/v1/workflows/:key), not here. The token's ns claim limits which workflow namespaces may run (403 capability.namespace_denied). ?wait=ms waits for the result: 201 finished or served from cache, 202 still queued/running (poll GET /api/v1/runs/:id), 200 an idempotent replay. Errors are problem+json: 404 workflow.not_found, 409 workflow.inactive, 409 idempotency.conflict, 413 input.too_large, 422 input.invalid, 429 quota.exceeded or queue.full with Retry-After. The direct operations POST /api/v1/{chat,generate,summarize,classify,extract,enrich,embed} take the same fields (except workflow and version) with the input fields at the top level and run workflow ai.<op>. target (EntityRef): what the output is about, part of the cache scope and a run filter. attribution (EntityRef): what the spend is attributed to for quotas and usage. on_behalf_of (SubjectRef): the person or actor the caller acts for, part of the cache scope and per-actor quotas.",
++ "$id": "https://openvibe.network/contracts/events/payloads/ai.run.failed.v1.json",
++ "title": "AiRunFailedPayload",
++ "description": "ai.run.failed v1 (OpenVibe.AI server/runs.js execute / recoverInterrupted). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A run ended failed: a provider, route, source or output error, run.internal, or run.interrupted after a restart or shutdown (retryable with POST /api/v1/runs/:id/retry unless its input was not retained). A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority important, actor service:ai.",
"type": "object",
"required": [
-- "workflow"
++ "run_id",
++ "status",
++ "workflow",
++ "requester",
++ "on_behalf_of",
++ "target",
++ "attribution",
++ "provider",
++ "model",
++ "fallback_used",
++ "synthetic",
++ "cached_from",
++ "usage",
++ "error",
++ "citations_count",
++ "retry_of",
++ "created_at",
++ "finished_at"
],
"additionalProperties": false,
"properties": {
-- "workflow": {
++ "run_id": {
"type": "string",
-- "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)+$",
-- "maxLength": 200,
-- "description": "Workflow key, <namespace>.<name> (live.chat_reply, news.summarize_story, network.site_copy…)."
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
},
-- "version": {
-- "type": "integer",
-- "minimum": 1,
-- "description": "Pin a workflow version (active or deprecated). Omitted: the newest active version."
++ "status": {
++ "const": "failed"
},
-- "input": {
++ "workflow": {
"type": "object",
-- "description": "Workflow input; validated against the workflow version's input_schema. Default {}."
++ "required": [
++ "key",
++ "version"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ },
++ "requester": {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ "on_behalf_of": {
++ "anyOf": [
++ {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
},
"target": {
-- "$ref": "../common/entity-ref.v1.json"
++ "anyOf": [
++ {
++ "$ref": "../../common/entity-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
},
"attribution": {
-- "$ref": "../common/entity-ref.v1.json"
++ "anyOf": [
++ {
++ "$ref": "../../common/entity-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
},
-- "on_behalf_of": {
-- "$ref": "../identity/subject-ref.v1.json"
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Provider that produced the output; null before a provider call and for cached runs."
},
-- "idempotency_key": {
-- "type": "string",
-- "pattern": "^[A-Za-z0-9._:-]{8,128}$",
-- "description": "Same (requester, key) returns the existing run; a different workflow or input under it is 409 idempotency.conflict. The Idempotency-Key header is used when this is absent."
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- "options": {
++ "fallback_used": {
++ "type": "boolean"
++ },
++ "synthetic": {
++ "type": "boolean",
++ "description": "Stub provider output."
++ },
++ "cached_from": {
++ "type": "null"
++ },
++ "usage": {
"type": "object",
++ "required": [
++ "tokens_in",
++ "tokens_out",
++ "cost_usd",
++ "attempts"
++ ],
"additionalProperties": false,
"properties": {
-- "cache": {
-- "type": "boolean",
-- "default": true,
-- "description": "false skips the scoped cache lookup and store."
++ "tokens_in": {
++ "type": "integer",
++ "minimum": 0
},
-- "debug": {
-- "type": "boolean",
-- "default": false,
-- "description": "Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on."
++ "tokens_out": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "error": {
++ "type": "object",
++ "required": [
++ "code",
++ "detail"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
++ },
++ "detail": {
++ "type": [
++ "string",
++ "null"
++ ]
}
}
++ },
++ "citations_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "retry_of": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "finished_at": {
++ "type": "string",
++ "format": "date-time"
}
}
}
},
-- "ai.run": {
++ "ai.run.cached": {
"version": "1.0.0",
"owner": "ai",
-- "status": "active",
++ "status": "planned",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/ai/run.v1.json",
-- "title": "AiRun",
-- "description": "A workflow run on OpenVibe.AI (ADR-015; server/runs.js decode): the `run` member of every run response (POST /api/v1/runs and the direct operations, GET /api/v1/runs/:id, cancel, retry) and each item of GET /api/v1/runs { runs }. Output is attributable to workflow + version + template version + route version + provider/model + run id, never to a person. A draft stays a draft: products store it with ai/ai_assisted authorship and publish nothing until a person reviews it. input keeps no inline media (replaced by data_url_sha256 and data_url_bytes) and no passthrough prompt ({ not_retained }); output is null until the run succeeded or was served from cache and then matches the workflow version's output_schema. requester is the token principal (service, app or mod). A run interrupted by a restart or shutdown ends failed with error.code run.interrupted.",
++ "$id": "https://openvibe.network/contracts/events/payloads/ai.run.cached.v1.json",
++ "title": "AiRunCachedPayload",
++ "description": "ai.run.cached v1 (OpenVibe.AI server/runs.js create). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A create was served from the scoped cache: no provider call, cached_from names the run whose output was reused. A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority low, actor service:ai.",
"type": "object",
"required": [
-- "id",
++ "run_id",
"status",
"workflow",
-- "template",
-- "route",
"requester",
"on_behalf_of",
-- "attribution",
-- "source_service",
"target",
-- "input",
-- "output",
-- "error",
++ "attribution",
++ "provider",
++ "model",
++ "fallback_used",
"synthetic",
-- "provenance",
++ "cached_from",
"usage",
++ "error",
"citations_count",
"retry_of",
-- "idempotency_key",
-- "trace_id",
"created_at",
-- "started_at",
"finished_at"
],
"additionalProperties": false,
"properties": {
-- "id": {
++ "run_id": {
"type": "string",
"pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
},
"status": {
-- "enum": [
-- "queued",
-- "running",
-- "succeeded",
-- "failed",
-- "cancelled",
-- "cached"
-- ],
-- "description": "Terminal: succeeded, failed, cancelled, cached (a scoped cache hit, no provider call; provenance.cached_from names the run it reused)."
++ "const": "cached"
},
"workflow": {
"type": "object",
@@@ -5428,958 +6848,1329 @@
}
}
},
-- "template": {
-- "type": [
-- "object",
-- "null"
-- ],
-- "required": [
-- "key",
-- "version"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "key": {
-- "type": "string"
-- },
-- "version": {
-- "type": [
-- "integer",
-- "null"
-- ]
-- }
-- },
-- "description": "The prompt template of the workflow's llm step; null for workflows without one."
-- },
-- "route": {
-- "type": [
-- "object",
-- "null"
-- ],
-- "required": [
-- "key",
-- "version"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "key": {
-- "type": "string"
-- },
-- "version": {
-- "type": [
-- "integer",
-- "null"
-- ]
-- }
-- },
-- "description": "Provider route; version null while the route is unresolved or disabled."
-- },
"requester": {
-- "$ref": "../identity/subject-ref.v1.json"
++ "$ref": "../../identity/subject-ref.v1.json"
},
"on_behalf_of": {
"anyOf": [
{
-- "$ref": "../identity/subject-ref.v1.json"
++ "$ref": "../../identity/subject-ref.v1.json"
},
{
"type": "null"
}
]
},
-- "attribution": {
++ "target": {
"anyOf": [
{
-- "$ref": "../common/entity-ref.v1.json"
++ "$ref": "../../common/entity-ref.v1.json"
},
{
"type": "null"
}
]
},
-- "source_service": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "description": "The requester's service id when the requester is a service."
-- },
-- "target": {
++ "attribution": {
"anyOf": [
{
-- "$ref": "../common/entity-ref.v1.json"
++ "$ref": "../../common/entity-ref.v1.json"
},
{
"type": "null"
}
]
},
-- "input": {
-- "type": "object",
-- "description": "The retained input (see the description)."
-- },
-- "output": {
++ "provider": {
"type": [
-- "object",
++ "string",
"null"
],
-- "description": "Workflow output, or null."
++ "description": "Provider that produced the output; null before a provider call and for cached runs."
},
-- "error": {
++ "model": {
"type": [
-- "object",
++ "string",
"null"
-- ],
++ ]
++ },
++ "fallback_used": {
++ "type": "boolean"
++ },
++ "synthetic": {
++ "type": "boolean",
++ "description": "Stub provider output."
++ },
++ "cached_from": {
++ "type": "string",
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "usage": {
++ "type": "object",
"required": [
-- "code",
-- "detail"
++ "tokens_in",
++ "tokens_out",
++ "cost_usd",
++ "attempts"
],
"additionalProperties": false,
"properties": {
-- "code": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
-- "description": "run.cancelled, run.interrupted, run.internal, output.invalid, output.empty, provider.unavailable, route.unavailable, source.unavailable, media.unresolvable…"
++ "tokens_in": {
++ "type": "integer",
++ "minimum": 0
},
-- "detail": {
++ "tokens_out": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "error": {
++ "type": "null"
++ },
++ "citations_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "retry_of": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "finished_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "ai.definition-version-request": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/definition-version-request.v1.json",
++ "title": "AiDefinitionVersionRequest",
++ "description": "ai.definition-version-request@1: bodies of ai.workflow.manage on OpenVibe.AI. An edit is always a new version (the previous one stays readable); fields left out are taken from the newest existing version. POST /api/v1/templates/:key/versions: a prompt template (input_schema and output_schema must compile as JSON Schema; visibility public, first-party or internal). POST /api/v1/workflows/:key/versions (key <namespace>.<name>): steps (at least one; kind llm (naming an existing template), passthrough, transcribe or embed), schemas, default route, cache mode. POST /api/v1/routes/:key/versions: primary { provider, model } and fallbacks (every provider must exist), or alias_of another route. POST …/:key/versions/:version/status: { status } (templates and workflows draft|active|deprecated|archived; routes active|disabled). Refusals are 422 ai.invalid.",
++ "anyOf": [
++ {
++ "type": "object",
++ "properties": {
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "input_schema": {
++ "type": "object",
++ "description": "A JSON Schema the run input must match."
++ },
++ "output_schema": {
++ "type": "object",
++ "description": "A JSON Schema the output must match."
++ },
++ "system_prompt": {
++ "type": "string"
++ },
++ "user_prompt": {
++ "type": "string"
++ },
++ "default_route": {
"type": [
"string",
"null"
]
++ },
++ "owner": {
++ "type": "string"
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "first-party",
++ "internal"
++ ]
++ },
++ "status": {
++ "enum": [
++ "draft",
++ "active",
++ "deprecated",
++ "archived"
++ ]
++ },
++ "metadata": {
++ "type": "object"
}
}
},
-- "synthetic": {
-- "type": "boolean",
-- "description": "Produced by the stub provider; never served to production callers and never cached."
-- },
-- "provenance": {
++ {
"type": "object",
-- "additionalProperties": false,
-- "required": [
-- "origin",
-- "workflow",
-- "workflow_version",
-- "template_version",
-- "route",
-- "route_version",
-- "provider",
-- "model",
-- "fallback_used",
-- "run_id",
-- "cached_from",
-- "synthetic"
-- ],
"properties": {
-- "origin": {
-- "const": "ai"
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "namespace": {
++ "type": "string"
++ },
++ "input_schema": {
++ "type": "object",
++ "description": "A JSON Schema."
++ },
++ "output_schema": {
++ "type": "object",
++ "description": "A JSON Schema."
++ },
++ "steps": {
++ "type": "array",
++ "minItems": 1,
++ "items": {
++ "type": "object",
++ "required": [
++ "kind"
++ ],
++ "properties": {
++ "kind": {
++ "enum": [
++ "llm",
++ "passthrough",
++ "transcribe",
++ "embed"
++ ]
++ },
++ "template": {
++ "type": "string"
++ }
++ }
++ }
},
-- "workflow": {
-- "type": "string"
++ "default_route": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- "workflow_version": {
-- "type": "integer",
-- "minimum": 1
++ "cache_mode": {
++ "enum": [
++ "none",
++ "private",
++ "service"
++ ]
},
-- "template_version": {
++ "cache_ttl_sec": {
"type": [
"integer",
"null"
]
},
-- "route": {
-- "type": [
-- "string",
-- "null"
++ "status": {
++ "enum": [
++ "draft",
++ "active",
++ "deprecated",
++ "archived"
]
},
-- "route_version": {
++ "metadata": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "properties": {
++ "primary": {
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "properties": {
++ "provider": {
++ "type": "string"
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "fallbacks": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "properties": {
++ "provider": {
++ "type": "string"
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ },
++ "options": {
++ "type": "object"
++ },
++ "max_output_tokens": {
"type": [
"integer",
"null"
]
},
-- "provider": {
-- "type": [
-- "string",
-- "null"
++ "response_format": {
++ "enum": [
++ "text",
++ "json"
]
},
-- "model": {
++ "timeout_ms": {
"type": [
-- "string",
++ "integer",
"null"
]
},
-- "fallback_used": {
-- "type": "boolean",
-- "description": "The primary provider was degraded and the run fell back (ADR-015: fallbacks are recorded)."
-- },
-- "run_id": {
-- "type": "string",
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "cached_from": {
++ "alias_of": {
"type": [
"string",
"null"
-- ],
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "synthetic": {
-- "type": "boolean"
-- }
-- }
-- },
-- "usage": {
-- "type": "object",
-- "required": [
-- "tokens_in",
-- "tokens_out",
-- "cost_usd",
-- "attempts"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "tokens_in": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "tokens_out": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "cost_usd": {
-- "type": "number",
-- "minimum": 0
++ ]
},
-- "attempts": {
-- "type": "integer",
-- "minimum": 0,
-- "description": "Provider calls made (skipped routes not counted)."
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
}
}
-- },
-- "citations_count": {
-- "type": "integer",
-- "minimum": 0,
-- "description": "Citations stored for the run (GET /api/v1/runs/:id/citations): the sources the workflow was given, and any the requester attached later."
-- },
-- "retry_of": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "idempotency_key": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "trace_id": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "created_at": {
-- "type": "string",
-- "format": "date-time"
-- },
-- "started_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- },
-- "finished_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
}
-- }
++ ]
}
},
-- "ai.run.queued": {
++ "ai.definition-version-result": {
"version": "1.0.0",
"owner": "ai",
-- "status": "planned",
++ "status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/ai.run.queued.v1.json",
-- "title": "AiRunQueuedPayload",
-- "description": "ai.run.queued v1 (OpenVibe.AI server/runs.js create). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A run was stored queued (it then runs inline within ?wait or on the worker). Not published for a cache hit (ai.run.cached) or a create refused by quota or a full queue. A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority low, actor service:ai.",
-- "type": "object",
-- "required": [
-- "run_id",
-- "status",
-- "workflow",
-- "requester",
-- "on_behalf_of",
-- "target",
-- "attribution",
-- "provider",
-- "model",
-- "fallback_used",
-- "synthetic",
-- "cached_from",
-- "usage",
-- "error",
-- "citations_count",
-- "retry_of",
-- "created_at",
-- "finished_at"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "run_id": {
-- "type": "string",
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "status": {
-- "const": "queued"
-- },
-- "workflow": {
++ "$id": "https://openvibe.network/contracts/ai/definition-version-result.v1.json",
++ "title": "AiDefinitionVersionResult",
++ "description": "ai.definition-version-result@1: answers of ai.workflow.manage on OpenVibe.AI. POST …/:key/versions → 201 { template | workflow | route } (the new version, whole: prompts and schemas for a template, steps for a workflow, primary/fallbacks for a route); POST …/versions/:version/status → { template | workflow | route: { key, version, status } }.",
++ "anyOf": [
++ {
"type": "object",
"required": [
-- "key",
-- "version"
++ "template"
],
-- "additionalProperties": false,
"properties": {
-- "key": {
-- "type": "string"
-- },
-- "version": {
-- "type": "integer",
-- "minimum": 1
-- }
-- }
-- },
-- "requester": {
-- "$ref": "../../identity/subject-ref.v1.json"
-- },
-- "on_behalf_of": {
-- "anyOf": [
-- {
-- "$ref": "../../identity/subject-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
-- ]
-- },
-- "target": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
-- ]
-- },
-- "attribution": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
-- },
-- {
-- "type": "null"
++ "template": {
++ "type": "object",
++ "required": [
++ "key",
++ "version",
++ "status"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "status": {
++ "type": "string"
++ }
++ }
}
-- ]
-- },
-- "provider": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "description": "Provider that produced the output; null before a provider call and for cached runs."
-- },
-- "model": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "fallback_used": {
-- "type": "boolean"
-- },
-- "synthetic": {
-- "type": "boolean",
-- "description": "Stub provider output."
-- },
-- "cached_from": {
-- "type": "null"
++ }
},
-- "usage": {
++ {
"type": "object",
"required": [
-- "tokens_in",
-- "tokens_out",
-- "cost_usd",
-- "attempts"
++ "workflow"
],
-- "additionalProperties": false,
"properties": {
-- "tokens_in": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "tokens_out": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "cost_usd": {
-- "type": "number",
-- "minimum": 0
-- },
-- "attempts": {
-- "type": "integer",
-- "minimum": 0
++ "workflow": {
++ "type": "object",
++ "required": [
++ "key",
++ "version",
++ "status"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "status": {
++ "type": "string"
++ }
++ }
}
}
},
-- "error": {
-- "type": "null"
-- },
-- "citations_count": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "retry_of": {
-- "type": [
-- "string",
-- "null"
++ {
++ "type": "object",
++ "required": [
++ "route"
],
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "created_at": {
-- "type": "string",
-- "format": "date-time"
-- },
-- "finished_at": {
-- "type": "null"
++ "properties": {
++ "route": {
++ "type": "object",
++ "required": [
++ "key",
++ "version",
++ "status"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "version": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "status": {
++ "type": "string"
++ }
++ }
++ }
++ }
}
-- }
++ ]
}
},
-- "ai.run.succeeded": {
++ "ai.model": {
"version": "1.0.0",
"owner": "ai",
-- "status": "planned",
++ "status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/ai.run.succeeded.v1.json",
-- "title": "AiRunSucceededPayload",
-- "description": "ai.run.succeeded v1 (OpenVibe.AI server/runs.js execute). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A run finished with output that matched the workflow output schema. The output itself is not in the event: a consumer that needs it reads GET /api/v1/runs/:id with ai.run.read (own runs only). A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority important, actor service:ai.",
++ "$id": "https://openvibe.network/contracts/ai/model.v1.json",
++ "title": "AiModel",
++ "description": "ai.model@1: one model of a provider in OpenVibe.AI's registry (server/registry.js decodeModel): type, status, limits, per-million-token costs and what it supports.",
"type": "object",
"required": [
-- "run_id",
++ "provider_key",
++ "model_key",
++ "type",
"status",
-- "workflow",
-- "requester",
-- "on_behalf_of",
-- "target",
-- "attribution",
-- "provider",
-- "model",
-- "fallback_used",
-- "synthetic",
-- "cached_from",
-- "usage",
-- "error",
-- "citations_count",
-- "retry_of",
-- "created_at",
-- "finished_at"
++ "cost",
++ "supports"
],
-- "additionalProperties": false,
"properties": {
-- "run_id": {
-- "type": "string",
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "status": {
-- "const": "succeeded"
-- },
-- "workflow": {
-- "type": "object",
-- "required": [
-- "key",
-- "version"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "key": {
-- "type": "string"
-- },
-- "version": {
-- "type": "integer",
-- "minimum": 1
-- }
-- }
++ "provider_key": {
++ "type": "string"
},
-- "requester": {
-- "$ref": "../../identity/subject-ref.v1.json"
++ "model_key": {
++ "type": "string",
++ "maxLength": 200
},
-- "on_behalf_of": {
-- "anyOf": [
-- {
-- "$ref": "../../identity/subject-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
++ "display_name": {
++ "type": [
++ "string",
++ "null"
]
},
-- "target": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
++ "type": {
++ "enum": [
++ "chat",
++ "vision",
++ "embedding",
++ "stt"
]
},
-- "attribution": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
]
},
-- "provider": {
++ "context_window": {
"type": [
-- "string",
++ "integer",
"null"
-- ],
-- "description": "Provider that produced the output; null before a provider call and for cached runs."
++ ]
},
-- "model": {
++ "max_output": {
"type": [
-- "string",
++ "integer",
"null"
]
},
-- "fallback_used": {
-- "type": "boolean"
-- },
-- "synthetic": {
-- "type": "boolean",
-- "description": "Stub provider output."
-- },
-- "cached_from": {
-- "type": "null"
++ "cost": {
++ "type": "object",
++ "properties": {
++ "in_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "out_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "cached_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ }
++ }
},
-- "usage": {
++ "supports": {
"type": "object",
-- "required": [
-- "tokens_in",
-- "tokens_out",
-- "cost_usd",
-- "attempts"
-- ],
-- "additionalProperties": false,
"properties": {
-- "tokens_in": {
-- "type": "integer",
-- "minimum": 0
++ "json": {
++ "type": "boolean"
},
-- "tokens_out": {
-- "type": "integer",
-- "minimum": 0
++ "tools": {
++ "type": "boolean"
},
-- "cost_usd": {
-- "type": "number",
-- "minimum": 0
++ "streaming": {
++ "type": "boolean"
},
-- "attempts": {
-- "type": "integer",
-- "minimum": 0
++ "vision": {
++ "type": "boolean"
}
}
},
-- "error": {
-- "type": "null"
-- },
-- "citations_count": {
-- "type": "integer",
-- "minimum": 0
++ "metadata": {
++ "type": "object"
},
-- "retry_of": {
++ "created_at": {
"type": [
"string",
"null"
-- ],
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "created_at": {
-- "type": "string",
-- "format": "date-time"
++ ]
},
-- "finished_at": {
-- "type": "string",
-- "format": "date-time"
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ]
}
}
}
},
-- "ai.run.failed": {
++ "ai.provider-manage-request": {
"version": "1.0.0",
"owner": "ai",
-- "status": "planned",
++ "status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/ai.run.failed.v1.json",
-- "title": "AiRunFailedPayload",
-- "description": "ai.run.failed v1 (OpenVibe.AI server/runs.js execute / recoverInterrupted). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A run ended failed: a provider, route, source or output error, run.internal, or run.interrupted after a restart or shutdown (retryable with POST /api/v1/runs/:id/retry unless its input was not retained). A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority important, actor service:ai.",
-- "type": "object",
-- "required": [
-- "run_id",
-- "status",
-- "workflow",
-- "requester",
-- "on_behalf_of",
-- "target",
-- "attribution",
-- "provider",
-- "model",
-- "fallback_used",
-- "synthetic",
-- "cached_from",
-- "usage",
-- "error",
-- "citations_count",
-- "retry_of",
-- "created_at",
-- "finished_at"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "run_id": {
-- "type": "string",
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- "status": {
-- "const": "failed"
-- },
-- "workflow": {
++ "$id": "https://openvibe.network/contracts/ai/provider-manage-request.v1.json",
++ "title": "AiProviderManageRequest",
++ "description": "ai.provider-manage-request@1: bodies of ai.provider.manage on OpenVibe.AI. POST /api/v1/providers creates or replaces a provider (key required) and PATCH /api/v1/providers/:key changes one (fields left out keep their value); a body carrying api_key or secret is refused (422: send secret_ref env:NAME, never a secret value). POST /api/v1/models (provider_key and model_key required) and PATCH /api/v1/models/:provider/:model upsert a model. POST /api/v1/quotas upserts a quota (scope_type and window required; scope_id too unless global; the same scope, window and workflow_prefix is one quota). POST /api/v1/providers/:key/disable|enable|reset and DELETE /api/v1/cache[?workflow=] take no body. Unknown fields are ignored.",
++ "anyOf": [
++ {
"type": "object",
-- "required": [
-- "key",
-- "version"
-- ],
-- "additionalProperties": false,
++ "not": {
++ "anyOf": [
++ {
++ "required": [
++ "api_key"
++ ]
++ },
++ {
++ "required": [
++ "secret"
++ ]
++ }
++ ]
++ },
"properties": {
"key": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)*$"
++ },
++ "display_name": {
"type": "string"
},
-- "version": {
-- "type": "integer",
-- "minimum": 1
-- }
-- }
-- },
-- "requester": {
-- "$ref": "../../identity/subject-ref.v1.json"
-- },
-- "on_behalf_of": {
-- "anyOf": [
-- {
-- "$ref": "../../identity/subject-ref.v1.json"
++ "kind": {
++ "enum": [
++ "stub",
++ "openai",
++ "anthropic",
++ "http",
++ "whisper"
++ ]
},
-- {
-- "type": "null"
-- }
-- ]
-- },
-- "target": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
},
-- {
-- "type": "null"
-- }
-- ]
-- },
-- "attribution": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^https?://\\S+$"
},
-- {
-- "type": "null"
++ "auth_mode": {
++ "enum": [
++ "none",
++ "bearer",
++ "x-api-key"
++ ]
++ },
++ "secret_ref": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^(env:[A-Z][A-Z0-9_]{0,63})?$"
++ },
++ "default_model": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "capabilities": {
++ "type": "array",
++ "items": {
++ "enum": [
++ "chat",
++ "generate",
++ "summarize",
++ "classify",
++ "extract",
++ "enrich",
++ "embed",
++ "vision",
++ "json",
++ "transcribe"
++ ]
++ }
++ },
++ "timeout_ms": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "Clamped to 1000..600000."
++ },
++ "priority": {
++ "type": [
++ "number",
++ "string"
++ ]
++ },
++ "metadata": {
++ "type": "object"
}
-- ]
-- },
-- "provider": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "description": "Provider that produced the output; null before a provider call and for cached runs."
-- },
-- "model": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "fallback_used": {
-- "type": "boolean"
-- },
-- "synthetic": {
-- "type": "boolean",
-- "description": "Stub provider output."
-- },
-- "cached_from": {
-- "type": "null"
++ }
},
-- "usage": {
++ {
"type": "object",
-- "required": [
-- "tokens_in",
-- "tokens_out",
-- "cost_usd",
-- "attempts"
-- ],
-- "additionalProperties": false,
"properties": {
-- "tokens_in": {
-- "type": "integer",
-- "minimum": 0
++ "provider_key": {
++ "type": "string"
},
-- "tokens_out": {
-- "type": "integer",
-- "minimum": 0
++ "model_key": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
},
-- "cost_usd": {
-- "type": "number",
-- "minimum": 0
++ "display_name": {
++ "type": "string"
},
-- "attempts": {
-- "type": "integer",
-- "minimum": 0
++ "type": {
++ "enum": [
++ "chat",
++ "vision",
++ "embedding",
++ "stt"
++ ]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
++ },
++ "context_window": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "max_output": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "cost": {
++ "type": "object",
++ "properties": {
++ "in_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "out_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "cached_per_mtok": {
++ "type": [
++ "number",
++ "null"
++ ]
++ }
++ }
++ },
++ "supports": {
++ "type": "object",
++ "properties": {
++ "json": {
++ "type": "boolean"
++ },
++ "tools": {
++ "type": "boolean"
++ },
++ "streaming": {
++ "type": "boolean"
++ },
++ "vision": {
++ "type": "boolean"
++ }
++ }
++ },
++ "metadata": {
++ "type": "object"
}
}
},
-- "error": {
++ {
"type": "object",
"required": [
-- "code",
-- "detail"
++ "scope_type",
++ "window"
],
-- "additionalProperties": false,
"properties": {
-- "code": {
++ "scope_type": {
++ "enum": [
++ "global",
++ "service",
++ "actor",
++ "attribution"
++ ]
++ },
++ "scope_id": {
"type": "string",
-- "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
++ "maxLength": 200,
++ "description": "* on a non-global scope = each one separately."
},
-- "detail": {
++ "window": {
++ "enum": [
++ "minute",
++ "hour",
++ "day"
++ ]
++ },
++ "max_requests": {
++ "type": [
++ "number",
++ "string",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "max_tokens": {
++ "type": [
++ "number",
++ "string",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "max_cost_usd": {
++ "type": [
++ "number",
++ "string",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "workflow_prefix": {
"type": [
"string",
"null"
]
++ },
++ "status": {
++ "enum": [
++ "active",
++ "disabled"
++ ]
}
}
},
-- "citations_count": {
-- "type": "integer",
-- "minimum": 0
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++ }
++ },
++ "ai.provider-manage-result": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/provider-manage-result.v1.json",
++ "title": "AiProviderManageResult",
++ "description": "ai.provider-manage-result@1: answers of ai.provider.manage on OpenVibe.AI. Provider create/update/disable/enable → { provider } (201 on POST); POST …/reset → { health } (the circuit breaker, closed again); model upserts → { model }; POST /api/v1/quotas → 201 { quota } (the stored row); DELETE /api/v1/cache → { removed } (entries purged). Every change is audited.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "properties": {
++ "provider": {
++ "$ref": "provider.v1.json"
++ }
++ }
},
-- "retry_of": {
-- "type": [
-- "string",
-- "null"
++ {
++ "type": "object",
++ "required": [
++ "health"
],
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ "properties": {
++ "health": {
++ "type": "object",
++ "required": [
++ "state"
++ ],
++ "properties": {
++ "provider_key": {
++ "type": "string"
++ },
++ "state": {
++ "enum": [
++ "closed",
++ "open",
++ "half_open"
++ ]
++ },
++ "consecutive_failures": {
++ "type": "integer"
++ }
++ }
++ }
++ }
},
-- "created_at": {
-- "type": "string",
-- "format": "date-time"
++ {
++ "type": "object",
++ "required": [
++ "model"
++ ],
++ "properties": {
++ "model": {
++ "$ref": "model.v1.json"
++ }
++ }
},
-- "finished_at": {
-- "type": "string",
-- "format": "date-time"
++ {
++ "type": "object",
++ "required": [
++ "quota"
++ ],
++ "properties": {
++ "quota": {
++ "type": "object",
++ "required": [
++ "id",
++ "scope_type",
++ "scope_id",
++ "window",
++ "status"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "scope_type": {
++ "type": "string"
++ },
++ "scope_id": {
++ "type": "string"
++ },
++ "window": {
++ "type": "string"
++ },
++ "max_requests": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "max_tokens": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "max_cost_usd": {
++ "type": [
++ "number",
++ "null"
++ ]
++ },
++ "workflow_prefix": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "status": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "removed"
++ ],
++ "properties": {
++ "removed": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
}
-- }
++ ]
}
},
-- "ai.run.cached": {
++ "ai.provider": {
"version": "1.0.0",
"owner": "ai",
-- "status": "planned",
++ "status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/ai.run.cached.v1.json",
-- "title": "AiRunCachedPayload",
-- "description": "ai.run.cached v1 (OpenVibe.AI server/runs.js create). PLANNED: AI does not publish run events yet (its manifest notes list ai.run.queued|succeeded|failed|cached once the Events client is wired in); this is the shape it is to send. A create was served from the scoped cache: no provider call, cached_from names the run whose output was reused. A projection of ai.run@1 without input or output (ADR-015: request metadata, never raw prompts). requester is the token principal that created the run (service, app or mod). Envelope: subject { type: run, id: <run_id> }, visibility internal, priority low, actor service:ai.",
++ "$id": "https://openvibe.network/contracts/ai/provider.v1.json",
++ "title": "AiProvider",
++ "description": "ai.provider@1: one model provider as OpenVibe.AI's registry API shows it (server/registry.js publicProvider): never a secret value, only the secret reference name (env:NAME) and whether it resolves (credentials), plus the circuit-breaker health.",
"type": "object",
"required": [
-- "run_id",
++ "key",
++ "kind",
"status",
-- "workflow",
-- "requester",
-- "on_behalf_of",
-- "target",
-- "attribution",
-- "provider",
-- "model",
-- "fallback_used",
-- "synthetic",
-- "cached_from",
-- "usage",
-- "error",
-- "citations_count",
-- "retry_of",
-- "created_at",
-- "finished_at"
++ "auth_mode",
++ "capabilities",
++ "credentials"
],
-- "additionalProperties": false,
"properties": {
-- "run_id": {
++ "key": {
"type": "string",
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)*$"
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "kind": {
++ "enum": [
++ "stub",
++ "openai",
++ "anthropic",
++ "http",
++ "whisper"
++ ]
},
"status": {
-- "const": "cached"
++ "enum": [
++ "active",
++ "disabled"
++ ]
},
-- "workflow": {
-- "type": "object",
-- "required": [
-- "key",
-- "version"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "key": {
-- "type": "string"
-- },
-- "version": {
-- "type": "integer",
-- "minimum": 1
-- }
-- }
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- "requester": {
-- "$ref": "../../identity/subject-ref.v1.json"
++ "auth_mode": {
++ "enum": [
++ "none",
++ "bearer",
++ "x-api-key"
++ ]
},
-- "on_behalf_of": {
-- "anyOf": [
-- {
-- "$ref": "../../identity/subject-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
++ "secret_ref": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "env:NAME, a reference; never the secret."
++ },
++ "default_model": {
++ "type": [
++ "string",
++ "null"
]
},
-- "target": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
++ "capabilities": {
++ "type": "array",
++ "items": {
++ "enum": [
++ "chat",
++ "generate",
++ "summarize",
++ "classify",
++ "extract",
++ "enrich",
++ "embed",
++ "vision",
++ "json",
++ "transcribe"
++ ]
++ }
++ },
++ "timeout_ms": {
++ "type": [
++ "integer",
++ "null"
]
},
-- "attribution": {
-- "anyOf": [
-- {
-- "$ref": "../../common/entity-ref.v1.json"
-- },
-- {
-- "type": "null"
-- }
++ "priority": {
++ "type": [
++ "number",
++ "null"
]
},
-- "provider": {
++ "metadata": {
++ "type": "object"
++ },
++ "origin": {
"type": [
"string",
"null"
-- ],
-- "description": "Provider that produced the output; null before a provider call and for cached runs."
++ ]
},
-- "model": {
++ "created_at": {
"type": [
"string",
"null"
]
},
-- "fallback_used": {
-- "type": "boolean"
-- },
-- "synthetic": {
-- "type": "boolean",
-- "description": "Stub provider output."
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- "cached_from": {
-- "type": "string",
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ "credentials": {
++ "enum": [
++ "not_required",
++ "configured",
++ "missing"
++ ]
},
-- "usage": {
++ "health": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "provider_key": {
++ "type": "string"
++ },
++ "state": {
++ "enum": [
++ "closed",
++ "open",
++ "half_open"
++ ]
++ },
++ "consecutive_failures": {
++ "type": "integer"
++ }
++ }
++ }
++ }
++ }
++ },
++ "ai.usage-result": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/usage-result.v1.json",
++ "title": "AiUsageResult",
++ "description": "ai.usage-result@1: answers of ai.usage.read on OpenVibe.AI (all GETs, parameters in the query). GET /api/v1/status → { providers, runs, today, workflows, templates, routes, inflight, quotas }; GET /api/v1/usage?from=&to=&requester= → { usage } (daily rows); GET /api/v1/quotas → { quotas, current }; GET /api/v1/requests?provider=&status=&run=&fallback=&limit= → { requests } (request-log metadata: hashes, tokens, cost, latency; raw debug fields only for provider managers who ask); GET /api/v1/audit → { audit }; GET /api/v1/cache → { cache }; GET /api/v1/providers → { providers }; GET /api/v1/models?provider= → { models }. Never raw prompts, never secrets.",
++ "anyOf": [
++ {
"type": "object",
"required": [
-- "tokens_in",
-- "tokens_out",
-- "cost_usd",
-- "attempts"
++ "providers",
++ "runs",
++ "today"
],
-- "additionalProperties": false,
"properties": {
-- "tokens_in": {
-- "type": "integer",
-- "minimum": 0
++ "providers": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "key"
++ ],
++ "properties": {
++ "key": {
++ "type": "string"
++ },
++ "kind": {
++ "type": "string"
++ },
++ "status": {
++ "type": "string"
++ },
++ "credentials": {
++ "type": "string"
++ },
++ "health": {
++ "type": "string"
++ }
++ }
++ }
},
-- "tokens_out": {
-- "type": "integer",
-- "minimum": 0
++ "runs": {
++ "type": "object",
++ "additionalProperties": {
++ "type": "integer"
++ },
++ "description": "Run counts by status."
},
-- "cost_usd": {
-- "type": "number",
-- "minimum": 0
++ "today": {
++ "type": "object",
++ "required": [
++ "day"
++ ],
++ "properties": {
++ "day": {
++ "type": "string"
++ },
++ "requests": {
++ "type": "number"
++ },
++ "cost_usd": {
++ "type": "number"
++ }
++ }
},
-- "attempts": {
-- "type": "integer",
-- "minimum": 0
++ "workflows": {
++ "type": "integer"
++ },
++ "templates": {
++ "type": "integer"
++ },
++ "routes": {
++ "type": "integer"
++ },
++ "inflight": {
++ "type": "integer"
++ },
++ "quotas": {
++ "type": "array"
}
}
},
-- "error": {
-- "type": "null"
++ {
++ "type": "object",
++ "required": [
++ "providers"
++ ],
++ "properties": {
++ "providers": {
++ "type": "array",
++ "items": {
++ "$ref": "provider.v1.json"
++ }
++ }
++ }
},
-- "citations_count": {
-- "type": "integer",
-- "minimum": 0
++ {
++ "type": "object",
++ "required": [
++ "models"
++ ],
++ "properties": {
++ "models": {
++ "type": "array",
++ "items": {
++ "$ref": "model.v1.json"
++ }
++ }
++ }
},
-- "retry_of": {
-- "type": [
-- "string",
-- "null"
++ {
++ "type": "object",
++ "required": [
++ "quotas",
++ "current"
],
-- "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
++ "properties": {
++ "quotas": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ },
++ "current": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "properties": {
++ "quota": {
++ "type": "object"
++ },
++ "scope_id": {
++ "type": "string"
++ },
++ "window_start": {
++ "type": "string"
++ },
++ "resets_in_seconds": {
++ "type": "number"
++ },
++ "used": {
++ "type": "object"
++ }
++ }
++ }
++ }
++ }
},
-- "created_at": {
-- "type": "string",
-- "format": "date-time"
++ {
++ "type": "object",
++ "required": [
++ "usage"
++ ],
++ "properties": {
++ "usage": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ }
++ }
},
-- "finished_at": {
-- "type": "string",
-- "format": "date-time"
++ {
++ "type": "object",
++ "required": [
++ "requests"
++ ],
++ "properties": {
++ "requests": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "audit"
++ ],
++ "properties": {
++ "audit": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "cache"
++ ],
++ "properties": {
++ "cache": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "properties": {
++ "workflow_key": {
++ "type": "string"
++ },
++ "privacy": {
++ "type": "string"
++ },
++ "entries": {
++ "type": "integer"
++ },
++ "hits": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
}
-- }
++ ]
}
},
"tips.interaction.ready": {
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex b7bf4a25..ba824b87 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -5793,6 +5793,178 @@ export interface TombstonePayload {
redacted_by: string;
}
++/** [email protected] (owner: events) */
++/**
++ * events.delivery-admin-request@1: the body of POST /api/v1/deliveries/replay on OpenVibe.Events (events.delivery.admin, operators): { subscription_id } and exactly one of from_seq (requeue everything the subscription matches from that seq) or event_ids (at most 1000). GET /api/v1/deliveries?status=&subscription_id=&after_seq=&limit= takes no body.
++ */
++export type EventsDeliveryAdminRequest =
++ | {
++ [k: string]: unknown | undefined;
++ }
++ | NoBody;
++
++/** [email protected] (owner: events) */
++/**
++ * events.delivery-admin-result@1: answers of events.delivery.admin on OpenVibe.Events. GET /api/v1/deliveries → { deliveries, counts } (per delivery: event, subscription, seq, status pending|delivered|failed|dead, attempt, next_attempt_at, the last HTTP status and error, delivered_at; counts by status); POST /api/v1/deliveries/replay → { subscription_id, queued }.
++ */
++export type EventsDeliveryAdminResult =
++ | {
++ deliveries: {
++ event_id: string;
++ subscription_id: string;
++ seq: number;
++ status: "pending" | "delivered" | "failed" | "dead";
++ attempt: number;
++ next_attempt_at?: string | null;
++ last_status?: number | null;
++ last_error?: string | null;
++ delivered_at?: string | null;
++ }[];
++ counts: {
++ pending: number;
++ delivered: number;
++ failed: number;
++ dead: number;
++ };
++ }
++ | {
++ subscription_id: string;
++ queued: number;
++ };
++
++/** [email protected] (owner: events) */
++/**
++ * events.publish-result@1: the answer of POST /api/v1/events on OpenVibe.Events (events.event.publish for services, events.app.publish for developer apps). One envelope → { event_id, seq, duplicate } (201 when stored, 200 when every event was a repeat); a batch { events: [...] } (at most 100, atomic) → { results } in the same order. Refusals are problem+json: 422 events.invalid_envelope / events.invalid_redaction, 403 events.source_mismatch / events.type_not_allowed / events.actor_mismatch, 413 events.payload_too_large / events.batch_too_large, 429 events.quota_exceeded (apps).
++ */
++export type EventsPublishResult =
++ | {
++ event_id: string;
++ seq: number;
++ /**
++ * true: this event_id was stored before; the stored seq is returned and nothing is stored twice.
++ */
++ duplicate: boolean;
++ }
++ | {
++ /**
++ * @minItems 1
++ * @maxItems 100
++ */
++ results: [
++ {
++ event_id: string;
++ seq: number;
++ /**
++ * true: this event_id was stored before; the stored seq is returned and nothing is stored twice.
++ */
++ duplicate: boolean;
++ },
++ ...{
++ event_id: string;
++ seq: number;
++ /**
++ * true: this event_id was stored before; the stored seq is returned and nothing is stored twice.
++ */
++ duplicate: boolean;
++ }[]
++ ];
++ };
++
++/** [email protected] (owner: events) */
++/**
++ * events.read-request@1: request bodies of the pull API on OpenVibe.Events (events.event.read, events.app.read). PUT /api/v1/checkpoints: { topic, cursor } stores the calling consumer's cursor for a topic pattern (an app's patterns must start with a literal segment and app.* must name its own project_key). GET /api/v1/events?topic=&after_seq=&limit=, GET /api/v1/events/:event_id, GET /api/v1/checkpoints?topic= and GET /realtime/stream?topics= take no body.
++ */
++export type EventsReadRequest =
++ | {
++ /**
++ * Dot-separated segments of [a-z0-9_] or *, where * stands for one or more whole segments (*.* is refused).
++ */
++ topic: string;
++ /**
++ * A seq (next_after_seq of the last page handled).
++ */
++ cursor: number;
++ }
++ | NoBody;
++
++/** [email protected] (owner: events) */
++/**
++ * events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). An app sees only its project's events in its token's environment plus public first-party events.
++ */
++export type EventsReadResult =
++ | {
++ /**
++ * @maxItems 1000
++ */
++ events: {
++ seq: number;
++ event: EventEnvelope;
++ }[];
++ next_after_seq: number;
++ latest_seq: number;
++ gap?: {
++ from_seq: number;
++ to_seq: number;
++ };
++ }
++ | {
++ seq: number;
++ event: EventEnvelope;
++ }
++ | {
++ consumer: string;
++ topic: string;
++ cursor: number;
++ updated_at: string | null;
++ };
++
++/** [email protected] (owner: events) */
++/**
++ * events.subscription-request@1: the body of POST /api/v1/subscriptions on OpenVibe.Events (events.subscription.manage for services, events.app.subscribe for developer apps; the consumer is always the caller). topic_pattern (topic is accepted for it) and endpoint are required: a service's endpoint must be http(s) on the allow-list (127.0.0.1, *.openvibe.*), an app's must be https resolving only to public addresses. secret, when given, is 32 to 256 characters (else one is generated); retry_policy accepts max_attempts (1..20) and backoff_ms (1..20 delays of 0..86400000 ms) and nothing else. The same pattern and endpoint twice is 409 events.subscription_exists. GET /api/v1/subscriptions[/:id] and POST /api/v1/subscriptions/:id/disable|enable take no body.
++ */
++export type EventsSubscriptionRequest =
++ | {
++ [k: string]: unknown | undefined;
++ }
++ | NoBody;
++
++/** [email protected] (owner: events) */
++/**
++ * events.subscription-result@1: answers of the subscription routes on OpenVibe.Events. POST /api/v1/subscriptions → 201 events.subscription@1 with its secret (shown once); GET /api/v1/subscriptions → { subscriptions } (the caller's own, no secrets); GET /api/v1/subscriptions/:id and POST …/disable|enable → events.subscription@1. Another consumer's subscription is 404.
++ */
++export type EventsSubscriptionResult =
++ | EventsSubscription
++ | {
++ subscriptions: EventsSubscription[];
++ };
++
++/** [email protected] (owner: events) */
++/**
++ * events.subscription@1: one push subscription as OpenVibe.Events shows it to its consumer (server/store.js subscriptionView). The signing secret appears only in the answer that creates it (and in a rotation's). A developer app's subscription also names its project and environment.
++ */
++export interface EventsSubscription {
++ id: string;
++ /**
++ * The calling service, or app:app_<ULID>.
++ */
++ consumer: string;
++ topic_pattern: string;
++ endpoint: string;
++ enabled: boolean;
++ retry_policy: {
++ max_attempts?: number;
++ backoff_ms?: number[];
++ } | null;
++ created_at: string;
++ updated_at: string;
++ project_id?: string;
++ env?: string;
++ /**
++ * Only when the subscription is created: shown once.
++ */
++ secret?: string;
++}
++
/** [email protected] (owner: chat) */
/**
* chat.message.deleted v1 (OpenVibe.Chat server/db/database.js _announceDeleted). Public messages were deleted: by a moderator, through Live's bridge, the author's or an anon's history, a time-range purge or the auto-delete sweep. Envelope: subject { type: chat_message, id: <first id> }, visibility public, priority important, actor service:chat. One event per 500 ids. Carries only ids, never the text, the author or who deleted it; payload.redacts makes OpenVibe.Events tombstone the chat.message.created of each id.
@@@ -8024,6 +8196,337 @@ export interface AiRunCachedPayload {
finished_at: string;
}
++/** [email protected] (owner: ai) */
++/**
++ * ai.definition-version-request@1: bodies of ai.workflow.manage on OpenVibe.AI. An edit is always a new version (the previous one stays readable); fields left out are taken from the newest existing version. POST /api/v1/templates/:key/versions: a prompt template (input_schema and output_schema must compile as JSON Schema; visibility public, first-party or internal). POST /api/v1/workflows/:key/versions (key <namespace>.<name>): steps (at least one; kind llm (naming an existing template), passthrough, transcribe or embed), schemas, default route, cache mode. POST /api/v1/routes/:key/versions: primary { provider, model } and fallbacks (every provider must exist), or alias_of another route. POST …/:key/versions/:version/status: { status } (templates and workflows draft|active|deprecated|archived; routes active|disabled). Refusals are 422 ai.invalid.
++ */
++export type AiDefinitionVersionRequest =
++ | {
++ name?: string;
++ description?: string | null;
++ /**
++ * A JSON Schema the run input must match.
++ */
++ input_schema?: {};
++ /**
++ * A JSON Schema the output must match.
++ */
++ output_schema?: {};
++ system_prompt?: string;
++ user_prompt?: string;
++ default_route?: string | null;
++ owner?: string;
++ visibility?: "public" | "first-party" | "internal";
++ status?: "draft" | "active" | "deprecated" | "archived";
++ metadata?: {};
++ }
++ | {
++ name?: string;
++ description?: string | null;
++ namespace?: string;
++ /**
++ * A JSON Schema.
++ */
++ input_schema?: {};
++ /**
++ * A JSON Schema.
++ */
++ output_schema?: {};
++ /**
++ * @minItems 1
++ */
++ steps?: [
++ {
++ kind: "llm" | "passthrough" | "transcribe" | "embed";
++ template?: string;
++ },
++ ...{
++ kind: "llm" | "passthrough" | "transcribe" | "embed";
++ template?: string;
++ }[]
++ ];
++ default_route?: string | null;
++ cache_mode?: "none" | "private" | "service";
++ cache_ttl_sec?: number | null;
++ status?: "draft" | "active" | "deprecated" | "archived";
++ metadata?: {};
++ }
++ | {
++ primary?: {
++ provider: string;
++ model?: string | null;
++ };
++ fallbacks?: {
++ provider: string;
++ model?: string | null;
++ }[];
++ options?: {};
++ max_output_tokens?: number | null;
++ response_format?: "text" | "json";
++ timeout_ms?: number | null;
++ alias_of?: string | null;
++ status?: "active" | "disabled";
++ };
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.definition-version-result@1: answers of ai.workflow.manage on OpenVibe.AI. POST …/:key/versions → 201 { template | workflow | route } (the new version, whole: prompts and schemas for a template, steps for a workflow, primary/fallbacks for a route); POST …/versions/:version/status → { template | workflow | route: { key, version, status } }.
++ */
++export type AiDefinitionVersionResult =
++ | {
++ template: {
++ key: string;
++ version: number;
++ status: string;
++ };
++ }
++ | {
++ workflow: {
++ key: string;
++ version: number;
++ status: string;
++ };
++ }
++ | {
++ route: {
++ key: string;
++ version: number;
++ status: string;
++ };
++ };
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.model@1: one model of a provider in OpenVibe.AI's registry (server/registry.js decodeModel): type, status, limits, per-million-token costs and what it supports.
++ */
++export interface AiModel {
++ provider_key: string;
++ model_key: string;
++ display_name?: string | null;
++ type: "chat" | "vision" | "embedding" | "stt";
++ status: "active" | "disabled";
++ context_window?: number | null;
++ max_output?: number | null;
++ cost: {
++ in_per_mtok?: number | null;
++ out_per_mtok?: number | null;
++ cached_per_mtok?: number | null;
++ };
++ supports: {
++ json?: boolean;
++ tools?: boolean;
++ streaming?: boolean;
++ vision?: boolean;
++ };
++ metadata?: {};
++ created_at?: string | null;
++ updated_at?: string | null;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.provider-manage-request@1: bodies of ai.provider.manage on OpenVibe.AI. POST /api/v1/providers creates or replaces a provider (key required) and PATCH /api/v1/providers/:key changes one (fields left out keep their value); a body carrying api_key or secret is refused (422: send secret_ref env:NAME, never a secret value). POST /api/v1/models (provider_key and model_key required) and PATCH /api/v1/models/:provider/:model upsert a model. POST /api/v1/quotas upserts a quota (scope_type and window required; scope_id too unless global; the same scope, window and workflow_prefix is one quota). POST /api/v1/providers/:key/disable|enable|reset and DELETE /api/v1/cache[?workflow=] take no body. Unknown fields are ignored.
++ */
++export type AiProviderManageRequest =
++ | {
++ key?: string;
++ display_name?: string;
++ kind?: "stub" | "openai" | "anthropic" | "http" | "whisper";
++ status?: "active" | "disabled";
++ base_url?: string | null;
++ auth_mode?: "none" | "bearer" | "x-api-key";
++ secret_ref?: string | null;
++ default_model?: string | null;
++ capabilities?: (
++ | "chat"
++ | "generate"
++ | "summarize"
++ | "classify"
++ | "extract"
++ | "enrich"
++ | "embed"
++ | "vision"
++ | "json"
++ | "transcribe"
++ )[];
++ /**
++ * Clamped to 1000..600000.
++ */
++ timeout_ms?: number | string;
++ priority?: number | string;
++ metadata?: {};
++ }
++ | {
++ provider_key?: string;
++ model_key?: string;
++ display_name?: string;
++ type?: "chat" | "vision" | "embedding" | "stt";
++ status?: "active" | "disabled";
++ context_window?: number | null;
++ max_output?: number | null;
++ cost?: {
++ in_per_mtok?: number | null;
++ out_per_mtok?: number | null;
++ cached_per_mtok?: number | null;
++ };
++ supports?: {
++ json?: boolean;
++ tools?: boolean;
++ streaming?: boolean;
++ vision?: boolean;
++ };
++ metadata?: {};
++ }
++ | {
++ scope_type: "global" | "service" | "actor" | "attribution";
++ /**
++ * * on a non-global scope = each one separately.
++ */
++ scope_id?: string;
++ window: "minute" | "hour" | "day";
++ max_requests?: number | string | null;
++ max_tokens?: number | string | null;
++ max_cost_usd?: number | string | null;
++ workflow_prefix?: string | null;
++ status?: "active" | "disabled";
++ }
++ | NoBody;
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.provider-manage-result@1: answers of ai.provider.manage on OpenVibe.AI. Provider create/update/disable/enable → { provider } (201 on POST); POST …/reset → { health } (the circuit breaker, closed again); model upserts → { model }; POST /api/v1/quotas → 201 { quota } (the stored row); DELETE /api/v1/cache → { removed } (entries purged). Every change is audited.
++ */
++export type AiProviderManageResult =
++ | {
++ provider: AiProvider;
++ }
++ | {
++ health: {
++ provider_key?: string;
++ state: "closed" | "open" | "half_open";
++ consecutive_failures?: number;
++ };
++ }
++ | {
++ model: AiModel;
++ }
++ | {
++ quota: {
++ id: number;
++ scope_type: string;
++ scope_id: string;
++ window: string;
++ max_requests?: number | null;
++ max_tokens?: number | null;
++ max_cost_usd?: number | null;
++ workflow_prefix?: string | null;
++ status: string;
++ };
++ }
++ | {
++ removed: number;
++ };
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.provider@1: one model provider as OpenVibe.AI's registry API shows it (server/registry.js publicProvider): never a secret value, only the secret reference name (env:NAME) and whether it resolves (credentials), plus the circuit-breaker health.
++ */
++export interface AiProvider {
++ key: string;
++ display_name?: string | null;
++ kind: "stub" | "openai" | "anthropic" | "http" | "whisper";
++ status: "active" | "disabled";
++ base_url?: string | null;
++ auth_mode: "none" | "bearer" | "x-api-key";
++ /**
++ * env:NAME, a reference; never the secret.
++ */
++ secret_ref?: string | null;
++ default_model?: string | null;
++ capabilities: (
++ "chat" | "generate" | "summarize" | "classify" | "extract" | "enrich" | "embed" | "vision" | "json" | "transcribe"
++ )[];
++ timeout_ms?: number | null;
++ priority?: number | null;
++ metadata?: {};
++ origin?: string | null;
++ created_at?: string | null;
++ updated_at?: string | null;
++ credentials: "not_required" | "configured" | "missing";
++ health?: {
++ provider_key?: string;
++ state?: "closed" | "open" | "half_open";
++ consecutive_failures?: number;
++ } | null;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.usage-result@1: answers of ai.usage.read on OpenVibe.AI (all GETs, parameters in the query). GET /api/v1/status → { providers, runs, today, workflows, templates, routes, inflight, quotas }; GET /api/v1/usage?from=&to=&requester= → { usage } (daily rows); GET /api/v1/quotas → { quotas, current }; GET /api/v1/requests?provider=&status=&run=&fallback=&limit= → { requests } (request-log metadata: hashes, tokens, cost, latency; raw debug fields only for provider managers who ask); GET /api/v1/audit → { audit }; GET /api/v1/cache → { cache }; GET /api/v1/providers → { providers }; GET /api/v1/models?provider= → { models }. Never raw prompts, never secrets.
++ */
++export type AiUsageResult =
++ | {
++ providers: {
++ key: string;
++ kind?: string;
++ status?: string;
++ credentials?: string;
++ health?: string;
++ }[];
++ /**
++ * Run counts by status.
++ */
++ runs: {
++ [k: string]: number | undefined;
++ };
++ today: {
++ day: string;
++ requests?: number;
++ cost_usd?: number;
++ };
++ workflows?: number;
++ templates?: number;
++ routes?: number;
++ inflight?: number;
++ quotas?: unknown[];
++ }
++ | {
++ providers: AiProvider[];
++ }
++ | {
++ models: AiModel[];
++ }
++ | {
++ quotas: {}[];
++ current: {
++ quota?: {};
++ scope_id?: string;
++ window_start?: string;
++ resets_in_seconds?: number;
++ used?: {};
++ }[];
++ }
++ | {
++ usage: {}[];
++ }
++ | {
++ /**
++ * @maxItems 500
++ */
++ requests: {}[];
++ }
++ | {
++ audit: {}[];
++ }
++ | {
++ cache: {
++ workflow_key?: string;
++ privacy?: string;
++ entries?: number;
++ hits?: number | null;
++ }[];
++ };
++
/** [email protected] (owner: tips) */
/**
* tips.interaction.ready v1 (OpenVibe.Tips server/domain/interactions.js settle, summary()). The interaction settled: it is active and its effects (overlay alert, chat line, paid message, TTS, media request) are queued. Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.
ddiff --git a/manifests/capabilities/ai.provider.manage.json b/manifests/capabilities/ai.provider.manage.json
iindex aaf3d72b..e5e41186 100644
---- a/manifests/capabilities/ai.provider.manage.json
++++ b/manifests/capabilities/ai.provider.manage.json
@@@ -23,5 +23,7 @@
"PATCH /api/v1/models/:provider/:model",
"POST /api/v1/quotas",
"DELETE /api/v1/cache"
-- ]
++ ],
++ "inputSchema": "ai.provider-manage-request@1",
++ "outputSchema": "ai.provider-manage-result@1"
}
ddiff --git a/manifests/capabilities/ai.run.read.json b/manifests/capabilities/ai.run.read.json
iindex 76616e9d..92c02286 100644
---- a/manifests/capabilities/ai.run.read.json
++++ b/manifests/capabilities/ai.run.read.json
@@@ -18,5 +18,6 @@
"GET /api/v1/runs",
"GET /api/v1/runs/:id",
"GET /api/v1/runs/:id/citations"
-- ]
++ ],
++ "inputSchema": "common.no-body@1"
}
ddiff --git a/manifests/capabilities/ai.usage.read.json b/manifests/capabilities/ai.usage.read.json
iindex e9f564c9..193e6650 100644
---- a/manifests/capabilities/ai.usage.read.json
++++ b/manifests/capabilities/ai.usage.read.json
@@@ -22,5 +22,7 @@
"GET /api/v1/cache",
"GET /api/v1/providers",
"GET /api/v1/models"
-- ]
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "ai.usage-result@1"
}
ddiff --git a/manifests/capabilities/ai.workflow.manage.json b/manifests/capabilities/ai.workflow.manage.json
iindex dad462de..5c22ea9f 100644
---- a/manifests/capabilities/ai.workflow.manage.json
++++ b/manifests/capabilities/ai.workflow.manage.json
@@@ -20,5 +20,7 @@
"POST /api/v1/workflows/:key/versions/:version/status",
"POST /api/v1/routes/:key/versions",
"POST /api/v1/routes/:key/versions/:version/status"
-- ]
++ ],
++ "inputSchema": "ai.definition-version-request@1",
++ "outputSchema": "ai.definition-version-result@1"
}
ddiff --git a/manifests/capabilities/events.app.publish.json b/manifests/capabilities/events.app.publish.json
iindex 80aded83..654a3682 100644
---- a/manifests/capabilities/events.app.publish.json
++++ b/manifests/capabilities/events.app.publish.json
@@@ -16,5 +16,6 @@
"events": [],
"implementedBy": [
"POST /api/v1/events"
-- ]
++ ],
++ "outputSchema": "events.publish-result@1"
}
ddiff --git a/manifests/capabilities/events.app.read.json b/manifests/capabilities/events.app.read.json
iindex 5ff7f9a8..e63f31d3 100644
---- a/manifests/capabilities/events.app.read.json
++++ b/manifests/capabilities/events.app.read.json
@@@ -18,5 +18,7 @@
"GET /api/v1/events/:event_id",
"GET /api/v1/checkpoints",
"PUT /api/v1/checkpoints"
-- ]
++ ],
++ "inputSchema": "events.read-request@1",
++ "outputSchema": "events.read-result@1"
}
ddiff --git a/manifests/capabilities/events.app.subscribe.json b/manifests/capabilities/events.app.subscribe.json
iindex 3fafc918..445e1ad9 100644
---- a/manifests/capabilities/events.app.subscribe.json
++++ b/manifests/capabilities/events.app.subscribe.json
@@@ -19,5 +19,7 @@
"GET /api/v1/subscriptions/:id",
"POST /api/v1/subscriptions/:id/disable",
"POST /api/v1/subscriptions/:id/enable"
-- ]
++ ],
++ "inputSchema": "events.subscription-request@1",
++ "outputSchema": "events.subscription-result@1"
}
ddiff --git a/manifests/capabilities/events.delivery.admin.json b/manifests/capabilities/events.delivery.admin.json
iindex 79c98890..dc2a5cad 100644
---- a/manifests/capabilities/events.delivery.admin.json
++++ b/manifests/capabilities/events.delivery.admin.json
@@@ -16,5 +16,7 @@
"implementedBy": [
"GET /api/v1/deliveries",
"POST /api/v1/deliveries/replay"
-- ]
++ ],
++ "inputSchema": "events.delivery-admin-request@1",
++ "outputSchema": "events.delivery-admin-result@1"
}
ddiff --git a/manifests/capabilities/events.event.publish.json b/manifests/capabilities/events.event.publish.json
iindex b3bb1e2e..a7c166cc 100644
---- a/manifests/capabilities/events.event.publish.json
++++ b/manifests/capabilities/events.event.publish.json
@@@ -16,5 +16,6 @@
"events": [],
"implementedBy": [
"POST /api/v1/events"
-- ]
++ ],
++ "outputSchema": "events.publish-result@1"
}
ddiff --git a/manifests/capabilities/events.event.read.json b/manifests/capabilities/events.event.read.json
iindex 5e9de8d4..ad514453 100644
---- a/manifests/capabilities/events.event.read.json
++++ b/manifests/capabilities/events.event.read.json
@@@ -20,5 +20,6 @@
"GET /api/v1/checkpoints",
"PUT /api/v1/checkpoints",
"GET /realtime/stream"
-- ]
++ ],
++ "inputSchema": "events.read-request@1"
}
ddiff --git a/manifests/capabilities/events.subscription.manage.json b/manifests/capabilities/events.subscription.manage.json
iindex 7c38ec98..140b3ce5 100644
---- a/manifests/capabilities/events.subscription.manage.json
++++ b/manifests/capabilities/events.subscription.manage.json
@@@ -19,5 +19,7 @@
"GET /api/v1/subscriptions/:id",
"POST /api/v1/subscriptions/:id/disable",
"POST /api/v1/subscriptions/:id/enable"
-- ]
++ ],
++ "inputSchema": "events.subscription-request@1",
++ "outputSchema": "events.subscription-result@1"
}