openvibers/openvibe.contracts Commits
v0.30.0: event payload contracts and producer redaction (ADR-026)
Changes
nnew file mode 100644@@@ -0,0 +1,70 @@++# Changelog++++All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) that consumers install++from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,++the notes were in the tag and commit messages (`git tag -n1`).++++## 0.30.0 (2026-09-23)++++Additive: `compat.js` reports no breaking change against v0.29.0. No existing contract changed.++++**Event payload contracts** (ADR-026). Each event type has a contract named after it, with the envelope++`version` as its major: ``contracts.validate(`${env.event_type}@${env.version}`, env.payload)``. The files++are `contracts/events/payloads/<event_type>.v1.json`, and every one has valid and invalid fixtures. The++shapes come from the producers' code.++++- Chat: `chat.message.deleted` (`message_ids`, and `redacts: { subject_type: chat_message, subject_ids }`, which is required).++- Tips: `tips.interaction.ready|failed|cancelled`, `tips.goal.updated`, `tips.overlay.delivered|failed`.++- VIP: `vip.plan.published`, `vip.membership.changed`.++- Billing: `billing.transaction.settled|reversed`, `billing.entitlement.changed`,++ `billing.subscription.canceled`, `billing.cashout.requested|paid|denied`, `billing.staff.action`,++ `billing.receipt.external`.++- Wiki: `wiki.space.updated`, `wiki.revision.created`, `wiki.watch.triggered`,++ `wiki.page.published|updated|unpublished|deleted`, `wiki.index_document.upserted|deleted`.++- Blog: `blog.post.created|published|updated|unpublished|deleted`, `blog.schedule.failed`,++ `blog.index_document.upserted|deleted`.++- Sources: `sources.item.created|updated|removed`, `sources.fetch.failed`,++ `sources.index_document.upserted|deleted`.++- Search: `search.document.indexed|removed`.++- Deals: `deals.watch.matched`. Trade: `trade.alert.triggered`.++- **Planned** (catalog status `planned`, not emitted yet): `ai.run.queued|succeeded|failed|cached` and++ `tools.job.created|started|succeeded|failed`. Tools job events carry ids, type, owner, state, times,++ result locations and errors, never inputs, output data, file names or the anonymous session.++- `*.index_document.upserted` payloads are `search.index-document@1`, with the owner and type fixed.++++**Redaction** (new ADR-026, linked from ADR-004):++++- `events.redaction-directive@1` is `payload.redacts` (`event_ids` and/or `subject_type` + `subject_ids`, up to 1000 each).++- `events.tombstone-payload@1` is what a redacted event's payload becomes.++- The ADR documents 403 `events.redaction_not_allowed`, 422 `events.invalid_redaction` and the realtime++ public replay window (`REALTIME_PUBLIC_REPLAY_SECONDS`, default 300, `gap` reason `public_window`).++- Delivery signature v2 was already documented (ADR-004, README).++++**AI** (ADR-015):++++- `ai.run-request@1` is the body of `POST /api/v1/runs`, and `ai.run@1` is the run resource.++- `ai.run.create` now declares both as `inputSchema`/`outputSchema`, and `ai.run.read` declares `outputSchema`.++++**Manifests**++++- Service manifests:++ - chat 0.3.0 produces `chat.message.deleted`.++ - tools 0.3.0 produces `tools.job.*`.++ - network 0.2.0 consumes `deals.watch.matched` and `trade.alert.triggered`.++ - billing 0.3.0 produces `billing.receipt.external`, and tips 0.3.0 consumes it.++ - ai 0.2.0 lists its planned `ai.run.*`.++- Capabilities: `chat.live_bridge.write` lists `chat.message.deleted`, `tools.job.create` lists `tools.job.*`++ and `ai.run.create` lists `ai.run.*`.++- Notes corrected:++ - Wiki is deployed and public at openvibe.wiki.++ - Reviews, VIP and Tips are deployed internally and not launched publicly. Tips also loses the stale "Proposed manifest" line.++- SDK, Shared and Examples are `alpha`, no longer `placeholder`: SDK and Shared are released libraries,++ and Examples is a repository with CI. Realtime is the only placeholder left.++++**Tests**++++- Every payload contract lives at its event type's path, is listed in its owner's `eventsProduced`, fits in an++ envelope and rejects a tombstone.++- Every consumed event type (wildcards included) has a producing manifest, and each type has one producer.++- The directive's limits are checked.++- Generated TypeScript declares every type it references.@@@ -2,7 +2,7 @@> Machine-readable contracts for the whole OpenVibe network.--**Status:** alpha, v0.29.0 (first released in Wave 1). 17 schemas, 30 service manifests, 170 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.++**Status:** alpha, v0.30.0 (first released in Wave 1). 74 schemas (53 of them event payloads), 30 service manifests, 170 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).**License:** AGPL-3.0 (same as every OpenVibe service).@@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) || `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |--Manifests: `manifests/services/` (30 services as of v0.29.0: 20 `alpha`, 1 `beta`, 5 `stable`, and 4 still marked `placeholder`: examples, realtime, sdk and shared), `manifests/capabilities/` (170 capabilities; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Some manifest `notes` predate deployment: wiki, reviews and vip still say "not deployed", and SDK and Shared are tagged packages and Examples is a working repository, although their manifests say `placeholder`. Correcting them needs a new tag.++Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (170 capabilities; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.@@@ -80,6 +80,17 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi`project_key` is `p` followed by the project's ULID in lowercase (`prj_01JAB…` → `p01jab…`), so it fits an event-type segment. An app event's `source` is `app-` followed by the app's ULID in lowercase (`app:app_01JAB…` → `app-01jab…`), which keeps `events.event-envelope@1` unchanged (its `source` pattern already allows it). OpenVibe.Events enforces the scope, the sandbox separation and per-project quotas.++## Event payload contracts (v0.30)++++Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`, `tools.job.*`).++++```js++if (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)++const r = contracts.validate(`${env.event_type}@${env.version}`, env.payload);++```++++**Redaction (ADR-026).** Any event may carry `payload.redacts` (`events.redaction-directive@1`), shaped `{ event_ids?, subject_type?, subject_ids? }` with up to 1000 ids each, to take back the producer's own earlier events. OpenVibe.Events rewrites each target into a tombstone at its original seq: `{ redacted: true, redacted_at, redacted_by }`, with the producer itself as actor. Naming another source's event refuses the whole batch with 403 `events.redaction_not_allowed`. A malformed directive gets 422 `events.invalid_redaction`. `chat.message.deleted` always carries one. Browsers are replayed only the `public` events of the last `REALTIME_PUBLIC_REPLAY_SECONDS` (default 300); an older cursor gets `event: gap` with reason `public_window`.++## Event delivery signaturesOpenVibe.Events POSTs each webhook delivery as `{ "event": <events.event-envelope@1>, "seq": n }`, keyed with the subscription secret:nnew file mode 100644@@@ -0,0 +1,31 @@++{++ "$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.",++ "type": "object",++ "required": ["workflow"],++ "additionalProperties": false,++ "properties": {++ "workflow": {++ "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…)."++ },++ "version": { "type": "integer", "minimum": 1, "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." }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,79 @@++{++ "$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.",++ "type": "object",++ "required": ["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": {++ "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": ["key", "version"], "additionalProperties": false,++ "properties": { "key": { "type": "string" }, "version": { "type": "integer", "minimum": 1 } }++ },++ "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" },++ "on_behalf_of": { "anyOf": [{ "$ref": "../identity/subject-ref.v1.json" }, { "type": "null" }] },++ "attribution": { "anyOf": [{ "$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": { "anyOf": [{ "$ref": "../common/entity-ref.v1.json" }, { "type": "null" }] },++ "input": { "type": "object", "description": "The retained input (see the description)." },++ "output": { "type": ["object", "null"], "description": "Workflow output, or null." },++ "error": {++ "type": ["object", "null"], "required": ["code", "detail"], "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…" },++ "detail": { "type": ["string", "null"] }++ }++ },++ "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" },++ "workflow": { "type": "string" },++ "workflow_version": { "type": "integer", "minimum": 1 },++ "template_version": { "type": ["integer", "null"] },++ "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" }++ }++ },++ "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)." }++ }++ },++ "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" }++ }++}@@@ -167,6 +167,554 @@"compatibility": "backward","status": "active","adr": "ADR-014"++ },++ {++ "id": "events.redaction-directive",++ "version": "1.0.0",++ "owner": "events",++ "schema": "events/redaction-directive.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-026"++ },++ {++ "id": "events.tombstone-payload",++ "version": "1.0.0",++ "owner": "events",++ "schema": "events/tombstone-payload.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-026"++ },++ {++ "id": "chat.message.deleted",++ "version": "1.0.0",++ "owner": "chat",++ "schema": "events/payloads/chat.message.deleted.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-026"++ },++ {++ "id": "sources.item.created",++ "version": "1.0.0",++ "owner": "sources",++ "schema": "events/payloads/sources.item.created.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-017"++ },++ {++ "id": "sources.item.updated",++ "version": "1.0.0",++ "owner": "sources",++ "schema": "events/payloads/sources.item.updated.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-017"++ },++ {++ "id": "sources.item.removed",++ "version": "1.0.0",++ "owner": "sources",++ "schema": "events/payloads/sources.item.removed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-017"++ },++ {++ "id": "sources.fetch.failed",++ "version": "1.0.0",++ "owner": "sources",++ "schema": "events/payloads/sources.fetch.failed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-017"++ },++ {++ "id": "sources.index_document.upserted",++ "version": "1.0.0",++ "owner": "sources",++ "schema": "events/payloads/sources.index_document.upserted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-017"++ },++ {++ "id": "sources.index_document.deleted",++ "version": "1.0.0",++ "owner": "sources",++ "schema": "events/payloads/sources.index_document.deleted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-017"++ },++ {++ "id": "search.document.indexed",++ "version": "1.0.0",++ "owner": "search",++ "schema": "events/payloads/search.document.indexed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-018"++ },++ {++ "id": "search.document.removed",++ "version": "1.0.0",++ "owner": "search",++ "schema": "events/payloads/search.document.removed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-018"++ },++ {++ "id": "wiki.space.updated",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.space.updated.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.revision.created",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.revision.created.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.watch.triggered",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.watch.triggered.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.page.published",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.page.published.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.page.updated",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.page.updated.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.page.unpublished",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.page.unpublished.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.page.deleted",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.page.deleted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.index_document.upserted",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.index_document.upserted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "wiki.index_document.deleted",++ "version": "1.0.0",++ "owner": "wiki",++ "schema": "events/payloads/wiki.index_document.deleted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.post.created",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.post.created.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.post.published",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.post.published.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.post.updated",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.post.updated.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.post.unpublished",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.post.unpublished.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.post.deleted",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.post.deleted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.schedule.failed",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.schedule.failed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.index_document.upserted",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.index_document.upserted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "blog.index_document.deleted",++ "version": "1.0.0",++ "owner": "blog",++ "schema": "events/payloads/blog.index_document.deleted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "ai.run-request",++ "version": "1.0.0",++ "owner": "ai",++ "schema": "ai/run-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-015"++ },++ {++ "id": "ai.run",++ "version": "1.0.0",++ "owner": "ai",++ "schema": "ai/run.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-015"++ },++ {++ "id": "ai.run.queued",++ "version": "1.0.0",++ "owner": "ai",++ "schema": "events/payloads/ai.run.queued.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-015"++ },++ {++ "id": "ai.run.succeeded",++ "version": "1.0.0",++ "owner": "ai",++ "schema": "events/payloads/ai.run.succeeded.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-015"++ },++ {++ "id": "ai.run.failed",++ "version": "1.0.0",++ "owner": "ai",++ "schema": "events/payloads/ai.run.failed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-015"++ },++ {++ "id": "ai.run.cached",++ "version": "1.0.0",++ "owner": "ai",++ "schema": "events/payloads/ai.run.cached.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-015"++ },++ {++ "id": "tips.interaction.ready",++ "version": "1.0.0",++ "owner": "tips",++ "schema": "events/payloads/tips.interaction.ready.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "tips.interaction.failed",++ "version": "1.0.0",++ "owner": "tips",++ "schema": "events/payloads/tips.interaction.failed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "tips.interaction.cancelled",++ "version": "1.0.0",++ "owner": "tips",++ "schema": "events/payloads/tips.interaction.cancelled.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "tips.goal.updated",++ "version": "1.0.0",++ "owner": "tips",++ "schema": "events/payloads/tips.goal.updated.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "tips.overlay.delivered",++ "version": "1.0.0",++ "owner": "tips",++ "schema": "events/payloads/tips.overlay.delivered.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "tips.overlay.failed",++ "version": "1.0.0",++ "owner": "tips",++ "schema": "events/payloads/tips.overlay.failed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "vip.plan.published",++ "version": "1.0.0",++ "owner": "vip",++ "schema": "events/payloads/vip.plan.published.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "vip.membership.changed",++ "version": "1.0.0",++ "owner": "vip",++ "schema": "events/payloads/vip.membership.changed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.transaction.settled",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.transaction.settled.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.transaction.reversed",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.transaction.reversed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.entitlement.changed",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.entitlement.changed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.subscription.canceled",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.subscription.canceled.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.cashout.requested",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.cashout.requested.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.cashout.paid",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.cashout.paid.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.cashout.denied",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.cashout.denied.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.staff.action",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.staff.action.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "billing.receipt.external",++ "version": "1.0.0",++ "owner": "billing",++ "schema": "events/payloads/billing.receipt.external.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-012"++ },++ {++ "id": "deals.watch.matched",++ "version": "1.0.0",++ "owner": "deals",++ "schema": "events/payloads/deals.watch.matched.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "trade.alert.triggered",++ "version": "1.0.0",++ "owner": "trade",++ "schema": "events/payloads/trade.alert.triggered.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-020"++ },++ {++ "id": "tools.job.created",++ "version": "1.0.0",++ "owner": "tools",++ "schema": "events/payloads/tools.job.created.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned"++ },++ {++ "id": "tools.job.started",++ "version": "1.0.0",++ "owner": "tools",++ "schema": "events/payloads/tools.job.started.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned"++ },++ {++ "id": "tools.job.succeeded",++ "version": "1.0.0",++ "owner": "tools",++ "schema": "events/payloads/tools.job.succeeded.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned"++ },++ {++ "id": "tools.job.failed",++ "version": "1.0.0",++ "owner": "tools",++ "schema": "events/payloads/tools.job.failed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned"}]}nnew file mode 100644@@@ -0,0 +1,63 @@++{++ "$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.",++ "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": "cached" },++ "workflow": {++ "type": "object",++ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "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": "string", "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$" },++ "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 }++ }++ },++ "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" }++ }++}nnew file mode 100644@@@ -0,0 +1,68 @@++{++ "$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": {++ "type": "object",++ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "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 },++ "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" }++ }++}nnew file mode 100644@@@ -0,0 +1,63 @@++{++ "$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": {++ "type": "object",++ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "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 },++ "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": "null" }++ }++}nnew file mode 100644@@@ -0,0 +1,63 @@++{++ "$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.",++ "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": "succeeded" },++ "workflow": {++ "type": "object",++ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },++ "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 },++ "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" }++ }++}nnew file mode 100644@@@ -0,0 +1,33 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.denied.v1.json",++ "title": "BillingCashoutDeniedPayload",++ "description": "billing.cashout.denied v1 (OpenVibe.Billing server/ops/cashouts.js deny). Staff denied a cash-out; the escrowed bits went back (a billing.transaction.reversed follows in the same transaction). Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["cashout", "transaction_id"],++ "additionalProperties": false,++ "properties": {++ "cashout": {++ "type": "object",++ "required": ["id", "subject", "amount_bits", "value_cents", "status", "payout_method", "escrow_until", "request_txn", "settle_txn", "payout_provider", "payout_reference", "reason", "created_at", "updated_at"],++ "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$" },++ "subject": { "$ref": "../../identity/subject-ref.v1.json" },++ "amount_bits": { "type": "integer", "minimum": 1 },++ "value_cents": { "type": "integer" },++ "status": { "const": "denied" },++ "payout_method": { "type": "object", "description": "{ type, address } as the creator entered it (e.g. a PayPal email)." },++ "escrow_until": { "type": "string" },++ "request_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "settle_txn": { "type": ["string", "null"], "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "payout_provider": { "type": ["string", "null"] },++ "payout_reference": { "type": ["string", "null"] },++ "reason": { "type": ["string", "null"] },++ "created_at": { "type": "string" },++ "updated_at": { "type": "string" }++ }++ },++ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The cashout_denied transaction." }++ }++}nnew file mode 100644@@@ -0,0 +1,33 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.paid.v1.json",++ "title": "BillingCashoutPaidPayload",++ "description": "billing.cashout.paid v1 (OpenVibe.Billing server/ops/cashouts.js approve). Staff paid a cash-out out through a provider. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["cashout", "transaction_id"],++ "additionalProperties": false,++ "properties": {++ "cashout": {++ "type": "object",++ "required": ["id", "subject", "amount_bits", "value_cents", "status", "payout_method", "escrow_until", "request_txn", "settle_txn", "payout_provider", "payout_reference", "reason", "created_at", "updated_at"],++ "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$" },++ "subject": { "$ref": "../../identity/subject-ref.v1.json" },++ "amount_bits": { "type": "integer", "minimum": 1 },++ "value_cents": { "type": "integer" },++ "status": { "const": "paid" },++ "payout_method": { "type": "object", "description": "{ type, address } as the creator entered it (e.g. a PayPal email)." },++ "escrow_until": { "type": "string" },++ "request_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "settle_txn": { "type": ["string", "null"], "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "payout_provider": { "type": ["string", "null"] },++ "payout_reference": { "type": ["string", "null"] },++ "reason": { "type": ["string", "null"] },++ "created_at": { "type": "string" },++ "updated_at": { "type": "string" }++ }++ },++ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The cashout_paid transaction." }++ }++}nnew file mode 100644@@@ -0,0 +1,33 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.requested.v1.json",++ "title": "BillingCashoutRequestedPayload",++ "description": "billing.cashout.requested v1 (OpenVibe.Billing server/ops/cashouts.js request). A creator asked to cash out payable bits; they are held in escrow. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["cashout", "transaction_id"],++ "additionalProperties": false,++ "properties": {++ "cashout": {++ "type": "object",++ "required": ["id", "subject", "amount_bits", "value_cents", "status", "payout_method", "escrow_until", "request_txn", "settle_txn", "payout_provider", "payout_reference", "reason", "created_at", "updated_at"],++ "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$" },++ "subject": { "$ref": "../../identity/subject-ref.v1.json" },++ "amount_bits": { "type": "integer", "minimum": 1 },++ "value_cents": { "type": "integer" },++ "status": { "const": "requested" },++ "payout_method": { "type": "object", "description": "{ type, address } as the creator entered it (e.g. a PayPal email)." },++ "escrow_until": { "type": "string" },++ "request_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "settle_txn": { "type": ["string", "null"], "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "payout_provider": { "type": ["string", "null"] },++ "payout_reference": { "type": ["string", "null"] },++ "reason": { "type": ["string", "null"] },++ "created_at": { "type": "string" },++ "updated_at": { "type": "string" }++ }++ },++ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The cashout_request transaction." }++ }++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.entitlement.changed.v1.json",++ "title": "BillingEntitlementChangedPayload",++ "description": "billing.entitlement.changed v1 (OpenVibe.Billing server/ops/subscriptions.js pay, setStatus, revokeForTxn; entitlement()). A subscriber's channel-subscription entitlement to a streamer changed: a period was granted or renewed, the subscription ended or expired, or a reversed payment revoked a period. The payload is the entitlement as of now. Envelope: subject { type: entitlement, id: <subscriber usr_…>:channel_subscription:<streamer usr_…> }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["subject", "streamer", "kind", "active", "expires_at", "subscription", "reason"],++ "additionalProperties": false,++ "properties": {++ "subject": { "$ref": "../../identity/subject-ref.v1.json" },++ "streamer": { "$ref": "../../identity/subject-ref.v1.json" },++ "kind": { "const": "channel_subscription" },++ "active": { "type": "boolean" },++ "expires_at": { "type": ["string", "null"], "format": "date-time" },++ "subscription": {++ "type": ["object", "null"],++ "required": ["id", "status", "auto_renew", "cancel_at_period_end", "provider"],++ "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$" },++ "status": { "enum": ["active", "canceled", "expired"] },++ "auto_renew": { "type": "boolean" },++ "cancel_at_period_end": { "type": "boolean" },++ "provider": { "type": "string" }++ }++ },++ "reason": {++ "type": "string",++ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."++ },++ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "granted/renewed: the transaction that paid the period." },++ "revoked_period": {++ "type": "object",++ "required": ["starts_at", "ends_at"],++ "additionalProperties": false,++ "properties": { "starts_at": { "type": "string", "format": "date-time" }, "ends_at": { "type": "string", "format": "date-time" } },++ "description": "refund/chargeback: the period the reversed payment had granted."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,34 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.receipt.external.v1.json",++ "title": "BillingReceiptExternalPayload",++ "description": "billing.receipt.external v1 (OpenVibe.Billing server/ops/external.js record). An EXTERNAL receipt (ADR-012): a tip paid on a creator's own provider account (PowerChat route direct). The money never touches OpenVibe (no journal entry); Billing announces it once per payment, only when BILLING_AUTHORITY=billing and the receiving account is mapped to a creator. OpenVibe.Tips celebrates it (chat line, overlay alert, goal). Envelope: subject { type: provider_receipt, id: <provider>:<payment id> }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["classification", "provider", "receipt_ref", "provider_event_id", "delivery_id", "streamer", "receiving_account", "amount_cents", "currency", "value_bits", "donor_name", "anonymous", "message", "app_ref", "app_purpose", "occurred_at", "test", "rates"],++ "additionalProperties": false,++ "properties": {++ "classification": { "const": "EXTERNAL" },++ "provider": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{1,39}$" },++ "receipt_ref": { "type": "string", "description": "<provider>:<payment id>, e.g. powerchat:…" },++ "provider_event_id": { "type": "string", "minLength": 1, "maxLength": 200, "description": "The provider's id of the payment, stable across redeliveries." },++ "delivery_id": { "type": "string", "description": "The webhook delivery that carried it." },++ "streamer": { "$ref": "../../identity/subject-ref.v1.json" },++ "receiving_account": {++ "type": "object",++ "required": ["provider", "id", "username"],++ "additionalProperties": false,++ "properties": { "provider": { "type": "string" }, "id": { "type": ["string", "null"] }, "username": { "type": ["string", "null"] } }++ },++ "amount_cents": { "type": "integer", "minimum": 1, "maximum": 100000000 },++ "currency": { "const": "usd-cents" },++ "value_bits": { "type": "integer", "minimum": 0, "description": "Billing's value of the money in bits." },++ "donor_name": { "type": ["string", "null"], "maxLength": 80, "description": "null when anonymous." },++ "anonymous": { "type": "boolean" },++ "message": { "type": ["string", "null"], "maxLength": 500 },++ "app_ref": { "type": ["string", "null"] },++ "app_purpose": { "type": ["string", "null"], "maxLength": 120 },++ "occurred_at": { "type": ["string", "null"], "description": "The provider's timestamp as it sent it." },++ "test": { "type": "boolean" },++ "rates": { "type": "object", "required": ["bits_per_usd"], "properties": { "bits_per_usd": { "type": "number" } }, "description": "Rate snapshot." }++ }++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.staff.action.v1.json",++ "title": "BillingStaffActionPayload",++ "description": "billing.staff.action v1 (OpenVibe.Billing server/console/audit.js record). A staff member completed an action in the Billing console (only outcome done is announced; refused actions stay in staff_audit). Holds no secrets: codes, payout reference, counts. Envelope: subject { type: staff_action, id: sa_… }, visibility internal, priority important, actor the staff user ({ type: user, id: usr_… }).",++ "type": "object",++ "required": ["audit_id", "action", "outcome", "target", "reason", "request_id", "detail"],++ "additionalProperties": false,++ "properties": {++ "audit_id": { "type": "string", "pattern": "^sa_[0-9A-HJKMNP-TV-Z]{26}$" },++ "action": { "type": "string" },++ "outcome": { "const": "done" },++ "target": {++ "type": ["object", "null"],++ "required": ["type", "id"],++ "additionalProperties": false,++ "properties": { "type": { "type": "string" }, "id": { "type": "string" } }++ },++ "reason": { "type": ["string", "null"], "maxLength": 500 },++ "request_id": { "type": ["string", "null"] },++ "detail": { "type": "object", "description": "Small whitelisted detail of the action." }++ }++}nnew file mode 100644@@@ -0,0 +1,44 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.subscription.canceled.v1.json",++ "title": "BillingSubscriptionCanceledPayload",++ "description": "billing.subscription.canceled v1 (OpenVibe.Billing server/ops/subscriptions.js cancel). A subscription was set to cancel at period end (for Stripe, after Stripe accepted it); the paid period still runs. Envelope: subject { type: subscription, id: sub_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["subscription", "provider_sync", "actor"],++ "additionalProperties": false,++ "properties": {++ "subscription": {++ "type": "object",++ "required": ["id", "subscriber", "streamer", "tier", "provider", "provider_ref", "route", "status", "auto_renew", "cancel_at_period_end", "price_cents", "current_period_end", "created_at", "updated_at"],++ "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$" },++ "subscriber": { "$ref": "../../identity/subject-ref.v1.json" },++ "streamer": { "$ref": "../../identity/subject-ref.v1.json" },++ "tier": { "type": "integer", "minimum": 1 },++ "provider": { "type": "string" },++ "provider_ref": { "type": ["string", "null"] },++ "route": { "type": ["string", "null"] },++ "status": { "enum": ["active", "canceled", "expired"] },++ "auto_renew": { "type": "boolean" },++ "cancel_at_period_end": { "type": "boolean" },++ "price_cents": { "type": "integer", "minimum": 0 },++ "current_period_end": { "type": ["string", "null"] },++ "created_at": { "type": "string" },++ "updated_at": { "type": "string" }++ }++ },++ "provider_sync": { "enum": ["not_needed", "stripe_cancel_at_period_end"] },++ "actor": {++ "type": "object",++ "required": ["principal", "request_id"],++ "additionalProperties": false,++ "properties": {++ "principal": { "type": ["string", "null"], "description": "Token sub of the caller (svc:…, usr_…)." },++ "request_id": { "type": ["string", "null"] },++ "on_behalf_of": { "$ref": "../../identity/subject-ref.v1.json" }++ },++ "description": "Who asked: the calling principal and the person it acted for."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",++ "title": "BillingTransactionReversedPayload",++ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["transaction_id", "type", "test", "from_subject", "to_subject", "provider", "metadata", "reverses_txn"],++ "additionalProperties": false,++ "properties": {++ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "type": {++ "enum": ["purchase", "donation", "subscription", "subscription_share", "cashout_request", "cashout_paid", "cashout_denied", "recycle", "refund", "chargeback", "adjustment", "import"]++ },++ "test": { "type": "boolean", "description": "Test money: never counted." },++ "from_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved from, null for provider money." },++ "to_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved to." },++ "provider": { "type": ["string", "null"], "description": "stripe, paypal, powerchat…; null for credit movements." },++ "metadata": {++ "type": "object",++ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."++ },++ "reverses_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The transaction this one reverses." },++ "review": { "enum": ["required", null], "description": "Provider reversals only: required when Billing kept value it could not recover (staff review)." },++ "entitlements_revoked": { "type": "integer", "minimum": 0, "description": "Provider reversals only: subscription periods revoked." }++ }++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.settled.v1.json",++ "title": "BillingTransactionSettledPayload",++ "description": "billing.transaction.settled v1 (OpenVibe.Billing server/ops/purchases.js summary(); emitted by purchases, transfers, subscriptions, cashouts recycle and admin adjustments). A journal transaction was posted. Envelope: subject { type: transaction, id: txn_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": ["transaction_id", "type", "test", "from_subject", "to_subject", "provider", "metadata"],++ "additionalProperties": false,++ "properties": {++ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },++ "type": {++ "enum": ["purchase", "donation", "subscription", "subscription_share", "cashout_request", "cashout_paid", "cashout_denied", "recycle", "refund", "chargeback", "adjustment", "import"]++ },++ "test": { "type": "boolean", "description": "Test money: never counted." },++ "from_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved from, null for provider money." },++ "to_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved to." },++ "provider": { "type": ["string", "null"], "description": "stripe, paypal, powerchat…; null for credit movements." },++ "metadata": {++ "type": "object",++ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "$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}$" },++ "revision": { "type": "integer", "minimum": 1, "maximum": 9007199254740991, "description": "Index revision of the tombstone; wins over any document at the same or an older revision." }++ }++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "$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" },++ {++ "type": "object", "required": ["owner", "type", "deleted"],++ "properties": { "owner": { "const": "blog" }, "type": { "const": "post" }, "id": { "type": "string", "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$" }, "deleted": { "const": false } }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,28 @@++{++ "$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.",++ "type": "object",++ "required": ["blog", "state", "visibility", "authorship"],++ "additionalProperties": false,++ "properties": {++ "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}$" } }++ },++ "state": { "const": "draft" },++ "visibility": { "enum": ["public", "unlisted", "members", "private"] },++ "authorship": { "enum": ["human", "ai", "hybrid", "imported"], "description": "openvibe-publishing authorship mode (not Search's vocabulary)." },++ "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" }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "$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).",++ "type": "object",++ "required": ["canonical_url", "publication_state", "indexability", "blog", "visibility"],++ "additionalProperties": false,++ "properties": {++ "canonical_url": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },++ "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." },++ "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": "The post's own visibility (members = entitlement-gated)." }++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "$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" },++ "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}$" } } } },++ "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": "The post's own visibility (members = entitlement-gated)." }++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "$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": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },++ "publication_state": { "const": "unpublished" },++ "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." },++ "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": "The post's own visibility (members = entitlement-gated)." }++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "$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" },++ "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}$" } } } },++ "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": "The post's own visibility (members = entitlement-gated)." }++ }++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "$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.",++ "type": "object",++ "required": ["job_id", "action", "revision", "run_at", "attempts", "error", "blog_id"],++ "additionalProperties": false,++ "properties": {++ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },++ "action": { "enum": ["publish", "unpublish"] },++ "revision": { "type": ["integer", "null"], "minimum": 1, "description": "The revision a publish was to make live; null for an unpublish." },++ "run_at": { "type": "string", "format": "date-time", "description": "When the job was last due." },++ "attempts": { "type": "integer", "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." }++ }++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "$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 }."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,31 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/deals.watch.matched.v1.json",++ "title": "DealsWatchMatchedPayload",++ "description": "deals.watch.matched v1 (OpenVibe.Deals server/domain/watches.js onObservation). A person's keyword, product or price-below watch matched a fresh observation of an active offer, inside the transaction that recorded the observation. At most one event per (watch, observation); a keyword/product watch notifies once per offer, a price-below watch again only for a lower price. Saved searches (kind search) never notify. Envelope: subject { type: watch, id: <watch_id> }, visibility internal, priority important, actor service:deals. OpenVibe.Network turns it into a DEAL_WATCH_MATCH notification for payload.recipient (server/notifications/events-consumer.js). A price is the decimal exactly as stated, never inferred; null when the observation stated none.",++ "type": "object",++ "required": ["watch_id", "recipient", "kind", "query", "product_id", "max_price", "currency", "offer_id", "offer_url", "title", "observation"],++ "additionalProperties": false,++ "properties": {++ "watch_id": { "type": "string", "pattern": "^dwt_[0-9A-HJKMNP-TV-Z]{26}$" },++ "recipient": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The watch owner's user subject id: who is notified." },++ "kind": { "enum": ["keyword", "product", "price_below"] },++ "query": { "type": ["string", "null"], "maxLength": 200, "description": "Keywords of a keyword watch, or of a price-below watch without a product." },++ "product_id": { "type": ["string", "null"], "maxLength": 64, "description": "Deals product id of a product or price-below watch." },++ "max_price": { "type": ["string", "null"], "maxLength": 40, "description": "Price-below threshold as entered (decimal text, e.g. 19.99)." },++ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$", "description": "ISO 4217 currency of max_price." },++ "offer_id": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The canonical (root) offer id (dof_…)." },++ "offer_url": { "type": "string", "format": "uri", "pattern": "^https?://", "maxLength": 2048, "description": "Canonical offer page URL on Deals." },++ "title": { "type": "string", "minLength": 1, "maxLength": 500 },++ "observation": {++ "type": "object", "required": ["id", "observed_at", "price", "currency", "availability"], "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "pattern": "^dpo_[0-9A-HJKMNP-TV-Z]{26}$" },++ "observed_at": { "type": "string", "format": "date-time" },++ "price": { "type": ["string", "null"], "maxLength": 40, "description": "Decimal text as stated; null when the observation stated no price." },++ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },++ "availability": { "enum": ["in_stock", "out_of_stock", "preorder", "discontinued", "limited", "sold_out", "online_only", "in_store_only", null] }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "$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." }++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "$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." }++ }++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "$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." }++ }++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "$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": ["type", "id", "revision"],++ "additionalProperties": false,++ "properties": {++ "type": { "const": "item" },++ "id": { "type": "string", "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$" },++ "revision": { "type": "integer", "minimum": 1 }++ }++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "$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"++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "$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." }++ }++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "$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 }++ }++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "$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}$" }++ }++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.goal.updated.v1.json",++ "title": "TipsGoalUpdatedPayload",++ "description": "tips.goal.updated v1 (OpenVibe.Tips server/domain/goals.js changed). A goal was created, edited or closed, or its settled total moved (a contribution, the contribution that reached it, a reversal). Envelope: subject { type: goal, id: tgoal_…, revision }, visibility internal, priority important, actor service:tips.",++ "type": "object",++ "required": ["goal_id", "creator", "title", "target_amount", "current_amount", "currency", "status", "reached", "reason", "interaction_id"],++ "additionalProperties": false,++ "properties": {++ "goal_id": { "type": "string", "pattern": "^tgoal_[0-9A-HJKMNP-TV-Z]{26}$" },++ "creator": { "$ref": "../../identity/subject-ref.v1.json" },++ "title": { "type": "string" },++ "target_amount": { "type": "integer", "minimum": 1 },++ "current_amount": { "type": "integer", "description": "Settled total including any amount carried over from Live." },++ "currency": { "type": "string" },++ "status": { "enum": ["active", "closed"] },++ "reached": { "type": "boolean" },++ "reason": { "enum": ["created", "updated", "closed", "contribution", "reached", "reversal"] },++ "interaction_id": {++ "type": ["string", "null"],++ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The interaction behind a contribution, reached or reversal change; null otherwise."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,31 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.cancelled.v1.json",++ "title": "TipsInteractionCancelledPayload",++ "description": "tips.interaction.cancelled v1 (OpenVibe.Tips server/domain/interactions.js failPayment, cancelQueued). Undelivered effects were cancelled: the payment failed before settling (reason payment_failed, with the Billing error code), Billing reversed it (payment_reversed) or an effect came up while the payment was no longer settled (payment_not_settled). Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",++ "type": "object",++ "required": ["interaction_id", "creator", "supporter", "supporter_name", "kind", "amount", "currency", "settlement", "payment_state", "delivery_state", "billing_txn_id", "test", "reason"],++ "additionalProperties": false,++ "properties": {++ "interaction_id": { "type": "string", "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$" },++ "creator": { "$ref": "../../identity/subject-ref.v1.json" },++ "supporter": {++ "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],++ "description": "The supporter, null when external, anonymous or unmapped."++ },++ "supporter_name": { "type": ["string", "null"], "description": "Display name at the time." },++ "kind": { "enum": ["tip", "paid_message", "tts", "media_request"] },++ "amount": { "type": "integer", "minimum": 1, "description": "Integer amount in currency minor units (Vibes bits)." },++ "currency": { "type": "string", "description": "vibes-bits today." },++ "settlement": { "enum": ["billing", "external", "simulated", "imported"] },++ "payment_state": { "enum": ["pending", "settled", "reversed", "failed"] },++ "delivery_state": { "enum": ["awaiting_payment", "queued", "delivered", "failed", "cancelled"] },++ "billing_txn_id": { "type": ["string", "null"], "description": "The Billing transaction (txn_…) that settled it." },++ "test": { "type": "boolean", "description": "Simulations never emit events, so false in practice." },++ "reason": { "enum": ["payment_failed", "payment_reversed", "payment_not_settled"] },++ "code": {++ "type": "string",++ "description": "Only with reason payment_failed: the Billing problem code (billing.insufficient_funds, billing.funding_reversed, billing.refused…)."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.failed.v1.json",++ "title": "TipsInteractionFailedPayload",++ "description": "tips.interaction.failed v1 (OpenVibe.Tips server/domain/interactions.js recomputeDelivery). A delivery effect gave up after its retries and none is still queued; the payment state is untouched. Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",++ "type": "object",++ "required": ["interaction_id", "creator", "supporter", "supporter_name", "kind", "amount", "currency", "settlement", "payment_state", "delivery_state", "billing_txn_id", "test", "failed_effects"],++ "additionalProperties": false,++ "properties": {++ "interaction_id": { "type": "string", "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$" },++ "creator": { "$ref": "../../identity/subject-ref.v1.json" },++ "supporter": {++ "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],++ "description": "The supporter, null when external, anonymous or unmapped."++ },++ "supporter_name": { "type": ["string", "null"], "description": "Display name at the time." },++ "kind": { "enum": ["tip", "paid_message", "tts", "media_request"] },++ "amount": { "type": "integer", "minimum": 1, "description": "Integer amount in currency minor units (Vibes bits)." },++ "currency": { "type": "string", "description": "vibes-bits today." },++ "settlement": { "enum": ["billing", "external", "simulated", "imported"] },++ "payment_state": { "enum": ["pending", "settled", "reversed", "failed"] },++ "delivery_state": { "enum": ["awaiting_payment", "queued", "delivered", "failed", "cancelled"] },++ "billing_txn_id": { "type": ["string", "null"], "description": "The Billing transaction (txn_…) that settled it." },++ "test": { "type": "boolean", "description": "Simulations never emit events, so false in practice." },++ "failed_effects": {++ "type": "array",++ "minItems": 1,++ "items": {++ "type": "object",++ "required": ["effect", "adapter", "last_error"],++ "additionalProperties": false,++ "properties": {++ "effect": { "enum": ["chat_line", "paid_message", "tts", "media_request", "overlay_alert"] },++ "adapter": { "type": "string" },++ "last_error": { "type": ["string", "null"] }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.ready.v1.json",++ "title": "TipsInteractionReadyPayload",++ "description": "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.",++ "type": "object",++ "required": ["interaction_id", "creator", "supporter", "supporter_name", "kind", "amount", "currency", "settlement", "payment_state", "delivery_state", "billing_txn_id", "test"],++ "additionalProperties": false,++ "properties": {++ "interaction_id": { "type": "string", "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$" },++ "creator": { "$ref": "../../identity/subject-ref.v1.json" },++ "supporter": {++ "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],++ "description": "The supporter, null when external, anonymous or unmapped."++ },++ "supporter_name": { "type": ["string", "null"], "description": "Display name at the time." },++ "kind": { "enum": ["tip", "paid_message", "tts", "media_request"] },++ "amount": { "type": "integer", "minimum": 1, "description": "Integer amount in currency minor units (Vibes bits)." },++ "currency": { "type": "string", "description": "vibes-bits today." },++ "settlement": { "enum": ["billing", "external", "simulated", "imported"] },++ "payment_state": { "enum": ["pending", "settled", "reversed", "failed"] },++ "delivery_state": { "enum": ["awaiting_payment", "queued", "delivered", "failed", "cancelled"] },++ "billing_txn_id": { "type": ["string", "null"], "description": "The Billing transaction (txn_…) that settled it." },++ "test": { "type": "boolean", "description": "Simulations never emit events, so false in practice." }++ }++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.delivered.v1.json",++ "title": "TipsOverlayDeliveredPayload",++ "description": "tips.overlay.delivered v1 (OpenVibe.Tips server/domain/overlays.js markDelivered). An overlay delivery (alert or goal update) reached at least one connected overlay, first time only. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",++ "type": "object",++ "required": ["delivery_id", "creator", "kind", "interaction_id", "goal_id"],++ "additionalProperties": false,++ "properties": {++ "delivery_id": { "type": "string", "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$" },++ "creator": { "$ref": "../../identity/subject-ref.v1.json" },++ "kind": { "enum": ["alert", "goal"] },++ "interaction_id": { "type": ["string", "null"] },++ "goal_id": { "type": ["string", "null"] }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.failed.v1.json",++ "title": "TipsOverlayFailedPayload",++ "description": "tips.overlay.failed v1 (OpenVibe.Tips server/domain/overlays.js sweepFailed). No overlay showed the delivery within the delivery window. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",++ "type": "object",++ "required": ["delivery_id", "creator", "kind", "interaction_id", "goal_id", "reason"],++ "additionalProperties": false,++ "properties": {++ "delivery_id": { "type": "string", "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$" },++ "creator": { "$ref": "../../identity/subject-ref.v1.json" },++ "kind": { "enum": ["alert", "goal"] },++ "interaction_id": { "type": ["string", "null"] },++ "goal_id": { "type": ["string", "null"] },++ "reason": { "type": "string" }++ }++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.created.v1.json",++ "title": "ToolsJobCreatedPayload",++ "description": "tools.job.created v1 (OpenVibe.Tools apps/_shared/jobs, planned). A job was accepted and queued (POST /api/v1/jobs, or a retry of a failed job, which carries retry_of). An Idempotency-Key replay creates no job and no event. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor the owner when it is a subject (user, service, app or mod), else service:tools.",++ "type": "object",++ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of"],++ "additionalProperties": false,++ "properties": {++ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },++ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },++ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },++ "type_version": { "type": "integer", "minimum": 1 },++ "state": { "const": "queued" },++ "owner": {++ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": { "type": "integer", "minimum": 0 },++ "max_attempts": { "type": "integer", "minimum": 1 },++ "created_at": { "type": "string", "format": "date-time" },++ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." }++ }++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.failed.v1.json",++ "title": "ToolsJobFailedPayload",++ "description": "tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",++ "type": "object",++ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of", "started_at", "finished_at", "expires_at", "error", "retryable"],++ "additionalProperties": false,++ "properties": {++ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },++ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },++ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },++ "type_version": { "type": "integer", "minimum": 1 },++ "state": { "const": "failed" },++ "owner": {++ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": { "type": "integer", "minimum": 0 },++ "max_attempts": { "type": "integer", "minimum": 1 },++ "created_at": { "type": "string", "format": "date-time" },++ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." },++ "started_at": { "type": ["string", "null"], "format": "date-time", "description": "null when the job failed before it ever ran." },++ "finished_at": { "type": "string", "format": "date-time" },++ "expires_at": { "type": ["string", "null"], "format": "date-time", "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)." },++ "error": {++ "type": "object", "required": ["status", "code", "detail"], "additionalProperties": false,++ "description": "The job's problem (errors.problem@1) without request or trace ids.",++ "properties": {++ "status": { "type": "integer", "minimum": 400, "maximum": 599 },++ "code": { "type": "string", "pattern": "^tools\\.[a-z0-9_.]+$", "description": "tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…)." },++ "detail": { "type": "string", "maxLength": 500, "description": "Human-readable reason; server paths are scrubbed." }++ }++ },++ "retryable": { "type": "boolean" }++ }++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.started.v1.json",++ "title": "ToolsJobStartedPayload",++ "description": "tools.job.started v1 (OpenVibe.Tools apps/_shared/jobs, planned). A worker claimed the job and it is running (attempts counts this attempt). A job requeued after a restart starts again and is announced again. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:tools.",++ "type": "object",++ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of", "started_at"],++ "additionalProperties": false,++ "properties": {++ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },++ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },++ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },++ "type_version": { "type": "integer", "minimum": 1 },++ "state": { "const": "running" },++ "owner": {++ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": { "type": "integer", "minimum": 0 },++ "max_attempts": { "type": "integer", "minimum": 1 },++ "created_at": { "type": "string", "format": "date-time" },++ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." },++ "started_at": { "type": "string", "format": "date-time" }++ }++}nnew file mode 100644@@@ -0,0 +1,47 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.succeeded.v1.json",++ "title": "ToolsJobSucceededPayload",++ "description": "tools.job.succeeded v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job finished and its result files are stored (in Media when TOOLS_JOB_RESULTS=media). Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",++ "type": "object",++ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of", "started_at", "finished_at", "expires_at", "result"],++ "additionalProperties": false,++ "properties": {++ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },++ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },++ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },++ "type_version": { "type": "integer", "minimum": 1 },++ "state": { "const": "succeeded" },++ "owner": {++ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": { "type": "integer", "minimum": 0 },++ "max_attempts": { "type": "integer", "minimum": 1 },++ "created_at": { "type": "string", "format": "date-time" },++ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." },++ "started_at": { "type": "string", "format": "date-time" },++ "finished_at": { "type": "string", "format": "date-time" },++ "expires_at": { "type": ["string", "null"], "format": "date-time", "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)." },++ "result": {++ "type": "object", "required": ["files"], "additionalProperties": false,++ "description": "Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).",++ "properties": {++ "files": {++ "type": "array", "maxItems": 100,++ "items": {++ "type": "object", "required": ["index", "mime", "size", "sha256", "storage", "media"], "additionalProperties": false,++ "properties": {++ "index": { "type": "integer", "minimum": 0, "description": "n in /api/v1/jobs/:id/files/:n." },++ "mime": { "type": "string", "minLength": 1, "maxLength": 200 },++ "size": { "type": "integer", "minimum": 0 },++ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },++ "storage": { "enum": ["local", "media"], "description": "media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires." },++ "media": { "oneOf": [{ "$ref": "../../media/media-ref.v1.json" }, { "type": "null" }], "description": "{ media_id, role: output } when storage is media." }++ }++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,78 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/trade.alert.triggered.v1.json",++ "title": "TradeAlertTriggeredPayload",++ "description": "trade.alert.triggered v1 (OpenVibe.Trade server/domain/alerts.js deliver). One delivery of one private alert rule: a threshold crossed by a new observation (trigger kind observation), or a newly seen document of the instrument (filing_type / new_document rules, trigger kind document). Written in the transaction that recorded the observation or document; at most one per (rule, trigger). Envelope: subject { type: user, id: <rule owner usr_…> }, visibility subject, priority important, actor service:trade. Data only, never rendered notification text (ADR-020): OpenVibe.Network renders a TRADE_ALERT notification (server/notifications/events-consumer.js). Information only, not investment advice.",++ "type": "object",++ "required": ["delivery_id", "rule", "instrument", "trigger", "disclaimer"],++ "additionalProperties": false,++ "properties": {++ "delivery_id": { "type": "string", "pattern": "^ald_[0-9A-HJKMNP-TV-Z]{26}$" },++ "rule": {++ "type": "object", "additionalProperties": false,++ "required": ["id", "kind", "status", "instrument", "metric", "operator", "threshold", "unit", "currency", "form_types", "armed", "created_at", "updated_at"],++ "properties": {++ "id": { "type": "string", "pattern": "^alr_[0-9A-HJKMNP-TV-Z]{26}$" },++ "kind": { "enum": ["threshold", "filing_type", "new_document"] },++ "status": { "enum": ["active", "deleted"] },++ "instrument": {++ "type": "object", "required": ["id"], "additionalProperties": false,++ "properties": { "id": { "type": "string", "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$" }, "symbol": { "type": "string" }, "name": { "type": "string" } }++ },++ "metric": { "type": ["string", "null"], "maxLength": 80, "description": "Threshold rules: e.g. price.close, volume, us-gaap:Revenues." },++ "operator": { "enum": ["above", "below", null] },++ "threshold": { "type": ["string", "null"], "maxLength": 64, "description": "Decimal text as entered." },++ "unit": { "type": ["string", "null"], "maxLength": 20 },++ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },++ "form_types": { "type": ["array", "null"], "items": { "type": "string", "maxLength": 20 }, "description": "filing_type rules: the 1–10 form types that trigger it (10-K, 8-K…)." },++ "armed": { "type": ["boolean", "null"], "description": "Threshold rules: armed before this delivery (always true when it fired); null for document rules." },++ "created_at": { "type": "string", "format": "date-time" },++ "updated_at": { "type": "string", "format": "date-time" }++ }++ },++ "instrument": {++ "type": "object", "required": ["id", "symbol", "name", "url"], "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$" },++ "symbol": { "type": "string", "minLength": 1, "maxLength": 32 },++ "name": { "type": "string", "minLength": 1, "maxLength": 300 },++ "url": { "type": "string", "format": "uri", "pattern": "^https?://", "maxLength": 2048, "description": "Instrument page on Trade." }++ }++ },++ "trigger": {++ "oneOf": [++ {++ "type": "object", "additionalProperties": false,++ "required": ["kind", "id", "metric", "value", "unit", "currency", "observed_at", "retrieved_at", "source_key", "source_url"],++ "properties": {++ "kind": { "const": "observation" },++ "id": { "type": "string", "pattern": "^obs_[0-9A-HJKMNP-TV-Z]{26}$" },++ "metric": { "type": "string", "minLength": 1, "maxLength": 80 },++ "value": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The decimal exactly as the source stated it." },++ "unit": { "type": "string", "minLength": 1, "maxLength": 20 },++ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },++ "observed_at": { "type": "string", "format": "date-time" },++ "retrieved_at": { "type": "string", "format": "date-time" },++ "source_key": { "type": "string", "minLength": 1, "maxLength": 128, "description": "OpenVibe.Sources source key." },++ "source_url": { "type": ["string", "null"], "maxLength": 2048 }++ }++ },++ {++ "type": "object", "additionalProperties": false,++ "required": ["kind", "id", "form_type", "title", "url", "published_at", "retrieved_at", "source_key"],++ "properties": {++ "kind": { "const": "document" },++ "id": { "type": "string", "pattern": "^doc_[0-9A-HJKMNP-TV-Z]{26}$" },++ "form_type": { "type": ["string", "null"], "maxLength": 20 },++ "title": { "type": ["string", "null"], "maxLength": 1000 },++ "url": { "type": ["string", "null"], "maxLength": 2048 },++ "published_at": { "type": ["string", "null"], "format": "date-time", "description": "The source's own date; null if it gave none." },++ "retrieved_at": { "type": "string", "format": "date-time" },++ "source_key": { "type": "string", "minLength": 1, "maxLength": 128 }++ }++ }++ ]++ },++ "disclaimer": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Currently \"Information only — not investment advice; no trading here.\"" }++ }++}nnew file mode 100644@@@ -0,0 +1,32 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vip.membership.changed.v1.json",++ "title": "VipMembershipChangedPayload",++ "description": "vip.membership.changed v1 (OpenVibe.VIP server/domain/entitlements.js emitChanged). A member's standing with a creator changed as projected from Billing (granted, renewed, cancel scheduled, expired, refunded…): active, expires_at or cancel_at_period_end differ from before, or a first look found an active membership. Names the plan version the membership was bought under. Envelope: subject { type: membership, id: <member usr_…>:<creator usr_…> }, visibility internal, priority important, actor service:vip.",++ "type": "object",++ "required": ["member", "creator", "kind", "active", "expires_at", "cancel_at_period_end", "previous", "reason", "source", "billing_event_id", "membership_id", "plan_id", "plan_version_id", "plan_version", "subscription_id"],++ "additionalProperties": false,++ "properties": {++ "member": { "$ref": "../../identity/subject-ref.v1.json" },++ "creator": { "$ref": "../../identity/subject-ref.v1.json" },++ "kind": { "type": "string", "description": "Billing entitlement kind; channel_subscription today." },++ "active": { "type": "boolean" },++ "expires_at": { "type": ["string", "null"] },++ "cancel_at_period_end": { "type": "boolean" },++ "previous": {++ "type": ["object", "null"],++ "required": ["active", "expires_at", "cancel_at_period_end"],++ "additionalProperties": false,++ "properties": { "active": { "type": "boolean" }, "expires_at": { "type": ["string", "null"] }, "cancel_at_period_end": { "type": "boolean" } },++ "description": "The projection before the change; null on first sight."++ },++ "reason": { "type": ["string", "null"], "description": "Billing's reason (granted, renewed, refund, expired…) or VIP's (checked, cancel_scheduled)." },++ "source": { "enum": ["event", "billing_check", "import"] },++ "billing_event_id": { "type": ["string", "null"], "description": "The Billing event (evt_…) behind the change, when it came from one." },++ "membership_id": { "type": ["string", "null"], "pattern": "^vms_[0-9A-HJKMNP-TV-Z]{26}$" },++ "plan_id": { "type": ["string", "null"] },++ "plan_version_id": { "type": ["string", "null"] },++ "plan_version": { "type": ["integer", "null"], "minimum": 1 },++ "subscription_id": { "type": ["string", "null"], "description": "The Billing subscription (sub_…)." }++ }++}nnew file mode 100644@@@ -0,0 +1,61 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vip.plan.published.v1.json",++ "title": "VipPlanPublishedPayload",++ "description": "vip.plan.published v1 (OpenVibe.VIP server/domain/plans.js emitPublished). A plan version became what new members buy: the plan was published, or a published plan was edited (a new immutable version). Carries the version's full terms, never a price (Billing prices and charges). Envelope: subject { type: plan, id: vpl_…, revision: <version> }, visibility public, priority important, actor service:vip.",++ "type": "object",++ "required": ["plan_id", "slug", "version", "version_id", "creator", "billing_kind", "terms", "published_at"],++ "additionalProperties": false,++ "properties": {++ "plan_id": { "type": "string", "pattern": "^vpl_[0-9A-HJKMNP-TV-Z]{26}$" },++ "slug": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,47}$" },++ "version": { "type": "integer", "minimum": 1 },++ "version_id": { "type": "string", "pattern": "^vpv_[0-9A-HJKMNP-TV-Z]{26}$" },++ "creator": {++ "type": "object",++ "required": ["id", "kind", "subject", "username", "display_name", "bio", "status"],++ "additionalProperties": false,++ "properties": {++ "id": { "type": "string", "description": "vcr_… or network." },++ "kind": { "enum": ["creator", "network"] },++ "subject": { "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }], "description": "null for the network creator." },++ "username": { "type": ["string", "null"] },++ "display_name": { "type": ["string", "null"] },++ "bio": { "type": ["string", "null"] },++ "status": { "enum": ["active", "suspended"] }++ }++ },++ "billing_kind": {++ "enum": ["channel_subscription", null],++ "description": "The Billing product that sells it; null for a plan Billing does not sell (network plans)."++ },++ "terms": {++ "type": "object",++ "required": ["name", "description", "benefits", "perks", "billing_kind", "price", "digest"],++ "additionalProperties": false,++ "properties": {++ "name": { "type": "string" },++ "description": { "type": ["string", "null"] },++ "benefits": { "type": ["array", "null"], "maxItems": 20, "items": { "type": "string" } },++ "perks": {++ "type": "array",++ "items": {++ "type": "object",++ "required": ["key", "name", "kind", "scope"],++ "additionalProperties": false,++ "properties": {++ "key": { "type": "string" },++ "name": { "type": "string" },++ "kind": { "enum": ["badge", "emote", "gated_content", "room", "role", "other"] },++ "scope": { "enum": ["creator", "network"] }++ }++ }++ },++ "billing_kind": { "enum": ["channel_subscription", null] },++ "price": { "type": ["string", "null"], "description": "Never an amount: a note that Billing sets and charges the price, or null." },++ "digest": { "type": "string", "pattern": "^[0-9a-f]{16}$", "description": "sha256 prefix of name, description, benefits, perks and billing_kind." }++ }++ },++ "published_at": { "type": "string", "format": "date-time" }++ }++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "$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." }++ }++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "$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" },++ {++ "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 } }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$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).",++ "type": "object",++ "required": ["canonical_url", "publication_state", "indexability", "space", "slug", "visibility"],++ "additionalProperties": false,++ "properties": {++ "canonical_url": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },++ "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": "^[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)." }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$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": "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)." }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$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).",++ "type": "object",++ "required": ["canonical_url", "publication_state", "indexability", "space", "slug", "visibility"],++ "additionalProperties": false,++ "properties": {++ "canonical_url": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },++ "publication_state": { "const": "unpublished" },++ "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": "^[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)." }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$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).",++ "type": "object",++ "required": ["canonical_url", "publication_state", "indexability", "space", "slug", "visibility"],++ "additionalProperties": false,++ "properties": {++ "canonical_url": { "type": "string", "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)." }++ }++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "$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.",++ "type": "object",++ "required": ["space_id", "space", "slug", "number", "kind", "parent_number", "reverted_to", "authorship", "author", "message"],++ "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": { "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 },++ "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." }++ }++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "$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." }++ }++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "$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", "null"], "minimum": 1 },++ "url": { "type": "string", "format": "uri", "pattern": "^https?://", "maxLength": 2048 },++ "recipients": {++ "type": "array", "minItems": 1, "uniqueItems": true,++ "items": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },++ "description": "Watchers to notify."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/redaction-directive.v1.json",++ "title": "RedactionDirective",++ "description": "payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.",++ "type": "object",++ "minProperties": 1,++ "additionalProperties": false,++ "dependentRequired": { "subject_type": ["subject_ids"], "subject_ids": ["subject_type"] },++ "properties": {++ "event_ids": {++ "type": "array", "minItems": 1, "maxItems": 1000,++ "items": { "type": "string", "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$" },++ "description": "Events to redact by id. Duplicates are ignored."++ },++ "subject_type": {++ "type": "string", "pattern": "^[a-z][a-z0-9_]{0,63}$",++ "description": "Envelope subject.type of the events to redact (chat_message, post…). Goes with subject_ids."++ },++ "subject_ids": {++ "type": "array", "minItems": 1, "maxItems": 1000,++ "items": { "type": "string", "minLength": 1, "maxLength": 200 },++ "description": "Envelope subject.id values; every not-yet-redacted event of the same source about each one is redacted."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/tombstone-payload.v1.json",++ "title": "TombstonePayload",++ "description": "The payload of a redacted event (ADR-026). OpenVibe.Events replaces the original payload with this and the actor with the producer itself ({ type: service, id: <source> }, or the app); event_id, seq, event_type, version, subject, timestamp and visibility stay. Every read path serves it from then on: pull, GET by id, SSE replay, queued deliveries and DLQ replays. A consumer checks payload.redacted before validating a payload against its event type's schema.",++ "type": "object",++ "required": ["redacted", "redacted_at", "redacted_by"],++ "additionalProperties": false,++ "properties": {++ "redacted": { "const": true },++ "redacted_at": { "type": "string", "format": "date-time" },++ "redacted_by": {++ "type": "string", "minLength": 1, "maxLength": 64,++ "description": "The event_id of the redacting event; an operator label (e.g. backfill) when OpenVibe.Events redacted by script."++ }++ }++}@@@ -20,6 +20,12 @@ Cross-service side effects are best-effort POSTs and HMAC webhooks; a consumer t- Consumers accept a v2 signature only within ±300 s of their clock, compare in constant time, and never fall back to v1 when a v2 header is present but bad or stale. With `requireV2` they also refuse deliveries without v2, which closes the replay-by-stripping-the-header path.- Rollout is additive: Events sends v1 and v2 first; consumers then move to openvibe-sdk 0.4.0 (v2 checked whenever present) and set `requireV2: true` one by one; v1 stays on the wire until every consumer requires v2, and only then may it be removed. Rollback at any step is to unset `requireV2` (consumers) or stop sending v2 (Events, only while no consumer requires it).++### Redaction, payload contracts and the public replay window (2026-09-23)++++- A producer takes back its own events with `payload.redacts` (`events.redaction-directive@1`). Events turns the targets into tombstones (`events.tombstone-payload@1`) at their original seq. Naming another source's event is 403 `events.redaction_not_allowed`, and a malformed directive is 422 `events.invalid_redaction`. See [ADR-026](ADR-026-event-redaction.md).++- Each event type's payload has a contract named after the type, with the envelope `version` as its major (`contracts/events/payloads/<event_type>.v<version>.json`, from v0.30.0).++- Browsers are replayed only the `public` events of the last `REALTIME_PUBLIC_REPLAY_SECONDS` (default 300). An older cursor gets `event: gap` with reason `public_window`.++## Alternatives considered- Kafka/NATS now: rejected, operational weight far beyond current volume.nnew file mode 100644@@@ -0,0 +1,95 @@++# ADR-026: Producer redaction of its own events (`payload.redacts`)++++**Status:** Accepted 2026-09-23; implemented in OpenVibe.Events (`server/redaction.js`, `server/store.js`,++`test/redaction.test.js`) and used by OpenVibe.Chat (`chat.message.deleted`).++++## Context and current evidence++++OpenVibe.Events keeps every event for its retention period and serves it again through pull, `GET++/api/v1/events/:id`, SSE replay, queued deliveries and DLQ replays (ADR-004, ADR-005). A public++`chat.message.created` carries the message text and the author. When the message was deleted in Chat,++the stored event stayed replayable, anonymously, for 30 days. Deleting a row in the producer did not++reach the copy in Events, and no consumer could tell that the content was withdrawn.++++## Decision++++- **Any event may carry `payload.redacts`** (`events.redaction-directive@1`):++ `{ "event_ids": ["evt_…"], "subject_type": "chat_message", "subject_ids": ["123"] }`. `event_ids`++ names events directly. `subject_type` + `subject_ids` (always together) names every stored event of++ the same source about those subjects. Either form or both may be used, with up to 1000 ids each and no++ empty list. Normally the directive rides on the producer's own deletion event (`chat.message.deleted`),++ whose payload carries only ids.++- **A producer may redact only its own events.** A target must have the same `source` as the redacting++ event; for a developer app it must also be in the same project and environment. When an `event_id`++ names another source's event, Events refuses the whole publish batch with **403++ `events.redaction_not_allowed`**. A subject match never reaches another source. No capability is++ needed beyond `events.event.publish` (or `events.app.publish`).++- **A malformed directive is 422 `events.invalid_redaction`**. This covers a non-object, an unknown++ field, a bad or too long id list, `subject_type` without `subject_ids` (or the reverse), and a++ directive that names nothing.++- **Targets become tombstones in the same transaction that stores the directive.** Their payload is++ replaced by `{ "redacted": true, "redacted_at": "<ISO>", "redacted_by": "<redacting event_id>" }`++ (`events.tombstone-payload@1`), and the actor by the producer itself (`service:<source>`, or the app).++ `event_id`, `seq`, `event_type`, `version`, `subject`, `timestamp` and `visibility` stay, so sequences++ have no holes. The old payload is overwritten on disk (`secure_delete`).++- **Every read path serves the tombstone** from then on. First-party consumers receive the tombstone at++ the original seq and then the deletion event. **A consumer checks `payload.redacted === true` before++ validating a payload against its event type's schema.** A tombstone never matches the type's payload++ contract.++- An event that carries a directive is never redacted itself. Redaction applies to what is stored when++ the directive arrives; it does not block later events about the same subject.++- An operator may redact events published before the producer announced deletions++ (`scripts/redact-backfill.js` in OpenVibe.Events, dry run by default). Those tombstones name the++ operator run (e.g. `backfill`) as `redacted_by`.++- **The payload contract lists `redacts` when the producer sends it.** It is `"redacts": { "$ref":++ "../redaction-directive.v1.json" }`, required when every event of that type carries one++ (`chat.message.deleted`). A producer that starts redacting from another event type adds the optional++ property in a minor contract release.++++### Public realtime replay window++++Browsers (signed out or signed in) are replayed `public` events received in the last++`REALTIME_PUBLIC_REPLAY_SECONDS` only (default **300**), which is enough to ride out a reconnect. An++older cursor gets `event: gap` with `reason: "public_window"` (`from_seq`, `to_seq`, `latest_seq`,++`window_seconds`), so the SSE stream cannot be used to page through a month of public history.++`subject` events addressed to the viewer, and service viewers, keep the whole retention. Redacted++events are replayed as their tombstones.++++## Event payload contracts++++Each event type's payload has its own contract, named after the event type. The major version is the++envelope `version`. The file is `contracts/events/payloads/<event_type>.v<version>.json`, so a++consumer validates `contracts.validate(`${env.event_type}@${env.version}`, env.payload)` after++checking for a tombstone. The owner is the producing service, and the service manifest lists the type++in `eventsProduced`. Contracts with catalog status `planned` describe events that the owner has not++emitted yet (`tools.job.*` in v0.30.0).++++## Alternatives considered++++- **Hard delete in Events:** rejected, because it leaves holes in `seq` that break every cursor and++ resume, and consumers could not tell a withdrawal from a lost event.++- **A separate redaction endpoint and capability:** rejected, because the directive has to be atomic with++ the producer's own outbox (the deletion and its announcement commit together). The publish owner rule++ already gives exactly the authority needed.++- **Consumers filtering deleted ids themselves:** rejected, because anonymous replays and deliveries that++ are already queued would still carry the text.++++## Migration consequences++++Chat publishes `chat.message.deleted` with `redacts` for every delete path. The operator backfill++redacted what Chat had deleted earlier. Other producers adopt the directive on their own `*.deleted` or++`*.removed` events when those carry personal content.++++## Rollback++++A producer stops sending `redacts`. Tombstones already written stay tombstones, because the original++payload is gone by design.++++## Acceptance tests++++OpenVibe.Events `test/redaction.test.js`: every read path (pull, GET, anonymous/signed-in/service SSE++replay, queued delivery, DLQ replay) returns the tombstone at an unchanged seq; another source's event++is refused with 403 and the whole batch is not stored; a malformed directive is 422; a directive event++is never redacted. The public window test checks that anonymous replay older than 300 s yields `gap`++`public_window`. Contracts: `events.redaction-directive@1` and `events.tombstone-payload@1` fixtures;++every event payload contract has valid and invalid fixtures.@@@ -29,4 +29,5 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each| [ADR-023](ADR-023-client-surfaces.md) | First-party client surfaces | Accepted 2026-09-23: OpenVibe.Extensions at Wave 18 || [ADR-024](ADR-024-theme-authority.md) | Theme authority between Network and Shared | Accepted 2026-09-23 || [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational |++| [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) |nnew file mode 100644@@@ -0,0 +1 @@++{ "workflow": "live.chat_reply", "on_behalf_of": { "type": "user", "id": "42" } }nnew file mode 100644@@@ -0,0 +1 @@++{ "input": { "text": "hi" } }nnew file mode 100644@@@ -0,0 +1 @@++{ "workflow": "summarize", "input": {} }nnew file mode 100644@@@ -0,0 +1 @@++{ "workflow": "network.site_copy", "input": { "sites": [] } }nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "workflow": "news.summarize_story",++ "input": { "topic": "Transit strike", "sources": [{ "source_type": "news.item", "source_id": "itm_42", "url": "https://example.com/a", "title": "Strike begins" }] },++ "target": { "service": "news", "type": "story", "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ" },++ "idempotency_key": "news:story:sty_01JAB2C3:v3",++ "options": { "cache": false }++}nnew file mode 100644@@@ -0,0 +1,35 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "cached",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "attribution": null,++ "provider": "anthropic",++ "model": "claude-sonnet",++ "fallback_used": true,++ "synthetic": false,++ "cached_from": null,++ "usage": {++ "tokens_in": 1200,++ "tokens_out": 300,++ "cost_usd": 0.0081,++ "attempts": 2++ },++ "error": null,++ "citations_count": 1,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,31 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "status": "cached",++ "workflow": {++ "key": "network.site_copy",++ "version": 1++ },++ "requester": {++ "type": "service",++ "id": "network"++ },++ "on_behalf_of": null,++ "target": null,++ "attribution": null,++ "provider": null,++ "model": "claude-sonnet",++ "fallback_used": false,++ "synthetic": false,++ "cached_from": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "usage": {++ "tokens_in": 0,++ "tokens_out": 0,++ "cost_usd": 0,++ "attempts": 0++ },++ "error": null,++ "citations_count": 0,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": "2026-09-23T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,35 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "failed",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "attribution": null,++ "provider": "anthropic",++ "model": "claude-sonnet",++ "fallback_used": true,++ "synthetic": false,++ "cached_from": null,++ "usage": {++ "tokens_in": 1200,++ "tokens_out": 300,++ "cost_usd": 0.0081,++ "attempts": 2++ },++ "error": null,++ "citations_count": 1,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,38 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "failed",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "attribution": null,++ "provider": null,++ "model": null,++ "fallback_used": false,++ "synthetic": false,++ "cached_from": null,++ "usage": {++ "tokens_in": 0,++ "tokens_out": 0,++ "cost_usd": 0,++ "attempts": 1++ },++ "error": {++ "code": "run.interrupted",++ "detail": "the service restarted while this run was in progress"++ },++ "citations_count": 0,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,35 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "queued",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "attribution": null,++ "provider": "anthropic",++ "model": "claude-sonnet",++ "fallback_used": true,++ "synthetic": false,++ "cached_from": null,++ "usage": {++ "tokens_in": 1200,++ "tokens_out": 300,++ "cost_usd": 0.0081,++ "attempts": 2++ },++ "error": null,++ "citations_count": 1,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,34 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "queued",++ "workflow": {++ "key": "live.chat_reply",++ "version": 1++ },++ "requester": {++ "type": "service",++ "id": "live"++ },++ "on_behalf_of": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "target": null,++ "attribution": null,++ "provider": null,++ "model": null,++ "fallback_used": false,++ "synthetic": false,++ "cached_from": null,++ "usage": {++ "tokens_in": 0,++ "tokens_out": 0,++ "cost_usd": 0,++ "attempts": 0++ },++ "error": null,++ "citations_count": 0,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": null++}nnew file mode 100644@@@ -0,0 +1,38 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "succeeded",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "attribution": null,++ "provider": "anthropic",++ "model": "claude-sonnet",++ "fallback_used": true,++ "synthetic": false,++ "cached_from": null,++ "usage": {++ "tokens_in": 1200,++ "tokens_out": 300,++ "cost_usd": 0.0081,++ "attempts": 2++ },++ "error": null,++ "citations_count": 1,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": "2026-09-23T10:00:04.200Z",++ "output": {++ "summary": "x"++ }++}nnew file mode 100644@@@ -0,0 +1,38 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "succeeded",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "attribution": null,++ "provider": "anthropic",++ "model": "claude-sonnet",++ "fallback_used": true,++ "synthetic": false,++ "cached_from": null,++ "usage": {++ "tokens_in": 1200,++ "tokens_out": 300,++ "cost_usd": 0.0081,++ "attempts": 2++ },++ "error": {++ "code": "run.internal",++ "detail": "internal error"++ },++ "citations_count": 1,++ "retry_of": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ", "status": "succeeded",++ "workflow": { "key": "news.summarize_story", "version": 2 },++ "requester": { "type": "service", "id": "news" }, "on_behalf_of": null,++ "target": { "service": "news", "type": "story", "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ" }, "attribution": null,++ "provider": "anthropic", "model": "claude-sonnet", "fallback_used": true, "synthetic": false, "cached_from": null,++ "usage": { "tokens_in": 1200, "tokens_out": 300, "cost_usd": 0.0081, "attempts": 2 }, "error": null,++ "citations_count": 1, "retry_of": null, "created_at": "2026-09-23T10:00:00.000Z", "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,68 @@++{++ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "succeeded",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "template": {++ "key": "news.summarize_story",++ "version": 3++ },++ "route": {++ "key": "default.text",++ "version": 1++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "attribution": null,++ "source_service": "news",++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "input": {++ "topic": "Transit strike",++ "sources": [++ {++ "source_type": "news.item",++ "source_id": "itm_42"++ }++ ]++ },++ "output": {++ "headline": "Transit strike begins",++ "summary": "Workers walked out.",++ "citations": [++ 0++ ],++ "gaps": []++ },++ "error": null,++ "synthetic": false,++ "provenance": {++ "origin": "ai",++ "workflow": "news.summarize_story",++ "workflow_version": 2,++ "template_version": 3,++ "route": "default.text",++ "route_version": 1,++ "provider": "anthropic",++ "model": "claude-sonnet",++ "fallback_used": true,++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "cached_from": null,++ "synthetic": false++ },++ "citations_count": 1,++ "retry_of": null,++ "idempotency_key": null,++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",++ "created_at": "2026-09-23T10:00:00.000Z",++ "started_at": "2026-09-23T10:00:00.050Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,74 @@++{++ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "done",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "template": {++ "key": "news.summarize_story",++ "version": 3++ },++ "route": {++ "key": "default.text",++ "version": 1++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "attribution": null,++ "source_service": "news",++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "input": {++ "topic": "Transit strike",++ "sources": [++ {++ "source_type": "news.item",++ "source_id": "itm_42"++ }++ ]++ },++ "output": {++ "headline": "Transit strike begins",++ "summary": "Workers walked out.",++ "citations": [++ 0++ ],++ "gaps": []++ },++ "error": null,++ "synthetic": false,++ "provenance": {++ "origin": "ai",++ "workflow": "news.summarize_story",++ "workflow_version": 2,++ "template_version": 3,++ "route": "default.text",++ "route_version": 1,++ "provider": "anthropic",++ "model": "claude-sonnet",++ "fallback_used": true,++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "cached_from": null,++ "synthetic": false++ },++ "usage": {++ "tokens_in": 1200,++ "tokens_out": 300,++ "cost_usd": 0.0081,++ "attempts": 2++ },++ "citations_count": 1,++ "retry_of": null,++ "idempotency_key": null,++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",++ "created_at": "2026-09-23T10:00:00.000Z",++ "started_at": "2026-09-23T10:00:00.050Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,58 @@++{++ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "status": "queued",++ "workflow": {++ "key": "news.summarize_story",++ "version": 2++ },++ "template": null,++ "route": {++ "key": "live.{stream}.vision",++ "version": null++ },++ "requester": {++ "type": "service",++ "id": "news"++ },++ "on_behalf_of": null,++ "attribution": null,++ "source_service": "news",++ "target": {++ "service": "news",++ "type": "story",++ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "input": {++ "not_retained": "passthrough prompt; see input_hash"++ },++ "output": null,++ "error": null,++ "synthetic": false,++ "provenance": {++ "origin": "ai",++ "workflow": "news.summarize_story",++ "workflow_version": 2,++ "template_version": 3,++ "route": "default.text",++ "route_version": 1,++ "provider": null,++ "model": null,++ "fallback_used": false,++ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "cached_from": null,++ "synthetic": false++ },++ "usage": {++ "tokens_in": 0,++ "tokens_out": 0,++ "cost_usd": 0,++ "attempts": 0++ },++ "citations_count": 0,++ "retry_of": null,++ "idempotency_key": null,++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",++ "created_at": "2026-09-23T10:00:00.000Z",++ "started_at": null,++ "finished_at": null++}nnew file mode 100644@@@ -0,0 +1,28 @@++{++ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "status": "succeeded",++ "workflow": { "key": "news.summarize_story", "version": 2 },++ "template": { "key": "news.summarize_story", "version": 3 },++ "route": { "key": "default.text", "version": 1 },++ "requester": { "type": "service", "id": "news" },++ "on_behalf_of": null,++ "attribution": null,++ "source_service": "news",++ "target": { "service": "news", "type": "story", "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ" },++ "input": { "topic": "Transit strike", "sources": [{ "source_type": "news.item", "source_id": "itm_42" }] },++ "output": { "headline": "Transit strike begins", "summary": "Workers walked out.", "citations": [0], "gaps": [] },++ "error": null,++ "synthetic": false,++ "provenance": {++ "origin": "ai", "workflow": "news.summarize_story", "workflow_version": 2, "template_version": 3, "route": "default.text", "route_version": 1,++ "provider": "anthropic", "model": "claude-sonnet", "fallback_used": true, "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ", "cached_from": null, "synthetic": false++ },++ "usage": { "tokens_in": 1200, "tokens_out": 300, "cost_usd": 0.0081, "attempts": 2 },++ "citations_count": 1,++ "retry_of": null,++ "idempotency_key": null,++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",++ "created_at": "2026-09-23T10:00:00.000Z",++ "started_at": "2026-09-23T10:00:00.050Z",++ "finished_at": "2026-09-23T10:00:04.200Z"++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "cashout": {++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "amount_bits": 5000,++ "value_cents": 5000,++ "status": "denied",++ "payout_method": {++ "type": "paypal",++ "address": "[email protected]"++ },++ "escrow_until": "2026-09-30T10:00:00.000Z",++ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",++ "settle_txn": null,++ "payout_provider": null,++ "payout_reference": null,++ "reason": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T10:00:00.000Z"++ }++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "cashout": {++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "amount_bits": 5000,++ "value_cents": 5000,++ "status": "denied",++ "payout_method": {++ "type": "paypal",++ "address": "[email protected]"++ },++ "escrow_until": "2026-09-30T10:00:00.000Z",++ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",++ "settle_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ8",++ "payout_provider": null,++ "payout_reference": null,++ "reason": "payout details do not match",++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T10:00:00.000Z"++ },++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ8"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "cashout": {++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "amount_bits": 50.5,++ "value_cents": 5000,++ "status": "paid",++ "payout_method": {++ "type": "paypal",++ "address": "[email protected]"++ },++ "escrow_until": "2026-09-30T10:00:00.000Z",++ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",++ "settle_txn": null,++ "payout_provider": null,++ "payout_reference": null,++ "reason": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T10:00:00.000Z"++ },++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ7"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "cashout": {++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "amount_bits": 5000,++ "value_cents": 5000,++ "status": "paid",++ "payout_method": {++ "type": "paypal",++ "address": "[email protected]"++ },++ "escrow_until": "2026-09-30T10:00:00.000Z",++ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",++ "settle_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ7",++ "payout_provider": "paypal",++ "payout_reference": "8XK12345AB678901C",++ "reason": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T10:00:00.000Z"++ },++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ7"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "cashout": {++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "amount_bits": 5000,++ "value_cents": 5000,++ "status": "paid",++ "payout_method": {++ "type": "paypal",++ "address": "[email protected]"++ },++ "escrow_until": "2026-09-30T10:00:00.000Z",++ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",++ "settle_txn": null,++ "payout_provider": null,++ "payout_reference": null,++ "reason": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T10:00:00.000Z"++ },++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "cashout": {++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "amount_bits": 5000,++ "value_cents": 5000,++ "status": "requested",++ "payout_method": {++ "type": "paypal",++ "address": "[email protected]"++ },++ "escrow_until": "2026-09-30T10:00:00.000Z",++ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",++ "settle_txn": null,++ "payout_provider": null,++ "payout_reference": null,++ "reason": null,++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T10:00:00.000Z"++ },++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6"++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "network_membership",++ "active": true,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "status": "active",++ "auto_renew": true,++ "cancel_at_period_end": false,++ "provider": "credit"++ },++ "reason": "granted",++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ4"++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": true,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "status": "active",++ "auto_renew": true,++ "cancel_at_period_end": false,++ "provider": "credit"++ },++ "reason": "granted",++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ4"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": false,++ "expires_at": null,++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "status": "expired",++ "auto_renew": false,++ "cancel_at_period_end": false,++ "provider": "credit"++ },++ "reason": "refund",++ "revoked_period": {++ "starts_at": "2026-09-23T10:00:00.000Z",++ "ends_at": "2026-10-23T10:00:00.000Z"++ }++}nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "classification": "SITE",++ "provider": "powerchat",++ "receipt_ref": "powerchat:ext-1",++ "provider_event_id": "ext-1",++ "delivery_id": "dlv-ext-1",++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "receiving_account": {++ "provider": "powerchat",++ "id": "981",++ "username": "japaneseoldguy"++ },++ "amount_cents": 1234,++ "currency": "usd-cents",++ "value_bits": 1234,++ "donor_name": "Generous Fan",++ "anonymous": false,++ "message": "love the stream",++ "app_ref": null,++ "app_purpose": "goal:12",++ "occurred_at": "2026-09-23T20:00:00Z",++ "test": false,++ "rates": {++ "bits_per_usd": 100++ }++}nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "classification": "EXTERNAL",++ "provider": "powerchat",++ "receipt_ref": "powerchat:ext-1",++ "provider_event_id": "ext-1",++ "delivery_id": "dlv-ext-1",++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "receiving_account": {++ "provider": "powerchat",++ "id": "981",++ "username": "japaneseoldguy"++ },++ "amount_cents": 0,++ "currency": "usd-cents",++ "value_bits": 1234,++ "donor_name": "Generous Fan",++ "anonymous": false,++ "message": "love the stream",++ "app_ref": null,++ "app_purpose": "goal:12",++ "occurred_at": "2026-09-23T20:00:00Z",++ "test": false,++ "rates": {++ "bits_per_usd": 100++ }++}nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "classification": "EXTERNAL",++ "provider": "powerchat",++ "receipt_ref": "powerchat:ext-1",++ "provider_event_id": "ext-1",++ "delivery_id": "dlv-ext-1",++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "receiving_account": {++ "provider": "powerchat",++ "id": "981",++ "username": "japaneseoldguy"++ },++ "amount_cents": 1234,++ "currency": "usd-cents",++ "value_bits": 1234,++ "donor_name": null,++ "anonymous": true,++ "message": null,++ "app_ref": null,++ "app_purpose": null,++ "occurred_at": "2026-09-23T20:00:00Z",++ "test": false,++ "rates": {++ "bits_per_usd": 100++ }++}nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "classification": "EXTERNAL",++ "provider": "powerchat",++ "receipt_ref": "powerchat:ext-1",++ "provider_event_id": "ext-1",++ "delivery_id": "dlv-ext-1",++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "receiving_account": {++ "provider": "powerchat",++ "id": "981",++ "username": "japaneseoldguy"++ },++ "amount_cents": 1234,++ "currency": "usd-cents",++ "value_bits": 1234,++ "donor_name": "Generous Fan",++ "anonymous": false,++ "message": "love the stream",++ "app_ref": null,++ "app_purpose": "goal:12",++ "occurred_at": "2026-09-23T20:00:00Z",++ "test": false,++ "rates": {++ "bits_per_usd": 100++ }++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "audit_id": "sa_01JAB3C4D5E6F7G8H9J0K1MNQ9",++ "action": "cashout.approve",++ "outcome": "refused",++ "target": {++ "type": "cashout",++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5"++ },++ "reason": "verified payout email",++ "request_id": "req_abcdefgh",++ "detail": {++ "payout_reference": "8XK12345AB678901C"++ }++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "audit_id": "sa_01JAB3C4D5E6F7G8H9J0K1MNQ9",++ "action": "cashout.approve",++ "outcome": "done",++ "target": {++ "type": "cashout",++ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5"++ },++ "reason": "verified payout email",++ "request_id": "req_abcdefgh",++ "detail": {++ "payout_reference": "8XK12345AB678901C"++ }++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "subscriber": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "tier": 1,++ "provider": "stripe",++ "provider_ref": "sub_1Q2w3E4r5T6y",++ "route": "site",++ "status": "active",++ "auto_renew": false,++ "cancel_at_period_end": true,++ "price_cents": 499,++ "current_period_end": "2026-10-23T10:00:00.000Z",++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T11:00:00.000Z"++ },++ "provider_sync": "not_needed"++}nnew file mode 100644@@@ -0,0 +1,33 @@++{++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "subscriber": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "tier": 1,++ "provider": "stripe",++ "provider_ref": "sub_1Q2w3E4r5T6y",++ "route": "site",++ "status": "active",++ "auto_renew": false,++ "cancel_at_period_end": true,++ "price_cents": 499,++ "current_period_end": "2026-10-23T10:00:00.000Z",++ "created_at": "2026-09-23T10:00:00.000Z",++ "updated_at": "2026-09-23T11:00:00.000Z"++ },++ "provider_sync": "stripe_cancel_at_period_end",++ "actor": {++ "principal": "svc:vip",++ "request_id": "req_abcdefgh",++ "on_behalf_of": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",++ "type": "refund",++ "test": false,++ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "provider": null,++ "metadata": {++ "amount_bits": 500,++ "target": {++ "service": "tips",++ "type": "interaction",++ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"++ },++ "message": "gg"++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ3",++ "type": "chargeback",++ "test": false,++ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "provider": "stripe",++ "metadata": {++ "cents": 500,++ "original_paid_cents": 500,++ "reason": "charge.dispute.created",++ "review": "required"++ },++ "reverses_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",++ "review": "required",++ "entitlements_revoked": 0++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ3",++ "type": "refund",++ "test": false,++ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "provider": null,++ "metadata": {++ "amount_bits": 500,++ "target": {++ "service": "tips",++ "type": "interaction",++ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"++ },++ "message": "gg"++ },++ "reverses_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1"++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",++ "type": "donation",++ "test": false,++ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "to_subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "provider": null,++ "metadata": {++ "amount_bits": 500,++ "target": {++ "service": "tips",++ "type": "interaction",++ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"++ },++ "message": "gg"++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",++ "type": "tip",++ "test": false,++ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "provider": null,++ "metadata": {++ "amount_bits": 500,++ "target": {++ "service": "tips",++ "type": "interaction",++ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"++ },++ "message": "gg"++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",++ "type": "donation",++ "test": false,++ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "provider": null,++ "metadata": {++ "amount_bits": 500,++ "target": {++ "service": "tips",++ "type": "interaction",++ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"++ },++ "message": "gg"++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",++ "type": "purchase",++ "test": false,++ "from_subject": null,++ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "provider": "stripe",++ "metadata": {++ "paid_cents": 500,++ "bits": 500,++ "value_cents": 500,++ "revenue_cents": 0,++ "pricing": "bits",++ "intent_id": "pi_01JAB3C4D5E6F7G8H9J0K1MNQ2",++ "rates": {++ "bits_per_usd": 100++ }++ }++}nnew file mode 100644@@@ -0,0 +1 @@++{ "type": "page", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 3 }nnew file mode 100644@@@ -0,0 +1 @@++{ "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 3 }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 2, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.blog/@alex/hello-world", "title": "Hello world", "publication_state": "published" }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 2, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.blog/@alex/hello-world", "title": "Hello world", "summary": "First post.", "body": "Hello world, this is the first post.", "facets": { "blog": "alex", "tags": ["intro"], "categories": [] }, "language": "en", "authorship": "human", "provenance": [], "publication_state": "published", "published_at": "2026-09-22T12:00:00.000Z", "updated_at": "2026-09-22T12:00:00.000Z", "indexability": { "decision": "index", "reasons": [] } }nnew file mode 100644@@@ -0,0 +1 @@++{ "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "state": "published", "visibility": "public", "authorship": "human" }nnew file mode 100644@@@ -0,0 +1 @@++{ "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "state": "draft", "visibility": "public", "authorship": "ai", "workflow": { "id": "blog.draft_post", "runId": "run_01JAB2C3D4E5F6G7H8J9K0MNPY", "version": 2 } }nnew file mode 100644@@@ -0,0 +1 @@++{ "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "state": "draft", "visibility": "public", "authorship": "human" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "unpublished", "indexability": null, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "members" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "deleted", "indexability": null, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "members" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "unpublished", "indexability": null, "blog": "alex", "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "unpublished", "indexability": { "decision": "index", "reasons": [] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "maybe", "reasons": [] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "noindex", "reasons": ["thin_content"] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPZ", "action": "publish", "revision": 2, "run_at": 1790154000000, "attempts": 5, "error": "boom", "blog_id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT" }nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPZ", "action": "publish", "revision": 2, "run_at": "2026-09-23T09:00:00.000Z", "attempts": 5, "error": "post.not_found: No such post", "blog_id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT" }nnew file mode 100644@@@ -0,0 +1 @@++{ "message_ids": [1201], "text": "hello", "redacts": { "subject_type": "chat_message", "subject_ids": ["1201"] } }nnew file mode 100644@@@ -0,0 +1 @@++{ "message_ids": [] , "redacts": { "subject_type": "chat_message", "subject_ids": ["1"] } }nnew file mode 100644@@@ -0,0 +1 @@++{ "message_ids": [1201, 1202], "redacts": { "subject_type": "chat_message", "subject_ids": ["1201", "1202"] } }nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "recipient": "42",++ "kind": "keyword",++ "query": "keyboard",++ "product_id": null,++ "max_price": null,++ "currency": null,++ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "offer_url": "https://openvibe.deals/d/keychron-q1",++ "title": "Keychron Q1",++ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": "89", "currency": "USD", "availability": "in_stock" }++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "recipient": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "kind": "search",++ "query": "keyboard",++ "product_id": null,++ "max_price": null,++ "currency": null,++ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "offer_url": "https://openvibe.deals/d/keychron-q1",++ "title": "Keychron Q1",++ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": null, "currency": null, "availability": null }++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "recipient": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "kind": "keyword",++ "query": "mechanical keyboard",++ "product_id": null,++ "max_price": null,++ "currency": null,++ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "offer_url": "https://openvibe.deals/d/keychron-q1-mechanical-keyboard",++ "title": "Keychron Q1 mechanical keyboard",++ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": null, "currency": null, "availability": null }++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "recipient": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "kind": "price_below",++ "query": null,++ "product_id": "dpr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "max_price": "199.99",++ "currency": "USD",++ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "offer_url": "https://openvibe.deals/d/steam-deck-oled-512gb",++ "title": "Steam Deck OLED 512 GB",++ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": "179.00", "currency": "USD", "availability": "in_stock" }++}nnew file mode 100644@@@ -0,0 +1 @@++{ "event_ids": [], "subject_type": "post", "subject_ids": [] }nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1 @@++{ "subject_type": "chat_message" }nnew file mode 100644@@@ -0,0 +1 @@++{ "event_ids": ["evt_01JAB2C3D4E5F6G7H8J9K0MNPQ"], "reason": "spam" }nnew file mode 100644@@@ -0,0 +1 @@++{ "event_ids": ["evt_01JAB2C3D4E5F6G7H8J9K0MNPQ"] }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject_type": "chat_message", "subject_ids": ["1201", "1202"] }nnew file mode 100644@@@ -0,0 +1 @@++{ "redacted": true, "redacted_at": "2026-09-23T10:00:00.000Z", "redacted_by": "evt_01JAB2C3D4E5F6G7H8J9K0MNPR", "message": "hi" }nnew file mode 100644@@@ -0,0 +1 @@++{ "redacted": true, "redacted_at": "2026-09-23T10:00:00.000Z", "redacted_by": "evt_01JAB2C3D4E5F6G7H8J9K0MNPR" }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 7, "exposure": "none", "reindexed": true, "canonical_url": null }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 7, "exposure": "public_listed", "reindexed": true, "canonical_url": "https://openvibe.wiki/wiki/Main_Page" }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1, "exposure": "restricted", "reindexed": false, "canonical_url": null }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 12, "reason": "spam", "previous_exposure": "public_listed", "exposure": "none", "canonical_url": null }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 12, "reason": "not_published", "previous_exposure": "public_listed", "exposure": "none", "canonical_url": "https://openvibe.blog/alex/hello-world" }nnew file mode 100644@@@ -0,0 +1 @@++{ "source_key": "bbc-world", "category": "news", "run_id": "frn_01JAB2C3D4E5F6G7H8J9K0MNPQ", "endpoint_url": "https://feeds.bbci.co.uk/news/world/rss.xml", "state": "not_modified", "error_code": null, "http_status": 304, "consecutive_failures": 0 }nnew file mode 100644@@@ -0,0 +1 @@++{ "source_key": "bbc-world", "category": "news", "run_id": "frn_01JAB2C3D4E5F6G7H8J9K0MNPQ", "endpoint_url": "https://feeds.bbci.co.uk/news/world/rss.xml", "state": "http_error", "error_code": "http_503", "http_status": 503, "consecutive_failures": 2 }nnew file mode 100644@@@ -0,0 +1 @@++{ "source_key": "shop-sitemap", "category": "deals", "run_id": "frn_01JAB2C3D4E5F6G7H8J9K0MNPR", "endpoint_url": "https://shop.example.com/sitemap.xml", "state": "robots_denied", "error_code": "robots", "http_status": null, "consecutive_failures": 1 }nnew file mode 100644@@@ -0,0 +1 @@++{ "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ" }nnew file mode 100644@@@ -0,0 +1 @@++{ "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 3 }nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "owner": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1,++ "visibility": "members", "acl": { "groups": ["role:admin", "role:global_mod"] },++ "title": "Summit ends without agreement", "publication_state": "published"++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "owner": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1,++ "visibility": "members", "acl": { "groups": ["role:admin", "role:global_mod"] },++ "title": "Summit ends without agreement", "summary": "", "body": "",++ "facets": { "category": "news", "source": "bbc-world", "kind": "article" },++ "authorship": "imported",++ "provenance": [{ "service": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1, "url": "https://www.bbc.co.uk/news/world-12345678", "retrieved_at": "2026-09-23T08:00:00.000Z" }],++ "publication_state": "published", "published_at": "2026-09-23T07:45:00Z", "updated_at": null,++ "indexability": { "decision": "noindex", "reasons": ["third_party_content"] },++ "canonical_url": "https://www.bbc.co.uk/news/world-12345678"++}nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends", "summary": "Leaders left…", "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "sports", "kind": "article", "canonical_url": null, "title": null, "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends without agreement", "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPR", "source_key": "shop-sitemap", "category": "deals", "kind": "url", "canonical_url": null, "title": null, "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": null, "title": null, "revision": 3, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z", "reason": "" }nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends without agreement", "revision": 3, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z", "reason": "takedown request from the publisher" }nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": null, "title": null, "revision": 2, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends without agreement on climate", "revision": 2, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z", "previous_content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" }nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "title": "New microphone",++ "target_amount": 10000,++ "current_amount": 2500,++ "currency": "vibes-bits",++ "status": "active",++ "reached": false,++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "title": "New microphone",++ "target_amount": 10000,++ "current_amount": 2500,++ "currency": "vibes-bits",++ "status": "active",++ "reached": false,++ "reason": "contribution",++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "title": "New microphone",++ "target_amount": 10000,++ "current_amount": 0,++ "currency": "vibes-bits",++ "status": "active",++ "reached": false,++ "reason": "created",++ "interaction_id": null++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "settled",++ "delivery_state": "cancelled",++ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",++ "test": false,++ "reason": "creator_cancelled"++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "failed",++ "delivery_state": "cancelled",++ "billing_txn_id": null,++ "test": false,++ "reason": "payment_failed",++ "code": "billing.insufficient_funds"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "reversed",++ "delivery_state": "cancelled",++ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",++ "test": false,++ "reason": "payment_reversed"++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "settled",++ "delivery_state": "failed",++ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",++ "test": false++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "settled",++ "delivery_state": "failed",++ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",++ "test": false,++ "failed_effects": [++ {++ "effect": "chat_line",++ "adapter": "live",++ "last_error": "live 503"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "42"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "settled",++ "delivery_state": "queued",++ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",++ "test": false++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": "5.00",++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "settled",++ "delivery_state": "queued",++ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",++ "test": false++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": null,++ "supporter_name": "Anonymous",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "external",++ "payment_state": "settled",++ "delivery_state": "queued",++ "billing_txn_id": null,++ "test": false++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "supporter": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "supporter_name": "Generous Fan",++ "kind": "tip",++ "amount": 500,++ "currency": "vibes-bits",++ "settlement": "billing",++ "payment_state": "settled",++ "delivery_state": "queued",++ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",++ "test": false++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "sound",++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "goal_id": null++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "alert",++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "goal_id": null++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "alert",++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "goal_id": null++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "goal",++ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",++ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",++ "reason": "no overlay showed it within the delivery window"++}nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": { "type": "service", "id": "community" }, "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null, "input": { "format": "webp", "width": 640 } }nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": "session:3f2a9c", "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null }nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": null, "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:05:00.000Z", "retry_of": "job_01JAB2C3D4E5F6G7H8J9K0MNPZ" }nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" }, "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null }nnew file mode 100644@@@ -0,0 +1,3 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "failed", "owner": null, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,++ "started_at": null, "finished_at": "2026-09-23T09:00:02.000Z", "expires_at": "2026-09-23T10:00:02.000Z",++ "error": { "status": 500, "code": "ENOENT", "detail": "no such file" }, "retryable": false }nnew file mode 100644@@@ -0,0 +1,3 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "failed", "owner": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" }, "attempts": 3, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,++ "started_at": "2026-09-23T09:00:01.000Z", "finished_at": "2026-09-23T09:10:01.000Z", "expires_at": "2026-09-23T10:10:01.000Z",++ "error": { "status": 504, "code": "tools.job.timeout", "detail": "The job ran longer than 600 s" }, "retryable": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": null, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null, "started_at": "2026-09-23T09:00:01.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "running", "owner": { "type": "service", "id": "community" }, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null, "started_at": "2026-09-23T09:00:01.000Z" }nnew file mode 100644@@@ -0,0 +1,3 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "succeeded", "owner": null, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,++ "started_at": "2026-09-23T09:00:01.000Z", "finished_at": "2026-09-23T09:00:04.000Z", "expires_at": null,++ "result": { "files": [], "data": { "text": "transcribed words" } } }nnew file mode 100644@@@ -0,0 +1,6 @@++{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "succeeded", "owner": { "type": "app", "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPR" }, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,++ "started_at": "2026-09-23T09:00:01.000Z", "finished_at": "2026-09-23T09:00:04.000Z", "expires_at": "2026-09-23T10:00:04.000Z",++ "result": { "files": [++ { "index": 0, "mime": "image/webp", "size": 48213, "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "storage": "media", "media": { "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS", "role": "output" } },++ { "index": 1, "mime": "image/png", "size": 90112, "sha256": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "storage": "local", "media": null }++ ] } }nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "rule": {++ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "threshold", "status": "active",++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc." },++ "metric": "price.close", "operator": "above", "threshold": "250", "unit": "USD", "currency": "USD",++ "form_types": null, "armed": true, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-22T21:00:00.000Z"++ },++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },++ "trigger": { "kind": "observation", "id": "obs_01JAB2C3D4E5F6G7H8J9K0MNPT", "metric": "price.close", "unit": "USD", "currency": "USD", "observed_at": "2026-09-22T20:00:00.000Z", "retrieved_at": "2026-09-22T20:05:00.000Z", "source_key": "stooq-daily", "source_url": null },++ "disclaimer": "Information only — not investment advice; no trading here."++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "rule": {++ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "new_document", "status": "active",++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "metric": null, "operator": null, "threshold": null, "unit": null, "currency": null,++ "form_types": null, "armed": null, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-20T10:00:00.000Z"++ },++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },++ "trigger": { "kind": "document", "id": "doc_01JAB2C3D4E5F6G7H8J9K0MNPT", "form_type": null, "title": null, "url": null, "published_at": null, "retrieved_at": "2026-09-22T12:10:00.000Z", "source_key": "sec-edgar-filings" },++ "message": "AAPL filed a new document",++ "disclaimer": "Information only — not investment advice; no trading here."++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "rule": {++ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "filing_type", "status": "active",++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc." },++ "metric": null, "operator": null, "threshold": null, "unit": null, "currency": null,++ "form_types": ["10-K", "8-K"], "armed": null, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-20T10:00:00.000Z"++ },++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },++ "trigger": {++ "kind": "document", "id": "doc_01JAB2C3D4E5F6G7H8J9K0MNPT", "form_type": "8-K", "title": "Current report",++ "url": "https://www.sec.gov/Archives/edgar/data/320193/000032019326000001/0000320193-26-000001-index.htm",++ "published_at": "2026-09-22T12:00:00.000Z", "retrieved_at": "2026-09-22T12:10:00.000Z", "source_key": "sec-edgar-filings"++ },++ "disclaimer": "Information only — not investment advice; no trading here."++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "rule": {++ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "threshold", "status": "active",++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc." },++ "metric": "price.close", "operator": "above", "threshold": "250", "unit": "USD", "currency": "USD",++ "form_types": null, "armed": true, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-22T21:00:00.000Z"++ },++ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },++ "trigger": {++ "kind": "observation", "id": "obs_01JAB2C3D4E5F6G7H8J9K0MNPT", "metric": "price.close", "value": "251.37", "unit": "USD", "currency": "USD",++ "observed_at": "2026-09-22T20:00:00.000Z", "retrieved_at": "2026-09-22T20:05:00.000Z", "source_key": "stooq-daily", "source_url": null++ },++ "disclaimer": "Information only — not investment advice; no trading here."++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "member": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": true,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "cancel_at_period_end": false,++ "previous": null,++ "reason": "granted",++ "source": "webhook",++ "billing_event_id": "evt_01JAB3C4D5E6F7G8H9J0K1MNPY",++ "membership_id": "vms_01JAB3C4D5E6F7G8H9J0K1MNPZ",++ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",++ "plan_version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",++ "plan_version": 2,++ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0"++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "member": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": true,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "cancel_at_period_end": true,++ "previous": {++ "active": true,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "cancel_at_period_end": false++ },++ "reason": "cancel_scheduled",++ "source": "event",++ "billing_event_id": null,++ "membership_id": "vms_01JAB3C4D5E6F7G8H9J0K1MNPZ",++ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",++ "plan_version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",++ "plan_version": 2,++ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "member": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "creator": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": true,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "cancel_at_period_end": false,++ "previous": null,++ "reason": "granted",++ "source": "event",++ "billing_event_id": "evt_01JAB3C4D5E6F7G8H9J0K1MNPY",++ "membership_id": "vms_01JAB3C4D5E6F7G8H9J0K1MNPZ",++ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",++ "plan_version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",++ "plan_version": 2,++ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0"++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",++ "slug": "supporters",++ "version": 2,++ "version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",++ "creator": {++ "id": "vcr_01JAB3C4D5E6F7G8H9J0K1MNPX",++ "kind": "creator",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "username": "japaneseoldguy",++ "display_name": "JapaneseOldGuy",++ "bio": null,++ "status": "active"++ },++ "billing_kind": "channel_subscription",++ "terms": {++ "name": "Supporters",++ "description": "Back the channel.",++ "benefits": [++ "Sub badge",++ "Emote-only chat"++ ],++ "perks": [++ {++ "key": "badge",++ "name": "Sub badge",++ "kind": "badge",++ "scope": "creator"++ }++ ],++ "billing_kind": "channel_subscription",++ "price": "set and charged by OpenVibe.Billing",++ "digest": "3f9a0c1d2e4b5a67",++ "price_cents": 499++ },++ "published_at": "2026-09-23T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,39 @@++{++ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",++ "slug": "supporters",++ "version": 2,++ "version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",++ "creator": {++ "id": "vcr_01JAB3C4D5E6F7G8H9J0K1MNPX",++ "kind": "creator",++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "username": "japaneseoldguy",++ "display_name": "JapaneseOldGuy",++ "bio": null,++ "status": "active"++ },++ "billing_kind": "channel_subscription",++ "terms": {++ "name": "Supporters",++ "description": "Back the channel.",++ "benefits": [++ "Sub badge",++ "Emote-only chat"++ ],++ "perks": [++ {++ "key": "badge",++ "name": "Sub badge",++ "kind": "badge",++ "scope": "creator"++ }++ ],++ "billing_kind": "channel_subscription",++ "price": "set and charged by OpenVibe.Billing",++ "digest": "3f9a0c1d2e4b5a67"++ },++ "published_at": "2026-09-23T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",++ "slug": "supporters",++ "version": 2,++ "version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",++ "creator": {++ "id": "network",++ "kind": "network",++ "subject": null,++ "username": null,++ "display_name": "OpenVibe",++ "bio": null,++ "status": "active"++ },++ "billing_kind": null,++ "terms": {++ "name": "Supporters",++ "description": "Back the channel.",++ "benefits": null,++ "perks": [],++ "billing_kind": null,++ "price": null,++ "digest": "3f9a0c1d2e4b5a67"++ },++ "published_at": "2026-09-23T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 4, "deleted": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 4 }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "blog", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 3, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.wiki/w/food/ramen", "title": "Ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] } }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 4, "deleted": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 3, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.wiki/w/food/ramen", "title": "Ramen", "summary": "Noodle soup.", "body": "Ramen is a noodle soup.", "facets": { "space": "food", "space_kind": "user" }, "authorship": "human", "provenance": [{ "service": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPX", "retrieved_at": "2026-09-22T12:00:00.000Z" }], "publication_state": "published", "published_at": "2026-09-22T12:00:00.000Z", "updated_at": "2026-09-23T08:00:00.000Z", "indexability": { "decision": "index", "reasons": [] } }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "deleted", "indexability": null, "space": "food", "slug": "ramen", "visibility": "unlisted" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "deleted", "indexability": null, "space": "food", "slug": "ramen", "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.wiki/w/crew/roster", "publication_state": "published", "indexability": { "decision": "noindex", "reasons": ["members_only"] }, "space": "crew", "slug": "roster", "visibility": "members" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.wiki/w/food/ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "published", "indexability": null, "space": "food", "slug": "ramen", "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": null, "publication_state": "unpublished", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.wiki/w/food/ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public", "body": "Ramen is…" }nnew file mode 100644@@@ -0,0 +1 @@++{ "canonical_url": "https://openvibe.wiki/w/food/ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }nnew file mode 100644@@@ -0,0 +1 @@++{ "space_id": "spc_01JAB2C3D4E5F6G7H8J9K0MNPV", "space": "food", "slug": "ramen", "number": 2, "kind": "edit", "parent_number": 1, "reverted_to": null, "authorship": "human", "author": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "message": null, "content": "# Ramen" }nnew file mode 100644@@@ -0,0 +1 @@++{ "space_id": "spc_01JAB2C3D4E5F6G7H8J9K0MNPV", "space": "food", "slug": "ramen", "number": 3, "kind": "edit", "parent_number": 2, "reverted_to": null, "authorship": "ai_generated", "author": "svc:ai", "message": "AI proposal", "proposal_id": "prp_01JAB2C3D4E5F6G7H8J9K0MNPW" }nnew file mode 100644@@@ -0,0 +1 @@++{ "space_id": "spc_01JAB2C3D4E5F6G7H8J9K0MNPV", "space": "food", "slug": "ramen", "number": 2, "kind": "edit", "parent_number": 1, "reverted_to": null, "authorship": "human", "author": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "message": "Add broth section" }nnew file mode 100644@@@ -0,0 +1 @@++{ "slug": "food", "name": "Food", "kind": "user", "visibility": "public", "deleted": false, "change": "archived", "url": "https://openvibe.wiki/w/food" }nnew file mode 100644@@@ -0,0 +1 @@++{ "slug": "food", "name": "Food", "kind": "user", "visibility": "public", "deleted": false, "change": "created", "url": "https://openvibe.wiki/w/food" }nnew file mode 100644@@@ -0,0 +1 @@++{ "action": "revised", "space": "food", "slug": "ramen", "title": "Ramen", "revision": 3, "url": "https://openvibe.wiki/w/food/ramen", "recipients": [] }nnew file mode 100644@@@ -0,0 +1 @@++{ "action": "published", "space": "food", "slug": "ramen", "title": "Ramen", "revision": 2, "url": "https://openvibe.wiki/w/food/ramen", "recipients": ["usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"] }@@@ -1967,6 +1967,5630 @@}}}++ },++ "events.redaction-directive": {++ "version": "1.0.0",++ "owner": "events",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/redaction-directive.v1.json",++ "title": "RedactionDirective",++ "description": "payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.",++ "type": "object",++ "minProperties": 1,++ "additionalProperties": false,++ "dependentRequired": {++ "subject_type": [++ "subject_ids"++ ],++ "subject_ids": [++ "subject_type"++ ]++ },++ "properties": {++ "event_ids": {++ "type": "array",++ "minItems": 1,++ "maxItems": 1000,++ "items": {++ "type": "string",++ "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "description": "Events to redact by id. Duplicates are ignored."++ },++ "subject_type": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9_]{0,63}$",++ "description": "Envelope subject.type of the events to redact (chat_message, post…). Goes with subject_ids."++ },++ "subject_ids": {++ "type": "array",++ "minItems": 1,++ "maxItems": 1000,++ "items": {++ "type": "string",++ "minLength": 1,++ "maxLength": 200++ },++ "description": "Envelope subject.id values; every not-yet-redacted event of the same source about each one is redacted."++ }++ }++ }++ },++ "events.tombstone-payload": {++ "version": "1.0.0",++ "owner": "events",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/tombstone-payload.v1.json",++ "title": "TombstonePayload",++ "description": "The payload of a redacted event (ADR-026). OpenVibe.Events replaces the original payload with this and the actor with the producer itself ({ type: service, id: <source> }, or the app); event_id, seq, event_type, version, subject, timestamp and visibility stay. Every read path serves it from then on: pull, GET by id, SSE replay, queued deliveries and DLQ replays. A consumer checks payload.redacted before validating a payload against its event type's schema.",++ "type": "object",++ "required": [++ "redacted",++ "redacted_at",++ "redacted_by"++ ],++ "additionalProperties": false,++ "properties": {++ "redacted": {++ "const": true++ },++ "redacted_at": {++ "type": "string",++ "format": "date-time"++ },++ "redacted_by": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64,++ "description": "The event_id of the redacting event; an operator label (e.g. backfill) when OpenVibe.Events redacted by script."++ }++ }++ }++ },++ "chat.message.deleted": {++ "version": "1.0.0",++ "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/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": [++ "type",++ "id",++ "revision"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "const": "item"++ },++ "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."++ }++ }++ }++ },++ "wiki.revision.created": {++ "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.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": [++ "space_id",++ "space",++ "slug",++ "number",++ "kind",++ "parent_number",++ "reverted_to",++ "authorship",++ "author",++ "message"++ ],++ "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": {++ "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++ },++ "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",++ "null"++ ],++ "minimum": 1++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "maxLength": 2048++ },++ "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": "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)."++ }++ }++ }++ },++ "wiki.page.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.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": [++ "canonical_url",++ "publication_state",++ "indexability",++ "space",++ "slug",++ "visibility"++ ],++ "additionalProperties": false,++ "properties": {++ "canonical_url": {++ "type": "string",++ "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)."++ }++ }++ }++ },++ "wiki.page.unpublished": {++ "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.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": [++ "canonical_url",++ "publication_state",++ "indexability",++ "space",++ "slug",++ "visibility"++ ],++ "additionalProperties": false,++ "properties": {++ "canonical_url": {++ "type": [++ "string",++ "null"++ ],++ "description": "null: the event carries a tombstone, which has no URL."++ },++ "publication_state": {++ "const": "unpublished"++ },++ "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": "^[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.page.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.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": [++ "canonical_url",++ "publication_state",++ "indexability",++ "space",++ "slug",++ "visibility"++ ],++ "additionalProperties": false,++ "properties": {++ "canonical_url": {++ "type": [++ "string",++ "null"++ ],++ "description": "null: the event carries a tombstone, which has no URL."++ },++ "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": "^[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"++ },++ {++ "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++ }++ }++ }++ ]++ }++ },++ "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."++ }++ }++ }++ },++ "blog.post.created": {++ "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.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": [++ "blog",++ "state",++ "visibility",++ "authorship"++ ],++ "additionalProperties": false,++ "properties": {++ "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}$"++ }++ }++ },++ "state": {++ "const": "draft"++ },++ "visibility": {++ "enum": [++ "public",++ "unlisted",++ "members",++ "private"++ ]++ },++ "authorship": {++ "enum": [++ "human",++ "ai",++ "hybrid",++ "imported"++ ],++ "description": "openvibe-publishing authorship mode (not Search's vocabulary)."++ },++ "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"++ }++ }++ }++ }++ }++ },++ "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"++ },++ "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}$"++ }++ }++ }++ },++ "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": "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"++ },++ "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}$"++ }++ }++ }++ },++ "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": "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": {++ "type": [++ "string",++ "null"++ ],++ "description": "null: the event carries a tombstone, which has no URL."++ },++ "publication_state": {++ "const": "unpublished"++ },++ "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."++ },++ "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": "The post's own visibility (members = entitlement-gated)."++ }++ }++ }++ },++ "blog.post.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.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",++ "blog",++ "visibility"++ ],++ "additionalProperties": false,++ "properties": {++ "canonical_url": {++ "type": [++ "string",++ "null"++ ],++ "description": "null: the event carries a tombstone, which has no URL."++ },++ "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."++ },++ "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": "The post's own visibility (members = entitlement-gated)."++ }++ }++ }++ },++ "blog.schedule.failed": {++ "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.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": [++ "job_id",++ "action",++ "revision",++ "run_at",++ "attempts",++ "error",++ "blog_id"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "action": {++ "enum": [++ "publish",++ "unpublish"++ ]++ },++ "revision": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 1,++ "description": "The revision a publish was to make live; null for an unpublish."++ },++ "run_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the job was last due."++ },++ "attempts": {++ "type": "integer",++ "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."++ }++ }++ }++ },++ "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"++ },++ {++ "type": "object",++ "required": [++ "owner",++ "type",++ "deleted"++ ],++ "properties": {++ "owner": {++ "const": "blog"++ },++ "type": {++ "const": "post"++ },++ "id": {++ "type": "string",++ "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "deleted": {++ "const": false++ }++ }++ }++ ]++ }++ },++ "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}$"++ },++ "revision": {++ "type": "integer",++ "minimum": 1,++ "maximum": 9007199254740991,++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."++ }++ }++ }++ },++ "ai.run-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/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": [++ "workflow"++ ],++ "additionalProperties": false,++ "properties": {++ "workflow": {++ "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…)."++ },++ "version": {++ "type": "integer",++ "minimum": 1,++ "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."++ }++ }++ }++ }++ }++ },++ "ai.run": {++ "version": "1.0.0",++ "owner": "ai",++ "status": "active",++ "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.",++ "type": "object",++ "required": [++ "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": {++ "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": [++ "key",++ "version"++ ],++ "additionalProperties": false,++ "properties": {++ "key": {++ "type": "string"++ },++ "version": {++ "type": "integer",++ "minimum": 1++ }++ }++ },++ "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"++ },++ "on_behalf_of": {++ "anyOf": [++ {++ "$ref": "../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "attribution": {++ "anyOf": [++ {++ "$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": {++ "anyOf": [++ {++ "$ref": "../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "input": {++ "type": "object",++ "description": "The retained input (see the description)."++ },++ "output": {++ "type": [++ "object",++ "null"++ ],++ "description": "Workflow output, or null."++ },++ "error": {++ "type": [++ "object",++ "null"++ ],++ "required": [++ "code",++ "detail"++ ],++ "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…"++ },++ "detail": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ },++ "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"++ },++ "workflow": {++ "type": "string"++ },++ "workflow_version": {++ "type": "integer",++ "minimum": 1++ },++ "template_version": {++ "type": [++ "integer",++ "null"++ ]++ },++ "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"++ }++ }++ },++ "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)."++ }++ }++ },++ "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": {++ "version": "1.0.0",++ "owner": "ai",++ "status": "planned",++ "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": {++ "type": "object",++ "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": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "attribution": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "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++ },++ "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": "null"++ }++ }++ }++ },++ "ai.run.succeeded": {++ "version": "1.0.0",++ "owner": "ai",++ "status": "planned",++ "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.",++ "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": "succeeded"++ },++ "workflow": {++ "type": "object",++ "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": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "attribution": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "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++ },++ "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.run.failed": {++ "version": "1.0.0",++ "owner": "ai",++ "status": "planned",++ "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": {++ "type": "object",++ "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": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "attribution": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "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++ },++ "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.cached": {++ "version": "1.0.0",++ "owner": "ai",++ "status": "planned",++ "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.",++ "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": "cached"++ },++ "workflow": {++ "type": "object",++ "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": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "attribution": {++ "anyOf": [++ {++ "$ref": "../../common/entity-ref.v1.json"++ },++ {++ "type": "null"++ }++ ]++ },++ "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": "string",++ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "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++ }++ }++ },++ "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"++ }++ }++ }++ },++ "tips.interaction.ready": {++ "version": "1.0.0",++ "owner": "tips",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.ready.v1.json",++ "title": "TipsInteractionReadyPayload",++ "description": "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.",++ "type": "object",++ "required": [++ "interaction_id",++ "creator",++ "supporter",++ "supporter_name",++ "kind",++ "amount",++ "currency",++ "settlement",++ "payment_state",++ "delivery_state",++ "billing_txn_id",++ "test"++ ],++ "additionalProperties": false,++ "properties": {++ "interaction_id": {++ "type": "string",++ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "creator": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "supporter": {++ "anyOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "The supporter, null when external, anonymous or unmapped."++ },++ "supporter_name": {++ "type": [++ "string",++ "null"++ ],++ "description": "Display name at the time."++ },++ "kind": {++ "enum": [++ "tip",++ "paid_message",++ "tts",++ "media_request"++ ]++ },++ "amount": {++ "type": "integer",++ "minimum": 1,++ "description": "Integer amount in currency minor units (Vibes bits)."++ },++ "currency": {++ "type": "string",++ "description": "vibes-bits today."++ },++ "settlement": {++ "enum": [++ "billing",++ "external",++ "simulated",++ "imported"++ ]++ },++ "payment_state": {++ "enum": [++ "pending",++ "settled",++ "reversed",++ "failed"++ ]++ },++ "delivery_state": {++ "enum": [++ "awaiting_payment",++ "queued",++ "delivered",++ "failed",++ "cancelled"++ ]++ },++ "billing_txn_id": {++ "type": [++ "string",++ "null"++ ],++ "description": "The Billing transaction (txn_…) that settled it."++ },++ "test": {++ "type": "boolean",++ "description": "Simulations never emit events, so false in practice."++ }++ }++ }++ },++ "tips.interaction.failed": {++ "version": "1.0.0",++ "owner": "tips",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.failed.v1.json",++ "title": "TipsInteractionFailedPayload",++ "description": "tips.interaction.failed v1 (OpenVibe.Tips server/domain/interactions.js recomputeDelivery). A delivery effect gave up after its retries and none is still queued; the payment state is untouched. Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",++ "type": "object",++ "required": [++ "interaction_id",++ "creator",++ "supporter",++ "supporter_name",++ "kind",++ "amount",++ "currency",++ "settlement",++ "payment_state",++ "delivery_state",++ "billing_txn_id",++ "test",++ "failed_effects"++ ],++ "additionalProperties": false,++ "properties": {++ "interaction_id": {++ "type": "string",++ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "creator": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "supporter": {++ "anyOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "The supporter, null when external, anonymous or unmapped."++ },++ "supporter_name": {++ "type": [++ "string",++ "null"++ ],++ "description": "Display name at the time."++ },++ "kind": {++ "enum": [++ "tip",++ "paid_message",++ "tts",++ "media_request"++ ]++ },++ "amount": {++ "type": "integer",++ "minimum": 1,++ "description": "Integer amount in currency minor units (Vibes bits)."++ },++ "currency": {++ "type": "string",++ "description": "vibes-bits today."++ },++ "settlement": {++ "enum": [++ "billing",++ "external",++ "simulated",++ "imported"++ ]++ },++ "payment_state": {++ "enum": [++ "pending",++ "settled",++ "reversed",++ "failed"++ ]++ },++ "delivery_state": {++ "enum": [++ "awaiting_payment",++ "queued",++ "delivered",++ "failed",++ "cancelled"++ ]++ },++ "billing_txn_id": {++ "type": [++ "string",++ "null"++ ],++ "description": "The Billing transaction (txn_…) that settled it."++ },++ "test": {++ "type": "boolean",++ "description": "Simulations never emit events, so false in practice."++ },++ "failed_effects": {++ "type": "array",++ "minItems": 1,++ "items": {++ "type": "object",++ "required": [++ "effect",++ "adapter",++ "last_error"++ ],++ "additionalProperties": false,++ "properties": {++ "effect": {++ "enum": [++ "chat_line",++ "paid_message",++ "tts",++ "media_request",++ "overlay_alert"++ ]++ },++ "adapter": {++ "type": "string"++ },++ "last_error": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ }++ }++ }++ },++ "tips.interaction.cancelled": {++ "version": "1.0.0",++ "owner": "tips",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.cancelled.v1.json",++ "title": "TipsInteractionCancelledPayload",++ "description": "tips.interaction.cancelled v1 (OpenVibe.Tips server/domain/interactions.js failPayment, cancelQueued). Undelivered effects were cancelled: the payment failed before settling (reason payment_failed, with the Billing error code), Billing reversed it (payment_reversed) or an effect came up while the payment was no longer settled (payment_not_settled). Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",++ "type": "object",++ "required": [++ "interaction_id",++ "creator",++ "supporter",++ "supporter_name",++ "kind",++ "amount",++ "currency",++ "settlement",++ "payment_state",++ "delivery_state",++ "billing_txn_id",++ "test",++ "reason"++ ],++ "additionalProperties": false,++ "properties": {++ "interaction_id": {++ "type": "string",++ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "creator": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "supporter": {++ "anyOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "The supporter, null when external, anonymous or unmapped."++ },++ "supporter_name": {++ "type": [++ "string",++ "null"++ ],++ "description": "Display name at the time."++ },++ "kind": {++ "enum": [++ "tip",++ "paid_message",++ "tts",++ "media_request"++ ]++ },++ "amount": {++ "type": "integer",++ "minimum": 1,++ "description": "Integer amount in currency minor units (Vibes bits)."++ },++ "currency": {++ "type": "string",++ "description": "vibes-bits today."++ },++ "settlement": {++ "enum": [++ "billing",++ "external",++ "simulated",++ "imported"++ ]++ },++ "payment_state": {++ "enum": [++ "pending",++ "settled",++ "reversed",++ "failed"++ ]++ },++ "delivery_state": {++ "enum": [++ "awaiting_payment",++ "queued",++ "delivered",++ "failed",++ "cancelled"++ ]++ },++ "billing_txn_id": {++ "type": [++ "string",++ "null"++ ],++ "description": "The Billing transaction (txn_…) that settled it."++ },++ "test": {++ "type": "boolean",++ "description": "Simulations never emit events, so false in practice."++ },++ "reason": {++ "enum": [++ "payment_failed",++ "payment_reversed",++ "payment_not_settled"++ ]++ },++ "code": {++ "type": "string",++ "description": "Only with reason payment_failed: the Billing problem code (billing.insufficient_funds, billing.funding_reversed, billing.refused…)."++ }++ }++ }++ },++ "tips.goal.updated": {++ "version": "1.0.0",++ "owner": "tips",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.goal.updated.v1.json",++ "title": "TipsGoalUpdatedPayload",++ "description": "tips.goal.updated v1 (OpenVibe.Tips server/domain/goals.js changed). A goal was created, edited or closed, or its settled total moved (a contribution, the contribution that reached it, a reversal). Envelope: subject { type: goal, id: tgoal_…, revision }, visibility internal, priority important, actor service:tips.",++ "type": "object",++ "required": [++ "goal_id",++ "creator",++ "title",++ "target_amount",++ "current_amount",++ "currency",++ "status",++ "reached",++ "reason",++ "interaction_id"++ ],++ "additionalProperties": false,++ "properties": {++ "goal_id": {++ "type": "string",++ "pattern": "^tgoal_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "creator": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "title": {++ "type": "string"++ },++ "target_amount": {++ "type": "integer",++ "minimum": 1++ },++ "current_amount": {++ "type": "integer",++ "description": "Settled total including any amount carried over from Live."++ },++ "currency": {++ "type": "string"++ },++ "status": {++ "enum": [++ "active",++ "closed"++ ]++ },++ "reached": {++ "type": "boolean"++ },++ "reason": {++ "enum": [++ "created",++ "updated",++ "closed",++ "contribution",++ "reached",++ "reversal"++ ]++ },++ "interaction_id": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The interaction behind a contribution, reached or reversal change; null otherwise."++ }++ }++ }++ },++ "tips.overlay.delivered": {++ "version": "1.0.0",++ "owner": "tips",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.delivered.v1.json",++ "title": "TipsOverlayDeliveredPayload",++ "description": "tips.overlay.delivered v1 (OpenVibe.Tips server/domain/overlays.js markDelivered). An overlay delivery (alert or goal update) reached at least one connected overlay, first time only. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",++ "type": "object",++ "required": [++ "delivery_id",++ "creator",++ "kind",++ "interaction_id",++ "goal_id"++ ],++ "additionalProperties": false,++ "properties": {++ "delivery_id": {++ "type": "string",++ "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "creator": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "kind": {++ "enum": [++ "alert",++ "goal"++ ]++ },++ "interaction_id": {++ "type": [++ "string",++ "null"++ ]++ },++ "goal_id": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ }++ },++ "tips.overlay.failed": {++ "version": "1.0.0",++ "owner": "tips",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.failed.v1.json",++ "title": "TipsOverlayFailedPayload",++ "description": "tips.overlay.failed v1 (OpenVibe.Tips server/domain/overlays.js sweepFailed). No overlay showed the delivery within the delivery window. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",++ "type": "object",++ "required": [++ "delivery_id",++ "creator",++ "kind",++ "interaction_id",++ "goal_id",++ "reason"++ ],++ "additionalProperties": false,++ "properties": {++ "delivery_id": {++ "type": "string",++ "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "creator": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "kind": {++ "enum": [++ "alert",++ "goal"++ ]++ },++ "interaction_id": {++ "type": [++ "string",++ "null"++ ]++ },++ "goal_id": {++ "type": [++ "string",++ "null"++ ]++ },++ "reason": {++ "type": "string"++ }++ }++ }++ },++ "vip.plan.published": {++ "version": "1.0.0",++ "owner": "vip",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vip.plan.published.v1.json",++ "title": "VipPlanPublishedPayload",++ "description": "vip.plan.published v1 (OpenVibe.VIP server/domain/plans.js emitPublished). A plan version became what new members buy: the plan was published, or a published plan was edited (a new immutable version). Carries the version's full terms, never a price (Billing prices and charges). Envelope: subject { type: plan, id: vpl_…, revision: <version> }, visibility public, priority important, actor service:vip.",++ "type": "object",++ "required": [++ "plan_id",++ "slug",++ "version",++ "version_id",++ "creator",++ "billing_kind",++ "terms",++ "published_at"++ ],++ "additionalProperties": false,++ "properties": {++ "plan_id": {++ "type": "string",++ "pattern": "^vpl_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "slug": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9-]{0,47}$"++ },++ "version": {++ "type": "integer",++ "minimum": 1++ },++ "version_id": {++ "type": "string",++ "pattern": "^vpv_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "creator": {++ "type": "object",++ "required": [++ "id",++ "kind",++ "subject",++ "username",++ "display_name",++ "bio",++ "status"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "description": "vcr_… or network."++ },++ "kind": {++ "enum": [++ "creator",++ "network"++ ]++ },++ "subject": {++ "anyOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "null for the network creator."++ },++ "username": {++ "type": [++ "string",++ "null"++ ]++ },++ "display_name": {++ "type": [++ "string",++ "null"++ ]++ },++ "bio": {++ "type": [++ "string",++ "null"++ ]++ },++ "status": {++ "enum": [++ "active",++ "suspended"++ ]++ }++ }++ },++ "billing_kind": {++ "enum": [++ "channel_subscription",++ null++ ],++ "description": "The Billing product that sells it; null for a plan Billing does not sell (network plans)."++ },++ "terms": {++ "type": "object",++ "required": [++ "name",++ "description",++ "benefits",++ "perks",++ "billing_kind",++ "price",++ "digest"++ ],++ "additionalProperties": false,++ "properties": {++ "name": {++ "type": "string"++ },++ "description": {++ "type": [++ "string",++ "null"++ ]++ },++ "benefits": {++ "type": [++ "array",++ "null"++ ],++ "maxItems": 20,++ "items": {++ "type": "string"++ }++ },++ "perks": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "key",++ "name",++ "kind",++ "scope"++ ],++ "additionalProperties": false,++ "properties": {++ "key": {++ "type": "string"++ },++ "name": {++ "type": "string"++ },++ "kind": {++ "enum": [++ "badge",++ "emote",++ "gated_content",++ "room",++ "role",++ "other"++ ]++ },++ "scope": {++ "enum": [++ "creator",++ "network"++ ]++ }++ }++ }++ },++ "billing_kind": {++ "enum": [++ "channel_subscription",++ null++ ]++ },++ "price": {++ "type": [++ "string",++ "null"++ ],++ "description": "Never an amount: a note that Billing sets and charges the price, or null."++ },++ "digest": {++ "type": "string",++ "pattern": "^[0-9a-f]{16}$",++ "description": "sha256 prefix of name, description, benefits, perks and billing_kind."++ }++ }++ },++ "published_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "vip.membership.changed": {++ "version": "1.0.0",++ "owner": "vip",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/vip.membership.changed.v1.json",++ "title": "VipMembershipChangedPayload",++ "description": "vip.membership.changed v1 (OpenVibe.VIP server/domain/entitlements.js emitChanged). A member's standing with a creator changed as projected from Billing (granted, renewed, cancel scheduled, expired, refunded…): active, expires_at or cancel_at_period_end differ from before, or a first look found an active membership. Names the plan version the membership was bought under. Envelope: subject { type: membership, id: <member usr_…>:<creator usr_…> }, visibility internal, priority important, actor service:vip.",++ "type": "object",++ "required": [++ "member",++ "creator",++ "kind",++ "active",++ "expires_at",++ "cancel_at_period_end",++ "previous",++ "reason",++ "source",++ "billing_event_id",++ "membership_id",++ "plan_id",++ "plan_version_id",++ "plan_version",++ "subscription_id"++ ],++ "additionalProperties": false,++ "properties": {++ "member": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "creator": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "kind": {++ "type": "string",++ "description": "Billing entitlement kind; channel_subscription today."++ },++ "active": {++ "type": "boolean"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ]++ },++ "cancel_at_period_end": {++ "type": "boolean"++ },++ "previous": {++ "type": [++ "object",++ "null"++ ],++ "required": [++ "active",++ "expires_at",++ "cancel_at_period_end"++ ],++ "additionalProperties": false,++ "properties": {++ "active": {++ "type": "boolean"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ]++ },++ "cancel_at_period_end": {++ "type": "boolean"++ }++ },++ "description": "The projection before the change; null on first sight."++ },++ "reason": {++ "type": [++ "string",++ "null"++ ],++ "description": "Billing's reason (granted, renewed, refund, expired…) or VIP's (checked, cancel_scheduled)."++ },++ "source": {++ "enum": [++ "event",++ "billing_check",++ "import"++ ]++ },++ "billing_event_id": {++ "type": [++ "string",++ "null"++ ],++ "description": "The Billing event (evt_…) behind the change, when it came from one."++ },++ "membership_id": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^vms_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "plan_id": {++ "type": [++ "string",++ "null"++ ]++ },++ "plan_version_id": {++ "type": [++ "string",++ "null"++ ]++ },++ "plan_version": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 1++ },++ "subscription_id": {++ "type": [++ "string",++ "null"++ ],++ "description": "The Billing subscription (sub_…)."++ }++ }++ }++ },++ "billing.transaction.settled": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.settled.v1.json",++ "title": "BillingTransactionSettledPayload",++ "description": "billing.transaction.settled v1 (OpenVibe.Billing server/ops/purchases.js summary(); emitted by purchases, transfers, subscriptions, cashouts recycle and admin adjustments). A journal transaction was posted. Envelope: subject { type: transaction, id: txn_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "transaction_id",++ "type",++ "test",++ "from_subject",++ "to_subject",++ "provider",++ "metadata"++ ],++ "additionalProperties": false,++ "properties": {++ "transaction_id": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "type": {++ "enum": [++ "purchase",++ "donation",++ "subscription",++ "subscription_share",++ "cashout_request",++ "cashout_paid",++ "cashout_denied",++ "recycle",++ "refund",++ "chargeback",++ "adjustment",++ "import"++ ]++ },++ "test": {++ "type": "boolean",++ "description": "Test money: never counted."++ },++ "from_subject": {++ "type": [++ "string",++ "null"++ ],++ "description": "Subject id (usr_…) value moved from, null for provider money."++ },++ "to_subject": {++ "type": [++ "string",++ "null"++ ],++ "description": "Subject id (usr_…) value moved to."++ },++ "provider": {++ "type": [++ "string",++ "null"++ ],++ "description": "stripe, paypal, powerchat…; null for credit movements."++ },++ "metadata": {++ "type": "object",++ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."++ }++ }++ }++ },++ "billing.transaction.reversed": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",++ "title": "BillingTransactionReversedPayload",++ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "transaction_id",++ "type",++ "test",++ "from_subject",++ "to_subject",++ "provider",++ "metadata",++ "reverses_txn"++ ],++ "additionalProperties": false,++ "properties": {++ "transaction_id": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "type": {++ "enum": [++ "purchase",++ "donation",++ "subscription",++ "subscription_share",++ "cashout_request",++ "cashout_paid",++ "cashout_denied",++ "recycle",++ "refund",++ "chargeback",++ "adjustment",++ "import"++ ]++ },++ "test": {++ "type": "boolean",++ "description": "Test money: never counted."++ },++ "from_subject": {++ "type": [++ "string",++ "null"++ ],++ "description": "Subject id (usr_…) value moved from, null for provider money."++ },++ "to_subject": {++ "type": [++ "string",++ "null"++ ],++ "description": "Subject id (usr_…) value moved to."++ },++ "provider": {++ "type": [++ "string",++ "null"++ ],++ "description": "stripe, paypal, powerchat…; null for credit movements."++ },++ "metadata": {++ "type": "object",++ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."++ },++ "reverses_txn": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The transaction this one reverses."++ },++ "review": {++ "enum": [++ "required",++ null++ ],++ "description": "Provider reversals only: required when Billing kept value it could not recover (staff review)."++ },++ "entitlements_revoked": {++ "type": "integer",++ "minimum": 0,++ "description": "Provider reversals only: subscription periods revoked."++ }++ }++ }++ },++ "billing.entitlement.changed": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.entitlement.changed.v1.json",++ "title": "BillingEntitlementChangedPayload",++ "description": "billing.entitlement.changed v1 (OpenVibe.Billing server/ops/subscriptions.js pay, setStatus, revokeForTxn; entitlement()). A subscriber's channel-subscription entitlement to a streamer changed: a period was granted or renewed, the subscription ended or expired, or a reversed payment revoked a period. The payload is the entitlement as of now. Envelope: subject { type: entitlement, id: <subscriber usr_…>:channel_subscription:<streamer usr_…> }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "subject",++ "streamer",++ "kind",++ "active",++ "expires_at",++ "subscription",++ "reason"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "streamer": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "kind": {++ "const": "channel_subscription"++ },++ "active": {++ "type": "boolean"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "subscription": {++ "type": [++ "object",++ "null"++ ],++ "required": [++ "id",++ "status",++ "auto_renew",++ "cancel_at_period_end",++ "provider"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "status": {++ "enum": [++ "active",++ "canceled",++ "expired"++ ]++ },++ "auto_renew": {++ "type": "boolean"++ },++ "cancel_at_period_end": {++ "type": "boolean"++ },++ "provider": {++ "type": "string"++ }++ }++ },++ "reason": {++ "type": "string",++ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."++ },++ "transaction_id": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "granted/renewed: the transaction that paid the period."++ },++ "revoked_period": {++ "type": "object",++ "required": [++ "starts_at",++ "ends_at"++ ],++ "additionalProperties": false,++ "properties": {++ "starts_at": {++ "type": "string",++ "format": "date-time"++ },++ "ends_at": {++ "type": "string",++ "format": "date-time"++ }++ },++ "description": "refund/chargeback: the period the reversed payment had granted."++ }++ }++ }++ },++ "billing.subscription.canceled": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.subscription.canceled.v1.json",++ "title": "BillingSubscriptionCanceledPayload",++ "description": "billing.subscription.canceled v1 (OpenVibe.Billing server/ops/subscriptions.js cancel). A subscription was set to cancel at period end (for Stripe, after Stripe accepted it); the paid period still runs. Envelope: subject { type: subscription, id: sub_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "subscription",++ "provider_sync",++ "actor"++ ],++ "additionalProperties": false,++ "properties": {++ "subscription": {++ "type": "object",++ "required": [++ "id",++ "subscriber",++ "streamer",++ "tier",++ "provider",++ "provider_ref",++ "route",++ "status",++ "auto_renew",++ "cancel_at_period_end",++ "price_cents",++ "current_period_end",++ "created_at",++ "updated_at"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "subscriber": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "streamer": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "tier": {++ "type": "integer",++ "minimum": 1++ },++ "provider": {++ "type": "string"++ },++ "provider_ref": {++ "type": [++ "string",++ "null"++ ]++ },++ "route": {++ "type": [++ "string",++ "null"++ ]++ },++ "status": {++ "enum": [++ "active",++ "canceled",++ "expired"++ ]++ },++ "auto_renew": {++ "type": "boolean"++ },++ "cancel_at_period_end": {++ "type": "boolean"++ },++ "price_cents": {++ "type": "integer",++ "minimum": 0++ },++ "current_period_end": {++ "type": [++ "string",++ "null"++ ]++ },++ "created_at": {++ "type": "string"++ },++ "updated_at": {++ "type": "string"++ }++ }++ },++ "provider_sync": {++ "enum": [++ "not_needed",++ "stripe_cancel_at_period_end"++ ]++ },++ "actor": {++ "type": "object",++ "required": [++ "principal",++ "request_id"++ ],++ "additionalProperties": false,++ "properties": {++ "principal": {++ "type": [++ "string",++ "null"++ ],++ "description": "Token sub of the caller (svc:…, usr_…)."++ },++ "request_id": {++ "type": [++ "string",++ "null"++ ]++ },++ "on_behalf_of": {++ "$ref": "../../identity/subject-ref.v1.json"++ }++ },++ "description": "Who asked: the calling principal and the person it acted for."++ }++ }++ }++ },++ "billing.cashout.requested": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.requested.v1.json",++ "title": "BillingCashoutRequestedPayload",++ "description": "billing.cashout.requested v1 (OpenVibe.Billing server/ops/cashouts.js request). A creator asked to cash out payable bits; they are held in escrow. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "cashout",++ "transaction_id"++ ],++ "additionalProperties": false,++ "properties": {++ "cashout": {++ "type": "object",++ "required": [++ "id",++ "subject",++ "amount_bits",++ "value_cents",++ "status",++ "payout_method",++ "escrow_until",++ "request_txn",++ "settle_txn",++ "payout_provider",++ "payout_reference",++ "reason",++ "created_at",++ "updated_at"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "subject": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "amount_bits": {++ "type": "integer",++ "minimum": 1++ },++ "value_cents": {++ "type": "integer"++ },++ "status": {++ "const": "requested"++ },++ "payout_method": {++ "type": "object",++ "description": "{ type, address } as the creator entered it (e.g. a PayPal email)."++ },++ "escrow_until": {++ "type": "string"++ },++ "request_txn": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "settle_txn": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "payout_provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "payout_reference": {++ "type": [++ "string",++ "null"++ ]++ },++ "reason": {++ "type": [++ "string",++ "null"++ ]++ },++ "created_at": {++ "type": "string"++ },++ "updated_at": {++ "type": "string"++ }++ }++ },++ "transaction_id": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The cashout_request transaction."++ }++ }++ }++ },++ "billing.cashout.paid": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.paid.v1.json",++ "title": "BillingCashoutPaidPayload",++ "description": "billing.cashout.paid v1 (OpenVibe.Billing server/ops/cashouts.js approve). Staff paid a cash-out out through a provider. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "cashout",++ "transaction_id"++ ],++ "additionalProperties": false,++ "properties": {++ "cashout": {++ "type": "object",++ "required": [++ "id",++ "subject",++ "amount_bits",++ "value_cents",++ "status",++ "payout_method",++ "escrow_until",++ "request_txn",++ "settle_txn",++ "payout_provider",++ "payout_reference",++ "reason",++ "created_at",++ "updated_at"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "subject": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "amount_bits": {++ "type": "integer",++ "minimum": 1++ },++ "value_cents": {++ "type": "integer"++ },++ "status": {++ "const": "paid"++ },++ "payout_method": {++ "type": "object",++ "description": "{ type, address } as the creator entered it (e.g. a PayPal email)."++ },++ "escrow_until": {++ "type": "string"++ },++ "request_txn": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "settle_txn": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "payout_provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "payout_reference": {++ "type": [++ "string",++ "null"++ ]++ },++ "reason": {++ "type": [++ "string",++ "null"++ ]++ },++ "created_at": {++ "type": "string"++ },++ "updated_at": {++ "type": "string"++ }++ }++ },++ "transaction_id": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The cashout_paid transaction."++ }++ }++ }++ },++ "billing.cashout.denied": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.denied.v1.json",++ "title": "BillingCashoutDeniedPayload",++ "description": "billing.cashout.denied v1 (OpenVibe.Billing server/ops/cashouts.js deny). Staff denied a cash-out; the escrowed bits went back (a billing.transaction.reversed follows in the same transaction). Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "cashout",++ "transaction_id"++ ],++ "additionalProperties": false,++ "properties": {++ "cashout": {++ "type": "object",++ "required": [++ "id",++ "subject",++ "amount_bits",++ "value_cents",++ "status",++ "payout_method",++ "escrow_until",++ "request_txn",++ "settle_txn",++ "payout_provider",++ "payout_reference",++ "reason",++ "created_at",++ "updated_at"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "subject": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "amount_bits": {++ "type": "integer",++ "minimum": 1++ },++ "value_cents": {++ "type": "integer"++ },++ "status": {++ "const": "denied"++ },++ "payout_method": {++ "type": "object",++ "description": "{ type, address } as the creator entered it (e.g. a PayPal email)."++ },++ "escrow_until": {++ "type": "string"++ },++ "request_txn": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "settle_txn": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "payout_provider": {++ "type": [++ "string",++ "null"++ ]++ },++ "payout_reference": {++ "type": [++ "string",++ "null"++ ]++ },++ "reason": {++ "type": [++ "string",++ "null"++ ]++ },++ "created_at": {++ "type": "string"++ },++ "updated_at": {++ "type": "string"++ }++ }++ },++ "transaction_id": {++ "type": "string",++ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The cashout_denied transaction."++ }++ }++ }++ },++ "billing.staff.action": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.staff.action.v1.json",++ "title": "BillingStaffActionPayload",++ "description": "billing.staff.action v1 (OpenVibe.Billing server/console/audit.js record). A staff member completed an action in the Billing console (only outcome done is announced; refused actions stay in staff_audit). Holds no secrets: codes, payout reference, counts. Envelope: subject { type: staff_action, id: sa_… }, visibility internal, priority important, actor the staff user ({ type: user, id: usr_… }).",++ "type": "object",++ "required": [++ "audit_id",++ "action",++ "outcome",++ "target",++ "reason",++ "request_id",++ "detail"++ ],++ "additionalProperties": false,++ "properties": {++ "audit_id": {++ "type": "string",++ "pattern": "^sa_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "action": {++ "type": "string"++ },++ "outcome": {++ "const": "done"++ },++ "target": {++ "type": [++ "object",++ "null"++ ],++ "required": [++ "type",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "type": "string"++ },++ "id": {++ "type": "string"++ }++ }++ },++ "reason": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500++ },++ "request_id": {++ "type": [++ "string",++ "null"++ ]++ },++ "detail": {++ "type": "object",++ "description": "Small whitelisted detail of the action."++ }++ }++ }++ },++ "billing.receipt.external": {++ "version": "1.0.0",++ "owner": "billing",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/billing.receipt.external.v1.json",++ "title": "BillingReceiptExternalPayload",++ "description": "billing.receipt.external v1 (OpenVibe.Billing server/ops/external.js record). An EXTERNAL receipt (ADR-012): a tip paid on a creator's own provider account (PowerChat route direct). The money never touches OpenVibe (no journal entry); Billing announces it once per payment, only when BILLING_AUTHORITY=billing and the receiving account is mapped to a creator. OpenVibe.Tips celebrates it (chat line, overlay alert, goal). Envelope: subject { type: provider_receipt, id: <provider>:<payment id> }, visibility internal, priority important, actor service:billing.",++ "type": "object",++ "required": [++ "classification",++ "provider",++ "receipt_ref",++ "provider_event_id",++ "delivery_id",++ "streamer",++ "receiving_account",++ "amount_cents",++ "currency",++ "value_bits",++ "donor_name",++ "anonymous",++ "message",++ "app_ref",++ "app_purpose",++ "occurred_at",++ "test",++ "rates"++ ],++ "additionalProperties": false,++ "properties": {++ "classification": {++ "const": "EXTERNAL"++ },++ "provider": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9_-]{1,39}$"++ },++ "receipt_ref": {++ "type": "string",++ "description": "<provider>:<payment id>, e.g. powerchat:…"++ },++ "provider_event_id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 200,++ "description": "The provider's id of the payment, stable across redeliveries."++ },++ "delivery_id": {++ "type": "string",++ "description": "The webhook delivery that carried it."++ },++ "streamer": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "receiving_account": {++ "type": "object",++ "required": [++ "provider",++ "id",++ "username"++ ],++ "additionalProperties": false,++ "properties": {++ "provider": {++ "type": "string"++ },++ "id": {++ "type": [++ "string",++ "null"++ ]++ },++ "username": {++ "type": [++ "string",++ "null"++ ]++ }++ }++ },++ "amount_cents": {++ "type": "integer",++ "minimum": 1,++ "maximum": 100000000++ },++ "currency": {++ "const": "usd-cents"++ },++ "value_bits": {++ "type": "integer",++ "minimum": 0,++ "description": "Billing's value of the money in bits."++ },++ "donor_name": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80,++ "description": "null when anonymous."++ },++ "anonymous": {++ "type": "boolean"++ },++ "message": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500++ },++ "app_ref": {++ "type": [++ "string",++ "null"++ ]++ },++ "app_purpose": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 120++ },++ "occurred_at": {++ "type": [++ "string",++ "null"++ ],++ "description": "The provider's timestamp as it sent it."++ },++ "test": {++ "type": "boolean"++ },++ "rates": {++ "type": "object",++ "required": [++ "bits_per_usd"++ ],++ "properties": {++ "bits_per_usd": {++ "type": "number"++ }++ },++ "description": "Rate snapshot."++ }++ }++ }++ },++ "deals.watch.matched": {++ "version": "1.0.0",++ "owner": "deals",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/deals.watch.matched.v1.json",++ "title": "DealsWatchMatchedPayload",++ "description": "deals.watch.matched v1 (OpenVibe.Deals server/domain/watches.js onObservation). A person's keyword, product or price-below watch matched a fresh observation of an active offer, inside the transaction that recorded the observation. At most one event per (watch, observation); a keyword/product watch notifies once per offer, a price-below watch again only for a lower price. Saved searches (kind search) never notify. Envelope: subject { type: watch, id: <watch_id> }, visibility internal, priority important, actor service:deals. OpenVibe.Network turns it into a DEAL_WATCH_MATCH notification for payload.recipient (server/notifications/events-consumer.js). A price is the decimal exactly as stated, never inferred; null when the observation stated none.",++ "type": "object",++ "required": [++ "watch_id",++ "recipient",++ "kind",++ "query",++ "product_id",++ "max_price",++ "currency",++ "offer_id",++ "offer_url",++ "title",++ "observation"++ ],++ "additionalProperties": false,++ "properties": {++ "watch_id": {++ "type": "string",++ "pattern": "^dwt_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "recipient": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The watch owner's user subject id: who is notified."++ },++ "kind": {++ "enum": [++ "keyword",++ "product",++ "price_below"++ ]++ },++ "query": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200,++ "description": "Keywords of a keyword watch, or of a price-below watch without a product."++ },++ "product_id": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 64,++ "description": "Deals product id of a product or price-below watch."++ },++ "max_price": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 40,++ "description": "Price-below threshold as entered (decimal text, e.g. 19.99)."++ },++ "currency": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^[A-Z]{3}$",++ "description": "ISO 4217 currency of max_price."++ },++ "offer_id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64,++ "description": "The canonical (root) offer id (dof_…)."++ },++ "offer_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "maxLength": 2048,++ "description": "Canonical offer page URL on Deals."++ },++ "title": {++ "type": "string",++ "minLength": 1,++ "maxLength": 500++ },++ "observation": {++ "type": "object",++ "required": [++ "id",++ "observed_at",++ "price",++ "currency",++ "availability"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^dpo_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "observed_at": {++ "type": "string",++ "format": "date-time"++ },++ "price": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 40,++ "description": "Decimal text as stated; null when the observation stated no price."++ },++ "currency": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^[A-Z]{3}$"++ },++ "availability": {++ "enum": [++ "in_stock",++ "out_of_stock",++ "preorder",++ "discontinued",++ "limited",++ "sold_out",++ "online_only",++ "in_store_only",++ null++ ]++ }++ }++ }++ }++ }++ },++ "trade.alert.triggered": {++ "version": "1.0.0",++ "owner": "trade",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/trade.alert.triggered.v1.json",++ "title": "TradeAlertTriggeredPayload",++ "description": "trade.alert.triggered v1 (OpenVibe.Trade server/domain/alerts.js deliver). One delivery of one private alert rule: a threshold crossed by a new observation (trigger kind observation), or a newly seen document of the instrument (filing_type / new_document rules, trigger kind document). Written in the transaction that recorded the observation or document; at most one per (rule, trigger). Envelope: subject { type: user, id: <rule owner usr_…> }, visibility subject, priority important, actor service:trade. Data only, never rendered notification text (ADR-020): OpenVibe.Network renders a TRADE_ALERT notification (server/notifications/events-consumer.js). Information only, not investment advice.",++ "type": "object",++ "required": [++ "delivery_id",++ "rule",++ "instrument",++ "trigger",++ "disclaimer"++ ],++ "additionalProperties": false,++ "properties": {++ "delivery_id": {++ "type": "string",++ "pattern": "^ald_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "rule": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "kind",++ "status",++ "instrument",++ "metric",++ "operator",++ "threshold",++ "unit",++ "currency",++ "form_types",++ "armed",++ "created_at",++ "updated_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^alr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "kind": {++ "enum": [++ "threshold",++ "filing_type",++ "new_document"++ ]++ },++ "status": {++ "enum": [++ "active",++ "deleted"++ ]++ },++ "instrument": {++ "type": "object",++ "required": [++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "symbol": {++ "type": "string"++ },++ "name": {++ "type": "string"++ }++ }++ },++ "metric": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80,++ "description": "Threshold rules: e.g. price.close, volume, us-gaap:Revenues."++ },++ "operator": {++ "enum": [++ "above",++ "below",++ null++ ]++ },++ "threshold": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 64,++ "description": "Decimal text as entered."++ },++ "unit": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 20++ },++ "currency": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^[A-Z]{3}$"++ },++ "form_types": {++ "type": [++ "array",++ "null"++ ],++ "items": {++ "type": "string",++ "maxLength": 20++ },++ "description": "filing_type rules: the 1–10 form types that trigger it (10-K, 8-K…)."++ },++ "armed": {++ "type": [++ "boolean",++ "null"++ ],++ "description": "Threshold rules: armed before this delivery (always true when it fired); null for document rules."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "updated_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "instrument": {++ "type": "object",++ "required": [++ "id",++ "symbol",++ "name",++ "url"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "symbol": {++ "type": "string",++ "minLength": 1,++ "maxLength": 32++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 300++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://",++ "maxLength": 2048,++ "description": "Instrument page on Trade."++ }++ }++ },++ "trigger": {++ "oneOf": [++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind",++ "id",++ "metric",++ "value",++ "unit",++ "currency",++ "observed_at",++ "retrieved_at",++ "source_key",++ "source_url"++ ],++ "properties": {++ "kind": {++ "const": "observation"++ },++ "id": {++ "type": "string",++ "pattern": "^obs_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "metric": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "value": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64,++ "description": "The decimal exactly as the source stated it."++ },++ "unit": {++ "type": "string",++ "minLength": 1,++ "maxLength": 20++ },++ "currency": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^[A-Z]{3}$"++ },++ "observed_at": {++ "type": "string",++ "format": "date-time"++ },++ "retrieved_at": {++ "type": "string",++ "format": "date-time"++ },++ "source_key": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "description": "OpenVibe.Sources source key."++ },++ "source_url": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 2048++ }++ }++ },++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind",++ "id",++ "form_type",++ "title",++ "url",++ "published_at",++ "retrieved_at",++ "source_key"++ ],++ "properties": {++ "kind": {++ "const": "document"++ },++ "id": {++ "type": "string",++ "pattern": "^doc_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "form_type": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 20++ },++ "title": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 1000++ },++ "url": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 2048++ },++ "published_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "The source's own date; null if it gave none."++ },++ "retrieved_at": {++ "type": "string",++ "format": "date-time"++ },++ "source_key": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128++ }++ }++ }++ ]++ },++ "disclaimer": {++ "type": "string",++ "minLength": 1,++ "maxLength": 200,++ "description": "Currently \"Information only — not investment advice; no trading here.\""++ }++ }++ }++ },++ "tools.job.created": {++ "version": "1.0.0",++ "owner": "tools",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.created.v1.json",++ "title": "ToolsJobCreatedPayload",++ "description": "tools.job.created v1 (OpenVibe.Tools apps/_shared/jobs, planned). A job was accepted and queued (POST /api/v1/jobs, or a retry of a failed job, which carries retry_of). An Idempotency-Key replay creates no job and no event. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor the owner when it is a subject (user, service, app or mod), else service:tools.",++ "type": "object",++ "required": [++ "job_id",++ "service",++ "type",++ "type_version",++ "state",++ "owner",++ "attempts",++ "max_attempts",++ "created_at",++ "retry_of"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$",++ "description": "The Tools satellite that runs the job (img, audio, docs…)."++ },++ "type": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",++ "maxLength": 80,++ "description": "Job type, e.g. img.process."++ },++ "type_version": {++ "type": "integer",++ "minimum": 1++ },++ "state": {++ "const": "queued"++ },++ "owner": {++ "oneOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": {++ "type": "integer",++ "minimum": 0++ },++ "max_attempts": {++ "type": "integer",++ "minimum": 1++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "retry_of": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."++ }++ }++ }++ },++ "tools.job.started": {++ "version": "1.0.0",++ "owner": "tools",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.started.v1.json",++ "title": "ToolsJobStartedPayload",++ "description": "tools.job.started v1 (OpenVibe.Tools apps/_shared/jobs, planned). A worker claimed the job and it is running (attempts counts this attempt). A job requeued after a restart starts again and is announced again. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:tools.",++ "type": "object",++ "required": [++ "job_id",++ "service",++ "type",++ "type_version",++ "state",++ "owner",++ "attempts",++ "max_attempts",++ "created_at",++ "retry_of",++ "started_at"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$",++ "description": "The Tools satellite that runs the job (img, audio, docs…)."++ },++ "type": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",++ "maxLength": 80,++ "description": "Job type, e.g. img.process."++ },++ "type_version": {++ "type": "integer",++ "minimum": 1++ },++ "state": {++ "const": "running"++ },++ "owner": {++ "oneOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": {++ "type": "integer",++ "minimum": 0++ },++ "max_attempts": {++ "type": "integer",++ "minimum": 1++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "retry_of": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."++ },++ "started_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "tools.job.succeeded": {++ "version": "1.0.0",++ "owner": "tools",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.succeeded.v1.json",++ "title": "ToolsJobSucceededPayload",++ "description": "tools.job.succeeded v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job finished and its result files are stored (in Media when TOOLS_JOB_RESULTS=media). Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",++ "type": "object",++ "required": [++ "job_id",++ "service",++ "type",++ "type_version",++ "state",++ "owner",++ "attempts",++ "max_attempts",++ "created_at",++ "retry_of",++ "started_at",++ "finished_at",++ "expires_at",++ "result"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$",++ "description": "The Tools satellite that runs the job (img, audio, docs…)."++ },++ "type": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",++ "maxLength": 80,++ "description": "Job type, e.g. img.process."++ },++ "type_version": {++ "type": "integer",++ "minimum": 1++ },++ "state": {++ "const": "succeeded"++ },++ "owner": {++ "oneOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": {++ "type": "integer",++ "minimum": 0++ },++ "max_attempts": {++ "type": "integer",++ "minimum": 1++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "retry_of": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."++ },++ "started_at": {++ "type": "string",++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)."++ },++ "result": {++ "type": "object",++ "required": [++ "files"++ ],++ "additionalProperties": false,++ "description": "Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).",++ "properties": {++ "files": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "type": "object",++ "required": [++ "index",++ "mime",++ "size",++ "sha256",++ "storage",++ "media"++ ],++ "additionalProperties": false,++ "properties": {++ "index": {++ "type": "integer",++ "minimum": 0,++ "description": "n in /api/v1/jobs/:id/files/:n."++ },++ "mime": {++ "type": "string",++ "minLength": 1,++ "maxLength": 200++ },++ "size": {++ "type": "integer",++ "minimum": 0++ },++ "sha256": {++ "type": "string",++ "pattern": "^[0-9a-f]{64}$"++ },++ "storage": {++ "enum": [++ "local",++ "media"++ ],++ "description": "media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires."++ },++ "media": {++ "oneOf": [++ {++ "$ref": "../../media/media-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "{ media_id, role: output } when storage is media."++ }++ }++ }++ }++ }++ }++ }++ }++ },++ "tools.job.failed": {++ "version": "1.0.0",++ "owner": "tools",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.failed.v1.json",++ "title": "ToolsJobFailedPayload",++ "description": "tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",++ "type": "object",++ "required": [++ "job_id",++ "service",++ "type",++ "type_version",++ "state",++ "owner",++ "attempts",++ "max_attempts",++ "created_at",++ "retry_of",++ "started_at",++ "finished_at",++ "expires_at",++ "error",++ "retryable"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$",++ "description": "The Tools satellite that runs the job (img, audio, docs…)."++ },++ "type": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",++ "maxLength": 80,++ "description": "Job type, e.g. img.process."++ },++ "type_version": {++ "type": "integer",++ "minimum": 1++ },++ "state": {++ "const": "failed"++ },++ "owner": {++ "oneOf": [++ {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ {++ "type": "null"++ }++ ],++ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."++ },++ "attempts": {++ "type": "integer",++ "minimum": 0++ },++ "max_attempts": {++ "type": "integer",++ "minimum": 1++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "retry_of": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "null when the job failed before it ever ran."++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ },++ "expires_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)."++ },++ "error": {++ "type": "object",++ "required": [++ "status",++ "code",++ "detail"++ ],++ "additionalProperties": false,++ "description": "The job's problem (errors.problem@1) without request or trace ids.",++ "properties": {++ "status": {++ "type": "integer",++ "minimum": 400,++ "maximum": 599++ },++ "code": {++ "type": "string",++ "pattern": "^tools\\.[a-z0-9_.]+$",++ "description": "tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…)."++ },++ "detail": {++ "type": "string",++ "maxLength": 500,++ "description": "Human-readable reason; server paths are scrubbed."++ }++ }++ },++ "retryable": {++ "type": "boolean"++ }++ }++ }}}}@@@ -3371,3 +3371,3245 @@ export interface AppManifest {sdk?: string;};}++++/** [email protected] (owner: events) */++/**++ * payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.++ */++export interface RedactionDirective {++ /**++ * Events to redact by id. Duplicates are ignored.++ *++ * @minItems 1++ * @maxItems 1000++ */++ event_ids?: [string, ...string[]];++ /**++ * Envelope subject.type of the events to redact (chat_message, post…). Goes with subject_ids.++ */++ subject_type?: string;++ /**++ * Envelope subject.id values; every not-yet-redacted event of the same source about each one is redacted.++ *++ * @minItems 1++ * @maxItems 1000++ */++ subject_ids?: [string, ...string[]];++}++++/** [email protected] (owner: events) */++/**++ * The payload of a redacted event (ADR-026). OpenVibe.Events replaces the original payload with this and the actor with the producer itself ({ type: service, id: <source> }, or the app); event_id, seq, event_type, version, subject, timestamp and visibility stay. Every read path serves it from then on: pull, GET by id, SSE replay, queued deliveries and DLQ replays. A consumer checks payload.redacted before validating a payload against its event type's schema.++ */++export interface TombstonePayload {++ redacted: true;++ redacted_at: string;++ /**++ * The event_id of the redacting event; an operator label (e.g. backfill) when OpenVibe.Events redacted by script.++ */++ redacted_by: 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.++ */++export interface ChatMessageDeletedPayload {++ /**++ * Chat message ids (the id in chat.message.created).++ *++ * @minItems 1++ * @maxItems 500++ */++ message_ids: [number, ...number[]];++ redacts: RedactionDirective;++}++++/** [email protected] (owner: sources) */++/**++ * 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.++ */++export interface SourcesItemCreatedPayload {++ item_id: string;++ source_key: string;++ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";++ /**++ * article | url | sitemap | product | offer | review | record … (sources.item@1 kind).++ */++ kind: string;++ canonical_url: string | null;++ title: string | null;++ revision: number;++ content_hash: string;++ /**++ * Last successful fetch (or manual entry) that contained the item.++ */++ retrieved_at: string;++}++++/** [email protected] (owner: sources) */++/**++ * 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.++ */++export interface SourcesItemUpdatedPayload {++ item_id: string;++ source_key: string;++ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";++ /**++ * article | url | sitemap | product | offer | review | record … (sources.item@1 kind).++ */++ kind: string;++ canonical_url: string | null;++ title: string | null;++ revision: number;++ content_hash: string;++ /**++ * Last successful fetch (or manual entry) that contained the item.++ */++ retrieved_at: string;++ previous_content_hash: string;++}++++/** [email protected] (owner: sources) */++/**++ * 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.++ */++export interface SourcesItemRemovedPayload {++ item_id: string;++ source_key: string;++ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";++ /**++ * article | url | sitemap | product | offer | review | record … (sources.item@1 kind).++ */++ kind: string;++ canonical_url: string | null;++ title: string | null;++ revision: number;++ content_hash: string;++ /**++ * Last successful fetch (or manual entry) that contained the item.++ */++ retrieved_at: string;++ reason: string;++}++++/** [email protected] (owner: sources) */++/**++ * 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.++ */++export interface SourcesFetchFailedPayload {++ source_key: string;++ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";++ /**++ * The recorded fetch run (GET /api/v1/sources/:key/runs).++ */++ run_id: string;++ endpoint_url: string;++ state: "robots_denied" | "http_error" | "rate_limited" | "parse_error";++ /**++ * robots | address_refused | port_refused | bad_url | upstream_429 | http_<status> | unreadable | adapter_error | internal.++ */++ error_code: string;++ http_status: number | null;++ /**++ * The source's consecutive failed runs including this one.++ */++ consecutive_failures: number;++}++++/** [email protected] (owner: sources) */++/**++ * 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.++ */++export interface SourcesIndexDocumentUpsertedPayload {++ /**++ * Owning service id (wiki, blog, news, community, sources…). Only that service may write the document.++ */++ owner: string;++ /**++ * Resource type inside the owner (page, post, story, entity, offer, coupon, instrument, item…).++ */++ type: string;++ /**++ * Resource id inside owner+type. Stable across revisions.++ */++ id: string;++ /**++ * Owner's monotonic revision of the resource. An older revision never overwrites a newer one; at equal revision a deletion wins.++ */++ revision: number;++ /**++ * Deletion marker. true = tombstone; every other content field is ignored.++ */++ deleted?: boolean;++ /**++ * public: anyone. unlisted: never listed except to ACL matches; fetchable by exact id by a signed-in subject. members: subjects, groups or entitlements in acl. private: subjects in acl.subjects only. draft: never served.++ */++ visibility?: "public" | "unlisted" | "members" | "private" | "draft";++ /**++ * Who may see a non-public document. Ignored for public documents. Empty means nobody (for members/private) or direct-id only (for unlisted).++ */++ acl?: {++ /**++ * @maxItems 200++ */++ subjects?: string[];++ /**++ * Opaque group keys owned by some service, e.g. role:admin (Network role), wiki.space:spc_…:member.++ *++ * @maxItems 100++ */++ groups?: string[];++ /**++ * Entitlement keys (VIP/Billing), e.g. vip.plan:pln_…. Only a first-party service that just resolved them may present them for a viewer.++ *++ * @maxItems 100++ */++ entitlements?: string[];++ };++ canonical_url?: string;++ title?: string;++ summary?: string;++ /**++ * Plain text for full-text search (no HTML). Keep it under the Events payload limit when publishing through Events.++ */++ body?: string;++ /**++ * Filterable values (category, tags, space, author subject, currency…). Filter with facet.<key>=<value>.++ */++ facets?: {++ [k: string]: (string | number | boolean | string[]) | undefined;++ };++ /**++ * BCP 47 tag.++ */++ language?: string;++ authorship?: "human" | "ai_assisted" | "ai_generated" | "imported";++ /**++ * Where the content came from: typed references to source records (Sources items, citations, Media objects).++ *++ * @maxItems 50++ */++ provenance?: {++ service: string;++ type: string;++ id: string;++ revision?: number;++ label?: string;++ url?: string;++ retrieved_at?: string;++ /**++ * true when the reference came from a stub provider; the document is then never indexable.++ */++ stub?: boolean;++ }[];++ /**++ * Only published documents are ever served.++ */++ publication_state?: "draft" | "scheduled" | "published" | "unpublished" | "retracted" | "archived";++ published_at?: string | null;++ updated_at?: string | null;++ /**++ * The owner's deterministic indexability decision (publishing packages, roadmap §32.3). Search can only make it stricter.++ */++ indexability?: {++ decision: "index" | "noindex";++ /**++ * Known reasons: draft, private, members_only, unlisted, not_published, deleted, thin_content, duplicate_without_canonical, unsupported_claims, unsourced, stub_provider, sensitive_unreviewed, ai_unreviewed, missing_canonical_url, third_party_content, owner_decision.++ *++ * @maxItems 20++ */++ reasons?:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ };++}++++/** [email protected] (owner: sources) */++/**++ * 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.++ */++export interface SourcesIndexDocumentDeletedPayload {++ type: "item";++ id: string;++ revision: number;++}++++/** [email protected] (owner: search) */++/**++ * 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.++ */++export interface SearchDocumentIndexedPayload {++ owner: string;++ type: string;++ id: string;++ revision: number;++ exposure: "restricted" | "public_unlisted" | "public_listed";++ /**++ * true when Search already held an earlier revision of the document.++ */++ reindexed: boolean;++ /**++ * Only when the document is public (public_unlisted or public_listed); null otherwise or when it has none.++ */++ canonical_url: string | null;++}++++/** [email protected] (owner: search) */++/**++ * 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.++ */++export interface SearchDocumentRemovedPayload {++ owner: string;++ type: string;++ id: string;++ revision: number;++ reason: "deleted" | "draft" | "not_published" | "visibility_changed" | "noindex";++ previous_exposure: "restricted" | "public_unlisted" | "public_listed";++ /**++ * Lower than previous_exposure. none: no longer served to anyone.++ */++ exposure: "none" | "restricted" | "public_unlisted";++ /**++ * The URL only when the document was public before (so it must be purged); null otherwise.++ */++ canonical_url: string | null;++}++++/** [email protected] (owner: wiki) */++/**++ * 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.++ */++export interface WikiSpaceUpdatedPayload {++ slug: string;++ name: string;++ kind: "official" | "user";++ visibility: "public" | "members" | "private";++ deleted: boolean;++ /**++ * What changed. renamed = a new slug.++ */++ change: "created" | "renamed" | "visibility" | "settings" | "roles" | "deleted";++ /**++ * Canonical URL of the space.++ */++ url: string;++}++++/** [email protected] (owner: wiki) */++/**++ * 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.++ */++export interface WikiRevisionCreatedPayload {++ space_id: string;++ /**++ * Space slug.++ */++ space: string;++ /**++ * Page slug.++ */++ slug: string;++ /**++ * The new revision number.++ */++ number: number;++ kind: "edit" | "revert" | "import";++ parent_number: number | null;++ /**++ * The revision a revert restored; null otherwise.++ */++ reverted_to: number | null;++ /**++ * Search's authorship vocabulary; null when the revision has no authorship record.++ */++ authorship: "human" | "ai_assisted" | "ai_generated" | "imported" | null;++ /**++ * Who wrote it: a subject id (usr_…) or a service (svc:<id>).++ */++ author: string | null;++ message: string | null;++ /**++ * Present when the revision is an AI proposal awaiting a person's approval.++ */++ proposal_id?: string;++}++++/** [email protected] (owner: wiki) */++/**++ * 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.++ */++export interface WikiWatchTriggeredPayload {++ /**++ * revised = a new draft revision; proposed = an AI proposal awaits approval; the rest follow wiki.page.*.++ */++ action: "published" | "updated" | "unpublished" | "deleted" | "revised" | "proposed";++ /**++ * Space slug.++ */++ space: string;++ /**++ * Page slug.++ */++ slug: string;++ title: string;++ revision: number | null;++ url: string;++ /**++ * Watchers to notify.++ *++ * @minItems 1++ */++ recipients: [string, ...string[]];++}++++/** [email protected] (owner: wiki) */++/**++ * 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).++ */++export interface WikiPagePublishedPayload {++ canonical_url: string;++ publication_state: "published";++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ };++ /**++ * Space slug.++ */++ space: string;++ /**++ * Page slug inside the space.++ */++ slug: string;++ /**++ * Effective visibility (the page's, narrowed by its space's).++ */++ visibility: "public" | "members" | "private";++}++++/** [email protected] (owner: wiki) */++/**++ * 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).++ */++export interface WikiPageUpdatedPayload {++ canonical_url: string;++ publication_state: "published";++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ };++ /**++ * Space slug.++ */++ space: string;++ /**++ * Page slug inside the space.++ */++ slug: string;++ /**++ * Effective visibility (the page's, narrowed by its space's).++ */++ visibility: "public" | "members" | "private";++}++++/** [email protected] (owner: wiki) */++/**++ * 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).++ */++export interface WikiPageUnpublishedPayload {++ /**++ * null: the event carries a tombstone, which has no URL.++ */++ canonical_url: string | null;++ publication_state: "unpublished";++ /**++ * The last gate decision, or null when there is none.++ */++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ } | null;++ /**++ * Space slug.++ */++ space: string;++ /**++ * Page slug inside the space.++ */++ slug: string;++ /**++ * Effective visibility (the page's, narrowed by its space's).++ */++ visibility: "public" | "members" | "private";++}++++/** [email protected] (owner: wiki) */++/**++ * 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).++ */++export interface WikiPageDeletedPayload {++ /**++ * null: the event carries a tombstone, which has no URL.++ */++ canonical_url: string | null;++ publication_state: "deleted";++ /**++ * The last gate decision, or null when there is none.++ */++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ } | null;++ /**++ * Space slug.++ */++ space: string;++ /**++ * Page slug inside the space.++ */++ slug: string;++ /**++ * Effective visibility (the page's, narrowed by its space's).++ */++ visibility: "public" | "members" | "private";++}++++/** [email protected] (owner: wiki) */++/**++ * 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.++ */++export type WikiIndexDocumentUpsertedPayload = IndexDocument & {++ owner: "wiki";++ type: "page";++ id?: string;++ deleted: false;++};++++/** [email protected] (owner: wiki) */++/**++ * 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.++ */++export interface WikiIndexDocumentDeletedPayload {++ type: "page";++ id: string;++ /**++ * Index revision of the tombstone; wins over any document at the same or an older revision.++ */++ revision: number;++}++++/** [email protected] (owner: blog) */++/**++ * 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.++ */++export interface BlogPostCreatedPayload {++ blog: {++ id: string;++ handle: string;++ };++ state: "draft";++ visibility: "public" | "unlisted" | "members" | "private";++ /**++ * openvibe-publishing authorship mode (not Search's vocabulary).++ */++ authorship: "human" | "ai" | "hybrid" | "imported";++ /**++ * The OpenVibe.AI workflow and run behind ai and hybrid posts; absent for human posts.++ */++ workflow?: {++ id: string;++ runId: string;++ version?: number | string;++ model?: string;++ };++}++++/** [email protected] (owner: blog) */++/**++ * 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).++ */++export interface BlogPostPublishedPayload {++ canonical_url: string;++ publication_state: "published";++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ };++ blog: {++ id: string;++ handle: string;++ };++ /**++ * The post's own visibility (members = entitlement-gated).++ */++ visibility: "public" | "unlisted" | "members" | "private";++}++++/** [email protected] (owner: blog) */++/**++ * 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).++ */++export interface BlogPostUpdatedPayload {++ canonical_url: string;++ publication_state: "published";++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ };++ blog: {++ id: string;++ handle: string;++ };++ /**++ * The post's own visibility (members = entitlement-gated).++ */++ visibility: "public" | "unlisted" | "members" | "private";++}++++/** [email protected] (owner: blog) */++/**++ * 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).++ */++export interface BlogPostUnpublishedPayload {++ /**++ * null: the event carries a tombstone, which has no URL.++ */++ canonical_url: string | null;++ publication_state: "unpublished";++ /**++ * The last gate decision, or null when there is none.++ */++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ } | null;++ blog: {++ id: string;++ handle: string;++ };++ /**++ * The post's own visibility (members = entitlement-gated).++ */++ visibility: "public" | "unlisted" | "members" | "private";++}++++/** [email protected] (owner: blog) */++/**++ * 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).++ */++export interface BlogPostDeletedPayload {++ /**++ * null: the event carries a tombstone, which has no URL.++ */++ canonical_url: string | null;++ publication_state: "deleted";++ /**++ * The last gate decision, or null when there is none.++ */++ indexability: {++ decision: "index" | "noindex";++ /**++ * @maxItems 20++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ];++ } | null;++ blog: {++ id: string;++ handle: string;++ };++ /**++ * The post's own visibility (members = entitlement-gated).++ */++ visibility: "public" | "unlisted" | "members" | "private";++}++++/** [email protected] (owner: blog) */++/**++ * 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.++ */++export interface BlogScheduleFailedPayload {++ job_id: string;++ action: "publish" | "unpublish";++ /**++ * The revision a publish was to make live; null for an unpublish.++ */++ revision: number | null;++ /**++ * When the job was last due.++ */++ run_at: string;++ attempts: number;++ /**++ * The last error.++ */++ error: string | null;++ /**++ * null when the post no longer exists.++ */++ blog_id: string | null;++}++++/** [email protected] (owner: blog) */++/**++ * 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.++ */++export type BlogIndexDocumentUpsertedPayload = IndexDocument & {++ owner: "blog";++ type: "post";++ id?: string;++ deleted: false;++};++++/** [email protected] (owner: blog) */++/**++ * 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.++ */++export interface BlogIndexDocumentDeletedPayload {++ type: "post";++ id: string;++ /**++ * Index revision of the tombstone; wins over any document at the same or an older revision.++ */++ revision: number;++}++++/** [email protected] (owner: ai) */++/**++ * 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.++ */++export interface AiRunRequest {++ /**++ * Workflow key, <namespace>.<name> (live.chat_reply, news.summarize_story, network.site_copy…).++ */++ workflow: string;++ /**++ * Pin a workflow version (active or deprecated). Omitted: the newest active version.++ */++ version?: number;++ /**++ * Workflow input; validated against the workflow version's input_schema. Default {}.++ */++ input?: {};++ target?: EntityRef;++ attribution?: EntityRef;++ on_behalf_of?: SubjectRef;++ /**++ * 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.++ */++ idempotency_key?: string;++ options?: {++ /**++ * false skips the scoped cache lookup and store.++ */++ cache?: boolean;++ /**++ * Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on.++ */++ debug?: boolean;++ };++}++++/** [email protected] (owner: ai) */++/**++ * 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.++ */++export interface AiRun {++ id: string;++ /**++ * Terminal: succeeded, failed, cancelled, cached (a scoped cache hit, no provider call; provenance.cached_from names the run it reused).++ */++ status: "queued" | "running" | "succeeded" | "failed" | "cancelled" | "cached";++ workflow: {++ key: string;++ version: number;++ };++ /**++ * The prompt template of the workflow's llm step; null for workflows without one.++ */++ template: {++ key: string;++ version: number | null;++ } | null;++ /**++ * Provider route; version null while the route is unresolved or disabled.++ */++ route: {++ key: string;++ version: number | null;++ } | null;++ requester: SubjectRef;++ on_behalf_of: SubjectRef | null;++ attribution: EntityRef | null;++ /**++ * The requester's service id when the requester is a service.++ */++ source_service: string | null;++ target: EntityRef | null;++ /**++ * The retained input (see the description).++ */++ input: {};++ /**++ * Workflow output, or null.++ */++ output: {} | null;++ error: {++ /**++ * run.cancelled, run.interrupted, run.internal, output.invalid, output.empty, provider.unavailable, route.unavailable, source.unavailable, media.unresolvable…++ */++ code: string;++ detail: string | null;++ } | null;++ /**++ * Produced by the stub provider; never served to production callers and never cached.++ */++ synthetic: boolean;++ provenance: {++ origin: "ai";++ workflow: string;++ workflow_version: number;++ template_version: number | null;++ route: string | null;++ route_version: number | null;++ provider: string | null;++ model: string | null;++ /**++ * The primary provider was degraded and the run fell back (ADR-015: fallbacks are recorded).++ */++ fallback_used: boolean;++ run_id: string;++ cached_from: string | null;++ synthetic: boolean;++ };++ usage: {++ tokens_in: number;++ tokens_out: number;++ cost_usd: number;++ /**++ * Provider calls made (skipped routes not counted).++ */++ attempts: number;++ };++ /**++ * Citations stored for the run (GET /api/v1/runs/:id/citations): the sources the workflow was given, and any the requester attached later.++ */++ citations_count: number;++ retry_of: string | null;++ idempotency_key: string | null;++ trace_id: string | null;++ created_at: string;++ started_at: string | null;++ finished_at: string | null;++}++++/** [email protected] (owner: ai) */++/**++ * 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.++ */++export interface AiRunQueuedPayload {++ run_id: string;++ status: "queued";++ workflow: {++ key: string;++ version: number;++ };++ requester: SubjectRef;++ on_behalf_of: SubjectRef | null;++ target: EntityRef | null;++ attribution: EntityRef | null;++ /**++ * Provider that produced the output; null before a provider call and for cached runs.++ */++ provider: string | null;++ model: string | null;++ fallback_used: boolean;++ /**++ * Stub provider output.++ */++ synthetic: boolean;++ cached_from: null;++ usage: {++ tokens_in: number;++ tokens_out: number;++ cost_usd: number;++ attempts: number;++ };++ error: null;++ citations_count: number;++ retry_of: string | null;++ created_at: string;++ finished_at: null;++}++++/** [email protected] (owner: ai) */++/**++ * 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.++ */++export interface AiRunSucceededPayload {++ run_id: string;++ status: "succeeded";++ workflow: {++ key: string;++ version: number;++ };++ requester: SubjectRef;++ on_behalf_of: SubjectRef | null;++ target: EntityRef | null;++ attribution: EntityRef | null;++ /**++ * Provider that produced the output; null before a provider call and for cached runs.++ */++ provider: string | null;++ model: string | null;++ fallback_used: boolean;++ /**++ * Stub provider output.++ */++ synthetic: boolean;++ cached_from: null;++ usage: {++ tokens_in: number;++ tokens_out: number;++ cost_usd: number;++ attempts: number;++ };++ error: null;++ citations_count: number;++ retry_of: string | null;++ created_at: string;++ finished_at: string;++}++++/** [email protected] (owner: ai) */++/**++ * 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.++ */++export interface AiRunFailedPayload {++ run_id: string;++ status: "failed";++ workflow: {++ key: string;++ version: number;++ };++ requester: SubjectRef;++ on_behalf_of: SubjectRef | null;++ target: EntityRef | null;++ attribution: EntityRef | null;++ /**++ * Provider that produced the output; null before a provider call and for cached runs.++ */++ provider: string | null;++ model: string | null;++ fallback_used: boolean;++ /**++ * Stub provider output.++ */++ synthetic: boolean;++ cached_from: null;++ usage: {++ tokens_in: number;++ tokens_out: number;++ cost_usd: number;++ attempts: number;++ };++ error: {++ code: string;++ detail: string | null;++ };++ citations_count: number;++ retry_of: string | null;++ created_at: string;++ finished_at: string;++}++++/** [email protected] (owner: ai) */++/**++ * 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.++ */++export interface AiRunCachedPayload {++ run_id: string;++ status: "cached";++ workflow: {++ key: string;++ version: number;++ };++ requester: SubjectRef;++ on_behalf_of: SubjectRef | null;++ target: EntityRef | null;++ attribution: EntityRef | null;++ /**++ * Provider that produced the output; null before a provider call and for cached runs.++ */++ provider: string | null;++ model: string | null;++ fallback_used: boolean;++ /**++ * Stub provider output.++ */++ synthetic: boolean;++ cached_from: string;++ usage: {++ tokens_in: number;++ tokens_out: number;++ cost_usd: number;++ attempts: number;++ };++ error: null;++ citations_count: number;++ retry_of: string | null;++ created_at: string;++ finished_at: string;++}++++/** [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.++ */++export interface TipsInteractionReadyPayload {++ interaction_id: string;++ creator: SubjectRef;++ /**++ * The supporter, null when external, anonymous or unmapped.++ */++ supporter: SubjectRef | null;++ /**++ * Display name at the time.++ */++ supporter_name: string | null;++ kind: "tip" | "paid_message" | "tts" | "media_request";++ /**++ * Integer amount in currency minor units (Vibes bits).++ */++ amount: number;++ /**++ * vibes-bits today.++ */++ currency: string;++ settlement: "billing" | "external" | "simulated" | "imported";++ payment_state: "pending" | "settled" | "reversed" | "failed";++ delivery_state: "awaiting_payment" | "queued" | "delivered" | "failed" | "cancelled";++ /**++ * The Billing transaction (txn_…) that settled it.++ */++ billing_txn_id: string | null;++ /**++ * Simulations never emit events, so false in practice.++ */++ test: boolean;++}++++/** [email protected] (owner: tips) */++/**++ * tips.interaction.failed v1 (OpenVibe.Tips server/domain/interactions.js recomputeDelivery). A delivery effect gave up after its retries and none is still queued; the payment state is untouched. Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.++ */++export interface TipsInteractionFailedPayload {++ interaction_id: string;++ creator: SubjectRef;++ /**++ * The supporter, null when external, anonymous or unmapped.++ */++ supporter: SubjectRef | null;++ /**++ * Display name at the time.++ */++ supporter_name: string | null;++ kind: "tip" | "paid_message" | "tts" | "media_request";++ /**++ * Integer amount in currency minor units (Vibes bits).++ */++ amount: number;++ /**++ * vibes-bits today.++ */++ currency: string;++ settlement: "billing" | "external" | "simulated" | "imported";++ payment_state: "pending" | "settled" | "reversed" | "failed";++ delivery_state: "awaiting_payment" | "queued" | "delivered" | "failed" | "cancelled";++ /**++ * The Billing transaction (txn_…) that settled it.++ */++ billing_txn_id: string | null;++ /**++ * Simulations never emit events, so false in practice.++ */++ test: boolean;++ /**++ * @minItems 1++ */++ failed_effects: [++ {++ effect: "chat_line" | "paid_message" | "tts" | "media_request" | "overlay_alert";++ adapter: string;++ last_error: string | null;++ },++ ...{++ effect: "chat_line" | "paid_message" | "tts" | "media_request" | "overlay_alert";++ adapter: string;++ last_error: string | null;++ }[]++ ];++}++++/** [email protected] (owner: tips) */++/**++ * tips.interaction.cancelled v1 (OpenVibe.Tips server/domain/interactions.js failPayment, cancelQueued). Undelivered effects were cancelled: the payment failed before settling (reason payment_failed, with the Billing error code), Billing reversed it (payment_reversed) or an effect came up while the payment was no longer settled (payment_not_settled). Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.++ */++export interface TipsInteractionCancelledPayload {++ interaction_id: string;++ creator: SubjectRef;++ /**++ * The supporter, null when external, anonymous or unmapped.++ */++ supporter: SubjectRef | null;++ /**++ * Display name at the time.++ */++ supporter_name: string | null;++ kind: "tip" | "paid_message" | "tts" | "media_request";++ /**++ * Integer amount in currency minor units (Vibes bits).++ */++ amount: number;++ /**++ * vibes-bits today.++ */++ currency: string;++ settlement: "billing" | "external" | "simulated" | "imported";++ payment_state: "pending" | "settled" | "reversed" | "failed";++ delivery_state: "awaiting_payment" | "queued" | "delivered" | "failed" | "cancelled";++ /**++ * The Billing transaction (txn_…) that settled it.++ */++ billing_txn_id: string | null;++ /**++ * Simulations never emit events, so false in practice.++ */++ test: boolean;++ reason: "payment_failed" | "payment_reversed" | "payment_not_settled";++ /**++ * Only with reason payment_failed: the Billing problem code (billing.insufficient_funds, billing.funding_reversed, billing.refused…).++ */++ code?: string;++}++++/** [email protected] (owner: tips) */++/**++ * tips.goal.updated v1 (OpenVibe.Tips server/domain/goals.js changed). A goal was created, edited or closed, or its settled total moved (a contribution, the contribution that reached it, a reversal). Envelope: subject { type: goal, id: tgoal_…, revision }, visibility internal, priority important, actor service:tips.++ */++export interface TipsGoalUpdatedPayload {++ goal_id: string;++ creator: SubjectRef;++ title: string;++ target_amount: number;++ /**++ * Settled total including any amount carried over from Live.++ */++ current_amount: number;++ currency: string;++ status: "active" | "closed";++ reached: boolean;++ reason: "created" | "updated" | "closed" | "contribution" | "reached" | "reversal";++ /**++ * The interaction behind a contribution, reached or reversal change; null otherwise.++ */++ interaction_id: string | null;++}++++/** [email protected] (owner: tips) */++/**++ * tips.overlay.delivered v1 (OpenVibe.Tips server/domain/overlays.js markDelivered). An overlay delivery (alert or goal update) reached at least one connected overlay, first time only. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.++ */++export interface TipsOverlayDeliveredPayload {++ delivery_id: string;++ creator: SubjectRef;++ kind: "alert" | "goal";++ interaction_id: string | null;++ goal_id: string | null;++}++++/** [email protected] (owner: tips) */++/**++ * tips.overlay.failed v1 (OpenVibe.Tips server/domain/overlays.js sweepFailed). No overlay showed the delivery within the delivery window. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.++ */++export interface TipsOverlayFailedPayload {++ delivery_id: string;++ creator: SubjectRef;++ kind: "alert" | "goal";++ interaction_id: string | null;++ goal_id: string | null;++ reason: string;++}++++/** [email protected] (owner: vip) */++/**++ * vip.plan.published v1 (OpenVibe.VIP server/domain/plans.js emitPublished). A plan version became what new members buy: the plan was published, or a published plan was edited (a new immutable version). Carries the version's full terms, never a price (Billing prices and charges). Envelope: subject { type: plan, id: vpl_…, revision: <version> }, visibility public, priority important, actor service:vip.++ */++export interface VipPlanPublishedPayload {++ plan_id: string;++ slug: string;++ version: number;++ version_id: string;++ creator: {++ /**++ * vcr_… or network.++ */++ id: string;++ kind: "creator" | "network";++ /**++ * null for the network creator.++ */++ subject: SubjectRef | null;++ username: string | null;++ display_name: string | null;++ bio: string | null;++ status: "active" | "suspended";++ };++ /**++ * The Billing product that sells it; null for a plan Billing does not sell (network plans).++ */++ billing_kind: "channel_subscription" | null;++ terms: {++ name: string;++ description: string | null;++ /**++ * @maxItems 20++ */++ benefits:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | [++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string,++ string++ ]++ | null;++ perks: {++ key: string;++ name: string;++ kind: "badge" | "emote" | "gated_content" | "room" | "role" | "other";++ scope: "creator" | "network";++ }[];++ billing_kind: "channel_subscription" | null;++ /**++ * Never an amount: a note that Billing sets and charges the price, or null.++ */++ price: string | null;++ /**++ * sha256 prefix of name, description, benefits, perks and billing_kind.++ */++ digest: string;++ };++ published_at: string;++}++++/** [email protected] (owner: vip) */++/**++ * vip.membership.changed v1 (OpenVibe.VIP server/domain/entitlements.js emitChanged). A member's standing with a creator changed as projected from Billing (granted, renewed, cancel scheduled, expired, refunded…): active, expires_at or cancel_at_period_end differ from before, or a first look found an active membership. Names the plan version the membership was bought under. Envelope: subject { type: membership, id: <member usr_…>:<creator usr_…> }, visibility internal, priority important, actor service:vip.++ */++export interface VipMembershipChangedPayload {++ member: SubjectRef;++ creator: SubjectRef;++ /**++ * Billing entitlement kind; channel_subscription today.++ */++ kind: string;++ active: boolean;++ expires_at: string | null;++ cancel_at_period_end: boolean;++ /**++ * The projection before the change; null on first sight.++ */++ previous: {++ active: boolean;++ expires_at: string | null;++ cancel_at_period_end: boolean;++ } | null;++ /**++ * Billing's reason (granted, renewed, refund, expired…) or VIP's (checked, cancel_scheduled).++ */++ reason: string | null;++ source: "event" | "billing_check" | "import";++ /**++ * The Billing event (evt_…) behind the change, when it came from one.++ */++ billing_event_id: string | null;++ membership_id: string | null;++ plan_id: string | null;++ plan_version_id: string | null;++ plan_version: number | null;++ /**++ * The Billing subscription (sub_…).++ */++ subscription_id: string | null;++}++++/** [email protected] (owner: billing) */++/**++ * billing.transaction.settled v1 (OpenVibe.Billing server/ops/purchases.js summary(); emitted by purchases, transfers, subscriptions, cashouts recycle and admin adjustments). A journal transaction was posted. Envelope: subject { type: transaction, id: txn_… }, visibility internal, priority important, actor service:billing.++ */++export interface BillingTransactionSettledPayload {++ transaction_id: string;++ type:++ | "purchase"++ | "donation"++ | "subscription"++ | "subscription_share"++ | "cashout_request"++ | "cashout_paid"++ | "cashout_denied"++ | "recycle"++ | "refund"++ | "chargeback"++ | "adjustment"++ | "import";++ /**++ * Test money: never counted.++ */++ test: boolean;++ /**++ * Subject id (usr_…) value moved from, null for provider money.++ */++ from_subject: string | null;++ /**++ * Subject id (usr_…) value moved to.++ */++ to_subject: string | null;++ /**++ * stripe, paypal, powerchat…; null for credit movements.++ */++ provider: string | null;++ /**++ * Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type.++ */++ metadata: {};++}++++/** [email protected] (owner: billing) */++/**++ * billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.++ */++export interface BillingTransactionReversedPayload {++ transaction_id: string;++ type:++ | "purchase"++ | "donation"++ | "subscription"++ | "subscription_share"++ | "cashout_request"++ | "cashout_paid"++ | "cashout_denied"++ | "recycle"++ | "refund"++ | "chargeback"++ | "adjustment"++ | "import";++ /**++ * Test money: never counted.++ */++ test: boolean;++ /**++ * Subject id (usr_…) value moved from, null for provider money.++ */++ from_subject: string | null;++ /**++ * Subject id (usr_…) value moved to.++ */++ to_subject: string | null;++ /**++ * stripe, paypal, powerchat…; null for credit movements.++ */++ provider: string | null;++ /**++ * Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type.++ */++ metadata: {};++ /**++ * The transaction this one reverses.++ */++ reverses_txn: string;++ /**++ * Provider reversals only: required when Billing kept value it could not recover (staff review).++ */++ review?: "required" | null;++ /**++ * Provider reversals only: subscription periods revoked.++ */++ entitlements_revoked?: number;++}++++/** [email protected] (owner: billing) */++/**++ * billing.entitlement.changed v1 (OpenVibe.Billing server/ops/subscriptions.js pay, setStatus, revokeForTxn; entitlement()). A subscriber's channel-subscription entitlement to a streamer changed: a period was granted or renewed, the subscription ended or expired, or a reversed payment revoked a period. The payload is the entitlement as of now. Envelope: subject { type: entitlement, id: <subscriber usr_…>:channel_subscription:<streamer usr_…> }, visibility internal, priority important, actor service:billing.++ */++export interface BillingEntitlementChangedPayload {++ subject: SubjectRef;++ streamer: SubjectRef;++ kind: "channel_subscription";++ active: boolean;++ expires_at: string | null;++ subscription: {++ id: string;++ status: "active" | "canceled" | "expired";++ auto_renew: boolean;++ cancel_at_period_end: boolean;++ provider: string;++ } | null;++ /**++ * granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback.++ */++ reason: string;++ /**++ * granted/renewed: the transaction that paid the period.++ */++ transaction_id?: string;++ /**++ * refund/chargeback: the period the reversed payment had granted.++ */++ revoked_period?: {++ starts_at: string;++ ends_at: string;++ };++}++++/** [email protected] (owner: billing) */++/**++ * billing.subscription.canceled v1 (OpenVibe.Billing server/ops/subscriptions.js cancel). A subscription was set to cancel at period end (for Stripe, after Stripe accepted it); the paid period still runs. Envelope: subject { type: subscription, id: sub_… }, visibility internal, priority important, actor service:billing.++ */++export interface BillingSubscriptionCanceledPayload {++ subscription: {++ id: string;++ subscriber: SubjectRef;++ streamer: SubjectRef;++ tier: number;++ provider: string;++ provider_ref: string | null;++ route: string | null;++ status: "active" | "canceled" | "expired";++ auto_renew: boolean;++ cancel_at_period_end: boolean;++ price_cents: number;++ current_period_end: string | null;++ created_at: string;++ updated_at: string;++ };++ provider_sync: "not_needed" | "stripe_cancel_at_period_end";++ /**++ * Who asked: the calling principal and the person it acted for.++ */++ actor: {++ /**++ * Token sub of the caller (svc:…, usr_…).++ */++ principal: string | null;++ request_id: string | null;++ on_behalf_of?: SubjectRef;++ };++}++++/** [email protected] (owner: billing) */++/**++ * billing.cashout.requested v1 (OpenVibe.Billing server/ops/cashouts.js request). A creator asked to cash out payable bits; they are held in escrow. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.++ */++export interface BillingCashoutRequestedPayload {++ cashout: {++ id: string;++ subject: SubjectRef;++ amount_bits: number;++ value_cents: number;++ status: "requested";++ /**++ * { type, address } as the creator entered it (e.g. a PayPal email).++ */++ payout_method: {};++ escrow_until: string;++ request_txn: string;++ settle_txn: string | null;++ payout_provider: string | null;++ payout_reference: string | null;++ reason: string | null;++ created_at: string;++ updated_at: string;++ };++ /**++ * The cashout_request transaction.++ */++ transaction_id: string;++}++++/** [email protected] (owner: billing) */++/**++ * billing.cashout.paid v1 (OpenVibe.Billing server/ops/cashouts.js approve). Staff paid a cash-out out through a provider. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.++ */++export interface BillingCashoutPaidPayload {++ cashout: {++ id: string;++ subject: SubjectRef;++ amount_bits: number;++ value_cents: number;++ status: "paid";++ /**++ * { type, address } as the creator entered it (e.g. a PayPal email).++ */++ payout_method: {};++ escrow_until: string;++ request_txn: string;++ settle_txn: string | null;++ payout_provider: string | null;++ payout_reference: string | null;++ reason: string | null;++ created_at: string;++ updated_at: string;++ };++ /**++ * The cashout_paid transaction.++ */++ transaction_id: string;++}++++/** [email protected] (owner: billing) */++/**++ * billing.cashout.denied v1 (OpenVibe.Billing server/ops/cashouts.js deny). Staff denied a cash-out; the escrowed bits went back (a billing.transaction.reversed follows in the same transaction). Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.++ */++export interface BillingCashoutDeniedPayload {++ cashout: {++ id: string;++ subject: SubjectRef;++ amount_bits: number;++ value_cents: number;++ status: "denied";++ /**++ * { type, address } as the creator entered it (e.g. a PayPal email).++ */++ payout_method: {};++ escrow_until: string;++ request_txn: string;++ settle_txn: string | null;++ payout_provider: string | null;++ payout_reference: string | null;++ reason: string | null;++ created_at: string;++ updated_at: string;++ };++ /**++ * The cashout_denied transaction.++ */++ transaction_id: string;++}++++/** [email protected] (owner: billing) */++/**++ * billing.staff.action v1 (OpenVibe.Billing server/console/audit.js record). A staff member completed an action in the Billing console (only outcome done is announced; refused actions stay in staff_audit). Holds no secrets: codes, payout reference, counts. Envelope: subject { type: staff_action, id: sa_… }, visibility internal, priority important, actor the staff user ({ type: user, id: usr_… }).++ */++export interface BillingStaffActionPayload {++ audit_id: string;++ action: string;++ outcome: "done";++ target: {++ type: string;++ id: string;++ } | null;++ reason: string | null;++ request_id: string | null;++ /**++ * Small whitelisted detail of the action.++ */++ detail: {};++}++++/** [email protected] (owner: billing) */++/**++ * billing.receipt.external v1 (OpenVibe.Billing server/ops/external.js record). An EXTERNAL receipt (ADR-012): a tip paid on a creator's own provider account (PowerChat route direct). The money never touches OpenVibe (no journal entry); Billing announces it once per payment, only when BILLING_AUTHORITY=billing and the receiving account is mapped to a creator. OpenVibe.Tips celebrates it (chat line, overlay alert, goal). Envelope: subject { type: provider_receipt, id: <provider>:<payment id> }, visibility internal, priority important, actor service:billing.++ */++export interface BillingReceiptExternalPayload {++ classification: "EXTERNAL";++ provider: string;++ /**++ * <provider>:<payment id>, e.g. powerchat:…++ */++ receipt_ref: string;++ /**++ * The provider's id of the payment, stable across redeliveries.++ */++ provider_event_id: string;++ /**++ * The webhook delivery that carried it.++ */++ delivery_id: string;++ streamer: SubjectRef;++ receiving_account: {++ provider: string;++ id: string | null;++ username: string | null;++ };++ amount_cents: number;++ currency: "usd-cents";++ /**++ * Billing's value of the money in bits.++ */++ value_bits: number;++ /**++ * null when anonymous.++ */++ donor_name: string | null;++ anonymous: boolean;++ message: string | null;++ app_ref: string | null;++ app_purpose: string | null;++ /**++ * The provider's timestamp as it sent it.++ */++ occurred_at: string | null;++ test: boolean;++ /**++ * Rate snapshot.++ */++ rates: {++ bits_per_usd: number;++ };++}++++/** [email protected] (owner: deals) */++/**++ * deals.watch.matched v1 (OpenVibe.Deals server/domain/watches.js onObservation). A person's keyword, product or price-below watch matched a fresh observation of an active offer, inside the transaction that recorded the observation. At most one event per (watch, observation); a keyword/product watch notifies once per offer, a price-below watch again only for a lower price. Saved searches (kind search) never notify. Envelope: subject { type: watch, id: <watch_id> }, visibility internal, priority important, actor service:deals. OpenVibe.Network turns it into a DEAL_WATCH_MATCH notification for payload.recipient (server/notifications/events-consumer.js). A price is the decimal exactly as stated, never inferred; null when the observation stated none.++ */++export interface DealsWatchMatchedPayload {++ watch_id: string;++ /**++ * The watch owner's user subject id: who is notified.++ */++ recipient: string;++ kind: "keyword" | "product" | "price_below";++ /**++ * Keywords of a keyword watch, or of a price-below watch without a product.++ */++ query: string | null;++ /**++ * Deals product id of a product or price-below watch.++ */++ product_id: string | null;++ /**++ * Price-below threshold as entered (decimal text, e.g. 19.99).++ */++ max_price: string | null;++ /**++ * ISO 4217 currency of max_price.++ */++ currency: string | null;++ /**++ * The canonical (root) offer id (dof_…).++ */++ offer_id: string;++ /**++ * Canonical offer page URL on Deals.++ */++ offer_url: string;++ title: string;++ observation: {++ id: string;++ observed_at: string;++ /**++ * Decimal text as stated; null when the observation stated no price.++ */++ price: string | null;++ currency: string | null;++ availability:++ | "in_stock"++ | "out_of_stock"++ | "preorder"++ | "discontinued"++ | "limited"++ | "sold_out"++ | "online_only"++ | "in_store_only"++ | null;++ };++}++++/** [email protected] (owner: trade) */++/**++ * trade.alert.triggered v1 (OpenVibe.Trade server/domain/alerts.js deliver). One delivery of one private alert rule: a threshold crossed by a new observation (trigger kind observation), or a newly seen document of the instrument (filing_type / new_document rules, trigger kind document). Written in the transaction that recorded the observation or document; at most one per (rule, trigger). Envelope: subject { type: user, id: <rule owner usr_…> }, visibility subject, priority important, actor service:trade. Data only, never rendered notification text (ADR-020): OpenVibe.Network renders a TRADE_ALERT notification (server/notifications/events-consumer.js). Information only, not investment advice.++ */++export interface TradeAlertTriggeredPayload {++ delivery_id: string;++ rule: {++ id: string;++ kind: "threshold" | "filing_type" | "new_document";++ status: "active" | "deleted";++ instrument: {++ id: string;++ symbol?: string;++ name?: string;++ };++ /**++ * Threshold rules: e.g. price.close, volume, us-gaap:Revenues.++ */++ metric: string | null;++ operator: "above" | "below" | null;++ /**++ * Decimal text as entered.++ */++ threshold: string | null;++ unit: string | null;++ currency: string | null;++ /**++ * filing_type rules: the 1–10 form types that trigger it (10-K, 8-K…).++ */++ form_types: string[] | null;++ /**++ * Threshold rules: armed before this delivery (always true when it fired); null for document rules.++ */++ armed: boolean | null;++ created_at: string;++ updated_at: string;++ };++ instrument: {++ id: string;++ symbol: string;++ name: string;++ /**++ * Instrument page on Trade.++ */++ url: string;++ };++ trigger:++ | {++ kind: "observation";++ id: string;++ metric: string;++ /**++ * The decimal exactly as the source stated it.++ */++ value: string;++ unit: string;++ currency: string | null;++ observed_at: string;++ retrieved_at: string;++ /**++ * OpenVibe.Sources source key.++ */++ source_key: string;++ source_url: string | null;++ }++ | {++ kind: "document";++ id: string;++ form_type: string | null;++ title: string | null;++ url: string | null;++ /**++ * The source's own date; null if it gave none.++ */++ published_at: string | null;++ retrieved_at: string;++ source_key: string;++ };++ /**++ * Currently "Information only — not investment advice; no trading here."++ */++ disclaimer: string;++}++++/** [email protected] (owner: tools) */++/**++ * tools.job.created v1 (OpenVibe.Tools apps/_shared/jobs, planned). A job was accepted and queued (POST /api/v1/jobs, or a retry of a failed job, which carries retry_of). An Idempotency-Key replay creates no job and no event. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor the owner when it is a subject (user, service, app or mod), else service:tools.++ */++export interface ToolsJobCreatedPayload {++ job_id: string;++ /**++ * The Tools satellite that runs the job (img, audio, docs…).++ */++ service: string;++ /**++ * Job type, e.g. img.process.++ */++ type: string;++ type_version: number;++ state: "queued";++ /**++ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.++ */++ owner: SubjectRef | null;++ attempts: number;++ max_attempts: number;++ created_at: string;++ /**++ * The failed job this one retries (POST /api/v1/jobs/:id/retry).++ */++ retry_of: string | null;++}++++/** [email protected] (owner: tools) */++/**++ * tools.job.started v1 (OpenVibe.Tools apps/_shared/jobs, planned). A worker claimed the job and it is running (attempts counts this attempt). A job requeued after a restart starts again and is announced again. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:tools.++ */++export interface ToolsJobStartedPayload {++ job_id: string;++ /**++ * The Tools satellite that runs the job (img, audio, docs…).++ */++ service: string;++ /**++ * Job type, e.g. img.process.++ */++ type: string;++ type_version: number;++ state: "running";++ /**++ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.++ */++ owner: SubjectRef | null;++ attempts: number;++ max_attempts: number;++ created_at: string;++ /**++ * The failed job this one retries (POST /api/v1/jobs/:id/retry).++ */++ retry_of: string | null;++ started_at: string;++}++++/** [email protected] (owner: tools) */++/**++ * tools.job.succeeded v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job finished and its result files are stored (in Media when TOOLS_JOB_RESULTS=media). Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.++ */++export interface ToolsJobSucceededPayload {++ job_id: string;++ /**++ * The Tools satellite that runs the job (img, audio, docs…).++ */++ service: string;++ /**++ * Job type, e.g. img.process.++ */++ type: string;++ type_version: number;++ state: "succeeded";++ /**++ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.++ */++ owner: SubjectRef | null;++ attempts: number;++ max_attempts: number;++ created_at: string;++ /**++ * The failed job this one retries (POST /api/v1/jobs/:id/retry).++ */++ retry_of: string | null;++ started_at: string;++ finished_at: string;++ /**++ * When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref).++ */++ expires_at: string | null;++ /**++ * Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).++ */++ result: {++ /**++ * @maxItems 100++ */++ files: {++ /**++ * n in /api/v1/jobs/:id/files/:n.++ */++ index: number;++ mime: string;++ size: number;++ sha256: string;++ /**++ * media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires.++ */++ storage: "local" | "media";++ /**++ * { media_id, role: output } when storage is media.++ */++ media: MediaRef | null;++ }[];++ };++}++++/** [email protected] (owner: tools) */++/**++ * tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.++ */++export interface ToolsJobFailedPayload {++ job_id: string;++ /**++ * The Tools satellite that runs the job (img, audio, docs…).++ */++ service: string;++ /**++ * Job type, e.g. img.process.++ */++ type: string;++ type_version: number;++ state: "failed";++ /**++ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.++ */++ owner: SubjectRef | null;++ attempts: number;++ max_attempts: number;++ created_at: string;++ /**++ * The failed job this one retries (POST /api/v1/jobs/:id/retry).++ */++ retry_of: string | null;++ /**++ * null when the job failed before it ever ran.++ */++ started_at: string | null;++ finished_at: string;++ /**++ * When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref).++ */++ expires_at: string | null;++ /**++ * The job's problem (errors.problem@1) without request or trace ids.++ */++ error: {++ status: number;++ /**++ * tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…).++ */++ code: string;++ /**++ * Human-readable reason; server paths are scrubbed.++ */++ detail: string;++ };++ retryable: boolean;++}@@@ -5,6 +5,8 @@"status": "active","visibility": "first-party","description": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",++ "inputSchema": "ai.run-request@1",++ "outputSchema": "ai.run@1","permissions": ["ai:run"],@@@ -13,7 +15,12 @@"owner"],"quotaClass": "ai-run",-- "events": [],++ "events": [++ "ai.run.queued",++ "ai.run.succeeded",++ "ai.run.failed",++ "ai.run.cached"++ ],"implementedBy": ["POST /api/v1/runs","POST /api/v1/runs/:id/cancel",@@@ -5,6 +5,7 @@"status": "active","visibility": "first-party","description": "Read the caller's own runs, their citations and request-log metadata (hashes, tokens, latency; never raw prompts).",++ "outputSchema": "ai.run@1","permissions": ["ai:read"],@@@ -14,7 +14,8 @@"quotaClass": "internal-write","events": ["chat.message.created",-- "chat.moderation.action"++ "chat.moderation.action",++ "chat.message.deleted"],"implementedBy": ["POST /internal/live/calls"@@@ -12,7 +12,12 @@"owner"],"quotaClass": "tools-job",-- "events": [],++ "events": [++ "tools.job.created",++ "tools.job.started",++ "tools.job.succeeded",++ "tools.job.failed"++ ],"implementedBy": ["POST /api/v1/jobs (Tools satellites: img, audio, docs)"]@@@ -1,5 +1,5 @@{-- "version": "0.1.0",++ "version": "0.2.0","contractRanges": {"openvibe-contracts": ">=0.6.0 <1.0.0"},@@@ -20,8 +20,13 @@"ai.provider.manage","ai.usage.read"],-- "eventsProduced": [],++ "eventsProduced": [++ "ai.run.queued",++ "ai.run.succeeded",++ "ai.run.failed",++ "ai.run.cached"++ ],"eventsConsumed": [],"namespacesOwned": [],-- "notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached events follow once the Events client is wired in."++ "notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached have payload contracts (v0.30.0, status planned): AI publishes them once its Events client is wired in; it publishes none today. ai.run.create takes ai.run-request@1 and returns ai.run@1."}@@@ -1,5 +1,5 @@{-- "version": "0.2.0",++ "version": "0.3.0","contractRanges": {"openvibe-contracts": ">=0.5.1 <1.0.0"},@@@ -31,7 +31,8 @@"billing.cashout.requested","billing.cashout.paid","billing.cashout.denied",-- "billing.staff.action"++ "billing.staff.action",++ "billing.receipt.external"],"eventsConsumed": [],"namespacesOwned": [@@@ -1,5 +1,5 @@{-- "version": "0.2.0",++ "version": "0.3.0","contractRanges": {"openvibe-contracts": ">=0.13.0 <1.0.0"},@@@ -14,6 +14,7 @@],"eventsProduced": ["chat.message.created",++ "chat.message.deleted","chat.dm.created","chat.moderation.action"],@@@ -1,16 +1,16 @@{-- "version": "0.1.0",++ "version": "0.2.0","contractRanges": {-- "openvibe-contracts": "^0.1.0"++ "openvibe-contracts": ">=0.28.0 <1.0.0"},"id": "examples","name": "OpenVibe.Examples",-- "status": "placeholder",++ "status": "alpha","repository": "OpenVibers/OpenVibe.Examples","domains": [],"capabilities": [],"eventsProduced": [],"eventsConsumed": [],"namespacesOwned": [],-- "notes": "Executable public integration examples, tested in CI against the platform. Charter only; no runtime."++ "notes": "Executable public integration examples (developer apps on openvibe-sdk), each with a smoke test that CI runs against openvibe-sdk/testing's mock platform. A repository with CI, not a runtime and not a published package; nothing to deploy or poll."}@@@ -1,5 +1,5 @@{-- "version": "0.1.0",++ "version": "0.2.0","contractRanges": {"openvibe-contracts": ">=0.2.0 <1.0.0"},@@@ -36,7 +36,10 @@"network.credential.revoked","network.grant.changed"],-- "eventsConsumed": [],++ "eventsConsumed": [++ "deals.watch.matched",++ "trade.alert.triggered"++ ],"namespacesOwned": ["identity.*","network.*",@@@ -1,5 +1,5 @@{-- "version": "0.1.0",++ "version": "0.1.1","contractRanges": {"openvibe-contracts": ">=0.19.0 <1.0.0"},@@@ -42,5 +42,5 @@"namespacesOwned": ["reviews.*"],-- "notes": "Entities with aliases and typed links; resolution of OpenVibe.Sources review items by deterministic rules (source binding, URL, GTIN/SKU/MPN) with editor confirmation for name-only and conflicting matches; merges that rewrite nothing and splits that restore attribution exactly, both audited; typed signals with provenance (source item, item revision, retrieval time, licence note) and trust metadata; aggregates computed only from signals present (method reviews-aggregate@1, inputs and exclusions exposed, absent without qualifying signals, a new revision on every change); editor-written or AI-drafted-and-approved summaries whose points cite signals and never carry a rating; source removal withdraws signals, flags citing summaries and prepares a pending revision; corrections queue; Community discussion referenced, not copied. Server-rendered pages useful without JavaScript; AggregateRating/Product/Review JSON-LD only when signals back it; sitemaps, feeds and Search index events from the openvibe-publishing v0.2.0 gate. Not deployed; openvibe.reviews stays on OpenVibe.Sites until launch."++ "notes": "Entities with aliases and typed links; resolution of OpenVibe.Sources review items by deterministic rules (source binding, URL, GTIN/SKU/MPN) with editor confirmation for name-only and conflicting matches; merges that rewrite nothing and splits that restore attribution exactly, both audited; typed signals with provenance (source item, item revision, retrieval time, licence note) and trust metadata; aggregates computed only from signals present (method reviews-aggregate@1, inputs and exclusions exposed, absent without qualifying signals, a new revision on every change); editor-written or AI-drafted-and-approved summaries whose points cite signals and never carry a rating; source removal withdraws signals, flags citing summaries and prepares a pending revision; corrections queue; Community discussion referenced, not copied. Server-rendered pages useful without JavaScript; AggregateRating/Product/Review JSON-LD only when signals back it; sitemaps, feeds and Search index events from the openvibe-publishing v0.2.0 gate. Deployed internally (loopback only), not launched publicly: openvibe.reviews serves the OpenVibe.Sites placeholder until the launch rule holds."}@@@ -1,16 +1,13 @@{-- "version": "0.1.0",-- "contractRanges": {-- "openvibe-contracts": "^0.1.0"-- },++ "version": "0.2.0","id": "sdk","name": "OpenVibe.SDK",-- "status": "placeholder",++ "status": "alpha","repository": "OpenVibers/OpenVibe.SDK","domains": [],"capabilities": [],"eventsProduced": [],"eventsConsumed": [],"namespacesOwned": [],-- "notes": "Supported browser and server clients for the OpenVibe platform. Charter only; no runtime."++ "notes": "Supported browser and server clients for the OpenVibe platform (openvibe-sdk). A released library, not a runtime: tagged releases installed from https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/<tag> (v0.4.0 on 2026-09-23). Nothing to deploy or poll."}@@@ -1,16 +1,13 @@{-- "version": "0.1.0",-- "contractRanges": {-- "openvibe-contracts": "^0.1.0"-- },++ "version": "0.2.0","id": "shared","name": "OpenVibe.Shared",-- "status": "placeholder",++ "status": "alpha","repository": "OpenVibers/OpenVibe.Shared","domains": [],"capabilities": [],"eventsProduced": [],"eventsConsumed": [],"namespacesOwned": [],-- "notes": "Versioned UI, chrome, SEO, legal and release-client packages every OpenVibe site renders. Charter only; no runtime."++ "notes": "Versioned UI, chrome, SEO, legal and release-client packages every OpenVibe site renders (openvibe-shared). A released library, not a runtime: tagged releases installed from https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/<tag> (v1.3.0 on 2026-09-23) and served at /shared/* by each site. Nothing to deploy or poll."}@@@ -1,5 +1,5 @@{-- "version": "0.2.0",++ "version": "0.3.0","contractRanges": {"openvibe-contracts": ">=0.11.0 <1.0.0"},@@@ -42,10 +42,11 @@],"eventsConsumed": ["billing.transaction.settled",-- "billing.transaction.reversed"++ "billing.transaction.reversed",++ "billing.receipt.external"],"namespacesOwned": ["tips.*"],-- "notes": "Creator support (roadmap Wave 9): tip interactions whose payment state mirrors OpenVibe.Billing (keyed by Billing transaction id) and whose delivery state is Tips' own; goals derived from settled interactions; paid messages, TTS and media requests created on settlement; scoped revocable overlay tokens with SSE overlays; simulation mode. Tips never moves money (ADR-012). Alpha: the placeholder on OpenVibe.Sites stays until the launch rule in README.md holds. Proposed manifest; replaces the placeholder in manifests/services/tips.json."++ "notes": "Creator support (roadmap Wave 9): tip interactions whose payment state mirrors OpenVibe.Billing (keyed by Billing transaction id) and whose delivery state is Tips' own; goals derived from settled interactions; paid messages, TTS and media requests created on settlement; scoped revocable overlay tokens with SSE overlays; simulation mode. Tips never moves money (ADR-012). Alpha: deployed internally (loopback only), not launched publicly: openvibe.tips serves the OpenVibe.Sites placeholder until the launch rule in README.md holds."}@@@ -1,5 +1,5 @@{-- "version": "0.2.0",++ "version": "0.3.0","contractRanges": {"openvibe-contracts": ">=0.7.0 <1.0.0"},@@@ -17,10 +17,15 @@"tools.job.read","tools.job.cancel"],-- "eventsProduced": [],++ "eventsProduced": [++ "tools.job.created",++ "tools.job.started",++ "tools.job.succeeded",++ "tools.job.failed"++ ],"eventsConsumed": [],"namespacesOwned": ["tools.*"],-- "notes": "Wave 11: asynchronous jobs on the img, audio and docs satellites (POST/GET/DELETE /api/v1/jobs, SSE events). Job lifecycle is not yet published to OpenVibe.Events, so eventsProduced stays empty. Tools consumes media.object.upload and media.object.read (namespace tools) when TOOLS_JOB_RESULTS=media."++ "notes": "Wave 11: asynchronous jobs on the img, audio and docs satellites (POST/GET/DELETE /api/v1/jobs, SSE events). Job lifecycle events tools.job.created|started|succeeded|failed have payload contracts (v0.30.0); Tools publishes them to OpenVibe.Events next. Tools consumes media.object.upload and media.object.read (namespace tools) when TOOLS_JOB_RESULTS=media."}@@@ -1,5 +1,5 @@{-- "version": "0.2.0",++ "version": "0.2.1","contractRanges": {"openvibe-contracts": ">=0.14.0 <1.0.0"},@@@ -41,5 +41,5 @@"namespacesOwned": ["vip.*"],-- "notes": "Memberships (ADR-012): plans, immutable versioned plan terms, perks, product bindings, member preferences and gated-resource policy. OpenVibe.Billing holds subscriptions, charges, renewals, refunds and the canonical entitlements; VIP keeps a short-lived projection of them (valid_until, authoritative fallback, 'unknown' never authorizes). Alpha: not deployed; openvibe.vip stays on its OpenVibe.Sites placeholder until the launch rule holds."++ "notes": "Memberships (ADR-012): plans, immutable versioned plan terms, perks, product bindings, member preferences and gated-resource policy. OpenVibe.Billing holds subscriptions, charges, renewals, refunds and the canonical entitlements; VIP keeps a short-lived projection of them (valid_until, authoritative fallback, 'unknown' never authorizes). Alpha: deployed internally (loopback only), not launched publicly: openvibe.vip serves the OpenVibe.Sites placeholder until the launch rule holds."}@@@ -1,5 +1,5 @@{-- "version": "0.1.0",++ "version": "0.1.1","contractRanges": {"openvibe-contracts": ">=0.13.0 <1.0.0"},@@@ -38,5 +38,5 @@"namespacesOwned": ["wiki.*"],-- "notes": "Wiki spaces (official and user), page trees with canonical slugs and history-aware redirects, immutable revisions with diff and revert, typed infoboxes, [[internal links]] with red links, citations per revision (Sources items or URLs with retrieval time), Media attachments with an explicit broken state, Community discussion references, watchers notified through wiki.watch.triggered, AI proposals held for a person's approval. Server-rendered public routes useful without JavaScript; sitemaps, Atom/JSON feeds and Search index events only for public, published, indexable pages (openvibe-publishing v0.2.0). Not deployed; openvibe.wiki stays on OpenVibe.Sites until launch."++ "notes": "Wiki spaces (official and user), page trees with canonical slugs and history-aware redirects, immutable revisions with diff and revert, typed infoboxes, [[internal links]] with red links, citations per revision (Sources items or URLs with retrieval time), Media attachments with an explicit broken state, Community discussion references, watchers notified through wiki.watch.triggered, AI proposals held for a person's approval. Server-rendered public routes useful without JavaScript; sitemaps, Atom/JSON feeds and Search index events only for public, published, indexable pages (openvibe-publishing v0.2.0). Deployed and public at https://openvibe.wiki."}@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.1.0",++ "version": "0.30.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.1.0",++ "version": "0.30.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.29.0",++ "version": "0.30.0","description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.","main": "index.js","types": "generated/typescript/index.d.ts",@@@ -74,6 +74,51 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscriptok(!contracts.validate('events.event-envelope@1', { ...env, source: 'app:app_01JAB2C3D4E5F6G7H8J9K0MNPS' }).valid, 'the raw app subject is not a valid source');}++// ── Event payload contracts (v0.30, ADR-026) ─────────────────────────────++// A payload contract is named after its event type; its major is the envelope `version`; its owner++// is the producer, which lists the type in eventsProduced. Every valid fixture rides in a valid++// envelope, and a tombstone never passes for a live payload.++{++ const payloads = contracts.catalog.filter(c => c.schema.startsWith('events/payloads/'));++ ok(payloads.length >= 40, `event payload contracts present (${payloads.length})`);++ const tombstone = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/events.tombstone-payload/valid/chat.json'), 'utf8'));++ for (const c of payloads) {++ const major = Number(c.version.split('.')[0]);++ ok(c.schema === `events/payloads/${c.id}.v${major}.json`, `${c.id} lives at events/payloads/<event_type>.v<major>.json`);++ ok(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$/.test(c.id), `${c.id} is an event type`);++ ok(services.get(c.owner) && services.get(c.owner).eventsProduced.includes(c.id), `${c.owner} manifest lists ${c.id} in eventsProduced`);++ ok(['active', 'planned'].includes(c.status), `${c.id} status ${c.status}`);++ const s = contracts.schema(c.id);++ ok((s.type === 'object' || s.$ref || s.allOf) && s.title.endsWith('Payload'), `${c.id} is an object payload schema`);++ if (s.properties && s.properties.redacts) ok(s.properties.redacts.$ref === '../redaction-directive.v1.json', `${c.id} redacts uses events.redaction-directive@1`);++ ok(!contracts.validate(c.id, tombstone).valid, `${c.id}: a tombstone is not a valid payload (check payload.redacted first)`);++ const dir = path.join(ROOT, 'fixtures', c.id, 'valid');++ for (const f of fs.readdirSync(dir).filter(n => n.endsWith('.json'))) {++ const payload = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));++ const env = { event_id: ids.newId('event'), event_type: c.id, version: major, source: c.owner, actor: { type: 'service', id: c.owner }, timestamp: new Date().toISOString(), subject: { type: 'x', id: '1' }, payload };++ ok(contracts.validate('events.event-envelope@1', env).valid, `${c.id} valid/${f} fits in an envelope`);++ ok(contracts.validate(`${env.event_type}@${env.version}`, env.payload).valid, `${c.id} resolves as <event_type>@<version>`);++ }++ }++ // Every event type a manifest produces and consumes is well formed; consumed types have a producer.++ const produced = new Map();++ for (const m of services.manifests) for (const t of m.eventsProduced) { ok(!produced.has(t), `${t} has one producer`); produced.set(t, m.id); }++ const matches = (pattern, t) => new RegExp(`^${pattern.split('.').map(s => (s === '*' ? '[a-z0-9_]+' : s)).join('\\.')}$`).test(t);++ for (const m of services.manifests) for (const t of m.eventsConsumed) ok([...produced.keys()].some(p => matches(t, p)), `${m.id} consumes ${t}, which some manifest produces`);++++ // payload.redacts: what Chat sends, and what Events refuses (403/422 are Events' answers).++ const del = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/chat.message.deleted/valid/two-messages.json'), 'utf8'));++ ok(contracts.validate('events.redaction-directive@1', del.redacts).valid, 'chat.message.deleted carries a valid directive');++ ok(JSON.stringify(del.redacts.subject_ids) === JSON.stringify(del.message_ids.map(String)), 'Chat redacts exactly the deleted ids');++ ok(!contracts.validate('chat.message.deleted@1', { message_ids: del.message_ids }).valid, 'chat.message.deleted without redacts fails');++ const D = (v) => contracts.validate('events.redaction-directive@1', v).valid;++ ok(D({ event_ids: ['evt_01JAB2C3D4E5F6G7H8J9K0MNPQ'], subject_type: 'post', subject_ids: ['p1'] }), 'both forms together pass');++ ok(!D({ subject_ids: ['1'] }) && !D({ subject_type: 'post' }), 'subject_type and subject_ids go together');++ ok(!D({ event_ids: Array.from({ length: 1001 }, () => ids.newId('event')) }), 'more than 1000 ids fail');++ ok(!D({ event_ids: ['evt_1'] }) && !D({ subject_type: 'Chat Message', subject_ids: ['1'] }), 'bad ids and subject types fail');++ ok(!contracts.validate('events.tombstone-payload@1', { redacted: false, redacted_at: tombstone.redacted_at, redacted_by: 'x' }).valid, 'a tombstone is redacted: true');++}++// ── Ids ──────────────────────────────────────────────────────────────────for (const kind of ['user', 'guest', 'app', 'mod']) {const id = ids.newId(kind);@@@ -229,6 +274,13 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);// ── Generated output and compatibility gate ──────────────────────────────execFileSync(process.execPath, [path.join(ROOT, 'scripts/generate.js'), '--check'], { stdio: 'inherit' });execFileSync(process.execPath, [path.join(ROOT, 'scripts/compat.js')], { stdio: 'inherit' });++{++ // A $ref with sibling keywords makes json-schema-to-typescript emit SubjectRef1 & co. without declaring them.++ const dts = fs.readFileSync(path.join(ROOT, 'generated/typescript/index.d.ts'), 'utf8');++ const declared = new Set([...dts.matchAll(/^export (?:interface|type) (\w+)/gm)].map(m => m[1]));++ const dangling = [...new Set([...dts.replace(/\/\*[\s\S]*?\*\//g, '').matchAll(/\b([A-Z][A-Za-z]*\d+)\b/g)].map(m => m[1]))].filter(n => !declared.has(n));++ ok(dangling.length === 0, `generated types reference undeclared names: ${dangling.join(', ')}`);++}console.log(`openvibe-contracts: ${n} checks passed`);})().catch((err) => { console.error(err); process.exit(1); });
new file mode 100644
new file mode 100644
@@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
+# Changelog
+
+All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) that consumers install
+from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
+the notes were in the tag and commit messages (`git tag -n1`).
+
+## 0.30.0 (2026-09-23)
+
+Additive: `compat.js` reports no breaking change against v0.29.0. No existing contract changed.
+
+**Event payload contracts** (ADR-026). Each event type has a contract named after it, with the envelope
+`version` as its major: ``contracts.validate(`${env.event_type}@${env.version}`, env.payload)``. The files
+are `contracts/events/payloads/<event_type>.v1.json`, and every one has valid and invalid fixtures. The
+shapes come from the producers' code.
+
+- Chat: `chat.message.deleted` (`message_ids`, and `redacts: { subject_type: chat_message, subject_ids }`, which is required).
+- Tips: `tips.interaction.ready|failed|cancelled`, `tips.goal.updated`, `tips.overlay.delivered|failed`.
+- VIP: `vip.plan.published`, `vip.membership.changed`.
+- Billing: `billing.transaction.settled|reversed`, `billing.entitlement.changed`,
+ `billing.subscription.canceled`, `billing.cashout.requested|paid|denied`, `billing.staff.action`,
+ `billing.receipt.external`.
+- Wiki: `wiki.space.updated`, `wiki.revision.created`, `wiki.watch.triggered`,
+ `wiki.page.published|updated|unpublished|deleted`, `wiki.index_document.upserted|deleted`.
+- Blog: `blog.post.created|published|updated|unpublished|deleted`, `blog.schedule.failed`,
+ `blog.index_document.upserted|deleted`.
+- Sources: `sources.item.created|updated|removed`, `sources.fetch.failed`,
+ `sources.index_document.upserted|deleted`.
+- Search: `search.document.indexed|removed`.
+- Deals: `deals.watch.matched`. Trade: `trade.alert.triggered`.
+- **Planned** (catalog status `planned`, not emitted yet): `ai.run.queued|succeeded|failed|cached` and
+ `tools.job.created|started|succeeded|failed`. Tools job events carry ids, type, owner, state, times,
+ result locations and errors, never inputs, output data, file names or the anonymous session.
+- `*.index_document.upserted` payloads are `search.index-document@1`, with the owner and type fixed.
+
+**Redaction** (new ADR-026, linked from ADR-004):
+
+- `events.redaction-directive@1` is `payload.redacts` (`event_ids` and/or `subject_type` + `subject_ids`, up to 1000 each).
+- `events.tombstone-payload@1` is what a redacted event's payload becomes.
+- The ADR documents 403 `events.redaction_not_allowed`, 422 `events.invalid_redaction` and the realtime
+ public replay window (`REALTIME_PUBLIC_REPLAY_SECONDS`, default 300, `gap` reason `public_window`).
+- Delivery signature v2 was already documented (ADR-004, README).
+
+**AI** (ADR-015):
+
+- `ai.run-request@1` is the body of `POST /api/v1/runs`, and `ai.run@1` is the run resource.
+- `ai.run.create` now declares both as `inputSchema`/`outputSchema`, and `ai.run.read` declares `outputSchema`.
+
+**Manifests**
+
+- Service manifests:
+ - chat 0.3.0 produces `chat.message.deleted`.
+ - tools 0.3.0 produces `tools.job.*`.
+ - network 0.2.0 consumes `deals.watch.matched` and `trade.alert.triggered`.
+ - billing 0.3.0 produces `billing.receipt.external`, and tips 0.3.0 consumes it.
+ - ai 0.2.0 lists its planned `ai.run.*`.
+- Capabilities: `chat.live_bridge.write` lists `chat.message.deleted`, `tools.job.create` lists `tools.job.*`
+ and `ai.run.create` lists `ai.run.*`.
+- Notes corrected:
+ - Wiki is deployed and public at openvibe.wiki.
+ - Reviews, VIP and Tips are deployed internally and not launched publicly. Tips also loses the stale "Proposed manifest" line.
+- SDK, Shared and Examples are `alpha`, no longer `placeholder`: SDK and Shared are released libraries,
+ and Examples is a repository with CI. Realtime is the only placeholder left.
+
+**Tests**
+
+- Every payload contract lives at its event type's path, is listed in its owner's `eventsProduced`, fits in an
+ envelope and rejects a tombstone.
+- Every consumed event type (wildcards included) has a producing manifest, and each type has one producer.
+- The directive's limits are checked.
+- Generated TypeScript declares every type it references.
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Machine-readable contracts for the whole OpenVibe network.
> Machine-readable contracts for the whole OpenVibe network.
-**Status:** alpha, v0.29.0 (first released in Wave 1). 17 schemas, 30 service manifests, 170 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.
+**Status:** alpha, v0.30.0 (first released in Wave 1). 74 schemas (53 of them event payloads), 30 service manifests, 170 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**License:** AGPL-3.0 (same as every OpenVibe service).
**License:** AGPL-3.0 (same as every OpenVibe service).
@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).
@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).
| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) |
| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) |
| `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |
| `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |
-Manifests: `manifests/services/` (30 services as of v0.29.0: 20 `alpha`, 1 `beta`, 5 `stable`, and 4 still marked `placeholder`: examples, realtime, sdk and shared), `manifests/capabilities/` (170 capabilities; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Some manifest `notes` predate deployment: wiki, reviews and vip still say "not deployed", and SDK and Shared are tagged packages and Examples is a working repository, although their manifests say `placeholder`. Correcting them needs a new tag.
+Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (170 capabilities; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.
**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.
**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.
@@ -80,6 +80,17 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi
@@ -80,6 +80,17 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi
`project_key` is `p` followed by the project's ULID in lowercase (`prj_01JAB…` → `p01jab…`), so it fits an event-type segment. An app event's `source` is `app-` followed by the app's ULID in lowercase (`app:app_01JAB…` → `app-01jab…`), which keeps `events.event-envelope@1` unchanged (its `source` pattern already allows it). OpenVibe.Events enforces the scope, the sandbox separation and per-project quotas.
`project_key` is `p` followed by the project's ULID in lowercase (`prj_01JAB…` → `p01jab…`), so it fits an event-type segment. An app event's `source` is `app-` followed by the app's ULID in lowercase (`app:app_01JAB…` → `app-01jab…`), which keeps `events.event-envelope@1` unchanged (its `source` pattern already allows it). OpenVibe.Events enforces the scope, the sandbox separation and per-project quotas.
+## Event payload contracts (v0.30)
+
+Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`, `tools.job.*`).
+
+```js
+if (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)
+const r = contracts.validate(`${env.event_type}@${env.version}`, env.payload);
+```
+
+**Redaction (ADR-026).** Any event may carry `payload.redacts` (`events.redaction-directive@1`), shaped `{ event_ids?, subject_type?, subject_ids? }` with up to 1000 ids each, to take back the producer's own earlier events. OpenVibe.Events rewrites each target into a tombstone at its original seq: `{ redacted: true, redacted_at, redacted_by }`, with the producer itself as actor. Naming another source's event refuses the whole batch with 403 `events.redaction_not_allowed`. A malformed directive gets 422 `events.invalid_redaction`. `chat.message.deleted` always carries one. Browsers are replayed only the `public` events of the last `REALTIME_PUBLIC_REPLAY_SECONDS` (default 300); an older cursor gets `event: gap` with reason `public_window`.
+
## Event delivery signatures
## Event delivery signatures
OpenVibe.Events POSTs each webhook delivery as `{ "event": <events.event-envelope@1>, "seq": n }`, keyed with the subscription secret:
OpenVibe.Events POSTs each webhook delivery as `{ "event": <events.event-envelope@1>, "seq": n }`, keyed with the subscription secret:
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+{
+ "$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.",
+ "type": "object",
+ "required": ["workflow"],
+ "additionalProperties": false,
+ "properties": {
+ "workflow": {
+ "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…)."
+ },
+ "version": { "type": "integer", "minimum": 1, "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." }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
+{
+ "$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.",
+ "type": "object",
+ "required": ["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": {
+ "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": ["key", "version"], "additionalProperties": false,
+ "properties": { "key": { "type": "string" }, "version": { "type": "integer", "minimum": 1 } }
+ },
+ "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" },
+ "on_behalf_of": { "anyOf": [{ "$ref": "../identity/subject-ref.v1.json" }, { "type": "null" }] },
+ "attribution": { "anyOf": [{ "$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": { "anyOf": [{ "$ref": "../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "input": { "type": "object", "description": "The retained input (see the description)." },
+ "output": { "type": ["object", "null"], "description": "Workflow output, or null." },
+ "error": {
+ "type": ["object", "null"], "required": ["code", "detail"], "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…" },
+ "detail": { "type": ["string", "null"] }
+ }
+ },
+ "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" },
+ "workflow": { "type": "string" },
+ "workflow_version": { "type": "integer", "minimum": 1 },
+ "template_version": { "type": ["integer", "null"] },
+ "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" }
+ }
+ },
+ "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)." }
+ }
+ },
+ "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" }
+ }
+}
@@ -167,6 +167,554 @@
@@ -167,6 +167,554 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active",
"status": "active",
"adr": "ADR-014"
"adr": "ADR-014"
+ },
+ {
+ "id": "events.redaction-directive",
+ "version": "1.0.0",
+ "owner": "events",
+ "schema": "events/redaction-directive.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-026"
+ },
+ {
+ "id": "events.tombstone-payload",
+ "version": "1.0.0",
+ "owner": "events",
+ "schema": "events/tombstone-payload.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-026"
+ },
+ {
+ "id": "chat.message.deleted",
+ "version": "1.0.0",
+ "owner": "chat",
+ "schema": "events/payloads/chat.message.deleted.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-026"
+ },
+ {
+ "id": "sources.item.created",
+ "version": "1.0.0",
+ "owner": "sources",
+ "schema": "events/payloads/sources.item.created.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-017"
+ },
+ {
+ "id": "sources.item.updated",
+ "version": "1.0.0",
+ "owner": "sources",
+ "schema": "events/payloads/sources.item.updated.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-017"
+ },
+ {
+ "id": "sources.item.removed",
+ "version": "1.0.0",
+ "owner": "sources",
+ "schema": "events/payloads/sources.item.removed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-017"
+ },
+ {
+ "id": "sources.fetch.failed",
+ "version": "1.0.0",
+ "owner": "sources",
+ "schema": "events/payloads/sources.fetch.failed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-017"
+ },
+ {
+ "id": "sources.index_document.upserted",
+ "version": "1.0.0",
+ "owner": "sources",
+ "schema": "events/payloads/sources.index_document.upserted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-017"
+ },
+ {
+ "id": "sources.index_document.deleted",
+ "version": "1.0.0",
+ "owner": "sources",
+ "schema": "events/payloads/sources.index_document.deleted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-017"
+ },
+ {
+ "id": "search.document.indexed",
+ "version": "1.0.0",
+ "owner": "search",
+ "schema": "events/payloads/search.document.indexed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-018"
+ },
+ {
+ "id": "search.document.removed",
+ "version": "1.0.0",
+ "owner": "search",
+ "schema": "events/payloads/search.document.removed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-018"
+ },
+ {
+ "id": "wiki.space.updated",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.space.updated.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.revision.created",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.revision.created.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.watch.triggered",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.watch.triggered.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.page.published",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.page.published.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.page.updated",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.page.updated.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.page.unpublished",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.page.unpublished.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.page.deleted",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.page.deleted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.index_document.upserted",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.index_document.upserted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "wiki.index_document.deleted",
+ "version": "1.0.0",
+ "owner": "wiki",
+ "schema": "events/payloads/wiki.index_document.deleted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.post.created",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.post.created.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.post.published",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.post.published.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.post.updated",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.post.updated.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.post.unpublished",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.post.unpublished.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.post.deleted",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.post.deleted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.schedule.failed",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.schedule.failed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.index_document.upserted",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.index_document.upserted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "blog.index_document.deleted",
+ "version": "1.0.0",
+ "owner": "blog",
+ "schema": "events/payloads/blog.index_document.deleted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "ai.run-request",
+ "version": "1.0.0",
+ "owner": "ai",
+ "schema": "ai/run-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-015"
+ },
+ {
+ "id": "ai.run",
+ "version": "1.0.0",
+ "owner": "ai",
+ "schema": "ai/run.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-015"
+ },
+ {
+ "id": "ai.run.queued",
+ "version": "1.0.0",
+ "owner": "ai",
+ "schema": "events/payloads/ai.run.queued.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-015"
+ },
+ {
+ "id": "ai.run.succeeded",
+ "version": "1.0.0",
+ "owner": "ai",
+ "schema": "events/payloads/ai.run.succeeded.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-015"
+ },
+ {
+ "id": "ai.run.failed",
+ "version": "1.0.0",
+ "owner": "ai",
+ "schema": "events/payloads/ai.run.failed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-015"
+ },
+ {
+ "id": "ai.run.cached",
+ "version": "1.0.0",
+ "owner": "ai",
+ "schema": "events/payloads/ai.run.cached.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-015"
+ },
+ {
+ "id": "tips.interaction.ready",
+ "version": "1.0.0",
+ "owner": "tips",
+ "schema": "events/payloads/tips.interaction.ready.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "tips.interaction.failed",
+ "version": "1.0.0",
+ "owner": "tips",
+ "schema": "events/payloads/tips.interaction.failed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "tips.interaction.cancelled",
+ "version": "1.0.0",
+ "owner": "tips",
+ "schema": "events/payloads/tips.interaction.cancelled.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "tips.goal.updated",
+ "version": "1.0.0",
+ "owner": "tips",
+ "schema": "events/payloads/tips.goal.updated.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "tips.overlay.delivered",
+ "version": "1.0.0",
+ "owner": "tips",
+ "schema": "events/payloads/tips.overlay.delivered.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "tips.overlay.failed",
+ "version": "1.0.0",
+ "owner": "tips",
+ "schema": "events/payloads/tips.overlay.failed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "vip.plan.published",
+ "version": "1.0.0",
+ "owner": "vip",
+ "schema": "events/payloads/vip.plan.published.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "vip.membership.changed",
+ "version": "1.0.0",
+ "owner": "vip",
+ "schema": "events/payloads/vip.membership.changed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.transaction.settled",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.transaction.settled.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.transaction.reversed",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.transaction.reversed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.entitlement.changed",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.entitlement.changed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.subscription.canceled",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.subscription.canceled.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.cashout.requested",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.cashout.requested.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.cashout.paid",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.cashout.paid.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.cashout.denied",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.cashout.denied.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.staff.action",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.staff.action.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "billing.receipt.external",
+ "version": "1.0.0",
+ "owner": "billing",
+ "schema": "events/payloads/billing.receipt.external.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-012"
+ },
+ {
+ "id": "deals.watch.matched",
+ "version": "1.0.0",
+ "owner": "deals",
+ "schema": "events/payloads/deals.watch.matched.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "trade.alert.triggered",
+ "version": "1.0.0",
+ "owner": "trade",
+ "schema": "events/payloads/trade.alert.triggered.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-020"
+ },
+ {
+ "id": "tools.job.created",
+ "version": "1.0.0",
+ "owner": "tools",
+ "schema": "events/payloads/tools.job.created.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned"
+ },
+ {
+ "id": "tools.job.started",
+ "version": "1.0.0",
+ "owner": "tools",
+ "schema": "events/payloads/tools.job.started.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned"
+ },
+ {
+ "id": "tools.job.succeeded",
+ "version": "1.0.0",
+ "owner": "tools",
+ "schema": "events/payloads/tools.job.succeeded.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned"
+ },
+ {
+ "id": "tools.job.failed",
+ "version": "1.0.0",
+ "owner": "tools",
+ "schema": "events/payloads/tools.job.failed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned"
}
}
]
]
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
+{
+ "$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.",
+ "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": "cached" },
+ "workflow": {
+ "type": "object",
+ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "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": "string", "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "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 }
+ }
+ },
+ "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" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
+{
+ "$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": {
+ "type": "object",
+ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "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 },
+ "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" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
+{
+ "$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": {
+ "type": "object",
+ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "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 },
+ "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": "null" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
+{
+ "$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.",
+ "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": "succeeded" },
+ "workflow": {
+ "type": "object",
+ "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": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "attribution": { "anyOf": [{ "$ref": "../../common/entity-ref.v1.json" }, { "type": "null" }] },
+ "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 },
+ "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" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.denied.v1.json",
+ "title": "BillingCashoutDeniedPayload",
+ "description": "billing.cashout.denied v1 (OpenVibe.Billing server/ops/cashouts.js deny). Staff denied a cash-out; the escrowed bits went back (a billing.transaction.reversed follows in the same transaction). Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["cashout", "transaction_id"],
+ "additionalProperties": false,
+ "properties": {
+ "cashout": {
+ "type": "object",
+ "required": ["id", "subject", "amount_bits", "value_cents", "status", "payout_method", "escrow_until", "request_txn", "settle_txn", "payout_provider", "payout_reference", "reason", "created_at", "updated_at"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "subject": { "$ref": "../../identity/subject-ref.v1.json" },
+ "amount_bits": { "type": "integer", "minimum": 1 },
+ "value_cents": { "type": "integer" },
+ "status": { "const": "denied" },
+ "payout_method": { "type": "object", "description": "{ type, address } as the creator entered it (e.g. a PayPal email)." },
+ "escrow_until": { "type": "string" },
+ "request_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "settle_txn": { "type": ["string", "null"], "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "payout_provider": { "type": ["string", "null"] },
+ "payout_reference": { "type": ["string", "null"] },
+ "reason": { "type": ["string", "null"] },
+ "created_at": { "type": "string" },
+ "updated_at": { "type": "string" }
+ }
+ },
+ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The cashout_denied transaction." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.paid.v1.json",
+ "title": "BillingCashoutPaidPayload",
+ "description": "billing.cashout.paid v1 (OpenVibe.Billing server/ops/cashouts.js approve). Staff paid a cash-out out through a provider. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["cashout", "transaction_id"],
+ "additionalProperties": false,
+ "properties": {
+ "cashout": {
+ "type": "object",
+ "required": ["id", "subject", "amount_bits", "value_cents", "status", "payout_method", "escrow_until", "request_txn", "settle_txn", "payout_provider", "payout_reference", "reason", "created_at", "updated_at"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "subject": { "$ref": "../../identity/subject-ref.v1.json" },
+ "amount_bits": { "type": "integer", "minimum": 1 },
+ "value_cents": { "type": "integer" },
+ "status": { "const": "paid" },
+ "payout_method": { "type": "object", "description": "{ type, address } as the creator entered it (e.g. a PayPal email)." },
+ "escrow_until": { "type": "string" },
+ "request_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "settle_txn": { "type": ["string", "null"], "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "payout_provider": { "type": ["string", "null"] },
+ "payout_reference": { "type": ["string", "null"] },
+ "reason": { "type": ["string", "null"] },
+ "created_at": { "type": "string" },
+ "updated_at": { "type": "string" }
+ }
+ },
+ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The cashout_paid transaction." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.requested.v1.json",
+ "title": "BillingCashoutRequestedPayload",
+ "description": "billing.cashout.requested v1 (OpenVibe.Billing server/ops/cashouts.js request). A creator asked to cash out payable bits; they are held in escrow. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["cashout", "transaction_id"],
+ "additionalProperties": false,
+ "properties": {
+ "cashout": {
+ "type": "object",
+ "required": ["id", "subject", "amount_bits", "value_cents", "status", "payout_method", "escrow_until", "request_txn", "settle_txn", "payout_provider", "payout_reference", "reason", "created_at", "updated_at"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "subject": { "$ref": "../../identity/subject-ref.v1.json" },
+ "amount_bits": { "type": "integer", "minimum": 1 },
+ "value_cents": { "type": "integer" },
+ "status": { "const": "requested" },
+ "payout_method": { "type": "object", "description": "{ type, address } as the creator entered it (e.g. a PayPal email)." },
+ "escrow_until": { "type": "string" },
+ "request_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "settle_txn": { "type": ["string", "null"], "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "payout_provider": { "type": ["string", "null"] },
+ "payout_reference": { "type": ["string", "null"] },
+ "reason": { "type": ["string", "null"] },
+ "created_at": { "type": "string" },
+ "updated_at": { "type": "string" }
+ }
+ },
+ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The cashout_request transaction." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.entitlement.changed.v1.json",
+ "title": "BillingEntitlementChangedPayload",
+ "description": "billing.entitlement.changed v1 (OpenVibe.Billing server/ops/subscriptions.js pay, setStatus, revokeForTxn; entitlement()). A subscriber's channel-subscription entitlement to a streamer changed: a period was granted or renewed, the subscription ended or expired, or a reversed payment revoked a period. The payload is the entitlement as of now. Envelope: subject { type: entitlement, id: <subscriber usr_…>:channel_subscription:<streamer usr_…> }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["subject", "streamer", "kind", "active", "expires_at", "subscription", "reason"],
+ "additionalProperties": false,
+ "properties": {
+ "subject": { "$ref": "../../identity/subject-ref.v1.json" },
+ "streamer": { "$ref": "../../identity/subject-ref.v1.json" },
+ "kind": { "const": "channel_subscription" },
+ "active": { "type": "boolean" },
+ "expires_at": { "type": ["string", "null"], "format": "date-time" },
+ "subscription": {
+ "type": ["object", "null"],
+ "required": ["id", "status", "auto_renew", "cancel_at_period_end", "provider"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "status": { "enum": ["active", "canceled", "expired"] },
+ "auto_renew": { "type": "boolean" },
+ "cancel_at_period_end": { "type": "boolean" },
+ "provider": { "type": "string" }
+ }
+ },
+ "reason": {
+ "type": "string",
+ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."
+ },
+ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "granted/renewed: the transaction that paid the period." },
+ "revoked_period": {
+ "type": "object",
+ "required": ["starts_at", "ends_at"],
+ "additionalProperties": false,
+ "properties": { "starts_at": { "type": "string", "format": "date-time" }, "ends_at": { "type": "string", "format": "date-time" } },
+ "description": "refund/chargeback: the period the reversed payment had granted."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.receipt.external.v1.json",
+ "title": "BillingReceiptExternalPayload",
+ "description": "billing.receipt.external v1 (OpenVibe.Billing server/ops/external.js record). An EXTERNAL receipt (ADR-012): a tip paid on a creator's own provider account (PowerChat route direct). The money never touches OpenVibe (no journal entry); Billing announces it once per payment, only when BILLING_AUTHORITY=billing and the receiving account is mapped to a creator. OpenVibe.Tips celebrates it (chat line, overlay alert, goal). Envelope: subject { type: provider_receipt, id: <provider>:<payment id> }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["classification", "provider", "receipt_ref", "provider_event_id", "delivery_id", "streamer", "receiving_account", "amount_cents", "currency", "value_bits", "donor_name", "anonymous", "message", "app_ref", "app_purpose", "occurred_at", "test", "rates"],
+ "additionalProperties": false,
+ "properties": {
+ "classification": { "const": "EXTERNAL" },
+ "provider": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{1,39}$" },
+ "receipt_ref": { "type": "string", "description": "<provider>:<payment id>, e.g. powerchat:…" },
+ "provider_event_id": { "type": "string", "minLength": 1, "maxLength": 200, "description": "The provider's id of the payment, stable across redeliveries." },
+ "delivery_id": { "type": "string", "description": "The webhook delivery that carried it." },
+ "streamer": { "$ref": "../../identity/subject-ref.v1.json" },
+ "receiving_account": {
+ "type": "object",
+ "required": ["provider", "id", "username"],
+ "additionalProperties": false,
+ "properties": { "provider": { "type": "string" }, "id": { "type": ["string", "null"] }, "username": { "type": ["string", "null"] } }
+ },
+ "amount_cents": { "type": "integer", "minimum": 1, "maximum": 100000000 },
+ "currency": { "const": "usd-cents" },
+ "value_bits": { "type": "integer", "minimum": 0, "description": "Billing's value of the money in bits." },
+ "donor_name": { "type": ["string", "null"], "maxLength": 80, "description": "null when anonymous." },
+ "anonymous": { "type": "boolean" },
+ "message": { "type": ["string", "null"], "maxLength": 500 },
+ "app_ref": { "type": ["string", "null"] },
+ "app_purpose": { "type": ["string", "null"], "maxLength": 120 },
+ "occurred_at": { "type": ["string", "null"], "description": "The provider's timestamp as it sent it." },
+ "test": { "type": "boolean" },
+ "rates": { "type": "object", "required": ["bits_per_usd"], "properties": { "bits_per_usd": { "type": "number" } }, "description": "Rate snapshot." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.staff.action.v1.json",
+ "title": "BillingStaffActionPayload",
+ "description": "billing.staff.action v1 (OpenVibe.Billing server/console/audit.js record). A staff member completed an action in the Billing console (only outcome done is announced; refused actions stay in staff_audit). Holds no secrets: codes, payout reference, counts. Envelope: subject { type: staff_action, id: sa_… }, visibility internal, priority important, actor the staff user ({ type: user, id: usr_… }).",
+ "type": "object",
+ "required": ["audit_id", "action", "outcome", "target", "reason", "request_id", "detail"],
+ "additionalProperties": false,
+ "properties": {
+ "audit_id": { "type": "string", "pattern": "^sa_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "action": { "type": "string" },
+ "outcome": { "const": "done" },
+ "target": {
+ "type": ["object", "null"],
+ "required": ["type", "id"],
+ "additionalProperties": false,
+ "properties": { "type": { "type": "string" }, "id": { "type": "string" } }
+ },
+ "reason": { "type": ["string", "null"], "maxLength": 500 },
+ "request_id": { "type": ["string", "null"] },
+ "detail": { "type": "object", "description": "Small whitelisted detail of the action." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.subscription.canceled.v1.json",
+ "title": "BillingSubscriptionCanceledPayload",
+ "description": "billing.subscription.canceled v1 (OpenVibe.Billing server/ops/subscriptions.js cancel). A subscription was set to cancel at period end (for Stripe, after Stripe accepted it); the paid period still runs. Envelope: subject { type: subscription, id: sub_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["subscription", "provider_sync", "actor"],
+ "additionalProperties": false,
+ "properties": {
+ "subscription": {
+ "type": "object",
+ "required": ["id", "subscriber", "streamer", "tier", "provider", "provider_ref", "route", "status", "auto_renew", "cancel_at_period_end", "price_cents", "current_period_end", "created_at", "updated_at"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "subscriber": { "$ref": "../../identity/subject-ref.v1.json" },
+ "streamer": { "$ref": "../../identity/subject-ref.v1.json" },
+ "tier": { "type": "integer", "minimum": 1 },
+ "provider": { "type": "string" },
+ "provider_ref": { "type": ["string", "null"] },
+ "route": { "type": ["string", "null"] },
+ "status": { "enum": ["active", "canceled", "expired"] },
+ "auto_renew": { "type": "boolean" },
+ "cancel_at_period_end": { "type": "boolean" },
+ "price_cents": { "type": "integer", "minimum": 0 },
+ "current_period_end": { "type": ["string", "null"] },
+ "created_at": { "type": "string" },
+ "updated_at": { "type": "string" }
+ }
+ },
+ "provider_sync": { "enum": ["not_needed", "stripe_cancel_at_period_end"] },
+ "actor": {
+ "type": "object",
+ "required": ["principal", "request_id"],
+ "additionalProperties": false,
+ "properties": {
+ "principal": { "type": ["string", "null"], "description": "Token sub of the caller (svc:…, usr_…)." },
+ "request_id": { "type": ["string", "null"] },
+ "on_behalf_of": { "$ref": "../../identity/subject-ref.v1.json" }
+ },
+ "description": "Who asked: the calling principal and the person it acted for."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",
+ "title": "BillingTransactionReversedPayload",
+ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["transaction_id", "type", "test", "from_subject", "to_subject", "provider", "metadata", "reverses_txn"],
+ "additionalProperties": false,
+ "properties": {
+ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "type": {
+ "enum": ["purchase", "donation", "subscription", "subscription_share", "cashout_request", "cashout_paid", "cashout_denied", "recycle", "refund", "chargeback", "adjustment", "import"]
+ },
+ "test": { "type": "boolean", "description": "Test money: never counted." },
+ "from_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved from, null for provider money." },
+ "to_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved to." },
+ "provider": { "type": ["string", "null"], "description": "stripe, paypal, powerchat…; null for credit movements." },
+ "metadata": {
+ "type": "object",
+ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."
+ },
+ "reverses_txn": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The transaction this one reverses." },
+ "review": { "enum": ["required", null], "description": "Provider reversals only: required when Billing kept value it could not recover (staff review)." },
+ "entitlements_revoked": { "type": "integer", "minimum": 0, "description": "Provider reversals only: subscription periods revoked." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.settled.v1.json",
+ "title": "BillingTransactionSettledPayload",
+ "description": "billing.transaction.settled v1 (OpenVibe.Billing server/ops/purchases.js summary(); emitted by purchases, transfers, subscriptions, cashouts recycle and admin adjustments). A journal transaction was posted. Envelope: subject { type: transaction, id: txn_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": ["transaction_id", "type", "test", "from_subject", "to_subject", "provider", "metadata"],
+ "additionalProperties": false,
+ "properties": {
+ "transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "type": {
+ "enum": ["purchase", "donation", "subscription", "subscription_share", "cashout_request", "cashout_paid", "cashout_denied", "recycle", "refund", "chargeback", "adjustment", "import"]
+ },
+ "test": { "type": "boolean", "description": "Test money: never counted." },
+ "from_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved from, null for provider money." },
+ "to_subject": { "type": ["string", "null"], "description": "Subject id (usr_…) value moved to." },
+ "provider": { "type": ["string", "null"], "description": "stripe, paypal, powerchat…; null for credit movements." },
+ "metadata": {
+ "type": "object",
+ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "$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}$" },
+ "revision": { "type": "integer", "minimum": 1, "maximum": 9007199254740991, "description": "Index revision of the tombstone; wins over any document at the same or an older revision." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "$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" },
+ {
+ "type": "object", "required": ["owner", "type", "deleted"],
+ "properties": { "owner": { "const": "blog" }, "type": { "const": "post" }, "id": { "type": "string", "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$" }, "deleted": { "const": false } }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
+{
+ "$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.",
+ "type": "object",
+ "required": ["blog", "state", "visibility", "authorship"],
+ "additionalProperties": false,
+ "properties": {
+ "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}$" } }
+ },
+ "state": { "const": "draft" },
+ "visibility": { "enum": ["public", "unlisted", "members", "private"] },
+ "authorship": { "enum": ["human", "ai", "hybrid", "imported"], "description": "openvibe-publishing authorship mode (not Search's vocabulary)." },
+ "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" }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "$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).",
+ "type": "object",
+ "required": ["canonical_url", "publication_state", "indexability", "blog", "visibility"],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },
+ "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." },
+ "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": "The post's own visibility (members = entitlement-gated)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "$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" },
+ "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}$" } } } },
+ "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": "The post's own visibility (members = entitlement-gated)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "$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": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },
+ "publication_state": { "const": "unpublished" },
+ "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." },
+ "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": "The post's own visibility (members = entitlement-gated)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "$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" },
+ "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}$" } } } },
+ "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": "The post's own visibility (members = entitlement-gated)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "$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.",
+ "type": "object",
+ "required": ["job_id", "action", "revision", "run_at", "attempts", "error", "blog_id"],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "action": { "enum": ["publish", "unpublish"] },
+ "revision": { "type": ["integer", "null"], "minimum": 1, "description": "The revision a publish was to make live; null for an unpublish." },
+ "run_at": { "type": "string", "format": "date-time", "description": "When the job was last due." },
+ "attempts": { "type": "integer", "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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "$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 }."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/deals.watch.matched.v1.json",
+ "title": "DealsWatchMatchedPayload",
+ "description": "deals.watch.matched v1 (OpenVibe.Deals server/domain/watches.js onObservation). A person's keyword, product or price-below watch matched a fresh observation of an active offer, inside the transaction that recorded the observation. At most one event per (watch, observation); a keyword/product watch notifies once per offer, a price-below watch again only for a lower price. Saved searches (kind search) never notify. Envelope: subject { type: watch, id: <watch_id> }, visibility internal, priority important, actor service:deals. OpenVibe.Network turns it into a DEAL_WATCH_MATCH notification for payload.recipient (server/notifications/events-consumer.js). A price is the decimal exactly as stated, never inferred; null when the observation stated none.",
+ "type": "object",
+ "required": ["watch_id", "recipient", "kind", "query", "product_id", "max_price", "currency", "offer_id", "offer_url", "title", "observation"],
+ "additionalProperties": false,
+ "properties": {
+ "watch_id": { "type": "string", "pattern": "^dwt_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "recipient": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The watch owner's user subject id: who is notified." },
+ "kind": { "enum": ["keyword", "product", "price_below"] },
+ "query": { "type": ["string", "null"], "maxLength": 200, "description": "Keywords of a keyword watch, or of a price-below watch without a product." },
+ "product_id": { "type": ["string", "null"], "maxLength": 64, "description": "Deals product id of a product or price-below watch." },
+ "max_price": { "type": ["string", "null"], "maxLength": 40, "description": "Price-below threshold as entered (decimal text, e.g. 19.99)." },
+ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$", "description": "ISO 4217 currency of max_price." },
+ "offer_id": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The canonical (root) offer id (dof_…)." },
+ "offer_url": { "type": "string", "format": "uri", "pattern": "^https?://", "maxLength": 2048, "description": "Canonical offer page URL on Deals." },
+ "title": { "type": "string", "minLength": 1, "maxLength": 500 },
+ "observation": {
+ "type": "object", "required": ["id", "observed_at", "price", "currency", "availability"], "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "pattern": "^dpo_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "observed_at": { "type": "string", "format": "date-time" },
+ "price": { "type": ["string", "null"], "maxLength": 40, "description": "Decimal text as stated; null when the observation stated no price." },
+ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },
+ "availability": { "enum": ["in_stock", "out_of_stock", "preorder", "discontinued", "limited", "sold_out", "online_only", "in_store_only", null] }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "$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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "$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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "$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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "$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": ["type", "id", "revision"],
+ "additionalProperties": false,
+ "properties": {
+ "type": { "const": "item" },
+ "id": { "type": "string", "pattern": "^itm_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "revision": { "type": "integer", "minimum": 1 }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "$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"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "$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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "$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 }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "$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}$" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.goal.updated.v1.json",
+ "title": "TipsGoalUpdatedPayload",
+ "description": "tips.goal.updated v1 (OpenVibe.Tips server/domain/goals.js changed). A goal was created, edited or closed, or its settled total moved (a contribution, the contribution that reached it, a reversal). Envelope: subject { type: goal, id: tgoal_…, revision }, visibility internal, priority important, actor service:tips.",
+ "type": "object",
+ "required": ["goal_id", "creator", "title", "target_amount", "current_amount", "currency", "status", "reached", "reason", "interaction_id"],
+ "additionalProperties": false,
+ "properties": {
+ "goal_id": { "type": "string", "pattern": "^tgoal_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "creator": { "$ref": "../../identity/subject-ref.v1.json" },
+ "title": { "type": "string" },
+ "target_amount": { "type": "integer", "minimum": 1 },
+ "current_amount": { "type": "integer", "description": "Settled total including any amount carried over from Live." },
+ "currency": { "type": "string" },
+ "status": { "enum": ["active", "closed"] },
+ "reached": { "type": "boolean" },
+ "reason": { "enum": ["created", "updated", "closed", "contribution", "reached", "reversal"] },
+ "interaction_id": {
+ "type": ["string", "null"],
+ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The interaction behind a contribution, reached or reversal change; null otherwise."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.cancelled.v1.json",
+ "title": "TipsInteractionCancelledPayload",
+ "description": "tips.interaction.cancelled v1 (OpenVibe.Tips server/domain/interactions.js failPayment, cancelQueued). Undelivered effects were cancelled: the payment failed before settling (reason payment_failed, with the Billing error code), Billing reversed it (payment_reversed) or an effect came up while the payment was no longer settled (payment_not_settled). Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",
+ "type": "object",
+ "required": ["interaction_id", "creator", "supporter", "supporter_name", "kind", "amount", "currency", "settlement", "payment_state", "delivery_state", "billing_txn_id", "test", "reason"],
+ "additionalProperties": false,
+ "properties": {
+ "interaction_id": { "type": "string", "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "creator": { "$ref": "../../identity/subject-ref.v1.json" },
+ "supporter": {
+ "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],
+ "description": "The supporter, null when external, anonymous or unmapped."
+ },
+ "supporter_name": { "type": ["string", "null"], "description": "Display name at the time." },
+ "kind": { "enum": ["tip", "paid_message", "tts", "media_request"] },
+ "amount": { "type": "integer", "minimum": 1, "description": "Integer amount in currency minor units (Vibes bits)." },
+ "currency": { "type": "string", "description": "vibes-bits today." },
+ "settlement": { "enum": ["billing", "external", "simulated", "imported"] },
+ "payment_state": { "enum": ["pending", "settled", "reversed", "failed"] },
+ "delivery_state": { "enum": ["awaiting_payment", "queued", "delivered", "failed", "cancelled"] },
+ "billing_txn_id": { "type": ["string", "null"], "description": "The Billing transaction (txn_…) that settled it." },
+ "test": { "type": "boolean", "description": "Simulations never emit events, so false in practice." },
+ "reason": { "enum": ["payment_failed", "payment_reversed", "payment_not_settled"] },
+ "code": {
+ "type": "string",
+ "description": "Only with reason payment_failed: the Billing problem code (billing.insufficient_funds, billing.funding_reversed, billing.refused…)."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.failed.v1.json",
+ "title": "TipsInteractionFailedPayload",
+ "description": "tips.interaction.failed v1 (OpenVibe.Tips server/domain/interactions.js recomputeDelivery). A delivery effect gave up after its retries and none is still queued; the payment state is untouched. Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",
+ "type": "object",
+ "required": ["interaction_id", "creator", "supporter", "supporter_name", "kind", "amount", "currency", "settlement", "payment_state", "delivery_state", "billing_txn_id", "test", "failed_effects"],
+ "additionalProperties": false,
+ "properties": {
+ "interaction_id": { "type": "string", "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "creator": { "$ref": "../../identity/subject-ref.v1.json" },
+ "supporter": {
+ "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],
+ "description": "The supporter, null when external, anonymous or unmapped."
+ },
+ "supporter_name": { "type": ["string", "null"], "description": "Display name at the time." },
+ "kind": { "enum": ["tip", "paid_message", "tts", "media_request"] },
+ "amount": { "type": "integer", "minimum": 1, "description": "Integer amount in currency minor units (Vibes bits)." },
+ "currency": { "type": "string", "description": "vibes-bits today." },
+ "settlement": { "enum": ["billing", "external", "simulated", "imported"] },
+ "payment_state": { "enum": ["pending", "settled", "reversed", "failed"] },
+ "delivery_state": { "enum": ["awaiting_payment", "queued", "delivered", "failed", "cancelled"] },
+ "billing_txn_id": { "type": ["string", "null"], "description": "The Billing transaction (txn_…) that settled it." },
+ "test": { "type": "boolean", "description": "Simulations never emit events, so false in practice." },
+ "failed_effects": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object",
+ "required": ["effect", "adapter", "last_error"],
+ "additionalProperties": false,
+ "properties": {
+ "effect": { "enum": ["chat_line", "paid_message", "tts", "media_request", "overlay_alert"] },
+ "adapter": { "type": "string" },
+ "last_error": { "type": ["string", "null"] }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.ready.v1.json",
+ "title": "TipsInteractionReadyPayload",
+ "description": "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.",
+ "type": "object",
+ "required": ["interaction_id", "creator", "supporter", "supporter_name", "kind", "amount", "currency", "settlement", "payment_state", "delivery_state", "billing_txn_id", "test"],
+ "additionalProperties": false,
+ "properties": {
+ "interaction_id": { "type": "string", "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "creator": { "$ref": "../../identity/subject-ref.v1.json" },
+ "supporter": {
+ "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],
+ "description": "The supporter, null when external, anonymous or unmapped."
+ },
+ "supporter_name": { "type": ["string", "null"], "description": "Display name at the time." },
+ "kind": { "enum": ["tip", "paid_message", "tts", "media_request"] },
+ "amount": { "type": "integer", "minimum": 1, "description": "Integer amount in currency minor units (Vibes bits)." },
+ "currency": { "type": "string", "description": "vibes-bits today." },
+ "settlement": { "enum": ["billing", "external", "simulated", "imported"] },
+ "payment_state": { "enum": ["pending", "settled", "reversed", "failed"] },
+ "delivery_state": { "enum": ["awaiting_payment", "queued", "delivered", "failed", "cancelled"] },
+ "billing_txn_id": { "type": ["string", "null"], "description": "The Billing transaction (txn_…) that settled it." },
+ "test": { "type": "boolean", "description": "Simulations never emit events, so false in practice." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.delivered.v1.json",
+ "title": "TipsOverlayDeliveredPayload",
+ "description": "tips.overlay.delivered v1 (OpenVibe.Tips server/domain/overlays.js markDelivered). An overlay delivery (alert or goal update) reached at least one connected overlay, first time only. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",
+ "type": "object",
+ "required": ["delivery_id", "creator", "kind", "interaction_id", "goal_id"],
+ "additionalProperties": false,
+ "properties": {
+ "delivery_id": { "type": "string", "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "creator": { "$ref": "../../identity/subject-ref.v1.json" },
+ "kind": { "enum": ["alert", "goal"] },
+ "interaction_id": { "type": ["string", "null"] },
+ "goal_id": { "type": ["string", "null"] }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.failed.v1.json",
+ "title": "TipsOverlayFailedPayload",
+ "description": "tips.overlay.failed v1 (OpenVibe.Tips server/domain/overlays.js sweepFailed). No overlay showed the delivery within the delivery window. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",
+ "type": "object",
+ "required": ["delivery_id", "creator", "kind", "interaction_id", "goal_id", "reason"],
+ "additionalProperties": false,
+ "properties": {
+ "delivery_id": { "type": "string", "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "creator": { "$ref": "../../identity/subject-ref.v1.json" },
+ "kind": { "enum": ["alert", "goal"] },
+ "interaction_id": { "type": ["string", "null"] },
+ "goal_id": { "type": ["string", "null"] },
+ "reason": { "type": "string" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.created.v1.json",
+ "title": "ToolsJobCreatedPayload",
+ "description": "tools.job.created v1 (OpenVibe.Tools apps/_shared/jobs, planned). A job was accepted and queued (POST /api/v1/jobs, or a retry of a failed job, which carries retry_of). An Idempotency-Key replay creates no job and no event. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor the owner when it is a subject (user, service, app or mod), else service:tools.",
+ "type": "object",
+ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of"],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },
+ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },
+ "type_version": { "type": "integer", "minimum": 1 },
+ "state": { "const": "queued" },
+ "owner": {
+ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": { "type": "integer", "minimum": 0 },
+ "max_attempts": { "type": "integer", "minimum": 1 },
+ "created_at": { "type": "string", "format": "date-time" },
+ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.failed.v1.json",
+ "title": "ToolsJobFailedPayload",
+ "description": "tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",
+ "type": "object",
+ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of", "started_at", "finished_at", "expires_at", "error", "retryable"],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },
+ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },
+ "type_version": { "type": "integer", "minimum": 1 },
+ "state": { "const": "failed" },
+ "owner": {
+ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": { "type": "integer", "minimum": 0 },
+ "max_attempts": { "type": "integer", "minimum": 1 },
+ "created_at": { "type": "string", "format": "date-time" },
+ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." },
+ "started_at": { "type": ["string", "null"], "format": "date-time", "description": "null when the job failed before it ever ran." },
+ "finished_at": { "type": "string", "format": "date-time" },
+ "expires_at": { "type": ["string", "null"], "format": "date-time", "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)." },
+ "error": {
+ "type": "object", "required": ["status", "code", "detail"], "additionalProperties": false,
+ "description": "The job's problem (errors.problem@1) without request or trace ids.",
+ "properties": {
+ "status": { "type": "integer", "minimum": 400, "maximum": 599 },
+ "code": { "type": "string", "pattern": "^tools\\.[a-z0-9_.]+$", "description": "tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…)." },
+ "detail": { "type": "string", "maxLength": 500, "description": "Human-readable reason; server paths are scrubbed." }
+ }
+ },
+ "retryable": { "type": "boolean" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.started.v1.json",
+ "title": "ToolsJobStartedPayload",
+ "description": "tools.job.started v1 (OpenVibe.Tools apps/_shared/jobs, planned). A worker claimed the job and it is running (attempts counts this attempt). A job requeued after a restart starts again and is announced again. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:tools.",
+ "type": "object",
+ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of", "started_at"],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },
+ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },
+ "type_version": { "type": "integer", "minimum": 1 },
+ "state": { "const": "running" },
+ "owner": {
+ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": { "type": "integer", "minimum": 0 },
+ "max_attempts": { "type": "integer", "minimum": 1 },
+ "created_at": { "type": "string", "format": "date-time" },
+ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." },
+ "started_at": { "type": "string", "format": "date-time" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.succeeded.v1.json",
+ "title": "ToolsJobSucceededPayload",
+ "description": "tools.job.succeeded v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job finished and its result files are stored (in Media when TOOLS_JOB_RESULTS=media). Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",
+ "type": "object",
+ "required": ["job_id", "service", "type", "type_version", "state", "owner", "attempts", "max_attempts", "created_at", "retry_of", "started_at", "finished_at", "expires_at", "result"],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": { "type": "string", "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The Tools satellite that runs the job (img, audio, docs…)." },
+ "type": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$", "maxLength": 80, "description": "Job type, e.g. img.process." },
+ "type_version": { "type": "integer", "minimum": 1 },
+ "state": { "const": "succeeded" },
+ "owner": {
+ "oneOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": { "type": "integer", "minimum": 0 },
+ "max_attempts": { "type": "integer", "minimum": 1 },
+ "created_at": { "type": "string", "format": "date-time" },
+ "retry_of": { "type": ["string", "null"], "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)." },
+ "started_at": { "type": "string", "format": "date-time" },
+ "finished_at": { "type": "string", "format": "date-time" },
+ "expires_at": { "type": ["string", "null"], "format": "date-time", "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)." },
+ "result": {
+ "type": "object", "required": ["files"], "additionalProperties": false,
+ "description": "Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).",
+ "properties": {
+ "files": {
+ "type": "array", "maxItems": 100,
+ "items": {
+ "type": "object", "required": ["index", "mime", "size", "sha256", "storage", "media"], "additionalProperties": false,
+ "properties": {
+ "index": { "type": "integer", "minimum": 0, "description": "n in /api/v1/jobs/:id/files/:n." },
+ "mime": { "type": "string", "minLength": 1, "maxLength": 200 },
+ "size": { "type": "integer", "minimum": 0 },
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
+ "storage": { "enum": ["local", "media"], "description": "media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires." },
+ "media": { "oneOf": [{ "$ref": "../../media/media-ref.v1.json" }, { "type": "null" }], "description": "{ media_id, role: output } when storage is media." }
+ }
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/trade.alert.triggered.v1.json",
+ "title": "TradeAlertTriggeredPayload",
+ "description": "trade.alert.triggered v1 (OpenVibe.Trade server/domain/alerts.js deliver). One delivery of one private alert rule: a threshold crossed by a new observation (trigger kind observation), or a newly seen document of the instrument (filing_type / new_document rules, trigger kind document). Written in the transaction that recorded the observation or document; at most one per (rule, trigger). Envelope: subject { type: user, id: <rule owner usr_…> }, visibility subject, priority important, actor service:trade. Data only, never rendered notification text (ADR-020): OpenVibe.Network renders a TRADE_ALERT notification (server/notifications/events-consumer.js). Information only, not investment advice.",
+ "type": "object",
+ "required": ["delivery_id", "rule", "instrument", "trigger", "disclaimer"],
+ "additionalProperties": false,
+ "properties": {
+ "delivery_id": { "type": "string", "pattern": "^ald_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "rule": {
+ "type": "object", "additionalProperties": false,
+ "required": ["id", "kind", "status", "instrument", "metric", "operator", "threshold", "unit", "currency", "form_types", "armed", "created_at", "updated_at"],
+ "properties": {
+ "id": { "type": "string", "pattern": "^alr_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "kind": { "enum": ["threshold", "filing_type", "new_document"] },
+ "status": { "enum": ["active", "deleted"] },
+ "instrument": {
+ "type": "object", "required": ["id"], "additionalProperties": false,
+ "properties": { "id": { "type": "string", "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$" }, "symbol": { "type": "string" }, "name": { "type": "string" } }
+ },
+ "metric": { "type": ["string", "null"], "maxLength": 80, "description": "Threshold rules: e.g. price.close, volume, us-gaap:Revenues." },
+ "operator": { "enum": ["above", "below", null] },
+ "threshold": { "type": ["string", "null"], "maxLength": 64, "description": "Decimal text as entered." },
+ "unit": { "type": ["string", "null"], "maxLength": 20 },
+ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },
+ "form_types": { "type": ["array", "null"], "items": { "type": "string", "maxLength": 20 }, "description": "filing_type rules: the 1–10 form types that trigger it (10-K, 8-K…)." },
+ "armed": { "type": ["boolean", "null"], "description": "Threshold rules: armed before this delivery (always true when it fired); null for document rules." },
+ "created_at": { "type": "string", "format": "date-time" },
+ "updated_at": { "type": "string", "format": "date-time" }
+ }
+ },
+ "instrument": {
+ "type": "object", "required": ["id", "symbol", "name", "url"], "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "symbol": { "type": "string", "minLength": 1, "maxLength": 32 },
+ "name": { "type": "string", "minLength": 1, "maxLength": 300 },
+ "url": { "type": "string", "format": "uri", "pattern": "^https?://", "maxLength": 2048, "description": "Instrument page on Trade." }
+ }
+ },
+ "trigger": {
+ "oneOf": [
+ {
+ "type": "object", "additionalProperties": false,
+ "required": ["kind", "id", "metric", "value", "unit", "currency", "observed_at", "retrieved_at", "source_key", "source_url"],
+ "properties": {
+ "kind": { "const": "observation" },
+ "id": { "type": "string", "pattern": "^obs_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "metric": { "type": "string", "minLength": 1, "maxLength": 80 },
+ "value": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The decimal exactly as the source stated it." },
+ "unit": { "type": "string", "minLength": 1, "maxLength": 20 },
+ "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },
+ "observed_at": { "type": "string", "format": "date-time" },
+ "retrieved_at": { "type": "string", "format": "date-time" },
+ "source_key": { "type": "string", "minLength": 1, "maxLength": 128, "description": "OpenVibe.Sources source key." },
+ "source_url": { "type": ["string", "null"], "maxLength": 2048 }
+ }
+ },
+ {
+ "type": "object", "additionalProperties": false,
+ "required": ["kind", "id", "form_type", "title", "url", "published_at", "retrieved_at", "source_key"],
+ "properties": {
+ "kind": { "const": "document" },
+ "id": { "type": "string", "pattern": "^doc_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "form_type": { "type": ["string", "null"], "maxLength": 20 },
+ "title": { "type": ["string", "null"], "maxLength": 1000 },
+ "url": { "type": ["string", "null"], "maxLength": 2048 },
+ "published_at": { "type": ["string", "null"], "format": "date-time", "description": "The source's own date; null if it gave none." },
+ "retrieved_at": { "type": "string", "format": "date-time" },
+ "source_key": { "type": "string", "minLength": 1, "maxLength": 128 }
+ }
+ }
+ ]
+ },
+ "disclaimer": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Currently \"Information only — not investment advice; no trading here.\"" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vip.membership.changed.v1.json",
+ "title": "VipMembershipChangedPayload",
+ "description": "vip.membership.changed v1 (OpenVibe.VIP server/domain/entitlements.js emitChanged). A member's standing with a creator changed as projected from Billing (granted, renewed, cancel scheduled, expired, refunded…): active, expires_at or cancel_at_period_end differ from before, or a first look found an active membership. Names the plan version the membership was bought under. Envelope: subject { type: membership, id: <member usr_…>:<creator usr_…> }, visibility internal, priority important, actor service:vip.",
+ "type": "object",
+ "required": ["member", "creator", "kind", "active", "expires_at", "cancel_at_period_end", "previous", "reason", "source", "billing_event_id", "membership_id", "plan_id", "plan_version_id", "plan_version", "subscription_id"],
+ "additionalProperties": false,
+ "properties": {
+ "member": { "$ref": "../../identity/subject-ref.v1.json" },
+ "creator": { "$ref": "../../identity/subject-ref.v1.json" },
+ "kind": { "type": "string", "description": "Billing entitlement kind; channel_subscription today." },
+ "active": { "type": "boolean" },
+ "expires_at": { "type": ["string", "null"] },
+ "cancel_at_period_end": { "type": "boolean" },
+ "previous": {
+ "type": ["object", "null"],
+ "required": ["active", "expires_at", "cancel_at_period_end"],
+ "additionalProperties": false,
+ "properties": { "active": { "type": "boolean" }, "expires_at": { "type": ["string", "null"] }, "cancel_at_period_end": { "type": "boolean" } },
+ "description": "The projection before the change; null on first sight."
+ },
+ "reason": { "type": ["string", "null"], "description": "Billing's reason (granted, renewed, refund, expired…) or VIP's (checked, cancel_scheduled)." },
+ "source": { "enum": ["event", "billing_check", "import"] },
+ "billing_event_id": { "type": ["string", "null"], "description": "The Billing event (evt_…) behind the change, when it came from one." },
+ "membership_id": { "type": ["string", "null"], "pattern": "^vms_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "plan_id": { "type": ["string", "null"] },
+ "plan_version_id": { "type": ["string", "null"] },
+ "plan_version": { "type": ["integer", "null"], "minimum": 1 },
+ "subscription_id": { "type": ["string", "null"], "description": "The Billing subscription (sub_…)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vip.plan.published.v1.json",
+ "title": "VipPlanPublishedPayload",
+ "description": "vip.plan.published v1 (OpenVibe.VIP server/domain/plans.js emitPublished). A plan version became what new members buy: the plan was published, or a published plan was edited (a new immutable version). Carries the version's full terms, never a price (Billing prices and charges). Envelope: subject { type: plan, id: vpl_…, revision: <version> }, visibility public, priority important, actor service:vip.",
+ "type": "object",
+ "required": ["plan_id", "slug", "version", "version_id", "creator", "billing_kind", "terms", "published_at"],
+ "additionalProperties": false,
+ "properties": {
+ "plan_id": { "type": "string", "pattern": "^vpl_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "slug": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,47}$" },
+ "version": { "type": "integer", "minimum": 1 },
+ "version_id": { "type": "string", "pattern": "^vpv_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "creator": {
+ "type": "object",
+ "required": ["id", "kind", "subject", "username", "display_name", "bio", "status"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string", "description": "vcr_… or network." },
+ "kind": { "enum": ["creator", "network"] },
+ "subject": { "anyOf": [{ "$ref": "../../identity/subject-ref.v1.json" }, { "type": "null" }], "description": "null for the network creator." },
+ "username": { "type": ["string", "null"] },
+ "display_name": { "type": ["string", "null"] },
+ "bio": { "type": ["string", "null"] },
+ "status": { "enum": ["active", "suspended"] }
+ }
+ },
+ "billing_kind": {
+ "enum": ["channel_subscription", null],
+ "description": "The Billing product that sells it; null for a plan Billing does not sell (network plans)."
+ },
+ "terms": {
+ "type": "object",
+ "required": ["name", "description", "benefits", "perks", "billing_kind", "price", "digest"],
+ "additionalProperties": false,
+ "properties": {
+ "name": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "benefits": { "type": ["array", "null"], "maxItems": 20, "items": { "type": "string" } },
+ "perks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["key", "name", "kind", "scope"],
+ "additionalProperties": false,
+ "properties": {
+ "key": { "type": "string" },
+ "name": { "type": "string" },
+ "kind": { "enum": ["badge", "emote", "gated_content", "room", "role", "other"] },
+ "scope": { "enum": ["creator", "network"] }
+ }
+ }
+ },
+ "billing_kind": { "enum": ["channel_subscription", null] },
+ "price": { "type": ["string", "null"], "description": "Never an amount: a note that Billing sets and charges the price, or null." },
+ "digest": { "type": "string", "pattern": "^[0-9a-f]{16}$", "description": "sha256 prefix of name, description, benefits, perks and billing_kind." }
+ }
+ },
+ "published_at": { "type": "string", "format": "date-time" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "$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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "$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" },
+ {
+ "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 } }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$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).",
+ "type": "object",
+ "required": ["canonical_url", "publication_state", "indexability", "space", "slug", "visibility"],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },
+ "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": "^[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)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$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": "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)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$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).",
+ "type": "object",
+ "required": ["canonical_url", "publication_state", "indexability", "space", "slug", "visibility"],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": { "type": ["string", "null"], "description": "null: the event carries a tombstone, which has no URL." },
+ "publication_state": { "const": "unpublished" },
+ "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": "^[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)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$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).",
+ "type": "object",
+ "required": ["canonical_url", "publication_state", "indexability", "space", "slug", "visibility"],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": { "type": "string", "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)." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "$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.",
+ "type": "object",
+ "required": ["space_id", "space", "slug", "number", "kind", "parent_number", "reverted_to", "authorship", "author", "message"],
+ "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": { "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 },
+ "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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "$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." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "$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", "null"], "minimum": 1 },
+ "url": { "type": "string", "format": "uri", "pattern": "^https?://", "maxLength": 2048 },
+ "recipients": {
+ "type": "array", "minItems": 1, "uniqueItems": true,
+ "items": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "description": "Watchers to notify."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/redaction-directive.v1.json",
+ "title": "RedactionDirective",
+ "description": "payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.",
+ "type": "object",
+ "minProperties": 1,
+ "additionalProperties": false,
+ "dependentRequired": { "subject_type": ["subject_ids"], "subject_ids": ["subject_type"] },
+ "properties": {
+ "event_ids": {
+ "type": "array", "minItems": 1, "maxItems": 1000,
+ "items": { "type": "string", "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "description": "Events to redact by id. Duplicates are ignored."
+ },
+ "subject_type": {
+ "type": "string", "pattern": "^[a-z][a-z0-9_]{0,63}$",
+ "description": "Envelope subject.type of the events to redact (chat_message, post…). Goes with subject_ids."
+ },
+ "subject_ids": {
+ "type": "array", "minItems": 1, "maxItems": 1000,
+ "items": { "type": "string", "minLength": 1, "maxLength": 200 },
+ "description": "Envelope subject.id values; every not-yet-redacted event of the same source about each one is redacted."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/tombstone-payload.v1.json",
+ "title": "TombstonePayload",
+ "description": "The payload of a redacted event (ADR-026). OpenVibe.Events replaces the original payload with this and the actor with the producer itself ({ type: service, id: <source> }, or the app); event_id, seq, event_type, version, subject, timestamp and visibility stay. Every read path serves it from then on: pull, GET by id, SSE replay, queued deliveries and DLQ replays. A consumer checks payload.redacted before validating a payload against its event type's schema.",
+ "type": "object",
+ "required": ["redacted", "redacted_at", "redacted_by"],
+ "additionalProperties": false,
+ "properties": {
+ "redacted": { "const": true },
+ "redacted_at": { "type": "string", "format": "date-time" },
+ "redacted_by": {
+ "type": "string", "minLength": 1, "maxLength": 64,
+ "description": "The event_id of the redacting event; an operator label (e.g. backfill) when OpenVibe.Events redacted by script."
+ }
+ }
+}
@@ -20,6 +20,12 @@ Cross-service side effects are best-effort POSTs and HMAC webhooks; a consumer t
@@ -20,6 +20,12 @@ Cross-service side effects are best-effort POSTs and HMAC webhooks; a consumer t
- Consumers accept a v2 signature only within ±300 s of their clock, compare in constant time, and never fall back to v1 when a v2 header is present but bad or stale. With `requireV2` they also refuse deliveries without v2, which closes the replay-by-stripping-the-header path.
- Consumers accept a v2 signature only within ±300 s of their clock, compare in constant time, and never fall back to v1 when a v2 header is present but bad or stale. With `requireV2` they also refuse deliveries without v2, which closes the replay-by-stripping-the-header path.
- Rollout is additive: Events sends v1 and v2 first; consumers then move to openvibe-sdk 0.4.0 (v2 checked whenever present) and set `requireV2: true` one by one; v1 stays on the wire until every consumer requires v2, and only then may it be removed. Rollback at any step is to unset `requireV2` (consumers) or stop sending v2 (Events, only while no consumer requires it).
- Rollout is additive: Events sends v1 and v2 first; consumers then move to openvibe-sdk 0.4.0 (v2 checked whenever present) and set `requireV2: true` one by one; v1 stays on the wire until every consumer requires v2, and only then may it be removed. Rollback at any step is to unset `requireV2` (consumers) or stop sending v2 (Events, only while no consumer requires it).
+### Redaction, payload contracts and the public replay window (2026-09-23)
+
+- A producer takes back its own events with `payload.redacts` (`events.redaction-directive@1`). Events turns the targets into tombstones (`events.tombstone-payload@1`) at their original seq. Naming another source's event is 403 `events.redaction_not_allowed`, and a malformed directive is 422 `events.invalid_redaction`. See [ADR-026](ADR-026-event-redaction.md).
+- Each event type's payload has a contract named after the type, with the envelope `version` as its major (`contracts/events/payloads/<event_type>.v<version>.json`, from v0.30.0).
+- Browsers are replayed only the `public` events of the last `REALTIME_PUBLIC_REPLAY_SECONDS` (default 300). An older cursor gets `event: gap` with reason `public_window`.
+
## Alternatives considered
## Alternatives considered
- Kafka/NATS now: rejected, operational weight far beyond current volume.
- Kafka/NATS now: rejected, operational weight far beyond current volume.
new file mode 100644
new file mode 100644
@@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
+# ADR-026: Producer redaction of its own events (`payload.redacts`)
+
+**Status:** Accepted 2026-09-23; implemented in OpenVibe.Events (`server/redaction.js`, `server/store.js`,
+`test/redaction.test.js`) and used by OpenVibe.Chat (`chat.message.deleted`).
+
+## Context and current evidence
+
+OpenVibe.Events keeps every event for its retention period and serves it again through pull, `GET
+/api/v1/events/:id`, SSE replay, queued deliveries and DLQ replays (ADR-004, ADR-005). A public
+`chat.message.created` carries the message text and the author. When the message was deleted in Chat,
+the stored event stayed replayable, anonymously, for 30 days. Deleting a row in the producer did not
+reach the copy in Events, and no consumer could tell that the content was withdrawn.
+
+## Decision
+
+- **Any event may carry `payload.redacts`** (`events.redaction-directive@1`):
+ `{ "event_ids": ["evt_…"], "subject_type": "chat_message", "subject_ids": ["123"] }`. `event_ids`
+ names events directly. `subject_type` + `subject_ids` (always together) names every stored event of
+ the same source about those subjects. Either form or both may be used, with up to 1000 ids each and no
+ empty list. Normally the directive rides on the producer's own deletion event (`chat.message.deleted`),
+ whose payload carries only ids.
+- **A producer may redact only its own events.** A target must have the same `source` as the redacting
+ event; for a developer app it must also be in the same project and environment. When an `event_id`
+ names another source's event, Events refuses the whole publish batch with **403
+ `events.redaction_not_allowed`**. A subject match never reaches another source. No capability is
+ needed beyond `events.event.publish` (or `events.app.publish`).
+- **A malformed directive is 422 `events.invalid_redaction`**. This covers a non-object, an unknown
+ field, a bad or too long id list, `subject_type` without `subject_ids` (or the reverse), and a
+ directive that names nothing.
+- **Targets become tombstones in the same transaction that stores the directive.** Their payload is
+ replaced by `{ "redacted": true, "redacted_at": "<ISO>", "redacted_by": "<redacting event_id>" }`
+ (`events.tombstone-payload@1`), and the actor by the producer itself (`service:<source>`, or the app).
+ `event_id`, `seq`, `event_type`, `version`, `subject`, `timestamp` and `visibility` stay, so sequences
+ have no holes. The old payload is overwritten on disk (`secure_delete`).
+- **Every read path serves the tombstone** from then on. First-party consumers receive the tombstone at
+ the original seq and then the deletion event. **A consumer checks `payload.redacted === true` before
+ validating a payload against its event type's schema.** A tombstone never matches the type's payload
+ contract.
+- An event that carries a directive is never redacted itself. Redaction applies to what is stored when
+ the directive arrives; it does not block later events about the same subject.
+- An operator may redact events published before the producer announced deletions
+ (`scripts/redact-backfill.js` in OpenVibe.Events, dry run by default). Those tombstones name the
+ operator run (e.g. `backfill`) as `redacted_by`.
+- **The payload contract lists `redacts` when the producer sends it.** It is `"redacts": { "$ref":
+ "../redaction-directive.v1.json" }`, required when every event of that type carries one
+ (`chat.message.deleted`). A producer that starts redacting from another event type adds the optional
+ property in a minor contract release.
+
+### Public realtime replay window
+
+Browsers (signed out or signed in) are replayed `public` events received in the last
+`REALTIME_PUBLIC_REPLAY_SECONDS` only (default **300**), which is enough to ride out a reconnect. An
+older cursor gets `event: gap` with `reason: "public_window"` (`from_seq`, `to_seq`, `latest_seq`,
+`window_seconds`), so the SSE stream cannot be used to page through a month of public history.
+`subject` events addressed to the viewer, and service viewers, keep the whole retention. Redacted
+events are replayed as their tombstones.
+
+## Event payload contracts
+
+Each event type's payload has its own contract, named after the event type. The major version is the
+envelope `version`. The file is `contracts/events/payloads/<event_type>.v<version>.json`, so a
+consumer validates `contracts.validate(`${env.event_type}@${env.version}`, env.payload)` after
+checking for a tombstone. The owner is the producing service, and the service manifest lists the type
+in `eventsProduced`. Contracts with catalog status `planned` describe events that the owner has not
+emitted yet (`tools.job.*` in v0.30.0).
+
+## Alternatives considered
+
+- **Hard delete in Events:** rejected, because it leaves holes in `seq` that break every cursor and
+ resume, and consumers could not tell a withdrawal from a lost event.
+- **A separate redaction endpoint and capability:** rejected, because the directive has to be atomic with
+ the producer's own outbox (the deletion and its announcement commit together). The publish owner rule
+ already gives exactly the authority needed.
+- **Consumers filtering deleted ids themselves:** rejected, because anonymous replays and deliveries that
+ are already queued would still carry the text.
+
+## Migration consequences
+
+Chat publishes `chat.message.deleted` with `redacts` for every delete path. The operator backfill
+redacted what Chat had deleted earlier. Other producers adopt the directive on their own `*.deleted` or
+`*.removed` events when those carry personal content.
+
+## Rollback
+
+A producer stops sending `redacts`. Tombstones already written stay tombstones, because the original
+payload is gone by design.
+
+## Acceptance tests
+
+OpenVibe.Events `test/redaction.test.js`: every read path (pull, GET, anonymous/signed-in/service SSE
+replay, queued delivery, DLQ replay) returns the tombstone at an unchanged seq; another source's event
+is refused with 403 and the whole batch is not stored; a malformed directive is 422; a directive event
+is never redacted. The public window test checks that anonymous replay older than 300 s yields `gap`
+`public_window`. Contracts: `events.redaction-directive@1` and `events.tombstone-payload@1` fixtures;
+every event payload contract has valid and invalid fixtures.
@@ -29,4 +29,5 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -29,4 +29,5 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-023](ADR-023-client-surfaces.md) | First-party client surfaces | Accepted 2026-09-23: OpenVibe.Extensions at Wave 18 |
| [ADR-023](ADR-023-client-surfaces.md) | First-party client surfaces | Accepted 2026-09-23: OpenVibe.Extensions at Wave 18 |
| [ADR-024](ADR-024-theme-authority.md) | Theme authority between Network and Shared | Accepted 2026-09-23 |
| [ADR-024](ADR-024-theme-authority.md) | Theme authority between Network and Shared | Accepted 2026-09-23 |
| [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational |
| [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational |
+| [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) |
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "workflow": "live.chat_reply", "on_behalf_of": { "type": "user", "id": "42" } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "input": { "text": "hi" } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "workflow": "summarize", "input": {} }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "workflow": "network.site_copy", "input": { "sites": [] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "workflow": "news.summarize_story",
+ "input": { "topic": "Transit strike", "sources": [{ "source_type": "news.item", "source_id": "itm_42", "url": "https://example.com/a", "title": "Strike begins" }] },
+ "target": { "service": "news", "type": "story", "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
+ "idempotency_key": "news:story:sty_01JAB2C3:v3",
+ "options": { "cache": false }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "cached",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "attribution": null,
+ "provider": "anthropic",
+ "model": "claude-sonnet",
+ "fallback_used": true,
+ "synthetic": false,
+ "cached_from": null,
+ "usage": {
+ "tokens_in": 1200,
+ "tokens_out": 300,
+ "cost_usd": 0.0081,
+ "attempts": 2
+ },
+ "error": null,
+ "citations_count": 1,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "status": "cached",
+ "workflow": {
+ "key": "network.site_copy",
+ "version": 1
+ },
+ "requester": {
+ "type": "service",
+ "id": "network"
+ },
+ "on_behalf_of": null,
+ "target": null,
+ "attribution": null,
+ "provider": null,
+ "model": "claude-sonnet",
+ "fallback_used": false,
+ "synthetic": false,
+ "cached_from": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "usage": {
+ "tokens_in": 0,
+ "tokens_out": 0,
+ "cost_usd": 0,
+ "attempts": 0
+ },
+ "error": null,
+ "citations_count": 0,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": "2026-09-23T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "failed",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "attribution": null,
+ "provider": "anthropic",
+ "model": "claude-sonnet",
+ "fallback_used": true,
+ "synthetic": false,
+ "cached_from": null,
+ "usage": {
+ "tokens_in": 1200,
+ "tokens_out": 300,
+ "cost_usd": 0.0081,
+ "attempts": 2
+ },
+ "error": null,
+ "citations_count": 1,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "failed",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "attribution": null,
+ "provider": null,
+ "model": null,
+ "fallback_used": false,
+ "synthetic": false,
+ "cached_from": null,
+ "usage": {
+ "tokens_in": 0,
+ "tokens_out": 0,
+ "cost_usd": 0,
+ "attempts": 1
+ },
+ "error": {
+ "code": "run.interrupted",
+ "detail": "the service restarted while this run was in progress"
+ },
+ "citations_count": 0,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "queued",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "attribution": null,
+ "provider": "anthropic",
+ "model": "claude-sonnet",
+ "fallback_used": true,
+ "synthetic": false,
+ "cached_from": null,
+ "usage": {
+ "tokens_in": 1200,
+ "tokens_out": 300,
+ "cost_usd": 0.0081,
+ "attempts": 2
+ },
+ "error": null,
+ "citations_count": 1,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "queued",
+ "workflow": {
+ "key": "live.chat_reply",
+ "version": 1
+ },
+ "requester": {
+ "type": "service",
+ "id": "live"
+ },
+ "on_behalf_of": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "target": null,
+ "attribution": null,
+ "provider": null,
+ "model": null,
+ "fallback_used": false,
+ "synthetic": false,
+ "cached_from": null,
+ "usage": {
+ "tokens_in": 0,
+ "tokens_out": 0,
+ "cost_usd": 0,
+ "attempts": 0
+ },
+ "error": null,
+ "citations_count": 0,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "succeeded",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "attribution": null,
+ "provider": "anthropic",
+ "model": "claude-sonnet",
+ "fallback_used": true,
+ "synthetic": false,
+ "cached_from": null,
+ "usage": {
+ "tokens_in": 1200,
+ "tokens_out": 300,
+ "cost_usd": 0.0081,
+ "attempts": 2
+ },
+ "error": null,
+ "citations_count": 1,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": "2026-09-23T10:00:04.200Z",
+ "output": {
+ "summary": "x"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "succeeded",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "attribution": null,
+ "provider": "anthropic",
+ "model": "claude-sonnet",
+ "fallback_used": true,
+ "synthetic": false,
+ "cached_from": null,
+ "usage": {
+ "tokens_in": 1200,
+ "tokens_out": 300,
+ "cost_usd": 0.0081,
+ "attempts": 2
+ },
+ "error": {
+ "code": "run.internal",
+ "detail": "internal error"
+ },
+ "citations_count": 1,
+ "retry_of": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ", "status": "succeeded",
+ "workflow": { "key": "news.summarize_story", "version": 2 },
+ "requester": { "type": "service", "id": "news" }, "on_behalf_of": null,
+ "target": { "service": "news", "type": "story", "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ" }, "attribution": null,
+ "provider": "anthropic", "model": "claude-sonnet", "fallback_used": true, "synthetic": false, "cached_from": null,
+ "usage": { "tokens_in": 1200, "tokens_out": 300, "cost_usd": 0.0081, "attempts": 2 }, "error": null,
+ "citations_count": 1, "retry_of": null, "created_at": "2026-09-23T10:00:00.000Z", "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
+{
+ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "succeeded",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "template": {
+ "key": "news.summarize_story",
+ "version": 3
+ },
+ "route": {
+ "key": "default.text",
+ "version": 1
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "attribution": null,
+ "source_service": "news",
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "input": {
+ "topic": "Transit strike",
+ "sources": [
+ {
+ "source_type": "news.item",
+ "source_id": "itm_42"
+ }
+ ]
+ },
+ "output": {
+ "headline": "Transit strike begins",
+ "summary": "Workers walked out.",
+ "citations": [
+ 0
+ ],
+ "gaps": []
+ },
+ "error": null,
+ "synthetic": false,
+ "provenance": {
+ "origin": "ai",
+ "workflow": "news.summarize_story",
+ "workflow_version": 2,
+ "template_version": 3,
+ "route": "default.text",
+ "route_version": 1,
+ "provider": "anthropic",
+ "model": "claude-sonnet",
+ "fallback_used": true,
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "cached_from": null,
+ "synthetic": false
+ },
+ "citations_count": 1,
+ "retry_of": null,
+ "idempotency_key": null,
+ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "started_at": "2026-09-23T10:00:00.050Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
+{
+ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "done",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "template": {
+ "key": "news.summarize_story",
+ "version": 3
+ },
+ "route": {
+ "key": "default.text",
+ "version": 1
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "attribution": null,
+ "source_service": "news",
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "input": {
+ "topic": "Transit strike",
+ "sources": [
+ {
+ "source_type": "news.item",
+ "source_id": "itm_42"
+ }
+ ]
+ },
+ "output": {
+ "headline": "Transit strike begins",
+ "summary": "Workers walked out.",
+ "citations": [
+ 0
+ ],
+ "gaps": []
+ },
+ "error": null,
+ "synthetic": false,
+ "provenance": {
+ "origin": "ai",
+ "workflow": "news.summarize_story",
+ "workflow_version": 2,
+ "template_version": 3,
+ "route": "default.text",
+ "route_version": 1,
+ "provider": "anthropic",
+ "model": "claude-sonnet",
+ "fallback_used": true,
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "cached_from": null,
+ "synthetic": false
+ },
+ "usage": {
+ "tokens_in": 1200,
+ "tokens_out": 300,
+ "cost_usd": 0.0081,
+ "attempts": 2
+ },
+ "citations_count": 1,
+ "retry_of": null,
+ "idempotency_key": null,
+ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "started_at": "2026-09-23T10:00:00.050Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
+{
+ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "status": "queued",
+ "workflow": {
+ "key": "news.summarize_story",
+ "version": 2
+ },
+ "template": null,
+ "route": {
+ "key": "live.{stream}.vision",
+ "version": null
+ },
+ "requester": {
+ "type": "service",
+ "id": "news"
+ },
+ "on_behalf_of": null,
+ "attribution": null,
+ "source_service": "news",
+ "target": {
+ "service": "news",
+ "type": "story",
+ "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "input": {
+ "not_retained": "passthrough prompt; see input_hash"
+ },
+ "output": null,
+ "error": null,
+ "synthetic": false,
+ "provenance": {
+ "origin": "ai",
+ "workflow": "news.summarize_story",
+ "workflow_version": 2,
+ "template_version": 3,
+ "route": "default.text",
+ "route_version": 1,
+ "provider": null,
+ "model": null,
+ "fallback_used": false,
+ "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "cached_from": null,
+ "synthetic": false
+ },
+ "usage": {
+ "tokens_in": 0,
+ "tokens_out": 0,
+ "cost_usd": 0,
+ "attempts": 0
+ },
+ "citations_count": 0,
+ "retry_of": null,
+ "idempotency_key": null,
+ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "started_at": null,
+ "finished_at": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
+{
+ "id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "status": "succeeded",
+ "workflow": { "key": "news.summarize_story", "version": 2 },
+ "template": { "key": "news.summarize_story", "version": 3 },
+ "route": { "key": "default.text", "version": 1 },
+ "requester": { "type": "service", "id": "news" },
+ "on_behalf_of": null,
+ "attribution": null,
+ "source_service": "news",
+ "target": { "service": "news", "type": "story", "id": "sty_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
+ "input": { "topic": "Transit strike", "sources": [{ "source_type": "news.item", "source_id": "itm_42" }] },
+ "output": { "headline": "Transit strike begins", "summary": "Workers walked out.", "citations": [0], "gaps": [] },
+ "error": null,
+ "synthetic": false,
+ "provenance": {
+ "origin": "ai", "workflow": "news.summarize_story", "workflow_version": 2, "template_version": 3, "route": "default.text", "route_version": 1,
+ "provider": "anthropic", "model": "claude-sonnet", "fallback_used": true, "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ", "cached_from": null, "synthetic": false
+ },
+ "usage": { "tokens_in": 1200, "tokens_out": 300, "cost_usd": 0.0081, "attempts": 2 },
+ "citations_count": 1,
+ "retry_of": null,
+ "idempotency_key": null,
+ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "started_at": "2026-09-23T10:00:00.050Z",
+ "finished_at": "2026-09-23T10:00:04.200Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "cashout": {
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "amount_bits": 5000,
+ "value_cents": 5000,
+ "status": "denied",
+ "payout_method": {
+ "type": "paypal",
+ "address": "[email protected]"
+ },
+ "escrow_until": "2026-09-30T10:00:00.000Z",
+ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",
+ "settle_txn": null,
+ "payout_provider": null,
+ "payout_reference": null,
+ "reason": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T10:00:00.000Z"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "cashout": {
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "amount_bits": 5000,
+ "value_cents": 5000,
+ "status": "denied",
+ "payout_method": {
+ "type": "paypal",
+ "address": "[email protected]"
+ },
+ "escrow_until": "2026-09-30T10:00:00.000Z",
+ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",
+ "settle_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ8",
+ "payout_provider": null,
+ "payout_reference": null,
+ "reason": "payout details do not match",
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T10:00:00.000Z"
+ },
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ8"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "cashout": {
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "amount_bits": 50.5,
+ "value_cents": 5000,
+ "status": "paid",
+ "payout_method": {
+ "type": "paypal",
+ "address": "[email protected]"
+ },
+ "escrow_until": "2026-09-30T10:00:00.000Z",
+ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",
+ "settle_txn": null,
+ "payout_provider": null,
+ "payout_reference": null,
+ "reason": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T10:00:00.000Z"
+ },
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "cashout": {
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "amount_bits": 5000,
+ "value_cents": 5000,
+ "status": "paid",
+ "payout_method": {
+ "type": "paypal",
+ "address": "[email protected]"
+ },
+ "escrow_until": "2026-09-30T10:00:00.000Z",
+ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",
+ "settle_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ7",
+ "payout_provider": "paypal",
+ "payout_reference": "8XK12345AB678901C",
+ "reason": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T10:00:00.000Z"
+ },
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "cashout": {
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "amount_bits": 5000,
+ "value_cents": 5000,
+ "status": "paid",
+ "payout_method": {
+ "type": "paypal",
+ "address": "[email protected]"
+ },
+ "escrow_until": "2026-09-30T10:00:00.000Z",
+ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",
+ "settle_txn": null,
+ "payout_provider": null,
+ "payout_reference": null,
+ "reason": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T10:00:00.000Z"
+ },
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "cashout": {
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "amount_bits": 5000,
+ "value_cents": 5000,
+ "status": "requested",
+ "payout_method": {
+ "type": "paypal",
+ "address": "[email protected]"
+ },
+ "escrow_until": "2026-09-30T10:00:00.000Z",
+ "request_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6",
+ "settle_txn": null,
+ "payout_provider": null,
+ "payout_reference": null,
+ "reason": null,
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T10:00:00.000Z"
+ },
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ6"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "network_membership",
+ "active": true,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "status": "active",
+ "auto_renew": true,
+ "cancel_at_period_end": false,
+ "provider": "credit"
+ },
+ "reason": "granted",
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ4"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": true,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "status": "active",
+ "auto_renew": true,
+ "cancel_at_period_end": false,
+ "provider": "credit"
+ },
+ "reason": "granted",
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ4"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": false,
+ "expires_at": null,
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "status": "expired",
+ "auto_renew": false,
+ "cancel_at_period_end": false,
+ "provider": "credit"
+ },
+ "reason": "refund",
+ "revoked_period": {
+ "starts_at": "2026-09-23T10:00:00.000Z",
+ "ends_at": "2026-10-23T10:00:00.000Z"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "classification": "SITE",
+ "provider": "powerchat",
+ "receipt_ref": "powerchat:ext-1",
+ "provider_event_id": "ext-1",
+ "delivery_id": "dlv-ext-1",
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "receiving_account": {
+ "provider": "powerchat",
+ "id": "981",
+ "username": "japaneseoldguy"
+ },
+ "amount_cents": 1234,
+ "currency": "usd-cents",
+ "value_bits": 1234,
+ "donor_name": "Generous Fan",
+ "anonymous": false,
+ "message": "love the stream",
+ "app_ref": null,
+ "app_purpose": "goal:12",
+ "occurred_at": "2026-09-23T20:00:00Z",
+ "test": false,
+ "rates": {
+ "bits_per_usd": 100
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "classification": "EXTERNAL",
+ "provider": "powerchat",
+ "receipt_ref": "powerchat:ext-1",
+ "provider_event_id": "ext-1",
+ "delivery_id": "dlv-ext-1",
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "receiving_account": {
+ "provider": "powerchat",
+ "id": "981",
+ "username": "japaneseoldguy"
+ },
+ "amount_cents": 0,
+ "currency": "usd-cents",
+ "value_bits": 1234,
+ "donor_name": "Generous Fan",
+ "anonymous": false,
+ "message": "love the stream",
+ "app_ref": null,
+ "app_purpose": "goal:12",
+ "occurred_at": "2026-09-23T20:00:00Z",
+ "test": false,
+ "rates": {
+ "bits_per_usd": 100
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "classification": "EXTERNAL",
+ "provider": "powerchat",
+ "receipt_ref": "powerchat:ext-1",
+ "provider_event_id": "ext-1",
+ "delivery_id": "dlv-ext-1",
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "receiving_account": {
+ "provider": "powerchat",
+ "id": "981",
+ "username": "japaneseoldguy"
+ },
+ "amount_cents": 1234,
+ "currency": "usd-cents",
+ "value_bits": 1234,
+ "donor_name": null,
+ "anonymous": true,
+ "message": null,
+ "app_ref": null,
+ "app_purpose": null,
+ "occurred_at": "2026-09-23T20:00:00Z",
+ "test": false,
+ "rates": {
+ "bits_per_usd": 100
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "classification": "EXTERNAL",
+ "provider": "powerchat",
+ "receipt_ref": "powerchat:ext-1",
+ "provider_event_id": "ext-1",
+ "delivery_id": "dlv-ext-1",
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "receiving_account": {
+ "provider": "powerchat",
+ "id": "981",
+ "username": "japaneseoldguy"
+ },
+ "amount_cents": 1234,
+ "currency": "usd-cents",
+ "value_bits": 1234,
+ "donor_name": "Generous Fan",
+ "anonymous": false,
+ "message": "love the stream",
+ "app_ref": null,
+ "app_purpose": "goal:12",
+ "occurred_at": "2026-09-23T20:00:00Z",
+ "test": false,
+ "rates": {
+ "bits_per_usd": 100
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "audit_id": "sa_01JAB3C4D5E6F7G8H9J0K1MNQ9",
+ "action": "cashout.approve",
+ "outcome": "refused",
+ "target": {
+ "type": "cashout",
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5"
+ },
+ "reason": "verified payout email",
+ "request_id": "req_abcdefgh",
+ "detail": {
+ "payout_reference": "8XK12345AB678901C"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "audit_id": "sa_01JAB3C4D5E6F7G8H9J0K1MNQ9",
+ "action": "cashout.approve",
+ "outcome": "done",
+ "target": {
+ "type": "cashout",
+ "id": "co_01JAB3C4D5E6F7G8H9J0K1MNQ5"
+ },
+ "reason": "verified payout email",
+ "request_id": "req_abcdefgh",
+ "detail": {
+ "payout_reference": "8XK12345AB678901C"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "subscriber": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "tier": 1,
+ "provider": "stripe",
+ "provider_ref": "sub_1Q2w3E4r5T6y",
+ "route": "site",
+ "status": "active",
+ "auto_renew": false,
+ "cancel_at_period_end": true,
+ "price_cents": 499,
+ "current_period_end": "2026-10-23T10:00:00.000Z",
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T11:00:00.000Z"
+ },
+ "provider_sync": "not_needed"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+{
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "subscriber": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "tier": 1,
+ "provider": "stripe",
+ "provider_ref": "sub_1Q2w3E4r5T6y",
+ "route": "site",
+ "status": "active",
+ "auto_renew": false,
+ "cancel_at_period_end": true,
+ "price_cents": 499,
+ "current_period_end": "2026-10-23T10:00:00.000Z",
+ "created_at": "2026-09-23T10:00:00.000Z",
+ "updated_at": "2026-09-23T11:00:00.000Z"
+ },
+ "provider_sync": "stripe_cancel_at_period_end",
+ "actor": {
+ "principal": "svc:vip",
+ "request_id": "req_abcdefgh",
+ "on_behalf_of": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",
+ "type": "refund",
+ "test": false,
+ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "provider": null,
+ "metadata": {
+ "amount_bits": 500,
+ "target": {
+ "service": "tips",
+ "type": "interaction",
+ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"
+ },
+ "message": "gg"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ3",
+ "type": "chargeback",
+ "test": false,
+ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "provider": "stripe",
+ "metadata": {
+ "cents": 500,
+ "original_paid_cents": 500,
+ "reason": "charge.dispute.created",
+ "review": "required"
+ },
+ "reverses_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",
+ "review": "required",
+ "entitlements_revoked": 0
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ3",
+ "type": "refund",
+ "test": false,
+ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "provider": null,
+ "metadata": {
+ "amount_bits": 500,
+ "target": {
+ "service": "tips",
+ "type": "interaction",
+ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"
+ },
+ "message": "gg"
+ },
+ "reverses_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",
+ "type": "donation",
+ "test": false,
+ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "to_subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "provider": null,
+ "metadata": {
+ "amount_bits": 500,
+ "target": {
+ "service": "tips",
+ "type": "interaction",
+ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"
+ },
+ "message": "gg"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",
+ "type": "tip",
+ "test": false,
+ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "provider": null,
+ "metadata": {
+ "amount_bits": 500,
+ "target": {
+ "service": "tips",
+ "type": "interaction",
+ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"
+ },
+ "message": "gg"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",
+ "type": "donation",
+ "test": false,
+ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "provider": null,
+ "metadata": {
+ "amount_bits": 500,
+ "target": {
+ "service": "tips",
+ "type": "interaction",
+ "id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"
+ },
+ "message": "gg"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ1",
+ "type": "purchase",
+ "test": false,
+ "from_subject": null,
+ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "provider": "stripe",
+ "metadata": {
+ "paid_cents": 500,
+ "bits": 500,
+ "value_cents": 500,
+ "revenue_cents": 0,
+ "pricing": "bits",
+ "intent_id": "pi_01JAB3C4D5E6F7G8H9J0K1MNQ2",
+ "rates": {
+ "bits_per_usd": 100
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "type": "page", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 3 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 3 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 2, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.blog/@alex/hello-world", "title": "Hello world", "publication_state": "published" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS", "revision": 2, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.blog/@alex/hello-world", "title": "Hello world", "summary": "First post.", "body": "Hello world, this is the first post.", "facets": { "blog": "alex", "tags": ["intro"], "categories": [] }, "language": "en", "authorship": "human", "provenance": [], "publication_state": "published", "published_at": "2026-09-22T12:00:00.000Z", "updated_at": "2026-09-22T12:00:00.000Z", "indexability": { "decision": "index", "reasons": [] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "state": "published", "visibility": "public", "authorship": "human" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "state": "draft", "visibility": "public", "authorship": "ai", "workflow": { "id": "blog.draft_post", "runId": "run_01JAB2C3D4E5F6G7H8J9K0MNPY", "version": 2 } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "state": "draft", "visibility": "public", "authorship": "human" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "unpublished", "indexability": null, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "members" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "deleted", "indexability": null, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "members" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "unpublished", "indexability": null, "blog": "alex", "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "unpublished", "indexability": { "decision": "index", "reasons": [] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "maybe", "reasons": [] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.blog/@alex/hello-world", "publication_state": "published", "indexability": { "decision": "noindex", "reasons": ["thin_content"] }, "blog": { "id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT", "handle": "alex" }, "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPZ", "action": "publish", "revision": 2, "run_at": 1790154000000, "attempts": 5, "error": "boom", "blog_id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPZ", "action": "publish", "revision": 2, "run_at": "2026-09-23T09:00:00.000Z", "attempts": 5, "error": "post.not_found: No such post", "blog_id": "blg_01JAB2C3D4E5F6G7H8J9K0MNPT" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "message_ids": [1201], "text": "hello", "redacts": { "subject_type": "chat_message", "subject_ids": ["1201"] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "message_ids": [] , "redacts": { "subject_type": "chat_message", "subject_ids": ["1"] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "message_ids": [1201, 1202], "redacts": { "subject_type": "chat_message", "subject_ids": ["1201", "1202"] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "recipient": "42",
+ "kind": "keyword",
+ "query": "keyboard",
+ "product_id": null,
+ "max_price": null,
+ "currency": null,
+ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "offer_url": "https://openvibe.deals/d/keychron-q1",
+ "title": "Keychron Q1",
+ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": "89", "currency": "USD", "availability": "in_stock" }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "recipient": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "kind": "search",
+ "query": "keyboard",
+ "product_id": null,
+ "max_price": null,
+ "currency": null,
+ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "offer_url": "https://openvibe.deals/d/keychron-q1",
+ "title": "Keychron Q1",
+ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": null, "currency": null, "availability": null }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "recipient": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "kind": "keyword",
+ "query": "mechanical keyboard",
+ "product_id": null,
+ "max_price": null,
+ "currency": null,
+ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "offer_url": "https://openvibe.deals/d/keychron-q1-mechanical-keyboard",
+ "title": "Keychron Q1 mechanical keyboard",
+ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": null, "currency": null, "availability": null }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "watch_id": "dwt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "recipient": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "kind": "price_below",
+ "query": null,
+ "product_id": "dpr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "max_price": "199.99",
+ "currency": "USD",
+ "offer_id": "dof_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "offer_url": "https://openvibe.deals/d/steam-deck-oled-512gb",
+ "title": "Steam Deck OLED 512 GB",
+ "observation": { "id": "dpo_01JAB2C3D4E5F6G7H8J9K0MNPV", "observed_at": "2026-09-23T08:00:00.000Z", "price": "179.00", "currency": "USD", "availability": "in_stock" }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "event_ids": [], "subject_type": "post", "subject_ids": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject_type": "chat_message" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "event_ids": ["evt_01JAB2C3D4E5F6G7H8J9K0MNPQ"], "reason": "spam" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "event_ids": ["evt_01JAB2C3D4E5F6G7H8J9K0MNPQ"] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject_type": "chat_message", "subject_ids": ["1201", "1202"] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "redacted": true, "redacted_at": "2026-09-23T10:00:00.000Z", "redacted_by": "evt_01JAB2C3D4E5F6G7H8J9K0MNPR", "message": "hi" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "redacted": true, "redacted_at": "2026-09-23T10:00:00.000Z", "redacted_by": "evt_01JAB2C3D4E5F6G7H8J9K0MNPR" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 7, "exposure": "none", "reindexed": true, "canonical_url": null }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 7, "exposure": "public_listed", "reindexed": true, "canonical_url": "https://openvibe.wiki/wiki/Main_Page" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1, "exposure": "restricted", "reindexed": false, "canonical_url": null }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 12, "reason": "spam", "previous_exposure": "public_listed", "exposure": "none", "canonical_url": null }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "blog", "type": "post", "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 12, "reason": "not_published", "previous_exposure": "public_listed", "exposure": "none", "canonical_url": "https://openvibe.blog/alex/hello-world" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "source_key": "bbc-world", "category": "news", "run_id": "frn_01JAB2C3D4E5F6G7H8J9K0MNPQ", "endpoint_url": "https://feeds.bbci.co.uk/news/world/rss.xml", "state": "not_modified", "error_code": null, "http_status": 304, "consecutive_failures": 0 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "source_key": "bbc-world", "category": "news", "run_id": "frn_01JAB2C3D4E5F6G7H8J9K0MNPQ", "endpoint_url": "https://feeds.bbci.co.uk/news/world/rss.xml", "state": "http_error", "error_code": "http_503", "http_status": 503, "consecutive_failures": 2 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "source_key": "shop-sitemap", "category": "deals", "run_id": "frn_01JAB2C3D4E5F6G7H8J9K0MNPR", "endpoint_url": "https://shop.example.com/sitemap.xml", "state": "robots_denied", "error_code": "robots", "http_status": null, "consecutive_failures": 1 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 3 }
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "owner": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1,
+ "visibility": "members", "acl": { "groups": ["role:admin", "role:global_mod"] },
+ "title": "Summit ends without agreement", "publication_state": "published"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "owner": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1,
+ "visibility": "members", "acl": { "groups": ["role:admin", "role:global_mod"] },
+ "title": "Summit ends without agreement", "summary": "", "body": "",
+ "facets": { "category": "news", "source": "bbc-world", "kind": "article" },
+ "authorship": "imported",
+ "provenance": [{ "service": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "revision": 1, "url": "https://www.bbc.co.uk/news/world-12345678", "retrieved_at": "2026-09-23T08:00:00.000Z" }],
+ "publication_state": "published", "published_at": "2026-09-23T07:45:00Z", "updated_at": null,
+ "indexability": { "decision": "noindex", "reasons": ["third_party_content"] },
+ "canonical_url": "https://www.bbc.co.uk/news/world-12345678"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends", "summary": "Leaders left…", "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "sports", "kind": "article", "canonical_url": null, "title": null, "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends without agreement", "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPR", "source_key": "shop-sitemap", "category": "deals", "kind": "url", "canonical_url": null, "title": null, "revision": 1, "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "retrieved_at": "2026-09-23T08:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": null, "title": null, "revision": 3, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z", "reason": "" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends without agreement", "revision": 3, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z", "reason": "takedown request from the publisher" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": null, "title": null, "revision": 2, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "item_id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPQ", "source_key": "bbc-world", "category": "news", "kind": "article", "canonical_url": "https://www.bbc.co.uk/news/world-12345678", "title": "Summit ends without agreement on climate", "revision": 2, "content_hash": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "retrieved_at": "2026-09-23T09:00:00.000Z", "previous_content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" }
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "title": "New microphone",
+ "target_amount": 10000,
+ "current_amount": 2500,
+ "currency": "vibes-bits",
+ "status": "active",
+ "reached": false,
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "title": "New microphone",
+ "target_amount": 10000,
+ "current_amount": 2500,
+ "currency": "vibes-bits",
+ "status": "active",
+ "reached": false,
+ "reason": "contribution",
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "title": "New microphone",
+ "target_amount": 10000,
+ "current_amount": 0,
+ "currency": "vibes-bits",
+ "status": "active",
+ "reached": false,
+ "reason": "created",
+ "interaction_id": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "settled",
+ "delivery_state": "cancelled",
+ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",
+ "test": false,
+ "reason": "creator_cancelled"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "failed",
+ "delivery_state": "cancelled",
+ "billing_txn_id": null,
+ "test": false,
+ "reason": "payment_failed",
+ "code": "billing.insufficient_funds"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "reversed",
+ "delivery_state": "cancelled",
+ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",
+ "test": false,
+ "reason": "payment_reversed"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "settled",
+ "delivery_state": "failed",
+ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",
+ "test": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "settled",
+ "delivery_state": "failed",
+ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",
+ "test": false,
+ "failed_effects": [
+ {
+ "effect": "chat_line",
+ "adapter": "live",
+ "last_error": "live 503"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "42"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "settled",
+ "delivery_state": "queued",
+ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",
+ "test": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": "5.00",
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "settled",
+ "delivery_state": "queued",
+ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",
+ "test": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": null,
+ "supporter_name": "Anonymous",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "external",
+ "payment_state": "settled",
+ "delivery_state": "queued",
+ "billing_txn_id": null,
+ "test": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "supporter": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "supporter_name": "Generous Fan",
+ "kind": "tip",
+ "amount": 500,
+ "currency": "vibes-bits",
+ "settlement": "billing",
+ "payment_state": "settled",
+ "delivery_state": "queued",
+ "billing_txn_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNPR",
+ "test": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "sound",
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "goal_id": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "alert",
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "goal_id": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "alert",
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "goal_id": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "delivery_id": "tovd_01JAB3C4D5E6F7G8H9J0K1MNPT",
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "goal",
+ "interaction_id": "tint_01JAB3C4D5E6F7G8H9J0K1MNPQ",
+ "goal_id": "tgoal_01JAB3C4D5E6F7G8H9J0K1MNPS",
+ "reason": "no overlay showed it within the delivery window"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": { "type": "service", "id": "community" }, "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null, "input": { "format": "webp", "width": 640 } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": "session:3f2a9c", "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": null, "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:05:00.000Z", "retry_of": "job_01JAB2C3D4E5F6G7H8J9K0MNPZ" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" }, "attempts": 0, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null }
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "failed", "owner": null, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,
+ "started_at": null, "finished_at": "2026-09-23T09:00:02.000Z", "expires_at": "2026-09-23T10:00:02.000Z",
+ "error": { "status": 500, "code": "ENOENT", "detail": "no such file" }, "retryable": false }
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "failed", "owner": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" }, "attempts": 3, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,
+ "started_at": "2026-09-23T09:00:01.000Z", "finished_at": "2026-09-23T09:10:01.000Z", "expires_at": "2026-09-23T10:10:01.000Z",
+ "error": { "status": 504, "code": "tools.job.timeout", "detail": "The job ran longer than 600 s" }, "retryable": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "queued", "owner": null, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null, "started_at": "2026-09-23T09:00:01.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "running", "owner": { "type": "service", "id": "community" }, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null, "started_at": "2026-09-23T09:00:01.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "succeeded", "owner": null, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,
+ "started_at": "2026-09-23T09:00:01.000Z", "finished_at": "2026-09-23T09:00:04.000Z", "expires_at": null,
+ "result": { "files": [], "data": { "text": "transcribed words" } } }
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ", "service": "img", "type": "img.process", "type_version": 1, "state": "succeeded", "owner": { "type": "app", "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPR" }, "attempts": 1, "max_attempts": 3, "created_at": "2026-09-23T09:00:00.000Z", "retry_of": null,
+ "started_at": "2026-09-23T09:00:01.000Z", "finished_at": "2026-09-23T09:00:04.000Z", "expires_at": "2026-09-23T10:00:04.000Z",
+ "result": { "files": [
+ { "index": 0, "mime": "image/webp", "size": 48213, "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "storage": "media", "media": { "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS", "role": "output" } },
+ { "index": 1, "mime": "image/png", "size": 90112, "sha256": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752", "storage": "local", "media": null }
+ ] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "rule": {
+ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "threshold", "status": "active",
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc." },
+ "metric": "price.close", "operator": "above", "threshold": "250", "unit": "USD", "currency": "USD",
+ "form_types": null, "armed": true, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-22T21:00:00.000Z"
+ },
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },
+ "trigger": { "kind": "observation", "id": "obs_01JAB2C3D4E5F6G7H8J9K0MNPT", "metric": "price.close", "unit": "USD", "currency": "USD", "observed_at": "2026-09-22T20:00:00.000Z", "retrieved_at": "2026-09-22T20:05:00.000Z", "source_key": "stooq-daily", "source_url": null },
+ "disclaimer": "Information only — not investment advice; no trading here."
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "rule": {
+ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "new_document", "status": "active",
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "metric": null, "operator": null, "threshold": null, "unit": null, "currency": null,
+ "form_types": null, "armed": null, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-20T10:00:00.000Z"
+ },
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },
+ "trigger": { "kind": "document", "id": "doc_01JAB2C3D4E5F6G7H8J9K0MNPT", "form_type": null, "title": null, "url": null, "published_at": null, "retrieved_at": "2026-09-22T12:10:00.000Z", "source_key": "sec-edgar-filings" },
+ "message": "AAPL filed a new document",
+ "disclaimer": "Information only — not investment advice; no trading here."
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "rule": {
+ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "filing_type", "status": "active",
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc." },
+ "metric": null, "operator": null, "threshold": null, "unit": null, "currency": null,
+ "form_types": ["10-K", "8-K"], "armed": null, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-20T10:00:00.000Z"
+ },
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },
+ "trigger": {
+ "kind": "document", "id": "doc_01JAB2C3D4E5F6G7H8J9K0MNPT", "form_type": "8-K", "title": "Current report",
+ "url": "https://www.sec.gov/Archives/edgar/data/320193/000032019326000001/0000320193-26-000001-index.htm",
+ "published_at": "2026-09-22T12:00:00.000Z", "retrieved_at": "2026-09-22T12:10:00.000Z", "source_key": "sec-edgar-filings"
+ },
+ "disclaimer": "Information only — not investment advice; no trading here."
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "delivery_id": "ald_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "rule": {
+ "id": "alr_01JAB2C3D4E5F6G7H8J9K0MNPR", "kind": "threshold", "status": "active",
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc." },
+ "metric": "price.close", "operator": "above", "threshold": "250", "unit": "USD", "currency": "USD",
+ "form_types": null, "armed": true, "created_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-22T21:00:00.000Z"
+ },
+ "instrument": { "id": "ins_01JAB2C3D4E5F6G7H8J9K0MNPS", "symbol": "AAPL", "name": "Apple Inc.", "url": "https://openvibe.trade/i/AAPL" },
+ "trigger": {
+ "kind": "observation", "id": "obs_01JAB2C3D4E5F6G7H8J9K0MNPT", "metric": "price.close", "value": "251.37", "unit": "USD", "currency": "USD",
+ "observed_at": "2026-09-22T20:00:00.000Z", "retrieved_at": "2026-09-22T20:05:00.000Z", "source_key": "stooq-daily", "source_url": null
+ },
+ "disclaimer": "Information only — not investment advice; no trading here."
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "member": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": true,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "cancel_at_period_end": false,
+ "previous": null,
+ "reason": "granted",
+ "source": "webhook",
+ "billing_event_id": "evt_01JAB3C4D5E6F7G8H9J0K1MNPY",
+ "membership_id": "vms_01JAB3C4D5E6F7G8H9J0K1MNPZ",
+ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",
+ "plan_version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",
+ "plan_version": 2,
+ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "member": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": true,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "cancel_at_period_end": true,
+ "previous": {
+ "active": true,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "cancel_at_period_end": false
+ },
+ "reason": "cancel_scheduled",
+ "source": "event",
+ "billing_event_id": null,
+ "membership_id": "vms_01JAB3C4D5E6F7G8H9J0K1MNPZ",
+ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",
+ "plan_version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",
+ "plan_version": 2,
+ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "member": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "creator": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": true,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "cancel_at_period_end": false,
+ "previous": null,
+ "reason": "granted",
+ "source": "event",
+ "billing_event_id": "evt_01JAB3C4D5E6F7G8H9J0K1MNPY",
+ "membership_id": "vms_01JAB3C4D5E6F7G8H9J0K1MNPZ",
+ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",
+ "plan_version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",
+ "plan_version": 2,
+ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",
+ "slug": "supporters",
+ "version": 2,
+ "version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",
+ "creator": {
+ "id": "vcr_01JAB3C4D5E6F7G8H9J0K1MNPX",
+ "kind": "creator",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "username": "japaneseoldguy",
+ "display_name": "JapaneseOldGuy",
+ "bio": null,
+ "status": "active"
+ },
+ "billing_kind": "channel_subscription",
+ "terms": {
+ "name": "Supporters",
+ "description": "Back the channel.",
+ "benefits": [
+ "Sub badge",
+ "Emote-only chat"
+ ],
+ "perks": [
+ {
+ "key": "badge",
+ "name": "Sub badge",
+ "kind": "badge",
+ "scope": "creator"
+ }
+ ],
+ "billing_kind": "channel_subscription",
+ "price": "set and charged by OpenVibe.Billing",
+ "digest": "3f9a0c1d2e4b5a67",
+ "price_cents": 499
+ },
+ "published_at": "2026-09-23T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
+{
+ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",
+ "slug": "supporters",
+ "version": 2,
+ "version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",
+ "creator": {
+ "id": "vcr_01JAB3C4D5E6F7G8H9J0K1MNPX",
+ "kind": "creator",
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "username": "japaneseoldguy",
+ "display_name": "JapaneseOldGuy",
+ "bio": null,
+ "status": "active"
+ },
+ "billing_kind": "channel_subscription",
+ "terms": {
+ "name": "Supporters",
+ "description": "Back the channel.",
+ "benefits": [
+ "Sub badge",
+ "Emote-only chat"
+ ],
+ "perks": [
+ {
+ "key": "badge",
+ "name": "Sub badge",
+ "kind": "badge",
+ "scope": "creator"
+ }
+ ],
+ "billing_kind": "channel_subscription",
+ "price": "set and charged by OpenVibe.Billing",
+ "digest": "3f9a0c1d2e4b5a67"
+ },
+ "published_at": "2026-09-23T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "plan_id": "vpl_01JAB3C4D5E6F7G8H9J0K1MNPV",
+ "slug": "supporters",
+ "version": 2,
+ "version_id": "vpv_01JAB3C4D5E6F7G8H9J0K1MNPW",
+ "creator": {
+ "id": "network",
+ "kind": "network",
+ "subject": null,
+ "username": null,
+ "display_name": "OpenVibe",
+ "bio": null,
+ "status": "active"
+ },
+ "billing_kind": null,
+ "terms": {
+ "name": "Supporters",
+ "description": "Back the channel.",
+ "benefits": null,
+ "perks": [],
+ "billing_kind": null,
+ "price": null,
+ "digest": "3f9a0c1d2e4b5a67"
+ },
+ "published_at": "2026-09-23T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 4, "deleted": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 4 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "blog", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 3, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.wiki/w/food/ramen", "title": "Ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 4, "deleted": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "owner": "wiki", "type": "page", "id": "pg_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 3, "deleted": false, "visibility": "public", "canonical_url": "https://openvibe.wiki/w/food/ramen", "title": "Ramen", "summary": "Noodle soup.", "body": "Ramen is a noodle soup.", "facets": { "space": "food", "space_kind": "user" }, "authorship": "human", "provenance": [{ "service": "sources", "type": "item", "id": "itm_01JAB2C3D4E5F6G7H8J9K0MNPX", "retrieved_at": "2026-09-22T12:00:00.000Z" }], "publication_state": "published", "published_at": "2026-09-22T12:00:00.000Z", "updated_at": "2026-09-23T08:00:00.000Z", "indexability": { "decision": "index", "reasons": [] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "deleted", "indexability": null, "space": "food", "slug": "ramen", "visibility": "unlisted" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "deleted", "indexability": null, "space": "food", "slug": "ramen", "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.wiki/w/crew/roster", "publication_state": "published", "indexability": { "decision": "noindex", "reasons": ["members_only"] }, "space": "crew", "slug": "roster", "visibility": "members" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.wiki/w/food/ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "published", "indexability": null, "space": "food", "slug": "ramen", "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": null, "publication_state": "unpublished", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.wiki/w/food/ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public", "body": "Ramen is…" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "canonical_url": "https://openvibe.wiki/w/food/ramen", "publication_state": "published", "indexability": { "decision": "index", "reasons": [] }, "space": "food", "slug": "ramen", "visibility": "public" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "space_id": "spc_01JAB2C3D4E5F6G7H8J9K0MNPV", "space": "food", "slug": "ramen", "number": 2, "kind": "edit", "parent_number": 1, "reverted_to": null, "authorship": "human", "author": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "message": null, "content": "# Ramen" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "space_id": "spc_01JAB2C3D4E5F6G7H8J9K0MNPV", "space": "food", "slug": "ramen", "number": 3, "kind": "edit", "parent_number": 2, "reverted_to": null, "authorship": "ai_generated", "author": "svc:ai", "message": "AI proposal", "proposal_id": "prp_01JAB2C3D4E5F6G7H8J9K0MNPW" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "space_id": "spc_01JAB2C3D4E5F6G7H8J9K0MNPV", "space": "food", "slug": "ramen", "number": 2, "kind": "edit", "parent_number": 1, "reverted_to": null, "authorship": "human", "author": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "message": "Add broth section" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "slug": "food", "name": "Food", "kind": "user", "visibility": "public", "deleted": false, "change": "archived", "url": "https://openvibe.wiki/w/food" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "slug": "food", "name": "Food", "kind": "user", "visibility": "public", "deleted": false, "change": "created", "url": "https://openvibe.wiki/w/food" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "action": "revised", "space": "food", "slug": "ramen", "title": "Ramen", "revision": 3, "url": "https://openvibe.wiki/w/food/ramen", "recipients": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "action": "published", "space": "food", "slug": "ramen", "title": "Ramen", "revision": 2, "url": "https://openvibe.wiki/w/food/ramen", "recipients": ["usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"] }
@@ -1967,6 +1967,5630 @@
@@ -1967,6 +1967,5630 @@
}
}
}
}
}
}
+ },
+ "events.redaction-directive": {
+ "version": "1.0.0",
+ "owner": "events",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/redaction-directive.v1.json",
+ "title": "RedactionDirective",
+ "description": "payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.",
+ "type": "object",
+ "minProperties": 1,
+ "additionalProperties": false,
+ "dependentRequired": {
+ "subject_type": [
+ "subject_ids"
+ ],
+ "subject_ids": [
+ "subject_type"
+ ]
+ },
+ "properties": {
+ "event_ids": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 1000,
+ "items": {
+ "type": "string",
+ "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "description": "Events to redact by id. Duplicates are ignored."
+ },
+ "subject_type": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9_]{0,63}$",
+ "description": "Envelope subject.type of the events to redact (chat_message, post…). Goes with subject_ids."
+ },
+ "subject_ids": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 1000,
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 200
+ },
+ "description": "Envelope subject.id values; every not-yet-redacted event of the same source about each one is redacted."
+ }
+ }
+ }
+ },
+ "events.tombstone-payload": {
+ "version": "1.0.0",
+ "owner": "events",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/tombstone-payload.v1.json",
+ "title": "TombstonePayload",
+ "description": "The payload of a redacted event (ADR-026). OpenVibe.Events replaces the original payload with this and the actor with the producer itself ({ type: service, id: <source> }, or the app); event_id, seq, event_type, version, subject, timestamp and visibility stay. Every read path serves it from then on: pull, GET by id, SSE replay, queued deliveries and DLQ replays. A consumer checks payload.redacted before validating a payload against its event type's schema.",
+ "type": "object",
+ "required": [
+ "redacted",
+ "redacted_at",
+ "redacted_by"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "redacted": {
+ "const": true
+ },
+ "redacted_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "redacted_by": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64,
+ "description": "The event_id of the redacting event; an operator label (e.g. backfill) when OpenVibe.Events redacted by script."
+ }
+ }
+ }
+ },
+ "chat.message.deleted": {
+ "version": "1.0.0",
+ "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/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": [
+ "type",
+ "id",
+ "revision"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "const": "item"
+ },
+ "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."
+ }
+ }
+ }
+ },
+ "wiki.revision.created": {
+ "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.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": [
+ "space_id",
+ "space",
+ "slug",
+ "number",
+ "kind",
+ "parent_number",
+ "reverted_to",
+ "authorship",
+ "author",
+ "message"
+ ],
+ "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": {
+ "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
+ },
+ "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",
+ "null"
+ ],
+ "minimum": 1
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "maxLength": 2048
+ },
+ "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": "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)."
+ }
+ }
+ }
+ },
+ "wiki.page.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.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": [
+ "canonical_url",
+ "publication_state",
+ "indexability",
+ "space",
+ "slug",
+ "visibility"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": {
+ "type": "string",
+ "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)."
+ }
+ }
+ }
+ },
+ "wiki.page.unpublished": {
+ "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.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": [
+ "canonical_url",
+ "publication_state",
+ "indexability",
+ "space",
+ "slug",
+ "visibility"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "null: the event carries a tombstone, which has no URL."
+ },
+ "publication_state": {
+ "const": "unpublished"
+ },
+ "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": "^[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.page.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.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": [
+ "canonical_url",
+ "publication_state",
+ "indexability",
+ "space",
+ "slug",
+ "visibility"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "null: the event carries a tombstone, which has no URL."
+ },
+ "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": "^[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"
+ },
+ {
+ "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
+ }
+ }
+ }
+ ]
+ }
+ },
+ "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."
+ }
+ }
+ }
+ },
+ "blog.post.created": {
+ "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.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": [
+ "blog",
+ "state",
+ "visibility",
+ "authorship"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "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}$"
+ }
+ }
+ },
+ "state": {
+ "const": "draft"
+ },
+ "visibility": {
+ "enum": [
+ "public",
+ "unlisted",
+ "members",
+ "private"
+ ]
+ },
+ "authorship": {
+ "enum": [
+ "human",
+ "ai",
+ "hybrid",
+ "imported"
+ ],
+ "description": "openvibe-publishing authorship mode (not Search's vocabulary)."
+ },
+ "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"
+ }
+ }
+ }
+ }
+ }
+ },
+ "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"
+ },
+ "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}$"
+ }
+ }
+ }
+ },
+ "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": "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"
+ },
+ "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}$"
+ }
+ }
+ }
+ },
+ "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": "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": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "null: the event carries a tombstone, which has no URL."
+ },
+ "publication_state": {
+ "const": "unpublished"
+ },
+ "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."
+ },
+ "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": "The post's own visibility (members = entitlement-gated)."
+ }
+ }
+ }
+ },
+ "blog.post.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.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",
+ "blog",
+ "visibility"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "canonical_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "null: the event carries a tombstone, which has no URL."
+ },
+ "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."
+ },
+ "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": "The post's own visibility (members = entitlement-gated)."
+ }
+ }
+ }
+ },
+ "blog.schedule.failed": {
+ "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.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": [
+ "job_id",
+ "action",
+ "revision",
+ "run_at",
+ "attempts",
+ "error",
+ "blog_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "action": {
+ "enum": [
+ "publish",
+ "unpublish"
+ ]
+ },
+ "revision": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1,
+ "description": "The revision a publish was to make live; null for an unpublish."
+ },
+ "run_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the job was last due."
+ },
+ "attempts": {
+ "type": "integer",
+ "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."
+ }
+ }
+ }
+ },
+ "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"
+ },
+ {
+ "type": "object",
+ "required": [
+ "owner",
+ "type",
+ "deleted"
+ ],
+ "properties": {
+ "owner": {
+ "const": "blog"
+ },
+ "type": {
+ "const": "post"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^pst_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "deleted": {
+ "const": false
+ }
+ }
+ }
+ ]
+ }
+ },
+ "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}$"
+ },
+ "revision": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 9007199254740991,
+ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
+ }
+ }
+ }
+ },
+ "ai.run-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/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": [
+ "workflow"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "workflow": {
+ "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…)."
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 1,
+ "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."
+ }
+ }
+ }
+ }
+ }
+ },
+ "ai.run": {
+ "version": "1.0.0",
+ "owner": "ai",
+ "status": "active",
+ "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.",
+ "type": "object",
+ "required": [
+ "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": {
+ "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": [
+ "key",
+ "version"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 1
+ }
+ }
+ },
+ "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"
+ },
+ "on_behalf_of": {
+ "anyOf": [
+ {
+ "$ref": "../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "attribution": {
+ "anyOf": [
+ {
+ "$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": {
+ "anyOf": [
+ {
+ "$ref": "../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "input": {
+ "type": "object",
+ "description": "The retained input (see the description)."
+ },
+ "output": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "description": "Workflow output, or null."
+ },
+ "error": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "code",
+ "detail"
+ ],
+ "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…"
+ },
+ "detail": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "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"
+ },
+ "workflow": {
+ "type": "string"
+ },
+ "workflow_version": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "template_version": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "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"
+ }
+ }
+ },
+ "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)."
+ }
+ }
+ },
+ "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": {
+ "version": "1.0.0",
+ "owner": "ai",
+ "status": "planned",
+ "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": {
+ "type": "object",
+ "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": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "attribution": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "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
+ },
+ "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": "null"
+ }
+ }
+ }
+ },
+ "ai.run.succeeded": {
+ "version": "1.0.0",
+ "owner": "ai",
+ "status": "planned",
+ "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.",
+ "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": "succeeded"
+ },
+ "workflow": {
+ "type": "object",
+ "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": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "attribution": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "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
+ },
+ "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.run.failed": {
+ "version": "1.0.0",
+ "owner": "ai",
+ "status": "planned",
+ "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": {
+ "type": "object",
+ "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": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "attribution": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "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
+ },
+ "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.cached": {
+ "version": "1.0.0",
+ "owner": "ai",
+ "status": "planned",
+ "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.",
+ "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": "cached"
+ },
+ "workflow": {
+ "type": "object",
+ "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": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "attribution": {
+ "anyOf": [
+ {
+ "$ref": "../../common/entity-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "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": "string",
+ "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "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
+ }
+ }
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "tips.interaction.ready": {
+ "version": "1.0.0",
+ "owner": "tips",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.ready.v1.json",
+ "title": "TipsInteractionReadyPayload",
+ "description": "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.",
+ "type": "object",
+ "required": [
+ "interaction_id",
+ "creator",
+ "supporter",
+ "supporter_name",
+ "kind",
+ "amount",
+ "currency",
+ "settlement",
+ "payment_state",
+ "delivery_state",
+ "billing_txn_id",
+ "test"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "interaction_id": {
+ "type": "string",
+ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "creator": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "supporter": {
+ "anyOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The supporter, null when external, anonymous or unmapped."
+ },
+ "supporter_name": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Display name at the time."
+ },
+ "kind": {
+ "enum": [
+ "tip",
+ "paid_message",
+ "tts",
+ "media_request"
+ ]
+ },
+ "amount": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Integer amount in currency minor units (Vibes bits)."
+ },
+ "currency": {
+ "type": "string",
+ "description": "vibes-bits today."
+ },
+ "settlement": {
+ "enum": [
+ "billing",
+ "external",
+ "simulated",
+ "imported"
+ ]
+ },
+ "payment_state": {
+ "enum": [
+ "pending",
+ "settled",
+ "reversed",
+ "failed"
+ ]
+ },
+ "delivery_state": {
+ "enum": [
+ "awaiting_payment",
+ "queued",
+ "delivered",
+ "failed",
+ "cancelled"
+ ]
+ },
+ "billing_txn_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The Billing transaction (txn_…) that settled it."
+ },
+ "test": {
+ "type": "boolean",
+ "description": "Simulations never emit events, so false in practice."
+ }
+ }
+ }
+ },
+ "tips.interaction.failed": {
+ "version": "1.0.0",
+ "owner": "tips",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.failed.v1.json",
+ "title": "TipsInteractionFailedPayload",
+ "description": "tips.interaction.failed v1 (OpenVibe.Tips server/domain/interactions.js recomputeDelivery). A delivery effect gave up after its retries and none is still queued; the payment state is untouched. Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",
+ "type": "object",
+ "required": [
+ "interaction_id",
+ "creator",
+ "supporter",
+ "supporter_name",
+ "kind",
+ "amount",
+ "currency",
+ "settlement",
+ "payment_state",
+ "delivery_state",
+ "billing_txn_id",
+ "test",
+ "failed_effects"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "interaction_id": {
+ "type": "string",
+ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "creator": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "supporter": {
+ "anyOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The supporter, null when external, anonymous or unmapped."
+ },
+ "supporter_name": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Display name at the time."
+ },
+ "kind": {
+ "enum": [
+ "tip",
+ "paid_message",
+ "tts",
+ "media_request"
+ ]
+ },
+ "amount": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Integer amount in currency minor units (Vibes bits)."
+ },
+ "currency": {
+ "type": "string",
+ "description": "vibes-bits today."
+ },
+ "settlement": {
+ "enum": [
+ "billing",
+ "external",
+ "simulated",
+ "imported"
+ ]
+ },
+ "payment_state": {
+ "enum": [
+ "pending",
+ "settled",
+ "reversed",
+ "failed"
+ ]
+ },
+ "delivery_state": {
+ "enum": [
+ "awaiting_payment",
+ "queued",
+ "delivered",
+ "failed",
+ "cancelled"
+ ]
+ },
+ "billing_txn_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The Billing transaction (txn_…) that settled it."
+ },
+ "test": {
+ "type": "boolean",
+ "description": "Simulations never emit events, so false in practice."
+ },
+ "failed_effects": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object",
+ "required": [
+ "effect",
+ "adapter",
+ "last_error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "effect": {
+ "enum": [
+ "chat_line",
+ "paid_message",
+ "tts",
+ "media_request",
+ "overlay_alert"
+ ]
+ },
+ "adapter": {
+ "type": "string"
+ },
+ "last_error": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tips.interaction.cancelled": {
+ "version": "1.0.0",
+ "owner": "tips",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.interaction.cancelled.v1.json",
+ "title": "TipsInteractionCancelledPayload",
+ "description": "tips.interaction.cancelled v1 (OpenVibe.Tips server/domain/interactions.js failPayment, cancelQueued). Undelivered effects were cancelled: the payment failed before settling (reason payment_failed, with the Billing error code), Billing reversed it (payment_reversed) or an effect came up while the payment was no longer settled (payment_not_settled). Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.",
+ "type": "object",
+ "required": [
+ "interaction_id",
+ "creator",
+ "supporter",
+ "supporter_name",
+ "kind",
+ "amount",
+ "currency",
+ "settlement",
+ "payment_state",
+ "delivery_state",
+ "billing_txn_id",
+ "test",
+ "reason"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "interaction_id": {
+ "type": "string",
+ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "creator": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "supporter": {
+ "anyOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The supporter, null when external, anonymous or unmapped."
+ },
+ "supporter_name": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Display name at the time."
+ },
+ "kind": {
+ "enum": [
+ "tip",
+ "paid_message",
+ "tts",
+ "media_request"
+ ]
+ },
+ "amount": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Integer amount in currency minor units (Vibes bits)."
+ },
+ "currency": {
+ "type": "string",
+ "description": "vibes-bits today."
+ },
+ "settlement": {
+ "enum": [
+ "billing",
+ "external",
+ "simulated",
+ "imported"
+ ]
+ },
+ "payment_state": {
+ "enum": [
+ "pending",
+ "settled",
+ "reversed",
+ "failed"
+ ]
+ },
+ "delivery_state": {
+ "enum": [
+ "awaiting_payment",
+ "queued",
+ "delivered",
+ "failed",
+ "cancelled"
+ ]
+ },
+ "billing_txn_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The Billing transaction (txn_…) that settled it."
+ },
+ "test": {
+ "type": "boolean",
+ "description": "Simulations never emit events, so false in practice."
+ },
+ "reason": {
+ "enum": [
+ "payment_failed",
+ "payment_reversed",
+ "payment_not_settled"
+ ]
+ },
+ "code": {
+ "type": "string",
+ "description": "Only with reason payment_failed: the Billing problem code (billing.insufficient_funds, billing.funding_reversed, billing.refused…)."
+ }
+ }
+ }
+ },
+ "tips.goal.updated": {
+ "version": "1.0.0",
+ "owner": "tips",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.goal.updated.v1.json",
+ "title": "TipsGoalUpdatedPayload",
+ "description": "tips.goal.updated v1 (OpenVibe.Tips server/domain/goals.js changed). A goal was created, edited or closed, or its settled total moved (a contribution, the contribution that reached it, a reversal). Envelope: subject { type: goal, id: tgoal_…, revision }, visibility internal, priority important, actor service:tips.",
+ "type": "object",
+ "required": [
+ "goal_id",
+ "creator",
+ "title",
+ "target_amount",
+ "current_amount",
+ "currency",
+ "status",
+ "reached",
+ "reason",
+ "interaction_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "goal_id": {
+ "type": "string",
+ "pattern": "^tgoal_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "creator": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "title": {
+ "type": "string"
+ },
+ "target_amount": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "current_amount": {
+ "type": "integer",
+ "description": "Settled total including any amount carried over from Live."
+ },
+ "currency": {
+ "type": "string"
+ },
+ "status": {
+ "enum": [
+ "active",
+ "closed"
+ ]
+ },
+ "reached": {
+ "type": "boolean"
+ },
+ "reason": {
+ "enum": [
+ "created",
+ "updated",
+ "closed",
+ "contribution",
+ "reached",
+ "reversal"
+ ]
+ },
+ "interaction_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^tint_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The interaction behind a contribution, reached or reversal change; null otherwise."
+ }
+ }
+ }
+ },
+ "tips.overlay.delivered": {
+ "version": "1.0.0",
+ "owner": "tips",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.delivered.v1.json",
+ "title": "TipsOverlayDeliveredPayload",
+ "description": "tips.overlay.delivered v1 (OpenVibe.Tips server/domain/overlays.js markDelivered). An overlay delivery (alert or goal update) reached at least one connected overlay, first time only. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",
+ "type": "object",
+ "required": [
+ "delivery_id",
+ "creator",
+ "kind",
+ "interaction_id",
+ "goal_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "delivery_id": {
+ "type": "string",
+ "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "creator": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "kind": {
+ "enum": [
+ "alert",
+ "goal"
+ ]
+ },
+ "interaction_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "goal_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ }
+ },
+ "tips.overlay.failed": {
+ "version": "1.0.0",
+ "owner": "tips",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tips.overlay.failed.v1.json",
+ "title": "TipsOverlayFailedPayload",
+ "description": "tips.overlay.failed v1 (OpenVibe.Tips server/domain/overlays.js sweepFailed). No overlay showed the delivery within the delivery window. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.",
+ "type": "object",
+ "required": [
+ "delivery_id",
+ "creator",
+ "kind",
+ "interaction_id",
+ "goal_id",
+ "reason"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "delivery_id": {
+ "type": "string",
+ "pattern": "^tovd_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "creator": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "kind": {
+ "enum": [
+ "alert",
+ "goal"
+ ]
+ },
+ "interaction_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "goal_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "vip.plan.published": {
+ "version": "1.0.0",
+ "owner": "vip",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vip.plan.published.v1.json",
+ "title": "VipPlanPublishedPayload",
+ "description": "vip.plan.published v1 (OpenVibe.VIP server/domain/plans.js emitPublished). A plan version became what new members buy: the plan was published, or a published plan was edited (a new immutable version). Carries the version's full terms, never a price (Billing prices and charges). Envelope: subject { type: plan, id: vpl_…, revision: <version> }, visibility public, priority important, actor service:vip.",
+ "type": "object",
+ "required": [
+ "plan_id",
+ "slug",
+ "version",
+ "version_id",
+ "creator",
+ "billing_kind",
+ "terms",
+ "published_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "plan_id": {
+ "type": "string",
+ "pattern": "^vpl_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "slug": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9-]{0,47}$"
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "version_id": {
+ "type": "string",
+ "pattern": "^vpv_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "creator": {
+ "type": "object",
+ "required": [
+ "id",
+ "kind",
+ "subject",
+ "username",
+ "display_name",
+ "bio",
+ "status"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "vcr_… or network."
+ },
+ "kind": {
+ "enum": [
+ "creator",
+ "network"
+ ]
+ },
+ "subject": {
+ "anyOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "null for the network creator."
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "display_name": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "bio": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "status": {
+ "enum": [
+ "active",
+ "suspended"
+ ]
+ }
+ }
+ },
+ "billing_kind": {
+ "enum": [
+ "channel_subscription",
+ null
+ ],
+ "description": "The Billing product that sells it; null for a plan Billing does not sell (network plans)."
+ },
+ "terms": {
+ "type": "object",
+ "required": [
+ "name",
+ "description",
+ "benefits",
+ "perks",
+ "billing_kind",
+ "price",
+ "digest"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "benefits": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "maxItems": 20,
+ "items": {
+ "type": "string"
+ }
+ },
+ "perks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "key",
+ "name",
+ "kind",
+ "scope"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "badge",
+ "emote",
+ "gated_content",
+ "room",
+ "role",
+ "other"
+ ]
+ },
+ "scope": {
+ "enum": [
+ "creator",
+ "network"
+ ]
+ }
+ }
+ }
+ },
+ "billing_kind": {
+ "enum": [
+ "channel_subscription",
+ null
+ ]
+ },
+ "price": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Never an amount: a note that Billing sets and charges the price, or null."
+ },
+ "digest": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{16}$",
+ "description": "sha256 prefix of name, description, benefits, perks and billing_kind."
+ }
+ }
+ },
+ "published_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "vip.membership.changed": {
+ "version": "1.0.0",
+ "owner": "vip",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/vip.membership.changed.v1.json",
+ "title": "VipMembershipChangedPayload",
+ "description": "vip.membership.changed v1 (OpenVibe.VIP server/domain/entitlements.js emitChanged). A member's standing with a creator changed as projected from Billing (granted, renewed, cancel scheduled, expired, refunded…): active, expires_at or cancel_at_period_end differ from before, or a first look found an active membership. Names the plan version the membership was bought under. Envelope: subject { type: membership, id: <member usr_…>:<creator usr_…> }, visibility internal, priority important, actor service:vip.",
+ "type": "object",
+ "required": [
+ "member",
+ "creator",
+ "kind",
+ "active",
+ "expires_at",
+ "cancel_at_period_end",
+ "previous",
+ "reason",
+ "source",
+ "billing_event_id",
+ "membership_id",
+ "plan_id",
+ "plan_version_id",
+ "plan_version",
+ "subscription_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "member": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "creator": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "kind": {
+ "type": "string",
+ "description": "Billing entitlement kind; channel_subscription today."
+ },
+ "active": {
+ "type": "boolean"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cancel_at_period_end": {
+ "type": "boolean"
+ },
+ "previous": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "active",
+ "expires_at",
+ "cancel_at_period_end"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "active": {
+ "type": "boolean"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "cancel_at_period_end": {
+ "type": "boolean"
+ }
+ },
+ "description": "The projection before the change; null on first sight."
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Billing's reason (granted, renewed, refund, expired…) or VIP's (checked, cancel_scheduled)."
+ },
+ "source": {
+ "enum": [
+ "event",
+ "billing_check",
+ "import"
+ ]
+ },
+ "billing_event_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The Billing event (evt_…) behind the change, when it came from one."
+ },
+ "membership_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^vms_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "plan_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "plan_version_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "plan_version": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1
+ },
+ "subscription_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The Billing subscription (sub_…)."
+ }
+ }
+ }
+ },
+ "billing.transaction.settled": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.settled.v1.json",
+ "title": "BillingTransactionSettledPayload",
+ "description": "billing.transaction.settled v1 (OpenVibe.Billing server/ops/purchases.js summary(); emitted by purchases, transfers, subscriptions, cashouts recycle and admin adjustments). A journal transaction was posted. Envelope: subject { type: transaction, id: txn_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "transaction_id",
+ "type",
+ "test",
+ "from_subject",
+ "to_subject",
+ "provider",
+ "metadata"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "transaction_id": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "type": {
+ "enum": [
+ "purchase",
+ "donation",
+ "subscription",
+ "subscription_share",
+ "cashout_request",
+ "cashout_paid",
+ "cashout_denied",
+ "recycle",
+ "refund",
+ "chargeback",
+ "adjustment",
+ "import"
+ ]
+ },
+ "test": {
+ "type": "boolean",
+ "description": "Test money: never counted."
+ },
+ "from_subject": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Subject id (usr_…) value moved from, null for provider money."
+ },
+ "to_subject": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Subject id (usr_…) value moved to."
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "stripe, paypal, powerchat…; null for credit movements."
+ },
+ "metadata": {
+ "type": "object",
+ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."
+ }
+ }
+ }
+ },
+ "billing.transaction.reversed": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",
+ "title": "BillingTransactionReversedPayload",
+ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "transaction_id",
+ "type",
+ "test",
+ "from_subject",
+ "to_subject",
+ "provider",
+ "metadata",
+ "reverses_txn"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "transaction_id": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "type": {
+ "enum": [
+ "purchase",
+ "donation",
+ "subscription",
+ "subscription_share",
+ "cashout_request",
+ "cashout_paid",
+ "cashout_denied",
+ "recycle",
+ "refund",
+ "chargeback",
+ "adjustment",
+ "import"
+ ]
+ },
+ "test": {
+ "type": "boolean",
+ "description": "Test money: never counted."
+ },
+ "from_subject": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Subject id (usr_…) value moved from, null for provider money."
+ },
+ "to_subject": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Subject id (usr_…) value moved to."
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "stripe, paypal, powerchat…; null for credit movements."
+ },
+ "metadata": {
+ "type": "object",
+ "description": "Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type."
+ },
+ "reverses_txn": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The transaction this one reverses."
+ },
+ "review": {
+ "enum": [
+ "required",
+ null
+ ],
+ "description": "Provider reversals only: required when Billing kept value it could not recover (staff review)."
+ },
+ "entitlements_revoked": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Provider reversals only: subscription periods revoked."
+ }
+ }
+ }
+ },
+ "billing.entitlement.changed": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.entitlement.changed.v1.json",
+ "title": "BillingEntitlementChangedPayload",
+ "description": "billing.entitlement.changed v1 (OpenVibe.Billing server/ops/subscriptions.js pay, setStatus, revokeForTxn; entitlement()). A subscriber's channel-subscription entitlement to a streamer changed: a period was granted or renewed, the subscription ended or expired, or a reversed payment revoked a period. The payload is the entitlement as of now. Envelope: subject { type: entitlement, id: <subscriber usr_…>:channel_subscription:<streamer usr_…> }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "subject",
+ "streamer",
+ "kind",
+ "active",
+ "expires_at",
+ "subscription",
+ "reason"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "streamer": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "kind": {
+ "const": "channel_subscription"
+ },
+ "active": {
+ "type": "boolean"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "subscription": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "id",
+ "status",
+ "auto_renew",
+ "cancel_at_period_end",
+ "provider"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "status": {
+ "enum": [
+ "active",
+ "canceled",
+ "expired"
+ ]
+ },
+ "auto_renew": {
+ "type": "boolean"
+ },
+ "cancel_at_period_end": {
+ "type": "boolean"
+ },
+ "provider": {
+ "type": "string"
+ }
+ }
+ },
+ "reason": {
+ "type": "string",
+ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."
+ },
+ "transaction_id": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "granted/renewed: the transaction that paid the period."
+ },
+ "revoked_period": {
+ "type": "object",
+ "required": [
+ "starts_at",
+ "ends_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "starts_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "ends_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ "description": "refund/chargeback: the period the reversed payment had granted."
+ }
+ }
+ }
+ },
+ "billing.subscription.canceled": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.subscription.canceled.v1.json",
+ "title": "BillingSubscriptionCanceledPayload",
+ "description": "billing.subscription.canceled v1 (OpenVibe.Billing server/ops/subscriptions.js cancel). A subscription was set to cancel at period end (for Stripe, after Stripe accepted it); the paid period still runs. Envelope: subject { type: subscription, id: sub_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "subscription",
+ "provider_sync",
+ "actor"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subscription": {
+ "type": "object",
+ "required": [
+ "id",
+ "subscriber",
+ "streamer",
+ "tier",
+ "provider",
+ "provider_ref",
+ "route",
+ "status",
+ "auto_renew",
+ "cancel_at_period_end",
+ "price_cents",
+ "current_period_end",
+ "created_at",
+ "updated_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "subscriber": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "streamer": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "tier": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "provider": {
+ "type": "string"
+ },
+ "provider_ref": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "route": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "status": {
+ "enum": [
+ "active",
+ "canceled",
+ "expired"
+ ]
+ },
+ "auto_renew": {
+ "type": "boolean"
+ },
+ "cancel_at_period_end": {
+ "type": "boolean"
+ },
+ "price_cents": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "current_period_end": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "provider_sync": {
+ "enum": [
+ "not_needed",
+ "stripe_cancel_at_period_end"
+ ]
+ },
+ "actor": {
+ "type": "object",
+ "required": [
+ "principal",
+ "request_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "principal": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "Token sub of the caller (svc:…, usr_…)."
+ },
+ "request_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "on_behalf_of": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ }
+ },
+ "description": "Who asked: the calling principal and the person it acted for."
+ }
+ }
+ }
+ },
+ "billing.cashout.requested": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.requested.v1.json",
+ "title": "BillingCashoutRequestedPayload",
+ "description": "billing.cashout.requested v1 (OpenVibe.Billing server/ops/cashouts.js request). A creator asked to cash out payable bits; they are held in escrow. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "cashout",
+ "transaction_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "cashout": {
+ "type": "object",
+ "required": [
+ "id",
+ "subject",
+ "amount_bits",
+ "value_cents",
+ "status",
+ "payout_method",
+ "escrow_until",
+ "request_txn",
+ "settle_txn",
+ "payout_provider",
+ "payout_reference",
+ "reason",
+ "created_at",
+ "updated_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "subject": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "amount_bits": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "value_cents": {
+ "type": "integer"
+ },
+ "status": {
+ "const": "requested"
+ },
+ "payout_method": {
+ "type": "object",
+ "description": "{ type, address } as the creator entered it (e.g. a PayPal email)."
+ },
+ "escrow_until": {
+ "type": "string"
+ },
+ "request_txn": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "settle_txn": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "payout_provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "payout_reference": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "transaction_id": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The cashout_request transaction."
+ }
+ }
+ }
+ },
+ "billing.cashout.paid": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.paid.v1.json",
+ "title": "BillingCashoutPaidPayload",
+ "description": "billing.cashout.paid v1 (OpenVibe.Billing server/ops/cashouts.js approve). Staff paid a cash-out out through a provider. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "cashout",
+ "transaction_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "cashout": {
+ "type": "object",
+ "required": [
+ "id",
+ "subject",
+ "amount_bits",
+ "value_cents",
+ "status",
+ "payout_method",
+ "escrow_until",
+ "request_txn",
+ "settle_txn",
+ "payout_provider",
+ "payout_reference",
+ "reason",
+ "created_at",
+ "updated_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "subject": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "amount_bits": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "value_cents": {
+ "type": "integer"
+ },
+ "status": {
+ "const": "paid"
+ },
+ "payout_method": {
+ "type": "object",
+ "description": "{ type, address } as the creator entered it (e.g. a PayPal email)."
+ },
+ "escrow_until": {
+ "type": "string"
+ },
+ "request_txn": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "settle_txn": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "payout_provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "payout_reference": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "transaction_id": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The cashout_paid transaction."
+ }
+ }
+ }
+ },
+ "billing.cashout.denied": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.cashout.denied.v1.json",
+ "title": "BillingCashoutDeniedPayload",
+ "description": "billing.cashout.denied v1 (OpenVibe.Billing server/ops/cashouts.js deny). Staff denied a cash-out; the escrowed bits went back (a billing.transaction.reversed follows in the same transaction). Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "cashout",
+ "transaction_id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "cashout": {
+ "type": "object",
+ "required": [
+ "id",
+ "subject",
+ "amount_bits",
+ "value_cents",
+ "status",
+ "payout_method",
+ "escrow_until",
+ "request_txn",
+ "settle_txn",
+ "payout_provider",
+ "payout_reference",
+ "reason",
+ "created_at",
+ "updated_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^co_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "subject": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "amount_bits": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "value_cents": {
+ "type": "integer"
+ },
+ "status": {
+ "const": "denied"
+ },
+ "payout_method": {
+ "type": "object",
+ "description": "{ type, address } as the creator entered it (e.g. a PayPal email)."
+ },
+ "escrow_until": {
+ "type": "string"
+ },
+ "request_txn": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "settle_txn": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "payout_provider": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "payout_reference": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "transaction_id": {
+ "type": "string",
+ "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The cashout_denied transaction."
+ }
+ }
+ }
+ },
+ "billing.staff.action": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.staff.action.v1.json",
+ "title": "BillingStaffActionPayload",
+ "description": "billing.staff.action v1 (OpenVibe.Billing server/console/audit.js record). A staff member completed an action in the Billing console (only outcome done is announced; refused actions stay in staff_audit). Holds no secrets: codes, payout reference, counts. Envelope: subject { type: staff_action, id: sa_… }, visibility internal, priority important, actor the staff user ({ type: user, id: usr_… }).",
+ "type": "object",
+ "required": [
+ "audit_id",
+ "action",
+ "outcome",
+ "target",
+ "reason",
+ "request_id",
+ "detail"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "audit_id": {
+ "type": "string",
+ "pattern": "^sa_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "action": {
+ "type": "string"
+ },
+ "outcome": {
+ "const": "done"
+ },
+ "target": {
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "type",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ }
+ }
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500
+ },
+ "request_id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "detail": {
+ "type": "object",
+ "description": "Small whitelisted detail of the action."
+ }
+ }
+ }
+ },
+ "billing.receipt.external": {
+ "version": "1.0.0",
+ "owner": "billing",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/billing.receipt.external.v1.json",
+ "title": "BillingReceiptExternalPayload",
+ "description": "billing.receipt.external v1 (OpenVibe.Billing server/ops/external.js record). An EXTERNAL receipt (ADR-012): a tip paid on a creator's own provider account (PowerChat route direct). The money never touches OpenVibe (no journal entry); Billing announces it once per payment, only when BILLING_AUTHORITY=billing and the receiving account is mapped to a creator. OpenVibe.Tips celebrates it (chat line, overlay alert, goal). Envelope: subject { type: provider_receipt, id: <provider>:<payment id> }, visibility internal, priority important, actor service:billing.",
+ "type": "object",
+ "required": [
+ "classification",
+ "provider",
+ "receipt_ref",
+ "provider_event_id",
+ "delivery_id",
+ "streamer",
+ "receiving_account",
+ "amount_cents",
+ "currency",
+ "value_bits",
+ "donor_name",
+ "anonymous",
+ "message",
+ "app_ref",
+ "app_purpose",
+ "occurred_at",
+ "test",
+ "rates"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "classification": {
+ "const": "EXTERNAL"
+ },
+ "provider": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9_-]{1,39}$"
+ },
+ "receipt_ref": {
+ "type": "string",
+ "description": "<provider>:<payment id>, e.g. powerchat:…"
+ },
+ "provider_event_id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 200,
+ "description": "The provider's id of the payment, stable across redeliveries."
+ },
+ "delivery_id": {
+ "type": "string",
+ "description": "The webhook delivery that carried it."
+ },
+ "streamer": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "receiving_account": {
+ "type": "object",
+ "required": [
+ "provider",
+ "id",
+ "username"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "provider": {
+ "type": "string"
+ },
+ "id": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "username": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ },
+ "amount_cents": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 100000000
+ },
+ "currency": {
+ "const": "usd-cents"
+ },
+ "value_bits": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Billing's value of the money in bits."
+ },
+ "donor_name": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80,
+ "description": "null when anonymous."
+ },
+ "anonymous": {
+ "type": "boolean"
+ },
+ "message": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500
+ },
+ "app_ref": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "app_purpose": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 120
+ },
+ "occurred_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The provider's timestamp as it sent it."
+ },
+ "test": {
+ "type": "boolean"
+ },
+ "rates": {
+ "type": "object",
+ "required": [
+ "bits_per_usd"
+ ],
+ "properties": {
+ "bits_per_usd": {
+ "type": "number"
+ }
+ },
+ "description": "Rate snapshot."
+ }
+ }
+ }
+ },
+ "deals.watch.matched": {
+ "version": "1.0.0",
+ "owner": "deals",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/deals.watch.matched.v1.json",
+ "title": "DealsWatchMatchedPayload",
+ "description": "deals.watch.matched v1 (OpenVibe.Deals server/domain/watches.js onObservation). A person's keyword, product or price-below watch matched a fresh observation of an active offer, inside the transaction that recorded the observation. At most one event per (watch, observation); a keyword/product watch notifies once per offer, a price-below watch again only for a lower price. Saved searches (kind search) never notify. Envelope: subject { type: watch, id: <watch_id> }, visibility internal, priority important, actor service:deals. OpenVibe.Network turns it into a DEAL_WATCH_MATCH notification for payload.recipient (server/notifications/events-consumer.js). A price is the decimal exactly as stated, never inferred; null when the observation stated none.",
+ "type": "object",
+ "required": [
+ "watch_id",
+ "recipient",
+ "kind",
+ "query",
+ "product_id",
+ "max_price",
+ "currency",
+ "offer_id",
+ "offer_url",
+ "title",
+ "observation"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "watch_id": {
+ "type": "string",
+ "pattern": "^dwt_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "recipient": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The watch owner's user subject id: who is notified."
+ },
+ "kind": {
+ "enum": [
+ "keyword",
+ "product",
+ "price_below"
+ ]
+ },
+ "query": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200,
+ "description": "Keywords of a keyword watch, or of a price-below watch without a product."
+ },
+ "product_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 64,
+ "description": "Deals product id of a product or price-below watch."
+ },
+ "max_price": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 40,
+ "description": "Price-below threshold as entered (decimal text, e.g. 19.99)."
+ },
+ "currency": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^[A-Z]{3}$",
+ "description": "ISO 4217 currency of max_price."
+ },
+ "offer_id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64,
+ "description": "The canonical (root) offer id (dof_…)."
+ },
+ "offer_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "maxLength": 2048,
+ "description": "Canonical offer page URL on Deals."
+ },
+ "title": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 500
+ },
+ "observation": {
+ "type": "object",
+ "required": [
+ "id",
+ "observed_at",
+ "price",
+ "currency",
+ "availability"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^dpo_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "observed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "price": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 40,
+ "description": "Decimal text as stated; null when the observation stated no price."
+ },
+ "currency": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^[A-Z]{3}$"
+ },
+ "availability": {
+ "enum": [
+ "in_stock",
+ "out_of_stock",
+ "preorder",
+ "discontinued",
+ "limited",
+ "sold_out",
+ "online_only",
+ "in_store_only",
+ null
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "trade.alert.triggered": {
+ "version": "1.0.0",
+ "owner": "trade",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/trade.alert.triggered.v1.json",
+ "title": "TradeAlertTriggeredPayload",
+ "description": "trade.alert.triggered v1 (OpenVibe.Trade server/domain/alerts.js deliver). One delivery of one private alert rule: a threshold crossed by a new observation (trigger kind observation), or a newly seen document of the instrument (filing_type / new_document rules, trigger kind document). Written in the transaction that recorded the observation or document; at most one per (rule, trigger). Envelope: subject { type: user, id: <rule owner usr_…> }, visibility subject, priority important, actor service:trade. Data only, never rendered notification text (ADR-020): OpenVibe.Network renders a TRADE_ALERT notification (server/notifications/events-consumer.js). Information only, not investment advice.",
+ "type": "object",
+ "required": [
+ "delivery_id",
+ "rule",
+ "instrument",
+ "trigger",
+ "disclaimer"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "delivery_id": {
+ "type": "string",
+ "pattern": "^ald_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "rule": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "kind",
+ "status",
+ "instrument",
+ "metric",
+ "operator",
+ "threshold",
+ "unit",
+ "currency",
+ "form_types",
+ "armed",
+ "created_at",
+ "updated_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^alr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "kind": {
+ "enum": [
+ "threshold",
+ "filing_type",
+ "new_document"
+ ]
+ },
+ "status": {
+ "enum": [
+ "active",
+ "deleted"
+ ]
+ },
+ "instrument": {
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "symbol": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "metric": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80,
+ "description": "Threshold rules: e.g. price.close, volume, us-gaap:Revenues."
+ },
+ "operator": {
+ "enum": [
+ "above",
+ "below",
+ null
+ ]
+ },
+ "threshold": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 64,
+ "description": "Decimal text as entered."
+ },
+ "unit": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 20
+ },
+ "currency": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^[A-Z]{3}$"
+ },
+ "form_types": {
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "type": "string",
+ "maxLength": 20
+ },
+ "description": "filing_type rules: the 1–10 form types that trigger it (10-K, 8-K…)."
+ },
+ "armed": {
+ "type": [
+ "boolean",
+ "null"
+ ],
+ "description": "Threshold rules: armed before this delivery (always true when it fired); null for document rules."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "instrument": {
+ "type": "object",
+ "required": [
+ "id",
+ "symbol",
+ "name",
+ "url"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^ins_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "symbol": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 32
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 300
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://",
+ "maxLength": 2048,
+ "description": "Instrument page on Trade."
+ }
+ }
+ },
+ "trigger": {
+ "oneOf": [
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind",
+ "id",
+ "metric",
+ "value",
+ "unit",
+ "currency",
+ "observed_at",
+ "retrieved_at",
+ "source_key",
+ "source_url"
+ ],
+ "properties": {
+ "kind": {
+ "const": "observation"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^obs_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "metric": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "value": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64,
+ "description": "The decimal exactly as the source stated it."
+ },
+ "unit": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20
+ },
+ "currency": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^[A-Z]{3}$"
+ },
+ "observed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "retrieved_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "source_key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "description": "OpenVibe.Sources source key."
+ },
+ "source_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 2048
+ }
+ }
+ },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind",
+ "id",
+ "form_type",
+ "title",
+ "url",
+ "published_at",
+ "retrieved_at",
+ "source_key"
+ ],
+ "properties": {
+ "kind": {
+ "const": "document"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^doc_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "form_type": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 20
+ },
+ "title": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 1000
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 2048
+ },
+ "published_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "The source's own date; null if it gave none."
+ },
+ "retrieved_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "source_key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128
+ }
+ }
+ }
+ ]
+ },
+ "disclaimer": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 200,
+ "description": "Currently \"Information only — not investment advice; no trading here.\""
+ }
+ }
+ }
+ },
+ "tools.job.created": {
+ "version": "1.0.0",
+ "owner": "tools",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.created.v1.json",
+ "title": "ToolsJobCreatedPayload",
+ "description": "tools.job.created v1 (OpenVibe.Tools apps/_shared/jobs, planned). A job was accepted and queued (POST /api/v1/jobs, or a retry of a failed job, which carries retry_of). An Idempotency-Key replay creates no job and no event. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor the owner when it is a subject (user, service, app or mod), else service:tools.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "service",
+ "type",
+ "type_version",
+ "state",
+ "owner",
+ "attempts",
+ "max_attempts",
+ "created_at",
+ "retry_of"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$",
+ "description": "The Tools satellite that runs the job (img, audio, docs…)."
+ },
+ "type": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",
+ "maxLength": 80,
+ "description": "Job type, e.g. img.process."
+ },
+ "type_version": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "state": {
+ "const": "queued"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "max_attempts": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "retry_of": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."
+ }
+ }
+ }
+ },
+ "tools.job.started": {
+ "version": "1.0.0",
+ "owner": "tools",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.started.v1.json",
+ "title": "ToolsJobStartedPayload",
+ "description": "tools.job.started v1 (OpenVibe.Tools apps/_shared/jobs, planned). A worker claimed the job and it is running (attempts counts this attempt). A job requeued after a restart starts again and is announced again. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:tools.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "service",
+ "type",
+ "type_version",
+ "state",
+ "owner",
+ "attempts",
+ "max_attempts",
+ "created_at",
+ "retry_of",
+ "started_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$",
+ "description": "The Tools satellite that runs the job (img, audio, docs…)."
+ },
+ "type": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",
+ "maxLength": 80,
+ "description": "Job type, e.g. img.process."
+ },
+ "type_version": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "state": {
+ "const": "running"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "max_attempts": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "retry_of": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "tools.job.succeeded": {
+ "version": "1.0.0",
+ "owner": "tools",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.succeeded.v1.json",
+ "title": "ToolsJobSucceededPayload",
+ "description": "tools.job.succeeded v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job finished and its result files are stored (in Media when TOOLS_JOB_RESULTS=media). Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "service",
+ "type",
+ "type_version",
+ "state",
+ "owner",
+ "attempts",
+ "max_attempts",
+ "created_at",
+ "retry_of",
+ "started_at",
+ "finished_at",
+ "expires_at",
+ "result"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$",
+ "description": "The Tools satellite that runs the job (img, audio, docs…)."
+ },
+ "type": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",
+ "maxLength": 80,
+ "description": "Job type, e.g. img.process."
+ },
+ "type_version": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "state": {
+ "const": "succeeded"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "max_attempts": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "retry_of": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)."
+ },
+ "result": {
+ "type": "object",
+ "required": [
+ "files"
+ ],
+ "additionalProperties": false,
+ "description": "Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).",
+ "properties": {
+ "files": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "type": "object",
+ "required": [
+ "index",
+ "mime",
+ "size",
+ "sha256",
+ "storage",
+ "media"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "index": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "n in /api/v1/jobs/:id/files/:n."
+ },
+ "mime": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 200
+ },
+ "size": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "sha256": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{64}$"
+ },
+ "storage": {
+ "enum": [
+ "local",
+ "media"
+ ],
+ "description": "media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires."
+ },
+ "media": {
+ "oneOf": [
+ {
+ "$ref": "../../media/media-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "{ media_id, role: output } when storage is media."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tools.job.failed": {
+ "version": "1.0.0",
+ "owner": "tools",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/tools.job.failed.v1.json",
+ "title": "ToolsJobFailedPayload",
+ "description": "tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "service",
+ "type",
+ "type_version",
+ "state",
+ "owner",
+ "attempts",
+ "max_attempts",
+ "created_at",
+ "retry_of",
+ "started_at",
+ "finished_at",
+ "expires_at",
+ "error",
+ "retryable"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$",
+ "description": "The Tools satellite that runs the job (img, audio, docs…)."
+ },
+ "type": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9]*\\.[a-z0-9_.]+$",
+ "maxLength": 80,
+ "description": "Job type, e.g. img.process."
+ },
+ "type_version": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "state": {
+ "const": "failed"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published."
+ },
+ "attempts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "max_attempts": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "retry_of": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The failed job this one retries (POST /api/v1/jobs/:id/retry)."
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "null when the job failed before it ever ran."
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref)."
+ },
+ "error": {
+ "type": "object",
+ "required": [
+ "status",
+ "code",
+ "detail"
+ ],
+ "additionalProperties": false,
+ "description": "The job's problem (errors.problem@1) without request or trace ids.",
+ "properties": {
+ "status": {
+ "type": "integer",
+ "minimum": 400,
+ "maximum": 599
+ },
+ "code": {
+ "type": "string",
+ "pattern": "^tools\\.[a-z0-9_.]+$",
+ "description": "tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…)."
+ },
+ "detail": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "Human-readable reason; server paths are scrubbed."
+ }
+ }
+ },
+ "retryable": {
+ "type": "boolean"
+ }
+ }
+ }
}
}
}
}
}
}
@@ -3371,3 +3371,3245 @@ export interface AppManifest {
@@ -3371,3 +3371,3245 @@ export interface AppManifest {
sdk?: string;
sdk?: string;
};
};
}
}
+
+/** [email protected] (owner: events) */
+/**
+ * payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.
+ */
+export interface RedactionDirective {
+ /**
+ * Events to redact by id. Duplicates are ignored.
+ *
+ * @minItems 1
+ * @maxItems 1000
+ */
+ event_ids?: [string, ...string[]];
+ /**
+ * Envelope subject.type of the events to redact (chat_message, post…). Goes with subject_ids.
+ */
+ subject_type?: string;
+ /**
+ * Envelope subject.id values; every not-yet-redacted event of the same source about each one is redacted.
+ *
+ * @minItems 1
+ * @maxItems 1000
+ */
+ subject_ids?: [string, ...string[]];
+}
+
+/** [email protected] (owner: events) */
+/**
+ * The payload of a redacted event (ADR-026). OpenVibe.Events replaces the original payload with this and the actor with the producer itself ({ type: service, id: <source> }, or the app); event_id, seq, event_type, version, subject, timestamp and visibility stay. Every read path serves it from then on: pull, GET by id, SSE replay, queued deliveries and DLQ replays. A consumer checks payload.redacted before validating a payload against its event type's schema.
+ */
+export interface TombstonePayload {
+ redacted: true;
+ redacted_at: string;
+ /**
+ * The event_id of the redacting event; an operator label (e.g. backfill) when OpenVibe.Events redacted by script.
+ */
+ redacted_by: 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.
+ */
+export interface ChatMessageDeletedPayload {
+ /**
+ * Chat message ids (the id in chat.message.created).
+ *
+ * @minItems 1
+ * @maxItems 500
+ */
+ message_ids: [number, ...number[]];
+ redacts: RedactionDirective;
+}
+
+/** [email protected] (owner: sources) */
+/**
+ * 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.
+ */
+export interface SourcesItemCreatedPayload {
+ item_id: string;
+ source_key: string;
+ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";
+ /**
+ * article | url | sitemap | product | offer | review | record … (sources.item@1 kind).
+ */
+ kind: string;
+ canonical_url: string | null;
+ title: string | null;
+ revision: number;
+ content_hash: string;
+ /**
+ * Last successful fetch (or manual entry) that contained the item.
+ */
+ retrieved_at: string;
+}
+
+/** [email protected] (owner: sources) */
+/**
+ * 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.
+ */
+export interface SourcesItemUpdatedPayload {
+ item_id: string;
+ source_key: string;
+ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";
+ /**
+ * article | url | sitemap | product | offer | review | record … (sources.item@1 kind).
+ */
+ kind: string;
+ canonical_url: string | null;
+ title: string | null;
+ revision: number;
+ content_hash: string;
+ /**
+ * Last successful fetch (or manual entry) that contained the item.
+ */
+ retrieved_at: string;
+ previous_content_hash: string;
+}
+
+/** [email protected] (owner: sources) */
+/**
+ * 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.
+ */
+export interface SourcesItemRemovedPayload {
+ item_id: string;
+ source_key: string;
+ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";
+ /**
+ * article | url | sitemap | product | offer | review | record … (sources.item@1 kind).
+ */
+ kind: string;
+ canonical_url: string | null;
+ title: string | null;
+ revision: number;
+ content_hash: string;
+ /**
+ * Last successful fetch (or manual entry) that contained the item.
+ */
+ retrieved_at: string;
+ reason: string;
+}
+
+/** [email protected] (owner: sources) */
+/**
+ * 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.
+ */
+export interface SourcesFetchFailedPayload {
+ source_key: string;
+ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";
+ /**
+ * The recorded fetch run (GET /api/v1/sources/:key/runs).
+ */
+ run_id: string;
+ endpoint_url: string;
+ state: "robots_denied" | "http_error" | "rate_limited" | "parse_error";
+ /**
+ * robots | address_refused | port_refused | bad_url | upstream_429 | http_<status> | unreadable | adapter_error | internal.
+ */
+ error_code: string;
+ http_status: number | null;
+ /**
+ * The source's consecutive failed runs including this one.
+ */
+ consecutive_failures: number;
+}
+
+/** [email protected] (owner: sources) */
+/**
+ * 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.
+ */
+export interface SourcesIndexDocumentUpsertedPayload {
+ /**
+ * Owning service id (wiki, blog, news, community, sources…). Only that service may write the document.
+ */
+ owner: string;
+ /**
+ * Resource type inside the owner (page, post, story, entity, offer, coupon, instrument, item…).
+ */
+ type: string;
+ /**
+ * Resource id inside owner+type. Stable across revisions.
+ */
+ id: string;
+ /**
+ * Owner's monotonic revision of the resource. An older revision never overwrites a newer one; at equal revision a deletion wins.
+ */
+ revision: number;
+ /**
+ * Deletion marker. true = tombstone; every other content field is ignored.
+ */
+ deleted?: boolean;
+ /**
+ * public: anyone. unlisted: never listed except to ACL matches; fetchable by exact id by a signed-in subject. members: subjects, groups or entitlements in acl. private: subjects in acl.subjects only. draft: never served.
+ */
+ visibility?: "public" | "unlisted" | "members" | "private" | "draft";
+ /**
+ * Who may see a non-public document. Ignored for public documents. Empty means nobody (for members/private) or direct-id only (for unlisted).
+ */
+ acl?: {
+ /**
+ * @maxItems 200
+ */
+ subjects?: string[];
+ /**
+ * Opaque group keys owned by some service, e.g. role:admin (Network role), wiki.space:spc_…:member.
+ *
+ * @maxItems 100
+ */
+ groups?: string[];
+ /**
+ * Entitlement keys (VIP/Billing), e.g. vip.plan:pln_…. Only a first-party service that just resolved them may present them for a viewer.
+ *
+ * @maxItems 100
+ */
+ entitlements?: string[];
+ };
+ canonical_url?: string;
+ title?: string;
+ summary?: string;
+ /**
+ * Plain text for full-text search (no HTML). Keep it under the Events payload limit when publishing through Events.
+ */
+ body?: string;
+ /**
+ * Filterable values (category, tags, space, author subject, currency…). Filter with facet.<key>=<value>.
+ */
+ facets?: {
+ [k: string]: (string | number | boolean | string[]) | undefined;
+ };
+ /**
+ * BCP 47 tag.
+ */
+ language?: string;
+ authorship?: "human" | "ai_assisted" | "ai_generated" | "imported";
+ /**
+ * Where the content came from: typed references to source records (Sources items, citations, Media objects).
+ *
+ * @maxItems 50
+ */
+ provenance?: {
+ service: string;
+ type: string;
+ id: string;
+ revision?: number;
+ label?: string;
+ url?: string;
+ retrieved_at?: string;
+ /**
+ * true when the reference came from a stub provider; the document is then never indexable.
+ */
+ stub?: boolean;
+ }[];
+ /**
+ * Only published documents are ever served.
+ */
+ publication_state?: "draft" | "scheduled" | "published" | "unpublished" | "retracted" | "archived";
+ published_at?: string | null;
+ updated_at?: string | null;
+ /**
+ * The owner's deterministic indexability decision (publishing packages, roadmap §32.3). Search can only make it stricter.
+ */
+ indexability?: {
+ decision: "index" | "noindex";
+ /**
+ * Known reasons: draft, private, members_only, unlisted, not_published, deleted, thin_content, duplicate_without_canonical, unsupported_claims, unsourced, stub_provider, sensitive_unreviewed, ai_unreviewed, missing_canonical_url, third_party_content, owner_decision.
+ *
+ * @maxItems 20
+ */
+ reasons?:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ };
+}
+
+/** [email protected] (owner: sources) */
+/**
+ * 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.
+ */
+export interface SourcesIndexDocumentDeletedPayload {
+ type: "item";
+ id: string;
+ revision: number;
+}
+
+/** [email protected] (owner: search) */
+/**
+ * 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.
+ */
+export interface SearchDocumentIndexedPayload {
+ owner: string;
+ type: string;
+ id: string;
+ revision: number;
+ exposure: "restricted" | "public_unlisted" | "public_listed";
+ /**
+ * true when Search already held an earlier revision of the document.
+ */
+ reindexed: boolean;
+ /**
+ * Only when the document is public (public_unlisted or public_listed); null otherwise or when it has none.
+ */
+ canonical_url: string | null;
+}
+
+/** [email protected] (owner: search) */
+/**
+ * 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.
+ */
+export interface SearchDocumentRemovedPayload {
+ owner: string;
+ type: string;
+ id: string;
+ revision: number;
+ reason: "deleted" | "draft" | "not_published" | "visibility_changed" | "noindex";
+ previous_exposure: "restricted" | "public_unlisted" | "public_listed";
+ /**
+ * Lower than previous_exposure. none: no longer served to anyone.
+ */
+ exposure: "none" | "restricted" | "public_unlisted";
+ /**
+ * The URL only when the document was public before (so it must be purged); null otherwise.
+ */
+ canonical_url: string | null;
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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.
+ */
+export interface WikiSpaceUpdatedPayload {
+ slug: string;
+ name: string;
+ kind: "official" | "user";
+ visibility: "public" | "members" | "private";
+ deleted: boolean;
+ /**
+ * What changed. renamed = a new slug.
+ */
+ change: "created" | "renamed" | "visibility" | "settings" | "roles" | "deleted";
+ /**
+ * Canonical URL of the space.
+ */
+ url: string;
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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.
+ */
+export interface WikiRevisionCreatedPayload {
+ space_id: string;
+ /**
+ * Space slug.
+ */
+ space: string;
+ /**
+ * Page slug.
+ */
+ slug: string;
+ /**
+ * The new revision number.
+ */
+ number: number;
+ kind: "edit" | "revert" | "import";
+ parent_number: number | null;
+ /**
+ * The revision a revert restored; null otherwise.
+ */
+ reverted_to: number | null;
+ /**
+ * Search's authorship vocabulary; null when the revision has no authorship record.
+ */
+ authorship: "human" | "ai_assisted" | "ai_generated" | "imported" | null;
+ /**
+ * Who wrote it: a subject id (usr_…) or a service (svc:<id>).
+ */
+ author: string | null;
+ message: string | null;
+ /**
+ * Present when the revision is an AI proposal awaiting a person's approval.
+ */
+ proposal_id?: string;
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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.
+ */
+export interface WikiWatchTriggeredPayload {
+ /**
+ * revised = a new draft revision; proposed = an AI proposal awaits approval; the rest follow wiki.page.*.
+ */
+ action: "published" | "updated" | "unpublished" | "deleted" | "revised" | "proposed";
+ /**
+ * Space slug.
+ */
+ space: string;
+ /**
+ * Page slug.
+ */
+ slug: string;
+ title: string;
+ revision: number | null;
+ url: string;
+ /**
+ * Watchers to notify.
+ *
+ * @minItems 1
+ */
+ recipients: [string, ...string[]];
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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).
+ */
+export interface WikiPagePublishedPayload {
+ canonical_url: string;
+ publication_state: "published";
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ };
+ /**
+ * Space slug.
+ */
+ space: string;
+ /**
+ * Page slug inside the space.
+ */
+ slug: string;
+ /**
+ * Effective visibility (the page's, narrowed by its space's).
+ */
+ visibility: "public" | "members" | "private";
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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).
+ */
+export interface WikiPageUpdatedPayload {
+ canonical_url: string;
+ publication_state: "published";
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ };
+ /**
+ * Space slug.
+ */
+ space: string;
+ /**
+ * Page slug inside the space.
+ */
+ slug: string;
+ /**
+ * Effective visibility (the page's, narrowed by its space's).
+ */
+ visibility: "public" | "members" | "private";
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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).
+ */
+export interface WikiPageUnpublishedPayload {
+ /**
+ * null: the event carries a tombstone, which has no URL.
+ */
+ canonical_url: string | null;
+ publication_state: "unpublished";
+ /**
+ * The last gate decision, or null when there is none.
+ */
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ } | null;
+ /**
+ * Space slug.
+ */
+ space: string;
+ /**
+ * Page slug inside the space.
+ */
+ slug: string;
+ /**
+ * Effective visibility (the page's, narrowed by its space's).
+ */
+ visibility: "public" | "members" | "private";
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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).
+ */
+export interface WikiPageDeletedPayload {
+ /**
+ * null: the event carries a tombstone, which has no URL.
+ */
+ canonical_url: string | null;
+ publication_state: "deleted";
+ /**
+ * The last gate decision, or null when there is none.
+ */
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ } | null;
+ /**
+ * Space slug.
+ */
+ space: string;
+ /**
+ * Page slug inside the space.
+ */
+ slug: string;
+ /**
+ * Effective visibility (the page's, narrowed by its space's).
+ */
+ visibility: "public" | "members" | "private";
+}
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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.
+ */
+export type WikiIndexDocumentUpsertedPayload = IndexDocument & {
+ owner: "wiki";
+ type: "page";
+ id?: string;
+ deleted: false;
+};
+
+/** [email protected] (owner: wiki) */
+/**
+ * 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.
+ */
+export interface WikiIndexDocumentDeletedPayload {
+ type: "page";
+ id: string;
+ /**
+ * Index revision of the tombstone; wins over any document at the same or an older revision.
+ */
+ revision: number;
+}
+
+/** [email protected] (owner: blog) */
+/**
+ * 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.
+ */
+export interface BlogPostCreatedPayload {
+ blog: {
+ id: string;
+ handle: string;
+ };
+ state: "draft";
+ visibility: "public" | "unlisted" | "members" | "private";
+ /**
+ * openvibe-publishing authorship mode (not Search's vocabulary).
+ */
+ authorship: "human" | "ai" | "hybrid" | "imported";
+ /**
+ * The OpenVibe.AI workflow and run behind ai and hybrid posts; absent for human posts.
+ */
+ workflow?: {
+ id: string;
+ runId: string;
+ version?: number | string;
+ model?: string;
+ };
+}
+
+/** [email protected] (owner: blog) */
+/**
+ * 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).
+ */
+export interface BlogPostPublishedPayload {
+ canonical_url: string;
+ publication_state: "published";
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ };
+ blog: {
+ id: string;
+ handle: string;
+ };
+ /**
+ * The post's own visibility (members = entitlement-gated).
+ */
+ visibility: "public" | "unlisted" | "members" | "private";
+}
+
+/** [email protected] (owner: blog) */
+/**
+ * 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).
+ */
+export interface BlogPostUpdatedPayload {
+ canonical_url: string;
+ publication_state: "published";
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ };
+ blog: {
+ id: string;
+ handle: string;
+ };
+ /**
+ * The post's own visibility (members = entitlement-gated).
+ */
+ visibility: "public" | "unlisted" | "members" | "private";
+}
+
+/** [email protected] (owner: blog) */
+/**
+ * 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).
+ */
+export interface BlogPostUnpublishedPayload {
+ /**
+ * null: the event carries a tombstone, which has no URL.
+ */
+ canonical_url: string | null;
+ publication_state: "unpublished";
+ /**
+ * The last gate decision, or null when there is none.
+ */
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ } | null;
+ blog: {
+ id: string;
+ handle: string;
+ };
+ /**
+ * The post's own visibility (members = entitlement-gated).
+ */
+ visibility: "public" | "unlisted" | "members" | "private";
+}
+
+/** [email protected] (owner: blog) */
+/**
+ * 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).
+ */
+export interface BlogPostDeletedPayload {
+ /**
+ * null: the event carries a tombstone, which has no URL.
+ */
+ canonical_url: string | null;
+ publication_state: "deleted";
+ /**
+ * The last gate decision, or null when there is none.
+ */
+ indexability: {
+ decision: "index" | "noindex";
+ /**
+ * @maxItems 20
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ];
+ } | null;
+ blog: {
+ id: string;
+ handle: string;
+ };
+ /**
+ * The post's own visibility (members = entitlement-gated).
+ */
+ visibility: "public" | "unlisted" | "members" | "private";
+}
+
+/** [email protected] (owner: blog) */
+/**
+ * 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.
+ */
+export interface BlogScheduleFailedPayload {
+ job_id: string;
+ action: "publish" | "unpublish";
+ /**
+ * The revision a publish was to make live; null for an unpublish.
+ */
+ revision: number | null;
+ /**
+ * When the job was last due.
+ */
+ run_at: string;
+ attempts: number;
+ /**
+ * The last error.
+ */
+ error: string | null;
+ /**
+ * null when the post no longer exists.
+ */
+ blog_id: string | null;
+}
+
+/** [email protected] (owner: blog) */
+/**
+ * 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.
+ */
+export type BlogIndexDocumentUpsertedPayload = IndexDocument & {
+ owner: "blog";
+ type: "post";
+ id?: string;
+ deleted: false;
+};
+
+/** [email protected] (owner: blog) */
+/**
+ * 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.
+ */
+export interface BlogIndexDocumentDeletedPayload {
+ type: "post";
+ id: string;
+ /**
+ * Index revision of the tombstone; wins over any document at the same or an older revision.
+ */
+ revision: number;
+}
+
+/** [email protected] (owner: ai) */
+/**
+ * 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.
+ */
+export interface AiRunRequest {
+ /**
+ * Workflow key, <namespace>.<name> (live.chat_reply, news.summarize_story, network.site_copy…).
+ */
+ workflow: string;
+ /**
+ * Pin a workflow version (active or deprecated). Omitted: the newest active version.
+ */
+ version?: number;
+ /**
+ * Workflow input; validated against the workflow version's input_schema. Default {}.
+ */
+ input?: {};
+ target?: EntityRef;
+ attribution?: EntityRef;
+ on_behalf_of?: SubjectRef;
+ /**
+ * 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.
+ */
+ idempotency_key?: string;
+ options?: {
+ /**
+ * false skips the scoped cache lookup and store.
+ */
+ cache?: boolean;
+ /**
+ * Keep raw prompts and responses in the request log, only when AI runs with raw debug logging on.
+ */
+ debug?: boolean;
+ };
+}
+
+/** [email protected] (owner: ai) */
+/**
+ * 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.
+ */
+export interface AiRun {
+ id: string;
+ /**
+ * Terminal: succeeded, failed, cancelled, cached (a scoped cache hit, no provider call; provenance.cached_from names the run it reused).
+ */
+ status: "queued" | "running" | "succeeded" | "failed" | "cancelled" | "cached";
+ workflow: {
+ key: string;
+ version: number;
+ };
+ /**
+ * The prompt template of the workflow's llm step; null for workflows without one.
+ */
+ template: {
+ key: string;
+ version: number | null;
+ } | null;
+ /**
+ * Provider route; version null while the route is unresolved or disabled.
+ */
+ route: {
+ key: string;
+ version: number | null;
+ } | null;
+ requester: SubjectRef;
+ on_behalf_of: SubjectRef | null;
+ attribution: EntityRef | null;
+ /**
+ * The requester's service id when the requester is a service.
+ */
+ source_service: string | null;
+ target: EntityRef | null;
+ /**
+ * The retained input (see the description).
+ */
+ input: {};
+ /**
+ * Workflow output, or null.
+ */
+ output: {} | null;
+ error: {
+ /**
+ * run.cancelled, run.interrupted, run.internal, output.invalid, output.empty, provider.unavailable, route.unavailable, source.unavailable, media.unresolvable…
+ */
+ code: string;
+ detail: string | null;
+ } | null;
+ /**
+ * Produced by the stub provider; never served to production callers and never cached.
+ */
+ synthetic: boolean;
+ provenance: {
+ origin: "ai";
+ workflow: string;
+ workflow_version: number;
+ template_version: number | null;
+ route: string | null;
+ route_version: number | null;
+ provider: string | null;
+ model: string | null;
+ /**
+ * The primary provider was degraded and the run fell back (ADR-015: fallbacks are recorded).
+ */
+ fallback_used: boolean;
+ run_id: string;
+ cached_from: string | null;
+ synthetic: boolean;
+ };
+ usage: {
+ tokens_in: number;
+ tokens_out: number;
+ cost_usd: number;
+ /**
+ * Provider calls made (skipped routes not counted).
+ */
+ attempts: number;
+ };
+ /**
+ * Citations stored for the run (GET /api/v1/runs/:id/citations): the sources the workflow was given, and any the requester attached later.
+ */
+ citations_count: number;
+ retry_of: string | null;
+ idempotency_key: string | null;
+ trace_id: string | null;
+ created_at: string;
+ started_at: string | null;
+ finished_at: string | null;
+}
+
+/** [email protected] (owner: ai) */
+/**
+ * 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.
+ */
+export interface AiRunQueuedPayload {
+ run_id: string;
+ status: "queued";
+ workflow: {
+ key: string;
+ version: number;
+ };
+ requester: SubjectRef;
+ on_behalf_of: SubjectRef | null;
+ target: EntityRef | null;
+ attribution: EntityRef | null;
+ /**
+ * Provider that produced the output; null before a provider call and for cached runs.
+ */
+ provider: string | null;
+ model: string | null;
+ fallback_used: boolean;
+ /**
+ * Stub provider output.
+ */
+ synthetic: boolean;
+ cached_from: null;
+ usage: {
+ tokens_in: number;
+ tokens_out: number;
+ cost_usd: number;
+ attempts: number;
+ };
+ error: null;
+ citations_count: number;
+ retry_of: string | null;
+ created_at: string;
+ finished_at: null;
+}
+
+/** [email protected] (owner: ai) */
+/**
+ * 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.
+ */
+export interface AiRunSucceededPayload {
+ run_id: string;
+ status: "succeeded";
+ workflow: {
+ key: string;
+ version: number;
+ };
+ requester: SubjectRef;
+ on_behalf_of: SubjectRef | null;
+ target: EntityRef | null;
+ attribution: EntityRef | null;
+ /**
+ * Provider that produced the output; null before a provider call and for cached runs.
+ */
+ provider: string | null;
+ model: string | null;
+ fallback_used: boolean;
+ /**
+ * Stub provider output.
+ */
+ synthetic: boolean;
+ cached_from: null;
+ usage: {
+ tokens_in: number;
+ tokens_out: number;
+ cost_usd: number;
+ attempts: number;
+ };
+ error: null;
+ citations_count: number;
+ retry_of: string | null;
+ created_at: string;
+ finished_at: string;
+}
+
+/** [email protected] (owner: ai) */
+/**
+ * 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.
+ */
+export interface AiRunFailedPayload {
+ run_id: string;
+ status: "failed";
+ workflow: {
+ key: string;
+ version: number;
+ };
+ requester: SubjectRef;
+ on_behalf_of: SubjectRef | null;
+ target: EntityRef | null;
+ attribution: EntityRef | null;
+ /**
+ * Provider that produced the output; null before a provider call and for cached runs.
+ */
+ provider: string | null;
+ model: string | null;
+ fallback_used: boolean;
+ /**
+ * Stub provider output.
+ */
+ synthetic: boolean;
+ cached_from: null;
+ usage: {
+ tokens_in: number;
+ tokens_out: number;
+ cost_usd: number;
+ attempts: number;
+ };
+ error: {
+ code: string;
+ detail: string | null;
+ };
+ citations_count: number;
+ retry_of: string | null;
+ created_at: string;
+ finished_at: string;
+}
+
+/** [email protected] (owner: ai) */
+/**
+ * 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.
+ */
+export interface AiRunCachedPayload {
+ run_id: string;
+ status: "cached";
+ workflow: {
+ key: string;
+ version: number;
+ };
+ requester: SubjectRef;
+ on_behalf_of: SubjectRef | null;
+ target: EntityRef | null;
+ attribution: EntityRef | null;
+ /**
+ * Provider that produced the output; null before a provider call and for cached runs.
+ */
+ provider: string | null;
+ model: string | null;
+ fallback_used: boolean;
+ /**
+ * Stub provider output.
+ */
+ synthetic: boolean;
+ cached_from: string;
+ usage: {
+ tokens_in: number;
+ tokens_out: number;
+ cost_usd: number;
+ attempts: number;
+ };
+ error: null;
+ citations_count: number;
+ retry_of: string | null;
+ created_at: string;
+ finished_at: string;
+}
+
+/** [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.
+ */
+export interface TipsInteractionReadyPayload {
+ interaction_id: string;
+ creator: SubjectRef;
+ /**
+ * The supporter, null when external, anonymous or unmapped.
+ */
+ supporter: SubjectRef | null;
+ /**
+ * Display name at the time.
+ */
+ supporter_name: string | null;
+ kind: "tip" | "paid_message" | "tts" | "media_request";
+ /**
+ * Integer amount in currency minor units (Vibes bits).
+ */
+ amount: number;
+ /**
+ * vibes-bits today.
+ */
+ currency: string;
+ settlement: "billing" | "external" | "simulated" | "imported";
+ payment_state: "pending" | "settled" | "reversed" | "failed";
+ delivery_state: "awaiting_payment" | "queued" | "delivered" | "failed" | "cancelled";
+ /**
+ * The Billing transaction (txn_…) that settled it.
+ */
+ billing_txn_id: string | null;
+ /**
+ * Simulations never emit events, so false in practice.
+ */
+ test: boolean;
+}
+
+/** [email protected] (owner: tips) */
+/**
+ * tips.interaction.failed v1 (OpenVibe.Tips server/domain/interactions.js recomputeDelivery). A delivery effect gave up after its retries and none is still queued; the payment state is untouched. Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.
+ */
+export interface TipsInteractionFailedPayload {
+ interaction_id: string;
+ creator: SubjectRef;
+ /**
+ * The supporter, null when external, anonymous or unmapped.
+ */
+ supporter: SubjectRef | null;
+ /**
+ * Display name at the time.
+ */
+ supporter_name: string | null;
+ kind: "tip" | "paid_message" | "tts" | "media_request";
+ /**
+ * Integer amount in currency minor units (Vibes bits).
+ */
+ amount: number;
+ /**
+ * vibes-bits today.
+ */
+ currency: string;
+ settlement: "billing" | "external" | "simulated" | "imported";
+ payment_state: "pending" | "settled" | "reversed" | "failed";
+ delivery_state: "awaiting_payment" | "queued" | "delivered" | "failed" | "cancelled";
+ /**
+ * The Billing transaction (txn_…) that settled it.
+ */
+ billing_txn_id: string | null;
+ /**
+ * Simulations never emit events, so false in practice.
+ */
+ test: boolean;
+ /**
+ * @minItems 1
+ */
+ failed_effects: [
+ {
+ effect: "chat_line" | "paid_message" | "tts" | "media_request" | "overlay_alert";
+ adapter: string;
+ last_error: string | null;
+ },
+ ...{
+ effect: "chat_line" | "paid_message" | "tts" | "media_request" | "overlay_alert";
+ adapter: string;
+ last_error: string | null;
+ }[]
+ ];
+}
+
+/** [email protected] (owner: tips) */
+/**
+ * tips.interaction.cancelled v1 (OpenVibe.Tips server/domain/interactions.js failPayment, cancelQueued). Undelivered effects were cancelled: the payment failed before settling (reason payment_failed, with the Billing error code), Billing reversed it (payment_reversed) or an effect came up while the payment was no longer settled (payment_not_settled). Envelope: subject { type: interaction, id: tint_… }, visibility internal, priority important, actor service:tips. Never emitted for tests or imports.
+ */
+export interface TipsInteractionCancelledPayload {
+ interaction_id: string;
+ creator: SubjectRef;
+ /**
+ * The supporter, null when external, anonymous or unmapped.
+ */
+ supporter: SubjectRef | null;
+ /**
+ * Display name at the time.
+ */
+ supporter_name: string | null;
+ kind: "tip" | "paid_message" | "tts" | "media_request";
+ /**
+ * Integer amount in currency minor units (Vibes bits).
+ */
+ amount: number;
+ /**
+ * vibes-bits today.
+ */
+ currency: string;
+ settlement: "billing" | "external" | "simulated" | "imported";
+ payment_state: "pending" | "settled" | "reversed" | "failed";
+ delivery_state: "awaiting_payment" | "queued" | "delivered" | "failed" | "cancelled";
+ /**
+ * The Billing transaction (txn_…) that settled it.
+ */
+ billing_txn_id: string | null;
+ /**
+ * Simulations never emit events, so false in practice.
+ */
+ test: boolean;
+ reason: "payment_failed" | "payment_reversed" | "payment_not_settled";
+ /**
+ * Only with reason payment_failed: the Billing problem code (billing.insufficient_funds, billing.funding_reversed, billing.refused…).
+ */
+ code?: string;
+}
+
+/** [email protected] (owner: tips) */
+/**
+ * tips.goal.updated v1 (OpenVibe.Tips server/domain/goals.js changed). A goal was created, edited or closed, or its settled total moved (a contribution, the contribution that reached it, a reversal). Envelope: subject { type: goal, id: tgoal_…, revision }, visibility internal, priority important, actor service:tips.
+ */
+export interface TipsGoalUpdatedPayload {
+ goal_id: string;
+ creator: SubjectRef;
+ title: string;
+ target_amount: number;
+ /**
+ * Settled total including any amount carried over from Live.
+ */
+ current_amount: number;
+ currency: string;
+ status: "active" | "closed";
+ reached: boolean;
+ reason: "created" | "updated" | "closed" | "contribution" | "reached" | "reversal";
+ /**
+ * The interaction behind a contribution, reached or reversal change; null otherwise.
+ */
+ interaction_id: string | null;
+}
+
+/** [email protected] (owner: tips) */
+/**
+ * tips.overlay.delivered v1 (OpenVibe.Tips server/domain/overlays.js markDelivered). An overlay delivery (alert or goal update) reached at least one connected overlay, first time only. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.
+ */
+export interface TipsOverlayDeliveredPayload {
+ delivery_id: string;
+ creator: SubjectRef;
+ kind: "alert" | "goal";
+ interaction_id: string | null;
+ goal_id: string | null;
+}
+
+/** [email protected] (owner: tips) */
+/**
+ * tips.overlay.failed v1 (OpenVibe.Tips server/domain/overlays.js sweepFailed). No overlay showed the delivery within the delivery window. Envelope: subject { type: overlay_delivery, id: tovd_… }, visibility internal, priority important, actor service:tips. Never for tests.
+ */
+export interface TipsOverlayFailedPayload {
+ delivery_id: string;
+ creator: SubjectRef;
+ kind: "alert" | "goal";
+ interaction_id: string | null;
+ goal_id: string | null;
+ reason: string;
+}
+
+/** [email protected] (owner: vip) */
+/**
+ * vip.plan.published v1 (OpenVibe.VIP server/domain/plans.js emitPublished). A plan version became what new members buy: the plan was published, or a published plan was edited (a new immutable version). Carries the version's full terms, never a price (Billing prices and charges). Envelope: subject { type: plan, id: vpl_…, revision: <version> }, visibility public, priority important, actor service:vip.
+ */
+export interface VipPlanPublishedPayload {
+ plan_id: string;
+ slug: string;
+ version: number;
+ version_id: string;
+ creator: {
+ /**
+ * vcr_… or network.
+ */
+ id: string;
+ kind: "creator" | "network";
+ /**
+ * null for the network creator.
+ */
+ subject: SubjectRef | null;
+ username: string | null;
+ display_name: string | null;
+ bio: string | null;
+ status: "active" | "suspended";
+ };
+ /**
+ * The Billing product that sells it; null for a plan Billing does not sell (network plans).
+ */
+ billing_kind: "channel_subscription" | null;
+ terms: {
+ name: string;
+ description: string | null;
+ /**
+ * @maxItems 20
+ */
+ benefits:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | [
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string,
+ string
+ ]
+ | null;
+ perks: {
+ key: string;
+ name: string;
+ kind: "badge" | "emote" | "gated_content" | "room" | "role" | "other";
+ scope: "creator" | "network";
+ }[];
+ billing_kind: "channel_subscription" | null;
+ /**
+ * Never an amount: a note that Billing sets and charges the price, or null.
+ */
+ price: string | null;
+ /**
+ * sha256 prefix of name, description, benefits, perks and billing_kind.
+ */
+ digest: string;
+ };
+ published_at: string;
+}
+
+/** [email protected] (owner: vip) */
+/**
+ * vip.membership.changed v1 (OpenVibe.VIP server/domain/entitlements.js emitChanged). A member's standing with a creator changed as projected from Billing (granted, renewed, cancel scheduled, expired, refunded…): active, expires_at or cancel_at_period_end differ from before, or a first look found an active membership. Names the plan version the membership was bought under. Envelope: subject { type: membership, id: <member usr_…>:<creator usr_…> }, visibility internal, priority important, actor service:vip.
+ */
+export interface VipMembershipChangedPayload {
+ member: SubjectRef;
+ creator: SubjectRef;
+ /**
+ * Billing entitlement kind; channel_subscription today.
+ */
+ kind: string;
+ active: boolean;
+ expires_at: string | null;
+ cancel_at_period_end: boolean;
+ /**
+ * The projection before the change; null on first sight.
+ */
+ previous: {
+ active: boolean;
+ expires_at: string | null;
+ cancel_at_period_end: boolean;
+ } | null;
+ /**
+ * Billing's reason (granted, renewed, refund, expired…) or VIP's (checked, cancel_scheduled).
+ */
+ reason: string | null;
+ source: "event" | "billing_check" | "import";
+ /**
+ * The Billing event (evt_…) behind the change, when it came from one.
+ */
+ billing_event_id: string | null;
+ membership_id: string | null;
+ plan_id: string | null;
+ plan_version_id: string | null;
+ plan_version: number | null;
+ /**
+ * The Billing subscription (sub_…).
+ */
+ subscription_id: string | null;
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.transaction.settled v1 (OpenVibe.Billing server/ops/purchases.js summary(); emitted by purchases, transfers, subscriptions, cashouts recycle and admin adjustments). A journal transaction was posted. Envelope: subject { type: transaction, id: txn_… }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingTransactionSettledPayload {
+ transaction_id: string;
+ type:
+ | "purchase"
+ | "donation"
+ | "subscription"
+ | "subscription_share"
+ | "cashout_request"
+ | "cashout_paid"
+ | "cashout_denied"
+ | "recycle"
+ | "refund"
+ | "chargeback"
+ | "adjustment"
+ | "import";
+ /**
+ * Test money: never counted.
+ */
+ test: boolean;
+ /**
+ * Subject id (usr_…) value moved from, null for provider money.
+ */
+ from_subject: string | null;
+ /**
+ * Subject id (usr_…) value moved to.
+ */
+ to_subject: string | null;
+ /**
+ * stripe, paypal, powerchat…; null for credit movements.
+ */
+ provider: string | null;
+ /**
+ * Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type.
+ */
+ metadata: {};
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingTransactionReversedPayload {
+ transaction_id: string;
+ type:
+ | "purchase"
+ | "donation"
+ | "subscription"
+ | "subscription_share"
+ | "cashout_request"
+ | "cashout_paid"
+ | "cashout_denied"
+ | "recycle"
+ | "refund"
+ | "chargeback"
+ | "adjustment"
+ | "import";
+ /**
+ * Test money: never counted.
+ */
+ test: boolean;
+ /**
+ * Subject id (usr_…) value moved from, null for provider money.
+ */
+ from_subject: string | null;
+ /**
+ * Subject id (usr_…) value moved to.
+ */
+ to_subject: string | null;
+ /**
+ * stripe, paypal, powerchat…; null for credit movements.
+ */
+ provider: string | null;
+ /**
+ * Transaction metadata as posted (amount_bits, paid_cents, value_cents, target EntityRef, intent_id, message, donor_name, rates…). Its keys depend on the transaction type.
+ */
+ metadata: {};
+ /**
+ * The transaction this one reverses.
+ */
+ reverses_txn: string;
+ /**
+ * Provider reversals only: required when Billing kept value it could not recover (staff review).
+ */
+ review?: "required" | null;
+ /**
+ * Provider reversals only: subscription periods revoked.
+ */
+ entitlements_revoked?: number;
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.entitlement.changed v1 (OpenVibe.Billing server/ops/subscriptions.js pay, setStatus, revokeForTxn; entitlement()). A subscriber's channel-subscription entitlement to a streamer changed: a period was granted or renewed, the subscription ended or expired, or a reversed payment revoked a period. The payload is the entitlement as of now. Envelope: subject { type: entitlement, id: <subscriber usr_…>:channel_subscription:<streamer usr_…> }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingEntitlementChangedPayload {
+ subject: SubjectRef;
+ streamer: SubjectRef;
+ kind: "channel_subscription";
+ active: boolean;
+ expires_at: string | null;
+ subscription: {
+ id: string;
+ status: "active" | "canceled" | "expired";
+ auto_renew: boolean;
+ cancel_at_period_end: boolean;
+ provider: string;
+ } | null;
+ /**
+ * granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback.
+ */
+ reason: string;
+ /**
+ * granted/renewed: the transaction that paid the period.
+ */
+ transaction_id?: string;
+ /**
+ * refund/chargeback: the period the reversed payment had granted.
+ */
+ revoked_period?: {
+ starts_at: string;
+ ends_at: string;
+ };
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.subscription.canceled v1 (OpenVibe.Billing server/ops/subscriptions.js cancel). A subscription was set to cancel at period end (for Stripe, after Stripe accepted it); the paid period still runs. Envelope: subject { type: subscription, id: sub_… }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingSubscriptionCanceledPayload {
+ subscription: {
+ id: string;
+ subscriber: SubjectRef;
+ streamer: SubjectRef;
+ tier: number;
+ provider: string;
+ provider_ref: string | null;
+ route: string | null;
+ status: "active" | "canceled" | "expired";
+ auto_renew: boolean;
+ cancel_at_period_end: boolean;
+ price_cents: number;
+ current_period_end: string | null;
+ created_at: string;
+ updated_at: string;
+ };
+ provider_sync: "not_needed" | "stripe_cancel_at_period_end";
+ /**
+ * Who asked: the calling principal and the person it acted for.
+ */
+ actor: {
+ /**
+ * Token sub of the caller (svc:…, usr_…).
+ */
+ principal: string | null;
+ request_id: string | null;
+ on_behalf_of?: SubjectRef;
+ };
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.cashout.requested v1 (OpenVibe.Billing server/ops/cashouts.js request). A creator asked to cash out payable bits; they are held in escrow. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingCashoutRequestedPayload {
+ cashout: {
+ id: string;
+ subject: SubjectRef;
+ amount_bits: number;
+ value_cents: number;
+ status: "requested";
+ /**
+ * { type, address } as the creator entered it (e.g. a PayPal email).
+ */
+ payout_method: {};
+ escrow_until: string;
+ request_txn: string;
+ settle_txn: string | null;
+ payout_provider: string | null;
+ payout_reference: string | null;
+ reason: string | null;
+ created_at: string;
+ updated_at: string;
+ };
+ /**
+ * The cashout_request transaction.
+ */
+ transaction_id: string;
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.cashout.paid v1 (OpenVibe.Billing server/ops/cashouts.js approve). Staff paid a cash-out out through a provider. Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingCashoutPaidPayload {
+ cashout: {
+ id: string;
+ subject: SubjectRef;
+ amount_bits: number;
+ value_cents: number;
+ status: "paid";
+ /**
+ * { type, address } as the creator entered it (e.g. a PayPal email).
+ */
+ payout_method: {};
+ escrow_until: string;
+ request_txn: string;
+ settle_txn: string | null;
+ payout_provider: string | null;
+ payout_reference: string | null;
+ reason: string | null;
+ created_at: string;
+ updated_at: string;
+ };
+ /**
+ * The cashout_paid transaction.
+ */
+ transaction_id: string;
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.cashout.denied v1 (OpenVibe.Billing server/ops/cashouts.js deny). Staff denied a cash-out; the escrowed bits went back (a billing.transaction.reversed follows in the same transaction). Envelope: subject { type: cashout, id: co_… }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingCashoutDeniedPayload {
+ cashout: {
+ id: string;
+ subject: SubjectRef;
+ amount_bits: number;
+ value_cents: number;
+ status: "denied";
+ /**
+ * { type, address } as the creator entered it (e.g. a PayPal email).
+ */
+ payout_method: {};
+ escrow_until: string;
+ request_txn: string;
+ settle_txn: string | null;
+ payout_provider: string | null;
+ payout_reference: string | null;
+ reason: string | null;
+ created_at: string;
+ updated_at: string;
+ };
+ /**
+ * The cashout_denied transaction.
+ */
+ transaction_id: string;
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.staff.action v1 (OpenVibe.Billing server/console/audit.js record). A staff member completed an action in the Billing console (only outcome done is announced; refused actions stay in staff_audit). Holds no secrets: codes, payout reference, counts. Envelope: subject { type: staff_action, id: sa_… }, visibility internal, priority important, actor the staff user ({ type: user, id: usr_… }).
+ */
+export interface BillingStaffActionPayload {
+ audit_id: string;
+ action: string;
+ outcome: "done";
+ target: {
+ type: string;
+ id: string;
+ } | null;
+ reason: string | null;
+ request_id: string | null;
+ /**
+ * Small whitelisted detail of the action.
+ */
+ detail: {};
+}
+
+/** [email protected] (owner: billing) */
+/**
+ * billing.receipt.external v1 (OpenVibe.Billing server/ops/external.js record). An EXTERNAL receipt (ADR-012): a tip paid on a creator's own provider account (PowerChat route direct). The money never touches OpenVibe (no journal entry); Billing announces it once per payment, only when BILLING_AUTHORITY=billing and the receiving account is mapped to a creator. OpenVibe.Tips celebrates it (chat line, overlay alert, goal). Envelope: subject { type: provider_receipt, id: <provider>:<payment id> }, visibility internal, priority important, actor service:billing.
+ */
+export interface BillingReceiptExternalPayload {
+ classification: "EXTERNAL";
+ provider: string;
+ /**
+ * <provider>:<payment id>, e.g. powerchat:…
+ */
+ receipt_ref: string;
+ /**
+ * The provider's id of the payment, stable across redeliveries.
+ */
+ provider_event_id: string;
+ /**
+ * The webhook delivery that carried it.
+ */
+ delivery_id: string;
+ streamer: SubjectRef;
+ receiving_account: {
+ provider: string;
+ id: string | null;
+ username: string | null;
+ };
+ amount_cents: number;
+ currency: "usd-cents";
+ /**
+ * Billing's value of the money in bits.
+ */
+ value_bits: number;
+ /**
+ * null when anonymous.
+ */
+ donor_name: string | null;
+ anonymous: boolean;
+ message: string | null;
+ app_ref: string | null;
+ app_purpose: string | null;
+ /**
+ * The provider's timestamp as it sent it.
+ */
+ occurred_at: string | null;
+ test: boolean;
+ /**
+ * Rate snapshot.
+ */
+ rates: {
+ bits_per_usd: number;
+ };
+}
+
+/** [email protected] (owner: deals) */
+/**
+ * deals.watch.matched v1 (OpenVibe.Deals server/domain/watches.js onObservation). A person's keyword, product or price-below watch matched a fresh observation of an active offer, inside the transaction that recorded the observation. At most one event per (watch, observation); a keyword/product watch notifies once per offer, a price-below watch again only for a lower price. Saved searches (kind search) never notify. Envelope: subject { type: watch, id: <watch_id> }, visibility internal, priority important, actor service:deals. OpenVibe.Network turns it into a DEAL_WATCH_MATCH notification for payload.recipient (server/notifications/events-consumer.js). A price is the decimal exactly as stated, never inferred; null when the observation stated none.
+ */
+export interface DealsWatchMatchedPayload {
+ watch_id: string;
+ /**
+ * The watch owner's user subject id: who is notified.
+ */
+ recipient: string;
+ kind: "keyword" | "product" | "price_below";
+ /**
+ * Keywords of a keyword watch, or of a price-below watch without a product.
+ */
+ query: string | null;
+ /**
+ * Deals product id of a product or price-below watch.
+ */
+ product_id: string | null;
+ /**
+ * Price-below threshold as entered (decimal text, e.g. 19.99).
+ */
+ max_price: string | null;
+ /**
+ * ISO 4217 currency of max_price.
+ */
+ currency: string | null;
+ /**
+ * The canonical (root) offer id (dof_…).
+ */
+ offer_id: string;
+ /**
+ * Canonical offer page URL on Deals.
+ */
+ offer_url: string;
+ title: string;
+ observation: {
+ id: string;
+ observed_at: string;
+ /**
+ * Decimal text as stated; null when the observation stated no price.
+ */
+ price: string | null;
+ currency: string | null;
+ availability:
+ | "in_stock"
+ | "out_of_stock"
+ | "preorder"
+ | "discontinued"
+ | "limited"
+ | "sold_out"
+ | "online_only"
+ | "in_store_only"
+ | null;
+ };
+}
+
+/** [email protected] (owner: trade) */
+/**
+ * trade.alert.triggered v1 (OpenVibe.Trade server/domain/alerts.js deliver). One delivery of one private alert rule: a threshold crossed by a new observation (trigger kind observation), or a newly seen document of the instrument (filing_type / new_document rules, trigger kind document). Written in the transaction that recorded the observation or document; at most one per (rule, trigger). Envelope: subject { type: user, id: <rule owner usr_…> }, visibility subject, priority important, actor service:trade. Data only, never rendered notification text (ADR-020): OpenVibe.Network renders a TRADE_ALERT notification (server/notifications/events-consumer.js). Information only, not investment advice.
+ */
+export interface TradeAlertTriggeredPayload {
+ delivery_id: string;
+ rule: {
+ id: string;
+ kind: "threshold" | "filing_type" | "new_document";
+ status: "active" | "deleted";
+ instrument: {
+ id: string;
+ symbol?: string;
+ name?: string;
+ };
+ /**
+ * Threshold rules: e.g. price.close, volume, us-gaap:Revenues.
+ */
+ metric: string | null;
+ operator: "above" | "below" | null;
+ /**
+ * Decimal text as entered.
+ */
+ threshold: string | null;
+ unit: string | null;
+ currency: string | null;
+ /**
+ * filing_type rules: the 1–10 form types that trigger it (10-K, 8-K…).
+ */
+ form_types: string[] | null;
+ /**
+ * Threshold rules: armed before this delivery (always true when it fired); null for document rules.
+ */
+ armed: boolean | null;
+ created_at: string;
+ updated_at: string;
+ };
+ instrument: {
+ id: string;
+ symbol: string;
+ name: string;
+ /**
+ * Instrument page on Trade.
+ */
+ url: string;
+ };
+ trigger:
+ | {
+ kind: "observation";
+ id: string;
+ metric: string;
+ /**
+ * The decimal exactly as the source stated it.
+ */
+ value: string;
+ unit: string;
+ currency: string | null;
+ observed_at: string;
+ retrieved_at: string;
+ /**
+ * OpenVibe.Sources source key.
+ */
+ source_key: string;
+ source_url: string | null;
+ }
+ | {
+ kind: "document";
+ id: string;
+ form_type: string | null;
+ title: string | null;
+ url: string | null;
+ /**
+ * The source's own date; null if it gave none.
+ */
+ published_at: string | null;
+ retrieved_at: string;
+ source_key: string;
+ };
+ /**
+ * Currently "Information only — not investment advice; no trading here."
+ */
+ disclaimer: string;
+}
+
+/** [email protected] (owner: tools) */
+/**
+ * tools.job.created v1 (OpenVibe.Tools apps/_shared/jobs, planned). A job was accepted and queued (POST /api/v1/jobs, or a retry of a failed job, which carries retry_of). An Idempotency-Key replay creates no job and no event. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor the owner when it is a subject (user, service, app or mod), else service:tools.
+ */
+export interface ToolsJobCreatedPayload {
+ job_id: string;
+ /**
+ * The Tools satellite that runs the job (img, audio, docs…).
+ */
+ service: string;
+ /**
+ * Job type, e.g. img.process.
+ */
+ type: string;
+ type_version: number;
+ state: "queued";
+ /**
+ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.
+ */
+ owner: SubjectRef | null;
+ attempts: number;
+ max_attempts: number;
+ created_at: string;
+ /**
+ * The failed job this one retries (POST /api/v1/jobs/:id/retry).
+ */
+ retry_of: string | null;
+}
+
+/** [email protected] (owner: tools) */
+/**
+ * tools.job.started v1 (OpenVibe.Tools apps/_shared/jobs, planned). A worker claimed the job and it is running (attempts counts this attempt). A job requeued after a restart starts again and is announced again. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:tools.
+ */
+export interface ToolsJobStartedPayload {
+ job_id: string;
+ /**
+ * The Tools satellite that runs the job (img, audio, docs…).
+ */
+ service: string;
+ /**
+ * Job type, e.g. img.process.
+ */
+ type: string;
+ type_version: number;
+ state: "running";
+ /**
+ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.
+ */
+ owner: SubjectRef | null;
+ attempts: number;
+ max_attempts: number;
+ created_at: string;
+ /**
+ * The failed job this one retries (POST /api/v1/jobs/:id/retry).
+ */
+ retry_of: string | null;
+ started_at: string;
+}
+
+/** [email protected] (owner: tools) */
+/**
+ * tools.job.succeeded v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job finished and its result files are stored (in Media when TOOLS_JOB_RESULTS=media). Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.
+ */
+export interface ToolsJobSucceededPayload {
+ job_id: string;
+ /**
+ * The Tools satellite that runs the job (img, audio, docs…).
+ */
+ service: string;
+ /**
+ * Job type, e.g. img.process.
+ */
+ type: string;
+ type_version: number;
+ state: "succeeded";
+ /**
+ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.
+ */
+ owner: SubjectRef | null;
+ attempts: number;
+ max_attempts: number;
+ created_at: string;
+ /**
+ * The failed job this one retries (POST /api/v1/jobs/:id/retry).
+ */
+ retry_of: string | null;
+ started_at: string;
+ finished_at: string;
+ /**
+ * When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref).
+ */
+ expires_at: string | null;
+ /**
+ * Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).
+ */
+ result: {
+ /**
+ * @maxItems 100
+ */
+ files: {
+ /**
+ * n in /api/v1/jobs/:id/files/:n.
+ */
+ index: number;
+ mime: string;
+ size: number;
+ sha256: string;
+ /**
+ * media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires.
+ */
+ storage: "local" | "media";
+ /**
+ * { media_id, role: output } when storage is media.
+ */
+ media: MediaRef | null;
+ }[];
+ };
+}
+
+/** [email protected] (owner: tools) */
+/**
+ * tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs, planned). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.
+ */
+export interface ToolsJobFailedPayload {
+ job_id: string;
+ /**
+ * The Tools satellite that runs the job (img, audio, docs…).
+ */
+ service: string;
+ /**
+ * Job type, e.g. img.process.
+ */
+ type: string;
+ type_version: number;
+ state: "failed";
+ /**
+ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.
+ */
+ owner: SubjectRef | null;
+ attempts: number;
+ max_attempts: number;
+ created_at: string;
+ /**
+ * The failed job this one retries (POST /api/v1/jobs/:id/retry).
+ */
+ retry_of: string | null;
+ /**
+ * null when the job failed before it ever ran.
+ */
+ started_at: string | null;
+ finished_at: string;
+ /**
+ * When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref).
+ */
+ expires_at: string | null;
+ /**
+ * The job's problem (errors.problem@1) without request or trace ids.
+ */
+ error: {
+ status: number;
+ /**
+ * tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…).
+ */
+ code: string;
+ /**
+ * Human-readable reason; server paths are scrubbed.
+ */
+ detail: string;
+ };
+ retryable: boolean;
+}
@@ -5,6 +5,8 @@
@@ -5,6 +5,8 @@
"status": "active",
"status": "active",
"visibility": "first-party",
"visibility": "first-party",
"description": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
"description": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
+ "inputSchema": "ai.run-request@1",
+ "outputSchema": "ai.run@1",
"permissions": [
"permissions": [
"ai:run"
"ai:run"
],
],
@@ -13,7 +15,12 @@
@@ -13,7 +15,12 @@
"owner"
"owner"
],
],
"quotaClass": "ai-run",
"quotaClass": "ai-run",
- "events": [],
+ "events": [
+ "ai.run.queued",
+ "ai.run.succeeded",
+ "ai.run.failed",
+ "ai.run.cached"
+ ],
"implementedBy": [
"implementedBy": [
"POST /api/v1/runs",
"POST /api/v1/runs",
"POST /api/v1/runs/:id/cancel",
"POST /api/v1/runs/:id/cancel",
@@ -5,6 +5,7 @@
@@ -5,6 +5,7 @@
"status": "active",
"status": "active",
"visibility": "first-party",
"visibility": "first-party",
"description": "Read the caller's own runs, their citations and request-log metadata (hashes, tokens, latency; never raw prompts).",
"description": "Read the caller's own runs, their citations and request-log metadata (hashes, tokens, latency; never raw prompts).",
+ "outputSchema": "ai.run@1",
"permissions": [
"permissions": [
"ai:read"
"ai:read"
],
],
@@ -14,7 +14,8 @@
@@ -14,7 +14,8 @@
"quotaClass": "internal-write",
"quotaClass": "internal-write",
"events": [
"events": [
"chat.message.created",
"chat.message.created",
- "chat.moderation.action"
+ "chat.moderation.action",
+ "chat.message.deleted"
],
],
"implementedBy": [
"implementedBy": [
"POST /internal/live/calls"
"POST /internal/live/calls"
@@ -12,7 +12,12 @@
@@ -12,7 +12,12 @@
"owner"
"owner"
],
],
"quotaClass": "tools-job",
"quotaClass": "tools-job",
- "events": [],
+ "events": [
+ "tools.job.created",
+ "tools.job.started",
+ "tools.job.succeeded",
+ "tools.job.failed"
+ ],
"implementedBy": [
"implementedBy": [
"POST /api/v1/jobs (Tools satellites: img, audio, docs)"
"POST /api/v1/jobs (Tools satellites: img, audio, docs)"
]
]
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.1.0",
+ "version": "0.2.0",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.6.0 <1.0.0"
"openvibe-contracts": ">=0.6.0 <1.0.0"
},
},
@@ -20,8 +20,13 @@
@@ -20,8 +20,13 @@
"ai.provider.manage",
"ai.provider.manage",
"ai.usage.read"
"ai.usage.read"
],
],
- "eventsProduced": [],
+ "eventsProduced": [
+ "ai.run.queued",
+ "ai.run.succeeded",
+ "ai.run.failed",
+ "ai.run.cached"
+ ],
"eventsConsumed": [],
"eventsConsumed": [],
"namespacesOwned": [],
"namespacesOwned": [],
- "notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached events follow once the Events client is wired in."
+ "notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached have payload contracts (v0.30.0, status planned): AI publishes them once its Events client is wired in; it publishes none today. ai.run.create takes ai.run-request@1 and returns ai.run@1."
}
}
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.2.0",
+ "version": "0.3.0",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.5.1 <1.0.0"
"openvibe-contracts": ">=0.5.1 <1.0.0"
},
},
@@ -31,7 +31,8 @@
@@ -31,7 +31,8 @@
"billing.cashout.requested",
"billing.cashout.requested",
"billing.cashout.paid",
"billing.cashout.paid",
"billing.cashout.denied",
"billing.cashout.denied",
- "billing.staff.action"
+ "billing.staff.action",
+ "billing.receipt.external"
],
],
"eventsConsumed": [],
"eventsConsumed": [],
"namespacesOwned": [
"namespacesOwned": [
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.2.0",
+ "version": "0.3.0",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.13.0 <1.0.0"
"openvibe-contracts": ">=0.13.0 <1.0.0"
},
},
@@ -14,6 +14,7 @@
@@ -14,6 +14,7 @@
],
],
"eventsProduced": [
"eventsProduced": [
"chat.message.created",
"chat.message.created",
+ "chat.message.deleted",
"chat.dm.created",
"chat.dm.created",
"chat.moderation.action"
"chat.moderation.action"
],
],
@@ -1,16 +1,16 @@
@@ -1,16 +1,16 @@
{
{
- "version": "0.1.0",
+ "version": "0.2.0",
"contractRanges": {
"contractRanges": {
- "openvibe-contracts": "^0.1.0"
+ "openvibe-contracts": ">=0.28.0 <1.0.0"
},
},
"id": "examples",
"id": "examples",
"name": "OpenVibe.Examples",
"name": "OpenVibe.Examples",
- "status": "placeholder",
+ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Examples",
"repository": "OpenVibers/OpenVibe.Examples",
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
"eventsConsumed": [],
"eventsConsumed": [],
"namespacesOwned": [],
"namespacesOwned": [],
- "notes": "Executable public integration examples, tested in CI against the platform. Charter only; no runtime."
+ "notes": "Executable public integration examples (developer apps on openvibe-sdk), each with a smoke test that CI runs against openvibe-sdk/testing's mock platform. A repository with CI, not a runtime and not a published package; nothing to deploy or poll."
}
}
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.1.0",
+ "version": "0.2.0",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.2.0 <1.0.0"
"openvibe-contracts": ">=0.2.0 <1.0.0"
},
},
@@ -36,7 +36,10 @@
@@ -36,7 +36,10 @@
"network.credential.revoked",
"network.credential.revoked",
"network.grant.changed"
"network.grant.changed"
],
],
- "eventsConsumed": [],
+ "eventsConsumed": [
+ "deals.watch.matched",
+ "trade.alert.triggered"
+ ],
"namespacesOwned": [
"namespacesOwned": [
"identity.*",
"identity.*",
"network.*",
"network.*",
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.1.0",
+ "version": "0.1.1",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.19.0 <1.0.0"
"openvibe-contracts": ">=0.19.0 <1.0.0"
},
},
@@ -42,5 +42,5 @@
@@ -42,5 +42,5 @@
"namespacesOwned": [
"namespacesOwned": [
"reviews.*"
"reviews.*"
],
],
- "notes": "Entities with aliases and typed links; resolution of OpenVibe.Sources review items by deterministic rules (source binding, URL, GTIN/SKU/MPN) with editor confirmation for name-only and conflicting matches; merges that rewrite nothing and splits that restore attribution exactly, both audited; typed signals with provenance (source item, item revision, retrieval time, licence note) and trust metadata; aggregates computed only from signals present (method reviews-aggregate@1, inputs and exclusions exposed, absent without qualifying signals, a new revision on every change); editor-written or AI-drafted-and-approved summaries whose points cite signals and never carry a rating; source removal withdraws signals, flags citing summaries and prepares a pending revision; corrections queue; Community discussion referenced, not copied. Server-rendered pages useful without JavaScript; AggregateRating/Product/Review JSON-LD only when signals back it; sitemaps, feeds and Search index events from the openvibe-publishing v0.2.0 gate. Not deployed; openvibe.reviews stays on OpenVibe.Sites until launch."
+ "notes": "Entities with aliases and typed links; resolution of OpenVibe.Sources review items by deterministic rules (source binding, URL, GTIN/SKU/MPN) with editor confirmation for name-only and conflicting matches; merges that rewrite nothing and splits that restore attribution exactly, both audited; typed signals with provenance (source item, item revision, retrieval time, licence note) and trust metadata; aggregates computed only from signals present (method reviews-aggregate@1, inputs and exclusions exposed, absent without qualifying signals, a new revision on every change); editor-written or AI-drafted-and-approved summaries whose points cite signals and never carry a rating; source removal withdraws signals, flags citing summaries and prepares a pending revision; corrections queue; Community discussion referenced, not copied. Server-rendered pages useful without JavaScript; AggregateRating/Product/Review JSON-LD only when signals back it; sitemaps, feeds and Search index events from the openvibe-publishing v0.2.0 gate. Deployed internally (loopback only), not launched publicly: openvibe.reviews serves the OpenVibe.Sites placeholder until the launch rule holds."
}
}
@@ -1,16 +1,13 @@
@@ -1,16 +1,13 @@
{
{
- "version": "0.1.0",
- "contractRanges": {
- "openvibe-contracts": "^0.1.0"
- },
+ "version": "0.2.0",
"id": "sdk",
"id": "sdk",
"name": "OpenVibe.SDK",
"name": "OpenVibe.SDK",
- "status": "placeholder",
+ "status": "alpha",
"repository": "OpenVibers/OpenVibe.SDK",
"repository": "OpenVibers/OpenVibe.SDK",
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
"eventsConsumed": [],
"eventsConsumed": [],
"namespacesOwned": [],
"namespacesOwned": [],
- "notes": "Supported browser and server clients for the OpenVibe platform. Charter only; no runtime."
+ "notes": "Supported browser and server clients for the OpenVibe platform (openvibe-sdk). A released library, not a runtime: tagged releases installed from https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/<tag> (v0.4.0 on 2026-09-23). Nothing to deploy or poll."
}
}
@@ -1,16 +1,13 @@
@@ -1,16 +1,13 @@
{
{
- "version": "0.1.0",
- "contractRanges": {
- "openvibe-contracts": "^0.1.0"
- },
+ "version": "0.2.0",
"id": "shared",
"id": "shared",
"name": "OpenVibe.Shared",
"name": "OpenVibe.Shared",
- "status": "placeholder",
+ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Shared",
"repository": "OpenVibers/OpenVibe.Shared",
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
"eventsConsumed": [],
"eventsConsumed": [],
"namespacesOwned": [],
"namespacesOwned": [],
- "notes": "Versioned UI, chrome, SEO, legal and release-client packages every OpenVibe site renders. Charter only; no runtime."
+ "notes": "Versioned UI, chrome, SEO, legal and release-client packages every OpenVibe site renders (openvibe-shared). A released library, not a runtime: tagged releases installed from https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/<tag> (v1.3.0 on 2026-09-23) and served at /shared/* by each site. Nothing to deploy or poll."
}
}
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.2.0",
+ "version": "0.3.0",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.11.0 <1.0.0"
"openvibe-contracts": ">=0.11.0 <1.0.0"
},
},
@@ -42,10 +42,11 @@
@@ -42,10 +42,11 @@
],
],
"eventsConsumed": [
"eventsConsumed": [
"billing.transaction.settled",
"billing.transaction.settled",
- "billing.transaction.reversed"
+ "billing.transaction.reversed",
+ "billing.receipt.external"
],
],
"namespacesOwned": [
"namespacesOwned": [
"tips.*"
"tips.*"
],
],
- "notes": "Creator support (roadmap Wave 9): tip interactions whose payment state mirrors OpenVibe.Billing (keyed by Billing transaction id) and whose delivery state is Tips' own; goals derived from settled interactions; paid messages, TTS and media requests created on settlement; scoped revocable overlay tokens with SSE overlays; simulation mode. Tips never moves money (ADR-012). Alpha: the placeholder on OpenVibe.Sites stays until the launch rule in README.md holds. Proposed manifest; replaces the placeholder in manifests/services/tips.json."
+ "notes": "Creator support (roadmap Wave 9): tip interactions whose payment state mirrors OpenVibe.Billing (keyed by Billing transaction id) and whose delivery state is Tips' own; goals derived from settled interactions; paid messages, TTS and media requests created on settlement; scoped revocable overlay tokens with SSE overlays; simulation mode. Tips never moves money (ADR-012). Alpha: deployed internally (loopback only), not launched publicly: openvibe.tips serves the OpenVibe.Sites placeholder until the launch rule in README.md holds."
}
}
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.2.0",
+ "version": "0.3.0",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.7.0 <1.0.0"
"openvibe-contracts": ">=0.7.0 <1.0.0"
},
},
@@ -17,10 +17,15 @@
@@ -17,10 +17,15 @@
"tools.job.read",
"tools.job.read",
"tools.job.cancel"
"tools.job.cancel"
],
],
- "eventsProduced": [],
+ "eventsProduced": [
+ "tools.job.created",
+ "tools.job.started",
+ "tools.job.succeeded",
+ "tools.job.failed"
+ ],
"eventsConsumed": [],
"eventsConsumed": [],
"namespacesOwned": [
"namespacesOwned": [
"tools.*"
"tools.*"
],
],
- "notes": "Wave 11: asynchronous jobs on the img, audio and docs satellites (POST/GET/DELETE /api/v1/jobs, SSE events). Job lifecycle is not yet published to OpenVibe.Events, so eventsProduced stays empty. Tools consumes media.object.upload and media.object.read (namespace tools) when TOOLS_JOB_RESULTS=media."
+ "notes": "Wave 11: asynchronous jobs on the img, audio and docs satellites (POST/GET/DELETE /api/v1/jobs, SSE events). Job lifecycle events tools.job.created|started|succeeded|failed have payload contracts (v0.30.0); Tools publishes them to OpenVibe.Events next. Tools consumes media.object.upload and media.object.read (namespace tools) when TOOLS_JOB_RESULTS=media."
}
}
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.2.0",
+ "version": "0.2.1",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.14.0 <1.0.0"
"openvibe-contracts": ">=0.14.0 <1.0.0"
},
},
@@ -41,5 +41,5 @@
@@ -41,5 +41,5 @@
"namespacesOwned": [
"namespacesOwned": [
"vip.*"
"vip.*"
],
],
- "notes": "Memberships (ADR-012): plans, immutable versioned plan terms, perks, product bindings, member preferences and gated-resource policy. OpenVibe.Billing holds subscriptions, charges, renewals, refunds and the canonical entitlements; VIP keeps a short-lived projection of them (valid_until, authoritative fallback, 'unknown' never authorizes). Alpha: not deployed; openvibe.vip stays on its OpenVibe.Sites placeholder until the launch rule holds."
+ "notes": "Memberships (ADR-012): plans, immutable versioned plan terms, perks, product bindings, member preferences and gated-resource policy. OpenVibe.Billing holds subscriptions, charges, renewals, refunds and the canonical entitlements; VIP keeps a short-lived projection of them (valid_until, authoritative fallback, 'unknown' never authorizes). Alpha: deployed internally (loopback only), not launched publicly: openvibe.vip serves the OpenVibe.Sites placeholder until the launch rule holds."
}
}
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
{
{
- "version": "0.1.0",
+ "version": "0.1.1",
"contractRanges": {
"contractRanges": {
"openvibe-contracts": ">=0.13.0 <1.0.0"
"openvibe-contracts": ">=0.13.0 <1.0.0"
},
},
@@ -38,5 +38,5 @@
@@ -38,5 +38,5 @@
"namespacesOwned": [
"namespacesOwned": [
"wiki.*"
"wiki.*"
],
],
- "notes": "Wiki spaces (official and user), page trees with canonical slugs and history-aware redirects, immutable revisions with diff and revert, typed infoboxes, [[internal links]] with red links, citations per revision (Sources items or URLs with retrieval time), Media attachments with an explicit broken state, Community discussion references, watchers notified through wiki.watch.triggered, AI proposals held for a person's approval. Server-rendered public routes useful without JavaScript; sitemaps, Atom/JSON feeds and Search index events only for public, published, indexable pages (openvibe-publishing v0.2.0). Not deployed; openvibe.wiki stays on OpenVibe.Sites until launch."
+ "notes": "Wiki spaces (official and user), page trees with canonical slugs and history-aware redirects, immutable revisions with diff and revert, typed infoboxes, [[internal links]] with red links, citations per revision (Sources items or URLs with retrieval time), Media attachments with an explicit broken state, Community discussion references, watchers notified through wiki.watch.triggered, AI proposals held for a person's approval. Server-rendered public routes useful without JavaScript; sitemaps, Atom/JSON feeds and Search index events only for public, published, indexable pages (openvibe-publishing v0.2.0). Deployed and public at https://openvibe.wiki."
}
}
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.1.0",
+ "version": "0.30.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.1.0",
+ "version": "0.30.0",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.29.0",
+ "version": "0.30.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
@@ -74,6 +74,51 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
@@ -74,6 +74,51 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
ok(!contracts.validate('events.event-envelope@1', { ...env, source: 'app:app_01JAB2C3D4E5F6G7H8J9K0MNPS' }).valid, 'the raw app subject is not a valid source');
ok(!contracts.validate('events.event-envelope@1', { ...env, source: 'app:app_01JAB2C3D4E5F6G7H8J9K0MNPS' }).valid, 'the raw app subject is not a valid source');
}
}
+// ── Event payload contracts (v0.30, ADR-026) ─────────────────────────────
+// A payload contract is named after its event type; its major is the envelope `version`; its owner
+// is the producer, which lists the type in eventsProduced. Every valid fixture rides in a valid
+// envelope, and a tombstone never passes for a live payload.
+{
+ const payloads = contracts.catalog.filter(c => c.schema.startsWith('events/payloads/'));
+ ok(payloads.length >= 40, `event payload contracts present (${payloads.length})`);
+ const tombstone = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/events.tombstone-payload/valid/chat.json'), 'utf8'));
+ for (const c of payloads) {
+ const major = Number(c.version.split('.')[0]);
+ ok(c.schema === `events/payloads/${c.id}.v${major}.json`, `${c.id} lives at events/payloads/<event_type>.v<major>.json`);
+ ok(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$/.test(c.id), `${c.id} is an event type`);
+ ok(services.get(c.owner) && services.get(c.owner).eventsProduced.includes(c.id), `${c.owner} manifest lists ${c.id} in eventsProduced`);
+ ok(['active', 'planned'].includes(c.status), `${c.id} status ${c.status}`);
+ const s = contracts.schema(c.id);
+ ok((s.type === 'object' || s.$ref || s.allOf) && s.title.endsWith('Payload'), `${c.id} is an object payload schema`);
+ if (s.properties && s.properties.redacts) ok(s.properties.redacts.$ref === '../redaction-directive.v1.json', `${c.id} redacts uses events.redaction-directive@1`);
+ ok(!contracts.validate(c.id, tombstone).valid, `${c.id}: a tombstone is not a valid payload (check payload.redacted first)`);
+ const dir = path.join(ROOT, 'fixtures', c.id, 'valid');
+ for (const f of fs.readdirSync(dir).filter(n => n.endsWith('.json'))) {
+ const payload = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));
+ const env = { event_id: ids.newId('event'), event_type: c.id, version: major, source: c.owner, actor: { type: 'service', id: c.owner }, timestamp: new Date().toISOString(), subject: { type: 'x', id: '1' }, payload };
+ ok(contracts.validate('events.event-envelope@1', env).valid, `${c.id} valid/${f} fits in an envelope`);
+ ok(contracts.validate(`${env.event_type}@${env.version}`, env.payload).valid, `${c.id} resolves as <event_type>@<version>`);
+ }
+ }
+ // Every event type a manifest produces and consumes is well formed; consumed types have a producer.
+ const produced = new Map();
+ for (const m of services.manifests) for (const t of m.eventsProduced) { ok(!produced.has(t), `${t} has one producer`); produced.set(t, m.id); }
+ const matches = (pattern, t) => new RegExp(`^${pattern.split('.').map(s => (s === '*' ? '[a-z0-9_]+' : s)).join('\\.')}$`).test(t);
+ for (const m of services.manifests) for (const t of m.eventsConsumed) ok([...produced.keys()].some(p => matches(t, p)), `${m.id} consumes ${t}, which some manifest produces`);
+
+ // payload.redacts: what Chat sends, and what Events refuses (403/422 are Events' answers).
+ const del = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/chat.message.deleted/valid/two-messages.json'), 'utf8'));
+ ok(contracts.validate('events.redaction-directive@1', del.redacts).valid, 'chat.message.deleted carries a valid directive');
+ ok(JSON.stringify(del.redacts.subject_ids) === JSON.stringify(del.message_ids.map(String)), 'Chat redacts exactly the deleted ids');
+ ok(!contracts.validate('chat.message.deleted@1', { message_ids: del.message_ids }).valid, 'chat.message.deleted without redacts fails');
+ const D = (v) => contracts.validate('events.redaction-directive@1', v).valid;
+ ok(D({ event_ids: ['evt_01JAB2C3D4E5F6G7H8J9K0MNPQ'], subject_type: 'post', subject_ids: ['p1'] }), 'both forms together pass');
+ ok(!D({ subject_ids: ['1'] }) && !D({ subject_type: 'post' }), 'subject_type and subject_ids go together');
+ ok(!D({ event_ids: Array.from({ length: 1001 }, () => ids.newId('event')) }), 'more than 1000 ids fail');
+ ok(!D({ event_ids: ['evt_1'] }) && !D({ subject_type: 'Chat Message', subject_ids: ['1'] }), 'bad ids and subject types fail');
+ ok(!contracts.validate('events.tombstone-payload@1', { redacted: false, redacted_at: tombstone.redacted_at, redacted_by: 'x' }).valid, 'a tombstone is redacted: true');
+}
+
// ── Ids ──────────────────────────────────────────────────────────────────
// ── Ids ──────────────────────────────────────────────────────────────────
for (const kind of ['user', 'guest', 'app', 'mod']) {
for (const kind of ['user', 'guest', 'app', 'mod']) {
const id = ids.newId(kind);
const id = ids.newId(kind);
@@ -229,6 +274,13 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
@@ -229,6 +274,13 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
// ── Generated output and compatibility gate ──────────────────────────────
// ── Generated output and compatibility gate ──────────────────────────────
execFileSync(process.execPath, [path.join(ROOT, 'scripts/generate.js'), '--check'], { stdio: 'inherit' });
execFileSync(process.execPath, [path.join(ROOT, 'scripts/generate.js'), '--check'], { stdio: 'inherit' });
execFileSync(process.execPath, [path.join(ROOT, 'scripts/compat.js')], { stdio: 'inherit' });
execFileSync(process.execPath, [path.join(ROOT, 'scripts/compat.js')], { stdio: 'inherit' });
+{
+ // A $ref with sibling keywords makes json-schema-to-typescript emit SubjectRef1 & co. without declaring them.
+ const dts = fs.readFileSync(path.join(ROOT, 'generated/typescript/index.d.ts'), 'utf8');
+ const declared = new Set([...dts.matchAll(/^export (?:interface|type) (\w+)/gm)].map(m => m[1]));
+ const dangling = [...new Set([...dts.replace(/\/\*[\s\S]*?\*\//g, '').matchAll(/\b([A-Z][A-Za-z]*\d+)\b/g)].map(m => m[1]))].filter(n => !declared.has(n));
+ ok(dangling.length === 0, `generated types reference undeclared names: ${dangling.join(', ')}`);
+}
console.log(`openvibe-contracts: ${n} checks passed`);
console.log(`openvibe-contracts: ${n} checks passed`);
})().catch((err) => { console.error(err); process.exit(1); });
})().catch((err) => { console.error(err); process.exit(1); });