openvibers/openvibe.contracts Commits

openvibe-contracts 0.128.0: Search events for Work, Rent, Help, Inventory and Quest (#79)

OP OpenVibers committed 10 Oct 2026 · 5531a3c6d4 · 90 files, +1725 −66

Plan T9 ("Search: real documents from every product") and the T19 compositions (Rent, Work and Help compose Search). The record products get Search events:

- `<owner>.index_document.upserted` and `.deleted` v1 for **Work** (`job`, `/jobs/<id>`), **Rent** (`listing`, `/listings/<id>`), **Help** (`article`, `/a/<domain>/<slug>`, id `<domain>:<slug>`), **Inventory** (`definition`, `/definitions/<id>`) and **Quest** (`quest`, `/quests/<id>`). The payloads are `search.index-document@1` with owner, type and id pattern fixed, plus the usual tombstone.
- Each service manifest's `eventsProduced` lists them, and the catalog has their entries and fixtures (valid document, noindex document, tombstone; refused: other owner, bad id, tombstone sent as an upsert, a full document as a tombstone, another type).
- Each product sends them through `openvibe-publishing/search-feed` (Publishing#19, 1.4.0).

0.128.0. `compat.js v0.127.0`: no breaking changes. Suite: 18347 checks passed; `generated/` up to date.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 0fca767d..ac000f47 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
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.128.0 — 2026-10-10
++
++- **Record products send their pages to OpenVibe.Search.** `<owner>.index_document.upserted` and `.deleted` (v1) are
++ declared for Work (`job`, `/jobs/<id>`), Rent (`listing`, `/listings/<id>`), Help (`article`,
++ `/a/<domain>/<slug>`, id `<domain>:<slug>`), Inventory (`definition`, `/definitions/<id>`) and Quest (`quest`,
++ `/quests/<id>`), and each service manifest now produces them. The payloads are `search.index-document@1` with the
++ owner, type and id pattern fixed, plus the usual tombstone. Each product sends them through
++ `openvibe-publishing/search-feed` (1.4.0): on the write's transaction, and from a sweep that re-sends only what
++ changed. Search consumes them as `*.index_document.*` once the owners are in `SEARCH_EVENT_OWNERS`.
++
## 0.127.0 — 2026-10-10
- **Retired addresses leave the registry.** `ai.openvibe.network` and `events.openvibe.network` are no longer domains
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 3d95e7b2..1dea9681 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -6576,6 +6576,96 @@
"compatibility": "backward",
"status": "active",
"adr": "ADR-054"
++ },
++ {
++ "id": "work.index_document.upserted",
++ "version": "1.0.0",
++ "owner": "work",
++ "schema": "events/payloads/work.index_document.upserted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "work.index_document.deleted",
++ "version": "1.0.0",
++ "owner": "work",
++ "schema": "events/payloads/work.index_document.deleted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "rent.index_document.upserted",
++ "version": "1.0.0",
++ "owner": "rent",
++ "schema": "events/payloads/rent.index_document.upserted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "rent.index_document.deleted",
++ "version": "1.0.0",
++ "owner": "rent",
++ "schema": "events/payloads/rent.index_document.deleted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "help.index_document.upserted",
++ "version": "1.0.0",
++ "owner": "help",
++ "schema": "events/payloads/help.index_document.upserted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "help.index_document.deleted",
++ "version": "1.0.0",
++ "owner": "help",
++ "schema": "events/payloads/help.index_document.deleted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "inventory.index_document.upserted",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.index_document.upserted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "inventory.index_document.deleted",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.index_document.deleted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "quest.index_document.upserted",
++ "version": "1.0.0",
++ "owner": "quest",
++ "schema": "events/payloads/quest.index_document.upserted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "quest.index_document.deleted",
++ "version": "1.0.0",
++ "owner": "quest",
++ "schema": "events/payloads/quest.index_document.deleted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
}
]
}
ddiff --git a/contracts/events/payloads/help.index_document.deleted.v1.json b/contracts/events/payloads/help.index_document.deleted.v1.json
nnew file mode 100644
iindex 00000000..10befeb3
---- /dev/null
++++ b/contracts/events/payloads/help.index_document.deleted.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/help.index_document.deleted.v1.json",
++ "title": "HelpIndexDocumentDeletedPayload",
++ "description": "help.index_document.deleted v1 (OpenVibe.Help server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a article page (/a/<domain>/<slug>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: article, id, revision }, visibility internal, actor service:help.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "article"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9.-]{0,79}:[a-z0-9][a-z0-9-]{0,71}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/help.index_document.upserted.v1.json b/contracts/events/payloads/help.index_document.upserted.v1.json
nnew file mode 100644
iindex 00000000..3f8892ee
---- /dev/null
++++ b/contracts/events/payloads/help.index_document.upserted.v1.json
@@@ -0,0 +1,34 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/help.index_document.upserted.v1.json",
++ "title": "HelpIndexDocumentUpsertedPayload",
++ "description": "help.index_document.upserted v1 (OpenVibe.Help server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a help article page (type article, /a/<domain>/<slug>, id <domain>:<slug>): one question from a product manifest's FAQ with its answer, quoted verbatim from OpenVibe.Contracts. Sent by the sweep at boot and on a timer (server/search-index.js) whenever the catalog Help was built from changed; an article whose FAQ entry left the catalog becomes a tombstone. The sequencer (help_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: site (the product domain), kind. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: article, id, revision: <document revision> }, visibility internal, actor service:help.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "help"
++ },
++ "type": {
++ "const": "article"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9.-]{0,79}:[a-z0-9][a-z0-9-]{0,71}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/payloads/inventory.index_document.deleted.v1.json b/contracts/events/payloads/inventory.index_document.deleted.v1.json
nnew file mode 100644
iindex 00000000..95bbfa89
---- /dev/null
++++ b/contracts/events/payloads/inventory.index_document.deleted.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.index_document.deleted.v1.json",
++ "title": "InventoryIndexDocumentDeletedPayload",
++ "description": "inventory.index_document.deleted v1 (OpenVibe.Inventory server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a definition page (/definitions/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: definition, id, revision }, visibility internal, actor service:inventory.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "definition"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^itd_[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."
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/inventory.index_document.upserted.v1.json b/contracts/events/payloads/inventory.index_document.upserted.v1.json
nnew file mode 100644
iindex 00000000..d2305815
---- /dev/null
++++ b/contracts/events/payloads/inventory.index_document.upserted.v1.json
@@@ -0,0 +1,34 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.index_document.upserted.v1.json",
++ "title": "InventoryIndexDocumentUpsertedPayload",
++ "description": "inventory.index_document.upserted v1 (OpenVibe.Inventory server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of an item definition page (type definition, /definitions/<id>): name, description, kind, rarity, issuer and supply. Only published definitions are documents; a draft, one in review or a retired one is a tombstone. Sent in the transaction that published or retired it, and by the sweep (server/search-index.js). The sequencer (inventory_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: kind, rarity, issuer. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: definition, id, revision: <document revision> }, visibility internal, actor service:inventory.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "inventory"
++ },
++ "type": {
++ "const": "definition"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/payloads/quest.index_document.deleted.v1.json b/contracts/events/payloads/quest.index_document.deleted.v1.json
nnew file mode 100644
iindex 00000000..9f467a6e
---- /dev/null
++++ b/contracts/events/payloads/quest.index_document.deleted.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/quest.index_document.deleted.v1.json",
++ "title": "QuestIndexDocumentDeletedPayload",
++ "description": "quest.index_document.deleted v1 (OpenVibe.Quest server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a quest page (/quests/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: quest, id, revision }, visibility internal, actor service:quest.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "quest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/quest.index_document.upserted.v1.json b/contracts/events/payloads/quest.index_document.upserted.v1.json
nnew file mode 100644
iindex 00000000..8ee0c877
---- /dev/null
++++ b/contracts/events/payloads/quest.index_document.upserted.v1.json
@@@ -0,0 +1,34 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/quest.index_document.upserted.v1.json",
++ "title": "QuestIndexDocumentUpsertedPayload",
++ "description": "quest.index_document.upserted v1 (OpenVibe.Quest server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a quest page (type quest, /quests/<id>): its name, what to do and where, and the badge (and item) it rewards. Quests are Quest's own catalog (server/quests/catalog.js), so they are sent by the sweep at boot (server/search-index.js); a quest that left the catalog becomes a tombstone. The sequencer (quest_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: service (where the quest is done), badge. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: quest, id, revision: <document revision> }, visibility internal, actor service:quest.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "quest"
++ },
++ "type": {
++ "const": "quest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/payloads/rent.index_document.deleted.v1.json b/contracts/events/payloads/rent.index_document.deleted.v1.json
nnew file mode 100644
iindex 00000000..e5327358
---- /dev/null
++++ b/contracts/events/payloads/rent.index_document.deleted.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/rent.index_document.deleted.v1.json",
++ "title": "RentIndexDocumentDeletedPayload",
++ "description": "rent.index_document.deleted v1 (OpenVibe.Rent server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a listing page (/listings/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: listing, id, revision }, visibility internal, actor service:rent.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "listing"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^rnt_[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."
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/rent.index_document.upserted.v1.json b/contracts/events/payloads/rent.index_document.upserted.v1.json
nnew file mode 100644
iindex 00000000..a84c2016
---- /dev/null
++++ b/contracts/events/payloads/rent.index_document.upserted.v1.json
@@@ -0,0 +1,34 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/rent.index_document.upserted.v1.json",
++ "title": "RentIndexDocumentUpsertedPayload",
++ "description": "rent.index_document.upserted v1 (OpenVibe.Rent server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a rental listing page (type listing, /listings/<id>): title, description, price and period, city, region, country and neighbourhood (never a street address or the contact link). Sent in the transaction that created, edited, renewed, hid or removed the listing, and by the sweep (server/search-index.js); a listing that is hidden, removed or past its expiry becomes a tombstone. The sequencer (rent_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: kind, country, city, currency, period, bedrooms. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: listing, id, revision: <document revision> }, visibility internal, actor service:rent.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "rent"
++ },
++ "type": {
++ "const": "listing"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^rnt_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/events/payloads/work.index_document.deleted.v1.json b/contracts/events/payloads/work.index_document.deleted.v1.json
nnew file mode 100644
iindex 00000000..99219b0a
---- /dev/null
++++ b/contracts/events/payloads/work.index_document.deleted.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/work.index_document.deleted.v1.json",
++ "title": "WorkIndexDocumentDeletedPayload",
++ "description": "work.index_document.deleted v1 (OpenVibe.Work server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a job page (/jobs/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: job, id, revision }, visibility internal, actor service:work.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "job"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^job_[0-9abcdefghjkmnpqrstvwxyz]{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."
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/work.index_document.upserted.v1.json b/contracts/events/payloads/work.index_document.upserted.v1.json
nnew file mode 100644
iindex 00000000..06ab4f9e
---- /dev/null
++++ b/contracts/events/payloads/work.index_document.upserted.v1.json
@@@ -0,0 +1,34 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/work.index_document.upserted.v1.json",
++ "title": "WorkIndexDocumentUpsertedPayload",
++ "description": "work.index_document.upserted v1 (OpenVibe.Work server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a job listing page (type job, /jobs/<id>): the title, company, location, tags, salary text and the short excerpt Work keeps of a listing read from a public board, with the board as provenance (authorship imported). Sent after every ingest run that changed a listing and by the sweep (server/search-index.js); a listing that expired (not seen for the retention window) becomes a tombstone. The sequencer (work_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: source, remote, type, location. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: job, id, revision: <document revision> }, visibility internal, actor service:work.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "work"
++ },
++ "type": {
++ "const": "job"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^job_[0-9abcdefghjkmnpqrstvwxyz]{26}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++}
ddiff --git a/fixtures/help.index_document.deleted/invalid/full-document.json b/fixtures/help.index_document.deleted/invalid/full-document.json
nnew file mode 100644
iindex 00000000..fe7e183d
---- /dev/null
++++ b/fixtures/help.index_document.deleted/invalid/full-document.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "help",
++ "type": "article",
++ "id": "openvibe.live:how-do-i-go-live-from-my-browser",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.help/a/openvibe.live/how-do-i-go-live-from-my-browser",
++ "title": "How do I go live from my browser?",
++ "summary": "Open openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "body": "How do I go live from my browser?\nOpen openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "facets": {
++ "site": "openvibe.live",
++ "kind": "product"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/help.index_document.deleted/invalid/other-type.json b/fixtures/help.index_document.deleted/invalid/other-type.json
nnew file mode 100644
iindex 00000000..733dd54f
---- /dev/null
++++ b/fixtures/help.index_document.deleted/invalid/other-type.json
@@@ -0,0 +1,5 @@
++{
++ "type": "offer",
++ "id": "openvibe.live:how-do-i-go-live-from-my-browser",
++ "revision": 3
++}
ddiff --git a/fixtures/help.index_document.deleted/valid/tombstone.json b/fixtures/help.index_document.deleted/valid/tombstone.json
nnew file mode 100644
iindex 00000000..4abfb477
---- /dev/null
++++ b/fixtures/help.index_document.deleted/valid/tombstone.json
@@@ -0,0 +1,5 @@
++{
++ "type": "article",
++ "id": "openvibe.live:how-do-i-go-live-from-my-browser",
++ "revision": 3
++}
ddiff --git a/fixtures/help.index_document.upserted/invalid/bad-id.json b/fixtures/help.index_document.upserted/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..aaf3ac21
---- /dev/null
++++ b/fixtures/help.index_document.upserted/invalid/bad-id.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "help",
++ "type": "article",
++ "id": "how-do-i-go-live",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.help/a/openvibe.live/how-do-i-go-live-from-my-browser",
++ "title": "How do I go live from my browser?",
++ "summary": "Open openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "body": "How do I go live from my browser?\nOpen openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "facets": {
++ "site": "openvibe.live",
++ "kind": "product"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/help.index_document.upserted/invalid/other-owner.json b/fixtures/help.index_document.upserted/invalid/other-owner.json
nnew file mode 100644
iindex 00000000..73a3553e
---- /dev/null
++++ b/fixtures/help.index_document.upserted/invalid/other-owner.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "deals",
++ "type": "article",
++ "id": "openvibe.live:how-do-i-go-live-from-my-browser",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.help/a/openvibe.live/how-do-i-go-live-from-my-browser",
++ "title": "How do I go live from my browser?",
++ "summary": "Open openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "body": "How do I go live from my browser?\nOpen openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "facets": {
++ "site": "openvibe.live",
++ "kind": "product"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/help.index_document.upserted/invalid/tombstone-as-upsert.json b/fixtures/help.index_document.upserted/invalid/tombstone-as-upsert.json
nnew file mode 100644
iindex 00000000..c6074fcb
---- /dev/null
++++ b/fixtures/help.index_document.upserted/invalid/tombstone-as-upsert.json
@@@ -0,0 +1,7 @@
++{
++ "owner": "help",
++ "type": "article",
++ "id": "openvibe.live:how-do-i-go-live-from-my-browser",
++ "revision": 3,
++ "deleted": true
++}
ddiff --git a/fixtures/help.index_document.upserted/valid/article-noindex.json b/fixtures/help.index_document.upserted/valid/article-noindex.json
nnew file mode 100644
iindex 00000000..64ce4e0a
---- /dev/null
++++ b/fixtures/help.index_document.upserted/valid/article-noindex.json
@@@ -0,0 +1,27 @@
++{
++ "owner": "help",
++ "type": "article",
++ "id": "openvibe.live:how-do-i-go-live-from-my-browser",
++ "revision": 5,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.help/a/openvibe.live/how-do-i-go-live-from-my-browser",
++ "title": "How do I go live from my browser?",
++ "summary": "Open openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "body": "How do I go live from my browser?\nOpen openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "facets": {
++ "site": "openvibe.live",
++ "kind": "product"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "noindex",
++ "reasons": [
++ "owner_decision"
++ ]
++ }
++}
ddiff --git a/fixtures/help.index_document.upserted/valid/article.json b/fixtures/help.index_document.upserted/valid/article.json
nnew file mode 100644
iindex 00000000..fe7e183d
---- /dev/null
++++ b/fixtures/help.index_document.upserted/valid/article.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "help",
++ "type": "article",
++ "id": "openvibe.live:how-do-i-go-live-from-my-browser",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.help/a/openvibe.live/how-do-i-go-live-from-my-browser",
++ "title": "How do I go live from my browser?",
++ "summary": "Open openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "body": "How do I go live from my browser?\nOpen openvibe.live, sign in and press Go live: no OBS and no follower minimum.",
++ "facets": {
++ "site": "openvibe.live",
++ "kind": "product"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/inventory.index_document.deleted/invalid/full-document.json b/fixtures/inventory.index_document.deleted/invalid/full-document.json
nnew file mode 100644
iindex 00000000..e354b0a1
---- /dev/null
++++ b/fixtures/inventory.index_document.deleted/invalid/full-document.json
@@@ -0,0 +1,26 @@
++{
++ "owner": "inventory",
++ "type": "definition",
++ "id": "itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.inventory/definitions/itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Fire Name",
++ "summary": "A name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "body": "Fire Name\nA name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "facets": {
++ "kind": "live.name_effect",
++ "rarity": "rare",
++ "issuer": "service:live"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/inventory.index_document.deleted/invalid/other-type.json b/fixtures/inventory.index_document.deleted/invalid/other-type.json
nnew file mode 100644
iindex 00000000..a076f8fe
---- /dev/null
++++ b/fixtures/inventory.index_document.deleted/invalid/other-type.json
@@@ -0,0 +1,5 @@
++{
++ "type": "offer",
++ "id": "itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 3
++}
ddiff --git a/fixtures/inventory.index_document.deleted/valid/tombstone.json b/fixtures/inventory.index_document.deleted/valid/tombstone.json
nnew file mode 100644
iindex 00000000..826af11c
---- /dev/null
++++ b/fixtures/inventory.index_document.deleted/valid/tombstone.json
@@@ -0,0 +1,5 @@
++{
++ "type": "definition",
++ "id": "itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 3
++}
ddiff --git a/fixtures/inventory.index_document.upserted/invalid/bad-id.json b/fixtures/inventory.index_document.upserted/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..cff0ac0a
---- /dev/null
++++ b/fixtures/inventory.index_document.upserted/invalid/bad-id.json
@@@ -0,0 +1,26 @@
++{
++ "owner": "inventory",
++ "type": "definition",
++ "id": "fx_fire",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.inventory/definitions/itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Fire Name",
++ "summary": "A name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "body": "Fire Name\nA name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "facets": {
++ "kind": "live.name_effect",
++ "rarity": "rare",
++ "issuer": "service:live"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/inventory.index_document.upserted/invalid/other-owner.json b/fixtures/inventory.index_document.upserted/invalid/other-owner.json
nnew file mode 100644
iindex 00000000..7fcadcb0
---- /dev/null
++++ b/fixtures/inventory.index_document.upserted/invalid/other-owner.json
@@@ -0,0 +1,26 @@
++{
++ "owner": "deals",
++ "type": "definition",
++ "id": "itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.inventory/definitions/itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Fire Name",
++ "summary": "A name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "body": "Fire Name\nA name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "facets": {
++ "kind": "live.name_effect",
++ "rarity": "rare",
++ "issuer": "service:live"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/inventory.index_document.upserted/invalid/tombstone-as-upsert.json b/fixtures/inventory.index_document.upserted/invalid/tombstone-as-upsert.json
nnew file mode 100644
iindex 00000000..a8b9d271
---- /dev/null
++++ b/fixtures/inventory.index_document.upserted/invalid/tombstone-as-upsert.json
@@@ -0,0 +1,7 @@
++{
++ "owner": "inventory",
++ "type": "definition",
++ "id": "itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 3,
++ "deleted": true
++}
ddiff --git a/fixtures/inventory.index_document.upserted/valid/definition-noindex.json b/fixtures/inventory.index_document.upserted/valid/definition-noindex.json
nnew file mode 100644
iindex 00000000..35a4e707
---- /dev/null
++++ b/fixtures/inventory.index_document.upserted/valid/definition-noindex.json
@@@ -0,0 +1,28 @@
++{
++ "owner": "inventory",
++ "type": "definition",
++ "id": "itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 5,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.inventory/definitions/itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Fire Name",
++ "summary": "A name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "body": "Fire Name\nA name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "facets": {
++ "kind": "live.name_effect",
++ "rarity": "rare",
++ "issuer": "service:live"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "noindex",
++ "reasons": [
++ "owner_decision"
++ ]
++ }
++}
ddiff --git a/fixtures/inventory.index_document.upserted/valid/definition.json b/fixtures/inventory.index_document.upserted/valid/definition.json
nnew file mode 100644
iindex 00000000..e354b0a1
---- /dev/null
++++ b/fixtures/inventory.index_document.upserted/valid/definition.json
@@@ -0,0 +1,26 @@
++{
++ "owner": "inventory",
++ "type": "definition",
++ "id": "itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.inventory/definitions/itd_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Fire Name",
++ "summary": "A name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "body": "Fire Name\nA name effect for chat: your name burns. Rare, issued by OpenVibe.Live.",
++ "facets": {
++ "kind": "live.name_effect",
++ "rarity": "rare",
++ "issuer": "service:live"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/quest.index_document.deleted/invalid/full-document.json b/fixtures/quest.index_document.deleted/invalid/full-document.json
nnew file mode 100644
iindex 00000000..1667b33f
---- /dev/null
++++ b/fixtures/quest.index_document.deleted/invalid/full-document.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "quest",
++ "type": "quest",
++ "id": "go-live",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.quest/quests/go-live",
++ "title": "Go live once",
++ "summary": "Start your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "body": "Go live once\nStart your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "facets": {
++ "service": "live",
++ "badge": "first-broadcast"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/quest.index_document.deleted/invalid/other-type.json b/fixtures/quest.index_document.deleted/invalid/other-type.json
nnew file mode 100644
iindex 00000000..20813fe8
---- /dev/null
++++ b/fixtures/quest.index_document.deleted/invalid/other-type.json
@@@ -0,0 +1,5 @@
++{
++ "type": "offer",
++ "id": "go-live",
++ "revision": 3
++}
ddiff --git a/fixtures/quest.index_document.deleted/valid/tombstone.json b/fixtures/quest.index_document.deleted/valid/tombstone.json
nnew file mode 100644
iindex 00000000..8cef8822
---- /dev/null
++++ b/fixtures/quest.index_document.deleted/valid/tombstone.json
@@@ -0,0 +1,5 @@
++{
++ "type": "quest",
++ "id": "go-live",
++ "revision": 3
++}
ddiff --git a/fixtures/quest.index_document.upserted/invalid/bad-id.json b/fixtures/quest.index_document.upserted/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..9a327231
---- /dev/null
++++ b/fixtures/quest.index_document.upserted/invalid/bad-id.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "quest",
++ "type": "quest",
++ "id": "Go_Live!",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.quest/quests/go-live",
++ "title": "Go live once",
++ "summary": "Start your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "body": "Go live once\nStart your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "facets": {
++ "service": "live",
++ "badge": "first-broadcast"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/quest.index_document.upserted/invalid/other-owner.json b/fixtures/quest.index_document.upserted/invalid/other-owner.json
nnew file mode 100644
iindex 00000000..076656e2
---- /dev/null
++++ b/fixtures/quest.index_document.upserted/invalid/other-owner.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "deals",
++ "type": "quest",
++ "id": "go-live",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.quest/quests/go-live",
++ "title": "Go live once",
++ "summary": "Start your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "body": "Go live once\nStart your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "facets": {
++ "service": "live",
++ "badge": "first-broadcast"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/quest.index_document.upserted/invalid/tombstone-as-upsert.json b/fixtures/quest.index_document.upserted/invalid/tombstone-as-upsert.json
nnew file mode 100644
iindex 00000000..b8e9d96c
---- /dev/null
++++ b/fixtures/quest.index_document.upserted/invalid/tombstone-as-upsert.json
@@@ -0,0 +1,7 @@
++{
++ "owner": "quest",
++ "type": "quest",
++ "id": "go-live",
++ "revision": 3,
++ "deleted": true
++}
ddiff --git a/fixtures/quest.index_document.upserted/valid/quest-noindex.json b/fixtures/quest.index_document.upserted/valid/quest-noindex.json
nnew file mode 100644
iindex 00000000..6617f546
---- /dev/null
++++ b/fixtures/quest.index_document.upserted/valid/quest-noindex.json
@@@ -0,0 +1,27 @@
++{
++ "owner": "quest",
++ "type": "quest",
++ "id": "go-live",
++ "revision": 5,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.quest/quests/go-live",
++ "title": "Go live once",
++ "summary": "Start your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "body": "Go live once\nStart your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "facets": {
++ "service": "live",
++ "badge": "first-broadcast"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "noindex",
++ "reasons": [
++ "owner_decision"
++ ]
++ }
++}
ddiff --git a/fixtures/quest.index_document.upserted/valid/quest.json b/fixtures/quest.index_document.upserted/valid/quest.json
nnew file mode 100644
iindex 00000000..1667b33f
---- /dev/null
++++ b/fixtures/quest.index_document.upserted/valid/quest.json
@@@ -0,0 +1,25 @@
++{
++ "owner": "quest",
++ "type": "quest",
++ "id": "go-live",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.quest/quests/go-live",
++ "title": "Go live once",
++ "summary": "Start your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "body": "Go live once\nStart your first stream on OpenVibe.Live and earn the First Broadcast badge.",
++ "facets": {
++ "service": "live",
++ "badge": "first-broadcast"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/rent.index_document.deleted/invalid/full-document.json b/fixtures/rent.index_document.deleted/invalid/full-document.json
nnew file mode 100644
iindex 00000000..064d080e
---- /dev/null
++++ b/fixtures/rent.index_document.deleted/invalid/full-document.json
@@@ -0,0 +1,29 @@
++{
++ "owner": "rent",
++ "type": "listing",
++ "id": "rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.rent/listings/rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Bright room near the park",
++ "summary": "650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "body": "Bright room near the park\n650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "facets": {
++ "kind": "room",
++ "country": "GB",
++ "city": "Leeds",
++ "currency": "GBP",
++ "period": "month",
++ "bedrooms": "1"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/rent.index_document.deleted/invalid/other-type.json b/fixtures/rent.index_document.deleted/invalid/other-type.json
nnew file mode 100644
iindex 00000000..97c8c98b
---- /dev/null
++++ b/fixtures/rent.index_document.deleted/invalid/other-type.json
@@@ -0,0 +1,5 @@
++{
++ "type": "offer",
++ "id": "rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 3
++}
ddiff --git a/fixtures/rent.index_document.deleted/valid/tombstone.json b/fixtures/rent.index_document.deleted/valid/tombstone.json
nnew file mode 100644
iindex 00000000..0e5e28b9
---- /dev/null
++++ b/fixtures/rent.index_document.deleted/valid/tombstone.json
@@@ -0,0 +1,5 @@
++{
++ "type": "listing",
++ "id": "rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 3
++}
ddiff --git a/fixtures/rent.index_document.upserted/invalid/bad-id.json b/fixtures/rent.index_document.upserted/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..c40a2b3f
---- /dev/null
++++ b/fixtures/rent.index_document.upserted/invalid/bad-id.json
@@@ -0,0 +1,29 @@
++{
++ "owner": "rent",
++ "type": "listing",
++ "id": "lst_1",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.rent/listings/rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Bright room near the park",
++ "summary": "650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "body": "Bright room near the park\n650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "facets": {
++ "kind": "room",
++ "country": "GB",
++ "city": "Leeds",
++ "currency": "GBP",
++ "period": "month",
++ "bedrooms": "1"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/rent.index_document.upserted/invalid/other-owner.json b/fixtures/rent.index_document.upserted/invalid/other-owner.json
nnew file mode 100644
iindex 00000000..81160853
---- /dev/null
++++ b/fixtures/rent.index_document.upserted/invalid/other-owner.json
@@@ -0,0 +1,29 @@
++{
++ "owner": "deals",
++ "type": "listing",
++ "id": "rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.rent/listings/rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Bright room near the park",
++ "summary": "650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "body": "Bright room near the park\n650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "facets": {
++ "kind": "room",
++ "country": "GB",
++ "city": "Leeds",
++ "currency": "GBP",
++ "period": "month",
++ "bedrooms": "1"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/rent.index_document.upserted/invalid/tombstone-as-upsert.json b/fixtures/rent.index_document.upserted/invalid/tombstone-as-upsert.json
nnew file mode 100644
iindex 00000000..8687ce69
---- /dev/null
++++ b/fixtures/rent.index_document.upserted/invalid/tombstone-as-upsert.json
@@@ -0,0 +1,7 @@
++{
++ "owner": "rent",
++ "type": "listing",
++ "id": "rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 3,
++ "deleted": true
++}
ddiff --git a/fixtures/rent.index_document.upserted/valid/listing-noindex.json b/fixtures/rent.index_document.upserted/valid/listing-noindex.json
nnew file mode 100644
iindex 00000000..d6f71791
---- /dev/null
++++ b/fixtures/rent.index_document.upserted/valid/listing-noindex.json
@@@ -0,0 +1,31 @@
++{
++ "owner": "rent",
++ "type": "listing",
++ "id": "rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 5,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.rent/listings/rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Bright room near the park",
++ "summary": "650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "body": "Bright room near the park\n650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "facets": {
++ "kind": "room",
++ "country": "GB",
++ "city": "Leeds",
++ "currency": "GBP",
++ "period": "month",
++ "bedrooms": "1"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "noindex",
++ "reasons": [
++ "owner_decision"
++ ]
++ }
++}
ddiff --git a/fixtures/rent.index_document.upserted/valid/listing.json b/fixtures/rent.index_document.upserted/valid/listing.json
nnew file mode 100644
iindex 00000000..064d080e
---- /dev/null
++++ b/fixtures/rent.index_document.upserted/valid/listing.json
@@@ -0,0 +1,29 @@
++{
++ "owner": "rent",
++ "type": "listing",
++ "id": "rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.rent/listings/rnt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "title": "Bright room near the park",
++ "summary": "650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "body": "Bright room near the park\n650 GBP a month · Leeds, Headingley · available from 2026-11-01",
++ "facets": {
++ "kind": "room",
++ "country": "GB",
++ "city": "Leeds",
++ "currency": "GBP",
++ "period": "month",
++ "bedrooms": "1"
++ },
++ "authorship": "human",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/work.index_document.deleted/invalid/full-document.json b/fixtures/work.index_document.deleted/invalid/full-document.json
nnew file mode 100644
iindex 00000000..1c1e20ea
---- /dev/null
++++ b/fixtures/work.index_document.deleted/invalid/full-document.json
@@@ -0,0 +1,27 @@
++{
++ "owner": "work",
++ "type": "job",
++ "id": "job_0123456789abcdefghjkmnpqrs",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.work/jobs/job_0123456789abcdefghjkmnpqrs",
++ "title": "Senior backend engineer — Example GmbH",
++ "summary": "Remote (EU) · full time · Node.js, PostgreSQL",
++ "body": "Senior backend engineer — Example GmbH\nRemote (EU) · full time · Node.js, PostgreSQL",
++ "facets": {
++ "source": "arbeitnow",
++ "remote": "true",
++ "type": "full time",
++ "location": "Berlin"
++ },
++ "authorship": "imported",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/work.index_document.deleted/invalid/other-type.json b/fixtures/work.index_document.deleted/invalid/other-type.json
nnew file mode 100644
iindex 00000000..f82ee497
---- /dev/null
++++ b/fixtures/work.index_document.deleted/invalid/other-type.json
@@@ -0,0 +1,5 @@
++{
++ "type": "offer",
++ "id": "job_0123456789abcdefghjkmnpqrs",
++ "revision": 3
++}
ddiff --git a/fixtures/work.index_document.deleted/valid/tombstone.json b/fixtures/work.index_document.deleted/valid/tombstone.json
nnew file mode 100644
iindex 00000000..f660091a
---- /dev/null
++++ b/fixtures/work.index_document.deleted/valid/tombstone.json
@@@ -0,0 +1,5 @@
++{
++ "type": "job",
++ "id": "job_0123456789abcdefghjkmnpqrs",
++ "revision": 3
++}
ddiff --git a/fixtures/work.index_document.upserted/invalid/bad-id.json b/fixtures/work.index_document.upserted/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..8f466e9e
---- /dev/null
++++ b/fixtures/work.index_document.upserted/invalid/bad-id.json
@@@ -0,0 +1,27 @@
++{
++ "owner": "work",
++ "type": "job",
++ "id": "job_ABC",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.work/jobs/job_0123456789abcdefghjkmnpqrs",
++ "title": "Senior backend engineer — Example GmbH",
++ "summary": "Remote (EU) · full time · Node.js, PostgreSQL",
++ "body": "Senior backend engineer — Example GmbH\nRemote (EU) · full time · Node.js, PostgreSQL",
++ "facets": {
++ "source": "arbeitnow",
++ "remote": "true",
++ "type": "full time",
++ "location": "Berlin"
++ },
++ "authorship": "imported",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/work.index_document.upserted/invalid/other-owner.json b/fixtures/work.index_document.upserted/invalid/other-owner.json
nnew file mode 100644
iindex 00000000..55cc1cbb
---- /dev/null
++++ b/fixtures/work.index_document.upserted/invalid/other-owner.json
@@@ -0,0 +1,27 @@
++{
++ "owner": "deals",
++ "type": "job",
++ "id": "job_0123456789abcdefghjkmnpqrs",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.work/jobs/job_0123456789abcdefghjkmnpqrs",
++ "title": "Senior backend engineer — Example GmbH",
++ "summary": "Remote (EU) · full time · Node.js, PostgreSQL",
++ "body": "Senior backend engineer — Example GmbH\nRemote (EU) · full time · Node.js, PostgreSQL",
++ "facets": {
++ "source": "arbeitnow",
++ "remote": "true",
++ "type": "full time",
++ "location": "Berlin"
++ },
++ "authorship": "imported",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/fixtures/work.index_document.upserted/invalid/tombstone-as-upsert.json b/fixtures/work.index_document.upserted/invalid/tombstone-as-upsert.json
nnew file mode 100644
iindex 00000000..f802b7be
---- /dev/null
++++ b/fixtures/work.index_document.upserted/invalid/tombstone-as-upsert.json
@@@ -0,0 +1,7 @@
++{
++ "owner": "work",
++ "type": "job",
++ "id": "job_0123456789abcdefghjkmnpqrs",
++ "revision": 3,
++ "deleted": true
++}
ddiff --git a/fixtures/work.index_document.upserted/valid/job-noindex.json b/fixtures/work.index_document.upserted/valid/job-noindex.json
nnew file mode 100644
iindex 00000000..24944f1f
---- /dev/null
++++ b/fixtures/work.index_document.upserted/valid/job-noindex.json
@@@ -0,0 +1,29 @@
++{
++ "owner": "work",
++ "type": "job",
++ "id": "job_0123456789abcdefghjkmnpqrs",
++ "revision": 5,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.work/jobs/job_0123456789abcdefghjkmnpqrs",
++ "title": "Senior backend engineer — Example GmbH",
++ "summary": "Remote (EU) · full time · Node.js, PostgreSQL",
++ "body": "Senior backend engineer — Example GmbH\nRemote (EU) · full time · Node.js, PostgreSQL",
++ "facets": {
++ "source": "arbeitnow",
++ "remote": "true",
++ "type": "full time",
++ "location": "Berlin"
++ },
++ "authorship": "imported",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "noindex",
++ "reasons": [
++ "owner_decision"
++ ]
++ }
++}
ddiff --git a/fixtures/work.index_document.upserted/valid/job.json b/fixtures/work.index_document.upserted/valid/job.json
nnew file mode 100644
iindex 00000000..1c1e20ea
---- /dev/null
++++ b/fixtures/work.index_document.upserted/valid/job.json
@@@ -0,0 +1,27 @@
++{
++ "owner": "work",
++ "type": "job",
++ "id": "job_0123456789abcdefghjkmnpqrs",
++ "revision": 2,
++ "deleted": false,
++ "visibility": "public",
++ "canonical_url": "https://openvibe.work/jobs/job_0123456789abcdefghjkmnpqrs",
++ "title": "Senior backend engineer — Example GmbH",
++ "summary": "Remote (EU) · full time · Node.js, PostgreSQL",
++ "body": "Senior backend engineer — Example GmbH\nRemote (EU) · full time · Node.js, PostgreSQL",
++ "facets": {
++ "source": "arbeitnow",
++ "remote": "true",
++ "type": "full time",
++ "location": "Berlin"
++ },
++ "authorship": "imported",
++ "provenance": [],
++ "publication_state": "published",
++ "published_at": "2026-10-09T12:00:00.000Z",
++ "updated_at": "2026-10-10T08:00:00.000Z",
++ "indexability": {
++ "decision": "index",
++ "reasons": []
++ }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex ae947820..0f840153 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -74820,6 +74820,366 @@
}
}
}
++ },
++ "work.index_document.upserted": {
++ "version": "1.0.0",
++ "owner": "work",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/work.index_document.upserted.v1.json",
++ "title": "WorkIndexDocumentUpsertedPayload",
++ "description": "work.index_document.upserted v1 (OpenVibe.Work server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a job listing page (type job, /jobs/<id>): the title, company, location, tags, salary text and the short excerpt Work keeps of a listing read from a public board, with the board as provenance (authorship imported). Sent after every ingest run that changed a listing and by the sweep (server/search-index.js); a listing that expired (not seen for the retention window) becomes a tombstone. The sequencer (work_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: source, remote, type, location. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: job, id, revision: <document revision> }, visibility internal, actor service:work.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "work"
++ },
++ "type": {
++ "const": "job"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^job_[0-9abcdefghjkmnpqrstvwxyz]{26}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++ }
++ },
++ "work.index_document.deleted": {
++ "version": "1.0.0",
++ "owner": "work",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/work.index_document.deleted.v1.json",
++ "title": "WorkIndexDocumentDeletedPayload",
++ "description": "work.index_document.deleted v1 (OpenVibe.Work server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a job page (/jobs/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: job, id, revision }, visibility internal, actor service:work.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "job"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^job_[0-9abcdefghjkmnpqrstvwxyz]{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."
++ }
++ }
++ }
++ },
++ "rent.index_document.upserted": {
++ "version": "1.0.0",
++ "owner": "rent",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/rent.index_document.upserted.v1.json",
++ "title": "RentIndexDocumentUpsertedPayload",
++ "description": "rent.index_document.upserted v1 (OpenVibe.Rent server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a rental listing page (type listing, /listings/<id>): title, description, price and period, city, region, country and neighbourhood (never a street address or the contact link). Sent in the transaction that created, edited, renewed, hid or removed the listing, and by the sweep (server/search-index.js); a listing that is hidden, removed or past its expiry becomes a tombstone. The sequencer (rent_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: kind, country, city, currency, period, bedrooms. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: listing, id, revision: <document revision> }, visibility internal, actor service:rent.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "rent"
++ },
++ "type": {
++ "const": "listing"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^rnt_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++ }
++ },
++ "rent.index_document.deleted": {
++ "version": "1.0.0",
++ "owner": "rent",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/rent.index_document.deleted.v1.json",
++ "title": "RentIndexDocumentDeletedPayload",
++ "description": "rent.index_document.deleted v1 (OpenVibe.Rent server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a listing page (/listings/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: listing, id, revision }, visibility internal, actor service:rent.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "listing"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^rnt_[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."
++ }
++ }
++ }
++ },
++ "help.index_document.upserted": {
++ "version": "1.0.0",
++ "owner": "help",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/help.index_document.upserted.v1.json",
++ "title": "HelpIndexDocumentUpsertedPayload",
++ "description": "help.index_document.upserted v1 (OpenVibe.Help server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a help article page (type article, /a/<domain>/<slug>, id <domain>:<slug>): one question from a product manifest's FAQ with its answer, quoted verbatim from OpenVibe.Contracts. Sent by the sweep at boot and on a timer (server/search-index.js) whenever the catalog Help was built from changed; an article whose FAQ entry left the catalog becomes a tombstone. The sequencer (help_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: site (the product domain), kind. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: article, id, revision: <document revision> }, visibility internal, actor service:help.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "help"
++ },
++ "type": {
++ "const": "article"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9.-]{0,79}:[a-z0-9][a-z0-9-]{0,71}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++ }
++ },
++ "help.index_document.deleted": {
++ "version": "1.0.0",
++ "owner": "help",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/help.index_document.deleted.v1.json",
++ "title": "HelpIndexDocumentDeletedPayload",
++ "description": "help.index_document.deleted v1 (OpenVibe.Help server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a article page (/a/<domain>/<slug>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: article, id, revision }, visibility internal, actor service:help.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "article"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9.-]{0,79}:[a-z0-9][a-z0-9-]{0,71}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ }
++ }
++ }
++ },
++ "inventory.index_document.upserted": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.index_document.upserted.v1.json",
++ "title": "InventoryIndexDocumentUpsertedPayload",
++ "description": "inventory.index_document.upserted v1 (OpenVibe.Inventory server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of an item definition page (type definition, /definitions/<id>): name, description, kind, rarity, issuer and supply. Only published definitions are documents; a draft, one in review or a retired one is a tombstone. Sent in the transaction that published or retired it, and by the sweep (server/search-index.js). The sequencer (inventory_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: kind, rarity, issuer. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: definition, id, revision: <document revision> }, visibility internal, actor service:inventory.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "inventory"
++ },
++ "type": {
++ "const": "definition"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++ }
++ },
++ "inventory.index_document.deleted": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.index_document.deleted.v1.json",
++ "title": "InventoryIndexDocumentDeletedPayload",
++ "description": "inventory.index_document.deleted v1 (OpenVibe.Inventory server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a definition page (/definitions/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: definition, id, revision }, visibility internal, actor service:inventory.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "definition"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^itd_[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."
++ }
++ }
++ }
++ },
++ "quest.index_document.upserted": {
++ "version": "1.0.0",
++ "owner": "quest",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/quest.index_document.upserted.v1.json",
++ "title": "QuestIndexDocumentUpsertedPayload",
++ "description": "quest.index_document.upserted v1 (OpenVibe.Quest server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a quest page (type quest, /quests/<id>): its name, what to do and where, and the badge (and item) it rewards. Quests are Quest's own catalog (server/quests/catalog.js), so they are sent by the sweep at boot (server/search-index.js); a quest that left the catalog becomes a tombstone. The sequencer (quest_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: service (where the quest is done), badge. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: quest, id, revision: <document revision> }, visibility internal, actor service:quest.",
++ "allOf": [
++ {
++ "$ref": "../../search/index-document.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner",
++ "type",
++ "deleted"
++ ],
++ "properties": {
++ "owner": {
++ "const": "quest"
++ },
++ "type": {
++ "const": "quest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
++ },
++ "deleted": {
++ "const": false
++ }
++ }
++ }
++ ]
++ }
++ },
++ "quest.index_document.deleted": {
++ "version": "1.0.0",
++ "owner": "quest",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/quest.index_document.deleted.v1.json",
++ "title": "QuestIndexDocumentDeletedPayload",
++ "description": "quest.index_document.deleted v1 (OpenVibe.Quest server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a quest page (/quests/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: quest, id, revision }, visibility internal, actor service:quest.",
++ "type": "object",
++ "required": [
++ "type",
++ "id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "quest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Index revision of the tombstone; wins over any document at the same or an older revision."
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex aed27eb6..b4787935 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex e7b40c63..65c14428 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 7d6f77ab..70710aa1 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 3f4b083f..ebce2eb3 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex da9bf24a..3e663224 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 7e7ecc3a..4cceeb49 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex 796f699e..db2e41b4 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 5b561be0..0ed8e52b 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex 85c5f8a7..dac5f760 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex d2370ef8..9a03d9af 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/food.json b/generated/openapi/food.json
iindex 1cd08b59..ca9fd95f 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 921e9561..441e168b 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex 5bae6b61..2fd0a45d 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex cf97e762..91fe5545 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 751c4543..f08e3d6e 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex 5c311685..d28c4e97 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex cb87dd00..4f049d51 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex c58217c1..7c31482f 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex af2a13e2..ca15567e 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRestream API",
-- "version": "0.127.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 73550753..2afae470 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex 2f135229..4ff5fd0a 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex d3514ef7..9503f7ae 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex b04494b7..d14265de 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 4d48c286..45659949 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 240b46fc..75878086 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex a6d3e65e..d1fa1594 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex a8942db1..36db29cc 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 20713826..b7593e4c 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex 6c974674..344ea906 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex 50123c2e..08f2d0a3 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.127.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.127.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.128.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.128.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 0ac6de48..bdef0259 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -60863,3 +60863,123 @@ export interface InventoryDefinitionPublishedPayload {
rarity: "common" | "uncommon" | "rare" | "epic" | "legendary";
issuer: string;
}
++
++/** [email protected] (owner: work) */
++/**
++ * work.index_document.upserted v1 (OpenVibe.Work server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a job listing page (type job, /jobs/<id>): the title, company, location, tags, salary text and the short excerpt Work keeps of a listing read from a public board, with the board as provenance (authorship imported). Sent after every ingest run that changed a listing and by the sweep (server/search-index.js); a listing that expired (not seen for the retention window) becomes a tombstone. The sequencer (work_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: source, remote, type, location. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: job, id, revision: <document revision> }, visibility internal, actor service:work.
++ */
++export type WorkIndexDocumentUpsertedPayload = IndexDocument & {
++ owner: "work";
++ type: "job";
++ id?: string;
++ deleted: false;
++};
++
++/** [email protected] (owner: work) */
++/**
++ * work.index_document.deleted v1 (OpenVibe.Work server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a job page (/jobs/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: job, id, revision }, visibility internal, actor service:work.
++ */
++export interface WorkIndexDocumentDeletedPayload {
++ type: "job";
++ id: string;
++ /**
++ * Index revision of the tombstone; wins over any document at the same or an older revision.
++ */
++ revision: number;
++}
++
++/** [email protected] (owner: rent) */
++/**
++ * rent.index_document.upserted v1 (OpenVibe.Rent server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a rental listing page (type listing, /listings/<id>): title, description, price and period, city, region, country and neighbourhood (never a street address or the contact link). Sent in the transaction that created, edited, renewed, hid or removed the listing, and by the sweep (server/search-index.js); a listing that is hidden, removed or past its expiry becomes a tombstone. The sequencer (rent_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: kind, country, city, currency, period, bedrooms. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: listing, id, revision: <document revision> }, visibility internal, actor service:rent.
++ */
++export type RentIndexDocumentUpsertedPayload = IndexDocument & {
++ owner: "rent";
++ type: "listing";
++ id?: string;
++ deleted: false;
++};
++
++/** [email protected] (owner: rent) */
++/**
++ * rent.index_document.deleted v1 (OpenVibe.Rent server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a listing page (/listings/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: listing, id, revision }, visibility internal, actor service:rent.
++ */
++export interface RentIndexDocumentDeletedPayload {
++ type: "listing";
++ id: string;
++ /**
++ * Index revision of the tombstone; wins over any document at the same or an older revision.
++ */
++ revision: number;
++}
++
++/** [email protected] (owner: help) */
++/**
++ * help.index_document.upserted v1 (OpenVibe.Help server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a help article page (type article, /a/<domain>/<slug>, id <domain>:<slug>): one question from a product manifest's FAQ with its answer, quoted verbatim from OpenVibe.Contracts. Sent by the sweep at boot and on a timer (server/search-index.js) whenever the catalog Help was built from changed; an article whose FAQ entry left the catalog becomes a tombstone. The sequencer (help_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: site (the product domain), kind. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: article, id, revision: <document revision> }, visibility internal, actor service:help.
++ */
++export type HelpIndexDocumentUpsertedPayload = IndexDocument & {
++ owner: "help";
++ type: "article";
++ id?: string;
++ deleted: false;
++};
++
++/** [email protected] (owner: help) */
++/**
++ * help.index_document.deleted v1 (OpenVibe.Help server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a article page (/a/<domain>/<slug>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: article, id, revision }, visibility internal, actor service:help.
++ */
++export interface HelpIndexDocumentDeletedPayload {
++ type: "article";
++ id: string;
++ /**
++ * Index revision of the tombstone; wins over any document at the same or an older revision.
++ */
++ revision: number;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.index_document.upserted v1 (OpenVibe.Inventory server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of an item definition page (type definition, /definitions/<id>): name, description, kind, rarity, issuer and supply. Only published definitions are documents; a draft, one in review or a retired one is a tombstone. Sent in the transaction that published or retired it, and by the sweep (server/search-index.js). The sequencer (inventory_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: kind, rarity, issuer. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: definition, id, revision: <document revision> }, visibility internal, actor service:inventory.
++ */
++export type InventoryIndexDocumentUpsertedPayload = IndexDocument & {
++ owner: "inventory";
++ type: "definition";
++ id?: string;
++ deleted: false;
++};
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.index_document.deleted v1 (OpenVibe.Inventory server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a definition page (/definitions/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: definition, id, revision }, visibility internal, actor service:inventory.
++ */
++export interface InventoryIndexDocumentDeletedPayload {
++ type: "definition";
++ id: string;
++ /**
++ * Index revision of the tombstone; wins over any document at the same or an older revision.
++ */
++ revision: number;
++}
++
++/** [email protected] (owner: quest) */
++/**
++ * quest.index_document.upserted v1 (OpenVibe.Quest server/search-index.js, via openvibe-publishing/search-feed). The OpenVibe.Search document of a quest page (type quest, /quests/<id>): its name, what to do and where, and the badge (and item) it rewards. Quests are Quest's own catalog (server/quests/catalog.js), so they are sent by the sweep at boot (server/search-index.js); a quest that left the catalog becomes a tombstone. The sequencer (quest_index_revisions) gives an unchanged document its old revision and nothing is sent. Always visibility public and publication_state published; a page its own robots mark noindex stays in Search as a noindex document. Facets: service (where the quest is done), badge. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: quest, id, revision: <document revision> }, visibility internal, actor service:quest.
++ */
++export type QuestIndexDocumentUpsertedPayload = IndexDocument & {
++ owner: "quest";
++ type: "quest";
++ id?: string;
++ deleted: false;
++};
++
++/** [email protected] (owner: quest) */
++/**
++ * quest.index_document.deleted v1 (OpenVibe.Quest server/search-index.js, via openvibe-publishing/search-feed). A Search tombstone for a quest page (/quests/<id>) that is no longer listed or whose row is gone. Only sent for a page Search was sent before, and only when the tombstone's revision moved. Consumed by OpenVibe.Search ('*.index_document.*'). Envelope: subject { type: quest, id, revision }, visibility internal, actor service:quest.
++ */
++export interface QuestIndexDocumentDeletedPayload {
++ type: "quest";
++ id: string;
++ /**
++ * Index revision of the tombstone; wins over any document at the same or an older revision.
++ */
++ revision: number;
++}
ddiff --git a/manifests/services/help.json b/manifests/services/help.json
iindex 9a8e78c0..ee289d08 100644
---- a/manifests/services/help.json
++++ b/manifests/services/help.json
@@@ -16,7 +16,10 @@
"publicSite": "service"
},
"capabilities": [],
-- "eventsProduced": [],
++ "eventsProduced": [
++ "help.index_document.upserted",
++ "help.index_document.deleted"
++ ],
"eventsConsumed": [
"network.account.export_requested",
"network.account.deleted"
ddiff --git a/manifests/services/inventory.json b/manifests/services/inventory.json
iindex 4122dd90..6ebb83a6 100644
---- a/manifests/services/inventory.json
++++ b/manifests/services/inventory.json
@@@ -30,7 +30,9 @@
"inventory.item.revoked",
"inventory.item.equipped",
"inventory.item.unequipped",
-- "inventory.definition.published"
++ "inventory.definition.published",
++ "inventory.index_document.upserted",
++ "inventory.index_document.deleted"
],
"eventsConsumed": [
"network.account.export_requested",
ddiff --git a/manifests/services/quest.json b/manifests/services/quest.json
iindex 2025d8df..8119a1dd 100644
---- a/manifests/services/quest.json
++++ b/manifests/services/quest.json
@@@ -16,7 +16,10 @@
"publicSite": "service"
},
"capabilities": [],
-- "eventsProduced": [],
++ "eventsProduced": [
++ "quest.index_document.upserted",
++ "quest.index_document.deleted"
++ ],
"eventsConsumed": [
"chat.room.message.created",
"community.comment.created",
ddiff --git a/manifests/services/rent.json b/manifests/services/rent.json
iindex f7dfe6c6..879b4500 100644
---- a/manifests/services/rent.json
++++ b/manifests/services/rent.json
@@@ -16,7 +16,10 @@
"publicSite": "service"
},
"capabilities": [],
-- "eventsProduced": [],
++ "eventsProduced": [
++ "rent.index_document.upserted",
++ "rent.index_document.deleted"
++ ],
"eventsConsumed": [
"network.account.export_requested",
"network.account.deleted"
ddiff --git a/manifests/services/work.json b/manifests/services/work.json
iindex 4363daf8..8e0d4311 100644
---- a/manifests/services/work.json
++++ b/manifests/services/work.json
@@@ -16,7 +16,10 @@
"publicSite": "service"
},
"capabilities": [],
-- "eventsProduced": [],
++ "eventsProduced": [
++ "work.index_document.upserted",
++ "work.index_document.deleted"
++ ],
"eventsConsumed": [
"network.account.export_requested",
"network.account.deleted"
ddiff --git a/package.json b/package.json
iindex 774ccec9..4e6438ca 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.127.0",
++ "version": "0.128.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",